由于Selenium
框架采用PageObject
设计模式,因而提供提供了不少很方便的注解来达到目的,其中有一个注解就是@FindBy
。在使用中,只要通过在field
中使用注解,则可以将不同属性的元素对象转换成一个WebElement
对象。通过WebElement
提供的方法,则可以进行UI
上的操作了,下面来简单看看这个神奇的注解是怎么工作的。
webElement
包含的方法
通过WebElement
提供的方法,则可以进行UI
上的操作了,下面来简单看看这个神奇的注解是怎么工作的。
注解@FindBy
的使用
@FindBy(name = "修改密码")
public WebElement changePswTab;
通过指定name
属性,可以将changePswTab
转换成当前页面的一个WenElement
对象
注解定义
注解的定义很简单,直接看源码即可,通过注解定义可以知道,可以通过id
,name
,className
,xpath
等多种方式来锁定当前元素
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface FindBy {
How how() default How.UNSET;
String using() default "";
String id() default "";
String name() default "";
String className() default "";
String css() default "";
String tagName() default "";
String linkText() default "";
String partialLinkText() default "";
String xpath() default "";
}
注解行为注入
注解的行为肯定是在使用前注入的,那这个@FindBy
具体做了什么呢?
1.将元素构建成By
对象
public By buildBy() {
assertValidAnnotations();
By ans = null;
...
FindBy findBy = field.getAnnotation(FindBy.class);
if (ans == null && findBy != null) {
ans = buildByFromFindBy(findBy);
}
...
return ans;
}
在Annotations
中,selenium
通过反射拿到findby
对象,然后将对象构建成By
对象
protected By buildByFromShortFindBy(FindBy findBy) {
if (!"".equals(findBy.className()))
return By.className(findBy.className());
...
if (!"".equals(findBy.name()))
return By.name(findBy.name());
...
// Fall through
return null;
}
public static By name(final String name) {
if (name == null)
throw new IllegalArgumentException(
"Cannot find elements when name text is null.");
return new ByName(name);
}
表面上注解执行到这里就完成了,但其实可以发现,在field
注解使用时,拿到的是一个webelment
对象!并不是现在拿到的by
对象?这是怎么一回事呢?其实在这里,selenium
使用了动态代理的方式来讲by
对象转成webelment
对象!
动态代理转换实现
通过回溯buildBy
的调用方法,可以回溯到PageObject.initElements(...)
,但其实这个调用树中,起作用的是这一段
public Object decorate(ClassLoader loader, Field field) {
if (!(WebElement.class.isAssignableFrom(field.getType())
|| isDecoratableList(field))) {
return null;
}
ElementLocator locator = factory.createLocator(field);
if (locator == null) {
return null;
}
if (WebElement.class.isAssignableFrom(field.getType())) {
return proxyForLocator(loader, locator);
} else if (List.class.isAssignableFrom(field.getType())) {
return proxyForListLocator(loader, locator);
} else {
return null;
}
}
selenium
通过factory.createLocator(field)
来实现by
对象的构建,然后将by
转换成webelment
则是proxyForLocator(loader, locator)
来实现的。
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;
}
动态代理中,真正调用的是InvocationHandler
的实现对象. 当调用代理对象的接口时, 实际上会 通过InvocationHandler.invkoe
将调用转发给实际的对象,即new LocatingElementHandler(locator)
,所以只需要看看LocatingElementHandler
的invoke
方法做了啥就知道了。
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
WebElement element;
try {
element = locator.findElement();
} catch (NoSuchElementException e) {
if ("toString".equals(method.getName())) {
return "Proxy element for: " + locator.toString();
}
throw e;
}
if ("getWrappedElement".equals(method.getName())) {
return element;
}
try {
return method.invoke(element, objects);
} catch (InvocationTargetException e) {
// Unwrap the underlying exception
throw e.getCause();
}
}
很明显,调用了findElement()
方法,这样就实现了转换。
为什么要用动态代理
因为在对一个页面进行测试时,涉及到很多元素,但真正执行可能只用到其中几个元素而已,使用动态代理的好处就是,不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象, 而把这种指定延迟到程序运行时由JVM来实现,相当于用例执行,元素调用时才去加载元素,简单来说就是实现了元素延迟加载。