与服务端的测试不同,Web前端的测试基本都是依赖于浏览器,试想一下,正常用户行为就是基于浏览器来做各种页面上的增删改查,因此对于测试而言,就需要覆盖全用户能够执行的所有操作,除此之外用户的个人喜好不同,浏览器的选择以及版本的选择都五花八门,各种控件组件兼容性都容易出现异常,可见每次要测试的内容丰富而且麻烦,特别是如果还需要重复回归旧的功能,多来几次估计就十分废鼠标,对标一下服务端的自动化测试来考虑,如果要实现Web前端的自动化测试,应该是这样一种流程,我们写好测试代码,调用不同类型浏览器接口的API,来和浏览器交互,达到模仿用户行为的操作
有了上面的想法,可以来看一套基于WEB的自动化测试工具,Selenium的实现,它提供了一个WebDriver,既要监听测试脚本请求的测试动作,又要根据不同驱动类型去请求特定的浏览器API来完成具体的操作动作,简单画了张图,大致流程如下:
1、创建一个WebDriver实例,这时候会启动一个web服务
2、执行前端页面操作请求,web服务监听到了请求后,根据WebDriver实例的类型,调用具体类型浏览器的API来完成操作
3、浏览器完成具体页面响应操作
具体来做下测试
1、安装WebDriver,我是本地测试脚本执行本地的WebDriver,直接下载ChromeDriver,可以根据chrome版本选一个相近的安装,下载链接:https://chromedriver.storage.googleapis.com/index.html
下载好了之后,直接移到$PATH当中去,mv ./chromedriver /usr/local/bin,不然就需要测试代码里指定配置路径
2、可以验证一下ChromeDriver的功能,启动
✘ lihui@2022 ~ chromedriver Starting ChromeDriver 99.0.4844.51 (d537ec02474b5afe23684e7963d538896c63ac77-refs/branch-heads/4844@{#875}) on port 9515 Only local connections are allowed. Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe. [1647529567.525][WARNING]: FromSockAddr failed on netmask ChromeDriver was started successfully.
可以看到监听了端口9515,直接curl下
lihui@2022 ~ curl -s 127.0.0.1:9515 | jq . { "value": { "error": "unknown command", "message": "unknown command: unknown command: ", "stacktrace": "0 chromedriver 0x00000001098c2159 chromedriver + 5120345\n1 chromedriver 0x000000010984fb13 chromedriver + 4651795\n2 chromedriver 0x000000010943fe68 chromedriver + 392808\n3 chromedriver 0x00000001094903ae chromedriver + 721838\n4 chromedriver 0x0000000109490127 chromedriver + 721191\n5 chromedriver 0x00000001094187da chromedriver + 231386\n6 chromedriver 0x000000010987f38d chromedriver + 4846477\n7 chromedriver 0x000000010989921c chromedriver + 4952604\n8 chromedriver 0x000000010989ea12 chromedriver + 4975122\n9 chromedriver 0x0000000109899b4a chromedriver + 4954954\n10 chromedriver 0x00000001098745b0 chromedriver + 4801968\n11 chromedriver 0x00000001094178ae chromedriver + 227502\n12 libdyld.dylib 0x00007fff6928ccc9 start + 1\n" } }
3、可以写测试代码了,这里有一个超级详细的中文文档可以参考,python的:https://selenium-python-zh.readthedocs.io/en/latest/
我这里用java,首先加一个maven依赖,已经集成在springboot里了,只需要依赖一下就行了
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> </dependency>
来一个google搜索lihuia.com的操作
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.concurrent.TimeUnit;
public class GoogleSearch {
public static void main(String[] args) throws InterruptedException {
// 1.创建webdriver驱动
WebDriver driver = new ChromeDriver();
// 2.打开google
driver.get("https://google.com");
// 3.获取输入框,输入lihuia.com
driver.findElement(By.name("q")).sendKeys("lihuia.com");
// 4.获取"Google搜索"按钮,进行搜索
driver.findElement(By.name("btnK")).submit();
TimeUnit.SECONDS.sleep(5);
// 5.退出浏览器
driver.quit();
}
}
大概意思根据注释也能很容易看懂,但是有一点,findElement里面的条件是如何获取的,有种笨办法,检查元素
下面的Google搜索按钮同理即可;这里我只是举个例子,其实这里不严谨,假如有多个元素name为q,name会定位到第一个,就不一定是这个输入框了
所以测试不同的浏览器,首先得新建不同类型的WebDriver,可以看到目前实现了8种类型
WebDriver里提供的和浏览器交互的API在包package org.openqa.selenium里的WebDriver接口中
方法 | 描述 |
---|---|
get(String url) | 访问目标 url 地址,打开网页 |
getCurrentUrl() | 获取当前页面 url 地址 |
getTitle() | 获取页面标题 |
getPageSource() | 获取页面源代码 |
close() | 关闭浏览器当前打开的窗口 |
quit() | 关闭浏览器所有的窗口 |
findElement(by) | 查找单个元素 |
findElements(by) | 查到元素列表,返回一个集合 |
getWindowHandle() | 获取当前窗口句柄 |
getWindowHandles() | 获取所有窗口的句柄 |
和浏览器上元素控件交互的API在包package org.openqa.selenium的WebElement接口里
方法 | 描述 |
---|---|
click() | 对元素进行点击 |
clear() | 清空内容(如文本框内容) |
sendKeys(…) | 写入内容与模拟按键操作 |
isDisplayed() | 元素是否可见(true:可见,false:不可见) |
isEnabled() | 元素是否启用 |
isSelected() | 元素是否已选择 |
getTagName() | 获取元素标签名 |
getAttribute(attributeName) | 获取元素对应的属性值 |
getText() | 获取元素文本值(元素可见状态下才能获取到) |
submit() | 表单提交 |
最后,和浏览器交互之后,也知道了控件元素如何操作,可如何定位到这个元素呢,因为网页最终是一对HTML文件,因此WebDriver又提供了8中定位策略,找到对应的元素,在包package org.openqa.selenium的By类
方法 | 描述 | 参数 | 示例 |
---|---|---|---|
findElement(By.id()) | 通过元素的 id 属性值来定位元素 | 对应的id属性值 | findElement(By.id(“kw”)) |
findElement(By.name()) | 通过元素的 name 属性值来定位元素 | 对应的name值 | findElement(By.name(“user”)) |
findElement(By.className()) | 通过元素的 class 名来定位元素 | 对应的class类名 | findElement(By.className(“passworld”)) |
findElement(By.tagName()) | 通过元素的 tag 标签名来定位元素 | 对应的标签名 | findElement(By.tagName(“input”)) |
findElement(By.linkText()) | 通过元素标签对之间的文本信息来定位元素 | 文本内容 | findElement(By.linkText(“登录”)) |
findElement(By.partialLinkText()) | 通过元素标签对之间的部分文本信息来定位元素 | 部分文本内容 | findElement(By.partialLinkText(“百度”)) |
findElement(By.xpath()) | 通过xpath语法来定位元素 | xpath表达式 | findElement(By.xpath(“//input[@id=’kw’]”)) |
findElement(By.cssSelector()) | 通过css选择器来定位元素 | css元素选择器 | findElement(By.cssSelector(“#kw”)) |
因此,任何一段操作,都可以分解为操作浏览器,定位到特定元素,浏览器上操作这些元素这几步,由此上面那段测试代码就会打开chrome浏览器,输入google.com之后,搜索lihuia.com这个操作
虽然上面是正向思维完成了一个从测试脚本,到完成测试的过程,但显然这样写起测试代码十分痛苦,因为定位到页面每个空间,元素十分麻烦,因此是否可以直接在浏览器上先点击一遍,然后录制下操作动作,可以自定义修改参数,最后还能导出成测试代码呢,这个是有的,很完美地发现了Selenium IDE
Selenium ID可以模拟浏览器行为,录制操作动作,回放和导出测试脚本,因此可以帮我们做一些重复的人工测试;由于它是依托于浏览器,因此最终支持各种浏览器的使用,都是基于浏览器插件
首先浏览器安装插件,Chrome应用市场搜索selenium ide进行安装,安装完之后启用
插件打开,主界面有点像charles,功能也有点类似,下面简单做一个登录我主页后台的操作,然后通过Selenium IDE将这部分浏览器操作导出为Java测试脚本
导出Java测试脚本,用的是JUnit
package com.lihuia.code.selenium;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.HashMap;
import java.util.Map;
public class LoginTest {
private WebDriver driver;
private Map<String, Object> vars;
JavascriptExecutor js;
@Before
public void setUp() {
driver = new ChromeDriver();
js = (JavascriptExecutor) driver;
vars = new HashMap<String, Object>();
}
@After
public void tearDown() {
driver.quit();
}
@Test
public void untitled() {
driver.get("https://lihuia.com/wp-login.php?redirect_to=https%3A%2F%2Flihuia.com%2Fwp-admin%2F&reauth=1");
driver.manage().window().setSize(new Dimension(1547, 954));
driver.findElement(By.id("user_login")).sendKeys("FlyingCat");
driver.findElement(By.id("user_pass")).sendKeys("xxxxxxxx");
driver.findElement(By.id("wp-submit")).click();
driver.close();
}
}
这样就生成了登录操作的测试流程,前置新建了ChromeDriver,然后get打开登录页面,设置页面分辨率大小,配置用户名密码,最后提交,然后关闭退出driver
到了这一步,就比较清晰了,假如我们有一些异常测试,都可以直接在脚本里新增一个测试类,配置测试的参数,完成同种类型多个测试用例的覆盖;除此之外,Selenium IDE本身有很多操作功能,可以类似JMeter归总添加很多测试流程,甚至可以进行调试,执行
正常进行WEB前端自动化,可以将操作流程录制,然后在Selenium IDE或者导出来的测试脚本里添加总结测试用例,完成测试自动化的覆盖,至于Element的详情定位方式,可以根据WebDriver接口实现来完成
到了这里,假如我需要测试N种浏览器兼容性,假如一个一个执行,显然耗时非常久,甚至如果要测试不同操作系统上的不同浏览器,如果能分布式地在不同的节点上进行就完美了,正好Selenium Grid可以帮你完成
大致流程如下
基本就是一个master-slave架构,hub节点收到消息后,分发到不同的agent上,根据各自节点的webdriver来执行对应浏览器操作
首先下载selenium-server包,官网直接下载即可,比如Mac上:
Hub节点上,写一个配置文件,指定端口
{ "port": 7777, "newSessionWaitTimeout": -1, "servlets" : [], "withoutServlets": [], "custom": {}, "capabilityMatcher": "org.openqa.grid.internal.utils.DefaultCapabilityMatcher", "registry": "org.openqa.grid.internal.DefaultGridRegistry", "throwOnCapabilityNotPresent": true, "cleanUpCycle": 5000, "role": "hub", "debug": false, "browserTimeout": 0, "timeout": 1800 }
启动Hub节点进程
java -jar selenium-server-standalone-3.141.59.jar -role hub -hubConfig hub.json
Node节点上,写一个配置文件,指定要所有要测试的浏览器信息,以及hub信息
{ "capabilities": [ { "browserName": "chrome", "maxInstances": 5, "platform": "MAC", "webdriver.chrome.driver": "/usr/local/bin/chromedriver", "seleniumProtocol": "WebDriver" },{ "browserName": "safari", "maxInstances": 5, "platform": "MAC", "webdriver.chrome.driver": "/usr/bin/safaridriver", "seleniumProtocol": "WebDriver" },{ "browserName": "chrome", "maxInstances": 5, "platform": "LINUX", "webdriver.chrome.driver": "/usr/local/bin/chromedriver", "seleniumProtocol": "WebDriver" } ], "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy", "maxSession": 5, "port": -1, "register": true, "registerCycle": 5000, "hub": "http://172.16.70.92:7777", "nodeStatusCheckTimeout": 5000, "nodePolling": 5000, "role": "node", "unregisterIfStillDownAfter": 60000, "downPollingLimit": 2, "debug": false, "servlets" : [], "withoutServlets": [], "custom": {} }
启动Node节点进程
java -jar selenium-server-standalone-3.141.59.jar -role node -nodeConfig node.json
hub和node网络互通,没启动一个node节点的server,就会注册到hub节点服务里
最后,测试代码,和hub节点server进行交互,然后会被分发到不同的node节点找对应浏览器进行执行,返回的结果进行测试
package com.lihuia.code.selenium;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.net.MalformedURLException;
import java.net.URL;
public class SeleniumGridTest {
WebDriver driver;
String hubURL, testURL;
DesiredCapabilities safariCapability, chromeCapability;
@BeforeTest
public void setUp() {
testURL = "https://google.com";
hubURL = "http://172.16.70.92:7777/wd/hub";
safariCapability = DesiredCapabilities.safari();
chromeCapability = DesiredCapabilities.chrome();
}
@AfterTest
public void afterTest() {
}
@Test(testName = "Safari in MAC")
public void safariTest() throws MalformedURLException {
safariCapability.setBrowserName(BrowserType.SAFARI);
safariCapability.setPlatform(Platform.MAC);
driver = new RemoteWebDriver(new URL(hubURL), safariCapability);
driver.get(testURL);
Assert.assertEquals(driver.getTitle(), "Google");
driver.quit();
}
@Test(testName = "Chrome in MAC")
public void chromeMacTest() throws MalformedURLException {
chromeCapability.setBrowserName(BrowserType.CHROME);
chromeCapability.setPlatform(Platform.MAC);
driver = new RemoteWebDriver(new URL(hubURL), chromeCapability);
driver.get(testURL);
Assert.assertEquals(driver.getTitle(), "Google");
driver.quit();
}
@Test(testName = "Chrome in Linux")
public void chromeLinuxTest() throws MalformedURLException {
chromeCapability.setBrowserName(BrowserType.CHROME);
chromeCapability.setPlatform(Platform.LINUX);
driver = new RemoteWebDriver(new URL(hubURL), chromeCapability);
driver.get(testURL);
Assert.assertEquals(driver.getTitle(), "Google");
driver.quit();
}
}
Selenium的几个主要模块就这些,webdriver提供了一个用户和浏览器自动化测试的桥梁,ide提供了一个可视化信息录制和回放的功能,grid提供了一套分布式自动化测试,能在不同的节点操作系统上,进行不同的浏览器操作
最后,没做过这方面的实现,不太清楚具体实质提升有多大,目前自我感觉这样测试也不是太稳健,投入回报率不高