mobile/android/base/tests/helpers/WaitHelper.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/tests/helpers/WaitHelper.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,171 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.tests.helpers;
     1.9 +
    1.10 +import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
    1.11 +import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
    1.12 +
    1.13 +import java.util.regex.Pattern;
    1.14 +
    1.15 +import org.mozilla.gecko.Actions;
    1.16 +import org.mozilla.gecko.Actions.EventExpecter;
    1.17 +import org.mozilla.gecko.tests.UITestContext;
    1.18 +import org.mozilla.gecko.tests.UITestContext.ComponentType;
    1.19 +import org.mozilla.gecko.tests.components.ToolbarComponent;
    1.20 +
    1.21 +import com.jayway.android.robotium.solo.Condition;
    1.22 +import com.jayway.android.robotium.solo.Solo;
    1.23 +
    1.24 +/**
    1.25 + * Provides functionality related to waiting on certain events to happen.
    1.26 + */
    1.27 +public final class WaitHelper {
    1.28 +    // TODO: Make public for when Solo.waitForCondition is used directly (i.e. do not want
    1.29 +    // assertion from waitFor)?
    1.30 +    private static final int DEFAULT_MAX_WAIT_MS = 5000;
    1.31 +    private static final int PAGE_LOAD_WAIT_MS = 10000;
    1.32 +    private static final int CHANGE_WAIT_MS = 10000;
    1.33 +
    1.34 +    // TODO: via lucasr - Add ThrobberVisibilityChangeVerifier?
    1.35 +    private static final ChangeVerifier[] PAGE_LOAD_VERIFIERS = new ChangeVerifier[] {
    1.36 +        new ToolbarTitleTextChangeVerifier()
    1.37 +    };
    1.38 +
    1.39 +    private static UITestContext sContext;
    1.40 +    private static Solo sSolo;
    1.41 +    private static Actions sActions;
    1.42 +
    1.43 +    private static ToolbarComponent sToolbar;
    1.44 +
    1.45 +    private WaitHelper() { /* To disallow instantiation. */ }
    1.46 +
    1.47 +    protected static void init(final UITestContext context) {
    1.48 +        sContext = context;
    1.49 +        sSolo = context.getSolo();
    1.50 +        sActions = context.getActions();
    1.51 +
    1.52 +        sToolbar = (ToolbarComponent) context.getComponent(ComponentType.TOOLBAR);
    1.53 +    }
    1.54 +
    1.55 +    /**
    1.56 +     * Waits for the given {@link solo.Condition} using the default wait duration; will throw an
    1.57 +     * AssertionError if the duration is elapsed and the condition is not satisfied.
    1.58 +     */
    1.59 +    public static void waitFor(String message, final Condition condition) {
    1.60 +        message = "Waiting for " + message + ".";
    1.61 +        fAssertTrue(message, sSolo.waitForCondition(condition, DEFAULT_MAX_WAIT_MS));
    1.62 +    }
    1.63 +
    1.64 +    /**
    1.65 +     * Waits for the given {@link solo.Condition} using the given wait duration; will throw an
    1.66 +     * AssertionError if the duration is elapsed and the condition is not satisfied.
    1.67 +     */
    1.68 +    public static void waitFor(String message, final Condition condition, final int waitMillis) {
    1.69 +        message = "Waiting for " + message + " with timeout " + waitMillis + ".";
    1.70 +        fAssertTrue(message, sSolo.waitForCondition(condition, waitMillis));
    1.71 +    }
    1.72 +
    1.73 +    /**
    1.74 +     * Waits for the Gecko event declaring the page has loaded. Takes in and runs a Runnable
    1.75 +     * that will perform the action that will cause the page to load.
    1.76 +     */
    1.77 +    public static void waitForPageLoad(final Runnable initiatingAction) {
    1.78 +        fAssertNotNull("initiatingAction is not null", initiatingAction);
    1.79 +
    1.80 +        // Some changes to the UI occur in response to the same event we listen to for when
    1.81 +        // the page has finished loading (e.g. a page title update). As such, we ensure this
    1.82 +        // UI state has changed before returning from this method; here we store the initial
    1.83 +        // state.
    1.84 +        final ChangeVerifier[] pageLoadVerifiers = PAGE_LOAD_VERIFIERS;
    1.85 +        for (final ChangeVerifier verifier : pageLoadVerifiers) {
    1.86 +            verifier.storeState();
    1.87 +        }
    1.88 +
    1.89 +        // Wait for the page load and title changed event.
    1.90 +        final EventExpecter contentEventExpecter = sActions.expectGeckoEvent("DOMContentLoaded");
    1.91 +        final EventExpecter titleEventExpecter = sActions.expectGeckoEvent("DOMTitleChanged");
    1.92 +
    1.93 +        initiatingAction.run();
    1.94 +
    1.95 +        contentEventExpecter.blockForEventDataWithTimeout(PAGE_LOAD_WAIT_MS);
    1.96 +        contentEventExpecter.unregisterListener();
    1.97 +        titleEventExpecter.blockForEventDataWithTimeout(PAGE_LOAD_WAIT_MS);
    1.98 +        titleEventExpecter.unregisterListener();
    1.99 +
   1.100 +        // Verify remaining state has changed.
   1.101 +        for (final ChangeVerifier verifier : pageLoadVerifiers) {
   1.102 +            // If we timeout, either the state is set to the same value (which is fine), or
   1.103 +            // the state has not yet changed. Since we can't be sure it will ever change, move
   1.104 +            // on and let the assertions fail if applicable.
   1.105 +            final boolean hasTimedOut = !sSolo.waitForCondition(new Condition() {
   1.106 +                @Override
   1.107 +                public boolean isSatisfied() {
   1.108 +                    return verifier.hasStateChanged();
   1.109 +                }
   1.110 +            }, CHANGE_WAIT_MS);
   1.111 +
   1.112 +            sContext.dumpLog(verifier.getLogTag(),
   1.113 +                    (hasTimedOut ? "timed out." : "was satisfied."));
   1.114 +        }
   1.115 +    }
   1.116 +
   1.117 +    /**
   1.118 +     * Implementations of this interface verify that the state of the test has changed from
   1.119 +     * the invocation of storeState to the invocation of hasStateChanged. A boolean will be
   1.120 +     * returned from hasStateChanged, indicating this change of status.
   1.121 +     */
   1.122 +    private static interface ChangeVerifier {
   1.123 +        public String getLogTag();
   1.124 +
   1.125 +        /**
   1.126 +         * Stores the initial state of the system. This system state is used to diff against
   1.127 +         * the end state to determine if the system has changed. Since this is just a diff
   1.128 +         * (with a timeout), this method could potentially store state inconsistent with
   1.129 +         * what is visible to the user.
   1.130 +         */
   1.131 +        public void storeState();
   1.132 +        public boolean hasStateChanged();
   1.133 +    }
   1.134 +
   1.135 +    private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier {
   1.136 +        private static final String LOGTAG = ToolbarTitleTextChangeVerifier.class.getSimpleName();
   1.137 +
   1.138 +        // A regex that matches the page title that shows up while the page is loading.
   1.139 +        private static final Pattern LOADING_PREFIX = Pattern.compile("[A-Za-z]{3,9}://");
   1.140 +
   1.141 +        private CharSequence mOldTitleText;
   1.142 +
   1.143 +        @Override
   1.144 +        public String getLogTag() {
   1.145 +            return LOGTAG;
   1.146 +        }
   1.147 +
   1.148 +        @Override
   1.149 +        public void storeState() {
   1.150 +            mOldTitleText = sToolbar.getPotentiallyInconsistentTitle();
   1.151 +            sContext.dumpLog(LOGTAG, "stored title, \"" + mOldTitleText + "\".");
   1.152 +        }
   1.153 +
   1.154 +        @Override
   1.155 +        public boolean hasStateChanged() {
   1.156 +            // TODO: Additionally, consider Solo.waitForText.
   1.157 +            // TODO: Robocop sleeps .5 sec between calls. Cache title view?
   1.158 +            final CharSequence title = sToolbar.getPotentiallyInconsistentTitle();
   1.159 +
   1.160 +            // TODO: Handle the case where the URL is shown instead of page title by preference.
   1.161 +            // HACK: We want to wait until the title changes to the state a tester may assert
   1.162 +            // (e.g. the page title). However, the title is set to the URL before the title is
   1.163 +            // loaded from the server and set as the final page title; we ignore the
   1.164 +            // intermediate URL loading state here.
   1.165 +            final boolean isLoading = LOADING_PREFIX.matcher(title).lookingAt();
   1.166 +            final boolean hasStateChanged = !isLoading && !mOldTitleText.equals(title);
   1.167 +
   1.168 +            if (hasStateChanged) {
   1.169 +                sContext.dumpLog(LOGTAG, "state changed to title, \"" + title + "\".");
   1.170 +            }
   1.171 +            return hasStateChanged;
   1.172 +        }
   1.173 +    }
   1.174 +}

mercurial