org.jitsi.meet.test.web.WebParticipant.java Source code

Java tutorial

Introduction

Here is the source code for org.jitsi.meet.test.web.WebParticipant.java

Source

/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jitsi.meet.test.web;

import org.jitsi.meet.test.base.*;
import org.jitsi.meet.test.base.stats.*;
import org.jitsi.meet.test.pageobjects.web.*;
import org.jitsi.meet.test.util.*;
import org.jitsi.meet.test.web.stats.*;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.*;
import org.openqa.selenium.remote.*;
import org.openqa.selenium.support.ui.*;

import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;

/**
 * The web specific participant implementation.
 */
public class WebParticipant extends Participant<WebDriver> implements JavascriptExecutor {
    /**
     * Default config for Web participants.
     */
    private static final String DEFAULT_CONFIG = "config.requireDisplayName=false" + "&config.debug=true"
            + "&config.testing.testMode=true" + "&config.disableAEC=true" + "&config.disableNS=true"
            + "&config.callStatsID=false" + "&config.alwaysVisibleToolbar=true" + "&config.p2p.enabled=false"
            + "&config.p2p.useStunTurn=false" + "&config.gatherStats=true" + "&config.disable1On1Mode=true";

    /**
     * The javascript code which returns {@code true} if we are joined in
     * the muc.
     */
    private static final String IS_MUC_JOINED = "return APP.conference.isJoined();";

    /**
     * The javascript code which returns {@code true} if the ICE connection
     * is in state 'connected'.
     */
    public static final String ICE_CONNECTED_CHECK_SCRIPT = "return APP.conference.getConnectionState() === 'connected';";

    private ChatPanel chatPanel;
    private DialInNumbersPage dialInNumbersPage;
    private InfoDialog infoDialog;
    private LargeVideo largeVideo;
    private SettingsDialog settingsDialog;
    private Toolbar toolbar;
    private WebFilmstrip filmstrip;

