In Cypress, I'm setting up my login loop, then want to extract certain headers for use in subsequent requests. Here's how I log in:
function login() {
return cy.visit('/login')
.get("#username").type(Cypress.env("USERNAME"))
.get("#password").type(Cypress.env("PASSWORD"))
.get("form").submit()
.then((result) => {
// TODO - extract the last response's headers and save for later
});
}
But in that context, result is a DOM selection of the form, and I'm not sure how to get the most recent result.
Are there some interceptors or other way to submit a form that would give me more access to the request or response?
I can't just replace this with a cy.request() because it follows a few redirects to take me to a foreign SSO login page, hence why I need to only inspect the most recent result.
Thanks in advance.
You need to wait for the response from the url that contains your desired info. Look at this example.
cy.server().route('http://' + host + '/api/managerecord/userdetails/*').as('getUserDetails')
cy.get(submitButton).click()
cy.wait('#getUserDetails').then((response) => {
expect(response.response.body.waistSize).to.eq(100)
})
In your case, put a route on that last respone and wait for that one. If you don't know that url have a look in the Chrome inspector.
Related
I have a component that I want to cover with some e2e tests. This component takes the URL provided by the user in the input, calls the API after the button click and then returns the shortened version of that URL. After that, shortened url is added to the list below the input on the UI and makes some localStorage assertion. I want Cypress to wait for the API response and only then check the UI if the list item was added. I made this working but I hardcoded the wait time in the wait() method. How Can I achieve that programatically ?
describe("Shortener component", () => {
it("Should add the list item and data to localStorage", () => {
cy.visit("http://127.0.0.1:5500"); //Live server extension address
cy.get("#url-input").type("https://facebook.com");
cy.get("#form-submit-button").click();
// wait for the api response and make sure that the value has been added to the localStorage
cy.wait(40000); //todo - wait for the api response instead of hardcoding the wait time
const localStorageData = localStorage.getItem("linksData");
if (JSON.parse(localStorageData)) {
expect(JSON.parse(localStorageData)[0].inputValue).to.eq(
"https://facebook.com"
);
}
// check if the new list item with the corrct value has been addded
cy.get(".shortener-component__list-item")
.contains("https://facebook.com")
.should("be.visible");
//validation mesasge should not be visible
cy.get("#validationMesage")
.contains("Please add a valid link")
.should("not.be.visible");
});
});
I tried with intercept() however I failed. Not sure how to make it working. I also saw some similar SE topics on that but it did not help me.
Any ideas / examples apreciated :)
Thx !
From the order of events you've given
short URL returned
added to localStorage
added to list
just change the order of feature testing
test list - it is last event, but has retriable commands (you can increase the timeout)
now test localStorage, if UI has the short URL so will localStorage
cy.contains('.shortener-component__list-item', 'https://facebook.com', { timeout: 40000 })
.then(() => {
// nested inside .then() so that the above passes first
const localStorageData = localStorage.getItem("linksData");
const linksData = JSON.parse(localStorageData);
expect(linksData).not.to.eq(undefined);
expect(linksData[0].inputValue).to.eq("https://facebook.com");
})
Alternatively, to make use of retry and timeout on the localStorage check,
cy.wrap(localStorage.getItem("linksData"))
.should('not.eq', undefined, { timeout: 40000 }) // will retry the above until true
.then(data => JSON.parse(data))
.should(parsedData => {
expect(parsedData.inputValue).to.eq("https://facebook.com");
});
I guess you should also start the test with
cy.clearLocalStorage("linksData")
There're examples in the documentation, it only takes some reading and experimentation.
In general, you need three commands: cy.intercept(), .as(), and cy.wait():
cy.intercept(your_url).as('getShortenedUrl');
cy.wait('#getShortenedUrl');
you can also use .then() to access the interception object, e.g. a response:
cy.wait('#getShortenedUrl').then(interception => { });
or you can check something in the response using .its():
cy.wait('#getShortenedUrl').its('response.statusCode').should('eq', 200);
The point is that after cy.wait('#getShortenedUrl'), the response has been received.
Our application has a feature where you can press a button, wait for a while, and then get some information after it's been delivered. It'll send about 10 responses with identical url's and only the last one should have the information we care about in it. My script right now is:
beforeEach(function() {
cy.server()
cy.route('get', /waiting_for_response/).as('response')
})
it('checks for good data', function() {
cy.get('.get-data').click()
cy.wait('#response', {timeout:10000}).then((xhr) => {
expect(xhr.responseHeaders.data).to.not.be.a('null')
})
})
This always fails since cy.wait is resolved on the first matching url and only the last response's headers have a data value of not null. Is there a way to wait for all of the responses to have come back before checking if any of them were not null? Or perhaps specify the route has to have a certain response header in order to match? Any help or ideas would be super appreciated, thanks in advance!
You can break the problem into smaller conditions
cy.wait(10000)
check length of cy.get('<>').should('have.length', 10)
now you can check condition expect(xhr.responseHeaders.data).to.not.be.a('null')
You can refer to documentation
https://docs.cypress.io/api/commands/route.html#With-Stubbing
Section: Making multiple requests to the same route
I created a small sample application using VueJs and created a C# REST API to store and retrieve data in a SQL Server back end.
For testing, I created a simple web page with a form to create a "note". The note is stored by the following function, 'saveData()':
saveData()
{
let promiseStack = [];
var jsondata = JSON.stringify(this.note);
promiseStack.push(this.$http.post('REST_API/note', jsondata));
Promise.all(promiseStack).then(data =>
{
this.$http.get('REST_API/note');
this.$router.push({ name: 'viewnotes', params: { id: data[0].body.id }})
}, error =>
{
console.log(error);
});
}
I tried to use a promise to wait until the 'store' operation in the backend is complete, and issue a GET request to retrieve all notes once the promise is fulfilled.
However, the get request inside the promise doesn't return any data. If I issue the get request manually later one, I retrieve the data that was stored previously.
So I had look into the C# REST API. There are currently two functions: createNote(...), getAllNotes(...). I used a StreamWriter to log to the filesystem when these functions are called, using milisecond precision. What I see is that 'createNote' is called after 'getAllNotes'. So I suspect that the API is working correctly, but something with the way I'm using promises seems to be awfully wrong.
Maybe somebody has a hint?
UPDATE
I know that the GET request doesn't return any data by using the developer toolbar in Chromium. The response is empty
The developer toolbar in the network tab shows that the requests are submitted in the correct order, so the "POST" request is issued first
It seems I found the problem. I had a 'href' tag in my 'Save' link, which triggered an early routing. The intended 'POST' and 'GET' were fired correctly, but there was another 'GET' inbetween somewhere because of the 'href' tag in the link, even though it was empty.
I removed the tag, now it works as intended.
I'm wanting to invalidate a previously cached GET from my service worker when a POST, PUT, or DELETE to the same url or any url of a resource or collection 'under' it happens, for example:
let's say I cache /subscriptions and later on I do a POST to /subscriptions to add a new subscription, or say I PUT to /subscriptions/243 to update an existing subscription.
This means that my cached subscriptions collection is now stale data and I want to delete it from my cache so the next request will go to the server.
I've thought of two options, where I'm not sure either are possible:
Can I use a Regexp in the caches.match() call?
This way I could just match the parent collection piece of the requested url with keys found in the cache.
Can I get the keys of each cached data response?
If so, I could just loop through each response and see if the key meets my criteria for deleting it.
Any ideas?
Thanks!
You can't use a RegExp or anything other than string matching (optionally ignoring query parameters) when doing a lookup via caches.match().
I'd recommend the second approach, in which you open a named cache, get its keys, and then filter for the ones you care about. It's not that much code, and looks fairly nice with await/async:
async function deleteCacheEntriesMatching(cacheName, regexp) {
const cache = await caches.open(cacheName);
const cachedRequests = await cache.keys();
// request.url is a full URL, not just a path, so use an appropriate RegExp!
const requestsToDelete = cachedRequests.filter(request => request.url.match(regexp));
return Promise.all(requestsToDelete.map(request => cache.delete(request)));
}
// Call it like:
await deleteCacheEntriesMatching('my-cache', new RegExp('/subscriptions'));
Is there a way to redirect the user to a certain URL on the basis of what comes out of an XMLHttpRequest()? Here's what I am trying to achieve:
User hits submit, form gets submitted, XMLHttpRequest() fired
Response received from the server, stored in var hr
If hr = abc, show contents of hr
If hr = xyz, redirect user to http://www.something.com
What I am looking for is if there's any predesigned method in either JS or JQ to handle such redirects. I understand redirects can be specified in the <meta> tags in the <header> section of the page but if I did that, how will I be able to add conditions to it? I would have posted a copy of the script I have attempted but can't because right now, I have no idea where to even begin!
In case someone is curious about the scenario, this is a Web-based dictionary/conjugation service. So, on the verb conjugation page, if the user enters a valid verb, the response (i.e. the conjugation tables) is displayed. However, if the user enters a word that's valid but not a verb so it can't be conjugated, I want the user to be automatically redirected to the dictionary page where the entered word's dictionary entry will be displayed. Not sure if I have explained it well enough but please feel free to ask should you have any questions.
Try testing with switch(request.responseText) and call window.location.assign("http://your-url.com"); in the preferred case "xyz"! Alternatively window.open("http://anotherxxxwebsite.com") opens the link in a new browser window.
There's no "predesigned" method, but you can write that logic yourself. Depending on your current API you could either check if the returned value is an URI (or some other designated value instead) an redirect accordingly. Assuming a deferred object returned from jQuery.ajax:
defer.done(function(data, textStatus, jqXHR) {
// assuming a string, but this could really be anthing, e.g.
// an object containing an appropriate attribute, etc.
if (data.indexOf('http') === 0) {
window.open(data);
} else {
// render your stuff
}
});