一尘不染

与selenium一起在FindsBy中动态使用

selenium

我有这个规格

Scenario Outline: Display widget
    Given I have a valid connection
    When I navigate to home using <browser>
    Then The element in css selector #<id> > svg > g.x.axis.percent > text:nth-child(1) should be <value>
    Examples:
        | browser | id      | valye  |
        | Chrome  | Widget1 | 213.00 |

使用此页面定义

class BarSummaryPage
{

    [FindsBy(How = How.CssSelector, Using="#{DYNAMIC-ID} > svg > g.x.axis.percent > text:nth-child(1)")]
    private IWebElement Mes;
}

我需要像上面一样动态配置Using属性FindsBy:SEE#{DYNAMIC-ID}


阅读 242

收藏
2020-06-26

共1个答案

一尘不染

据我所知,这并不存在。该FindBy批注采用静态Strings而已。您可能需要自定义修改FindBy注释处理器,类似于此博客的行为:https
://web.archive.org/web/20180612042724/http:
//brimllc.com/2011/01/selenium-2-0-webdriver-扩展查找以支持动态idxpath
/

这里的另一个讨论线程:https
:
//groups.google.com/forum/#!topic/
webdriver /
awxOw0FoiYU,其中Simon
Stewart展示了如何实现此目的的示例。

更新:

我实际上已经实现了它,因为我需要足够的尝试。我没有创建自定义查找器注释(将来可能需要做)。

我编写了实现,ElementLocatorElementLocatorFactory允许使用现有注释指定的定位符进行字符串替换。如果您知道或可以在运行时确定要替换的值,那么它将为您工作。

默认情况下,PageFactory使用classes
DefaultElementLocatorDefaultElementLocatorFactory的实现ElementLocator,并ElementLocatorFactory
interfaces设立批注的处理,但真正的逻辑在Annotations class。我编写了自己的实现ElementLocator,并ElementLocatorFactory编写了自己的版本Annotations来进行处理。我定制classes的源代码与Selenium源代码中的源代码之间只有一些区别。

public class DynamicElementLocator implements ElementLocator {

    private static final XLogger log = XLoggerFactory.getXLogger(DynamicElementLocator.class.getCanonicalName());

    private final SearchContext searchContext;
    private final boolean shouldCache;
    private final By by;
    private WebElement cachedElement;
    private List<WebElement> cachedElementList;

    //The only thing that differs from DefaultElementLocator is
    //the substitutions parameter for this method. 
    public DynamicElementLocator(final SearchContext searchContext, final Field field, final Map<String,String>
            substitutions) {
        log.entry(searchContext, field, substitutions);
        this.searchContext = searchContext;
        //DynamicAnnotations is my implementation of annotation processing
        //that uses the substitutions to find and replace values in the
        //locator strings in the FindBy, FindAll, FindBys annotations 
        DynamicAnnotations annotations = new DynamicAnnotations(field, substitutions);
        shouldCache = annotations.isLookupCached();
        by = annotations.buildBy();
        log.debug("Successful completion of the dynamic element locator");
        log.exit();
    }

    /**
     * Find the element.
     */
    public WebElement findElement() {
        log.entry();
        if (cachedElement != null && shouldCache) {
            return log.exit(cachedElement);
        }

        WebElement element = searchContext.findElement(by);
        if (shouldCache) {
            cachedElement = element;
        }

        return log.exit(element);
    }

    /**
     * Find the element list.
     */
    public List<WebElement> findElements() {
        log.entry();
        if (cachedElementList != null && shouldCache) {
            return log.exit(cachedElementList);
        }

        List<WebElement> elements = searchContext.findElements(by);
        if (shouldCache) {
            cachedElementList = elements;
        }

        return log.exit(elements);
    }
}

这是DynamicElementLocatorFactory

public final class DynamicElementLocatorFactory implements ElementLocatorFactory {
    private final SearchContext searchContext;
    private final Map<String,String> substitutions;

        //The only thing that is different from DefaultElementLocatorFactory
        //is that the constructor for this class takes the substitutions
        //parameter that consists of the key/value mappings to use
        //for substituting keys in locator strings for FindBy, FindAll and     
        //FindBys with values known or determined at runtime.
        public DynamicElementLocatorFactory(final SearchContext searchContext, final Map<String,String> substitutions) {
            this.searchContext = searchContext;
            this.substitutions = substitutions;
        }

