Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
837 views
in Technique[技术] by (71.8m points)

dynamic Using in FindsBy with selenium

I have this spec

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 |

With this page definition

class BarSummaryPage
{

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

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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/

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

UPDATE:

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).

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.

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

And here is the 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), whi

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...