FilePond.setOptions({
server: {
fetch: (url, load, error, progress, abort, headers) => {
fetch(url)
.then(res => res.blob())
.then(load);
}
I've found this configuration online. I cannot understand why this works. Should'nt this lead to a stack-overflow?
IMO the function is calling itself recursively or am i getting anything wrong?
In this example the first fetch is a property of server, the second fetch is the JavaScript native fetch function.
You could also write it like this, which maybe makes things a bit more clear?
function getData(url, load) {
return fetch(url)
.then(res => res.blob())
.then(load)
}
const options = {
server: {
fetch: getData
}
}
FilePond.setOptions(options);
Related
I have been trying to grab an old response to assert it has a certain response.
The issue is that the same call is posted at the same time and I can only grab the second response.
I was just wondering if there was a way to grab both responses so I can read each body to make sure the correct posts are made
I have used the following
public assertMixPanelCall(call: string): void {
cy.intercept('POST', 'https://api-js.mixpanel.com/track/*', (req) => {
if (atob(req.body.replace('data=', '')).includes(`"event": "${call}"`)) {
req.alias = 'correctBody'
}
});
cy.wait('#correctBody');
}
So the response I get is the last response,
But I want to grab the penultimate response
I'm not seeing the complete picture, but I think you can use this pattern Intercepting a response
let responseCount = 0;
cy.intercept('POST', 'https://api-js.mixpanel.com/track/*', (req) => {
if (atob(req.body.replace('data=', '')).includes(`"event": "${call}"`)) {
req.continue((res) => {
responseCount++;
if (responseCount === 1) {
req.alias = 'penultimate'
}
if (responseCount === 2) {
req.alias = 'final'
}
})
}
});
cy.wait('#penultimate')
Not sure if dynamic aliasing works on a per-response basis.
There's also an undocumented alias suffix that lets you access the nth response
cy.wait('#correctBody'); // must wait first
cy.get('#correctBody.1'); // then get to access response history
// - not sure if you need #correctBody.0 or #correctBody.1
But I can't see why cy.wait('#correctBody') doesn't catch the first response, generally you need to issue the wait twice to get both responses. Anyway, there's some things to try out here.
So I found the solution
From wherever I want to start capturing
cy.intercept('POST', 'https://api-js.mixpanel.com/track/*').as('call');
generate array based on the number of calls previously I wish to check
const genArr = Array.from({length:noOfCalls},(v,k)=>k+1);
const calls = [];
cy.wrap(genArr).each(() => {
calls.push(`#${call}`)
})
make the call based on the amount of times I wish to post the response
cy.wait(calls).then(differentRequests => {
differentRequests.forEach(differentRequest => {
if(atob(differentRequest.request.body.replace('data=','')).includes(call)) {
pass = true
}
});
expect(pass).to.be.true
});
}
Got the solution from here
https://medium.com/slido-dev-blog/cypress-io-is-pretty-amazing-and-if-you-havent-used-it-yet-and-need-a-jump-start-i-recommend-my-66ee6ac5f8d9
Problem:
I’m using cypress with angular and apollo graphQl. I’m trying to mock the graph server so I write my tests using custom responses. The issue here is that all graph calls go on a single endpoint and that cypress doesn’t have default full network support yet to distinguish between these calls.
An example scenario would be:
access /accounts/account123
when the api is hit two graph calls are sent out - a query getAccountDetails and another one with getVehicles
Tried:
Using one stub of the graph endpoint per test. Not working as it stubs with the same stub all calls.
Changing the app such that the query is appended 'on the go' to the url where I can intercept it in cypress and therefore have a unique url for each query. Not possible to change the app.
My only bet seems to be intercepting the XHR call and using this, but I don't seem to be able to get it working Tried all options using XHR outlined here but to no luck (it picks only the stub declared last and uses that for all calls) https://github.com/cypress-io/cypress-documentation/issues/122.
The answer from this question uses Fetch and therefore doesn't apply:
Mock specific graphql request in cypress when running e2e tests
Anyone got any ideas?
With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
For anyone else hitting this issue, there is a working solution with the new cypress release using cy.route2()
The requests are sent to the server but the responses are stubbed/ altered on return.
Later Edit:
Noticed that the code version below doesn't alter the status code. If you need this, I'd recommend the version I left as a comment below.
Example code:
describe('account details', () => {
it('should display the account details correctly', () => {
cy.route2(graphEndpoint, (req) => {
let body = req.body;
if (body == getAccountDetailsQuery) {
req.reply((res) => {
res.body = getAccountDetailsResponse,
res.status = 200
});
} else if (body == getVehiclesQuery) {
req.reply((res) => {
res.body = getVehiclesResponse,
res.status = 200
});
}
}).as('accountStub');
cy.visit('/accounts/account123').wait('#accountStub');
});
});
Both your query and response should be in string format.
This is the cy command I'm using:
import * as hash from 'object-hash';
Cypress.Commands.add('stubRequest', ({ request, response, alias }) => {
const previousInteceptions = Cypress.config('interceptions');
const expectedKey = hash(
JSON.parse(
JSON.stringify({
query: request.query,
variables: request.variables,
}),
),
);
if (!(previousInteceptions || {})[expectedKey]) {
Cypress.config('interceptions', {
...(previousInteceptions || {}),
[expectedKey]: { alias, response },
});
}
cy.intercept('POST', '/api', (req) => {
const interceptions = Cypress.config('interceptions');
const receivedKey = hash(
JSON.parse(
JSON.stringify({
query: req.body.query,
variables: { ...req.body.variables },
}),
),
);
const match = interceptions[receivedKey];
if (match) {
req.alias = match.alias;
req.reply({ body: match.response });
}
});
});
With that is posible to stub exact request queries and variables:
import { MUTATION_LOGIN } from 'src/services/Auth';
...
cy.stubRequest({
request: {
query: MUTATION_LOGIN,
variables: {
loginInput: { email: 'test#user.com', password: 'test#user.com' },
},
},
response: {
data: {
login: {
accessToken: 'Bearer FakeToken',
user: {
username: 'Fake Username',
email: 'test#user.com',
},
},
},
});
...
Cypress.config is what make it possible, it is kind of a global key/val getter/setter in tests which I'm using to store interceptions with expected requests hash and fake responses
This helped me https://www.autoscripts.net/stubbing-in-cypress/
But I'm not sure where the original source is
A "fix" that I use is to create multiple aliases, with different names, on the same route, with wait on the alias between the different names, as many as requests you have.
I guess you can use aliases as already suggested in Answer by #Luis above like this. This is given in documentation too. Only thing you need to use here is multiple aliases as you have multiple calls and have to manage the sequence between them . Please correct me if i understood you question in other way ??
cy.route({
method: 'POST',
url: 'abc/*',
status: 200.
response: {whatever response is needed in mock }
}).as('mockAPI')
// HERE YOU SHOULD WAIT till the mockAPI is resolved.
cy.wait('#mockAPI')
I have an issue where we're using apollo client and specifically the useMutation react hook to perform mutation calls to our GraphQL Server.
At certain times, the server may return a 401 unauthorized response - at which point, we can make a call to special endpoint which re-authenticates the client and refreshes the cookie/token whatever.
I want to be able to re-run the same mutation again once the client is re-authenticated. So basically I would like to know if it is possible to do the following:
useMutation --> Receive 401 Unauthorized --> call to refresh token --> rerun same initial mutation
This is how our useMutation looks like:
const [mutationFunction, { data, ...rest }] = useMutation(query, {
onError(_err: any) {
const networkError = error?.networkError as any;
if (networkError?.statusCode === 401 && !refreshFailed) {
// eslint-disable-next-line prefer-destructuring
loading = true;
error = undefined;
fetch('/authentication/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(token => {
localStorage.setItem(jwtLocalStorageKey, token);
// re fetch here
})
.catch(() => {
refreshFailed = true;
});
} else {
showAlert(_err.message, 'error');
}
}
});
and this is how we call it currently:
const {
mutationFunction: updateTournamentUserMutation,
loading: updateTournamentUserLoading,
error: updateTournamentUserError,
data: updateTournamentUserData
} = useMutationHook(gqlUpdateTournamentUser);
updateTournamentUserMutation({ variables: { input } });
Because we're using hooks and the way we're using it above, I'm not entirely sure how we can save or reuse the same data that is initially sent in the first mutation (that is the mutation parameters)
Is it possible to do so using the current way we're doing it?
In Cypress, it is well-documented that you can alias specific network requests, which you can then "wait" on. This is especially helpful if you want to do something in Cypress after a specific network request has fired and finished.
Example below from Cypress documentation:
cy.server()
cy.route('POST', '**/users').as('postUser') // ALIASING OCCURS HERE
cy.visit('/users')
cy.get('#first-name').type('Julius{enter}')
cy.wait('#postUser')
However, since I'm using GraphQL in my app, aliasing no longer becomes a straightforward affair. This is because all GraphQL queries share one endpoint /graphql.
Despite it not being possible to differentiate between different graphQL queries using the url endpoint alone, it is possible to differentiate graphQL queries using operationName (refer to following image).
Having dug through the documentation, there doesn't appear to be a way to alias graphQL endpoints using operationName from the request body. I'm also returning the operationName (yellow arrow) as a custom property in my response header; however, I haven't managed to find a way to use it to alias specific graphQL queries either.
FAILED METHOD 1: This method attempts to use the purple arrow shown in image.
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
onResponse(reqObj) {
if (reqObj.request.body.operationName === 'editIpo') {
cy.wrap('editIpo').as('graphqlEditIpo');
}
},
});
cy.wait('#graphqlEditIpo');
This method doesn't work since the graphqlEditIpo alias is registered at runtime and as such, the error I receive is as follows.
CypressError: cy.wait() could not find a registered alias for: '#graphqlEditIpo'. Available aliases are: 'ipoInitial, graphql'.
FAILED METHOD 2: This method attempts to use the yellow arrow shown in image.
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
headers: {
'operation-name': 'editIpo',
},
}).as('graphql');
cy.wait('graphql');
This method doesn't work because the headers property in the options object for cy.route is actually meant to accept response headers for stubbed routes per the docs. Here, I'm trying to use it to identify my specific graphQL query, which obviously won't work.
Which leads me to my question: How can I alias specific graphQL queries/mutations in Cypress? Have I missed something?
The intercept API introduced in 6.0.0 supports this via the request handler function. I used it in my code like so:
cy.intercept('POST', '/graphql', req => {
if (req.body.operationName === 'queryName') {
req.alias = 'queryName';
} else if (req.body.operationName === 'mutationName') {
req.alias = 'mutationName';
} else if (...) {
...
}
});
Where queryName and mutationName are the names of your GQL operations. You can add an additional condition for each request that you would like to alias. You can then wait for them like so:
// Wait on single request
cy.wait('#mutationName');
// Wait on multiple requests.
// Useful if several requests are fired at once, for example on page load.
cy.wait(['#queryName, #mutationName',...]);
The docs have a similar example here: https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-requests.
This works for me!
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)
}
})
})
Just remember to write your queries with unique names as posible, because the operation name relies on it.
If 'waiting' and not 'aliasing' in itself is the main purpose, the easiest way to do this, as I've encountered thus far, is by aliasing the general graphql requests and then making a recursive function call to 'wait' targeting the newly created alias until you find the specific graphql operation you were looking for.
e.g.
Cypress.Commands.add('waitFor', operationName => {
cy.wait('#graphqlRequest').then(({ request }) => {
if (request.body.operationName !== operationName) {
return cy.waitFor(operationName)
}
})
})
This of course have its caveats and may or may not work in your context. But it works for us.
I hope Cypress enables this in a less hacky way in the future.
PS. I want to give credit to where I got the inspiration to this from, but it seemt to be lost in cyberspace.
Since I was having the same issue and I did not find a real solution for this problem I combined different options and created a workaround that solves my problem. Hopefully this can help someone else too.
I do not really 'wait' for the request to be happen but I catch them all, based on **/graphql url and match the operationName in the request. On a match a function will be executed with the data as parameter. In this function the tests can be defined.
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);
});
};
};
};
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
This is what you're looking for (New in Cypress 5.6.0):
cy.route2('POST', '/graphql', (req) => {
if (req.body.includes('operationName')) {
req.alias = 'gqlMutation'
}
})
// assert that a matching request has been made
cy.wait('#gqlMutation')
Documentation:
https://docs.cypress.io/api/commands/route2.html#Waiting-on-a-request
I hope that this helps!
I used some of these code examples but had to change it slightly to add the onRequest param to the cy.route and also add the date.Now (could add any auto incrementer, open to other solutions on this) to allow multiple calls to the same GraphQL operation name in the same test. Thanks for pointing me in the right direction!
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)
}
})
})
to use:
cy.waitForGraph('QueryAllOrganizations').then((xhr) => { ...
This is how I managed to differentiate each GraphQL request. We use cypress-cucumber-preprocessor so we have a common.js file in /cypress/integration/common/ where we can call a before and beforeEach hook which are called before any feature file.
I tried the solutions here, but couldn't come up with something stable since, in our application, many GraphQL requests are triggered at the same time for some actions.
I ended up storing every GraphQL requests in a global object called graphql_accumulator with a timestamp for each occurence.
It was then easier to manage individual request with cypress command 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})
}
});
});
I have to extract the queryName from the FormData since we don't have (yet) the key operationName in the request header, but this would be where you would use this key.
commands.js
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));
});
It's also important to allow cypress to manage GraphQL requests by adding these settings in /cypress/support/index.js:
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
});
I use it like this:
cy.waitGraphQL('QueryChannelConfigs');
cy.get(button_edit_market).click();
cy.waitGraphQL will wait for the latest target request, the one that will be stored after the call.
Hope this helps.
Somewhere else this method was suggested.
Btw it all becomes a bit easier once you migrate to Cypress v5.x and make use of the new route (route2) method.
Our use case involved multiple GraphQL calls on one page. We had to use a modified version of the responses from above:
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');
});
});
The issue is that ALL GraphQL requests share the same URL, so once you create a cy.route() for one GraphQL query, Cypress will match all the following GraphQL queries to that. After it matches, we set cy.route() to just a default label of gqlRequest or the next query.
Our test:
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();
I have a problem with redux trying to load initial data with an asynchronous call to my backend API that returns a JSON. Right now, I'm trying to load a bunch of different articles, but I have no idea how to load it asynchronously. Since it is an initial set of data, should I load it synchronously? If yes, then how would I acheive a synchronous call to my API? If not, how would I go about solving this problem asynchronously?
Right now, I have static json data, data/articles.js that creates a default state in store.js.
Thanks
You should use a redux-thunk middleware, which allows you to dispatch async actions and a fetch library (for example) for downloading your initial data.
So:
1) create an action which fetch your data, example:
export function fetchData() {
const options = {
method: 'GET',
headers: {
'Authorization': 'Client-ID xx' // if theres any needed
}
}
return (dispatch) => {
return fetch('yourUrl.json', options)
.then(response => response.json())
.then(data => dispatch(receiveYourData(data)))
.catch(err => console.log(err));
}
}
receiveYourData is a action which will place your data in your state, example:
export function receiveYourData (payload = []) {
return {
type: RECEIVE_DATA,
payload: payload
}
}
Of course you have to write action handler, which after dispatching an action, will place your data in your state.
If you have your setup (similar to above), you should dispatch fetchData in your componentDidMount lifecycle method (its one of the option of course :) ).
If you dont know how to do particular parts, you can refer to this Example.
Also official async example may be helpful :)
I also had this problem. It turned out that you have to add a lot of code for this simple task. So I simplified this process and created a package for async loading of initial state in redux - redux-async-initial-state.
You can check out examples and in your case in the end your store creator will look like this:
// Load initial state function
const loadStore = () => {
return Promise(resolve => {
fetch('/data/articles.js')
.then(response => response.json())
.then(resolve);
});
}
const storeCreator = applyMiddleware(asyncInitialState.middleware(loadStore));
const store = storeCreator(reducer);