小伙伴让我周末做技术分享,想着这是一件有意义的事情,便答应了下来,那就给大家讲讲ui自动化吧。这里会结合具体的代码给大家讲ui自动化一些理念,方案设计。
本文将探讨ui自动化设计思路,主要围绕以下方面展开讲解,希望阅读前对ui自动化有个基本了解
1、单例模式的运用
2、ui自动化分层思想
2.1PageObject设计模式
2.2业务流程封装
3、测试数据的准备
3.1 调用APi创建测试数据
3.2数据库操作创建测试数据
3.3数据驱动
3.4测试数据自动生成技术
4、ui自动化测试报告
4.1 调用 screenshot 函数实现截图
4.2 保存服务器日志
4.3 结合testng监听器对失败用例实时截图
5、ui自动化稳定性的关键技术
5.1 重试机制
5.2 异常场景恢复模式
5.3 元素模糊匹配
6、ui自动化执行效率的提升
7、无头浏览器的运用
8、分布式Selenium Grid运用
9、黑科技
9.1 页面对象自动生成技术
9.2 测试数据自动生成技术
在编写ui代码的时候,我们在很多地方都需要WebDriver对象,难道每次都new一个实例?这样会创建很多浏览器进程,我们只需要运用单例设计模式就可以解决这个问题,只有一个相同的WebDriver对象,需要时直接WebDriver driver = SingletonWebDriver.getWebDriver("chrome");
package com.yck.gui.utils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
public class SingletonWebDriver {
private static final String URL = "http://127.0.0.1:14444/wd/hub";
private static final int DRIVER_WAIT_TIME = 20;
private static WebDriver instance = null;
private SingletonWebDriver() {
}
public static WebDriver getWebDriver(String browserName) {
switch (browserName) {
case "chrome":
try {
instance = new RemoteWebDriver(new URL(URL), getCapabilitity(browserName));
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
break;
case "firefox":
try {
instance = new RemoteWebDriver(new URL(URL), getCapabilitity(browserName));
} catch (MalformedURLException e1) {
e1.printStackTrace();
}
break;
}
instance.manage().timeouts().implicitlyWait(DRIVER_WAIT_TIME, TimeUnit.SECONDS); // second为等待时间,单位秒
return instance;
}
public static DesiredCapabilities getCapabilitity(String browserName) {
DesiredCapabilities capability = DesiredCapabilities.chrome();
capability.setBrowserName(browserName);
capability.setPlatform(Platform.LINUX);
return capability;
}
}
在实际编写脚本中,假如把控件操作和测试用例写在一起,是不是会觉得代码难以维护,可读性极差。假如某一个控件被很多用例使用,这个控件发生变化,我们需要修改所有引用这个控件的用例,这样效率及其低下。我们可以引入页面对象模型解决这种问题。
页面对象模型的核心思想就是以页面为单位来封装页面的控件和部分操作,在测试用例的编写中,需要对某个页面进行操作的时候,直接XXXPage.XXX.Compent.XXXOperation();
页面对象模型有如下优点
1、用例与操作解耦,解决了代码可读性差,难以维护的问题。
2、代码重用性高,当某个控件或操作步骤被多个用例引用时,我们只需要这个相同的控件。
业务流程抽象是基于操作函数的更接近于实际业务的更高层次的抽象方式,有点拗口,举个例子就明白了。
一个注册业务包含用户注册、实名认证、后台审核这三步,我们就可以把这三步作为独立的类进行封装,也很方便的被其它业务流程重用。类的内部通常是调用操作函数,操作函数内部是基于页面对象模型完成具体的页面操作。业务流程封装更接近实际业务,遵循参数准备,实例化对象,给对象赋值,执行操作这四步。
测试数据直接由相关api生成,这种不会存在脏数据的问题,测试数据也比较稳定,缺点就是不是所有的测试数据都可以通过api生成,对于需要创建大量测试数据的场景来说,这种方式的效率也不是很理想。
实际项目中,不是所有的数据都有相关api支持,因为有的数据直接在代码中生成和修改且不对外提供接口,那么这种情况下,就需要操作数据库来生成测试数据。我们可以把操作数据库的封装成一个工具类,直接传入sql语句即可。但是这个也有个明显的缺点,就是接口改了,或者数据库表结构改了,要是sql语句更新不及时,就会导致用例的数据问题。
所以ui自动化最好的方式还是通过页面操作,其次api调用生成测试数据,最后通过数据库CRUD。
数据驱动实现了测试脚本与数据解耦,就是把数据源存在csv、xls、yaml、数据库中,需要的时候去相应文件中获取。
testng的Data Provider很好的实现数据驱动,我的另一篇博客有详细案例。
详见9.2
测试报告是ui自动化中很重要的一部分,当测试用例执行失败时,我们不需要手工复现失败场景,只需要去分析测试报告,查找失败的原因。对于测试报告需要包含浏览器界面截图跟服务器日志两部分。浏览器截图是为了知道用例是在什么界面什么操作出错了,服务器日志是为了方便定位代码问题。
注意,截图图片命名要规范,可以用url或者用例名称命名
package com.yck.gui.utils;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class ScreenShotsUtil {
private static final String FILE_PATH = "./Screenshots/";
public void ScreenShotByElement(WebElement webElement) {
File src = webElement.getScreenshotAs(OutputType.FILE);
try {
FileUtils.copyFile(src, new File(FILE_PATH + webElement.toString() + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void ScreenShotByDriver(WebDriver driver) {
File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
try {
FileUtils.copyFile(src, new File(FILE_PATH + driver.getCurrentUrl() + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.yck.gui.utils;
import ch.ethz.ssh2.Connection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
public class RemoteServiceLogUtil {
public static void main(String[] args) {
RemoteServiceLogUtil r = new RemoteServiceLogUtil();
String a = r.execute("10.100.35.146", 22, "root", "helpDesk.com", "tail -99f /opt/local/mall/api.out");
System.out.println("aa"+a);
}
public String processStdout(InputStream in) {
InputStream input = new StreamGobbler(in);
StringBuffer buffer = new StringBuffer();
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(input, "UTF-8"));
String line = null;
while ((line=br.readLine()) .length()>0) {
buffer.append(line + "\n");
}
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
public String execute(String host, int port, String userName, String passWord, String cmd) {
Connection conn = new Connection(host);
String result = null;
Session session = null;
try {
conn.connect();
boolean isconn = conn.authenticateWithPassword(userName, passWord);
if (!isconn) {
System.out.println("服务器连接失败");
}
session = conn.openSession();
session.execCommand(cmd);// 执行命令
result = processStdout(session.getStdout());
conn.close();
session.close();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
做过ui自动化的同学,我相信你们都遇到过测试稳定性的问题,在相同的环境,时而执行成功,时而执行失败,这会浪费很多时间去排查问题,ui自动化产出比也不高,所以ui自动化的稳定性关乎整个产出收益。
页面响应延迟导致元素定位失败,环境不稳定,这些异常情况都会导致用例执行失败,所以加入重试机制会很好的解决这方面的问题。重试可以是用例级别的,也可以是步骤或者业务流程级别的。
下面结合testng对失败用例重试
在测试用例执行过程中,操作系统和被测系统可能会突然弹出对话框,因为你不知道在哪一步骤会弹出什么对话框,你就无法预先处理弹框,这样会导致元素定位失败,造成ui自动化不稳定。在手工测试过程中,你遇到弹框是不是直接关掉?对于ui自动化来说,处理方式也是一样,当出现元素无法定位时,就进入异常场景恢复模式,程序依次检查各种可能出现的对话框,一旦确认了对话框的类型,就直接关掉,接着重试刚刚失败的用例。
页面元素发生细微的变化,也会导致元素定位失败,我们可以加入模糊匹配技术,提高控件的识别率。
无头浏览器,即Headless Browers,是一种没有界面的浏览器,主要用于ui自动化,网络爬虫,页面监控等方面。
业界比较成熟的当属Google 的Chrome Headless和Phantomjs,不过Phantomjs已经停止维护,建议使用Chrome Headless。
1、测试执行速度更快,少了css加载和页面渲染
2、cpu/内存的开销较低
3、在单机上运行多个无头浏览器,实现测试用例的并发执行
无头浏览器的缺点是,不能完全模拟真实的用户行为,而且由于没有实际完成页面的渲染,所以不太适用于需要对于页面布局进行验证的场景。
selenium主要解决了测试重复执行和浏览器兼容性的问题。当用例达到一定数量的时候,单个浏览器执行全部用例需要数个小时,那多个浏览器呢?自动化测试的执行一般在产品发布之前几个小时,这样会严重影响产品的发布时间。当然最笨的办法是在多台主机部署多套浏览器环境,把测试用例分开执行即可,你会不会又觉得这样很麻烦。Selenium Grid就解决了分布式执行测试的痛点。
Selenium Grid跟Jmeter分布式压测一样,所谓的分布式就是由一个hub节点和若干个node代理节点组成。Hub用来管理各个代理节点的注册信息和状态信息,并且接受远程客户端代码的请求调用,然后把请求的命令转发给代理节点来执行。下面结合环境部署来理解Hub与node节点的关系,强烈建议使用docker构建环境。
下载hub镜像
docker pull selenium/hub
下载chrome镜像
docker pull selenium/node-chrome
启动hub镜像
docker run -d -p 4444:4444 --name selenium-hub selenium/hub
-d 容器后台运行,并返回容器id
-p 4444:4444 端口映射,容器4444端口映射到宿主机端口4444,即通过本机端口访问容器
--name selenium-hub 指定容器名称为selenium-hub
访问http://127.0.0.1:4444/grid/console,出现如下图表明启动成功
启动Chrome镜像,连接hub,注册到Selenium Grid集群中
docker run -d --link selenium-hub:hub --name selenium-chrome selenium/node-chrome
启动chrome-headles浏览器,注册到Selenium Grid集群中去。
docker run -d --link selenium-hub:hub alpeware/chrome-headless-trunk
启动firefox浏览器,注册到Selenium Grid集群中去。
docker run -d --link selenium-hub:hub selenium/node-firefox
如下图表明浏览器node已经注册到hub中
通过Selenium Grid访问,代码如下
private static final String URL = "http://127.0.0.1:4444/wd/hub";
DesiredCapabilities capability = DesiredCapabilities.chrome();
capability.setBrowserName("chrome");
capability.setPlatform(Platform.LINUX);
WebDriver driver = new RemoteWebDriver(new URL(URL), capability);