All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.loyada.jdollarx.InBrowser Maven / Gradle / Ivy

There is a newer version: 1.5.5
Show newest version
package com.github.loyada.jdollarx;


import com.github.loyada.jdollarx.singlebrowser.InBrowserSinglton;
import org.openqa.selenium.ElementClickInterceptedException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.Wait;

import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;

import static java.util.Arrays.asList;

/**
 * A wrapper around Selenium WebDriver, used for interaction with the browser.
 * In case only a single instance of the browser is used, {@link com.github.loyada.jdollarx.singlebrowser.InBrowserSinglton}
 * offers a simpler API.
 */
public class InBrowser {
    private final WebDriver driver;
    private int implicitTimeout;
    private TimeUnit timeoutUnit;

    /**
     * Creates a connection to a browser, using the given driver
     * @param driver a WebDriver instance
     */
    public InBrowser(final WebDriver driver) {
        this.driver = driver;
    }

    public static InBrowser fromSingleton() {
        InBrowser browser = new InBrowser(InBrowserSinglton.driver);
        browser.timeoutUnit = InBrowserSinglton.getTimeoutUnit();
        browser.implicitTimeout = InBrowserSinglton.getImplicitTimeout();
        return browser;
    }

    /**
     *
     * @return the underlying WebDriver instance
     */
    public WebDriver getDriver() {
        return driver;
    }

    /**
     * Finds an element in the browser, based on the xpath representing el. It is similar to WebDriver.findElement(),
     * If el also has a WebElement (ie: getUnderlyingSource() is not empty), then it looks inside that
     * WebElement. This is useful also to integrate with existing WebDriver code.
     *
     * @param el - the path to find
     * @return - A WebElement instance from selenium, or throws NoSuchElementException exception
     */
    public WebElement find(final Path el) {
        return InBrowserFinder.find(driver, el);
    }

    /**
     * Don't use this directly. There are better ways to do equivalent operation.
     * @param el the path to find
     * @param numberOfOccurrences the base number to find
     * @param relationOperator whether we look for exactly the numberOfOccurrences, at least, or at most occurrences
     * @return the first WebElement found
     */
    public WebElement findPageWithNumberOfOccurrences(final Path el, int numberOfOccurrences, RelationOperator relationOperator) {
        return InBrowserFinder.findPageWithNumberOfOccurrences(driver, el, numberOfOccurrences, relationOperator);
    }

    /**
     * Finds an page in the browser, that does not contain the given path
     * @param el - the path that must not appear in the page
     * @return returns the page element or raises NoSuchElementException
     */
    public WebElement findPageWithout(final Path el) {
        return InBrowserFinder.findPageWithout(driver, el);
    }

    /**
     * Finds all elements in the browser, based on the xpath representing el. It is similar to WebDriver.findElements(),
     * If el also has a WebElement (ie: getUnderlyingSource() is not empty), then it looks inside that
     * WebElement. This is useful also to integrate with existing WebDriver code.
     *
     * @param el - the path to find
     * @return - A list of WebElement from selenium, or throws NoSuchElementException exception
     */
    public List findAll(final Path el) {
        return InBrowserFinder.findAll(driver, el);

    }

    /**
     * Get a specific attribute of all the elements matching the given path.
     * The implementation is optimized, and avoids multiple round trips to browser.
     *
     * @param el the elements to find
     * @param attribute - the attribute to extract
     * @return a list of string/int. In case the el is not found, it returns an empty list.
     */
    public List getAttributeOfAll(final Path el, String attribute) {
        return InBrowserFinder.getAttributeOfAll(driver, el, attribute);
    }


    /**
     * Count number of elements that are currently present.
     * @param el the element definition
     * @return number of elements
     */
    public int countAll(Path el) {
        return InBrowserFinder.countAll(driver, el);
    }

    /////////////// predicates

    /**
     * Returns the number of elements in the browser that match the given path. Typically you
     * should not use this method directly. Instead, use CustomMatchers.
     * @param el the element to find
     * @return the number of elements in the browser that match the given path
     */
    public Integer numberOfAppearances(Path el) {
        return InBrowserFinder.findAll(driver, el).size();
    }

