【问题标题】:dynamic Using in FindsBy with selenium在 FindsBy 中动态使用 selenium
【发布时间】:2014-10-14 16:51:26
【问题描述】:

我有这个规格

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}

【问题讨论】:

    标签: selenium selenium-webdriver webdriver


    【解决方案1】:

    据我所知,这不是开箱即用的。 FindBy 注释仅采用静态 Strings。您可能需要自定义修改 FindBy 注释处理器,类似于此博主所做的: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/forum/#!topic/webdriver/awxOw0FoiYU,其中 Simon Stewart 展示了如何实现这一点的示例。

    更新:

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

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

    默认情况下,PageFactory 使用 classes DefaultElementLocatorDefaultElementLocatorFactory 实现 ElementLocatorElementLocatorFactory interfaces 来设置注解的处理,但真正的逻辑在 @ 987654336@。我编写了自己的ElementLocatorElementLocatorFactory 实现,并编写了自己的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);
        }
    }
    

    2019 年 5 月 1 日更新:我不得不为我在答案开头引用的博客文章使用 Web 存档链接,因为无法通过其原始链接访问该博客文章。

    【讨论】:

    • 能否请您更新答案。此 URL 不再有效:brimllc.com/2011/01/…
    • @HelpingHands 完成。我引用了该帖子的网络存档链接。您现在可以单击该链接并查看帖子的内容。
    猜你喜欢
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-03
    • 1970-01-01
    相关资源
    最近更新 更多