与硒一起在FindsBy中动态使用 [英] dynamic Using in FindsBy with selenium

查看:62
本文介绍了与硒一起在FindsBy中动态使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个规格

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;
}

我需要在FindsBy动态中配置Using属性,如上:SEE #{DYNAMIC-ID}

I need to configure the Using property in FindsBy dynamic, like above: SEE #{DYNAMIC-ID}

推荐答案

据我所知,这并不存在. FindBy批注仅采用静态Strings.您可能需要自定义修改FindBy注释处理器,类似于此博客作者所做的:

As far as I know, this doesn't exist out of the box. The FindBy annotation takes static Strings only. You probably need to custom modify the FindBy annotation processor similarly to what this blogger did: https://web.archive.org/web/20180612042724/http://brimllc.com/2011/01/selenium-2-0-webdriver-extending-findby-annotation-to-support-dynamic-idxpath/

此处还有另一个讨论线程: https://groups.google.com/论坛/#!topic/webdriver/awxOw0FoiYU ,其中西蒙·斯图尔特(Simon Stewart)展示了如何实现此目标的示例.

Another discussion thread here: https://groups.google.com/forum/#!topic/webdriver/awxOw0FoiYU where Simon Stewart shows an example of how this could be accomplished.

更新:

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

I have actually implemented this because I needed it enough to try. I didn't create a custom finder annotation (which I may have to do in the future).

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

I wrote implementations for ElementLocator and ElementLocatorFactory that allow for string substitutions for locators specified using the existing annotations. If you know, or can determine, at runtime the values to substitute, this will work for you.

默认情况下,PageFactory使用ElementLocatorElementLocatorFactory interfacesclasses DefaultElementLocatorDefaultElementLocatorFactory实现来设置注释的处理,但是真正的逻辑在Annotations class.我编写了自己的ElementLocatorElementLocatorFactory实现,并编写了自己的Annotations版本进行处理.我自定义的classes的源代码与Selenium源代码中的源代码之间只有几处区别.

By default, PageFactory uses the classes DefaultElementLocator and DefaultElementLocatorFactory implementations of the ElementLocator and ElementLocatorFactory interfaces for setting up the processing of annotations, but the real logic is in the Annotations class. I wrote my own implementations of ElementLocator, and ElementLocatorFactory and wrote my own version of Annotations to do the processing. There are just a few differences between the source of my customized classes and the ones that are in the Selenium source code.

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);
        }
    }

这是我的自定义注释处理器.这是大部分工作的地方:

And here is my custom annotation processor. This is where most of the work was:

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存档链接,因为该博客文章无法通过其原始链接访问.

UPDATED 5/1/2019: I had to use a Web Archive link for the blog post I referenced at the beginning of my answer because that blog post is not accessible at its original link.

这篇关于与硒一起在FindsBy中动态使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