Selenium Webdriver 的 PageObject 改造

再见理想 · 2017年11月10日 · 最后由 再见理想 回复于 2018年10月12日 · 2411 次阅读
本帖已被设为精华帖!

PageObject 中提供了一个@FindBy注解,也非常好用,但由于其是一次性全部初始化所有的 WebElement,对于当前还不存在于页面上的 Element 在初始化时就会报错,为了解决这个问题,自然就不能用这个@FindBy注解了,而我们可以自已去写一个类似的注解来解决这个问题,下面是思路的实现:

自定义一个@FindBy注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface FindBy {

    String name();

    int age();

    int lazy() default 0;
}

定义一个 TestInfo 类来模拟 WebElement 类

public class TestInfo {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "TestInfo [name=" + name + ", age=" + age + "]";
    }

}

解析@FindBy注解

public class PageUtil {

    private static void initialLazyInfo(Object obj, int lazy) {
        try{
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if(field.isAnnotationPresent(FindBy.class) && field.getType().equals(TestInfo.class)){
                    FindBy findBy = field.getAnnotation(FindBy.class);
                    if(findBy.lazy()==lazy){
                        TestInfo temp = new TestInfo();
                        temp.setName(findBy.name());
                        temp.setAge(findBy.age());
                        field.setAccessible(true);
                        field.set(obj, temp);
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void initialLazy(Object obj){
        PageUtil.initialLazyInfo(obj, 0);
    }

    public static void initialLazy(Object obj, int lazy){
        PageUtil.initialLazyInfo(obj, lazy);
    }

}

使用方式

public class DemoPage {

    public DemoPage() {
        PageUtil.initialLazy(this);
    }

    @FindBy(name="zf1", age=1)
    private TestInfo info1;

    @FindBy(name="zf2", age=2, lazy=1)
    private TestInfo info2;

    public void test(){
        System.out.println("info1 is: " + info1);
        System.out.println("info2 is: " + info2);
        PageUtil.initialLazy(this, 1);
        System.out.println("info2 is: " + info2);
    }

    public static void main(String[] args) {
        DemoPage dp = new DemoPage();
        dp.test();
    }

}

运行结果

info1 is: TestInfo [name=zf1, age=1]
info2 is: null
info2 is: TestInfo [name=zf2, age=2]

说明

  • 将 TestInfo 初始化进行了分层次的初始化
  • 在需要用到的地方用PageUtil.initialLazy(this, int);进行初始化

以上虽然实现了分层次的初始化,但是在要用到的地方都需要调用PageUtil.initialLazy(this, int);,还是显得有点麻烦,要解决这个问题,还得从改造 WebElement 类开始,然后自定义一个@FindBy注解,并且在需要用到元素的地方再去判断并初始化。

定义@FindBy注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface FindBy {

    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注解中的值结赋到一个中间类中去,在 Page 类进行初始化的时候进行赋值

public class FindElement {

    private String id;

    private String name;

    private String className;

    private String css;

    private String tagName;

    private String linkText;

    private String partialLinkText;

    private String xpath;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getCss() {
        return css;
    }

    public void setCss(String css) {
        this.css = css;
    }

    public String getTagName() {
        return tagName;
    }

    public void setTagName(String tagName) {
        this.tagName = tagName;
    }

    public String getLinkText() {
        return linkText;
    }

    public void setLinkText(String linkText) {
        this.linkText = linkText;
    }

    public String getPartialLinkText() {
        return partialLinkText;
    }

    public void setPartialLinkText(String partialLinkText) {
        this.partialLinkText = partialLinkText;
    }

    public String getXpath() {
        return xpath;
    }

    public void setXpath(String xpath) {
        this.xpath = xpath;
    }

}

Page 类实例化时初始化@FindBy注解并赋值给 FindElement 类

public class PageUtil {

    public static void initialWebElement(Object obj) {
        try{
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if(field.isAnnotationPresent(FindBy.class) && field.getType().equals(WebElement.class)){
                    FindBy findBy = field.getAnnotation(FindBy.class);
                    FindElement findElement = new FindElement();
                    if(!"".equals(findBy.id())){
                        findElement.setId(findBy.id());
                    }else if(!"".equals(findBy.name())){
                        findElement.setName(findBy.name());
                    }else if(!"".equals(findBy.className())){
                        findElement.setClassName(findBy.className());
                    }else if(!"".equals(findBy.css())){
                        findElement.setCss(findBy.css());
                    }else if(!"".equals(findBy.tagName())){
                        findElement.setTagName(findBy.tagName());
                    }else if(!"".equals(findBy.linkText())){
                        findElement.setLinkText(findBy.linkText());
                    }else if(!"".equals(findBy.partialLinkText())){
                        findElement.setPartialLinkText(findBy.partialLinkText());
                    }else if(!"".equals(findBy.xpath())){
                        findElement.setXpath(findBy.xpath());
                    }
                    WebElementExt ext = new WebElementExt(findElement);
                    field.setAccessible(true);
                    field.set(obj, ext);
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

改造 WebElement 类,利用的是装饰器模式

public class WebElementExt implements WebElement {

    private static WebDriver driver = new FirefoxDriver();

    private FindElement findElement;

    public WebElementExt(FindElement findElement) {
        this.findElement = findElement;
    }

    private WebElement element;

    private WebElement getWebElement(){
        if(element != null){
            return element;
        }
        if(findElement.getId() != null){
            element = this.waitForElement(By.id(findElement.getId()));
        }else if(findElement.getName() != null){
            element = this.waitForElement(By.name(findElement.getName()));
        }else if(findElement.getClassName() != null){
            element = this.waitForElement(By.className(findElement.getClassName()));
        }else if(findElement.getCss() != null){
            element = this.waitForElement(By.cssSelector(findElement.getCss()));
        }else if(findElement.getTagName() != null){
            element = this.waitForElement(By.tagName(findElement.getTagName()));
        }else if(findElement.getLinkText() != null){
            element = this.waitForElement(By.linkText(findElement.getLinkText()));
        }else if(findElement.getPartialLinkText() != null){
            element = this.waitForElement(By.partialLinkText(findElement.getPartialLinkText()));
        }else if(findElement.getXpath() != null){
            element = this.waitForElement(By.xpath(findElement.getXpath()));
        }
        if(this.waitElementToBeDisplayed(element)){
            return element;
        }
        return null;
    }

    private WebElement waitForElement(final By by) {
        WebElement element = null;
        try {
            element = new WebDriverWait(driver, 20).until(new ExpectedCondition<WebElement>() {
                public WebElement apply(WebDriver d) {
                    return d.findElement(by);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return element;
    } 

    private boolean waitElementToBeDisplayed(final WebElement element) {
        boolean wait = false;
        if (element == null)
            return wait;
        try {           
            wait = new WebDriverWait(driver, 20).until(new ExpectedCondition<Boolean>() {
                public Boolean apply(WebDriver d) {
                    return element.isDisplayed();
                }
            });
        } catch (Exception e) {         
           e.printStackTrace();
        }
        return wait;
    }

    @Override
    public void click() {
        this.getWebElement();
        element.click();
    }

    @Override
    public void submit() {
        this.getWebElement();
        element.submit();
    }

    @Override
    public void sendKeys(CharSequence... charSequences) {
        this.getWebElement();
        element.sendKeys(charSequences);
    }

    @Override
    public void clear() {
        this.getWebElement();
        element.clear();
    }

    @Override
    public String getTagName() {
        this.getWebElement();
        return element.getTagName();
    }

    @Override
    public String getAttribute(String s) {
        this.getWebElement();
        return element.getAttribute(s);
    }

    @Override
    public boolean isSelected() {
        this.getWebElement();
        return element.isSelected();
    }

    @Override
    public boolean isEnabled() {
        this.getWebElement();
        return element.isEnabled();
    }

    @Override
    public String getText() {
        this.getWebElement();
        return element.getText();
    }

    @Override
    public List<WebElement> findElements(By by) {
        this.getWebElement();
        return element.findElements(by);
    }

    @Override
    public WebElement findElement(By by) {
        this.getWebElement();
        return element.findElement(by);
    }

    @Override
    public boolean isDisplayed() {
        this.getWebElement();
        return element.isDisplayed();
    }

    @Override
    public Point getLocation() {
        this.getWebElement();
        return element.getLocation();
    }

    @Override
    public Dimension getSize() {
        this.getWebElement();
        return element.getSize();
    }

    @Override
    public String getCssValue(String s) {
        this.getWebElement();
        return element.getCssValue(s);
    }
}

使用方法

public class DemoPage {

    public DemoPage() {
        PageUtil.initialWebElement(this);
    }

    @FindBy(xpath="//abc")
    private WebElement element;

    public void clieckButton(){
        element.click();
    }

}

经过上面这样改造后,即便是页面中动态加载的元素,也不用担心会报错了,也不用多次初始化了!

共收到 7 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 11月11日 22:13

有创新, 请在你的代码块中注明语法. markdown 语法支持语言标注.

有点像 selenium 的 PageFactor

学习了!想法不错,写的也通俗易懂,赞一个!

米阳MeYoung 回复

就是根据的 pagefactory 进行的改造!

vieira 回复

多谢支持!

思寒_seveniruby [该话题已被删除] 中提及了此贴 03月18日 15:20

用 selenium 的 PageFactory.initElements 是否就可以?资料中说,这个就是用的 lazy initialization。当使用元素的时候,才会加载

MRabbit 回复

因为这个重新的实现了 WebDriver 接口,所以,更有控制权,想加 LOG 啊,各种操作,都可以了。。。。

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册