我有这个规格
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}
Using
FindsBy
#{DYNAMIC-ID}
据我所知,这并不存在。该FindBy批注采用静态Strings而已。您可能需要自定义修改FindBy注释处理器,类似于此博客的行为:https ://web.archive.org/web/20180612042724/http: //brimllc.com/2011/01/selenium-2-0-webdriver-扩展查找以支持动态idxpath /
FindBy
Strings
这里的另一个讨论线程:https : //groups.google.com/forum/#!topic/ webdriver / awxOw0FoiYU,其中Simon Stewart展示了如何实现此目的的示例。
更新:
我实际上已经实现了它,因为我需要足够的尝试。我没有创建自定义查找器注释(将来可能需要做)。
我编写了实现,ElementLocator并ElementLocatorFactory允许使用现有注释指定的定位符进行字符串替换。如果您知道或可以在运行时确定要替换的值,那么它将为您工作。
ElementLocator
ElementLocatorFactory
默认情况下,PageFactory使用classes DefaultElementLocator和DefaultElementLocatorFactory的实现ElementLocator,并ElementLocatorFactory interfaces设立批注的处理,但真正的逻辑在Annotations class。我编写了自己的实现ElementLocator,并ElementLocatorFactory编写了自己的版本Annotations来进行处理。我定制classes的源代码与Selenium源代码中的源代码之间只有一些区别。
PageFactory
classes
DefaultElementLocator
DefaultElementLocatorFactory
interfaces
Annotations class
Annotations
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:
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存档链接,因为该博客文章的原始链接不可访问。