由于最近工作一直很紧张,拖了很久才在五一假期将Selenium实现自动化页面性能测试的代码实现部分补上,希望今后自己能更勤勉,多一些知识产出。
Selenium WebDriver(以下简称SW)提供了一套用于Web应用程序的自动化测试工具。SW按其应用场景不同可以分为(1)基于HtmlUnit的无界面实现,并非驱动真实浏览器进行测试;(2)模拟真实输入,对多浏览器的支持和测试,包括FirefoxDriver、InternetExplorerDriver、OperaDriver和ChromeDriver;(3)对移动应用的测试,包括AndroidDriver和iPhoneDriver。
针对SW进行功能性测试的文章和书已经很多了,比如如何操作获取页面元素内容。而本文所要写的是如何基于Selenium和ChromeDriver做页面性能测试,比如获取页面请求的加载时间、获取页面的DOM元素加载完成时间等等。类似于一些成熟的拨测产品的实现原型(这也是笔者正在做的项目)。我想这是非常有意义的一次探索。
1. Maven依赖
首先,项目需要引入依赖的相关selenium包:selenium-api和selenium-java,要考虑不同版本和JDK版本的兼容性,笔者是JDK 1.8。
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-api -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-api</artifactId>
<version>3.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.5.3</version>
</dependency>
2、ChromeDriver使用详解
本节内容参考https://sites.google.com/a/chromium.org/chromedriver/home,另外ChromeDriver的安装,笔者在《CentOS 7.x环境下搭建: Headless chrome + Selenium + ChromeDriver 实现自动化测试》中有详述。
2.1、DesiredCapabilities & ChromeOptions
Capabilities
属性可以定义和配置你的ChromeDriver会话,以满足对应功能和需求。
在Java实现中,类ChromeOptions
和类DesiredCapabilities
都可以用于具体定义Capabilities
。
比如以下代码,通过ChromeOptions
来定义Chrome的window-size属性:
// 设置chromedriver路径
System.setProperty("webdriver.chrome.driver","/opt/drivers/chromedriver");
ChromeOptions options = new ChromeOptions();
// 设置chrome启动时size大小
options.addArguments("--window-size=1980,1000");
// 根据ChromeOptions实例化ChromeDriver
WebDriver driver = new ChromeDriver(options);
try {
// 打开苏宁易购
driver.get("https://www.suning.com");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭浏览器
driver.quit();
}
当然,以上例子也可以改写为通过DesiredCapabilities
来实现:
// 设置chromedriver路径
System.setProperty("webdriver.chrome.driver","/opt/drivers/chromedriver");
ChromeOptions options = new ChromeOptions();
// 设置chrome启动时size大小
options.addArguments("--window-size=1980,1000");
DesiredCapabilities cap = DesiredCapabilities.chrome();
cap.setCapability(ChromeOptions.CAPABILITY, options);
// 根据DesiredCapabilities实例化ChromeDriver
WebDriver driver = new ChromeDriver(cap);
try {
// 打开苏宁易购
driver.get("https://www.suning.com");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭浏览器
driver.quit();
}
2.2、Performance Log
ChromeDriver支持性能日志(Performance Log)数据的采集。想想看Chrome的F12控制台,我们能够采集到”Network”、Page”等,而这些是实现页面性能测试的基础。
Performance Log并非是默认开启的属性,所以我们可以通过上节说的DesiredCapabilities
在创建新会话的时候开启Performance Log。
而采集到的日志,我们可以通过LogEntry
对象输出到Console。具体代码实现如下:
package com.suning.webdrivertest.chromedemo;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.logging.Level;
/**
*
* Created by zhuyiquan90 on 2018/1/3.
*/
public class ChromeDriverDemo1 {
public static void main(String[] args) {
// 设置chromedriver路径
System.setProperty("webdriver.chrome.driver", "/opt/drivers/chromedriver");
DesiredCapabilities cap = DesiredCapabilities.chrome();
LoggingPreferences logPrefs = new LoggingPreferences();
// 启用Performance Log日志采集
logPrefs.enable(LogType.PERFORMANCE, Level.ALL);
cap.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
// 根据DesiredCapabilities实例化ChromeDriver
WebDriver driver = new ChromeDriver(cap);
try {
// 打开苏宁易购
driver.get("https://www.suning.com");
for (LogEntry entry : driver.manage().logs().get(LogType.PERFORMANCE)) {
// 输出采集到的性能日志
System.out.println(Thread.currentThread().getName() + entry.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭浏览器
driver.quit();
}
}
}
其输出结果如下:
Starting ChromeDriver 2.34.522932 (4140ab217e1ca1bec0c4b4d1b148f3361eb3a03e) on port 29777
Only local connections are allowed.
四月 30, 2018 3:06:27 下午 org.openqa.selenium.remote.ProtocolHandshake createSession
信息: Detected dialect: OSS
main[2018-04-30T15:06:27+0800] [INFO] {
"message":{
"method":"Page.frameAttached","params":{
"frameId":"49C70573CE1145CEB5B38A270213A48","parentFrameId":"28DAFE9FE90E9292F1B8EDB3315608EC","stack":{
"callFrames":[{
"columnNumber":240,"functionName":"","lineNumber":0,"scriptId":"21","url":""}]}}},"webview":"28DAFE9FE90E9292F1B8EDB3315608EC"}
main[2018-04-30T15:06:27+0800] [INFO] {
"message":{
"method":"Page.frameStartedLoading","params":{
"frameId":"49C70573CE1145CEB5B38A270213A48"}},"webview":"28DAFE9FE90E9292F1B8EDB3315608EC"}
main[2018-04-30T15:06:27+0800] [INFO] {
"message":{
"method":"Page.frameNavigated","params":{
"frame":{
"id":"49C70573CE1145CEB5B38A270213A48","loaderId":"EE699DC52C8ACA226069D24DC92E16","mimeType":"text/html","name":"chromedriver dummy frame","parentId":"28DAFE9FE90E9292F1B8EDB3315608EC","securityOrigin":"://","url":"about:blank"}}},"webview":"28DAFE9FE90E9292F1B8EDB3315608EC"}
2.3、Chrome DevTools Protocol View
这一节,我们来讲讲Network和Page包含的内容,即针对上一节输出的内容,我们如何有效利用,通过它们来计算页面性能(参考Chrome DevTools Protocol)。
2.3.1、Network
Network中我们用到的事件主要是requestWillBeSent、responseReceived、loadingFailed和loadingFinished四种:
Network.requestWillBeSent
当页面即将发送HTTP请求时触发,其Json格式为:
{
"message": {
"method": "Network.requestWillBeSent",
"params": {
"documentURL": "about:blank",
"frameId": "C80F96297F4216E35079CFD86251AB8B",
"initiator": {
"lineNumber": 0,
"type": "parser",
"url": "https://www.suning.com/"
},
"loaderId": "58DDB2CF16600EAE484A541DF9440089",
"redirectResponse": {
"connectionId": 639,
"connectionReused": false,
"encodedDataLength": 497,
"fromDiskCache": false,
"fromServiceWorker": false,
"headers": {
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Length": "0",
"Date": "Mon, 30 Apr 2018 07:06:42 GMT",
"Expires": "Thu, 01 Jan 1970 00:00:00 GMT",
"Location": "https://cm.g.doubleclick.net/pixel?google_nid=ipy&google_cm",
"P3P": "CP=\"NON DSP COR CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa CONa HISa TELa OTPa OUR UNRa IND UNI COM NAV INT DEM CNT PRE LOC\"",
"Pragma": "no-cache",
"Server": "nginx/1.10.2",
"Set-Cookie": "CMBMP=IWl; Domain=.ipinyou.com; Expires=Thu, 10-May-2018 07:06:42 GMT; Path=/" },
"headersText": "HTTP/1.1 302 Found\r\nServer: nginx/1.10.2\r\nDate: Mon, 30 Apr 2018 07:06:42 GMT\r\nContent-Length: 0\r\nConnection: keep-alive\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\nP3P: CP=\"NON DSP COR CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa CONa HISa TELa OTPa OUR UNRa IND UNI COM NAV INT DEM CNT PRE LOC\"\r\nSet-Cookie: CMBMP=IWl; Domain=.ipinyou.com; Expires=Thu, 10-May-2018 07:06:42 GMT; Path=/\r\nLocation: https://cm.g.doubleclick.net/pixel?google_nid=ipy&google_cm\r\n\r\n",
"mimeType": "",
"protocol": "http/1.1",
"remoteIPAddress": "127.0.0.1",
"remotePort": 1086,
"requestHeaders": {
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
"Cookie": "sessionId=I4UF6b1WcgGMC; PYID=I4UF6b1Wcg99; CMTMS=p7Ik3Ve; CMSTMS=p7Ik3Ve; CMPUB=ADV-DefaultAdv; CMBMP=IW2",
"Host": "cm.ipinyou.com",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" },
"requestHeadersText": "GET /baidu/cms.gif?baidu_error=1×tamp=1525072001 HTTP/1.1\r\nHost: cm.ipinyou.com\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36\r\nAccept: image/webp,image/apng,image/*,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: sessionId=I4UF6b1WcgGMC; PYID=I4UF6b1Wcg99; CMTMS=p7Ik3Ve; CMSTMS=p7Ik3Ve; CMPUB=ADV-DefaultAdv; CMBMP=IW2\r\n",
"securityDetails": {
"certificateId": 0,