    /**
     * is the element present? Typically you should not use this method directly.
     * Instead, use CustomMatchers.
     * @param el the path to find
     * @return true if the element is present
     */
    public boolean isPresent(Path el) {
        try {
            find(el);
            return true;
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * is the element present? Typically you should not use this method directly.
     * Instead, use CustomMatchers.
     * @param el the path to find
     * @return true if it is not present
     */
    public boolean isNotPresent(Path el) {
        try {
            findPageWithout(el);
            return true;
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * is the element present and enabled? Typically you should not use this method directly.
     * Instead, use CustomMatchers.
     * @param el the element
     * @return true if it is present and enabled
     */
    public boolean isEnabled(Path el) {
        try {
            return find(el).isEnabled();
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * is the element present and selected? Typically you should not use this method directly.
     * Instead, use CustomMatchers.
     * @param el the element
     * @return true if it is present and selected
     */
    public boolean isSelected(Path el) {
        try {
            return find(el).isSelected();
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * is the element present and displayed? Typically you should not use this method directly.
     * Instead, use CustomMatchers. Also, this is limited to checking the inlined css style, so
     * it is quite limited.
     * @param el the element
     * @return true if it is present and selected
     */
    public boolean isDisplayed(Path el) {
        try {
            return find(el).isDisplayed();
        } catch (NoSuchElementException e) {
            return false;
        }
    }

    /**
     * Assuming the element exists, check if it is covered by another element
     * @param el the wanted element
     * @return is it covered
     */
    public boolean isCovered(Path el) {
        String script = getScriptToFindIfElementIsExposed();
        Object res = ((JavascriptExecutor) driver).executeScript(script, find(el));
        return (boolean) res;
    }

    private static String getScriptToFindIfElementIsExposed() {
        return "const rect=arguments[0].getBoundingClientRect();" +
                "const x=rect.left;" +
                "const y=rect.top;" +
                "topElt=document.elementFromPoint(x,y);" +
                "return !arguments[0].isSameNode(topElt);";
    }



    ////////////////////////////////////////////////////
    //// actions

    /**
     * Click on the first element that fits the given path. Only works for clickable elements.
     * If the element is currently not clickable, will wait up to a second for it to be clickable.
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement clickOn(Path el) {
        try {
            return Operations.doWithRetriesForException(() -> {
                WebElement found = find(el);
                Wait wait = getWaiter();
                wait.until(ExpectedConditions.elementToBeClickable(found));
                found.click();
                return found;
            }, ElementClickInterceptedException.class, 3, 500);
        } catch (Exception e) {
            if (e instanceof RuntimeException){
                throw (RuntimeException)e;
            }
            throw new RuntimeException(e);
        }
    }

    /**
     * Click at the location the first element that fits the given path. Does not require a clickable element.
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement clickAt(Path el) {
        WebElement found = find(el);
        preformActions(e -> e.moveToElement(found).click());
        return found;
    }

    /**
     * Context-click (right click) at the location the first element that fits the given path. Does not require a clickable element.
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement contextClick(Path el) {
        WebElement found = find(el);
        preformActions(e -> e.moveToElement(found).contextClick());
        return found;
    }

    /**
     * Context-click (right click) at the location the first element that fits the given path. Does not require a clickable element.
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement rightClick(Path el) {
        return contextClick(el);
    }

    /**
     * Hover over the location of the first element that fits the given path
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement hoverOver(Path el) {
        final WebElement found = find(el);
        preformActions(e -> e.moveToElement(found));
        return found;
    }

    /**
     * Scroll to the location of the first element that fits the given path
     * @param el the element
     * @return the clicked on WebElement
     */
    public WebElement scrollTo(Path el) {
        return hoverOver(el);
    }

    private void preformActions(UnaryOperator func) {
        final Actions actionBuilder = new Actions(driver);
        func.apply(actionBuilder).build().perform();
    }

    /**
     * Doubleclick the location of the first element that fits the given path.
     * @param el the element
     */
    public void doubleClickOn(Path el) {
        WebElement found = find(el);
        Wait wait = getWaiter();
        wait.until(ExpectedConditions.elementToBeClickable(found));
        preformActions(e -> e.doubleClick(found));
    }

    /**
     * send keys to the browser, or to a specific element. Two flavors of use:
     * browser.sendKeys("abc").toBrowser();
     * browser.sendKeys("abc").to(path);
     *
     * @param charsToSend  The characters to send. Can be "abc" or "a", "b", "c"
     * @return a KeySender instance that allows to send keys to the browser in general, or to a specific DOM element
     */
    public Operations.KeysSender sendKeys(CharSequence... charsToSend) {
        return new Operations.KeysSender(driver, charsToSend);
    }

    /**
     * Press key down in the browser, or on a specific element. Two flavors of use:
     * browser.pressKeyDown(Keys.TAB).inBrowser();
     * browser.pressKeyDown(Keys.TAB).on(path);
     *
     * @param thekey a key to press
     * @return returns a KeysDown instance that allows to press a key on the browser in general or on a specific DOM element
     */
    public Operations.KeysDown pressKeyDown(CharSequence thekey) {
        return new Operations.KeysDown(driver, thekey);
    }

    /**
     * Release key down in the browser, or on a specific element. Two flavors of use:
     * 
     * {@code
     *    browser.releaseKey(Keys.TAB).inBrowser();
     *    browser.releaseKey(Keys.TAB).on(path);
     * }
     * 
* * @param thekey a key to release * @return returns a ReleaseKey instance that allows to release on the browser in general or on a specific DOM element */ public Operations.ReleaseKey releaseKey(CharSequence thekey) { return new Operations.ReleaseKey(driver, thekey); } /** * scroll the browser. Several flavors of use: *
     * {@code
     *    browser.scroll().to(path);
     *    browser.scroll().left(50);
     *    browser.scroll().right(50);
     *    browser.scroll().up(50);
     *    browser.scroll().down(50);
     * }
     * 
* * @return a Scroll instance that allows to scroll by offset or to a location of a DOM element */ public Operations.Scroll scroll() { return new Operations.Scroll(driver); } public Operations.ScrollElement scrollElement(Path wrapper) { return new Operations.ScrollElement(driver, wrapper); } public Operations.ScrollElement scrollElementWithStepOverride(Path wrapper, int step) { return new Operations.ScrollElement(driver, wrapper, step); } /** * Drag and drop in the browser. Several flavors of use: * browser.dragAndDrop(source).to(target); * browser.dragAndDrop(source).to(xCor, yCor); * * @param path the source element * @return a DragAndDrop instance, that allows to drag and drop to a location or to another DOM element */ public Operations.DragAndDrop dragAndDrop(Path path) { return new Operations.DragAndDrop(driver, path); } private Wait getWaiter() { final Duration duration; if (implicitTimeout!=0 && timeoutUnit!=null) { long implicitTimeoutInMillis = TimeUnit.MILLISECONDS.convert(implicitTimeout, timeoutUnit); duration = Duration.ofMillis(implicitTimeoutInMillis); } else { duration = Duration.ofMillis(10000); } return new FluentWait<>(driver).withTimeout(duration) .pollingEvery(Duration.ofMillis(50)) .ignoring(org.openqa.selenium.NoSuchElementException.class); } /** * Get a Selenium select element, which provides a high level API to interacting with a "select" menu. * Since the Selenium API is good enough, there was no need to create a specialized dollarx version. * @param el - must be a "select" path, with "option" elements for the various selectable options. * @return org.openqa.selenium.support.ui.Select instance */ public Select getSelect(final Path el) { return new Select(find(el)); } /** * Get all classes of given Path element. * @param el the element to look for * @return a list of classes */ public List getCssClasses(final Path el) { String[] res = find(el).getAttribute("class").trim().split("\\s+"); return asList(res); } public void setImplicitTimeout(int implicitTimeout, TimeUnit unit) { this.implicitTimeout = implicitTimeout; this.timeoutUnit = unit; driver.manage().timeouts().implicitlyWait(implicitTimeout, unit); } public int getImplicitTimeout() { return implicitTimeout; } public TimeUnit getTimeoutUnit() { return timeoutUnit; } public long getImplicitTimeoutInMillisec() { return timeoutUnit.toMillis(implicitTimeout); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy