你可能会用到的一些奇技淫巧1:定制WebDriver的annotation

这篇博客探讨了在PageObject模式下,如何通过自定义WebDriver的注解解决复杂页面元素管理问题。当元素过多时,除了页面分解,还可以将元素信息存储在文件中。文章详细介绍了WebDriver的@FindBy注解的工作原理,并展示了如何通过反射和元素定位工厂类生成WebElement。此外,还提到了利用JSON文件管理元素的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

熟悉PageObject模式的朋友一定对 FindBy,FindBys,FindAll 这三个annotation不陌生,借助这三个注解WebDriver提供了一种比较直观的元素管理的解决方案.

但是如果单一页面的元素比较多的时候,都写在一个类里反而不太好管理,这里我们可能会采用两种方式来解决

1 把页面再次分解

2 把元素的xpath等信息写在文件里.

页面分解很好理解,把一个复杂的页面分成若干个类来管理.但是把元素的信息写到文件里又会碰到一个问题,文件的读写.这里我们就可以用自定义annotation来解决。


首先我们先来了解一下,WebDriver是如何通过一个 @FindBy 来组合我们的元素的。提示:以下内容包含大量Java反射内容,最好有所了解.

假如 你有这么一个类

public class BaiduHomePage {

	public BaiduHomePage(WebDriver driver) {
		PageFactory.initElements(driver, this);
	}

	@FindBy(id = "kw")
	private WebElement inputSearch;
	@FindBy(id = "su")
	private WebElement buttonSearch;

	public void searchByKeyWords(String keyWord) {
		inputSearch.clear();
		inputSearch.sendKeys(keyWord);
		buttonSearch.click();
	}
}

那么一切要从 

PageFactory.initElements(driver, this);
这一行开始说起

查看源码可以得知

  public static void initElements(WebDriver driver, Object page) {
    final WebDriver driverRef = driver;
    initElements(new DefaultElementLocatorFactory(driverRef), page);
  }

注意这里的

DefaultElementLocatorFactory

从名字看就可以知道这是一个元素定位工厂类,具体实现暂且不谈继续往下看

又调用了另一个方法

  public static void initElements(ElementLocatorFactory factory, Object page) {
    final ElementLocatorFactory factoryRef = factory;
    initElements(new DefaultFieldDecorator(factoryRef), page);
  }
这里又出现一个 

DefaultFieldDecorator
这个类里有两个主要的方法

public Object decorate(ClassLoader loader, Field field)
此方法主要是通过Field以及 ElementLocatorFactory 来返回WebElement,也就是说通过这个方法把源头的
DefaultElementLocatorFactory 和 
@FindBy(id = "kw") 修饰的域结合起来 生成一个合法的WebElement
这里面调用了两个 方法

  protected WebElement proxyForLocator(ClassLoader loader, ElementLocator locator) {
    InvocationHandler handler = new LocatingElementHandler(locator);
    WebElement proxy;
    proxy = (WebElement) Proxy.newProxyInstance(
        loader, new Class[]{WebElement.class, WrapsElement.class, Locatable.class}, handler);
    return proxy;
  }


这个方法用Java 动态代理的模式把一个不确定的WebElement和通过ElementLocator所定位到的WebElement捆绑在一起.
具体实现会在下一篇文章里讲到.
下面的方法非常类似 只是WebElement变成了List<WebElement>
  @SuppressWarnings("unchecked")
  protected List<WebElement> proxyForListLocator(ClassLoader loader, ElementLocator locator) {
    InvocationHandler handler = new LocatingElementListHandler(locator);
    List<WebElement> proxy;
    proxy = (List<WebElement>) Proxy.newProxyInstance(
        loader, new Class[]{List.class}, handler);
    return proxy;
  }


 protected boolean isDecoratableList(Field field) 
此方法主要是通过反射来确定一个域(Field)是否是一个合法的 List<WebElement>
那么回过头来看 
DefaultElementLocatorFactory
这里面是怎么从一个 Field 生成 一个可用的Element
首先 DefaultElementLocatorFactory调用
public ElementLocator createLocator(Field field)
生成一个new DefaultElementLocator(searchContext, field) //此处的searchContext就是你的driver
然后在DefaultElementLocator初始化的时候会把Field传递给另外一个类 Annotations
  public DefaultElementLocator(SearchContext searchContext, Field field) {
    this(searchContext, new Annotations(field));
  }


再来看最终的构造方法
  public DefaultElementLocator(SearchContext searchContext, AbstractAnnotations annotations) {
    this.searchContext = searchContext;
    this.shouldCache = annotations.isLookupCached();
    this.by = annotations.buildBy();
  }


在这里是不是看到了比较熟悉的东西  By
那么在annotations.buildBy()里又发生了什么
  public By buildBy() {
    assertValidAnnotations(); //校验是否为合法的annotation
    By ans = null;  //最终要返回的By
    FindBys findBys = field.getAnnotation(FindBys.class);
    if (findBys != null) { //是否annotation是FindBys
      ans = buildByFromFindBys(findBys);
    }
    FindAll findAll = field.getAnnotation(FindAll.class);
    if (ans == null && findAll != null) {//如果By没被初始化并且annotation是FindAll
      ans = buildBysFromFindByOneOf(findAll);
    }
    FindBy findBy = field.getAnnotation(FindBy.class);
    if (ans == null && findBy != null) {//如果By没被初始化并且annotation是FindBy
      ans = buildByFromFindBy(findBy);
    }
    if (ans == null) {
      ans = buildByFromDefault();//这里也很清晰,就是你可能写过这样的@FindBy("su"),因为FindBy默认是ById和ByName所以也就不用写成了@FindBy(id="su")
    }
    if (ans == null) {
      throw new IllegalArgumentException("Cannot determine how to locate element " + field);
    }
    return ans;
  }


那么通过Annotations所获得的By,
DefaultElementLocator 就可以通过findElement()方法找到WebElement
然后在 DefaultFieldDecorator里通过
  protected WebElement proxyForLocator(ClassLoader loader, ElementLocator locator) {
    InvocationHandler handler = new LocatingElementHandler(locator);
    WebElement proxy;
    proxy = (WebElement) Proxy.newProxyInstance(
        loader, new Class[]{WebElement.class, WrapsElement.class, Locatable.class}, handler);
    return proxy;
  }

把WebElement返回给你自己定义的WebElement例如上文中的
private WebElement inputSearch;
因为在LocatingElementHandler 里的invoke方法会首先调用findElement方法确定是否元素存在然后返回。


到这里 一个完整的从@FindBy到最终的WebElement的流程大概清晰了
initElements(WebDriver driver, Object page)->initElements(new DefaultElementLocatorFactory(driver), page)
->initElements(new DefaultFieldDecorator(DefaultElementLocatorFactory), page)->DefaultFieldDecorator.decorate(ClassLoader loader, Field field)
->DefaultFieldDecorator.proxyForLocator(ClassLoader loader, ElementLocator locator)->DefaultElementLocator(SearchContext searchContext, AbstractAnnotations annotations)
->DefaultElementLocator.findElement()->Annotations.buidBy()
那么如果我们想自定义一个Annotation的话就需要做这么几步
1 自定义一个Annotation
2 自定义一个annotation和元素管理文件的中间类 
3 自定义一个类继承自Annotations 并且重写buildBy()方法
4 自定义一个ElementLocatorFactory兵重写createLocator()方法,在这里我们会用到DefaultElementLocator和自定义的Annotation
5 自定义一个FieldDecorator,并重写isDecoratableList,因为Annotation已经改变如果再用默认的判断方法那么List<WebElement>查找会出现问题



一个小例子:https://pan.baidu.com/s/1skSP6Ex
有兴趣的朋友可以下载下来参考一下,你可能会用到的jar包有:

selenium-server-standalone-3.0.1.jar 

testng-6.8.7.jar 

commons-lang3-3.1.jar 

json-20160810.jar
代码里使用了JSON 文件来管理元素
注意:实例代码所使用的JDK为1.8并且Selenium版本为3.0.1

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值