Capture all XHR Requests with Cypress.io - javascript

When I call the URL, an undefined number of requests are sent.
Now I try to find out if one of the requests contains a certain payload.
cy.server();
cy.visit(url);
cy.route({
method: 'POST',
url: '**/t/e/**',
}).as('xhrRequest');
I have found a similar approach on How to capture all API calls in cypress? so far.
the problem here is that a fixed number of API calls is assumed.
cy.wait(Array(60).fill('#xhrRequest'), { timeout: 30000 }).then((xhrs) => {
xhrs.forEach((res) => {
expect(res.status).not.to.be.null
})
})
How do I get it that all requests are intercepted and fail my test if there is not a single request containing the payload.
I already wrote something like this in puppeteer
let hasSpecialRequest = false;
page.on('request', request => {
if (isSpecialRequest(request)) {
hasSpecialRequest = true;
}
request.continue();
});
await page.setRequestInterception(true);
expect(hasSpecialRequest).to.equal(true);
The system checks whether each request is one of the special requests and sets the variable accordingly. Something like this I try to recreate with Cypress.

You might consider doing cy.exec and run a script in another language and return status from subprocess.

I may have misunderstood the problem but since I got here through my Google expedition maybe this might help someone that had my problem and possibly you.
At first I used the cy.wait(#alias) but could never retrieve all the responses (only one response was being shown and I couldn't figure out how to access ALL the responses). So I ended up immediately storing the responses into another array to access within the test.
let xhrRequests;
function getXhrRequests() {
xhrRequests = [];
cy.intercept('GET', '**', (res) => {
xhrRequests.push(res);
});
return xhrRequests;
}
describe('Some', function () {
it('Thing 1', () => {
let thing1 = getXhrRequests();
cy.visit('http://some.site');
thing1.forEach((res) => {
expect(res.status).not.to.be.null;
})
}
it('Thing 2', () => {
let thing2 = getXhrRequests();
cy.visit('http://some.site/2');
thing2.forEach((res) => {
expect(res.status).not.to.be.null;
})
}
});

Related

Cancel old Promise.resolve if same method called multiple times with given timeframe

I am using Promise and axios in react to call POST api and fetch list of records.
Issue is when multiple API calls triggered then any one which response last is getting is updating state.
Where i want to use only last called API response.
Exp : call API 3 times with different postbody, in case first call response take time than 2nd & 3rd then callback is using response of 1st call to setstate, instead i want to forget 1 and second call and consider last call response only.
Following is example
Common File
const apiService = axios.create({
baseURL: 'https://example.com/api/,
});
function post(postData) {
return Promise.resolve(apiService.post('https://example.com/api/getuserlist', postData, {
headers: {'Authorization': `Bearer sdfsdfsdf-cvdfs`}
}));
}
Service File
static getUsers(postData) {
return post(postData);
}
React Component
function getUsersList = (Filters )=>{
getUsers({ "Filters": Filters }).then((response) => {
this.setState({ users: response.data})
})
}
Problem is when getUsersList get called multiple time whichever is last response is getting set to users state, where last call should be users list.
It's not yet possible to actually cancel promises in JavaScript (though there is a proposal for it).
However, it is possible to implement the functionality you want in React. Consider something like this:
// state shape:
// {
// loading: boolean
// apiPromise: null | Promise<{ data: User[] }>
// users: User[]
// }
getUsersList = async (Filters) => {
// raw promise - don't await it yet
const apiPromise = getUsers({ Filters })
this.setState({ apiPromise, loading: true })
// _here_ we await it
const response = await apiPromise
// check if it's the most recent API request we made
if (this.state.apiPromise === apiPromise) {
this.setState({ users: response.data, loading: false })
} else {
console.log("stale val discarded:", users)
}
}
CodeSandbox demo - simplified example with mocked API and single val rather than list of users. Try clicking the button many times in quick succession and watch the console output.
Using CPromise wrapper the code might look like this See the live demo:
const CPromise = require('c-promise2');
const axios= require('axios');
// Let's wrap axios get method to the CPromise
function get(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
let chain= null;
function makeRequest(url){
chain && chain.cancel();
chain= get(url).then((response)=> {
console.log(`Response ${JSON.stringify(response.data)}`);
}, function (err) {
console.warn(`Error: ${err}`);
}
);
}
// some endpoint with a delay of 3 seconds for a response
const url= "https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s";
makeRequest(url);
// make the same request again, abort the previous
setTimeout(()=> makeRequest(url), 1000);
But since the package is in early beta stage, you can use the plain cancellation token, provided by axios See Axios documentation

ReactJS / Axios / Firebase - Passing response data from POST to use in a PUT

I am using Axios.post to create a new record. With this new record there is a unique key assigned to this record from Firebase that I need in my next PUT operation.
Currently I'm using nested .then and it seems to be working as it should. I'm just unsure how I can convert this to a Promise.all without knowing the unique key to pass into my second query. It seems it's susceptible to problems the way I have it structured. Is there a way I can use Promise.all to ensure both queries executed? Or maybe it's fine as is?
Fully functioning sandbox is here: https://codesandbox.io/s/react-sample-5nc1z?file=/index.js
And here is the part in question:
postColumn = (columnArr) => {
axios
.post(
"/workflow/boards/" + this.state.boardID + "/columns.json",
columnArr
)
.then((response) => {
// console.log(response);
const newColumnIds = [...this.state.columnIds, response.data.name];
const newColState = {
columns: {
...this.state.columns,
[response.data.name]: { columnvalue: columnArr.columnvalue },
},
};
this.setState(newColState);
axios
.put(
"/workflow/boards/" + this.state.boardID + "/columnIds.json",
newColumnIds
)
.then((response) => {
const newColIdState = {
columnIds: newColumnIds,
};
this.setState(newColIdState);
})
.catch((error) => {
console.log("Error in putColumnIds", error);
});
})
.catch((error) => {
console.log("Error in postColumn", error);
});
};
If your second request is dependent on some data from your first request, Promise.all will not work. The purpose of Promise.all is to run two Promises (in your case, network requests) simultaneously, so if you are unable to do that (because you rely on one completing first, you cannot use it). That being said, your code looks totally fine and as long as it works, there should be nothing wrong with your implementation.
See more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Get only HTML in single fetch request in service worker

I'm using Cloudflare service workers and I want on every request to:
request only the HTML (therefore count as only 1 request)
search the response for a string
Purge that page's cache if the message exists
I've solved points #2 and #3. Can't figure out if #1 is feasible or possible at all.
I need it as only one request because there is a limit per day on the number of free requests. Otherwise I have about 50-60 requests per page.
My current attempt for #1, which doesn't work right:
async function handleRequest(request) {
const init = {
headers: {
'content-type': 'text/html;charset=UTF-8',
},
};
const response = await fetch(request);
await fetch(request.url, init).then(function(response) {
response.text().then(function(text) {
console.log(text);
})
}).catch(function(err) {
// There was an error
console.warn('Something went wrong.', err);
});
return response;
}
addEventListener('fetch', event => {
return event.respondWith(handleRequest(event.request))
});
You can't request "only the html", the worker will act on any request that matches the route that it is deployed at. If you only care about the html, you will need to set up your worker path to filter to only the endpoints that you want to run the worker on.
Alternatively you can use the worker on every request and only do your logic if the response Content-Type is one that you care about. This would be something along these lines:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
})
async function handleRequest(request) {
let response = await fetch(request);
let type = response.headers.get("Content-Type") || "";
if (type.startsWith("text/")) {
//this is where your custom logic goes
}
return response;
}

An object with two very similar arrays, one displays the other does not

I have an object with two embedded arrays of objects that seem to me to be almost identical, as seen here in my database:
But when I try to access one of the arrays in frontend javascript, it's apparently empty. The other is not, as seen here when I log it to the browser console:
The objects in the arrays are almost exactly the same. I am concerned that the problem is when I push a new object on to the 'stakeholders' array that the asynchronous function is not completing before the page loads again, but I am using async/await in that function before returning the response
addStakeholder = async (req, res, next) => {
...
project.stakeholders.push(stakeholder)
await project.save()
res.status(200).json({
status: 'success',
project: project
Could anyone please tell me what I am likely doing wrong here?
EDIT: Sorry I'll try and add some more detail, so on the form submission there is this.....
createStakeholderForm.addEventListener('submit', async (e) => {
// getting properties etc, this all works
await createStakeholder({ stakeholders, project })
window.setTimeout(() => {
location.reload()
}, 1000)
})
which passes it to this axios function....
createStakeholder = async (data) => {
try {
const url = `http://127.0.0.1:3000/stakeholder`
const res = await axios({
method: 'POST',
url: url,
data: data
})
if (res.data.status === 'success') {
showAlert('success', `Stakeholder created`)
}
} catch (err) {
showAlert('error', err.response.data.message)
}
}
and that routes posts to this function.....
addStakeholder = async (req, res, next) => {
const query = { _id: req.body.project }
const project = await Project.findById(query)
const stakeholder = req.body.stakeholders
project.stakeholders.push(stakeholder)
await project.save()
res.status(200).json({
status: 'success',
data: {
data: project
}
})
})
While it's not obvious what is wrong from your code. The debugging path is, fortunately.
Start tracing the wire. It sound like things are saving in the database correctly, but are not reaching the frontend. I would console.log in your code on the backend at the call site that queries the database. Confirm its what you expect. Assuming that worked, add another console.log downstream, keep doing that until stakeholder data vanishes. This exercise will show you where in the code stakeholders are getting dropped.

Cypress - get value from json response body

I'm using Cypress to do some API testing, but I am struggling to access values in the JSON response body; however I can perform assertions against the body which suggests it's receiving it correctly.
Below I am trying to assign the JSON body (response.body) and then get the value of 'id' out of it:
describe('Creating a board', () => {
it('should create a board', () => {
cy.request({
method : 'POST',
url:`${requestUrl}/boards/`,
qs: {
name : "test-board",
token : token,
key : key
}
}).then((response) => {
expect(response).property('status').to.equal(200)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const body = (response.body)
boardId = body['id']
})
})
I've done numerous searches and can't find a concrete way to do it. Any help would be appreciated...
I managed to solve this by using a Promise;
Doing some further reading, I found out the then function I am executing is synchronous (I'm new to JS, pls don't hurt me).
I refactored the then function to the following:
.then((response) => {
return new Promise(resolve => {
expect(response).property('status').to.equal(200)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const respBody = response.body;
boardId = respBody['id']
resolve(boardId)
})
It's probably not entirely correct or best practice, but it will do for my demo
Although not needed anymore as you found a workaround, I've looked into my cypress code. I was able to access properties of response body followingly:
cy.request({
...
}.its('body').then((body) => {
const whatever = body.whatever;
})
I believe it basically works the same as your workaround - waiting to resolve body in a promise.
I was able to do it in the following way:
cy.request(
'POST',
url,
payload()).then((response) => {
expect(response.body).to.have.property('ReturnCode', 'Success')
expect(response.body).to.have.property('ReturnText', 'Success')
expect(response.body).to.have.property('PaymentInstructionId')
paymentID = response.body.PaymentInstructionId
})
paymentID is the variable that is filled with the value that i want from the repply.

Categories

Resources