        //This produces an instance of the DynamicElementLocator class and
        //specifies the key value mappings to substitute in locator Strings
        public DynamicElementLocator createLocator(final Field field) {
            return new DynamicElementLocator(searchContext, field, substitutions);
        }
    }

这是我的自定义注释处理器。这是大多数工作的地方:

public class DynamicAnnotations extends Annotations {
    private static final XLogger log = XLoggerFactory.getXLogger(DynamicAnnotations.class.getCanonicalName());

    private final Field field;
    private final Map<String,String> substitutions;

    //Again, not much is different from the Selenium default class here
    //other than the additional substitutions parameter
    public DynamicAnnotations(final Field field, final Map<String,String> substitutions) {
        super(field);
        log.entry(field, substitutions);
        this.field = field;
        this.substitutions = substitutions;
        log.debug("Successful completion of the dynamic annotations constructor");
        log.exit();
    }

    public boolean isLookupCached() {
        log.entry();
        return log.exit((field.getAnnotation(CacheLookup.class) != null));
    }

    public By buildBy() {
        log.entry();
        assertValidAnnotations();

        By ans = null;

        FindBys findBys = field.getAnnotation(FindBys.class);
        if (findBys != null) {
            log.debug("Building a chained locator");
            ans = buildByFromFindBys(findBys);
        }

        FindAll findAll = field.getAnnotation(FindAll.class);
        if (ans == null && findAll != null) {
            log.debug("Building a find by one of locator");
            ans = buildBysFromFindByOneOf(findAll);
        }

        FindBy findBy = field.getAnnotation(FindBy.class);
        if (ans == null && findBy != null) {
            log.debug("Building an ordinary locator");
            ans = buildByFromFindBy(findBy);
        }

        if (ans == null) {
            log.debug("No locator annotation specified, so building a locator for id or name based on field name");
            ans = buildByFromDefault();
        }

        if (ans == null) {
            throw log.throwing(new IllegalArgumentException("Cannot determine how to locate element " + field));
        }

        return log.exit(ans);
    }

    protected By buildByFromDefault() {
        log.entry();
        return log.exit(new ByIdOrName(field.getName()));
    }

    protected By buildByFromFindBys(final FindBys findBys) {
        log.entry(findBys);
        assertValidFindBys(findBys);

        FindBy[] findByArray = findBys.value();
        By[] byArray = new By[findByArray.length];
        for (int i = 0; i < findByArray.length; i++) {
            byArray[i] = buildByFromFindBy(findByArray[i]);
        }

        return log.exit(new ByChained(byArray));
    }

    protected By buildBysFromFindByOneOf(final FindAll findBys) {
        log.entry(findBys);
        assertValidFindAll(findBys);

        FindBy[] findByArray = findBys.value();
        By[] byArray = new By[findByArray.length];
        for (int i = 0; i < findByArray.length; i++) {
            byArray[i] = buildByFromFindBy(findByArray[i]);
        }

        return log.exit(new ByAll(byArray));
    }

    protected By buildByFromFindBy(final FindBy findBy) {
        log.entry(findBy);
        assertValidFindBy(findBy);

        By ans = buildByFromShortFindBy(findBy);
        if (ans == null) {
            ans = buildByFromLongFindBy(findBy);
        }

        return log.exit(ans);
    }