    /**
     * Constructs a Participant.
     *
     * @param name    the name.
     * @param driver  its driver instance.
     * @param type    the type (type of browser).
     */
    public WebParticipant(String name, WebDriver driver, ParticipantType type) {
        super(name, driver, type, DEFAULT_CONFIG);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void doJoinConference(JitsiMeetUrl conferenceUrl) {
        // with chrome v52 we start getting error:
        // "Timed out receiving message from renderer" and
        // "Navigate timeout: cannot determine loading status"
        // seems its a bug or rare problem, maybe concerns async loading
        // of resources ...
        // https://bugs.chromium.org/p/chromedriver/issues/detail?id=402
        // even there is a TimeoutException the page is loaded correctly
        // and driver is operating, we just lower the page load timeout
        // default is 3 minutes and we log and skip this exception
        driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
        try {
            driver.get(conferenceUrl.toString());
        } catch (org.openqa.selenium.TimeoutException ex) {
            ex.printStackTrace();
            TestUtils.print(
                    "TimeoutException while loading page, " + "will skip it and continue:" + ex.getMessage());
        }

        if (conferenceUrl.getIframeToNavigateTo() != null) {
            // let's wait for loading and switch to that iframe so we can continue
            // with regular tests
            WebDriverWait wait = new WebDriverWait(driver, 10);
            wait.until(ExpectedConditions
                    .frameToBeAvailableAndSwitchToIt(By.id(conferenceUrl.getIframeToNavigateTo())));
        } else {
            MeetUtils.waitForPageToLoad(driver);
        }

        // disables animations
        executeScript("try { jQuery.fx.off = true; } catch(e) {}");

        executeScript("APP.UI.dockToolbar(true);");

        // disable keyframe animations (.fadeIn and .fadeOut classes)
        executeScript("$('<style>.notransition * { " + "animation-duration: 0s !important; "
                + "-webkit-animation-duration: 0s !important; transition:none; }"
                + " </style>').appendTo(document.head);");
        executeScript("$('body').toggleClass('notransition');");

        // disable the blur effect in firefox as it has some performance issues
        if (this.type.isFirefox()) {
            executeScript("try { var blur " + "= document.querySelector('.video_blurred_container'); "
                    + "if (blur) { " + "document.querySelector('.video_blurred_container')"
                    + ".style.display = 'none' " + "} } catch(e) {}");
        }

        if ("false".equals(conferenceUrl.getFragmentParam("config.callStatsID"))) {
            // Hack-in disabling of callstats (old versions of jitsi-meet don't
            // handle URL parameters)
            executeScript("config.callStatsID=false;");
        }

        String version = TestUtils.executeScriptAndReturnString(driver, "return JitsiMeetJS.version;");
        TestUtils.print(name + " lib-jitsi-meet version: " + version
                + (driver instanceof RemoteWebDriver ? " sessionID: " + ((RemoteWebDriver) driver).getSessionId()
                        : ""));

        executeScript("document.title='" + name + "'");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doHangUp() {
        getToolbar().clickHangUpButton();

        TestUtils.waitMillis(500);
        // open a blank page after hanging up, to make sure
        // we will successfully navigate to the new link containing the
        // parameters, which change during testing
        driver.get("about:blank");
        MeetUtils.waitForPageToLoad(driver);
    }

    /**
     * Executes a script in this {@link WebParticipant}'s {@link WebDriver}.
     * See {@link JavascriptExecutor#executeScript(String, Object...)}.
     */
    @Override
    public Object executeScript(String var1, Object... var2) {
        return getJSExecutor().executeScript(var1, var2);
    }

    /**
     * Executes a script asynchronously in this {@link WebParticipant}'s
     * {@link WebDriver}.
     * See {@link JavascriptExecutor#executeScript(String, Object...)}.
     */
    @Override
    public Object executeAsyncScript(String var1, Object... var2) {
        return getJSExecutor().executeAsyncScript(var1, var2);
    }

    private JavascriptExecutor getJSExecutor() {
        if (driver instanceof JavascriptExecutor) {
            return (JavascriptExecutor) driver;
        } else {
            throw new RuntimeException("The driver is not capable of executing JavaScript");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getEndpointId() {
        Object o = executeScript("return APP.conference.getMyUserId();");
        return o == null ? null : o.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getMeetDebugLog() {
        try {
            Object log = executeScript("try{ " + "return JSON.stringify("
                    + "  APP.conference.getLogs(), null, '    ');" + "}catch (e) {}");

            return log instanceof String ? (String) log : null;
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "Failed to get meet logs from " + name, e);

            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getRTPStats() {
        try {
            // we default all tests to not use p2p, so we default using jvb here
            Object log = MeetUtils.getRtpStats(this.getDriver(), true);

            return log instanceof String ? (String) log : null;
        } catch (Exception e) {
            Logger.getGlobal().log(Level.SEVERE, "Failed to get meet rtp stats from " + name, e);

            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getProtocol() {
        Object protocol = executeScript("try {" + "return APP.conference.getStats().transport[0].type;"
                + "} catch (err) { return 'error: '+err; }");

        return (protocol == null) ? null : protocol.toString().toLowerCase();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected RtpStatistics getRtpStatistics() {
        return new WebRtpStatistics(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isP2pConnected() {
        // FIXME hmm this checks for ICE connected state which does not
        // necessarily mean that the conference is currently in the P2P mode.
        // It may turn out we're not really checking if P2P is connected
        // and the method name may be confusing.
        return TestUtils.getBooleanResult(executeScript(MeetUtils.ICE_CONNECTED_CHECK_SCRIPT));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isXmppConnected() {
        return TestUtils.getBooleanResult(executeScript("return APP.conference._room.xmpp.connection.connected;"));
    }

    /**
     * Whether this participant supports video, based on the browser type.
     * @return {@code true} if the participant browser do not support video.
     */
    public boolean isAudioOnlyParticipant() {
        return getType() == ParticipantType.safari;
    }

    /**
     * {@inheritDoc}
     */
    protected boolean isIceConnected() {
        return TestUtils.executeScriptAndReturnBoolean(driver, ICE_CONNECTED_CHECK_SCRIPT);
    }

    /**
     * Checks whether this participant is in the MUC.
     *
     * @return {@code true} if the this participant has joined the
     * room; otherwise, {@code false}
     */
    @Override
    public boolean isInMuc() {
        Object res = executeScript(IS_MUC_JOINED);
        return res != null && res.equals(Boolean.TRUE);
    }

    /**
     * Waits 15 sec for the given participant to enter the ICE 'connected'
     * state.
     */
    public void waitForIceConnected() {
        waitForIceConnected(15);
    }

    /**
     * Waits for number of remote streams.
     * @param n number of remote streams to wait for.
     */
    public void waitForRemoteStreams(int n) {
        waitForCondition(
                () -> (Boolean) executeScript(
                        "return APP.conference" + ".getNumberOfParticipantsWithTracks() >= " + n + ";"),
                15, "waitForRemoteStreams:" + n);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDisplayName(String name) {
        WebDriver driver = getDriver();

        WebElement elem = driver
                .findElement(By.xpath("//span[@id='localVideoContainer']" + "//span[@id='localDisplayName']"));
        // hover the element before clicking
        Actions actions = new Actions(driver);
        actions.moveToElement(elem);
        actions.perform();

        elem.click();

        WebElement inputElem = driver
                .findElement(By.xpath("//span[@id='localVideoContainer']" + "//input[@id='editDisplayName']"));
        actions = new Actions(driver);
        actions.moveToElement(inputElem);
        actions.perform();

        if (name != null && name.length() > 0) {
            inputElem.sendKeys(name);
        } else {
            inputElem.sendKeys(Keys.BACK_SPACE);
        }

        inputElem.sendKeys(Keys.RETURN);
        // just click somewhere to lose focus, to make sure editing has ended
        MeetUIUtils.clickOnLocalVideo(driver);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void pressShortcut(Character shortcut) {
        // We've observed intermittent failures with the keys being sent to the
        // wrong element (e.g. the chat input field). Selecting "body" instead
        // of another element seems to make this condition appear less often.
        WebDriver driver = getDriver();
        WebElement body = driver.findElement(By.tagName("body"));
        Actions actions = new Actions(driver);
        actions.moveToElement(body);
        actions.sendKeys(body, shortcut.toString());
        actions.perform();
    }

    /**
     * @return a representation of the chat panel of this participant.
     */
    public ChatPanel getChatPanel() {
        if (chatPanel == null) {
            chatPanel = new ChatPanel(this);
        }

        return chatPanel;
    }

    /**
     * Returns the value for the given <tt>key</tt> from the config.js loaded
     * for the participant.
     *
     * @param key the <tt>String</tt> key from config.js.
     * @return the value for the given <tt>key</tt> from the config.js loaded
     * for the participant.
     */
    public Object getConfigValue(String key) {
        return executeScript("return config." + key);
    }

    /**
     * @return a representation of the dial in page of this participant.
     */
    public DialInNumbersPage getDialInNumbersPage() {
        if (dialInNumbersPage == null) {
            dialInNumbersPage = new DialInNumbersPage(this);
        }

        return dialInNumbersPage;
    }

    /**
     * @return a representation of the info dialog of this participant.
     */
    public InfoDialog getInfoDialog() {
        if (infoDialog == null) {
            infoDialog = new InfoDialog(this);
        }

        return infoDialog;
    }

    /**
     * @return a representation of the large video of this participant.
     */
    public LargeVideo getLargeVideo() {
        if (largeVideo == null) {
            largeVideo = new LargeVideo(this);
        }

        return largeVideo;
    }

    /**
     * @return a representation of the settings dialog of this participant.
     */
    public SettingsDialog getSettingsDialog() {
        if (settingsDialog == null) {
            settingsDialog = new SettingsDialog(this);
        }

        return settingsDialog;
    }

    /**
     * @return a representation of the toolbar of this participant.
     */
    public Toolbar getToolbar() {
        if (toolbar == null) {
            toolbar = new Toolbar(this);
        }

        return toolbar;
    }

    /**
     * @return a representation of the filmstrip of this participant.
     */
    public WebFilmstrip getFilmstrip() {
        if (filmstrip == null) {
            filmstrip = new WebFilmstrip(this);
        }

        return filmstrip;
    }

    /**
     /**
     * A list of log string entries.
     *
     * @return a list of log entries.
     */
    @Override
    public List getBrowserLogs() {
        try {
            return (List) executeScript("return APP.debugLogs.getLogs();");
        } catch (RuntimeException t) {
            // if APP is missing or debugLogs missing
            Logger.getGlobal().log(Level.SEVERE, "Failed to obtain browser logs:" + t.getMessage());
            return null;
        }
    }
}