提问人:Zuno 提问时间:11/10/2023 最后编辑:TesterDickZuno 更新时间:11/14/2023 访问量:71
Cypress - 验证别名的存在或将别名中的文本与“if”语句中的字符串进行比较
Cypress - verifying presence of alias or comparing text in alias to string in 'if' statement
问:
我正在使用 Cypress 创建一个 API 自动化套件。
我有不需要登录的 API 测试,也有需要登录的 API 测试。
我编写了一个名为“callAPI”的实用程序方法,我的所有测试都将使用它 - 它有一堆错误检查,并根据调用者传递到方法中的内容,自动使用相关的标头/授权令牌来增强调用。
我还有一个“logIn”方法,它创建一个存储授权令牌的别名。
在自动化工程师调用 callAPI 来执行需要登录的 API 请求的情况下,我希望能够检测到他们尚未登录并抛出错误消息以提示登录。
方法1: 如果别名“accessToken”不存在,则引发错误。
问题: cy.state('aliases') 的使用似乎已被弃用。
方法2: 在“before”钩子中创建别名,默认值为“accessToken not set”...
before(() => {
cy.wrap('accessToken not set').as('accessToken');
});
然后在 callAPI 中,检查别名的值是否等于“accessToken not set”并抛出错误。
问题:
if (useAccessToken && cy.get('@accessToken') == 'accessToken not set') {
throw new Error('callAPI() - access token not set. Log in first.');
}
if (useAccessToken && cy.get('@accessToken').invoke('text') == 'accessToken not set') {
throw new Error('callAPI() - access token not set. Log in first.');
}
第一个“if”语句显示“此比较似乎是无意的,因为类型'Chainable'和'string'没有重叠。
第二个“if”语句显示“此比较似乎是无意的,因为类型'Chainable'和'string'没有重叠。
我可能错误地认为在“if”语句中比较别名内的文本应该是微不足道的,但显然这不是一回事......?
方法3: 这里的目的是在“当时”内进行比较。
cy.get('@accessToken').then(data => {
cy.log(data.text());
});
问题: “data.text 不是函数”
方法4: 在我的“if”语句中,断言别名的文本等于“accessToken not set”。
if (useAccessToken && cy.get('@accessToken').should('equal', 'accessToken not set')) {
throw new Error('callAPI() - access token not set. Log in first.');
}
问题: 如果此断言失败,Cypress 将抛出自己的错误,从而破坏抛出自定义错误的对象。
我的代码:
const apiURL = Cypress.env('apiUrl');
export const APIURLs = {
Login: `${apiURL}access/login`,
Info: `${apiURL}info`,
InfoHealth: `${apiURL}info/health`
};
export class Common {
logInAndSetAccessToken(emailAddress: string, password: string) {
this.callAPI({
requestObject: {
method: 'POST',
url: APIURLs.Login,
body: {
email: emailAddress,
password: password
}
}, useAccessToken: false
}).then(response => {
cy.wrap(response.body.tokens.accessToken).as('accessToken');
});
}
/**
* 'headers' property, along with the following headers are automatically added to requestObject:
*
* 'Content-Type': 'application/json; charset=utf-8'
*
* 'Authorization': `Bearer ${accessToken}`
*/
callAPI({ url = '', requestObject = {}, useAccessToken = true } = {}) {
if (url.length > 3 && Object.keys(requestObject).length > 0) {
throw new Error('callAPI() - method call ambigious. Pass url or requestObject, not both.');
}
if (url.length < 4 && Object.keys(requestObject).length == 0) {
throw new Error('callAPI() - method call missing necessary information to make API call. Pass url or requestObject.');
}
if (useAccessToken && cy.get('@accessToken') == 'accessToken not set') { // This comparison appears to be unintentional because the types 'Chainable<JQuery<HTMLElement>>' and 'string' have no overlap.
throw new Error('callAPI() - access token not set. Log in first.');
}
if (Object.keys(requestObject).length > 0) {
if (!requestObject.method || !requestObject.url || !requestObject.body) {
throw new Error('callAPI() - method, url or body properties are missing in the requestObject.');
}
if (!requestObject.headers) {
Object.assign(requestObject, { headers: {} });
}
if (!requestObject.headers['Content-Type']) {
Object.assign(requestObject.headers, { 'Content-Type': 'application/json; charset=utf-8' });
}
if (useAccessToken && !requestObject.headers['Authorization']) {
Object.assign(requestObject.headers, { 'Authorization': `Bearer ${cy.get('@accessToken')}` });
}
return cy.request(requestObject);
} else {
if (url.length < 4) {
throw new Error('callAPI() - invalid url, cannot call API.');
}
return cy.request(url);
}
}
}
除非我错过了显而易见的东西(不会让我感到惊讶),否则有什么方法可以解决这个问题吗?还是我应该只依靠 API 通知自动化工程师他们需要登录?
如果这很重要,我正在使用 TypeScript。
感谢您提供的任何帮助。
答:
- 在语句本身中直接使用值是不可能的,因为会产生一个类型。因此,比较 or 生成一个元素,而不是字符串。
cy.get('@alias')
if
cy.get()
Chainable<Any>
cy.get('@alias')
cy.get('@alias').invoke('text')
Chainable
- 别名是按测试重置的,因此,如果您确实想在测试之前设置别名,则需要在 .
beforeEach()
before()
cy.get()
命令包含引用存在的隐式断言,这意味着如果未找到,Cypress 将无法通过测试。cy.get('@alias')
- 幸运的是,我们不必使用来检索别名值 - 我们可以使用 Mocha 的共享上下文通过
this.alias
引用值。cy.get()
因此,考虑到所有这些,一个潜在的解决方案可能是:
describe('tests', () => {
beforeEach(() => {
cy.wrap('accessToken not set').as('accessToken');
});
it('tests something', function () {
if (useAccessToken && this.accessToken == 'accessToken not set') {
throw new Error('callAPI() - access token not set. Log in first.');
}
});
});
遗憾的是,使用需要在具有 Mocha 共享上下文对象的内容中调用该功能,而您的帮助程序函数似乎没有该上下文。this.*
相反,我们可以使用 Cypress 环境变量,这将允许同步检查并返回一个字符串变量进行评估。
logInAndSetAccessToken(emailAddress: string, password: string) {
this.callAPI({
requestObject: {
method: 'POST',
url: APIURLs.Login,
body: {
email: emailAddress,
password: password
}
}, useAccessToken: false
}).then(response => {
Cypress.env('accessToken', response.body.tokens.accessToken)
});
}
...
if (useAccessToken && Cypress.env('accessToken') == 'accessToken not set') { // This comparison appears to be unintentional because the types 'Chainable<JQuery<HTMLElement>>' and 'string' have no overlap.
throw new Error('callAPI() - access token not set. Log in first.');
}
需要注意的是:Cypress 环境变量仅按规范重置,因此访问令牌将在同一规范文件中从测试转移到测试。如果你想避免这种情况,你可以简单地在你的beforeEach()
(Cypress.env('accessToken', 'accessToken not set');
)
您的大多数方法都可以结合每种方法的最佳部分。
“然后”和自定义错误内的比较
结合你的方法,你就有了一些有效的东西
cy.wrap('some-token').as('accessToken')
cy.get('@accessToken').then(data => {
if(data === 'accessToken not set') {
throw new Error('callAPI() - access token not set. Log in first.')
}
})
cy.wrap('accessToken not set').as('accessToken')
cy.get('@accessToken').then(data => {
if(data === 'accessToken not set') {
throw new Error('callAPI() - access token not set. Log in first.')
}
})
使用 assert() 自定义消息
您可以与自定义消息一起使用来执行类似的操作assert
function aliasIsSet(data) {
const isSet = (data !== 'accessToken not set')
const msg = isSet ? 'access token is set'
:'callAPI() - access token not set. Log in first.'
assert(isSet, msg, {log:!isSet})
}
cy.wrap('some-token').as('accessToken')
cy.get('@accessToken').then(aliasIsSet)
cy.wrap('accessToken not set').as('accessToken')
cy.get('@accessToken').then(aliasIsSet)
您可以采取主动方法,在未设置令牌时进行调用。
在这里,我在构造函数中设置了别名,使其始终具有一个值,以便 Cypress 在调用时不会中断。cy.get('@accessToken')
export class Common {
constructor() {
cy.wrap('accessToken not set').as('accessToken')
}
logInAndSetAccessToken(emailAddress: string, password: string) {
this.callAPI({
...
}, useAccessToken: false
}).then(response => {
cy.wrap(response.body.tokens.accessToken).as('accessToken');
});
}
callAPI({ url = '', requestObject = {}, useAccessToken = true } = {}) {
cy.get('@accessToken').then(data => {
if(useAccessToken && data === 'accessToken not set') {
logInAndSetAccessToken(emailAddress, password)
}
})
if (url.length > 3 && Object.keys(requestObject).length > 0) {
... rest of this method
或者,您可以通过使用类属性来完全避免别名问题
export class Common {
logInAndSetAccessToken(emailAddress: string, password: string) {
this.callAPI({
...
}, useAccessToken: false
}).then(response => {
this.token = response.body.tokens.accessToken;
});
}
callAPI({ url = '', requestObject = {}, useAccessToken = true } = {}) {
if(useAccessToken && !this.token) {
logInAndSetAccessToken(emailAddress, password)
}
if (url.length > 3 && Object.keys(requestObject).length > 0) {
... rest of this method
谢谢大家的帮助。
我最终将 logIn 方法和 callAPI 方法分开,如果需要使用访问令牌,请在 callAPI 中调用 logIn 方法。
export class Common {
/**
* Strictly for testing logging in - DOES NOT save access token for future calls.
*/
logIn(emailAddress: string, password: string) {
return cy.request({
method: 'POST',
url: APIURLs.AccessLogin,
body: {
email: emailAddress,
password: password
}
});
}
/**
* For API calls that require logging in/access token, pass userObject.
*/
callAPI({ url = '', requestObject = {}, useAccessToken = true, userObject = {} } = {}) {
if (url.length > 0 && Object.keys(requestObject).length > 0) {
throw new Error('callAPI() - method call ambigious. Pass url or requestObject, not both.');
}
if (url.length < (apiURL.length + 4) && Object.keys(requestObject).length == 0) {
throw new Error('callAPI() - method call missing necessary information to make API call. Pass url or requestObject.');
}
if (useAccessToken && Object.keys(userObject).length == 0) {
throw new Error('callAPI() - cannot use access token without passing a userObject from configuration.');
}
if (Object.keys(requestObject).length > 0) {
if (!requestObject.method || !requestObject.url || !requestObject.body) {
throw new Error('callAPI() - method, url or body properties are missing in the requestObject.');
}
if (!requestObject.headers) {
Object.assign(requestObject, { headers: {} });
}
if (!requestObject.headers['Content-Type']) {
Object.assign(requestObject.headers, { 'Content-Type': 'application/json; charset=utf-8' });
}
} else {
if (url.length < (apiURL.length + 4)) {
throw new Error('callAPI() - invalid url, cannot call API.');
}
}
if (useAccessToken) {
return this.logIn(userObject.Email, userObject.m_UIPassword).then(response => {
const accessToken = response.body.tokens.accessToken;
if (!requestObject.headers['Authorization']) {
Object.assign(requestObject.headers, { 'Authorization': `Bearer ${accessToken}` });
} else {
requestObject.headers['Authorization'] = `Bearer ${accessToken}`;
}
return cy.request(requestObject);
});
} else {
if (Object.keys(requestObject).length > 0) {
return cy.request(requestObject);
} else {
return cy.request(url);
}
}
}
}
评论