如何在Cypress中为特定的GraphQL请求添加别名?
在Cypress中,您可以为特定的网络请求添加别名,然后可以“等待”。如果您想在特定的网络请求触发并完成后在Cypress中执行某些操作,这一点尤其有用 下面来自Cypress文档的示例:如何在Cypress中为特定的GraphQL请求添加别名?,graphql,cypress,Graphql,Cypress,在Cypress中,您可以为特定的网络请求添加别名,然后可以“等待”。如果您想在特定的网络请求触发并完成后在Cypress中执行某些操作,这一点尤其有用 下面来自Cypress文档的示例: cy.server() cy.route('POST', '**/users').as('postUser') // ALIASING OCCURS HERE cy.visit('/users') cy.get('#first-name').type('Julius{enter}') cy.wait('@po
cy.server()
cy.route('POST', '**/users').as('postUser') // ALIASING OCCURS HERE
cy.visit('/users')
cy.get('#first-name').type('Julius{enter}')
cy.wait('@postUser')
然而,由于我在应用程序中使用GraphQL,别名不再是一件简单的事情。这是因为所有GraphQL查询共享一个端点/GraphQL
尽管无法单独使用url端点区分不同的graphQL查询,但可以使用operationName
区分graphQL查询(请参阅下图)
翻阅了文档之后,似乎没有一种方法可以使用请求正文中的operationName
来别名graphQL端点。我还将操作名
(黄色箭头)作为响应标题中的自定义属性返回;但是,我也没有找到一种方法来使用它来别名特定的graphQL查询
方法1失败:此方法尝试使用图中所示的紫色箭头
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
onResponse(reqObj) {
if (reqObj.request.body.operationName === 'editIpo') {
cy.wrap('editIpo').as('graphqlEditIpo');
}
},
});
cy.wait('@graphqlEditIpo');
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
headers: {
'operation-name': 'editIpo',
},
}).as('graphql');
cy.wait('graphql');
此方法不起作用,因为graphqlEditIpo
别名是在运行时注册的,因此,我收到的错误如下
CypressError:cy.wait()找不到“@graphqlEditIpo”的注册别名。可用别名为:“ipoInitial,graphql”
方法2失败:此方法尝试使用图中所示的黄色箭头
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
onResponse(reqObj) {
if (reqObj.request.body.operationName === 'editIpo') {
cy.wrap('editIpo').as('graphqlEditIpo');
}
},
});
cy.wait('@graphqlEditIpo');
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
headers: {
'operation-name': 'editIpo',
},
}).as('graphql');
cy.wait('graphql');
此方法不起作用,因为cy.route的options对象中的headers属性实际上意味着根据接受存根路由的响应头。在这里,我试图用它来识别我的特定graphQL查询,这显然是行不通的
这就引出了我的问题:如何在Cypress中别名特定的graphQL查询/突变?我错过了什么吗?如果“等待”而不是“别名”本身是主要目的,这是我迄今为止遇到的最简单的方法,是通过对常规graphql请求使用别名,然后针对新创建的别名进行递归函数调用“wait”,直到找到要查找的特定graphql操作。 e、 g 这当然有它的警告,在您的环境中可能有效,也可能无效。但它对我们有用 我希望Cypress将来能够以一种不那么烦人的方式实现这一点
注:我想感谢我从何处获得灵感,但它似乎在网络空间中消失了。因为我遇到了同样的问题,但我没有找到解决这个问题的真正方法,所以我结合了不同的选择,创建了一个解决我问题的变通方法。希望这也能帮助其他人 我并不是真的“等待”请求发生,而是基于
**/graphql
url捕获它们,并匹配请求中的operationName。匹配时,将以数据作为参数执行函数。在该函数中,可以定义测试
graphQLResponse.js
export const onGraphQLResponse = (resolvers, args) => {
resolvers.forEach((n) => {
const operationName = Object.keys(n).shift();
const nextFn = n[operationName];
if (args.request.body.operationName === operationName) {
handleGraphQLResponse(nextFn)(args.response)(operationName);
}
});
};
const handleGraphQLResponse = (next) => {
return (response) => {
const responseBody = Cypress._.get(response, "body");
return async (alias) => {
await Cypress.Blob.blobToBase64String(responseBody)
.then((blobResponse) => atob(blobResponse))
.then((jsonString) => JSON.parse(jsonString))
.then((jsonResponse) => {
Cypress.log({
name: "wait blob",
displayName: `Wait ${alias}`,
consoleProps: () => {
return jsonResponse.data;
}
}).end();
return jsonResponse.data;
})
.then((data) => {
next(data);
});
};
};
};
Cypress.Commands.add('waitGraphQL', {prevSubject:false}, (queryName) => {
Cypress.log({
displayName: 'wait gql',
consoleProps() {
return {
'graphQL Accumulator': graphql_accumulator
}
}
});
const timeMark = nowStamp('HHmmssSS');
cy.wrap(graphql_accumulator, {log:false}).should('have.property', queryName)
.and("satisfy", responses => responses.some(response => response['timeStamp'] >= timeMark));
});
在测试文件中
使用对象绑定数组,其中键是operationName,值是resolve函数
import { onGraphQLResponse } from "./util/graphQLResponse";
describe("Foo and Bar", function() {
it("Should be able to test GraphQL response data", () => {
cy.server();
cy.route({
method: "POST",
url: "**/graphql",
onResponse: onGraphQLResponse.bind(null, [
{"some operationName": testResponse},
{"some other operationName": testOtherResponse}
])
}).as("graphql");
cy.visit("");
function testResponse(result) {
const foo = result.foo;
expect(foo.label).to.equal("Foo label");
}
function testOtherResponse(result) {
const bar = result.bar;
expect(bar.label).to.equal("Bar label");
}
});
}
学分
export const onGraphQLResponse = (resolvers, args) => {
resolvers.forEach((n) => {
const operationName = Object.keys(n).shift();
const nextFn = n[operationName];
if (args.request.body.operationName === operationName) {
handleGraphQLResponse(nextFn)(args.response)(operationName);
}
});
};
const handleGraphQLResponse = (next) => {
return (response) => {
const responseBody = Cypress._.get(response, "body");
return async (alias) => {
await Cypress.Blob.blobToBase64String(responseBody)
.then((blobResponse) => atob(blobResponse))
.then((jsonString) => JSON.parse(jsonString))
.then((jsonResponse) => {
Cypress.log({
name: "wait blob",
displayName: `Wait ${alias}`,
consoleProps: () => {
return jsonResponse.data;
}
}).end();
return jsonResponse.data;
})
.then((data) => {
next(data);
});
};
};
};
Cypress.Commands.add('waitGraphQL', {prevSubject:false}, (queryName) => {
Cypress.log({
displayName: 'wait gql',
consoleProps() {
return {
'graphQL Accumulator': graphql_accumulator
}
}
});
const timeMark = nowStamp('HHmmssSS');
cy.wrap(graphql_accumulator, {log:false}).should('have.property', queryName)
.and("satisfy", responses => responses.some(response => response['timeStamp'] >= timeMark));
});
使用了来自的blob命令这对我来说很有用
Cypress.Commands.add('waitForGraph', operationName => {
const GRAPH_URL = '/api/v2/graph/';
cy.route('POST', GRAPH_URL).as("graphqlRequest");
//This will capture every request
cy.wait('@graphqlRequest').then(({ request }) => {
// If the captured request doesn't match the operation name of your query
// it will wait again for the next one until it gets matched.
if (request.body.operationName !== operationName) {
return cy.waitForGraph(operationName)
}
})
})
请记住尽可能使用唯一的名称编写查询,因为操作名称依赖于它。我使用了其中一些代码示例,但不得不稍微更改它,以便将onRequest参数添加到cy.route中,并添加日期。现在(可以添加任何自动递增器,打开此解决方案)允许在同一测试中多次调用同一GraphQL操作名称。谢谢你给我指明了正确的方向
Cypress.Commands.add('waitForGraph', (operationName) => {
const now = Date.now()
let operationNameFromRequest
cy.route({
method: 'POST',
url: '**graphql',
onRequest: (xhr) => {
operationNameFromRequest = xhr.request.body.operationName
},
}).as(`graphqlRequest${now}`)
//This will capture every request
cy.wait(`@graphqlRequest${now}`).then(({ xhr }) => {
// If the captured request doesn't match the operation name of your query
// it will wait again for the next one until it gets matched.
if (operationNameFromRequest !== operationName) {
return cy.waitForGraph(operationName)
}
})
})
使用:
cy.waitForGraph('QueryAllOrganizations').then((xhr) => { ...
这就是我如何区分每个GraphQL请求的方法。我们使用cypress Cumber预处理器,因此在/cypress/integration/common/中有一个common.js文件,我们可以在任何特征文件之前调用before和beforeach钩子 我在这里尝试了这些解决方案,但无法找到稳定的解决方案,因为在我们的应用程序中,许多GraphQL请求在某些操作的同时被触发 最后,我将每个GraphQL请求存储在一个名为GraphQL_accumulator的全局对象中,每个请求都有一个时间戳 然后,使用cypress命令should更容易管理单个请求 common.js:
beforeEach(() => {
for (const query in graphql_accumulator) {
delete graphql_accumulator[query];
}
cy.server();
cy.route({
method: 'POST',
url: '**/graphql',
onResponse(xhr) {
const queryName = xhr.requestBody.get('query').trim().split(/[({ ]/)[1];
if (!(queryName in graphql_accumulator)) graphql_accumulator[queryName] = [];
graphql_accumulator[queryName].push({timeStamp: nowStamp('HHmmssSS'), data: xhr.responseBody.data})
}
});
});
我必须从FormData中提取queryName,因为我们在请求头中还没有键operationName,但这将是使用该键的地方
commands.js
export const onGraphQLResponse = (resolvers, args) => {
resolvers.forEach((n) => {
const operationName = Object.keys(n).shift();
const nextFn = n[operationName];
if (args.request.body.operationName === operationName) {
handleGraphQLResponse(nextFn)(args.response)(operationName);
}
});
};
const handleGraphQLResponse = (next) => {
return (response) => {
const responseBody = Cypress._.get(response, "body");
return async (alias) => {
await Cypress.Blob.blobToBase64String(responseBody)
.then((blobResponse) => atob(blobResponse))
.then((jsonString) => JSON.parse(jsonString))
.then((jsonResponse) => {
Cypress.log({
name: "wait blob",
displayName: `Wait ${alias}`,
consoleProps: () => {
return jsonResponse.data;
}
}).end();
return jsonResponse.data;
})
.then((data) => {
next(data);
});
};
};
};
Cypress.Commands.add('waitGraphQL', {prevSubject:false}, (queryName) => {
Cypress.log({
displayName: 'wait gql',
consoleProps() {
return {
'graphQL Accumulator': graphql_accumulator
}
}
});
const timeMark = nowStamp('HHmmssSS');
cy.wrap(graphql_accumulator, {log:false}).should('have.property', queryName)
.and("satisfy", responses => responses.some(response => response['timeStamp'] >= timeMark));
});
允许cypress通过在/cypress/support/index.js中添加这些设置来管理GraphQL请求也很重要:
Cypress.on('window:before:load', win => {
// unfilters incoming GraphQL requests in cypress so we can see them in the UI
// and track them with cy.server; cy.route
win.fetch = null;
win.Blob = null; // Avoid Blob format for GraphQL responses
});
我是这样使用它的:
cy.waitGraphQL('QueryChannelConfigs');
cy.get(button_edit_market).click();
cy.waitGraphQL将等待最新的目标请求,该请求将在调用后存储
希望这能有所帮助。我们的用例涉及一个页面上的多个GraphQL调用。我们必须使用上述回复的修改版本:
Cypress.Commands.add('createGql', operation => {
cy.route({
method: 'POST',
url: '**/graphql',
}).as(operation);
});
Cypress.Commands.add('waitForGql', (operation, nextOperation) => {
cy.wait(`@${operation}`).then(({ request }) => {
if (request.body.operationName !== operation) {
return cy.waitForGql(operation);
}
cy.route({
method: 'POST',
url: '**/graphql',
}).as(nextOperation || 'gqlRequest');
});
});
问题是所有GraphQL请求共享相同的URL,因此,一旦为一个GraphQL查询创建了cy.route()
,Cypress就会将以下所有GraphQL查询与之匹配。匹配后,我们将cy.route()
设置为默认标签gqlRequest
或下一个查询
我们的测试:
cy.get(someSelector)
.should('be.visible')
.type(someText)
.createGql('gqlOperation1')
.waitForGql('gqlOperation1', 'gqlOperation2') // Create next cy.route() for the next query, or it won't match
.get(someSelector2)
.should('be.visible')
.click();
cy.waitForGql('gqlOperation2')
.get(someSelector3)
.should('be.visible')
.click();
在别的地方
顺便说一句,一旦你使用了这种方法,一切都会变得简单一些。这就是你想要的(Cypress 5.6.0中新增的): 文件:
我希望这有帮助 6.0.0中引入的
intercept
API通过请求处理函数支持这一点。我曾经