本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-07-07
PageObject 模式是自动化测试中的一个最佳实践,具备以下特征:
PageObject 减少了代码冗余,使业务流程变得清晰可读,降低了测试维护成本。
举例:Cypress 中的 PageObject 模式
// commonPage.js
export default class CommonPage {
constructor() {
// ...
}
isTargetPage() {
cy.url().should('eq', this.url);
}
}
// login.js
import CommonPage from './commonPage';
export default class LoginPage extends CommonPage {
constructor() {
super();
this.userNameLocator = 'input[name=username]';
this.passwordLocator = 'input[name=password]';
this.formLocator = 'form';
this.url = 'http://localhost:7077/login';
}
get username() {
return cy.get(this.userNameLocator);
}
get password() {
return cy.get(this.passwordLocator);
}
get form() {
return cy.get(this.formLocator);
}
login(username, password) {
this.userName.type(username);
this.password.type(password);
this.form.submit();
}
}
// mainPage.js
import CommonPage from './commonPage';
export default class mainPage extends CommonPage {
constructor() {
super();
this.h1Locator = 'h1';
this.url = 'http://localhost:7077/dashboard';
}
get welcomeText() {
return cy.get(this.h1Locator);
}
}
在测试中使用:
import LoginPage from '../pages/login';
import MainPage from '../pages/main';
descrbe('登录测试', () => {
const username = 'jane.lane';
const password = 'password123';
it('登录成功', () => {
cy.visit('/login');
const loginInstance = new LoginPage();
loginInstance.isTargetPage();
loginInstance.login(username, password);
cy.url().should('include', '/dashboard');
const mainInstance = new MainPage();
mainInstance.isTargetPage();
mainInstance.welcomeText.should('contain', 'jane.lane');
})
})
这就是 PageObject 的一个典型场景,需要注意的是,PageObject模式存在这样一个问题,如果一个测试需要访问多个页面对象,就意味着测试要初始化多个页面对象实例,如果这个页面对象需要登录才能访问(大部分是这样),则每次初始化都需要先登录再访问,无形增加了测试运行的时间。
这种场景下 Cypress 不认为 PageObject 是一个好的模式,Cypress 认为跨页面共享逻辑是一个反模式(Anti-Pattern),因为 Cypress 的实现原理与其他工具完全不同,Cypress 提供了很多方式允许用户通过“捷径”直接设置被测应用达到测试状态,而无须在不同页面一遍又一遍执行相同的操作。这个捷径就是 Custom Commands。
自定义命令默认存放在 cypress/support/commands.js
文件中,它会在任何测试文件被导入之前加载。
// cypress/support/commands.js
Cypress.Commands.add('login', (username, password) => {
cy.get('input[name=username]').type(username);
cy.get('input[name=password]').type(`${password}${enter}`);
})
在测试代码中这样使用:
it('登录成功', () => {
cy.visit('/login');
cy.login(username, password);
const mainInstance = new MainPage();
mainInstance.isTargetPage();
mainInstance.welcomeText.should('contain', 'jane.lane');
})
自定义命令的好处体现在:
比如,重写 visit 命令:
Cypress.Commands.overwrite('visit', (originalFn, url) => {
// 比如 重写 visit 命令,使每个visit命令都打印一行+++符号在 console里
console.log('++++++++++++');
// originalFn 代表传入进来的原 'visit' 命令
// url 是 visit() 接受到的 url 地址
return originalFn(url);
})
数据驱动是测试框架中的一个必要功能,使用数据驱动,可以在不增加代码量的前提下根据数据生成不同的测试策略。
describe('测试数据放在前置条件里', () => {
let testData;
beforeEach(() => {
testData = [
{ "name": 'iTesting', "password": "helloqa"},
{ "name": 'kevin', "password": "helloqa"},
]
})
for (const data in testData) {
it('demo1', () => {
cy.login(data.name, data.password);
})
}
})
describe('测试外部数据', () => {
it('测试外部数据$', () => {
// example.json 存放在 cypress/fixtures 下
cy.fixture('example.json').as('testData');
cy.get('@testData').each((data) => {
cy.log(data.name);
cy.log(data.password);
})
})
})
import testData from '../../settings/user.json';
describe('数据保存在自定义文件中', () => {
for (const data in testData) {
it('demo1', () => {
cy.login(data.name, data.password);
})
}
})
除了设置 cypress.json 中的 env 字段外,还可以使用专门的 cypress.env.json,例如在 cypress/config 下建立两个文件 cypress.dev.json
和 cypress.qa.json
// cypress.dev.json
{
"baseUrl": "http://localhost:7077/login",
"env": {
"uesrname": "jane.lane",
"password": "password123"
}
}
// cypress.qa.json
{
"baseUrl": "http://localhost:7077/login",
"env": {
"uesrname": "wrongUser",
"password": "wrongPassword"
}
}
在 plugins/index.js
更改配置如下:
// plugins/index.js
const fs = require('fs-extra');
const path = require('path');
function getConfig(env) {
const configPath = path.resolve('..', 'Cypress/cypress/config', `cypress.${env}.json`)
return fs.readJson(configPath)
}
module.exports = (on, config) => {
// 取参数
const file = config.env.configEnv || 'dev';
// 修改config
return getConfig(file);
}
这些运行命令行:
# 指定环境为 qa
npm run cypress:open --env configEnv=qa
设置 cypress.json
:
{
// ...
"targetEnv": "dev",
"env": {
"dev": {
"uesrname": "jane.lane",
"password": "password123",
"url": "http://localhost:5883"
},
"qa": {
"uesrname": "wrongUser",
"password": "wrongPassword",
"url": "https://qa.test.com:5883"
}
}
}
更改 support/index.js
:
关于 support 的作用,请前往 here
// 接受用户的参数 testEnv,如果没有指定 testEnv,则使用 cypress.json 中 targetEnv 的默认设置
beforeEach(() => {
const targetEnv = Cypress.env('targetEnv') || Cypress.config('targetEnv');
cy.log(`测试环境为: \n ${JSON.stringify(targetEnv)}`);
cy.log(`测试环境详细配置为: \n ${JSON.stringify(Cypress.env(targetEnv))}`);
// targetEnv = 'qa'
// Cypress.env(targetEnv) 取出对应的配置
Cypress.config('baseUrl', Cypress.env(targetEnv).url);
})
运行命令行指定运行的测试环境:
npm run cypress:open --env testEnv=qa
是指给测试用例添加一个或多个相应描述关键字,在运行时,指定相应的关键字,运行或者排斥测试用例。
1、安装插件
npnm i --save-dev cypress-select-tests
2、更改 cypress/plugins/index.js
const selectTestsWithGrep = require('cypress-select-tests/grep');
module.exports = (on, config) => {
on('file:preprocessor', selectTestsWithGrep(confog));
}
3、这样编写测试用例
describe("测试登录", () => {
const username = 'jane.lane';
const password = 'password123';
context('登录成功, 跳转到 dashboard 页', () => {
it("['smoke'] 登录用例1", () => {
cy.visit('http://localhost:7077/login');
cy.get('input[name=username]').type(username);
cy.get('input[name=password]').type(password);
cy.get('form').submit();
cy.get('h1').should('contain', 'jane.lane')
})
it("[e2e, 'smoke'] 登录用例2", () => {
cy.log('iTesting');
})
})
})
4、这样指定标签运行
npm run cypress:open --env grep=e2e
或者直接根据文件名来筛选待测试用例:
# 所有文件名中包含 “Login” 字符的测试套件将被运行
npm run cypress:open --env fgrep=Login
由于各种不确定因素,偶尔会发生测试用例失败的情况,此时如果测试用例可以自动重新运行,则减少了测试由于环境等不确定因素失败的情况。
1、安装 cypress-plugin-retries
npm i -D cypress-plugin-retries
2、在 cypress/support/index.js
下增加以下代码:
require('cypress-plugin-retries')
3、配置 package.json
执行脚本。
// package.json
{
"scripts": {
"retryCases": "CYPRESS_RETRIES=2 cypress run" // 重试测试为2次
}
}
4、使用
npm run retryCases
在测试时,为了达到某个测试状态,有时需要连接DB,这时候需要使用到 cy.task()
它的语法是:
cy.task(event);
cy.task(event, arg);
cy.task(event, arg, options);
1、修改 cypress/plugins/index.js
module.exports = (on, config) => {
on('task', {
log(message) {
console.log(message);
return null
}
})
}
2、在测试代码中这样使用
// 触发 log task
cy.task('log', '这是一个log')
1、安装 mysql
npm i mysql --save-dev
2、配置 cypress.json
{
// ...
"env": {
// ...
"db": {
"host": "your-host",
"user": "your-username",
"password": "your-password",
"database": "your-database"
}
}
}
3、修改 cypress/plugins.js
const mysql = require('mysql');
function queryTestDb(query, config) {
const connection = mysql.createConnection(config.env.db);
connection.connect();
return new Promise((resolve, reject) => {
connect.query(query, (error, results) => {
if (error) {
reject(error);
} else {
connection.end();
console.log(results);
return resolve(results);
}
})
})
}
module.exports = (on, config) => {
on('task', {
queryDb: (query) => {
return queryTestDb(query, config)
}
})
}
4、这样使用
var query = 'select id from id_table';
cy.task('queryDb', query);