质量第一,稳定压倒一切。
当下项目的重构接近尾声,大部分模块已在线上环境运行一段时间了。对一些基础核心业务,部分逻辑甚少发生变化的功能模块的稳定性是开发和测试需要考虑的问题。
日常业务项目开发的痛点之一便是前端的回归测试,免不了各种手动点点点,但凡改动了某个公用组件,函数,都要漫山遍野地把项目的主要页面都点进去看一遍有没有问题。目前项目产品迭代发版频繁,每次发版对回归测试验证工作是比较大的,消耗大量人力还不能完全保证回归测试100%场景覆盖。
相较于接口自动化测试,Web UI自动化测试面临着应用程序界面的复杂性和稳定性的诸多挑战,想建设起来完整的UI自动化测试需要更多人力和技术的投入。
依托于前端Chrome插件开发能力和现有ui自动化测试框架的结合,将自动化脚本可视化管理,来降低自动化测试的编写难度和维护成本。
因为本身对web自动化测试就比较感兴趣,恰巧今年项目在发版过程中遇到多次很明显的问题发到线上导致版本回滚, 这些事故完全可以通过UI自动化测试来提前发现避免一些很明显的bug带到线上去。
所以考虑在现有开发联调小工具插件中加UI测试的功能模块,从前端角度探索web ui自动化测试的可行性实践。探索出一个自动化测试雏形解决方案。
目前市面上已存在了很多 UI 自动化测试库和解决方案,百花齐放:
Selenium (多语言)
webdriver.io (nodejs)
Cypress (nodejs)
karate (java)
macaca (多语言)
Puppeteer
Nightwatch
...
方案虽然很多,不过合适的才是最好的,目前根据 我们项目的实际情况 ,针对 我们项目现在的现状和期望:
覆盖主流程功能测试用例
测试用例易编写,学习成本低
最终采用的是 Cypress,Cypress 的测试代码语法足够的简单且用javascript写脚本,前端同学上手无学习成本。
Cypress 是非常年轻但很受开发者欢迎的测试框架,本地开发的话仅需要nodejs环境不需要安装别的依赖,npm install Cypress 即可,开箱即用,对于ES6 ES7的语法天然支持,不仅支持本地浏览器直接模拟测试,也支持终端测试。
还有测试录屏功能,方便在测试失败的时候,查看当时的失败的场景,方便修改。整体来说上手快,学习成本较低。
使用cypress需要电脑先安装node.js环境。
创建一个空目录(my-cypress-demo),通过npm init -y 命令初始化创建package.json文件。
// 打开目录
cd my-cypress-demo
// 初始化创建package.json文件
npm init -y
通过npm安装cypress。
npm install cypress --save-dev
这个安装命令会在当前目录下自动创建如下内容:
|-- cypress.config.js // 配置文件
|-- cypress
-- fixtures // 用于存放自定义的json文件
-- e2e // 测试代码
-- plugins // 自定义指令时,与support文件夹组合使用
-- index.js
-- support
-- commands.js
-- index.js
package.json中添加Cypress的启动命令;
{
"scripts": {
"cypress:open": "cypress open"
}
}
启动时直接使用如下命令:
npm run cypress:open
至此已经把一个cypress.js的自动化工程搭建起来了,自动创建的cypress/e2e目录下有官方测试用例示例,可以直接运行启动命令体验一下。
优点:
简单易用:Cypress的API直观易懂,降低了学习曲线。
高性能:实时重载和WebSocket技术提高了测试执行速度和调试效率。
功能丰富:支持自动等待、截图、视频录制、网络请求监听等。
实时反馈:测试运行时提供实时步骤反馈,便于调试。
时间旅行:支持回放测试步骤,帮助深入理解测试过程。
易于集成:轻松集成到CI/CD流程中,支持自动化部署。
活跃社区:拥有活跃的社区和丰富的资源支持。
缺点:
语言限制:仅支持JavaScript,可能限制非JS开发者的使用。
浏览器支持有限:虽然支持多浏览器,但主要还是对Chrome和Electron的支持。
不支持多标签页:无法在多标签页或多窗口中执行测试。
用官方文档的一个例子说明一下测试代码怎么写:(cypress/e2e/1-getting-started/todo.cy.js)。
describe('example to-do app', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/todo')
})
it('displays two todo items by default', () => {
cy.get('.todo-list li').should('have.length', 2)
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})
it('can add new todo items', () => {
const newItem = 'Feed the cat'
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})
})
上面测试代码简洁语义化较好,代码量不多,也不需要写很多逻辑,上手比较简单。
一些常用Api代码示例。
describe('Cypress 常用API 示例', () => {
// 访问页面
it('访问 Cypress 官网', () => {
cy.visit('https://www.cypress.io')
cy.title().should('include', 'Cypress') // 断言页面标题包含 "Cypress"
})
// 元素查找与交互
it('在 Cypress 官网中查找并点击 "Features" 链接', () => {
cy.visit('https://www.cypress.io')
cy.contains('a', 'Features').click() // 查找包含 "Features" 的链接并点击
cy.url().should('include', '/features') // 断言URL中包含 "/features"
})
// 表单操作
it('在表单中填写并提交数据', () => {
cy.visit('https://example.cypress.io/commands/actions') // 假设这是一个包含表单的测试页面
cy.get('#email').type('test@example.com') // 在邮箱输入框中输入文本
cy.get('#submit').click() // 点击提交按钮
cy.url().should('not.include', '/commands/actions') // 断言提交后URL发生变化
})
// 断言
it('断言页面元素的存在与文本内容', () => {
cy.visit('https://www.cypress.io')
cy.get('h1').should('exist') // 断言页面中的h1标签存在
cy.get('h1').should('contain', 'Fast, Easy and Reliable Testing for Anything that Runs in a Browser') // 断言h1标签包含特定文本
})
// 循环与条件
it('遍历页面上的多个元素', () => {
cy.visit('https://example.cypress.io/commands/iterations-and-aliases') // 假设页面上有多个可迭代元素
cy.get('.item').each(($el, index, $list) => {
// 对每个元素执行操作,例如打印其文本
cy.wrap($el).invoke('text').then(text => {
console.log(`Item ${index + 1}: ${text}`)
})
})
})
// 等待与重试
it('等待元素加载完成', () => {
cy.visit('https://example.cypress.io/commands/waiting')
cy.get('.slow-loading-element', { timeout: 10000 }).should('be.visible') // 等待元素在10秒内变得可见
})
// 网络请求拦截
it('拦截并验证网络请求', () => {
cy.server() // 开启cy.route()和cy.stub()的拦截功能
cy.route('GET', '/some/api/endpoint').as('getApi') // 拦截GET请求并命名为'getApi'
cy.visit('https://example.cypress.io/commands/network-requests')
cy.wait('@getApi').then((xhr) => { // 等待名为'getApi'的请求完成
expect(xhr.status).to.eq(200) // 断言HTTP状态码为200
})
})
})
Cypress 的 API 设计得非常直观和强大,能够处理各种复杂的测试场景。通过充分利用这些 API,开发者可以编写出既易于维护又高效的测试代码,从而提高应用的质量和稳定性。
1.访问和操作网页
cy.visit(url): 加载并访问指定的 URL。cy.go(backOrForward): 在浏览器历史记录中向前或向后导航。
cy.reload(): 重新加载当前页面。
cy.location(): 获取并操作当前页面的 URL、路径、查询参数等。
2.DOM 元素查询和操作
cy.get(selector): 查询并返回匹配指定选择器的 DOM 元素。
cy.contains(content): 查询包含特定文本或子元素的 DOM 元素。
cy.check(selector): 选中复选框。cy.uncheck(selector): 取消选中复选框。cy.select(selector, option): 从下拉列表中选择一个选项。
cy.type(text): 在可编辑的元素中输入文本。cy.clear(): 清除可编辑元素中的内容。cy.click(selector): 点击元素。
3.等待和重试
cy.wait(ms): 等待指定的毫秒数。cy.wait(alias): 等待一个通过 cy.route() 或 cy.intercept() 拦截的网络请求完成。cy.should(chainer): 对 DOM 元素或其他对象执行断言,并在不满足条件时重试。
4.断言
cy.should(chainer): 对当前命令链的结果执行断言。
cy.and(chainer): 链式调用多个断言。cy.not(chainer): 反向断言,即断言某条件不成立。
5.网络请求
cy.intercept(url, options): 拦截和模拟网络请求。这是 Cypress 7.0 及更高版本中用于替代 cy.route() 和 cy.server() 的新方法。cy.wait(alias): 等待由 cy.intercept() 拦截的请求完成。
6.文件上传
cy.fixture(filePath): 加载测试夹具(fixture)文件,常用于模拟文件上传。cy.get(selector).attachFile(filePath): 将文件附加到文件输入元素上,模拟文件上传。
7.钩子(Hooks)
before(), beforeEach(), after(), afterEach(): 在测试运行的不同阶段执行代码,如设置前置条件、清理环境等。
8.工具函数
Cypress.env(variable): 访问 Cypress 配置文件或环境变量中定义的变量。
Cypress.log(): 记录自定义日志消息,这对于调试和构建复杂的测试非常有用。
9.插件和扩展
Cypress 还支持通过插件和扩展来增强其功能,比如添加新的命令、断言或报告器等。
未完待续,下篇我们将继续学习Page Object自动化测试模式、集成mochawesome生成测试报告、自定义cypress.config.js配置、Case数据转换、UI自动化平台架构方案、基于Chrome插件的测试脚本管理、生成自动化测试用例脚本文件等内容。
详细内容可点击下方链接或文末“阅读原文”~
《比Selenium好用!用这款工具来做UI自动化测试效率翻倍!(下)》
链接:https://juejin.cn/post/7410321051781906466
本文为51Testing经授权转载,转载文章所包含的文字来源于作者。如因内容或版权等问题,请联系51Testing进行删除