霍格沃兹测试开发学社
Web 自动化测试价值与体系
环境安装与使用
自动化用例录制,
自动化测试用例结构分析
Web 浏览器控制
常见控件定位方法
强制等待与隐式等待
常见控件交互方法
测试人论坛搜索功能自动化测试
高级定位:CSS、XPath
显式等待高级使用
高级控件交互方法
网页 frame 与多窗口处理
文件上传弹框处理
自动化关键数据记录
Cypress 测试框架介绍
PlayWright 测试框架介绍
Web 自动化测试用例流程设计
浏览器复用,Cookie 复用
Page Object 设计模式
Web 自动化测试进阶实战
有赞:很多人会认为,UI 自动化维护成本高、性价比低,但是为什么在有赞的前端质量保证体系中放在了最前面呢? 前端重用户交互,单纯的接口测试、单元测试不能真实反映用户的操作路径 ,并且从以往的经验中总结得出,因为各种不可控因素导致的发布 A 功能而 B 功能无法使用,特别是核心简单场景的不可用时有出现,所以每次发布一个应用前,都会将此应用提供的核心功能执行一遍,那随着业务的不断积累,需要回归的测试场景也越来越多,导致回归的工作量巨大。为了降低人力成本,我们亟需通过自动化手段释放劳动力,所以将核心流程回归的 UI 自动化提到了最核心地位
美团 App 的页面特点 对于不同的用户,美团 App 页面的呈现方式其实多种多样,这就是所谓的“千人千面”。以美团首页的“猜你喜欢”模块为例,针对于不同的用户有单列、Tab、双列等多种不同形式。这么多不同的页面样式需求,如果要在 1 天时间内完成开发、测试、上线流程,研发团队也面临着很大的挑战。所以测试工程师就需要重度依赖自动化测试来形成快速的验收机制。
前提:
安装:
pip install selenium
selenium 4.0 开始可以自动根据浏览器的版本自动下载,但是国内下载速度不稳定,可以参考本部分的手工配置。
# 导入selenium 包
from selenium import webdriver
# 创建一个 Chromdriver 的实例。Chrome()会从环境变量中寻找浏览器驱动
driver = webdriver.Chrome()
# 打开网址
driver.get("https://ceshiren.com/")
# 关闭driver
driver.quit()
# 导入selenium 包
from selenium import webdriver
# 创建一个 Geckodriver 的实例。Firefox()会从环境变量中寻找浏览器驱动
driver = webdriver.Firefox()
# 打开网址
driver.get("https://ceshiren.com/")
# 关闭driver
driver.quit()
操作 | 使用场景 | |
---|---|---|
get | 打开浏览器 | Web 自动化测试第一步 |
refresh | 浏览器刷新 | 模拟浏览器刷新 |
back | 浏览器退回 | 模拟退回步骤 |
forward | 浏览器前进 | 模拟浏览器前进步骤 |
maximize_window | 最大化浏览器 | 模拟浏览器最大化 |
minimize_window | 最小化浏览器 | 模拟浏览器最小化 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>测试人论坛</title>
</head>
<body>
<a href="https://ceshiren.com/" class="link">链接</a>
</body>
</html>
<a>
方式 | 描述 |
---|---|
class name | class 属性对应的值 |
CSS selector(重点) | CSS 表达式 |
id(重点) | id 属性对应的值 |
name(重点) | name 属性对应的值 |
link text | 查找其可见文本与搜索值匹配的锚元素 |
partial link text |
查找其可见文本包含搜索值的锚元素
如果多个元素匹配,则只会选择第一个元素 |
tag name | 标签名称 |
xpath(重点) | XPath 表达式 |
#格式:
driver.find_element_by_定位方式(定位元素)
driver.find_element(By.定位方式, 定位元素)
driver.find_element(By.ID, "su")
driver.find_element(By.CSS_SELECTOR, "css表达式")
driver.find_element(By.XPATH, "xpath表达式")
driver.find_element(By.LINK_TEXT,"文本信息")
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_sleep():
"""
如果直接执行,不添加任何等待,可能会报错
"""
driver = webdriver.Chrome()
driver.get("https://vip.ceshiren.com/")
# 不加等待,可能会因为网速等原因产生报错
driver.find_element(By.XPATH, "//*[text()='个人中心']")
time.sleep(3)
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_sleep():
"""
如果直接执行,不添加任何等待,可能会报错
"""
driver = webdriver.Chrome()
driver.get("https://vip.ceshiren.com/")
# 添加等待,让页面渲染完成
time.sleep(3)
driver.find_element(By.XPATH, "//*[text()='个人中心']")
#设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
driver.implicitly_wait(3)
WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
def wait_until():
driver = webdriver.Chrome()
driver.get("https://vip.ceshiren.com/#/ui_study")
WebDriverWait(driver, 10).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, '#success_btn')))
driver.find_element(By.CSS_SELECTOR, "#success_btn").click()
类型 | 使用方式 | 原理 | 适用场景 |
---|---|---|---|
直接等待 |
time.sleep(等待时间))
|
强制线程等待 | 调试代码,临时性添加 |
隐式等待 |
driver.implicitly_wait(等待时间)
|
在时间范围内,轮询查找元素 | 解决找不到元素问题,无法解决交互问题 |
显式等待 |
WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
|
设定特定的等待条件,轮询操作 | 解决特定条件下的等待问题,比如点击等交互性行为 |
# 绝对定位
$("#ember63 > td.main-link.clearfix.topic-list-data > span > span > a")
# 相对定位
$("#ember63 [title='新话题']")
$("css表达式")
$$("css表达式")
类型 | 表达式 |
---|---|
标签 | 标签名 |
类 |
.class属性值
|
ID |
#id属性值
|
属性 |
[属性名='属性值']
|
//在console中的写法
// https://www.baidu.com/
//标签名
$("input");
//.类属性值
$(".s_ipt");
//#id属性值
$("#kw");
//[属性名='属性值']
$('[name="wd"]');
类型 | 格式 |
---|---|
并集 | 元素,元素 |
邻近兄弟(了解即可) | 元素+元素 |
兄弟(了解即可) | 元素 1~元素 2 |
父子 | 元素>元素 |
后代 | 元素 元素 |
//在console中的写法
//元素,元素
$(".bg,.s_ipt_wr,.new-pmd,.quickdelete-wrap");
//元素>元素
$("#s_kw_wrap>input");
//元素 元素
$("#form input");
//元素+元素,了解即可
$(".soutu-btn+input");
//元素1~元素2,了解即可
$(".soutu-btn~i");
类型 | 格式 |
---|---|
父子关系+顺序 | nth-child |
父子关系+标签类型+顺序 | nth-of-type |
//:nth-child(n)
$('#form>input:nth-child(2)')
//:nth-of-type(n)
$('#form>input:nth-of-type(1)')
$x("xpath表达式")
表达式 | 结果 |
---|---|
/ | 从该节点的子元素选取 |
// | 从该节点的子孙元素选取 |
* | 通配符 |
nodename | 选取此节点的所有子节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
# 整个页面
$x("/")
# 页面中的所有的子元素
$x("/*")
# 整个页面中的所有元素
$x("//*")
# 查找页面上面所有的div标签节点
$x("//div")
# 查找id属性为site-logo的节点
$x('//*[@id="site-logo"]')
# 查找节点的父节点
$x('//*[@id="site-logo"]/..')
# 获取此节点下的所有的li元素
$x("//*[@id='ember21']//li")
# 获取此节点下【所有的节点的】第一个li元素
$x("//*[@id='ember21']//li[1]")
[last()]
: 选取最后一个
[@属性名='属性值' and @属性名='属性值']
: 与关系
[@属性名='属性值' or @属性名='属性值']
: 或关系
[text()='文本信息']
: 根据文本信息定位
[contains(text(),'文本信息')]
: 根据文本信息包含定位
[]
结合
# 选取最后一个input标签
//input[last()]
# 选取属性name的值为passward并且属性pwd的值为123456的input标签
//input[@name='passward' and @pwd='123456']
# 选取属性name的值为passward或属性pwd的值为123456的input标签
//input[@name='passward' or @pwd='123456']
# 选取所有文本信息为'霍格沃兹测试开发'的元素
//*[text()='霍格沃兹测试开发']
# 选取所有文本信息包'霍格沃兹'的元素
//*[contains(text(),'霍格沃兹')]
draggable = driver.find_element(By.ID, "draggable")
droppable = driver.find_element(By.ID, "droppable")
ActionChains(driver)\
.drag_and_drop(draggable, droppable)\
.perform()
<div id="modal">
<iframe id="buttonframe" name="myframe" src="https://seleniumhq.github.io">
<button>Click here</button>
</iframe>
</div>
iframe = driver.find_element(By.CSS_SELECTOR, "#modal > iframe")
driver.switch_to.frame(iframe)
driver.find_element(By.TAG_NAME, 'button').click()
driver.switch_to.default_content()
driver.current_window_handle
driver.window_handles
driver.switch_to.window(window_handle)
driver.switch_to.new_window('tab')
driver.switch_to.new_window('window')
def test_upload(self):
self.driver.get('https://www.baidu.com/')
self.driver.find_element(By.CLASS_NAME, 'soutu-btn').click()
self.driver.find_element(By.CLASS_NAME, 'upload-pic') \
.send_keys('/Users/seveniruby/Dropbox/sihanjishu/startup/霍格沃兹测试学院/banner/通用素材/学院长方形banner.png')
def test_allure(self):
self.driver.get('https://ceshiren.com/')
allure.attach(body=self.driver.get_screenshot_as_png(), attachment_type= allure.attachment_type.PNG)
self.driver.find_element(By.ID, 'search-button').click()
allure.attach(body=self.driver.get_screenshot_as_png(), attachment_type=allure.attachment_type.PNG)
pytest --alluredir=allure geektime_0/web/wework/reuse.py
allure serve allure/
# windows:
chrome --remote-debugging-port=9222
#mac
Google\ Chrome --remote-debugging-port=9222
#执行后的输出
hogwarts: ~ seveniruby$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
DevTools listening on ws://127.0.0.1:9222/devtools/browser/e00051cf-6a06-44be-ae6f-a8dec8c75410
from selenium.webdriver.chrome.options import Options
option = Options()
option.debugger_address = "localhost:9222"
self._driver = webdriver.Chrome(options=option)
self._driver.get("https://work.weixin.qq.com/wework_admin/frame")
driver.get_cookies()
driver.add_cookie(cookie)
class TestCookieLogin:
def setup_class(self):
self.drvier = webdriver.Chrome()
def test_get_cookies(self):
# 1. 访问企业微信主页/登录页面
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
# 2. 等待20s,人工扫码操作 或者使用显式等待url发生变化
time.sleep(20)
# 3. 等成功登陆之后,再去获取cookie信息
cookie = self.drvier.get_cookies()
# 4. 将cookie存入一个可持久存储的地方,文件
# 打开文件的时候添加写入权限
with open("cookie.yaml", "w") as f:
# 第一个参数是要写入的数据
yaml.safe_dump(cookie, f)
def test_add_cookie(self):
# 1. 访问企业微信主页面
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
# 2. 定义cookie,cookie信息从已经写入的cookie文件中获取
cookie = yaml.safe_load(open("cookie.yaml"))
# 3. 植入cookie
for c in cookie:
self.drvier.add_cookie(c)
time.sleep(3)
# 4.再次访问企业微信页面,发现无需扫码自动登录,而且可以多次使用
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
项目 | Cypress | Selenium |
---|---|---|
支持语言 | Javascript | Java, Python, Javascript, Ruby, C#等 |
支持浏览器 | Chrome、Electron | 各种主流浏览器 |
主要使用者 | 前端开发人员 | QA |
使用的测试框架 | Mocha | 无限制 |
是否需要浏览器驱动器 | 否 | 需要 |
测试速度 | 快 | 略慢 |
录制测试视频、快照 | 支持 | 支持,但需要写代码 |
社区支持 | 略显薄弱 | 强大 |
describe("Post Resource", () => {
it("Creating a New Post", () => {
cy.visit("/posts/new"); // 1.
cy.get("input.post-title") // 2.
.type("My First Post"); // 3.
cy.get("input.post-body") // 4.
.type("Hello, world!"); // 5.
cy.contains("Submit") // 6.
.click(); // 7.
cy.get("h1") // 8.
.should("contain", "My First Post");
});
});
import re
from playwright.sync_api import Page, expect
def test_homepage_has_Playwright_in_title_and_get_started_link_linking_to_the_intro_page(page: Page):
page.goto("https://playwright.dev/")
# Expect a title "to contain" a substring.
expect(page).to_have_title(re.compile("Playwright"))
# create a locator
get_started = page.locator("text=Get Started")
# Expect an attribute "to be strictly equal" to the value.
expect(get_started).to_have_attribute("href", "/docs/intro")
# Click the get started link.
get_started.click()
# Expects the URL to contain intro.
expect(page).to_have_url(re.compile(".*intro"))