赛普拉斯 - 向页面对象添加重试
Cypress - adding retries to page object
为了方便以后的修改,我们将登录脚本放在了一个页面对象中。
//Login.js
export class Login {
username_input= () => cy.get('#LoginForm_username');
password_input= () => cy.get('#LoginForm_password');
login_button= () => cy.contains('Login');
profile= () => cy.get('.profile-content');
login(email, password){
this.username_input().type(email);
this.password_input().type(password);
this.login_button().click();
this.profile().should('exist')
return this;
}
}
所以稍后我们可以在任何规范文件中重用它
//actual.spec.js
import {Login} from "../../pages/login/Login";
it('logs in', () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
}
现在我们的登录页面出现了有时没有响应的奇怪行为。因此我们添加了重试。
我们找到了两种可行的方法,但都不理想:
解决方案一:
将重试参数放入配置文件
"retries": 2
为什么这不理想?
这会为每一个测试启用重试,这是我们不想要的。我们只需要它用于登录脚本。
方案二:
将重试参数放入 'it'
import {Login} from "../../pages/login/Login";
it('logs in', {retries:2} () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
}
为什么这不理想?
我们必须将参数放入每个规范文件中,如果我们想要更改重试次数或完全取消重试,我们需要在每个规范文件中进行更改。
解法三???
我现在正在寻找的是一种将重试参数放在 login.js
的登录功能中某处的方法,但我找不到这样做的方法。
(编辑 post 以包含第二个解决方法...)
cypress 的整个想法是避免不稳定的测试。如果您的登录有时会失败,有时不会,那么您的环境甚至在您开始之前就已经不稳定了。
- 如果您所有的测试都依赖于登录,那么在套件级别(在描述中)包含重试可能不是一个坏主意,但如果您只有一些测试依赖于登录,那么您可以将它们全部分组仅在一个套件中,然后在套件级别添加重试。
有点像..
describe('Your suite name', {
retries: {
runMode: 2,
openMode: 2,
}
}, () => {
//number of retries will be applied per test
it('logs in', () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
})
it('other test', () => {
})
})
- 通过 API 进行登录,这使它成为依赖于登录的测试的一部分(因此它不必通过 UI 并且您不会得到问题)。当测试登录本身时,仅在那里,重试该测试。
当你写:
Now our login page has the strange behavior of sometimes not responding.
你放心,你的测试没有问题。
更多想法:
即时重试更改
TLDR
优点:简单
缺点:使用内部命令
有一个内部命令可以让您在测试中更改重试次数。
我强调内部这个词是为了警告它可能会在某些新版本中停止工作。
用你的代码
export class Login {
...
login(email, password) {
const originalRetries = cy.state('runnable')._retries
cy.state('runnable')._retries = 5 // WARNING - internal command
this.username_input().type(email);
...
cy.state('runnable')._retries = originalRetries // restore configured retries value
}
}
递归重试
TLDR
好处:适用于不稳定的情况
缺点:需要显式等待
如果你想要更长但更主流的解决方案,请使用递归。
您将需要一个非失败检查来处理重试或完成逻辑,这实际上意味着将 cy.get('.profile-content').should('exist')
更改为 jQuery 等效项并使用显式等待(缺点是这个模式)。
export class Login {
...
loginAttempt(attempt = 0) {
if (attempt === 3) throw 'Unable to login after 3 attempts'
this.login_button().click();
// wait for login actions to change the page
cy.wait(200).then(() => {
// test for success
if (Cypress.$('.profile-content').length > 0) {
return // found it, so finish
}
// retry
loginAttempt(email, password, ++attempt)
})
}
login(email, password) {
this.username_input().type(email);
this.password_input().type(password);
loginAttempt()
return this
}
}
你可以把cy.wait(200)
降低一点,提高最大尝试次数,比如说cy.wait(20)
和if (attempt === 300)
缺点是每次重试都会占用更多的堆内存,所以做得太过火是有害的 - 您需要进行试验。
直接设置登录状态
TLDR
好处:绕过测试的不稳定部分
缺点:需要了解登录状态
您可能会考虑的另一个方面是完全删除不稳定的登录。为此,您需要找出应用认为已登录的内容。是cookie、localstorage值等吗
对于每个需要处于登录状态但实际上并未测试登录过程的测试,直接设置该状态。
这里给出了一个例子recipes - logging-in__using-app-code
describe('logs in', () => {
it('by using application service', () => {
cy.log('user service login')
// see https://on.cypress.io/wrap
// cy.wrap(user promise) forces the test commands to wait until
// the user promise resolves. We also don't want to log empty "wrap {}"
// to the command log, since we already logged a good message right above
cy.wrap(userService.login(Cypress.env('username'), Cypress.env('password')), {
log: false,
}).then((user) => {
// the userService.login resolves with "user" object
// and we can assert its values inside .then()
// confirm general shape of the object
expect(user).to.be.an('object')
expect(user).to.have.keys([
'firstName',
'lastName',
'username',
'id',
'token',
])
// we don't know the token or id, but we know the expected names
expect(user).to.contain({
username: 'test',
firstName: 'Test',
lastName: 'User',
})
})
function login (username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}
return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
.then(handleResponse)
.then((user) => {
// login successful if there's a jwt token in the response
if (user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user))
}
return user
})
}
为了方便以后的修改,我们将登录脚本放在了一个页面对象中。
//Login.js
export class Login {
username_input= () => cy.get('#LoginForm_username');
password_input= () => cy.get('#LoginForm_password');
login_button= () => cy.contains('Login');
profile= () => cy.get('.profile-content');
login(email, password){
this.username_input().type(email);
this.password_input().type(password);
this.login_button().click();
this.profile().should('exist')
return this;
}
}
所以稍后我们可以在任何规范文件中重用它
//actual.spec.js
import {Login} from "../../pages/login/Login";
it('logs in', () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
}
现在我们的登录页面出现了有时没有响应的奇怪行为。因此我们添加了重试。
我们找到了两种可行的方法,但都不理想:
解决方案一:
将重试参数放入配置文件
"retries": 2
为什么这不理想?
这会为每一个测试启用重试,这是我们不想要的。我们只需要它用于登录脚本。
方案二:
将重试参数放入 'it'
import {Login} from "../../pages/login/Login";
it('logs in', {retries:2} () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
}
为什么这不理想?
我们必须将参数放入每个规范文件中,如果我们想要更改重试次数或完全取消重试,我们需要在每个规范文件中进行更改。
解法三???
我现在正在寻找的是一种将重试参数放在 login.js
的登录功能中某处的方法,但我找不到这样做的方法。
(编辑 post 以包含第二个解决方法...)
cypress 的整个想法是避免不稳定的测试。如果您的登录有时会失败,有时不会,那么您的环境甚至在您开始之前就已经不稳定了。
- 如果您所有的测试都依赖于登录,那么在套件级别(在描述中)包含重试可能不是一个坏主意,但如果您只有一些测试依赖于登录,那么您可以将它们全部分组仅在一个套件中,然后在套件级别添加重试。
有点像..
describe('Your suite name', {
retries: {
runMode: 2,
openMode: 2,
}
}, () => {
//number of retries will be applied per test
it('logs in', () => {
login.login(Cypress.env('userEmail'), Cypress.env('userPass'))
})
it('other test', () => {
})
})
- 通过 API 进行登录,这使它成为依赖于登录的测试的一部分(因此它不必通过 UI 并且您不会得到问题)。当测试登录本身时,仅在那里,重试该测试。
当你写:
Now our login page has the strange behavior of sometimes not responding.
你放心,你的测试没有问题。
更多想法:
即时重试更改
TLDR
优点:简单
缺点:使用内部命令
有一个内部命令可以让您在测试中更改重试次数。
我强调内部这个词是为了警告它可能会在某些新版本中停止工作。
用你的代码
export class Login {
...
login(email, password) {
const originalRetries = cy.state('runnable')._retries
cy.state('runnable')._retries = 5 // WARNING - internal command
this.username_input().type(email);
...
cy.state('runnable')._retries = originalRetries // restore configured retries value
}
}
递归重试
TLDR
好处:适用于不稳定的情况
缺点:需要显式等待
如果你想要更长但更主流的解决方案,请使用递归。
您将需要一个非失败检查来处理重试或完成逻辑,这实际上意味着将 cy.get('.profile-content').should('exist')
更改为 jQuery 等效项并使用显式等待(缺点是这个模式)。
export class Login {
...
loginAttempt(attempt = 0) {
if (attempt === 3) throw 'Unable to login after 3 attempts'
this.login_button().click();
// wait for login actions to change the page
cy.wait(200).then(() => {
// test for success
if (Cypress.$('.profile-content').length > 0) {
return // found it, so finish
}
// retry
loginAttempt(email, password, ++attempt)
})
}
login(email, password) {
this.username_input().type(email);
this.password_input().type(password);
loginAttempt()
return this
}
}
你可以把cy.wait(200)
降低一点,提高最大尝试次数,比如说cy.wait(20)
和if (attempt === 300)
缺点是每次重试都会占用更多的堆内存,所以做得太过火是有害的 - 您需要进行试验。
直接设置登录状态
TLDR
好处:绕过测试的不稳定部分
缺点:需要了解登录状态
您可能会考虑的另一个方面是完全删除不稳定的登录。为此,您需要找出应用认为已登录的内容。是cookie、localstorage值等吗
对于每个需要处于登录状态但实际上并未测试登录过程的测试,直接设置该状态。
这里给出了一个例子recipes - logging-in__using-app-code
describe('logs in', () => {
it('by using application service', () => {
cy.log('user service login')
// see https://on.cypress.io/wrap
// cy.wrap(user promise) forces the test commands to wait until
// the user promise resolves. We also don't want to log empty "wrap {}"
// to the command log, since we already logged a good message right above
cy.wrap(userService.login(Cypress.env('username'), Cypress.env('password')), {
log: false,
}).then((user) => {
// the userService.login resolves with "user" object
// and we can assert its values inside .then()
// confirm general shape of the object
expect(user).to.be.an('object')
expect(user).to.have.keys([
'firstName',
'lastName',
'username',
'id',
'token',
])
// we don't know the token or id, but we know the expected names
expect(user).to.contain({
username: 'test',
firstName: 'Test',
lastName: 'User',
})
})
function login (username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}
return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
.then(handleResponse)
.then((user) => {
// login successful if there's a jwt token in the response
if (user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user))
}
return user
})
}