自动化测试--实现一套完全解耦的测试框架

Posted clairejing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自动化测试--实现一套完全解耦的测试框架相关的知识,希望对你有一定的参考价值。

之前博客中已经将笔者实现的框架进行过简单介绍,在使用过程中,对以下几点提出优化:

1.页面URL和页面的定位信息保存不同的配置文件中-----------整合到一个配置文件中,相应的配置文件解析做出调整

2.将项目部署到Jenkins之后,出现Chrome驱动启动失败的问题(通过Jenkins运行时,会去找Chrome的chrome.exe)-----------修改driver的配置文件并对其解析类进行一些修改

3.在一个测试case中,对元素进行定位,每次都要传入多个参数(页面配置信息文件地址、页面名称、元素描述信息),会使得代码可读性不够高----------自定义一个TargetPage注解,将页面配置信息的文件地址、页面描述保存在其中。

4.抛弃比较丑的reportng报告,使用Allure2生成测试报告

5.不同的环境连接不同的数据库,为了解决可以读取不同数据库问题,修改了数据库配置文件,使用添加前缀的方式来指定数据库(数据量比较大,参数比较多时,笔者倾向于将测试数据放到数据库中。并且在计划使用SpringMVC实现一套自动化测试工具,这也是为其做准备)。

 

为了使大家能更清晰的理解该套框架,下面将UML图提供出来:

技术分享图片

 

 下面上源码:

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.claire.jing</groupId>
    <artifactId>UIAutoTest0928</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <aspectj.version>1.8.10</aspectj.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>2.6.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.testng/testng -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.14.3</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>






    </dependencies>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.10</version>
                <configuration>
                    <!--设置参数命令行-->
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                    <systemPropertyVariables>
                        <!--是否忽略html,解释见下图。与之后在reportNg报告上显示截图相关。-->
                        <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output>
                    </systemPropertyVariables>
                    <!--测试失败后,是否忽略并继续测试-->
                    <testFailureIgnore>true</testFailureIgnore>
                    <argLine>
                        -Dfile.encoding=UTF-8
                    </argLine>

                    <suiteXmlFiles>
                        <!--代表的是要执行的测试套件名称-->
                        <suiteXmlFile>src/test/resources/testNG.xml</suiteXmlFile>
                    </suiteXmlFiles>

                </configuration>


            </plugin>
        </plugins>

        <finalName>activiti-study</finalName>
        <resources>
            <resource>
                <directory>${basedir}</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>






</project>

 

2.driver配置文件以及解析文件的util

<?xml version="1.0" encoding="UTF-8"?>
<drivers index="0">
<!--Chrome浏览器的配置-->
    <driver name="org.openqa.selenium.chrome.ChromeDriver" index="0">
        <properties>
            <property>
                <name>webdriver.chrome.driver</name>
                <value>{resourcespath}chromedriver.exe</value>
            </property>
        </properties>
        <options>
            <option>
                <name>C:/Users/Samps/AppData/Local/Google/Chrome/Application/chrome.exe</name>
            </option>
        </options>
    </driver>
<!--为3.xselenium提供的配置文件,是需要driver的-->
    <driver name="org.openqa.selenium.firefox.FirefoxDriver" index="1">
        <properties>
            <property>
                <name>webdriver.gecko.driver</name>
                <value>{resourcespath}geckodriver.exe</value>
            </property>
            <property>
                <name>webdriver.firefox.bin</name>
                <value>C:Program FilesMozilla Firefoxfirefox.exe</value>
            </property>
        </properties>
    </driver>
<!--为selenium2.x提供的配置文件,内部继承了firfox的驱动,不需要设置驱动地址-->
    <driver name="org.openqa.selenium.firefox.FirefoxDriver" index="2">
        <properties>
            <property>
                <name>webdriver.firefox.bin</name>
                <value>C:Program FilesMozilla Firefoxfirefox.exe</value>
            </property>
        </properties>
    </driver>
<!--IE浏览器的配置-->
    <driver name="org.openqa.selenium.ie.InternetExplorerDriver" index="3">
        <properties>
            <property>
                <name>webdriver.ie.driver</name>
                <value>{resourcespath}IEDriverServer.exe</value>
            </property>
        </properties>
        <capabilities>
            <capability>
                <name>InternetExplorerDriver.IGNORE_ZOOM_SETTING</name>
            </capability>
            <capability>
                <name>InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS</name>
            </capability>
        </capabilities>
    </driver>


</drivers>
package com.claire.jing.utils;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.io.InputStream;
import java.util.List;

public class GetDriverUtil {
    /**
     * example
     * @param args
     */
    public static void main(String[] args) {

        WebDriver driver = getDriver();
        driver.quit();
    }


