在Web自动化测试中,如果使用页面模型,页面类一多,就不可避免的会遇到页面模型的管理问题。

如果还需要配合cucumber之类的BDD工具,我们还需要一个页面工厂来管理页面类与实际名称的对应关系。

通常这样的页面类可能会有一个对应关系的列表:

static Map BUILTIN_PAGES = new HashMap(){
    {
        put("Login Page", LoginPage.class)
        put("Home Page", MainPage.class)
        put("System Page", SystemSetting.class)
    }
}

当然也可能是其他形式,这里先以Map形式来说明,配合这个map,可能会有一个方法来创建一个具体的页面:

static Object createPage(pageName){
    return BUILTIN_PAGES.get(pageName).newInstance()
}

那么,问题就来了,随着页面的增多,Map对象会越来越来管理,特别是如果某些特殊的情况下类名或者包名还可能会重复,将来维护起来也会很困难。

这里提供一个使用注解的页面管理方法来代替简单工厂,将来的维护关注点也能直接关注到页面类。

这里有一个简单的工程结构:

pages包里包含了页面相关的内容。我们简单的设计下Page这个注解

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Page {

    /**
     * page's name
     * @return
     */
    String pageName()

    /**
     * page's type,default is USER_PAGE
     * @return
     */
    PageType pageType() default PageType.USER_PAGE
}

其中,pageName方法用于设置当前类对应的名称,pageType方法用于设置页面的类型,该类型是一个虚构的枚举,我们假设默认的页面为普通用户页面。这个方法可有可无,看实际需要。

有了注解,我们就可以把注解给具体的页面打上了,打上后的页面类可能是这样的

普通人员页面:

@Page(pageName = "user page 1")
class Page1 extends AbstractPage{

    Page1(){
        menuPath="a->b->c"
        tabName="page1 tab"
    }
}

管理员页面:

@Page(pageName = "group admin page",pageType = PageType.GROUP_ADMIN_PAGE)
class GroupAdminPage extends AbstractPage{
    GroupAdminPage(){
        menuPath="d->e"
        tabName=null
    }
}

以上2个页面类我虚构了一个抽象父类AbstractPage,里面定义了2个虚构的在当前系统所有页面都会有的属性:一个是进入页面的菜单路径menuPath,一个是页面可能存在的具体页签名称tabName。同样这两个属性是可有可无的。

现在我们的所有页面都有了自己对应的页面名称,现在我们修改下简单页面工厂,来自动获取指定包下所有带Page注解的类生产对应关系

这里面为了方便用了Reflections,这个包可以很容易在maven库里找到。

class PageFactory {

    //logger
    private final static Logger logger = LogUtils.getLogger(PageFactory)

    //package contains all pages
    final static String PACKAGE_NAME = "pages.production"

    static private Map<String, Class> pages = new HashMap<>()

    /**
     * get all pages
     * @return
     */
    static Map<String, Class> getPages() {
        if (pages.isEmpty())
            init()
        pages
    }

    /**
     * get a page by page name
     * @param pageName name of a page
     * @return
     */
    static Class get(String pageName) {
       def pageClass= getPages().get(pageName)
        if(!pageClass){
            throw new ClassNotFoundException("can not find page [${pageName}]")
        }
        pageClass
    }

    /**
     * get a page instance
     * @param pageName name of a page
     * @return
     */
    static AbstractPage createPage(String pageName) {
        def page = get(pageName).newInstance()
        if (!(page instanceof AbstractPage)) {
            throw new ClassCastException("page name [${pageName}] create a page [${page.class}] is not a instance of AbstractPage.")
        }
        page
    }

    /**
     * init all pages
     */
    private static void init() {
        pages.clear()
        //get all classes of a package by Reflections.
        Reflections reflections = new Reflections(PACKAGE_NAME)
        def classes = reflections.getTypesAnnotatedWith(Page)
        for (def page : classes) {
            def pageName = page.getAnnotation(Page).pageName()
            logger.debug("loading [${pageName}]:[${page}]")
            pages.put(pageName, page)
        }
    }
}

现在我们可以简单的使用

  AbstractPage page = PageFactory.createPage(pageName)

来获得一个页面的实例了。

通过以上的方法,我们还可以定义组件注释来标明组件、初始化的时候根据页面类型进行不同的处理等操作。这里就不在复述了。

示例工程地址:https://github.com/assilzm/PageObjectFactoryDemo