我只能在页面对象@FindBy注释中使用硬编码值 但我想动态解析定位器。
public class LoginPage extends BasePage {
// hardocded value works ok
@FindBy(name = "login field")
WebElement usernameFld;
// does not compile
// this is a kind of what I would like to have
@FindBy( getLocatorFromExternalSource() )
WebElement passwordFld;
}
我看到一些帖子提到可以通过实现自定义注释/装饰器/工厂来解决此类问题,但尚未找到示例。
问题: 有人可以提供一个如何实现自定义 ElementLocatorFactory 以便动态解析定位器的示例吗?
我知道我可以使用简单的旧式调用,例如:
driver.findElement( getLocatorFromExternalSource("passwordFld") ).click()
但我想用
passwordFld.click()
相反。
注释是元数据,因此它们需要在类加载期间在运行时启动时可用。你所寻求的没有直接的答案,但黑客可以使用反射,恕我直言,我会避免它,如果你需要外部化定位器,那么我建议你实现对象存储库,你可以在运行时动态读取定位器一些外部来源。
我个人不太喜欢
PageFactory
的东西,但如果你已经在使用它,我会做如下的事情。
public class LoginPage extends BasePage
{
WebDriver driver;
WebElement usernameFld;
@FindBy(name = "login field")
WebElement usernameIOs;
@FindBy(name = "something else")
WebElement usernameAndroid;
public LoginPage(WebDriver webDriver, Sites site)
{
this.driver = webDriver;
switch (site)
{
case IOS:
usernameFld = usernameIOs;
break;
case ANDROID:
usernameFld = usernameAndroid;
break;
}
}
public void setUsername(String username)
{
usernameFld.sendKeys(username);
}
}
在其他地方你可以定义
public enum Sites
{
IOS, ANDROID
}
我更喜欢声明定位器,然后根据需要抓取元素。我发现这性能更高,而且陈旧元素问题也更少,等等。
public class LoginPage extends BasePage
{
WebDriver driver;
By usernameLocator;
By usernameIOsLocator = By.name("login field");
By usernameAndroidLocator = By.name("something else");
public LoginPage(WebDriver webDriver, Sites site)
{
this.driver = webDriver;
switch (site)
{
case IOS:
usernameLocator = usernameIOsLocator;
break;
case ANDROID:
usernameLocator = usernameAndroidLocator;
break;
}
}
public void setUsername(String username)
{
driver.findElement(usernameLocator).sendKeys(username);
}
}
我在这里找到了一些线索:https://stackoverflow.com/a/3987430/1073584 最后通过实现 3 个类得到了我想要的东西: 自定义页面工厂、自定义元素定位器、自定义注释。
现在我可以将我的定位器外部化为类型安全配置 并使用以下代码来初始化页面对象
//======= 登录页面
public class LoginPage extends AbstractPage {
Config config;
@FindBy(using = "CONFIG") // take locator from config
WebElement passwordFld;
// .. other fields skipped
public LoginPage(WebDriver webDriver, Config config) throws IOException {
super(webDriver);
this.config = config;
PageFactory.initElements(new CustomPageFactory(webDriver, config), this);
}
}
//======登录.page.typesafe.config:
passwordFld = {
name = "password field"
}
//============= 自定义页面工厂
package com.company.pages.support;
import com.typesafe.config.Config;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import java.lang.reflect.Field;
public class CustomPageFactory implements ElementLocatorFactory {
private Config config;
private WebDriver driver;
public CustomPageFactory(WebDriver driver, Config config) {
this.driver = driver;
this.config = config;
}
public ElementLocator createLocator(Field field) {
return new CustomElementLocator(driver, field, config);
}
}
//================== 自定义元素定位器
package com.company.pages.support;
import com.typesafe.config.Config;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.support.pagefactory.DefaultElementLocator;
import java.lang.reflect.Field;
public class CustomElementLocator extends DefaultElementLocator {
private Config config;
public CustomElementLocator(SearchContext searchContext, Field field, Config config) {
super(searchContext, new CustomAnnotations(field, config));
this.config = config;
}
}
//====== 自定义注释
package com.company.pages.support;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
import org.openqa.selenium.By;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.pagefactory.Annotations;
import java.lang.reflect.Field;
public class CustomAnnotations extends Annotations {
Config config;
public CustomAnnotations(Field field, Config config) {
super(field);
this.config = config;
}
@Override
protected By buildByFromShortFindBy(FindBy findBy) {
if (findBy.using().equals("CONFIG")) {
if (null != config) {
ConfigObject fieldLocators = config.getObject(getField().getName());
if (fieldLocators.keySet().contains("className"))
return By.className(fieldLocators.get("className").unwrapped().toString());
if (fieldLocators.keySet().contains("css"))
return By.cssSelector(fieldLocators.get("css").unwrapped().toString());
if (fieldLocators.keySet().contains("id"))
return By.id(fieldLocators.get("id").unwrapped().toString());
if (fieldLocators.keySet().contains("linkText"))
return By.linkText(fieldLocators.get("linkText").unwrapped().toString());
if (fieldLocators.keySet().contains("name"))
return By.name(fieldLocators.get("name").unwrapped().toString());
if (fieldLocators.keySet().contains("partialLinkText"))
return By.partialLinkText(fieldLocators.get("partialLinkText").unwrapped().toString());
if (fieldLocators.keySet().contains("tagName"))
return By.tagName(fieldLocators.get("tagName").unwrapped().toString());
if (fieldLocators.keySet().contains("xpath"))
return By.xpath(fieldLocators.get("xpath").unwrapped().toString());
}
}
return super.buildByFromShortFindBy(findBy);
}
}
我认为您想通过 webElements 的名称执行操作
你需要在构造函数中放入一些代码
public AnyConstructorName(AndroidDriver<AndroidElement> driver) {
this.driver =driver;
PageFactory.initElements(driver, this);
}
如果您接受其他类的驱动程序,上面的代码将起作用 如果不是这种情况,请从构造函数中删除 this.driver =driver
初始化网络元素,例如
@FindBy(xpath ="//android.widget.TextView[@text='Payments']")
WebElement pay;
你可以执行pay.click();
或任何你想要的操作
但只能在您的页面类中执行这些操作
要读取 @findby 注释的属性值,即 (xpath, id),您可以使用外部类,您可以将 (xpath, id) 值声明为 public static Final 并使用它们的类名将它们访问到 @findby 注释中(.) 变量名。
示例:
Xpath.java
public class Xpath {
public static final String email = "//input[starts-with(@id,'email_address')]";
public static final String password = "//input[starts-with(@id,'password')]";
}
登录.Java
public class LoginPage extends BasePage {
// Uses of xpath, id is depending upon your own requirement
@FindBy(xpath=Xpath.email)
WebElement usernameFld;
@FindBy(xpath=Xpath.password)
WebElement passwordFld;
}
注:
我这里以X路径为例,您可以根据您的要求使用(xpath, id)。