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

geb.navigator.SearchContextBasedBasicLocator.groovy Maven / Gradle / Ivy

Go to download

Geb (pronounced "jeb") is a browser automation solution. It brings together the power of WebDriver, the elegance of jQuery content selection, the robustness of Page Object modelling and the expressiveness of the Groovy language.

There is a newer version: 7.0
Show newest version
/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed 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 geb.navigator

import geb.navigator.factory.NavigatorFactory
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FromString
import org.openqa.selenium.By
import org.openqa.selenium.SearchContext
import org.openqa.selenium.WebElement

import java.util.function.Supplier

import static geb.navigator.Locator.MATCH_ALL_SELECTOR
import static geb.navigator.BasicLocator.DYNAMIC_ATTRIBUTE_NAME
import static geb.navigator.WebElementPredicates.matches

class SearchContextBasedBasicLocator implements BasicLocator {

    private static final Map BY_SELECTING_ATTRIBUTES = [
            id   : By.&id,
            class: By.&className,
            name : By.&name
    ]

    public static final List NON_SELECTOR_TRANSLATABLE_ATTRIBUTES = ["text", DYNAMIC_ATTRIBUTE_NAME]

    private final Iterable searchContexts
    private final NavigatorFactory navigatorFactory

    SearchContextBasedBasicLocator(SearchContext searchContext, NavigatorFactory navigatorFactory) {
        this([searchContext], navigatorFactory)
    }

    SearchContextBasedBasicLocator(Iterable searchContexts, NavigatorFactory navigatorFactory) {
        this.searchContexts = searchContexts
        this.navigatorFactory = navigatorFactory
    }

    @Override
    Navigator find(By bySelector) {
        find(false, bySelector)
    }

    @Override
    Navigator find(By bySelector, int index) {
        find(false, bySelector, index)
    }

    @Override
    Navigator find(Map attributes, int index) {
        find(attributes) { dynamic, bySelector, filteredAttributes ->
            find(dynamic, bySelector, filteredAttributes, index)
        }
    }

    @Override
    Navigator find(Map attributes, Range range) {
        find(attributes) { dynamic, bySelector, filteredAttributes ->
            find(dynamic, bySelector, filteredAttributes, range)
        }
    }

    @Override
    Navigator find(Map attributes, By bySelector, int index) {
        find(dynamic(attributes), bySelector, attributes, index)
    }

    @Override
    Navigator find(Map attributes, By bySelector, Range range) {
        find(dynamic(attributes), bySelector, attributes, range)
    }

    @Override
    Navigator find(Map attributes, By bySelector) {
        find(dynamic(attributes), bySelector, attributes)
    }

    @Override
    Navigator find(Map attributes, String selector) {
        find(attributes, selector) { dynamic, bySelector, filteredAttributes ->
            find(dynamic, bySelector, filteredAttributes)
        }
    }

    @Override
    Navigator find(Map attributes, String selector, int index) {
        find(attributes, selector) { dynamic, bySelector, filteredAttributes ->
            find(dynamic, bySelector, filteredAttributes, index)
        }
    }

    @Override
    Navigator find(Map attributes, String selector, Range range) {
        find(attributes, selector) { dynamic, bySelector, filteredAttributes ->
            find(dynamic, bySelector, filteredAttributes, range)
        }
    }

    protected Navigator find(
            Map attributes,
            String selector = MATCH_ALL_SELECTOR,
            @ClosureParams(value = FromString, options = "Boolean,org.openqa.selenium.By,Map") Closure navigatorFromBy
    ) {
        def attributesCopy = attributes.clone()
        def selectedUsingBy = findUsingByIfPossible(attributesCopy, selector, navigatorFromBy)
        if (selectedUsingBy != null) {
            return selectedUsingBy
        }

        def optimizedSelector = optimizeSelector(selector, attributesCopy)
        if (optimizedSelector) {
            navigatorFromBy.call(dynamic(attributes), By.cssSelector(optimizedSelector), attributesCopy)
        } else {
            find(attributes, MATCH_ALL_SELECTOR)
        }
    }

    protected Navigator navigatorFor(Iterable contextElements) {
        navigatorFactory.createFromWebElements(contextElements)
    }

    protected Navigator findUsingByIfPossible(
            Map attributes,
            String selector,
            @ClosureParams(value = FromString, options = "Boolean,org.openqa.selenium.By,Map") Closure navigatorFromBy
    ) {
        if (attributes.size() == 1 && selector == MATCH_ALL_SELECTOR) {
            BY_SELECTING_ATTRIBUTES.findResult { String attributeName, Closure byFactory ->
                if (hasStringValueForKey(attributes, attributeName)) {
                    navigatorFromBy.call(false, byFactory.call(attributes[attributeName]), [:])
                }
            }
        }
    }

    protected boolean hasStringValueForKey(Map attributes, String key) {
        attributes.containsKey(key) && attributes[key] instanceof CharSequence
    }

    /**
     * Optimizes the selector by translating attributes map into a css attribute selector if possible.
     * Note this method has a side-effect in that it _removes_ those keys from the predicates map.
     */
    protected String optimizeSelector(String selector, Map attributes) {
        if (!selector) {
            return selector
        }

        def buffer = new StringBuilder(selector)
        for (def it = attributes.entrySet().iterator(); it.hasNext();) {
            def attribute = it.next()
            if (!(attribute.key in NON_SELECTOR_TRANSLATABLE_ATTRIBUTES) && attribute.value instanceof CharSequence) {
                def attributeValue = attribute.value.toString()
                if (attribute.key == "class") {
                    attributeValue.split(/\s+/).each { className ->
                        buffer << "." << CssSelector.escape(className)
                    }
                } else {
                    buffer << """[${attribute.key}="${CssSelector.escape(attributeValue)}"]"""
                }
                it.remove()
            }
        }

        if (buffer[0] == MATCH_ALL_SELECTOR && buffer.length() > 1) {
            buffer.deleteCharAt(0)
        }
        buffer.toString()
    }

    protected Navigator find(boolean dynamic, By bySelector, Map attributes = [:]) {
        toNavigator(dynamic, elementsSupplier(bySelector, attributes))
    }

    protected Navigator find(boolean dynamic, By bySelector, Map attributes = [:], int index) {
        toNavigator(dynamic, elementsSupplier(bySelector, attributes, index))
    }

    protected Navigator find(boolean dynamic, By bySelector, Map attributes, Range range) {
        toNavigator(dynamic, elementsSupplier(bySelector, attributes, range))
    }

    protected Supplier> elementsSupplier(By bySelector, Map attributes) {
        { ->
            searchContexts.collectMany { it.findElements(bySelector) }.findAll { matches(it, attributes) }
        }
    }

    protected Supplier> elementsSupplier(By bySelector, Map attributes, int index) {
        { ->
            [elementsSupplier(bySelector, attributes).get()[index]]
        }
    }

    protected Supplier> elementsSupplier(By bySelector, Map attributes, Range range) {
        { ->
            def elements = elementsSupplier(bySelector, attributes).get()
            if (elements) {
                elements.toList()[range]
            }
        }
    }

    protected Iterable toDynamicIterable(Supplier> contextElementsSupplier) {
        { -> contextElementsSupplier.get().iterator() } as Iterable
    }

    protected Navigator toNavigator(boolean dynamic, Supplier> contextElementsSupplier) {
        navigatorFor(dynamic ? toDynamicIterable(contextElementsSupplier) : contextElementsSupplier.get())
    }

    protected boolean dynamic(Map attributes) {
        attributes[DYNAMIC_ATTRIBUTE_NAME]
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy