org.kurento.test.browser.WebPage.java Source code

Java tutorial

Introduction

Here is the source code for org.kurento.test.browser.WebPage.java

Source

/*
 * (C) Copyright 2015 Kurento (http://kurento.org/)
 *
 * 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.kurento.test.browser;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.kurento.test.grid.GridHandler;
import org.kurento.test.latency.LatencyException;
import org.kurento.test.latency.VideoTag;
import org.kurento.test.monitor.PeerConnectionStats;
import org.kurento.test.monitor.SystemMonitorManager;
import org.kurento.test.utils.Ffmpeg;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Generic web page for tests using Kurento test infrastructure.
 *
 * @author Boni Garcia (bgarcia@gsyc.es)
 * @since 5.1.0
 */
public class WebPage {

    public static Logger log = LoggerFactory.getLogger(WebPage.class);

    private Map<String, CountDownLatch> countDownLatchEvents = new HashMap<>();
    private List<Thread> callbackThreads = new ArrayList<>();

    public Browser browser;

    public Browser getBrowser() {
        return browser;
    }

    public void setBrowser(Browser browser) {
        this.browser = browser;
    }

    public void takeScreeshot(String file) throws IOException {
        File scrFile = ((TakesScreenshot) getBrowser().getWebDriver()).getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(scrFile, new File(file));
    }

    /*
     * setThresholdTime
     */
    public void setThresholdTime(int thresholdTime) {
        browser.setThresholdTime(thresholdTime);
    }

    /*
     * setColorCoordinates
     */
    public void setColorCoordinates(int x, int y) {
        browser.executeScript("kurentoTest.setColorCoordinates(" + x + "," + y + ");");
    }

    /*
     * checkColor
     */
    public void checkColor(String... videoTags) {
        String tags = "";
        for (String s : videoTags) {
            if (!tags.isEmpty()) {
                tags += ",";
            }
            tags += "'" + s + "'";
        }
        browser.executeScript("kurentoTest.checkColor(" + tags + ");");
    }

    /*
     * similarColorAt
     */
    public boolean similarColorAt(String videoTag, Color expectedColor, int x, int y) {
        setColorCoordinates(x, y);
        return similarColor(videoTag, expectedColor);

    }

