51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1500|回复: 1
打印 上一主题 下一主题

Selenium Page Factory 模式源码分析

[复制链接]
  • TA的每日心情
    无聊
    2022-8-5 09:01
  • 签到天数: 2 天

    连续签到: 2 天

    [LV.1]测试小兵

    跳转到指定楼层
    1#
    发表于 2018-3-26 14:35:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    本文主要用来分析Page Factory实现的原理以及一些扩展的可能性。

    Page Factory 的例子

    Selenium Page Factory Wiki

    首先解释一下这个例子:

    使用注解描述元素定位
    使用
    1. PageFactory.initElements(driver, page);
    2. public class GoogleSearchPage {
    3.     // The element is now looked up using the name attribute
    4.     @FindBy(how = How.NAME, using = "q")
    5.     private WebElement searchBox;

    6.     public void searchFor(String text) {
    7.         // We continue using the element just as before
    8.         searchBox.sendKeys(text);
    9.         searchBox.submit();
    10.     }

    11.     public void searchFor(String text) {
    12.     GoogleSearchPage page= PageFactory.initElements(new ChromeDriver(), GoogleSearchPage.class);
    13.     }
    14. }
    复制代码

    以上一个显而易见的好处就是减少了查找元素的代码量,比如类似于一下的代码:

    driver.findElement(By.id("q"))
    但是只有这样的好处吗?我们先从分析Selenium Page Factory实现的原理说起

    Page Factory 实现的原理

    PageFactory 是使用反射(Reflection)和动态代理(dynamic proxies)的方式来创建页面的每
    每个元素:

    1. PageFactory: initElements and proxyFields ```java public static void initElements(FieldDecorator decorator, Object page) { for(Class proxyIn = page.getClass(); proxyIn != Object.class; proxyIn = proxyIn.getSuperclass()) { proxyFields(decorator, page, proxyIn); }
    2. }

    3. private static void proxyFields(FieldDecorator decorator, Object page, Class<?> proxyIn) {
    4. Field[] fields = proxyIn.getDeclaredFields();
    5. Field[] arr$ = fields;
    6. int len$ = fields.length;

    7. for(int i$ = 0; i$ < len$; ++i$) {
    8. Field field = arr$[i$];
    9. Object value = decorator.decorate(page.getClass().getClassLoader(), field);
    10. if(value != null) {
    11. try {
    12. field.setAccessible(true);
    13. field.set(page, value);
    14. } catch (IllegalAccessException var10) {
    15. throw new RuntimeException(var10);
    16. }
    17. }
    18. }

    19. }

    20. - FieldDecorator:DefaultFieldDecorator 源码
    21. 从DefaultFieldDecorator的源码看:
    22. 1. 实现decorate方法,WebElement 和List<WebElement> 都是通过proxy的方式创建的
    23. 2. 每个Proxy的方式都有一个对应的invocationHandler处理
    24.    Selenium的源码有两个invocationHandler:
    25.    - LocatingElementHandler
    26.    - LocatingElementListHandler

    27. ```java
    28. public Object decorate(ClassLoader loader, Field field) {
    29.         if(!WebElement.class.isAssignableFrom(field.getType()) && !this.isDecoratableList(field)) {
    30.             return null;
    31.         } else {
    32.             ElementLocator locator = this.factory.createLocator(field);
    33.             return locator == null?null:(WebElement.class.isAssignableFrom(field.getType())?this.proxyForLocator(loader, locator):(List.class.isAssignableFrom(field.getType())?this.proxyForListLocator(loader, locator):null));
    34.         }
    35.     }

    36.     private boolean isDecoratableList(Field field) {
    37.         if(!List.class.isAssignableFrom(field.getType())) {
    38.             return false;
    39.         } else {
    40.             Type genericType = field.getGenericType();
    41.             if(!(genericType instanceof ParameterizedType)) {
    42.                 return false;
    43.             } else {
    44.                 Type listType = ((ParameterizedType)genericType).getActualTypeArguments()[0];
    45.                 return !WebElement.class.equals(listType)?false:field.getAnnotation(FindBy.class) != null || field.getAnnotation(FindBys.class) != null || field.getAnnotation(FindAll.class) != null;
    46.             }
    47.         }
    48.     }

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

    54.     protected List<WebElement> proxyForListLocator(ClassLoader loader, ElementLocator locator) {
    55.         LocatingElementListHandler handler = new LocatingElementListHandler(locator);
    56.         List proxy = (List)Proxy.newProxyInstance(loader, new Class[]{List.class}, handler);
    57.         return proxy;
    58.     }
    复制代码

    我们再看一下LocatingElementHandler,可以看到实际在调用PageFactory创建的元素时候都是通过这个
    LocatingElementHandler这个invoke
    函数调用WebElement的方法:

    1. public class LocatingElementHandler implements InvocationHandler {
    2.     private final ElementLocator locator;

    3.     public LocatingElementHandler(ElementLocator locator) {
    4.         this.locator = locator;
    5.     }

    6.     public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
    7.         WebElement element;
    8.         try {
    9.             element = this.locator.findElement();
    10.         } catch (NoSuchElementException var7) {
    11.             if("toString".equals(method.getName())) {
    12.                 return "Proxy element for: " + this.locator.toString();
    13.             }

    14.             throw var7;
    15.         }

    16.         if("getWrappedElement".equals(method.getName())) {
    17.             return element;
    18.         } else {
    19.             try {
    20.                 return method.invoke(element, objects);
    21.             } catch (InvocationTargetException var6) {
    22.                 throw var6.getCause();
    23.             }
    24.         }
    25.     }
    26. }
    复制代码

    以上Selenium PageFactory大致的实现,从过程来看:
    PageFaction-> initElements-> proxyFields

    Page Factory 有什么好处

    个人理解的好处:

    可以通过修改InvocationHandler里面的处理,比如 java element = this.locator.findElement();  如果都使用wait
    ().util的方式,这样可以使所有查找的元素更加稳定
    使用了proxy的方式,在实例化WebElement的时候,实际上不管WebElement存在不存在都可以创建 而实际fi
    ndElement都会延迟到真的调用这个元素时执行,这带来一个好处就是如果WebElement的实例创建后 页面D
    OM刷新后,需要重新查找WebElement,否则可能抛出StaleElementReferenceException,而Proxy之后每次都会
    自动 查找,这样就减少了代码处理
    工厂的设计模式同时也带了了灵活程度,在创建页面或者页面元素的时候,可以开始添加统一的前置或者后置
    的处理
    但是可惜的是Selenium并没有提供开发的接口来让用户定制,所以如果自己定制PageFactory模式,则需要自己
    去实现Selenium这
    一套方法,同时加入自己特殊的实现

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-27 07:50 , Processed in 0.067139 second(s), 22 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表