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

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

/*
 * 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