michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.tests.helpers; michael@0: michael@0: import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull; michael@0: import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue; michael@0: michael@0: import java.util.regex.Pattern; michael@0: michael@0: import org.mozilla.gecko.Actions; michael@0: import org.mozilla.gecko.Actions.EventExpecter; michael@0: import org.mozilla.gecko.tests.UITestContext; michael@0: import org.mozilla.gecko.tests.UITestContext.ComponentType; michael@0: import org.mozilla.gecko.tests.components.ToolbarComponent; michael@0: michael@0: import com.jayway.android.robotium.solo.Condition; michael@0: import com.jayway.android.robotium.solo.Solo; michael@0: michael@0: /** michael@0: * Provides functionality related to waiting on certain events to happen. michael@0: */ michael@0: public final class WaitHelper { michael@0: // TODO: Make public for when Solo.waitForCondition is used directly (i.e. do not want michael@0: // assertion from waitFor)? michael@0: private static final int DEFAULT_MAX_WAIT_MS = 5000; michael@0: private static final int PAGE_LOAD_WAIT_MS = 10000; michael@0: private static final int CHANGE_WAIT_MS = 10000; michael@0: michael@0: // TODO: via lucasr - Add ThrobberVisibilityChangeVerifier? michael@0: private static final ChangeVerifier[] PAGE_LOAD_VERIFIERS = new ChangeVerifier[] { michael@0: new ToolbarTitleTextChangeVerifier() michael@0: }; michael@0: michael@0: private static UITestContext sContext; michael@0: private static Solo sSolo; michael@0: private static Actions sActions; michael@0: michael@0: private static ToolbarComponent sToolbar; michael@0: michael@0: private WaitHelper() { /* To disallow instantiation. */ } michael@0: michael@0: protected static void init(final UITestContext context) { michael@0: sContext = context; michael@0: sSolo = context.getSolo(); michael@0: sActions = context.getActions(); michael@0: michael@0: sToolbar = (ToolbarComponent) context.getComponent(ComponentType.TOOLBAR); michael@0: } michael@0: michael@0: /** michael@0: * Waits for the given {@link solo.Condition} using the default wait duration; will throw an michael@0: * AssertionError if the duration is elapsed and the condition is not satisfied. michael@0: */ michael@0: public static void waitFor(String message, final Condition condition) { michael@0: message = "Waiting for " + message + "."; michael@0: fAssertTrue(message, sSolo.waitForCondition(condition, DEFAULT_MAX_WAIT_MS)); michael@0: } michael@0: michael@0: /** michael@0: * Waits for the given {@link solo.Condition} using the given wait duration; will throw an michael@0: * AssertionError if the duration is elapsed and the condition is not satisfied. michael@0: */ michael@0: public static void waitFor(String message, final Condition condition, final int waitMillis) { michael@0: message = "Waiting for " + message + " with timeout " + waitMillis + "."; michael@0: fAssertTrue(message, sSolo.waitForCondition(condition, waitMillis)); michael@0: } michael@0: michael@0: /** michael@0: * Waits for the Gecko event declaring the page has loaded. Takes in and runs a Runnable michael@0: * that will perform the action that will cause the page to load. michael@0: */ michael@0: public static void waitForPageLoad(final Runnable initiatingAction) { michael@0: fAssertNotNull("initiatingAction is not null", initiatingAction); michael@0: michael@0: // Some changes to the UI occur in response to the same event we listen to for when michael@0: // the page has finished loading (e.g. a page title update). As such, we ensure this michael@0: // UI state has changed before returning from this method; here we store the initial michael@0: // state. michael@0: final ChangeVerifier[] pageLoadVerifiers = PAGE_LOAD_VERIFIERS; michael@0: for (final ChangeVerifier verifier : pageLoadVerifiers) { michael@0: verifier.storeState(); michael@0: } michael@0: michael@0: // Wait for the page load and title changed event. michael@0: final EventExpecter contentEventExpecter = sActions.expectGeckoEvent("DOMContentLoaded"); michael@0: final EventExpecter titleEventExpecter = sActions.expectGeckoEvent("DOMTitleChanged"); michael@0: michael@0: initiatingAction.run(); michael@0: michael@0: contentEventExpecter.blockForEventDataWithTimeout(PAGE_LOAD_WAIT_MS); michael@0: contentEventExpecter.unregisterListener(); michael@0: titleEventExpecter.blockForEventDataWithTimeout(PAGE_LOAD_WAIT_MS); michael@0: titleEventExpecter.unregisterListener(); michael@0: michael@0: // Verify remaining state has changed. michael@0: for (final ChangeVerifier verifier : pageLoadVerifiers) { michael@0: // If we timeout, either the state is set to the same value (which is fine), or michael@0: // the state has not yet changed. Since we can't be sure it will ever change, move michael@0: // on and let the assertions fail if applicable. michael@0: final boolean hasTimedOut = !sSolo.waitForCondition(new Condition() { michael@0: @Override michael@0: public boolean isSatisfied() { michael@0: return verifier.hasStateChanged(); michael@0: } michael@0: }, CHANGE_WAIT_MS); michael@0: michael@0: sContext.dumpLog(verifier.getLogTag(), michael@0: (hasTimedOut ? "timed out." : "was satisfied.")); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Implementations of this interface verify that the state of the test has changed from michael@0: * the invocation of storeState to the invocation of hasStateChanged. A boolean will be michael@0: * returned from hasStateChanged, indicating this change of status. michael@0: */ michael@0: private static interface ChangeVerifier { michael@0: public String getLogTag(); michael@0: michael@0: /** michael@0: * Stores the initial state of the system. This system state is used to diff against michael@0: * the end state to determine if the system has changed. Since this is just a diff michael@0: * (with a timeout), this method could potentially store state inconsistent with michael@0: * what is visible to the user. michael@0: */ michael@0: public void storeState(); michael@0: public boolean hasStateChanged(); michael@0: } michael@0: michael@0: private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier { michael@0: private static final String LOGTAG = ToolbarTitleTextChangeVerifier.class.getSimpleName(); michael@0: michael@0: // A regex that matches the page title that shows up while the page is loading. michael@0: private static final Pattern LOADING_PREFIX = Pattern.compile("[A-Za-z]{3,9}://"); michael@0: michael@0: private CharSequence mOldTitleText; michael@0: michael@0: @Override michael@0: public String getLogTag() { michael@0: return LOGTAG; michael@0: } michael@0: michael@0: @Override michael@0: public void storeState() { michael@0: mOldTitleText = sToolbar.getPotentiallyInconsistentTitle(); michael@0: sContext.dumpLog(LOGTAG, "stored title, \"" + mOldTitleText + "\"."); michael@0: } michael@0: michael@0: @Override michael@0: public boolean hasStateChanged() { michael@0: // TODO: Additionally, consider Solo.waitForText. michael@0: // TODO: Robocop sleeps .5 sec between calls. Cache title view? michael@0: final CharSequence title = sToolbar.getPotentiallyInconsistentTitle(); michael@0: michael@0: // TODO: Handle the case where the URL is shown instead of page title by preference. michael@0: // HACK: We want to wait until the title changes to the state a tester may assert michael@0: // (e.g. the page title). However, the title is set to the URL before the title is michael@0: // loaded from the server and set as the final page title; we ignore the michael@0: // intermediate URL loading state here. michael@0: final boolean isLoading = LOADING_PREFIX.matcher(title).lookingAt(); michael@0: final boolean hasStateChanged = !isLoading && !mOldTitleText.equals(title); michael@0: michael@0: if (hasStateChanged) { michael@0: sContext.dumpLog(LOGTAG, "state changed to title, \"" + title + "\"."); michael@0: } michael@0: return hasStateChanged; michael@0: } michael@0: } michael@0: }