    //The only thing that is different from the default Selenium implementation is that the locator string is processed for substitutions by the processForSubstitutions(using) method, which I have added
    protected By buildByFromLongFindBy(final FindBy findBy) {
        log.entry(findBy);
        How how = findBy.how();
        String using = findBy.using();

        switch (how) {
            case CLASS_NAME:
                log.debug("Long FindBy annotation specified lookup by class name, using {}", using);
                String className = processForSubstitutions(using);
                return log.exit(By.className(className));

            case CSS:
                log.debug("Long FindBy annotation specified lookup by css name, using {}", using);
                String css = processForSubstitutions(using);
                return log.exit(By.cssSelector(css));

            case ID:
                log.debug("Long FindBy annotation specified lookup by id, using {}", using);
                String id = processForSubstitutions(using);
                return log.exit(By.id(id));

            case ID_OR_NAME:
                log.debug("Long FindBy annotation specified lookup by id or name, using {}", using);
                String idOrName = processForSubstitutions(using);
                return log.exit(new ByIdOrName(idOrName));

            case LINK_TEXT:
                log.debug("Long FindBy annotation specified lookup by link text, using {}", using);
                String linkText = processForSubstitutions(using);
                return log.exit(By.linkText(linkText));

            case NAME:
                log.debug("Long FindBy annotation specified lookup by name, using {}", using);
                String name = processForSubstitutions(using);
                return log.exit(By.name(name));

            case PARTIAL_LINK_TEXT:
                log.debug("Long FindBy annotation specified lookup by partial link text, using {}", using);
                String partialLinkText = processForSubstitutions(using);
                return log.exit(By.partialLinkText(partialLinkText));

            case TAG_NAME:
                log.debug("Long FindBy annotation specified lookup by tag name, using {}", using);
                String tagName = processForSubstitutions(using);
                return log.exit(By.tagName(tagName));

            case XPATH:
                log.debug("Long FindBy annotation specified lookup by xpath, using {}", using);
                String xpath = processForSubstitutions(using);
                return log.exit(By.xpath(xpath));

            default:
                // Note that this shouldn't happen (eg, the above matches all
                // possible values for the How enum)
                throw log.throwing(new IllegalArgumentException("Cannot determine how to locate element " + field));
        }
    }

    //The only thing that differs from the default Selenium implementation is that the locator string is processed for substitutions by processForSubstitutions(using), which I wrote
    protected By buildByFromShortFindBy(final FindBy findBy) {
        log.entry(findBy);
        log.debug("Building from a short FindBy annotation");

        if (!"".equals(findBy.className())) {
            log.debug("Short FindBy annotation specifies lookup by class name: {}", findBy.className());
            String className = processForSubstitutions(findBy.className());
            return log.exit(By.className(className));
        }

        if (!"".equals(findBy.css())) {
            log.debug("Short FindBy annotation specifies lookup by css");
            String css = processForSubstitutions(findBy.css());
            return log.exit(By.cssSelector(css));
        }

        if (!"".equals(findBy.id())) {
            log.debug("Short FindBy annotation specified lookup by id");
            String id = processForSubstitutions(findBy.id());
            return log.exit(By.id(id));
        }

        if (!"".equals(findBy.linkText())) {
            log.debug("Short FindBy annotation specified lookup by link text");
            String linkText = processForSubstitutions(findBy.linkText());
            return log.exit(By.linkText(linkText));
        }

        if (!"".equals(findBy.name())) {
            log.debug("Short FindBy annotation specified lookup by name");
            String name = processForSubstitutions(findBy.name());
            return log.exit(By.name(name));
        }

        if (!"".equals(findBy.partialLinkText())) {
            log.debug("Short FindBy annotation specified lookup by partial link text");
            String partialLinkText = processForSubstitutions(findBy.partialLinkText());
            return log.exit(By.partialLinkText(partialLinkText));
        }

        if (!"".equals(findBy.tagName())) {
            log.debug("Short FindBy annotation specified lookup by tag name");
            String tagName = processForSubstitutions(findBy.tagName());
            return log.exit(By.tagName(tagName));
        }

        if (!"".equals(findBy.xpath())) {
            log.debug("Short FindBy annotation specified lookup by xpath");
            String xpath = processForSubstitutions(findBy.xpath());
            return log.exit(By.xpath(xpath));
        }

        // Fall through
        log.debug("Locator does not match any expected locator type");
        return log.exit(null);
    }

    //This method is where I find and do replacements. The method looks
    //for instances of ${key} and if there is a key in the substitutions
    //map that is equal to 'key', the substring ${key} is replaced by the
    //value mapped to 'key'
    private String processForSubstitutions(final String locator) {
        log.entry(locator);
        log.debug("Processing locator '{}' for substitutions");
        List<String> subs = Arrays.asList(StringUtils.substringsBetween(locator, "${", "}"));
        log.debug("List of substrings in locator which match substitution pattern: {}", subs);
        String processed = locator;

        for(String sub : subs) {
            log.debug("Processing substring {}", sub);
            //If there is no matching key, the substring "${ ..}" is treated as a literal
            if(substitutions.get(sub) != null) {
                log.debug("Replacing with {}", substitutions.get(sub));
                processed = StringUtils.replace(locator, "${" + sub + "}",substitutions.get(sub));
                log.debug("Locator after substitution: {}", processed);
            }
        }

        return log.exit(processed);
    }