    /*
     * similarColor
     */
    public boolean similarColor(String videoTag, Color expectedColor) {
        boolean out;
        final long endTimeMillis = System.currentTimeMillis() + browser.getTimeout() * 1000;

        boolean logWarn = true;
        while (true) {
            out = compareColor(videoTag, expectedColor, logWarn);
            if (out || System.currentTimeMillis() > endTimeMillis) {
                break;
            } else {
                // Polling: wait 200 ms and check again the color
                // Max wait = timeout variable
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    log.trace("InterruptedException in guard condition ({})", e.getMessage());
                }
            }
            logWarn = false;
        }
        return out;
    }

    /*
     * compareColor
     */
    public boolean compareColor(String videoTag, Color expectedColor, boolean logWarn) {
        @SuppressWarnings("unchecked")
        List<Long> realColor = (List<Long>) browser
                .executeScriptAndWaitOutput("return kurentoTest.colorInfo['" + videoTag + "'].currentColor;");

        long red = realColor.get(0);
        long green = realColor.get(1);
        long blue = realColor.get(2);

        double distance = Math.sqrt((red - expectedColor.getRed()) * (red - expectedColor.getRed())
                + (green - expectedColor.getGreen()) * (green - expectedColor.getGreen())
                + (blue - expectedColor.getBlue()) * (blue - expectedColor.getBlue()));

        String expectedColorStr = "[R=" + expectedColor.getRed() + ", G=" + expectedColor.getGreen() + ", B="
                + expectedColor.getBlue() + "]";
        String realColorStr = "[R=" + red + ", G=" + green + ", B=" + blue + "]";
        boolean out = distance <= browser.getColorDistance();
        if (!out) {
            if (logWarn) {
                log.warn("Color NOT detected in video stream. Expected: {}, Real: {}", expectedColorStr,
                        realColorStr);
            }
        } else {
            log.debug("Detected color in video stream. Expected: {}, Real: {}", expectedColorStr, realColorStr);
        }

        return out;
    }

    /*
     * activatePeerConnectionInboundStats
     */
    public void activatePeerConnectionInboundStats(String peerConnectionId) {
        activatePeerConnectionStats("activateInboundRtcStats", peerConnectionId);
    }

    /*
     * activatePeerConnectionOutboundStats
     */
    public void activatePeerConnectionOutboundStats(String peerConnectionId) {
        activatePeerConnectionStats("activateOutboundRtcStats", peerConnectionId);
    }

    private void activatePeerConnectionStats(String jsFunction, String peerConnectionId) {

        try {
            browser.executeScript("kurentoTest." + jsFunction + "('" + peerConnectionId + "');");

        } catch (WebDriverException we) {
            we.printStackTrace();

            // If client is not ready to gather rtc statistics, we just log it
            // as warning (it is not an error itself)
            log.warn("Client does not support RTC statistics (function kurentoTest.{}() not defined)", jsFunction);
        }
    }

    /**
     *
     * @param peerConnectionId
     */
    public void stopPeerConnectionInboundStats(String peerConnectionId) {
        stopPeerConnectionStats("stopInboundRtcStats", peerConnectionId);
    }

    /**
     *
     * @param peerConnectionId
     */
    public void stopPeerConnectionOutboundStats(String peerConnectionId) {
        stopPeerConnectionStats("stopOutboundRtcStats", peerConnectionId);
    }

    private void stopPeerConnectionStats(String jsFunction, String peerConnectionId) {

        try {
            log.debug("kurentoTest." + jsFunction + "('" + peerConnectionId + "');");
            browser.executeScript("kurentoTest." + jsFunction + "('" + peerConnectionId + "');");

        } catch (WebDriverException we) {
            we.printStackTrace();

            // If client is not ready to gather rtc statistics, we just log it
            // as warning (it is not an error itself)
            log.warn("Client does not support RTC statistics (function kurentoTest.{}() not defined)");
        }
    }

    /*
     * getLatency
     */
    @SuppressWarnings("deprecation")
    public long getLatency() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final long[] out = new long[1];
        Thread t = new Thread() {
            @Override
            public void run() {
                Object latency = browser.executeScript("return kurentoTest.getLatency();");
                if (latency != null) {
                    out[0] = (Long) latency;
                } else {
                    out[0] = Long.MIN_VALUE;
                }
                latch.countDown();
            }
        };
        t.start();
        if (!latch.await(browser.getTimeout(), TimeUnit.SECONDS)) {
            t.interrupt();
            t.stop();
            throw new LatencyException("Timeout getting latency (" + browser.getTimeout() + "  seconds)");
        }
        return out[0];
    }

    public void waitColor(long timeoutSeconds, final VideoTag videoTag, final Color color) {
        WebDriverWait wait = new WebDriverWait(browser.getWebDriver(), timeoutSeconds);
        wait.until(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                return !((JavascriptExecutor) d).executeScript(videoTag.getColor()).equals(color);
            }
        });
    }

    /*
     * getCurrentTime
     */
    public long getCurrentTime(VideoTag videoTag) {
        Object time = browser.executeScript(videoTag.getTime());
        return time == null ? 0 : (Long) time;
    }

    /*
     * getCurrentColor
     */
    @SuppressWarnings("unchecked")
    public Color getCurrentColor(VideoTag videoTag) {
        return getColor((List<Long>) browser.executeScript(videoTag.getColor()));
    }

    private Color getColor(List<Long> color) {
        return new Color(color.get(0).intValue(), color.get(1).intValue(), color.get(2).intValue());
    }

    /*
     * checkLatencyUntil
     */
    public void checkLatencyUntil(SystemMonitorManager monitor, long endTimeMillis)
            throws InterruptedException, IOException {
        while (true) {
            if (System.currentTimeMillis() > endTimeMillis) {
                break;
            }
            Thread.sleep(100);
            try {
                long latency = getLatency();
                if (latency != Long.MIN_VALUE) {
                    monitor.addCurrentLatency(latency);
                }
            } catch (LatencyException le) {
                monitor.incrementLatencyErrors();
            }
        }
    }

    /*
     * getRtcStats
     */
    @SuppressWarnings("unchecked")
    public PeerConnectionStats getRtcStats() {
        Map<String, Object> out = new HashMap<>();
        try {
            if (browser != null && browser.getWebDriver() != null) {
                out = (Map<String, Object>) browser.executeScript("return kurentoTest.rtcStats;");

                log.debug(">>>>>>>>>> kurentoTest.rtcStats {} {}", browser.getId(), out);
            }
        } catch (WebDriverException we) {
            // If client is not ready to gather rtc statistics, we just log it
            // as warning (it is not an error itself)
            log.warn("Client does not support RTC statistics" + " (variable rtcStats is not defined)");
        }
        return new PeerConnectionStats(out);
    }

    /*
     * activateLatencyControl
     */
    public void activateLatencyControl(String localId, String remoteId) {
        browser.executeScript("kurentoTest.activateLatencyControl('" + localId + "', '" + remoteId + "');");

    }

    /*
     * getTimeout
     */
    public int getTimeout() {
        return browser.getTimeout();
    }

    /*
     * setTimeout
     */
    public void setTimeout(int timeoutSeconds) {
        browser.changeTimeout(timeoutSeconds);
    }

    /*
     * getThresholdTime
     */
    public int getThresholdTime() {
        return browser.getThresholdTime();

    }

    /*
     * equalDataChannelMessage
     */
    public boolean compareDataChannelMessage(String message) {
        boolean out;
        final long endTimeMillis = System.currentTimeMillis() + browser.getTimeout() * 1000;
        while (true) {
            String messageReceived = (String) browser.executeScript("return kurentoTest.getDataChannelMessage()");
            out = (message.equals(messageReceived));
            if (out || System.currentTimeMillis() > endTimeMillis) {
                break;
            } else {
                // Polling: wait 200 ms and check again the color
                // Max wait = timeout variable
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    log.trace("InterruptedException in guard condition ({})", e.getMessage());
                }
            }
        }
        return out;
    }

    /*
     * syncTimeForOcr
     */
    public void syncTimeForOcr(String videoTagId, String peerConnectionId) {
        browser.executeScript("kurentoTest.syncTimeForOcr('" + videoTagId + "', '" + peerConnectionId + "');");

        log.debug("Sync time in {} {}", browser.getId(), videoTagId);
        WebDriverWait wait = new WebDriverWait(browser.getWebDriver(), browser.getTimeout());
        wait.until(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver d) {
                return (Boolean) ((JavascriptExecutor) d).executeScript("return kurentoTest.sync;");
            }
        });
        log.debug("[Done] Sync time in {} {}", browser.getId(), videoTagId);
    }

    /*
     * startOcr
     */
    public void startOcr() {
        browser.executeScript("kurentoTest.startOcr();");
    }

    /*
     * endOcr
     */
    public void endOcr() {
        browser.executeScript("kurentoTest.endOcr();");
    }

    /*
     * getOcrMap
     */
    @SuppressWarnings("unchecked")
    public Map<String, Map<String, Object>> getOcrMap() {
        Map<String, Map<String, String>> ocrMap = (Map<String, Map<String, String>>) browser
                .executeScript("return kurentoTest.ocrMap;");

        // Transform data structure to serializable (because
        // com.google.common.collect.Maps$TransformedEntriesMap is not)
        Map<String, Map<String, Object>> serializableMap = new TreeMap<String, Map<String, Object>>();
        for (String key : ocrMap.keySet()) {
            serializableMap.put(key, new HashMap<String, Object>(ocrMap.get(key)));
        }

        return serializableMap;
    }

    public void startRecording(String stream) {
        browser.executeScript("kurentoTest.startRecording(" + stream + ");");
    }

    public void stopRecording() {
        browser.executeScript("kurentoTest.stopRecording();");
        getProperty("recordRTC");
    }

    public File saveRecordingToDisk(String fileName, String downloadsFolder) {
        browser.executeScript("kurentoTest.saveRecordingToDisk('" + fileName + "');");
        File output = new File(downloadsFolder + fileName);
        do {
            if (!output.exists()) {
                try {
                    Thread.sleep(500); // polling
                } catch (InterruptedException e) {
                    log.warn("Exception waiting for file {}", output, e);
                }
            } else {
                break;
            }
        } while (true);
        return output;
    }

    public void openRecordingInNewTab() {
        browser.executeScript("kurentoTest.openRecordingInNewTab();");
    }

    public File getRecording() throws IOException {
        File tmpFile = File.createTempFile(String.valueOf(System.nanoTime()), ".webm");
        return getRecording(tmpFile.getAbsolutePath());
    }

    public File getRecording(String fileName) throws IOException {
        browser.executeScript("kurentoTest.recordingToData();");
        String recording = getProperty("recordingData").toString();

        // Base64 to File
        File outputFile = new File(fileName);
        byte[] bytes = Base64.decodeBase64(recording.substring(recording.lastIndexOf(",") + 1));
        FileUtils.writeByteArrayToFile(outputFile, bytes);

        return outputFile;
    }

    private Object getProperty(String property) {
        Object value = null;
        final int pollTimeMs = 200;
        for (int i = 0; i < 60; i++) {
            value = browser.executeScript("return kurentoTest." + property + ";");
            if (value != null) {
                break;
            } else {
                try {
                    log.debug("{} not present still... waiting {} ms", property, pollTimeMs);
                    Thread.sleep(pollTimeMs);
                } catch (InterruptedException e) {
                    log.warn("Exception wait polling whil getting {}", property, e);
                }
            }
        }

        String clazz = value != null ? value.getClass().getName() : "";
        log.trace(">>> getProperty {} {} {}", property, value, clazz);
        return value;
    }

    public void subscribeEvent(final String videoTag, final String eventType) {
        subscribeEventsToVideoTag("document.getElementById('" + videoTag + "')", eventType);
    }

    /*
     * subscribeEventsToVideoTag
     */
    protected void subscribeEventsToVideoTag(final String videoTag, final String eventType) {
        CountDownLatch latch = new CountDownLatch(1);

        final String browserName = browser.getId();
        log.debug("Subscribe event '{}' in browser '{}'", eventType, browserName);

        countDownLatchEvents.put(browserName + eventType, latch);
        addEventListener(videoTag, eventType, new BrowserEventListener() {
            @Override
            public void onEvent(String event) {
                consoleLog(ConsoleLogLevel.INFO, "Event in " + videoTag + " tag: " + event);
                countDownLatchEvents.get(browserName + eventType).countDown();
            }
        });
    }

    /*
     * waitForEvent
     */
    public boolean waitForEvent(final String eventType) throws InterruptedException {

        String browserName = browser.getId();
        log.debug("Waiting for event '{}' in browser '{}'", eventType, browserName);

        if (!countDownLatchEvents.containsKey(browserName + eventType)) {
            log.error("We cannot wait for an event without previous subscription");
            return false;
        }

        boolean result = countDownLatchEvents.get(browserName + eventType).await(browser.getTimeout(),
                TimeUnit.SECONDS);

        // Record local audio when playing event reaches the browser
        if (eventType.equalsIgnoreCase("playing") && browser.getRecordAudio() > 0) {
            if (browser.isRemote()) {
                Ffmpeg.recordRemote(GridHandler.getInstance().getNode(browser.getId()), browser.getRecordAudio(),
                        browser.getAudioSampleRate(), browser.getAudioChannel());
            } else {
                Ffmpeg.record(browser.getRecordAudio(), browser.getAudioSampleRate(), browser.getAudioChannel());
            }
        }

        countDownLatchEvents.remove(browserName + eventType);
        return result;
    }

    /*
     * addEventListener
     */
    @SuppressWarnings("deprecation")
    public void addEventListener(final String videoTag, final String eventType,
            final BrowserEventListener eventListener) {
        Thread t = new Thread() {
            @Override
            public void run() {
                browser.executeScript(
                        videoTag + ".addEventListener('" + eventType + "', kurentoTest.videoEvent, false);");
                try {
                    new WebDriverWait(browser.getWebDriver(), browser.getTimeout())
                            .until(new ExpectedCondition<Boolean>() {
                                @Override
                                public Boolean apply(WebDriver d) {
                                    Object videoEventValue = ((JavascriptExecutor) d)
                                            .executeScript("return kurentoTest.videoEventValue;");
                                    return videoEventValue != null
                                            && videoEventValue.toString().equalsIgnoreCase(eventType);
                                }
                            });
                    eventListener.onEvent(eventType);
                } catch (Throwable t) {
                    log.error("~~~ Exception in addEventListener {}", t.getMessage());
                    t.printStackTrace();
                    this.interrupt();
                    this.stop();
                }
            }
        };
        callbackThreads.add(t);
        t.setDaemon(true);
        t.start();
    }

    /*
     * consoleLog
     */
    public void consoleLog(ConsoleLogLevel level, String message) {
        message = message.replaceAll("'", "\"");
        browser.executeScript("console." + level.toString() + "('" + message + "');");
    }

    @After
    @SuppressWarnings("deprecation")
    public void teardownKurentoServices() throws Exception {
        for (Thread t : callbackThreads) {
            t.stop();
        }
    }

    /*
     * stopWebRtc
     */
    public void stopWebRtc() {
        browser.executeScript("stop();");
        browser.executeScript("var kurentoTest = new KurentoTest();");
        countDownLatchEvents.clear();
    }

    /*
     * close
     */
    public void close() {
        browser.close();
    }

}