您需要创建自定义注解、元素工厂和装饰器来完成此操作。在下面的代码中添加适当的导入。
注释
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MultipleFind {
String delimiter() default "@@";
String[] elementDetails();
}
Sample PageObject - usernameInput@@ID@@username - 第一部分将用作key在地图中。 second part 是定位器策略,这将对应于How.java enum 中的值。 第三部分是定位器。此页面对象可以包含标准注释,如 FindBy 等以及自定义注释。
public class LoginPageObject extends BasePageObject<LoginPageObject> {
//Conventional declaration
@FindBy(how=How.ID, using="username")
private WebElement usernameInput;
//Conventional declaration
@FindBy(how=How.NAME, using="pwd")
private WebElement passwordInput;
//Custom multiple annotation
@MultipleFind(elementDetails = { "usernameInput@@ID@@username", "passwordInput@@NAME@@pwd" })
private Map<String, WebElement> loginui;
public LoginMapPageObject(WebDriver driver) {
super(driver);
//PageFactory initialization with custom factory and decorator
MapElementLocatorFactory melf = new MapElementLocatorFactory(driver);
MapFieldDecorator mfd = new MapFieldDecorator(melf);
PageFactory.initElements(mfd, this);
}
private void enterAndSubmitLoginDetails() {
//Conventional calls
//usernameInput.sendKeys("user");
//passwordInput.sendKeys("password");
//Call to retrieve element from map
loginui.get("usernameInput").sendKeys("user");
loginui.get("passwordInput").sendKeys("password");
}
}
地图注解 -
public class MapAnnotations extends Annotations {
public MapAnnotations(Field field) {
super(field);
}
public By buildBy() {
if (getField().getAnnotation(MultipleFind.class) != null)
return null;
return super.buildBy();
}
public Map<String, By> buildMapBys() {
Map<String, By> details = null;
Optional<Annotation> annot = Arrays.asList(getField().getAnnotations())
.stream()
.filter(a -> a.annotationType().equals(MultipleFind.class))
.findAny();
if(annot.isPresent()) {
details = createLocatorDetails(annot.get());
}
return details;
}
private Map<String, By> createLocatorDetails(Annotation annot) {
String[] elemDets = ((MultipleFind) annot).elementDetails();
String delim = ((MultipleFind) annot).delimiter();
Map<String, By> details = Arrays.stream(elemDets)
.map(d -> d.split(delim))
.collect(Collectors.toMap(a -> a[0], a -> createBy(a[1], a[2])));
return details;
}
private By createBy(String howStr, String using) {
How how = How.valueOf(howStr);
switch (how) {
case CLASS_NAME:
return By.className(using);
case CSS:
return By.cssSelector(using);
case ID:
case UNSET:
return By.id(using);
case ID_OR_NAME:
return new ByIdOrName(using);
case LINK_TEXT:
return By.linkText(using);
case NAME:
return By.name(using);
case PARTIAL_LINK_TEXT:
return By.partialLinkText(using);
case TAG_NAME:
return By.tagName(using);
case XPATH:
return By.xpath(using);
default:
// Note that this shouldn't happen (eg, the above matches all
// possible values for the How enum)
throw new IllegalArgumentException("Cannot determine how to locate element ");
}
}
protected void assertValidAnnotations() {
FindBys findBys = getField().getAnnotation(FindBys.class);
FindAll findAll = getField().getAnnotation(FindAll.class);
FindBy findBy = getField().getAnnotation(FindBy.class);
MultipleFind multFind = getField().getAnnotation(MultipleFind.class);
if (multFind != null
&& (findBys != null || findAll != null || findBy != null)) {
throw new IllegalArgumentException(
"If you use a '@MultipleFind' annotation, "
+ "you must not also use a '@FindBy' or '@FindBys' or '@FindAll' annotation");
}
super.assertValidAnnotations();
}
}
MapElementLocatorFactory
public class MapElementLocatorFactory implements ElementLocatorFactory {
private final SearchContext searchContext;
public MapElementLocatorFactory(SearchContext searchContext) {
this.searchContext = searchContext;
}
public MapElementLocator createLocator(Field field) {
return new MapElementLocator(searchContext, field);
}
}
地图元素定位器
public class MapElementLocator extends DefaultElementLocator {
private Map<String, By> elementBys;
private SearchContext searchContext;
public MapElementLocator(SearchContext searchContext, Field field) {
this(searchContext, new MapAnnotations(field));
}
public MapElementLocator(SearchContext searchContext,MapAnnotations annotations) {
super(searchContext, annotations);
this.elementBys = annotations.buildMapBys();
this.searchContext = searchContext;
}
public WebElement findElement(String elementName) {
By by = elementBys.get(elementName);
return searchContext.findElement(by);
}
}
MapFieldDecorator
public class MapFieldDecorator extends DefaultFieldDecorator {
public MapFieldDecorator(ElementLocatorFactory factory) {
super(factory);
}
public Object decorate(ClassLoader loader, Field field) {
if (Map.class.isAssignableFrom(field.getType())) {
MapElementLocator locator = (MapElementLocator) factory.createLocator(field);
return proxyForMapLocator(loader, locator);
}
return super.decorate(loader, field);
}
@SuppressWarnings("unchecked")
protected Map<String, WebElement> proxyForMapLocator(ClassLoader loader,
MapElementLocator locator) {
InvocationHandler handler = new LocatingMapElementHandler(locator);
Map<String, WebElement> proxy;
proxy = (Map<String, WebElement>) Proxy.newProxyInstance(loader,
new Class[] { Map.class }, handler);
return proxy;
}
}
LocatingMapElementHandler
public class LocatingMapElementHandler implements InvocationHandler {
private final MapElementLocator locator;
public LocatingMapElementHandler(MapElementLocator locator) {
this.locator = locator;
}
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
if(method.getName() != "get")
throw new UnsupportedOperationException("Only get method of Map is supported for this proxy.");
return locator.findElement((String)objects[0]);
}
}
这仅适用于单个 WebElement 而不是列表。您可以尝试实现它,但问题是擦除会删除通用信息,因此无法正确确定您是否需要单个或多个元素。只有 get() 方法可以在 Map 代理上工作,其他的会抛出 UnSupportedException。