    private void assertValidAnnotations() {
        log.entry();

        FindBys findBys = field.getAnnotation(FindBys.class);
        FindAll findAll = field.getAnnotation(FindAll.class);
        FindBy findBy = field.getAnnotation(FindBy.class);

        if (findBys != null && findBy != null) {
            throw log.throwing(new IllegalArgumentException("If you use a '@FindBys' annotation, " +
                    "you must not also use a '@FindBy' annotation"));
        }
        if (findAll != null && findBy != null) {
            throw log.throwing(new IllegalArgumentException("If you use a '@FindAll' annotation, " +
                    "you must not also use a '@FindBy' annotation"));
        }
        if (findAll != null && findBys != null) {
            throw log.throwing(new IllegalArgumentException("If you use a '@FindAll' annotation, " +
                    "you must not also use a '@FindBys' annotation"));
        }
    }

    private void assertValidFindBys(final FindBys findBys) {
        log.entry(findBys);
        for (FindBy findBy : findBys.value()) {
            assertValidFindBy(findBy);
        }
        log.exit();
    }

    private void assertValidFindAll(final FindAll findBys) {
        log.entry(findBys);
        for (FindBy findBy : findBys.value()) {
            assertValidFindBy(findBy);
        }
        log.exit();
    }

    private void assertValidFindBy(final FindBy findBy) {
        log.entry();
        if (findBy.how() != null) {
            if (findBy.using() == null) {
                throw log.throwing(new IllegalArgumentException(
                        "If you set the 'how' property, you must also set 'using'"));
            }
        }

        Set<String> finders = new HashSet<>();
        if (!"".equals(findBy.using())) {
            log.debug("Locator string is: {}", findBy.using());
            finders.add("how: " + findBy.using());
        }
        if (!"".equals(findBy.className())) {
            log.debug("Class name locator string is {}", findBy.className());
            finders.add("class name:" + findBy.className());
        }

        if (!"".equals(findBy.css())) {
            log.debug("Css locator string is {}", findBy.css());
            finders.add("css:" + findBy.css());
        }

        if (!"".equals(findBy.id())) {
            log.debug("Id locator string is {}", findBy.id());
            finders.add("id: " + findBy.id());
        }

        if (!"".equals(findBy.linkText())) {
            log.debug("Link text locator string is {}", findBy.linkText());
            finders.add("link text: " + findBy.linkText());
        }

        if (!"".equals(findBy.name())) {
            log.debug("Name locator string is {}", findBy.name());
            finders.add("name: " + findBy.name());
        }

        if (!"".equals(findBy.partialLinkText())) {
            log.debug("Partial text locator string is {}", findBy.partialLinkText());
            finders.add("partial link text: " + findBy.partialLinkText());
        }

        if (!"".equals(findBy.tagName())) {
            log.debug("Tag name locator string is {}", findBy.tagName());
            finders.add("tag name: " + findBy.tagName());
        }
        if (!"".equals(findBy.xpath())) {
            log.debug("Xpath locator string is {}", findBy.xpath());
            finders.add("xpath: " + findBy.xpath());
        }

        // A zero count is okay: it means to look by name or id.
        if (finders.size() > 1) {
            throw log.throwing(new IllegalArgumentException(
                    String.format("You must specify at most one location strategy. Number found: %d (%s)",
                            finders.size(), finders.toString())));
        }
    }
}

用法示例:

public class ExampleClass extends SlowLoadableComponent<ExampleClass> {

    private final Map<String, String> substitutions;

    @FindBy(how = How.ID, using = "somelocator_with_a dynamic_${id}")
    private WebElement someElement;

    public ExampleClass(final WebDriver driver, final int
            loadTimeoutInSeconds, final String idValue) {

        substitutions = new HashMap<>(); substitutions.put("id", idValue);

    }

    //When you call PageFactory.initElements, you need to tell it to use the DynamicElementLocatorFactory
    protected void load() {
        PageFactory.initElements(new DynamicElementLocatorFactory(getDriver(), substitutions), this);
    }
}

更新的5/1/2019:我必须在答案开头使用我引用的博客文章的Web存档链接,因为该博客文章的原始链接不可访问。

2020-06-26