当我开始想要学习UI自动化测试,开始搜索相关资料时,展现在我眼前的除了经典的selenium,还有很多如puppeteer、cypress、testcafe、Airtest,我决定对提到最多的selenium、puppeteer和cypress的原理做一番研究。
Selenium
Selenium webdriver 使用WebDriver来远程控制浏览器实例, 并模拟用户与浏览器的交互。
当我们按照入门教程,在本地环境中实现了使用selenium自动化控制浏览器,完成打开amazon首页-> 输入"自动化测试"->点击搜索得到结果 的整个过程时,我们可以明确感知到三样东西:
-
selenium测试代码:Selenium测试代码是开发/测试人员使用不同的语言和相应的selenium API库完成的测试代码,具体可以查看下方以Amazon搜索商品功能为例的Python代码实现。 -
浏览器driver:即我们在运行selenium代码前,下载的针对不同浏览器开发的chromedriver、geckodriver等。 -
浏览器:自动化程序运行时,会打开代码中与浏览器Driver对应的浏览器,我们可以直观地看到代码的运行效果。
而,WebDriver,作为selenium的核心,却并不在这三个肉眼可见的东西里。这是怎么回事呢?我们先来看看WebDriver是什么。
WebDriver
WebDriver是一个基于HTTP的协议,它提供了一系列的接口用于发现和控制 Web 文档中的 DOM 元素,几乎可以操作浏览器做任何事情。WebDriver协议的详细信息你可以在W3C WebDriver 规范中查看。
以访问亚马逊中国首页为例,代码实现为driver.get('https://www.amazon.cn/') ,查看源码,可以在webdriver.py中看到get方法实际上是调用了self.execute(Command.GET, {'url': url}) ,而WebDriver类是继承自Remoteexecute 方法的核心逻辑是response = self.command_executor.execute(driver_command, params) ,其中Command.GET 在remote_connection.py中的定义为Command.GET: ('POST', '/session/$sessionId/url') 。
也就是说,Selenium测试代码运行时,会向浏览器driver发送对应的HTTP请求,浏览器driver在收到请求后对浏览器进行操作,再将结果返回给Selenium测试代码。那么,如果我们先运行浏览器driver,再通过Postman向其发送HTTP请求,是不是也能控制浏览器呢?答案当然是肯定的。下面我们以在Amazon中搜索商品的操作为例,实际操作一下。
Postman发送基于WebDriver协议的HTTP请求实现UI自动化
首先,启动chromedriver,双击chromedriver文件打开即可:
启动成功之后,注意到控制台中输出了chromedriver启动和监听的的端口号为9515。
然后启动Postman,我们可以开始发送基于WebDriver协议的请求了!
-
建立连接:driver = webdriver.Chrome(executable_path='/path/to/chromedriver') , 对应的请求为POST http://localhost:9515/session {
"desiredCapabilities": {
"caps": {
"nativeEvents": false,
"browserName": "chrome",
"version": "",
"platform": "ANY"
}
}
}
点击Send后,我们可以看到Chrome自动打开了,检查一下Response,返回了很多信息,其中sessionId 是后续所有操作都需要使用的参数,可以将其保存到postman的环境变量中,方便使用。 -
进入amazon首页:driver.get('https://www.amazon.cn/') 对应的请求为:POST http://localhost:9515/session/{{sessionId}}/url {"url":"https://www.amazon.cn/"}
-
查找输入框元素:search_input = driver.find_element(By.ID,'twotabsearchtextbox') 对应的请求为:POST http://localhost:9515/session/{{sessionId}}/element {"using":"id","value":"twotabsearchtextbox"}
这个请求会返回查找到的元素的ID即ELEMENT,我们将其保存到环境变量searchInputId 中。 -
在输入框中输入搜索关键词:search_input.send_keys("自动化测试") 对应的请求为:POST http://localhost:9515/session/{{sessionId}}/element/{{searchInputId}}/value {"value":["自动化测试"]}
-
查找搜索按钮:search_button = driver.find_elements(By.CLASS_NAME,'nav-input')[0] 对应的请求为:POST http://localhost:9515/session/{{sessionId}}/elements {"using": "className","value": "nav-input"}
这里会查找到2个元素,第一个是我们想要的搜索按钮,将搜索按钮元素ID保存到环境变量searchButtonId -
点击搜索按钮:search_button.click() 对应的请求为:http://localhost:9515/session/{{sessionId}}/element/{{searchButtonId}}/click -
获取浏览器当前窗口的标题,用做测试用例的断言:assertEqual(driver.title, '亚马逊 : 自动化测试') 获取标题的请求为:GET http://localhost:9515/session/{{sessionId}}/title 标题在返回的value中。 -
结束测试,断开连接:driver.close() ,对应为DELETE http://localhost:9515/session/{{sessionId}}
下面奉上完整的Postman Collection Json文件,请各位看官们自取。
浏览器driver是如何与浏览器沟通的呢?
对于Chrome浏览器,ChromeDriver解析webdriver协议后,然后根据解析结果,再去调用与之对应的Chrome DevTool Protocol (CDP)协议来操控chrome浏览器,它可以和浏览器内核进行交互进而操控浏览器。关于CDP协议的具体内容,我会在puppeteer框架的原理中详细介绍。
同样地,对于Firefox浏览器,也是由geckodriver解析webdriver协议后再去调用相应的Marionette remote protocol来操控Firefox浏览器。
Chrome DevTool Protocol和Marionette remote protocol这两个协议的作用是相同的,但由于浏览器内核的不同,协议的实现是存在差别的,因此当我们想要驱动不同的浏览器时,需要下载浏览器对应的driver。
Reference
- Selenium Webdriver原理终于搞清楚了!
- selenium doc
- Browser automation using ChromeDriver and Postman
- WebDriver 的协议标准 W3C
|