/**************************************************************************** * Copyright 2008-2011 ThoughtWorks, 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. * * Initial Contributors: * Håkan Råberg * Manish Chakravarty * Pavan K S ***************************************************************************/ package com.thoughtworks.krypton.driver.web.browser.wait; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import javax.imageio.ImageIO; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.swt.browser.BrowserFunction; import org.junit.Before; import org.junit.Test; import com.thoughtworks.krypton.driver.web.browser.AbstractBaseBrowserSessionWithWebServer; import com.thoughtworks.krypton.driver.web.browser.BrowserFamily; import com.thoughtworks.krypton.driver.web.browser.wait.AjaxWaitStrategy; import com.thoughtworks.krypton.driver.web.browser.wait.DocumentReadyWaitStrategy; import com.thoughtworks.krypton.driver.web.browser.wait.OnBeforeUnloadWaitStrategy; import com.thoughtworks.krypton.driver.web.browser.wait.SetTimeoutWaitStrategy; import static org.junit.Assert.*; public class WaitStrategiesTest extends AbstractBaseBrowserSessionWithWebServer { private static final int BLOCKING_TIME = 500; private static final int SET_TIMEOUT_PRECISION = 50; private static final int REFRESHES = 3; @Before public void setTimeoutToOneSecond() { SlowLoadingImageServlet.timesCalled = 0; session.setEventLoopTimeout(BLOCKING_TIME * 4); } @Test public void shouldVerifyWebServerIsResponding() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); HttpURLConnection connection = connectTo(path); try { assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode()); assertTrue(reader(connection).readLine().contains("Hello World")); } finally { if (connection != null) { connection.disconnect(); } } } @Test public void shouldRespondToBrowser() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); load(localUrl(path)); assertEquals("Hello World", session.dom().getElementsByTagName("body").item(0).getTextContent()); } @Test public void shouldWaitForPageToLoadUsingLocationChangedWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; session.addWaitStrategy(new DocumentReadyWaitStrategy()); session.openBrowser(); loadAndRefreshPageThreeTimes(path, timeout); } // Safari lacks the onbeforeunload event. @Test public void shouldWaitForPageToLoadUsingOnBeforeUnloadWaitStrategy() throws Exception { if (BrowserFamily.SAFARI == BrowserFamily.fromSystemProperty()) { return; } String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; OnBeforeUnloadWaitStrategy waitStrategy = new OnBeforeUnloadWaitStrategy(); session.addWaitStrategy(waitStrategy); session.openBrowser(); loadAndRefreshPageThreeTimes(path, timeout); } @Test public void shouldWaitForPageToLoadUsingDocumentReadyWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(SlowDocumentHelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; final boolean[] wasBusy = new boolean[1]; DocumentReadyWaitStrategy waitStrategy = new DocumentReadyWaitStrategy() { public boolean isBusy() { boolean busy = super.isBusy(); if (busy) { wasBusy[0] = true; } return busy; } }; session.addWaitStrategy(waitStrategy); session.openBrowser(); int i; for (i = 0; i < REFRESHES; i++) { wasBusy[0] = false; assertReloadHelloWorld(path, timeout); assertTrue(wasBusy[0]); } assertEquals(REFRESHES, i); } @Test public void shouldWaitForPageButNotLargeImageToLoadUsingDocumentReadyWaitStrategy() throws Exception { String path = "/blocking-servlet-with-image"; handler.addServletWithMapping(SlowDocumentHelloWorldServletWithImage.class, path); handler.addServletWithMapping(SlowLoadingImageServlet.class, "/image-servlet/*"); int timeout = BLOCKING_TIME * 2; final boolean[] wasBusy = new boolean[1]; final int[] timesBusy = new int[1]; DocumentReadyWaitStrategy waitStrategy = new DocumentReadyWaitStrategy() { public boolean isBusy() { boolean busy = super.isBusy(); if (busy) { if (!wasBusy[0]) { timesBusy[0]++; } wasBusy[0] = true; } return busy; } }; session.addWaitStrategy(waitStrategy); session.openBrowser(); int i; for (i = 0; i < REFRESHES; i++) { wasBusy[0] = false; assertReloadHelloWorld(path, timeout); int servedBytes = SlowLoadingImageServlet.servedBytes; assertTrue(servedBytes >= 0); assertTrue("only half of image should have been served at most, full size: " + SlowLoadingImageServlet.IMAGE_BYTES.length + " served: " + SlowLoadingImageServlet.servedBytes, servedBytes < (SlowLoadingImageServlet.IMAGE_BYTES.length / 2)); } assertEquals(REFRESHES, i); assertEquals(REFRESHES, timesBusy[0]); // Safari will request the image more than once per refresh for some // reason assertTrue(SlowLoadingImageServlet.timesCalled >= REFRESHES); } @Test public void shouldNotWaitForPageToLoadUsingDocumentReadyWaitStrategyForFastDOM() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; final boolean[] wasBusy = new boolean[1]; DocumentReadyWaitStrategy waitStrategy = new DocumentReadyWaitStrategy() { public boolean isBusy() { boolean busy = super.isBusy(); if (busy) { wasBusy[0] = true; } return busy; } }; session.addWaitStrategy(waitStrategy); session.openBrowser(); int i; for (i = 0; i < REFRESHES; i++) { wasBusy[0] = false; assertReloadHelloWorld(path, timeout); // The IE DocumentWaitStrategy will (probably) always be busy // if (IE != session.getBrowserFamily()) { // assertFalse(wasBusy[0]); // } } assertEquals(REFRESHES, i); } @Test public void shouldNotWaitForExcludedUrlUsinDocumentReadyWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); DocumentReadyWaitStrategy strategy = new DocumentReadyWaitStrategy(); strategy.addURLExclusionPattern(".*/blocking-.*"); session.addWaitStrategy(strategy); session.openBrowser(); load(localUrl(path)); session.getBrowser().execute("window.location = '" + localUrl(path) + "'"); timedWaitForIdle(); assertTrue("idle < blocking: " + idleTime + " < " + BLOCKING_TIME, idleTime < BLOCKING_TIME); } // This just doesn't work // @Test // public void shouldWaitForPageToLoadUsingOnUnloadWaitStrategy() throws // Exception { // String path = "/blocking-servlet"; // handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); // int timeout = BlockingHelloWorldServlet.BLOCKING_TIME * 2; // // render("<html/>"); // OnUnloadWaitStrategy waitStrategy = new OnUnloadWaitStrategy(timeout); // assertFalse(waitStrategy.changed); // assertFalse(waitStrategy.isUnloading); // // session.addWaitStrategy(waitStrategy); // // session.openBrowser(); // // loadAndRefreshPageThreeTimes(path, timeout); // } @Test public void shouldNotWaitForPageToLoadWithoutLocationChangedOrOnUnloadWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); session.openBrowser(); session.getBrowser().setUrl(localUrl(path)); timedWaitForIdle(); assertTrue("idle < blocking: " + idleTime + " < " + BLOCKING_TIME, idleTime < BLOCKING_TIME); } @Test public void shouldWaitForAjaxRequestUsingAjaxWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; final String[] doneOrder = new String[] { "" }; new BrowserFunction(session.getBrowser(), "ajaxDoneAt") { public Object function(Object[] arguments) { doneOrder[0] += "ajax, "; return super.function(arguments); } }; AjaxWaitStrategy strategy = new AjaxWaitStrategy() { public void decreaseNumberOfActiveAjaxRequests(String url) { doneOrder[0] += "strategy"; super.decreaseNumberOfActiveAjaxRequests(url); } }; session.addWaitStrategy(strategy); session.openBrowser(); load(localUrl(path)); doLocalAjaxRequest(path); assertEquals(1, strategy.getNumberOfActiveAjaxRequests()); timedWaitForIdle(); assertEquals("onreadystate call back was called after strategy became idle.", "ajax, strategy", doneOrder[0]); assertTrue("idle < timeout: " + idleTime + " < " + timeout, idleTime < timeout); assertTrue("idle >= blocking: " + idleTime + " >= " + BLOCKING_TIME, idleTime >= BLOCKING_TIME); assertEquals(0, strategy.getNumberOfActiveAjaxRequests()); } @Test public void shouldNotWaitForExcludedAjaxRequestUsingAjaxWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); AjaxWaitStrategy strategy = new AjaxWaitStrategy(); strategy.addURLExclusionPattern(".*/blocking-.*"); session.addWaitStrategy(strategy); session.openBrowser(); load(localUrl(path)); doLocalAjaxRequest(path); assertEquals(0, strategy.getNumberOfActiveAjaxRequests()); timedWaitForIdle(); assertTrue("idle < blocking: " + idleTime + " < " + BLOCKING_TIME, idleTime < BLOCKING_TIME); assertEquals(0, strategy.getNumberOfActiveAjaxRequests()); } @Test public void shouldNotWaitForAjaxRequestWithoutAjaxWaitStrategy() throws Exception { String path = "/blocking-servlet"; handler.addServletWithMapping(BlockingHelloWorldServlet.class, path); session.openBrowser(); load(localUrl(path)); doLocalAjaxRequest(path); timedWaitForIdle(); assertTrue("idle < blocking: " + idleTime + " < " + BLOCKING_TIME, idleTime < BLOCKING_TIME); } @Test public void shouldNotWaitForSetTimeouWithoutSetTimeoutWaitStrategy() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); session.openBrowser(); load(localUrl(path)); doSetTimeoutCall("''", BLOCKING_TIME); timedWaitForIdle(); int slowTimeoutID = Integer.parseInt(session.evaluate("slowTimeoutID")); assertTrue("slowTimeoutID > 0: " + slowTimeoutID + " > 0", slowTimeoutID > 0); assertTrue("idle < blocking: " + idleTime + " < " + BLOCKING_TIME, idleTime < BLOCKING_TIME); } @Test public void shouldWaitForSetTimeoutUsingSetTimeoutWaitStrategy() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; SetTimeoutWaitStrategy waitStrategy = new SetTimeoutWaitStrategy(); session.addWaitStrategy(waitStrategy); session.openBrowser(); load(localUrl(path)); doSetTimeoutCall("'Hello'", BLOCKING_TIME); assertEquals(1, waitStrategy.getNumberOfActiveSetTimeouts()); int slowTimeoutID = Integer.parseInt(session.evaluate("slowTimeoutID")); assertTrue("slowTimeoutID > 0: " + slowTimeoutID + " > 0", slowTimeoutID > 0); timedWaitForIdle(); assertTrue("idle < timeout: " + idleTime + " < " + timeout, idleTime < timeout); assertTrue("idle == blocking (precision " + SET_TIMEOUT_PRECISION + "): " + idleTime + " == " + BLOCKING_TIME, Math.abs(idleTime - BLOCKING_TIME) < SET_TIMEOUT_PRECISION); assertEquals(getExpectedMessageAsIEDoesntSupportParameters(), session.evaluate("Twist.slowSetTimeoutCalledWith")); assertEquals(0, waitStrategy.getNumberOfActiveSetTimeouts()); } @Test public void shouldWaitForSetIntervalForAsLongAsTheMaxSetTimeoutDelayUsingSetTimeoutWaitStrategy() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); int timeout = BLOCKING_TIME * 2; SetTimeoutWaitStrategy waitStrategy = new SetTimeoutWaitStrategy(); session.addWaitStrategy(waitStrategy); session.openBrowser(); waitStrategy.setSetTimeoutMaxDelay(BLOCKING_TIME); load(localUrl(path)); doSetIntervalCall("'Hello'", BLOCKING_TIME); assertEquals(1, waitStrategy.getNumberOfActiveSetTimeouts()); timedWaitForIdle(); int slowTimeoutID = Integer.parseInt(session.evaluate("slowTimeoutID")); assertTrue("slowTimeoutID > 0: " + slowTimeoutID + " > 0", slowTimeoutID > 0); assertTrue("idle < timeout: " + idleTime + " < " + timeout, idleTime < timeout); assertTrue("idle == blocking (precision " + SET_TIMEOUT_PRECISION + "): " + idleTime + " == " + BLOCKING_TIME, Math.abs(idleTime - BLOCKING_TIME) < SET_TIMEOUT_PRECISION); assertEquals(getExpectedMessageAsIEDoesntSupportParameters(), session.evaluate("Twist.slowSetTimeoutCalledWith")); assertEquals(0, waitStrategy.getNumberOfActiveSetTimeouts()); } private String getExpectedMessageAsIEDoesntSupportParameters() { return BrowserFamily.IE == BrowserFamily.fromSystemProperty() ? "Hello IE" : "Hello"; } @Test public void shouldWaitForSetTimeoutAndStopWhenClearingTimeoutUsingSetTimeoutWaitStrategy() throws Exception { String path = "/hello-world-servlet"; handler.addServletWithMapping(HelloWorldServlet.class, path); final int timeout = BLOCKING_TIME * 2; SetTimeoutWaitStrategy waitStrategy = new SetTimeoutWaitStrategy(); session.addWaitStrategy(waitStrategy); session.openBrowser(); load(localUrl(path)); doSetTimeoutCall("'Hello'", timeout); assertEquals(1, waitStrategy.getNumberOfActiveSetTimeouts()); int timeoutID = Integer.parseInt(session.evaluate("slowTimeoutID")); int slowTimeoutID = Integer.parseInt(session.evaluate("slowTimeoutID")); assertTrue("slowTimeoutID > 0: " + slowTimeoutID + " > 0", slowTimeoutID > 0); clearTimeoutInSeparateThread(timeoutID, BLOCKING_TIME); timedWaitForIdle(); assertTrue("idle < timeout: " + idleTime + " < " + timeout, idleTime < timeout); assertTrue("idle >= blocking: " + idleTime + " >= " + BLOCKING_TIME, idleTime >= BLOCKING_TIME); assertEquals("undefined", session.evaluate("Twist.slowSetTimeoutCalledWith")); assertEquals(0, waitStrategy.getNumberOfActiveSetTimeouts()); } private void clearTimeoutInSeparateThread(final int timeoutID, final int delay) { new Thread() { public void run() { session.getBrowser().getDisplay().syncExec(new Runnable() { public void run() { try { Thread.sleep(delay); session.execute("window.clearTimeout(" + timeoutID + ")"); } catch (InterruptedException e) { } } }); } }.start(); } private void doSetTimeoutCall(String parameter, int delay) { session.execute("slowTimeoutID = window.setTimeout(slowSetTimeout, " + delay + ", " + parameter + "); function slowSetTimeout(message) { Twist.slowSetTimeoutCalledWith = message ? message : 'Hello IE'; }"); session.pumpEvents(); } private void doSetIntervalCall(String parameter, int delay) { session.execute("slowTimeoutID = window.setInterval(slowSetTimeout, " + delay / 2 + ", " + parameter + "); function slowSetTimeout(message) { Twist.slowSetTimeoutCalledWith = message ? message : 'Hello IE'; }"); session.pumpEvents(); } private void loadAndRefreshPageThreeTimes(String path, int timeout) throws Exception { int i; for (i = 0; i < REFRESHES; i++) { assertReloadHelloWorld(path, timeout); } assertEquals(REFRESHES, i); } private void assertReloadHelloWorld(String path, int timeout) throws MalformedURLException { session.getBrowser().execute("window.location = '" + localUrl(path) + "'"); timedWaitForIdle(); assertTrue("idle < timeout: " + idleTime + " < " + timeout, idleTime < timeout); assertTrue("idle >= blocking: " + idleTime + " >= " + BLOCKING_TIME, idleTime >= BLOCKING_TIME); assertEquals("Hello World", session.dom().getElementById("helloworld").getTextContent()); } @SuppressWarnings("serial") public static class SlowDocumentHelloWorldServlet extends HttpServlet { private static final int CHUNK_SIZE = 1024; static final StringBuffer CHUNK_OF_DATA; static { CHUNK_OF_DATA = new StringBuffer(); for (int i = 0; i < CHUNK_SIZE; i++) { CHUNK_OF_DATA.append("x"); } } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { try { response.setHeader("Cache-Control", "no-cache"); OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream()); writer.write("<html><head/><body>"); writer.write("<div>" + CHUNK_OF_DATA.toString() + "</div>"); writer.flush(); Thread.sleep(BLOCKING_TIME); writer.write("<div id=\"helloworld\">Hello World</div></body></html>"); writer.close(); } catch (InterruptedException e) { } } } @SuppressWarnings("serial") public static class SlowDocumentHelloWorldServletWithImage extends SlowDocumentHelloWorldServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { try { response.setHeader("Cache-Control", "no-cache"); OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream()); writer.write("<html><head/><body>"); writer.write("<div>" + CHUNK_OF_DATA.toString() + "</div><img src=\"/image-servlet/" + System.currentTimeMillis() + "\"/>"); writer.flush(); Thread.sleep(BLOCKING_TIME); writer.write("<div id=\"helloworld\">Hello World</div></body></html>"); writer.close(); } catch (InterruptedException e) { } } } @SuppressWarnings("serial") public static class SlowLoadingImageServlet extends HttpServlet { private static final String IMAGE_FORMAT = "jpg"; private static final int CHUNKS = 10; private static int servedBytes; private static int timesCalled; private static final byte[] IMAGE_BYTES; static { try { BufferedImage largeImage = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB); Graphics g = largeImage.createGraphics(); g.drawLine(0, 0, largeImage.getWidth() - 1, largeImage.getHeight() - 1); ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(largeImage, IMAGE_FORMAT, out); IMAGE_BYTES = out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } // This method is only synchronized because of Safari is making multiple // requests to the image and we use a static to track the number of // served bytes. public synchronized void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { OutputStream os = null; try { timesCalled++; response.setHeader("Cache-Control", "no-cache"); response.setContentType("image/" + IMAGE_FORMAT); os = response.getOutputStream(); int chunkSize = IMAGE_BYTES.length / CHUNKS; servedBytes = 0; for (int i = 0; i < CHUNKS; i++) { os.write(IMAGE_BYTES, CHUNKS * i, chunkSize); os.flush(); Thread.sleep(BLOCKING_TIME * 2 / CHUNKS); servedBytes += chunkSize; } } catch (InterruptedException e) { } finally { if (os != null) { os.close(); } } } } @SuppressWarnings("serial") public static class BlockingHelloWorldServlet extends HelloWorldServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { try { response.setHeader("Cache-Control", "no-cache"); Thread.sleep(BLOCKING_TIME); super.doGet(request, response); } catch (InterruptedException e) { } } } }