com.google.caja.plugin.BrowserTestCase.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caja.plugin.BrowserTestCase.java

Source

// Copyright (C) 2009 Google Inc.
//
// 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 com.google.caja.plugin;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.List;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import com.google.caja.lexer.escaping.Escaping;
import com.google.caja.util.LocalServer;
import com.google.caja.util.TestFlag;
import com.google.caja.util.ThisHostName;
import com.google.common.base.Joiner;

/**
 * Test case class with tools for controlling a web browser running pages from
 * a local web server.
 * <p>
 * Browser testing is described in more detail at the
 * <a href="http://code.google.com/p/google-caja/wiki/CajaTesting"
 *   >CajaTesting wiki page</a>
 *
 * @author maoziqing@gmail.com (Ziqing Mao)
 * @author kpreid@switchb.org (Kevin Reid)
 */
public abstract class BrowserTestCase {
    // Constructed @BeforeClass to share a single web browser.
    private static WebDriverHandle wdh;
    private static int serverPort;
    private static String serverHost;

    protected String testBuildVersion = null;

    private final LocalServer localServer = new LocalServer(new LocalServer.ConfigureContextCallback() {
        @Override
        public void configureContext(ServletContextHandler ctx) {
            addServlets(ctx);
        }
    });

    @BeforeClass
    public static void setUpClass() throws Exception {
        wdh = new WebDriverHandle();
        serverPort = TestFlag.SERVER_PORT.getInt(0);
        serverHost = TestFlag.SERVER_HOSTNAME.getString(null);
        if (serverHost == null) {
            // If we're testing a remote browser, we need a hostname it can
            // use to contact the LocalServer instance.
            if (TestFlag.WEBDRIVER_URL.truthy()) {
                serverHost = ThisHostName.value();
            } else {
                serverHost = "localhost";
            }
        }
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
        wdh.release();
    }

    /**
     * Set a custom build version for testing. This will be used by the cajoling
     * service to stamp outgoing cajoled modules. Set this to <code>null</code>
     * to disable custom test build version and revert to default behavior.
     *
     * @param version the desired test build version.
     */
    protected void setTestBuildVersion(String version) {
        testBuildVersion = version;
    }

    private void debugHook() throws Exception {
        if (!TestFlag.DEBUG_BROWSER.truthy() && !TestFlag.DEBUG_SERVER.truthy()) {
            return;
        }
        serverPort = TestFlag.SERVER_PORT.getInt(8000);
        localServer.start("127.0.0.1", serverPort);
        String url = testUrl("test-index.html");
        if (TestFlag.DEBUG_BROWSER.truthy()) {
            wdh.begin().get(url);
        }
        Echo.echo("- See " + url);
        Thread.currentThread().join();
    }

    private String testUrl(String name) {
        return "http://" + serverHost + ":" + localServer.getPort() + "/ant-testlib/com/google/caja/plugin/" + name;
    }

    protected String runBrowserTest(String label, boolean isKnownFailure, String name, String... params)
            throws Exception {
        debugHook();
        String result = "";
        boolean passed = false;
        try {
            localServer.start("127.0.0.1", serverPort);

            String url = testUrl(name);
            if (params != null && 0 < params.length) {
                url += "?" + Joiner.on("&").join(params);
            }
            Echo.echo("- Running " + url);

            try {
                WebDriver driver = wdh.begin();
                driver.get(url);
                result = driveBrowser(driver);
                passed = true;
            } finally {
                captureResults(label, passed);
                wdh.end(passed || isKnownFailure);
            }
        } catch (Exception e) {
            Echo.rethrow(e);
        } finally {
            localServer.stop();
        }
        return result;
    }

    private void captureResults(String label, boolean passed) {
        if (alwaysCapture(label)) {
            wdh.captureResults("keep." + label);
        } else if (!passed) {
            wdh.captureResults("fail." + label);
        } else if (TestFlag.CAPTURE_PASSES.truthy()) {
            wdh.captureResults("pass." + label);
        }
    }

    protected boolean alwaysCapture(String label) {
        // TODO(felix8a): maybe this should be a flag in browser-tests.json
        return false;
    }

    protected static String escapeUri(String s) {
        StringBuilder sb = new StringBuilder();
        Escaping.escapeUri(s, sb);
        return sb.toString();
    }

