-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow dynamically matching requests with a function #387
Comments
a workaround for missing dynamic routing capabilities see cypress-io/cypress#521 and cypress-io/cypress#387
I have the same use case (using GraphQL). At the moment, rather than being able to wait on requests, having to hardcode timeouts wherever there are requests that can take a couple seconds. Being able to match on operation name and waiting on it to return would let us write tests in a way more similar to how the Cypress docs recommend. |
Hey @davidnorth - I'm researching for solutions for the same issue you reported. I'd be extremely grateful if you could share what solutions, if any, you have used to enable defining multiple graphQL routes in a test and waiting for specific ones to complete. Were you able to do this in Cypress (or any other e2e testing framework)? Hey @jennifer-shehane - any progress or rough ETAs on this feature 🙏 ? We are increasingly working on applications that utilise graphQL and Cypress' lack of isolating specific requests to |
+1 here |
@nazar There has been no work currently done on this feature. |
Thank you for the update @jennifer-shehane I've been able to find a work-around that provides me with the required functionality. I feel it's a bit hacky but it works for now. My beforeEach(() => {
cy
.server()
.route({
method: 'POST',
url: '/api/graphql' <---- this is our graphQL endpoint
})
.as('graphqlRequest');
}); And the following in my Cypress.Commands.add('waitForQuery', operationName => {
cy.wait('@graphqlRequest').then(({ request }) => {
if (request.body.operationName !== operationName) {
return cy.waitForQuery(operationName); <---- this is the hacky bit
}
});
}); The above can be used as follows: it('Should Foo Bar', () => {
cy
.waitForQuery('fooQuery')
.get('[data-cy=bar]')
.should('exist')
// can also be used to check XHR payloads
.waitForQuery('fooQuery')
.its('request')
.then(({ body: { variables: { search: { foo } } } }) => expect(foo).to.equal(1))
}); HTH anybody with the same requirements. |
I tried to solve the issue a different way where not to actually wait for the request to happen, but to execute a function that contains the tests when the request got a response. stackoverflow The reason I want to solve it this way is because I also think the solution that nazar is using is a bit hacky. Now the only issue I get is that the test passes while one of the expects fails (see screenshot). I think this is because Is there a wait to tell Cypress that the test should fail? 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);
}).catch((error) => {
return error;
});
};
};
}; In a test file Bind an array with objects where the key is the operationName and the value is the resolve function. 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");
}
});
} Credits Used the blob command from glebbahmutov.com |
Opened a PR for this feature request after spending way to much time trying to hack it in in userland. This should be supported out of the box IMO. |
I'm using this approach at the moment: const stubFetch = (win, routes) => {
const fetch = win.fetch;
cy.stub(win, 'fetch').callsFake((...args) => {
const routeIndex = routes.findIndex(r => matchRoute(r, args));
if (routeIndex >= 0) {
const route = routes.splice(routeIndex, 1)[0];
const response = {
status: route.status,
headers: new Headers(route.headers),
text: () => Promise.resolve(route.response),
json: () => Promise.resolve(JSON.parse(route.response)),
ok: route.status >= 200 && route.status <= 299,
};
return Promise.resolve(response);
} else {
console.log('No route match for:', args[0]);
return fetch(...args);
}
});
};
Cypress.Commands.add('stubFetch', ({ fixture }) => {
return cy.fixture(fixture, { log: false }).then(routes => {
cy.on('window:before:load', win => stubFetch(win, routes));
});
});
const escapeRegExp = string => {
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
};
const matchRoute = (route, fetchArgs) => {
const url = fetchArgs[0];
const options = fetchArgs[1];
const method = options && options.method ? options.method : 'GET';
const body = options && options.body;
// use pattern matching of the timestamp parameter
const urlRegex = escapeRegExp(route.url);
if (method === route.method && decodeURIComponent(url).match(new RegExp(urlRegex))) {
if (body && route.body) {
// some custom logic to match the bodies
} else {
return route;
}
}
}; |
I've run into the same issue and have created a command that supports 2 different methods which should be a good jumping off point for those that need it.
|
for those who got the same question, the official doc has provide a solution: https://docs.cypress.io/api/commands/route.html#Options you can use
to change the xhr.response, an example willl looks like this:
|
@thinkerelwin How can you use onResponse to wait for a response body that satisfies some application specific criteria? How do you prevent cy.wait('@namedRoute') from completing on "just any" request that matches the location and method declared in cy.route? |
Once #8974 is merged, you will be able to dynamically alias cy.route2('POST', '/graphql', (req) => {
if (req.body.includes('mutation')) {
req.alias = 'gqlMutation'
}
})
// assert that a matching request has been made
cy.wait('@gqlMutation') |
The code for this is done in cypress-io/cypress#8974, but has yet to be released. |
Released in This comment thread has been locked. If you are still experiencing this issue after upgrading to |
To allow highly flexible matching of which requests you want to stub, we could add the option to accept a function instead of a fixed method/URL. This would recieve the request object (or some wrapper around it). The motivation is that I have and SPA that uses a GraphQL backend where all requests have the same URL and I want to filter them by the query name which is sent as a parameter in the POST body.
The text was updated successfully, but these errors were encountered: