org.openqa.selenium.support.pagefactory.AjaxElementLocator.java Source code

Java tutorial

Introduction

Here is the source code for org.openqa.selenium.support.pagefactory.AjaxElementLocator.java

Source

// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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 org.openqa.selenium.support.pagefactory;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.SlowLoadableComponent;

import java.lang.reflect.Field;
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;

/**
 * An element locator that will wait for the specified number of seconds for an element to appear,
 * rather than failing instantly if it's not present. This works by polling the UI on a regular
 * basis. The element returned will be present on the DOM, but may not actually be visible: override
 * {@link #isElementUsable(WebElement)} if this is important to you.
 *
 * Because this class polls the interface on a regular basis, it is strongly recommended that users
 * avoid locating elements by XPath.
 */
public class AjaxElementLocator extends DefaultElementLocator {
    protected final int timeOutInSeconds;
    private final Clock clock;

    /**
     * Use this constructor in order to process custom annotaions.
     *
     * @param context The context to use when finding the element
     * @param timeOutInSeconds How long to wait for the element to appear. Measured in seconds.
     * @param annotations   AbstractAnnotations class implementation
     */
    public AjaxElementLocator(SearchContext context, int timeOutInSeconds, AbstractAnnotations annotations) {
        this(Clock.systemDefaultZone(), context, timeOutInSeconds, annotations);
    }

    public AjaxElementLocator(Clock clock, SearchContext context, int timeOutInSeconds,
            AbstractAnnotations annotations) {
        super(context, annotations);
        this.timeOutInSeconds = timeOutInSeconds;
        this.clock = clock;
    }

    /**
     * Main constructor.
     *
     * @param searchContext The context to use when finding the element
     * @param field The field representing this element
     * @param timeOutInSeconds How long to wait for the element to appear. Measured in seconds.
     */
    public AjaxElementLocator(SearchContext searchContext, Field field, int timeOutInSeconds) {
        this(Clock.systemDefaultZone(), searchContext, field, timeOutInSeconds);
    }

    public AjaxElementLocator(Clock clock, SearchContext searchContext, Field field, int timeOutInSeconds) {
        this(clock, searchContext, timeOutInSeconds, new Annotations(field));
    }

    /**
     * {@inheritDoc}
     *
     * Will poll the interface on a regular basis until the element is present.
     */
    @Override
    public WebElement findElement() {
        SlowLoadingElement loadingElement = new SlowLoadingElement(clock, timeOutInSeconds);
        try {
            return loadingElement.get().getElement();
        } catch (NoSuchElementError e) {
            throw new NoSuchElementException(
                    String.format("Timed out after %d seconds. %s", timeOutInSeconds, e.getMessage()),
                    e.getCause());
        }
    }

    /**
     * {@inheritDoc}
     *
     * Will poll the interface on a regular basis until at least one element is present.
     */
    @Override
    public List<WebElement> findElements() {
        SlowLoadingElementList list = new SlowLoadingElementList(clock, timeOutInSeconds);
        try {
            return list.get().getElements();
        } catch (NoSuchElementError e) {
            return new ArrayList<>();
        }
    }

    /**
     * By default, we sleep for 250ms between polls. You may override this method in order to change
     * how it sleeps.
     *
     * @return Duration to sleep in milliseconds
     */
    protected long sleepFor() {
        return 250;
    }

    /**
     * By default, elements are considered "found" if they are in the DOM. Override this method in
     * order to change whether or not you consider the element loaded. For example, perhaps you need
     * the element to be displayed:
     *
     * <pre>{@code
     *   return element.isDisplayed();
     * }</pre>
     *
     * @param element The element to use
     * @return Whether or not it meets your criteria for "found"
     */
    protected boolean isElementUsable(WebElement element) {
        return true;
    }

    private class SlowLoadingElement extends SlowLoadableComponent<SlowLoadingElement> {
        private NoSuchElementException lastException;
        private WebElement element;

        public SlowLoadingElement(Clock clock, int timeOutInSeconds) {
            super(clock, timeOutInSeconds);
        }

        @Override
        protected void load() {
            // Does nothing
        }

        @Override
        protected long sleepFor() {
            return AjaxElementLocator.this.sleepFor();
        }

        @Override
        protected void isLoaded() throws Error {
            try {
                element = AjaxElementLocator.super.findElement();
                if (!isElementUsable(element)) {
                    throw new NoSuchElementException("Element is not usable");
                }
            } catch (NoSuchElementException e) {
                lastException = e;
                // Should use JUnit's AssertionError, but it may not be present
                throw new NoSuchElementError("Unable to locate the element", e);
            }
        }

        public NoSuchElementException getLastException() {
            return lastException;
        }

        public WebElement getElement() {
            return element;
        }
    }

    private class SlowLoadingElementList extends SlowLoadableComponent<SlowLoadingElementList> {
        private NoSuchElementException lastException;
        private List<WebElement> elements;

        public SlowLoadingElementList(Clock clock, int timeOutInSeconds) {
            super(clock, timeOutInSeconds);
        }

        @Override
        protected void load() {
            // Does nothing
        }

        @Override
        protected long sleepFor() {
            return AjaxElementLocator.this.sleepFor();
        }

        @Override
        protected void isLoaded() throws Error {
            try {
                elements = AjaxElementLocator.super.findElements();
                if (elements.size() == 0) {
                    throw new NoSuchElementException("Unable to locate the element");
                }
                for (WebElement element : elements) {
                    if (!isElementUsable(element)) {
                        throw new NoSuchElementException("Element is not usable");
                    }
                }
            } catch (NoSuchElementException e) {
                lastException = e;
                // Should use JUnit's AssertionError, but it may not be present
                throw new NoSuchElementError("Unable to locate the element", e);
            }
        }

        public NoSuchElementException getLastException() {
            return lastException;
        }

        public List<WebElement> getElements() {
            return elements;
        }
    }

    private static class NoSuchElementError extends Error {
        private NoSuchElementError(String message, Throwable throwable) {
            super(message, throwable);
        }
    }

}