    /**
     * 启动浏览器,获取到浏览器驱动。
     * 支持浏览器类型为:火狐,谷歌,IE。
     * 类型设置在drivercon.xml文件中。修改根节点index值即可启动对应的浏览器
     * @return
     */
    public static WebDriver getDriver() {
        SAXReader reader = new SAXReader();
        InputStream in = GetDriverUtil.class.getResourceAsStream("/drivercon.xml");
        Document document = null;
        try {
            document = reader.read(in);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element rootElement = document.getRootElement();//获取到根节点
        String targetIndex = rootElement.attributeValue("index");//获取到根节点中的driver
        List<Element> drivers = rootElement.elements("driver");//获取到所有的driver节点
        String className = null;
        Element targetDriver = null;
        for (Element driver : drivers) {
            String index = driver.attributeValue("index");
            if (index.equals(targetIndex)){
                className =driver.attributeValue("name");//获取到目标浏览器驱动的类全名
                targetDriver = driver;
                break;
            }
        }

        Class<?> driverClass = null;//反射获取到驱动的类引用
        try {
            driverClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Element properties = targetDriver.element("properties");//获取到目标驱动的properties节点
        List<Element> allProperties = properties.elements("property");//获取到目标驱动的所有property节点,即:要设置的系统属性
        //遍历并设置所有的系统属性
        for (Element property : allProperties) {
            String propertyName = property.element("name").getText();
            String path = property.element("value").getText();
            String propertyValue = pathFormat(path);
            System.setProperty(propertyName,propertyValue);
        }



        Element capabilities = targetDriver.element("capabilities");//获取到目标驱动的capabilities
        if (capabilities != null){
            List<Element> allCapabilities = capabilities.elements("capability");//获取到目标驱动的所有capability节点,即:要设置IE浏览器的能力
            for (Element capability : allCapabilities) {
                String capabilieyToSet = capability.element("name").getText();
                DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
                desiredCapabilities.setCapability(capabilieyToSet,true);
            }
        }

        Element optionsElement = targetDriver.element("options");//获取到目标驱动下的options
        ChromeOptions chromeOptions =null;
        if (optionsElement != null){
            List<Element> allOptions = optionsElement.elements("option");
            for (Element option : allOptions) {
                String binaryPath = option.element("name").getText();
                System.out.println("chrome的binary地址为"+binaryPath);
                chromeOptions = new ChromeOptions();
                chromeOptions.setBinary(binaryPath);
            }
            System.out.println("创建的是Chrome的驱动哦");
            return new ChromeDriver(chromeOptions);
        }

        WebDriver  driver = null;
        try {
            driver = (WebDriver) driverClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return driver;


    }

    /**
     * 格式化driver文件的位置     *
     * @param path
     * 传入的浏览器路径设置值
     * 当带有占位符resourcespath时,解析为"src/test/resources/"
     * 没有占位符时,原样返回
     * @return
     * 返回格式化后的driver路径
     */
    public static String pathFormat(String path){

        if (path.contains("{resourcespath}")){
            path = "src/test/resources/"+path.substring(15);
            System.out.println("格式化之后的路径为"+path);
            return  path;
        }
            return path;

    }
}

 

3.BaseTester

package com.claire.jing.base;

import com.claire.jing.annocations.TargetPage;
import com.claire.jing.annocations.TargetTestData;
import com.claire.jing.elementLocator.ElementInfo;
import com.claire.jing.utils.ElementLocatorUtil;
import com.claire.jing.utils.ExcelDataproviderUtil;
import com.claire.jing.utils.GetDriverUtil;
import com.claire.jing.utils.MysqlDataproviderUtil;
import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.DataProvider;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class BaseTester {
    public static WebDriver driver;
    public static Logger logger = Logger.getLogger(BaseTester.class);


    /*-----------------------------------测试前后准备工作--------------------------------------------------------*/

    @BeforeSuite
    public void beforeSuit() {
        driver = GetDriverUtil.getDriver();//测试开始之前,启动浏览器
    }

    @AfterSuite
    public void afterSuit() {
        driver.quit();//测试结束之后,退出浏览器驱动,并关闭所有相关浏览器窗口
    }


    /*----------------------------------数据提供者----------------------------------------------------*/
    @DataProvider
    public Iterator<Object[]> testData(Method method) {
        TargetTestData annotation = method.getAnnotation(TargetTestData.class);
        String simpleName = this.getClass().getSimpleName();
        String substring = simpleName.substring(0, simpleName.indexOf("_"));
        // System.out.println(substring);
        if (annotation == null) {
            annotation = (TargetTestData) Proxy.newProxyInstance(BaseTester.class.getClassLoader(), new Class[]{TargetTestData.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if ("sourceType".equals(method.getName()))
                        return "excel";//默认去excel读取数据
                    if ("sourcePath".equals(method.getName()))
                        return "/testData/" + substring + ".xlsx";//如果没有写读取地址,则默认到类名前缀的文件中去找
                    if ("sourceSheetOrSql".equals(method.getName()))
                        return "Sheet1";
                    if ("mysqlPrefix".equals(method.getName()))
                        return ".test";
                    return null;
                }
            });
        }

        String sourceType = annotation.sourceType();
        if ("excel".equals(sourceType)) {
            return new ExcelDataproviderUtil(annotation.sourcePath(), annotation.sourceSheetOrSql());
        }
        if ("mysql".equals(sourceType)) {
            return new MysqlDataproviderUtil(annotation.sourceSheetOrSql(), annotation.mysqlPrefix());
        }

        return null;
    }

    /*-----------------------------------------智能等待定位元素--------------------------------------------------------*/

    /**
     * 智能定位元素
     *
     * @param path     要读取的配置文件地址
     * @param pageName 页面名称
     * @param desc     元素描述
     * @param timeOut  超时时间,单位为S
     * @return 返回定位到的元素
     */
    public WebElement intelligentWaitgetWebElement(String path, String pageName, String desc, int timeOut) {
        //System.out.println("path----->"+path+"pagename----->"+pageName+timeOut);
        ElementInfo locator = ElementLocatorUtil.getLocator(path, pageName, desc);//获取到定位器
        //定位器内容
        String byStr = locator.getBy();
        String value = locator.getValue();
        //通过反射,获取到By类的字节码文件(By类的引用)
        Class<By> byClass = By.class;
        Method declaredMethod = null;//根据方法名获取到方法By.id(),方法名就是id
        try {
            declaredMethod = byClass.getDeclaredMethod(byStr, String.class);
            By by = (By) declaredMethod.invoke(null, value);//调用null对象(即By类的静态方法)declaredMethod,参数为value
            WebDriverWait wait = new WebDriverWait(driver, timeOut);//创建等待对对象
            WebElement until = wait.until(new ExpectedCondition<WebElement>() {//等待--直到内部类返回一个element
                public WebElement apply(WebDriver input) {
                    WebElement element = input.findElement(by);
                    return element;
                }
            });
            return until;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 当没有传入path,pageName时,通过注解去获取path和pageName,
     * 然后再去调用重载方法IntelligentWaitgetWebElement(String path, String pageName, String desc, int timeOut)
     * 智能等待时间默认为3秒
     *
     * @param desc
     * @return
     */
    public WebElement intelligentWaitgetWebElement(String desc) {
        GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke();
        String path = getPathAndPagename.getPath();
        String pageName = getPathAndPagename.getPageName();
        return intelligentWaitgetWebElement(path, pageName, desc, 3);

    }

    /**
     * 返回元素列表
     *
     * @param path     要读取的配置文件地址
     * @param pageName 页面名称
     * @param desc     元素描述
     * @param timeOut  超时时间,单位为S
     * @return 返回定位到的元素列表
     */
    public List<WebElement> intelligentWaitgetWebElements(String path, String pageName, String desc, int timeOut) {
        //System.out.println("path----->"+path+"pagename----->"+pageName+timeOut);
        ElementInfo locator = ElementLocatorUtil.getLocator(path, pageName, desc);//获取到定位器
        //定位器内容
        String byStr = locator.getBy();
        String value = locator.getValue();
        //通过反射,获取到By类的字节码文件(By类的引用)
        Class<By> byClass = By.class;
        Method declaredMethod = null;//根据方法名获取到方法By.id(),方法名就是id
        try {
            declaredMethod = byClass.getDeclaredMethod(byStr, String.class);
            By by = (By) declaredMethod.invoke(null, value);//调用null对象(即By类的静态方法)declaredMethod,参数为value
            WebDriverWait wait = new WebDriverWait(driver, timeOut);//创建等待对对象
            List<WebElement> until = wait.until(new ExpectedCondition<List<WebElement>>() {//等待--直到内部类返回一个element
                public List<WebElement> apply(WebDriver input) {
                    List<WebElement> elements = input.findElements(by);
                    return elements;
                }
            });
            return until;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 返回元素列表
     * 当没有传入path,pageName时,通过注解去获取path和pageName,
     * 然后再去调用重载方法IntelligentWaitgetWebElements(String path, String pageName, String desc, int timeOut)
     * 智能等待时间默认为3秒
     *
     * @param desc
     * @return
     */
    public List<WebElement> intelligentWaitgetWebElements(String desc) {
        GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke();
        String path = getPathAndPagename.getPath();
        String pageName = getPathAndPagename.getPageName();
        return intelligentWaitgetWebElements(path, pageName, desc, 3);

    }

    /**
     * 内部类,用于获取到当前运行方法上的TargetPage注解
     */
    private class GetPathAndPagename {
        private String path;
        private String pageName;

        public String getPath() {
            return path;
        }

        public String getPageName() {
            return pageName;
        }

        public GetPathAndPagename invoke() {
            ITestNGMethod method = Reporter.getCurrentTestResult().getMethod();
            Method method1 = method.getMethod();
            String name = method.getMethodName();
            //  System.out.println("当前运行方法为" +method1.getName());
            //this.getClass().getDeclaredMethod(name,)
            TargetPage annotation = method1.getAnnotation(TargetPage.class);
            if (annotation == null) {
                annotation = (TargetPage) Proxy.newProxyInstance(BaseTester.class.getClassLoader(), new Class[]{TargetPage.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("path".equals(method.getName()))
                            return "/frontPage/frontPage.xml";
                        if ("pageName".equals(method.getName()))
                            return name;
                        return null;
                    }
                });
            }

            path = annotation.path();
            pageName = annotation.pageName();
            return this;
        }
    }

    /*------------------------------------普通常用方法封装-------------------------------------------*/

    /**
     * 打开浏览器页面
     */
    public void go() {
        GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke();
        String path = getPathAndPagename.getPath();
        String pageName = getPathAndPagename.getPageName();
        String url = ElementLocatorUtil.getUrl(path, pageName);
        driver.get(url);

    }

    /**
     * 打开浏览器指定页面
     *
     * @param path     读取的配置文件地址
     * @param pageName 页面名称
     */
    public void go(String path, String pageName) {
        String url = ElementLocatorUtil.getUrl(path, pageName);
        driver.get(url);
    }

    /**
     * 打开浏览器指定页面
     *
     * @param pageName 页面名称
     */
    public void go(String pageName) {
        GetPathAndPagename getPathAndPagename = new GetPathAndPagename().invoke();
        String path = getPathAndPagename.getPath();
        String url = ElementLocatorUtil.getUrl(path, pageName);
        driver.get(url);
    }

    /**
     * 打开指定url
     * @param url
     */
    public void goByUrl(String url){
        driver.get(url);
    }

    /**
     * 向指定元素输入内容
     *
     * @param desc      元素关键字
     * @param inputKeys 输入的内容
     */
    public void inputKeys(String desc, String inputKeys) {
        intelligentWaitgetWebElement(desc).sendKeys(inputKeys);
    }

    /**
     * 元素点击
     *
     * @param desc 元素关键字
     */
    public void click(String desc) {
        intelligentWaitgetWebElement(desc).click();
    }

    /**
     * 点击指定元素
     * @param element
     */
    public void click(WebElement element){
        element.click();
    }

    public void intelligentClick(WebElement element,int timeOut){
        WebDriverWait wait = new WebDriverWait(driver,timeOut);
        wait.until(ExpectedConditions.elementToBeClickable(element)).click();
    }

    /**
     * 获取到元素文本
     *
     * @param desc
     * @return
     */
    public String getText(String desc) {
        return intelligentWaitgetWebElement(desc).getText();
    }

    /**
     * 获取元素文本
     * @param path
     * @param pagename
     * @param desc
     * @return
     */
    public String getText(String path, String pagename, String desc) {
        return intelligentWaitgetWebElement(path, pagename, desc, 3).getText();
    }

    /**
     * 获取到指定元素的指定属性值
     * @param element
     * 元素
     * @param attrName
     * 属性名
     * @return
     * 属性值
     */
    public String getAttrVal(WebElement element,String attrName){
        return element.getAttribute(attrName);
    }

    /**
     * 获得当前窗口title
     * @return
     */
    public String getTitle(){
        return driver.getTitle();
    }

    /**
     * 获得当前窗口url
     * @return
     */
    public String getCurrentUrl(){
       return driver.getCurrentUrl();
    }

    /**
     * 返回之前页面
     */
    public void back(){
        driver.navigate().back();
    }

    /**
     * 获得当前所有窗口句柄
     * @return
     */
    public Set<String> getWindowHandles(){
        return driver.getWindowHandles();
    }
    /**
     * 获得当前窗口句柄
     * @return
     */
    public String getWindowHandle(){
        return driver.getWindowHandle();
    }

    /**
     * 进入指定句柄的窗口
     * @param windowhandle
     */
    public void goInNewWindow(String windowhandle){
        driver.switchTo().window(windowhandle);
    }

    public void selectBytext(String desc,String text){
        if (text != ""){
            WebElement webElement = intelligentWaitgetWebElement(desc);
            Select select =new Select(webElement);
            select.selectByVisibleText(text);
        }

    }

    /*----------------------------------------断言--------------------------------------------------*/

    /**
     * 断言预期字符串与实际字符串相等
     *
     * @param expected
     * @param actural
     */
    public void assertTextEqual(String expected, String actural) {
        Assert.assertEquals(expected, actural);
    }

    /**
     * 断言实际文本包含预期文本
     *
     * @param expected
     * @param actural
     */
    public void assertTextContain(String expected, String actural) {
        try {
            Assert.assertTrue(actural.contains(expected));
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("断言失败!");
        }
    }

    /**
     * 断言非空
     *
     * @param actural
     */
    public void assertNotNull(String actural) {
        Assert.assertNotNull(actural);
    }

    public void assertTrue(Boolean flag){
        Assert.assertTrue(flag);
    }


}

 

4.用于封装页面信息的2个类

package com.claire.jing.elementLocator;

/**
 * 该类模拟页面元素定位器,任何元素通过By.by(value)的方式都可以获得,如By.id()
 */
public class ElementInfo {
    private String by;
    private String value;
    private String desc;

    public ElementInfo() {
    }

    public ElementInfo(String by, String value, String desc) {
        this.by = by;
        this.value = value;
        this.desc = desc;
    }

    public String getBy() {
        return by;
    }

    public void setBy(String by) {
        this.by = by;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "ElementInfo{" +
                "by=‘" + by + ‘‘‘ +
                ", value=‘" + value + ‘‘‘ +
                ", desc=‘" + desc + ‘‘‘ +
                ‘}‘;
    }
}
package com.claire.jing.elementLocator;

import java.util.Arrays;
import java.util.Map;

public class PageInfo {
    private String pageName;
    private String pageUrl;
    private Map<String,ElementInfo> elementInfoMap;

    public PageInfo() {
    }

    public PageInfo(String pageName, String pageUrl, Map<String, ElementInfo> elementInfoMap) {
        this.pageName = pageName;
        this.pageUrl = pageUrl;
        this.elementInfoMap = elementInfoMap;
    }

    public String getPageName() {
        return pageName;
    }

    public void setPageName(String pageName) {
        this.pageName = pageName;
    }

    public String getPageUrl() {
        return pageUrl;
    }

    public void setPageUrl(String pageUrl) {
        this.pageUrl = pageUrl;
    }

    public Map<String, ElementInfo> getElementInfoMap() {
        return elementInfoMap;
    }

    public void setElementInfoMap(Map<String, ElementInfo> elementInfoMap) {
        this.elementInfoMap = elementInfoMap;
    }

    @Override
    public String toString() {
        return "PageInfo{" +
                "pageName=‘" + pageName + ‘‘‘ +
                ", pageUrl=‘" + pageUrl + ‘‘‘ +
                ", elementInfoMap=" + elementInfoMap +
                ‘}‘;
    }
}

5.页面信息配置文件以及文件解析的util(实现关键字驱动---将页面元素定位信息以及页面Url写到配置文件中,并读取映射到指定pojo上。对位提供获取url和获取定位器的方法)

<?xml version="1.0" encoding="UTF-8"?>
<!--登录页-->
<pages>
    <page pageName="Login" url="http://118.184.217.39/Organizer/Login">
        <locator by="id" value="UserInfo" desc="输入用户名"/>
        <locator by="id" value="Password" desc="输入密码"/>
        <locator by="id" value="button" desc="登录按钮"/>
        <locator by="cssSelector" value="span[data-valmsg-for=‘UserInfo‘]" desc="用户名错误提示"/>
        <locator by="cssSelector" value="span[data-valmsg-for=‘Password‘]" desc="密码错误提示"/>
    </page>

<!--首页-->
    <page pageName="HomePage" url="http://118.184.217.39/">
        <locator by="cssSelector" value="a[href=‘/Organizer/Index‘]" desc="首页欢迎您文本"/>
        <locator by="xpath" value="//div[@id=‘banner_list‘]/a" desc="首页banners"/>
        <locator by="xpath" value="//td[@height=‘28‘]/a" desc="首页所有竞赛链接"/>
    </page>

    <!--竞赛搜索页-->
    <page pageName="CompetitionSearch" url="http://118.184.217.39/Competition/Index">
        <locator by="id" value="ProvinceCode" desc="选择省"/>
        <locator by="id" value="CityCode" desc="选择市"/>
        <locator by="id" value="AreaCode" desc="选择区"/>
        <locator by="id" value="search_text" desc="竞赛搜索输入框"/>
        <locator by="id" value="search_btn" desc="竞赛搜索按钮"/>
        <locator by="id" value="loadMoreBtn" desc="点击加载更多"/>
        <locator by="cssSelector" value="div[class=‘fl search_comp_list‘]" desc="页面所有竞赛"/>
        <locator by="id" value="search_btn" desc="竞赛搜索按钮"/>
    </page>
<!--报名页-->
    <page pageName="ApplyCompetition" url="http://118.184.217.39/Project/Index/">
        <locator by="cssSelector" value="[href=‘/Organizer/Login‘]" desc="登录按钮"/>
        <locator by="xpath" value="//*[@id=‘EnrollType‘]/label/input" desc="角色类型"/>
        <locator by="cssSelector" value="input[name=‘GroupType‘]" desc="选择组别"/>
        <locator by="id" value="teamName" desc="团队全称"/>
        <locator by="id" value="leaderName" desc="领队姓名"/>
        <locator by="id" value="leaderPhone" desc="领队电话"/>
        <locator by="id" value="coachName" desc="教练姓名"/>
        <locator by="id" value="coachPhone" desc="教练电话"/>
        <locator by="id" value="coachName2" desc="教练姓名2"/>
        <locator by="id" value="coachPhone2" desc="教练电话2"/>
        <locator by="id" value="coachName3" desc="教练姓名3"/>
        <locator by="id" value="coachPhone3" desc="教练电话3"/>
        <locator by="id" value="coachName4" desc="教练姓名4"/>
        <locator by="id" value="coachPhone4" desc="教练电话4"/>
        <locator by="id" value="parentName" desc="家长姓名"/>
        <locator by="id" value="parentPhone" desc="家长电话"/>
        <locator by="id" value="email" desc="电子邮箱"/>
        <locator by="id" value="companyName" desc="单位名称"/>
        <locator by="id" value="taxPayerNumber" desc="税号"/>
        <locator by="id" value="row_name" desc="参赛人姓名"/>
        <locator by="id" value="row_sex" desc="参赛人性别"/>
        <locator by="id" value="row_idType" desc="参赛人证件类型"/>
        <locator by="id" value="row_idno" desc="参赛人证件号码"/>
        <locator by="id" value="row_birthday" desc="参赛人生日"/>
        <locator by="id" value="row_add" desc="添加参赛人按钮"/>
        <locator by="id" value="submit" desc="确定报名按钮"/>
        <locator by="id" value="search_btn" desc="竞赛搜索按钮"/>
    </page>



</pages>
package com.claire.jing.utils;

import com.claire.jing.elementLocator.ElementInfo;
import com.claire.jing.elementLocator.PageInfo;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ElementLocatorUtil {
    /**
     * example
     * @param args
     */
    public static void main(String[] args) {
        String pageUrl = ElementLocatorUtil.getUrl("/frontPage/frontPage.xml", "Login");
        System.out.println(pageUrl);
        ElementInfo locator = ElementLocatorUtil.getLocator("/frontPage/frontPage.xml", "Login", "输入用户名");
        System.out.println(locator);

    }

    private static String path;
    private static Map<String, Map<String, PageInfo>> sureInitMap = new HashMap<String, Map<String, PageInfo>>();

    /**
     * 构造函数,为了能将path传递进来
     * @param path
     */
    public ElementLocatorUtil(String path) {
        this.path = path;
    }

    /**
     * 确保同一份文件只被解析一遍
     */
    private static void sureInit() {
        if (sureInitMap.get(path) == null) {
            readXml();
        }

    }

    /**
     * 解析xml文件
     * 思想是:将xml解析到JavaBean中
     * Map<String, PageInfo> -------根据pageName 获取到对应的 pageInfo对象
     * Map<String, ElementInfo>------根据元素描述 获取到对应的 ElementInfo对象(即:获取到页面所有元素的定位信息)
     */
    private static void readXml() {

        SAXReader reader = new SAXReader();
        Document document = null;
        try {
            document = reader.read(ElementLocatorUtil.class.getResourceAsStream(path));
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element rootElement = document.getRootElement();//获取到根节点pages
        List<Element> allPages = rootElement.elements("page");//获取到所有的page子节点
        Map<String, PageInfo> pageInfoMap = new HashMap<String, PageInfo>();//为了可以通过pageName获取到页面所有信息,创建该map
        PageInfo pageInfo = null;//创建一个PageInfo对象

        for (Element page : allPages) {
            String pageName = page.attributeValue("pageName");//获取到pageName
            String url = page.attributeValue("url");//获取到url
            List<Element> allLocator = page.elements("locator");//获取到page节点下的所有locator子节点
            Map<String, ElementInfo> elementInfoMap = new HashMap<String, ElementInfo>();//为了可以通过元素的描述--获取到对应元素,创建该map
            //List<ElementInfo> elementInfo = null;//创建一个ElementInfo对象
            for (Element locator : allLocator) {
                String by = locator.attributeValue("by");//获取到locator的by属性值
                String value = locator.attributeValue("value");//获取到locator的value属性值
                String desc = locator.attributeValue("desc");//获取到locator的desc属性值
                elementInfoMap.put(desc, new ElementInfo(by, value, desc));//通过元素描述,可以直接获得ElementInfo
            }
            pageInfo = new PageInfo(pageName, url, elementInfoMap);
            pageInfoMap.put(pageName, pageInfo);
        }
        sureInitMap.put(path, pageInfoMap);
    }

    /**
     * 获取到测试页面的url
     * @param path
     * 要读取的配置文件路径(如:在resources下的路径为/frontPage(配置文件上一层包名)/frontPage.xml(配置文件名)
     * @param pageName
     * 页面名称
     * @return
     * 返回页面url
     */
    public static   String getUrl(String path, String pageName) {
        new ElementLocatorUtil(path);
        sureInit();
        Map<String, PageInfo> map =  sureInitMap.get(path);
        return map.get(pageName).getPageUrl();
    }

    /**
     * 获取到测试页面指定元素的定位器(定位器即JavaBean,元素所有定位信息封装在JavaBean中)
     * @param path
     * 要读取的配置文件路径(如:在resources下的路径为/frontPage(配置文件上一层包名)/frontPage.xml(配置文件名)
     * @param pageName
     * 页面名称
     * @param desc
     * 元素描述
     * @return
     * 返回定位器
     */
    public static ElementInfo getLocator(String path, String pageName, String desc) {
        new ElementLocatorUtil(path);
        sureInit();
        Map<String, PageInfo> map =  sureInitMap.get(path);
        PageInfo pageInfo = map.get(pageName);
        ElementInfo elementInfo = pageInfo.getElementInfoMap().get(desc);
        return elementInfo;
    }
}

 

6.数据提供者(关于数据驱动--将测试数据与代码完全分离)

读取Excel中的测试数据:

package com.claire.jing.utils;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ExcelDataproviderUtil implements Iterator<Object[]> {
    public static void main(String[] args) {
        ExcelDataproviderUtil excelDataproviderUtil = new ExcelDataproviderUtil("/testData/login.xlsx", "Sheet1");
        while (excelDataproviderUtil.hasNext()){
            Object[] next = excelDataproviderUtil.next();
            System.out.println(Arrays.deepToString(next));
        }

    }
    private String path;
    private String sheetName;
    String[] clomnName;
    Iterator<Row> rowIterator;
    Map<String, Workbook> workbookMap = new HashMap<>();

    public ExcelDataproviderUtil(String path, String sheetName) {
        this.path = path;
        this.sheetName = sheetName;
    }

    private void sureInit() {
        if (workbookMap.get(path) == null)
            initReadExcel();
    }

    public void initReadExcel() {
        InputStream inp = ExcelDataproviderUtil.class.getResourceAsStream(path);
        Workbook workbook = null;
        try {
            workbook = WorkbookFactory.create(inp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        }
        workbookMap.put(path, workbook);
        Sheet sheet = workbook.getSheet(this.sheetName);
        Row firstRow = sheet.getRow(0);
        short lastCellNum = firstRow.getLastCellNum();
        clomnName = new String[lastCellNum];
        //初始化列名数组
        for (int i = 0; i < clomnName.length; i++) {
            clomnName[i] = firstRow.getCell(i).getStringCellValue();
        }

        rowIterator = sheet.iterator();
        rowIterator.next();//直接将行迭代器移动到数据行上面
    }

    boolean flag = false;//默认假设没有下一个

    @Override
    public boolean hasNext() {
        sureInit();
        if (flag)
            return true;
        //判断,当有下一个的时候,将flag设置为true
        if (rowIterator.hasNext()) {
            flag = true;
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Object[] next() {
        sureInit();
        flag = false;
        //读取当前行数据
        Row currentRow = rowIterator.next();
        Map<String,String> currentRowData = new HashMap<>();
        for (int i = 0; i < clomnName.length; i++) {
            Cell cell = currentRow.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
            cell.setCellType(CellType.STRING);
            currentRowData.put(clomnName[i],cell.getStringCellValue());

        }
        return new Object[]{currentRowData};
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("不支持删除操作!");

    }
}

 

jdbc帮助类:

package com.claire.jing.utils.jdbc;

import com.claire.jing.utils.PropertiesUtil;

import java.sql.*;

public class JdbcUtil {
    private static final String JDBC_DRIVER = "jdbc.driver";
    private static final String JDBC_URL="jdbc.url";
    private static final String JDBC_USERNAME="jdbc.username";
    private static final String JDBC_PWD="jdbc.password";
    private static final String PATH = "/mysql.properties";

//保证driver只加载一次
    static {
        try {
            Class.forName(PropertiesUtil.getProVal(PATH,JDBC_DRIVER));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库链接
     * @return
     */
    public static Connection getConn(String prefix){
        String url = PropertiesUtil.getProVal(PATH, prefix+JDBC_URL);
        String username = PropertiesUtil.getProVal(PATH, prefix+JDBC_USERNAME);
        String pwd = PropertiesUtil.getProVal(PATH, prefix+JDBC_PWD);
        try {
            Connection connection = DriverManager.getConnection(url, username, pwd);
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void close(Connection conn, ResultSet resultSet, PreparedStatement statement){
        if (conn !=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (resultSet!=null)
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        if (statement!=null)
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

    }

}

数据库读取测试数据:

package com.claire.jing.utils;


import com.claire.jing.utils.jdbc.JdbcUtil;

import java.sql.*;
import java.util.*;

public class MysqlDataproviderUtil implements Iterator<Object[]> {
    public static void main(String[] args) {
        MysqlDataproviderUtil mysqlDataproviderUtil = new MysqlDataproviderUtil("SELECT * FROM `Organizers`", "test.");
        for (int i = 0; i < 3; i++) {
            if (mysqlDataproviderUtil.hasNext()){
                Object[] next = mysqlDataproviderUtil.next();
                System.out.println(Arrays.toString(next));}
        }


    }

    private String sql;
    private String prefix;
    Connection conn;
    PreparedStatement statement;
    private ResultSet resultSet;
    String[] columnName;
    Map<String, ResultSet> sureInitMap = new HashMap<>();

    /**
     * 构造函数
     * @param sql
     * @param prefix
     */
    public MysqlDataproviderUtil(String sql,String prefix) {
        this.sql = sql;
        this.prefix = prefix;
    }


    /**
     * 确保一个sql不去执行多遍
     */
    private void sureInit() {
        if (sureInitMap.get(sql) == null)
            doSelect();
    }

    /**
     * 执行查询
     */
    private void doSelect() {
        //System.out.println("我的执行次数");
        conn = JdbcUtil.getConn(prefix);
        try {
            statement = conn.prepareStatement(sql);
            resultSet = statement.executeQuery();
            sureInitMap.put(sql, resultSet);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        ResultSetMetaData metaData = null;
        int columnCount = 0;
        try {
            metaData = resultSet.getMetaData();
            columnCount = metaData.getColumnCount();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        columnName = new String[columnCount];
        for (int i = 0; i < columnName.length; i++) {
            try {
                columnName[i] = metaData.getColumnName(i + 1);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
       // System.out.println("执行结束,获得了resultSet");
    }

    Boolean flag = false;//消除next带来的副作用
    @Override
    public boolean hasNext() {
        sureInit();
        if (flag) {
            return true;
        }
    boolean temp = false;
        try {
            temp = resultSet.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        if (temp) {
            flag = true;
            return true;
        } else {
            close();//当没有下一行数据时,自动关闭数据库各种连接。如果在next=true时,就不再取数据了,建议手动调用close方法,去关闭数据库连接。
            return false;
        }
    }

    @Override
    public Object[] next() {
        sureInit();
        flag = false;
        Map<String, String> testData = new HashMap<>();
        for (int i = 0; i < columnName.length; i++) {//遍历,并获取到当前行的数据
            try {
                String setString = resultSet.getString(i + 1);
                testData.put(columnName[i], setString);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
        return new Object[]{testData};
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("不支持删除操作");

    }

    /**
     * 对外提供一个关闭数据库的方法,可以手动关闭。
     */
    public void close(){
        JdbcUtil.close(conn,resultSet,statement);
    }
}

 

7.为了实现失败截图的监听器,在上一篇文章中已经介绍,这里不再赘述。

package com.claire.jing.MyListener;

import com.claire.jing.base.BaseTester;
import com.claire.jing.utils.FailTestScreenShotUtil;
import io.qameta.allure.Attachment;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestFailListener extends TestListenerAdapter {

    @Override
    public void onTestFailure(ITestResult result) {
        takePhoto();
    }

    @Attachment(value = "screen shot",type = "image/png")
    public byte[]  takePhoto(){
        byte[] screenshotAs = ((TakesScreenshot)BaseTester.driver).getScreenshotAs(OutputType.BYTES);
        return screenshotAs;
    }

}

8.自定义注解,方便在测试用例执行时,指定测试数据以及要测试页面

package com.claire.jing.annocations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*要测试页面的配置文件路径以及页面名称
*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
public @interface TargetPage { public String path() default ""; public String pageName() default "";
}
package com.claire.jing.annocations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
*sourceType指定测试数据源的类型,当前支持Excel和mysql数据库
*sourcePath指定测试数据的配置文件路径
*sourceSheetOrSql 当测试数据从Excel读取时,指定sheet名。如果为mysql读取时,指定sql语句
*mysqlPrefix 当测试数据从mysql读取时,指定数据库前缀
*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
public @interface TargetTestData { public String sourceType() default "excel"; public String sourcePath() default ""; public String sourceSheetOrSql() default ""; public String mysqlPrefix() default "test."; }

 

9.测试脚本(只粘贴一个竞赛搜索页面的测试)

package com.claire.jing.testCases.competitionsList;

import com.claire.jing.MyListener.TestFailListener;
import com.claire.jing.annocations.TargetPage;
import com.claire.jing.annocations.TargetTestData;
import com.claire.jing.base.BaseTester;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.util.List;
import java.util.Map;

@Listeners(TestFailListener.class)
public class CompetitionsList_testCases extends BaseTester {
    private static final String PAGE_PATH = "/frontPage/frontPage.xml";
    private static final String PAGE_NAME = "CompetitionSearch";
    private static final String SOURCE_PATH = "/testData/competitionSearch.xlsx";
    private static final String SOURCE_SHEET="competitionSearch";


    @Test(dataProvider = "testData",description = "竞赛搜索测试")
    @Step("输入搜索关键字:{0},对应搜索条数预期结果为:{1}")
    @TargetTestData(sourcePath = SOURCE_PATH,sourceSheetOrSql = SOURCE_SHEET)
    @TargetPage(path = PAGE_PATH,pageName = PAGE_NAME)
    public void competitionSearchTest(Map<String,String> testData) throws Exception{
        go(PAGE_NAME);//打开竞赛搜索页面
        Thread.sleep(2000);
        selectBytext("选择省",testData.get("选择省"));//选择省
        selectBytext("选择市",testData.get("选择市"));//选择市
        selectBytext("选择区",testData.get("选择区"));//选择区
        inputKeys("竞赛搜索输入框",testData.get("搜索关键字"));//输入搜索关键字
        click("竞赛搜索按钮");
        int expectCounts =Integer.valueOf(testData.get("预期搜索到的竞赛个数"));
        logger.info("预期竞赛个数为"+expectCounts);
        List<WebElement> competitionsList = intelligentWaitgetWebElements("页面所有竞赛");
        int index =10;
        while (competitionsList.size() - index ==0){
            click(intelligentWaitgetWebElement("点击加载更多"));//点击加载更多
            Thread.sleep(1000);
            competitionsList = intelligentWaitgetWebElements("页面所有竞赛");
            index += 10;
        }
        logger.info("实际搜索到的竞赛有"+competitionsList.size()+"个");


        assertTrue(expectCounts==competitionsList.size());


    }



}

下面是对应的测试数据:

技术分享图片

 

 后续Jenkins的配置已经在之前博客中整理过,有兴趣可以学习下哦。











以上是关于自动化测试--实现一套完全解耦的测试框架的主要内容,如果未能解决你的问题,请参考以下文章

基于jmeter+ant实现的接口自动化测试

如何基于YAML设计接口自动化测试框架?看完秒会

Airtest+python+selenium 一套轻量级web自动化测试框架

App自动化测试是怎么实现H5测试的

自动化工具BAT巨头教你从零搭建自动化测试框架(6大热门框架)

React-基于Mocha搭建测试框架