001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 */
018
019package org.openqa.selenium.support.ui;
020
021import static java.util.concurrent.TimeUnit.MILLISECONDS;
022import static java.util.concurrent.TimeUnit.SECONDS;
023
024import org.openqa.selenium.NotFoundException;
025import org.openqa.selenium.WebDriver;
026import org.openqa.selenium.WebDriverException;
027
028import com.google.common.base.Function;
029
030/**
031 * An implementation of the Wait interface that makes use of WebDriver. The expected usage is in conjunction with the
032 * {@link ExpectedCondition} interface.
033 * <p>
034 * Because waiting for elements to appear on a page is such a common use-case, this class will silently swallow
035 * NotFoundException whilst waiting.
036 */
037public class WebDriverWait implements Wait<WebDriver> {
038    private final Clock clock;
039
040    private final WebDriver driver;
041
042    private final long timeOutInMillis;
043
044    private final long sleepTimeOut;
045
046    private static final long DEFAULT_SLEEP_TIMEOUT = 500;
047
048    /**
049     * @param driver The WebDriver instance to pass to the expected conditions
050     * @param timeOutInSeconds The timeout in seconds when an expectation is called
051     */
052    public WebDriverWait(WebDriver driver, long timeOutInSeconds) {
053        this(new SystemClock(), driver, timeOutInSeconds, DEFAULT_SLEEP_TIMEOUT);
054    }
055
056    /**
057     * @param clock The clock to use when measuring the timeout
058     * @param driver The WebDriver instance to pass to the expected conditions
059     * @param timeOutInSeconds The timeout in seconds when an expectation is
060     * @param sleepTimeOut The timeout used whilst sleeping. Defaults to 500ms called
061     */
062    protected WebDriverWait(Clock clock, WebDriver driver, long timeOutInSeconds, long sleepTimeOut) {
063        this.clock = clock;
064        this.driver = driver;
065        this.timeOutInMillis = SECONDS.toMillis(timeOutInSeconds);
066        this.sleepTimeOut = sleepTimeOut;
067    }
068
069    @Override
070    public <T> T until(Function<WebDriver, T> isTrue) {
071        long end = clock.laterBy(timeOutInMillis);
072        NotFoundException lastException = null;
073
074        while (clock.isNowBefore(end)) {
075            try {
076                T value = isTrue.apply(driver);
077
078                if (value != null && Boolean.class.equals(value.getClass())) {
079                    if (Boolean.TRUE.equals(value)) {
080                        return value;
081                    }
082                } else if (value != null) {
083                    return value;
084                }
085            } catch (NotFoundException e) {
086                // Common case in many conditions, so swallow here, but be ready to
087                // rethrow if it the element never appears.
088                lastException = e;
089            }
090            sleep();
091        }
092
093        throwTimeoutException(
094                String.format("Timed out after %d seconds", SECONDS.convert(timeOutInMillis, MILLISECONDS)),
095                lastException);
096
097        throw new IllegalStateException("'throwTimeoutException' should have thrown an exception!");
098    }
099
100    /**
101     * Override this method to throw an exception that is idiomatic for a given test infrastructure. E.g. JUnit4 should
102     * throw an {@link AssertionError}
103     */
104    protected void throwTimeoutException(String message, Exception lastException) {
105        throw new TimeoutException(message, lastException);
106    }
107
108    private void sleep() {
109        try {
110            Thread.sleep(sleepTimeOut);
111        } catch (InterruptedException e) {
112            Thread.currentThread().interrupt();
113            throw new WebDriverException(e);
114        }
115    }
116}