    protected static String[] add(String[] arr, String... rest) {
        String[] result = new String[arr.length + rest.length];
        System.arraycopy(arr, 0, result, 0, arr.length);
        System.arraycopy(rest, 0, result, arr.length, rest.length);
        return result;
    }

    /**
     * Do what should be done with the browser.
     */
    protected String driveBrowser(final WebDriver driver) {
        // long timeout: something we're doing is leading to huge unpredictable
        // slowdowns in random test startup; perhaps we're holding onto a lot of ram
        // and  we're losing on swapping/gc time.  unclear.
        countdown(10000, 200, new Countdown() {
            @Override
            public String toString() {
                return "startup";
            }

            public int run() {
                List<WebElement> readyElements = driver.findElements(By.className("readytotest"));
                return readyElements.size() == 0 ? 1 : 0;
            }
        });

        // 4s because test-domado-dom-events has non-click tests that can block
        // for a nontrivial amount of time, so our clicks aren't necessarily
        // processed right away.
        countdown(4000, 200, new Countdown() {
            private List<WebElement> clickingList = null;

            @Override
            public String toString() {
                return "clicking done (Remaining elements = " + renderElements(clickingList) + ")";
            }

            public int run() {
                clickingList = driver.findElements(By.xpath("//*[contains(@class,'clickme')]/*"));
                for (WebElement e : clickingList) {
                    // TODO(felix8a): webdriver fails if e has been removed
                    e.click();
                }
                return clickingList.size();
            }
        });

        // override point
        waitForCompletion(driver);

        // check the title of the document
        String title = driver.getTitle();
        assertTrue("The title shows " + title, title.contains("all tests passed"));
        return title;
    }

    /**
     * After startup and clicking is done, wait an appropriate amount of time
     * for tests to pass.
     */
    protected void waitForCompletion(final WebDriver driver) {
        countdown(waitForCompletionTimeout(), 200, new Countdown() {
            private List<WebElement> waitingList = null;

            @Override
            public String toString() {
                return "completion (Remaining elements = " + renderElements(waitingList) + ")";
            }

            public int run() {
                // TODO(felix8a): this used to check for just class "waiting", but now
                // "waiting" is redundant and should be removed.
                waitingList = driver.findElements(By.xpath("//*[contains(@class,'testcontainer')"
                        + " and not(contains(@class,'done'))" + " and not(contains(@class,'manual'))]"));
                return waitingList.size();
            }
        });
    }

    /** Override point */
    @SuppressWarnings("static-method")
    protected int waitForCompletionTimeout() {
        return 10000; // ms
    }

    /**
     * Run 'c' every 'intervalMillis' until it returns 0,
     * or 'timeoutMillis' have passed since the value has changed.
     */
    protected static void countdown(int timeoutMillis, int intervalMillis, Countdown c) {
        int lastValue = -1;
        long endTime = System.currentTimeMillis() + timeoutMillis;
        int value;
        while ((value = c.run()) != 0) {
            long now = System.currentTimeMillis();
            if (value != lastValue) {
                endTime = now + timeoutMillis;
                lastValue = value;
            }
            if (endTime < now) {
                fail(timeoutMillis + " ms passed while waiting for: " + c);
            }
            try {
                Thread.sleep(intervalMillis);
            } catch (InterruptedException e) {
                // keep going
            }
        }
    }

    protected static String renderElements(List<WebElement> elements) {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int i = 0, n = elements.size(); i < n; i++) {
            if (i != 0) {
                sb.append(", ");
            }
            WebElement el = elements.get(i);
            sb.append('<').append(el.getTagName());
            String id = el.getAttribute("id");
            if (id != null) {
                sb.append(" id=\"");
                Escaping.escapeXml(id, false, sb);
                sb.append('"');
            }
            String className = el.getAttribute("class");
            if (className != null) {
                sb.append(" class=\"");
                Escaping.escapeXml(className, false, sb);
                sb.append('"');
            }
            sb.append('>');
        }
        sb.append(']');
        return sb.toString();
    }

    /**
     * Add servlets as desired specific to a given test case.
     *
     * @param servlets a Jetty Context to which servlets can be added.
     */
    protected void addServlets(ServletContextHandler servlets) {
        // Adds none but may be overridden.
    }

    public interface Countdown {
        int run();
    }
}