最近用 java 在写自动化测试框架,写着写着有点感觉好繁复。所以写出来希望可以得到一些建议,以下主要针对的是网页测试,总体的想法是:

  1. Page Object 模式,页面元素和外面的测试数据的模型命名一样,这样省去一些对应的代码,里面自己分装了自定义的元素
  2. 使用类似于爬虫的第三方包生成 page object 模型
  3. 通过操作流程直接生成测试代码
  4. 使用 testng,data provider 来驱动测试

以下是一些实现:

使用一下脚本解析页面元素,source.txt 是页面的 html 源码,可以页面片段也可以是整个页面:


PageObjectParser poParser = PageObjectParser.buildFromFilePath("source.txt");
poParser.parseRadio();
poParser.parseInputBox("ng-model");

得到一下结果:

@ElementName(elementName="property.propertyUsage")
private Radio propertyUsage;
@FindBy(name="property.estateName")
@ElementName(elementName = "property.estateName")
private InputBox estateName;
@FindBy(name="property.address")
@ElementName(elementName = "property.address")
private InputBox address;
@FindBy(name="property.roomNo")
@ElementName(elementName = "property.roomNo")
private InputBox roomNo;
@FindBy(name="property.permitNo")
@ElementName(elementName = "property.permitNo")
private InputBox permitNo;

稍微复杂点的页面:

PageObjectParser poParser = PageObjectParser.buildFromFilePath("source.txt");
       poParser.parseRadio();
       poParser.parseInputBoxInTable("ng-model");
       poParser.parseSelect("div");

得到结果:

@FindBy(name="property_propertySource")
@ElementName(elementName="property.propertySource")
private Radio propertySource;
@FindBy(name="property_status")
@ElementName(elementName="property.status")
private Radio status;
@FindBy(name="property_shape")
@ElementName(elementName="property.shape")
private Radio shape;
@FindBy(name="property_currentStatus")
@ElementName(elementName="property.currentStatus")
private Radio currentStatus;
@FindBy(name="property_propertyLook")
@ElementName(elementName="property.propertyLook")
private Radio propertyLook;
@FindBy(name="property_isAuction")
@ElementName(elementName="property.isAuction")
private Radio isAuction;
@FindBy(name="property_flagLoan")
@ElementName(elementName="property.flagLoan")
private Radio flagLoan;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_contactName']")
@ElementName(elementName="property.contactName")
private InputBox contactName;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_floorAll']")
@ElementName(elementName="property.floorAll")
private InputBox floorAll;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_floorHeight']")
@ElementName(elementName="property.floorHeight")
private InputBox floorHeight;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//select[@name='property_relationship']")
@ElementName(elementName="property.relationship")
private SelectList relationship;

生成页面模型之后,生成一个页面元素描述的 excel,可以是多个页面:

WebUICodeGenerator.build().writePageObjectsToExcel("sample.xls",
               Lists.newArrayList(MockPage1.class,MockPage2.class));


给业务流程自定需要跑那些元素,标上顺序,使用代码生成一个流程的方法或者注解:

2. 自动生成 annotation 或者测试代码

当整个业务流程涉及的页面完成后,使用如下的代码生成一份 excel:

WebUICodeGenerator t=  WebUICodeGenerator.build("AddProperty.xls");
               t.generateAnnotationStatement("流程1");
       t.generateAnnotationStatement("流程12");

注解或者代码:得到注解

MockPage1:
@UIActions(actions={@UIAction(processName="流程1",elementActionDescription={"propertyUsage","estateName","address","roomNo"})
})
MockPage2:
@UIActions(actions={@UIAction(processName="流程1",elementActionDescription={"contactName","floorAll","isAuction","currentStatus","status","propertyDecoration"})
})

放到页面 Page Object 类去。编写流程类:

最后构建整个业务流程,使用如下代码:中文命名只是为了说明使用。

public class 流程1 extends BaseWebTestAction {

    public 流程1(WebDriver driver, TestData testData) {
        super(driver, testData);
    }

    @Override
    public void execute() {
        WebTestActionBuilder.createTestActionByUIAction(MockPage1.class, "流程1",driver, testData).execute();
        WebTestActionBuilder.createTestActionByUIAction(MockPage2.class, "流程1",driver, testData).execute();
    }
}

以上代码中 execute() 实质上等价:

MockPage1 page1 = ModifiedPageFactory.createPageObject(driver,MockPage1.class);
     page1.processUIAction("流程1",testData);
     MockPage2 page2 = ModifiedPageFactory.createPageObject(driver,MockPage2.class);
     page2.processUIAction("流程1",testData);

或者:

MockPage1 page1 = ModifiedPageFactory.createPageObject(driver,MockPage1.class);
     page1.getPropertyUsage().selectByVisibleText(testData.get("propertyUsage"));
     page1.getAddress().input(testData.get("address"));
     page1.getEstateName().input(testData.get("estateName"));
     page1.getRoomNo().input(testData.get("roomNo"));
     MockPage2 page2 = ModifiedPageFactory.createPageObject(driver,MockPage2.class);
     page2.getContactName().input(testData.get("contractName"));
     ...........

如果不生成注解,直接生成代码就是:
使用代码:

WebUICodeGenerator.build("AddProperty.xls").generateSingleTestStep("流程1");

生成流程的函数:

public static void 流程1(WebDriver driver,TestData testData){
WebTestActionBuilder.createTestActionByElementActionList(MockPage1.class,Lists.newArrayList("propertyUsage","estateName","address","roomNo"),driver,testData).execute();
WebTestActionBuilder.createTestActionByElementActionList(MockPage2.class,Lists.newArrayList("contactName","floorAll","isAuction","currentStatus","status","propertyDecoration"),driver,testData).execute();
}

3. 测试数据准备

测试离不开测试数据的准备,测试数据类可以直接页面 Page Object 来生成:

WebUICodeGenerator t=  WebUICodeGenerator.build();
       t.generateTestDataClass(Lists.newArrayList(MockPage1.class,MockPage2.class));

结果:


private String roomNo;
private String permitNo;
private String propertySource;
private String status;
private String shape;
private String currentStatus;
private String propertyLook;
..........

使用 testng 驱动测试

public class SampleTest extends BaseWebTest {

    @DataProvider(name = "sample_data")
    public Iterator<Object[]> getAPITestData(Method m) throws Exception{
        Map<String, Class> clazzMap = new HashMap<String, Class>();
        clazzMap.put("Sample", Sample.class);
        Iterator<Object[]> y = ExcelHelper.build("abc.xls").ToIteratorInColMode(clazzMap);
        return y;
    }

    @Test(dataProvider ="property_data" )
    public void addProperty(Sample testData) {
       Login。。。。。
        流程1 flow = new 流程1(driver,testData);
        flow.execute();
    }

}

excel 的数据就是类似与:

Sample.roomNo Sample.propertySource
134 345

大概的思路是这样的。请大家多提宝贵意见。在这个过程可能可能看不到一些 webdriver 的操作,这个操作主要是封装了一层对应关系,每个自定义元素都有一个默认操作,没有特别说明就使用默认的操作。

其中对于自定义的元素也是使用过了 PageFactory 的方式,参考 selenium 的实现,自己做了一些定制化。


↙↙↙阅读原文可查看相关链接,并与作者交流