Playwright delay, hang, never resolve response with route (add latency) - async-await

I need to test service when response is delayed or is never resolved. Done brute force implementation:
await page.route(url, () => page.waitForTimeout(1000));
As docs says: page.waitForTimeout() should only be used for debugging link
But how to i.e. make page request for url, and wait till page receives timeout.
Another scenario:
Page requesting data i.e. api/items
I need to check page state before api/items request is resolved
Dummy test example:
import { test, expect } from '#playwright/test';
test('banner shows in progress message', async ({ page }) => {
await page.route('**/async-resource.json', () => {
// this request should not be resolved before tests ends
page.waitForTimeout(1000);
});
await page.goto('https://async.page/');
const banner = page.getByRole('banner');
await expect(banner).toHaveText('Your data are being prepared!');
});

Related

Cypress wait() for intercept() is not triggered for NEW URL only

Initiating a visit(), it triggers three fetches before ending up on the final NEW URL.
Each fetches triggers their corresponding wait() - except the NEW URL.
describe('login', () => {
it('login', () => {
cy.intercept('/_next/static/development/_devMiddlewareManifest.json').as('fetch1')
cy.intercept('http://localhost:3333/auth/works/token-check').as('fetch2')
cy.intercept('/_next/static/development/_devPagesManifest.json').as('fetch3')
cy.intercept('http://localhost:3002/login').as('login')
cy.visit('/')
// cy.wait(['#fetch1', '#fetch2', '#fetch3', '#login'])
cy.wait('#fetch1')
cy.wait('#fetch2')
cy.wait('#fetch3')
// cy.wait('#login')
cy.wait('#login', {
requestTimeout: 10000
})
cy.url().should('include', '/login')
})
})
The (new page) log entry is not actually a network request, it just informs you that the page has navigated to a new URL.
Since your app is a SPA, this is most likely caused by the app router and not by any traffic coming over the network, so you can't use cy.intercept() to catch it.
You last command cy.url().should('include', '/login') should be sufficient to wait for the new page to occur.

Cypress test - Do not intercept api request

I need to test some pages on a project and this project do some APIs call to external services.
I need to make sure that these calls are made and check if my page change accordingly to the response.
This is my test:
describe('A logged in user', () =>{
it('can see his subscriptions', () => {
...... some checks .......
cy.intercept('https://example.com/api/v2/user-panel/get-subscription').as('userSubscription');
cy.wait('#userSubscription', { timeout: 35000 }).then(() => {
cy.contains('some text');
});
});
});
When I run the code seems that it can't se the API call but the page content change correctly.
This is the cypress response:
Timed out retrying after 35000ms: cy.wait() timed out waiting 35000ms
for the 1st request to the route: userSubscription. No request ever
occurred.
I tried to increase the timeout, event if the page loads in 1 second, but the result is the same.
There is something am I missing?
Doing the cy.wait() right after the cy.intercept() is not going to work.
Whatever triggers the API calls (a cy.visit() or a .click()) must occur after the intercept has been set up, and it therefore is ready to catch the API call.
From the Network Requests docs
cy.intercept('/activities/*', { fixture: 'activities' }).as('getActivities')
cy.intercept('/messages/*', { fixture: 'messages' }).as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')

cy.wait(#someXhr) taimeouts

I'm having a problem with stubbing a simple request to an API using the cypress' cy.server() and cy.route().
Here's the failing test:
it.only("should show an error message for server errors", () => {
const name = "It doesnt matter";
const email = "takenemail#yopmail.com";
const pass = "123123";
// run the server and define the stubbed route
cy.server();
cy.route(
"POST",
`${serverBaseUrl}/auth/register`,
"fixture:register-fail.json"
).as("postRegister");
// fill in the registration form and hit submit
cy.visit("/auth/register");
cy.get(selectors.registerForm.name).type(name);
cy.get(selectors.registerForm.email).type(email);
cy.get(selectors.registerForm.password).type(pass);
cy.get(selectors.registerForm.registerButton).click();
// intercept the request and mock it
cy.wait("#postRegister"); // this fails.
cy.get(selectors.registerForm.genericErrors).contains(
"This email has already been taken"
);
});
and the error:
cy.wait() timed out waiting 5000ms for the 1st request to the route: postRegister. No request ever occurred.
Note: even though it says that No request ever occurred. I can still see the request being send and a response received in the console's Network tab (which means the stub has been bypassed and a regular request's been made).
Any ideas what's happening?
Thanks in advance.
Ok, seems like i have found the problem.
It turns out that using the fetch API is not supported by cypress.
The workaround - using whatwg-fetch, which is basically a polyfill for the fetch api, working with XHR behind the scenes.
Install the whatwg-fetch package: npm install whatwg-fetch --save
Import it in your project: import "whatwg-fetch";
Last, but very important - remove the fetch object from the window before every page load in the cypress environment like this:
// you can define this in the commands file for example...
Cypress.on("window:before:load", (win) => delete win.fetch);
or an alternative, per-visit approach:
it("some test", () => {
cy.visit("/url", {
onBeforeLoad: win => delete win.fetch // <---
});
// ...the rest of the test
});
Doing this will kick-in the polyfill and the stubbing should be working properly after this intervention.

How to wait for a successful response in Cypress tests

Background
I use 3 back-end servers to provide fault tolerance for one of my online SaaS application. All important API calls, such as getting user data, contact all 3 servers and use value of first successfully resolved response, if any.
export function getSuccessValueOrThrow$<T>(
observables$: Observable<T>[],
tryUntilMillies = 30000,
): Observable<T> {
return race(
...observables$.map(observable$ => {
return observable$.pipe(
timeout(tryUntilMillies),
catchError(err => {
return of(err).pipe(delay(5000), mergeMap(_err => throwError(_err)));
}),
);
})
);
}
getSuccessValueOrThrow$ get called as following:
const shuffledApiDomainList = ['server1-domain', 'server2-domain', 'server3-domain';
const sessionInfo = await RequestUtils.getSuccessValueOrThrow(
...(shuffledApiDomainList.map(shuffledDomain => this.http.get<SessionDetails>(`${shuffledDomain}/file/converter/comm/session/info`))),
).toPromise();
Note: if one request resolve faster than others, usually the case, race rxjs function will cancel the other two requests. On Chrome dev network tab it will look like bellow where first request sent out was cancelled due to being too slow.
Question:
I use /file/converter/comm/session/info (lets call it Endpoint 1) to get some data related to a user. This request dispatched to all 3 back-end servers. If one resolve, then remaining 2 request will be cancelled, i.e. they will return null.
On my Cypress E2E test I have
cy.route('GET', '/file/converter/comm/session/info').as('getSessionInfo');
cy.visit('https://www.ps2pdf.com/compress-mp4');
cy.wait('#getSessionInfo').its('status').should('eq', 200)
This sometimes fails if the since getSessionInfo alias was hooked on to a request that ultimately get cancelled by getSuccessValueOrThrow$ because it wasn't the request that succeeded.Bellow image shows how 1 out of 3 request aliased with getSessionInfo succeeded but the test failed since the first request failed.
In Cypress, how do I wait for a successful i.e. status = 200 request?
Approach 1
Use .should() callback and repeat the cy.wait call if status was not 200:
function waitFor200(routeAlias, retries = 2) {
cy.wait(routeAlias).then(xhr => {
if (xhr.status === 200) return // OK
else if (retries > 0) waitFor200(routeAlias, retries - 1); // wait for the next response
else throw "All requests returned non-200 response";
})
}
// Usage example.
// Note that no assertions are chained here,
// the check has been performed inside this function already.
waitFor200('#getSessionInfo');
// Proceed with your test
cy.get('button').click(); // ...
Approach 2
Revise what it is that you want to test in the first place.
Chances are - there is something on the page that tells the user about a successful operation. E.g. show/hide a spinner or a progress bar, or just that the page content is updated to show new data fetched from the backend.
So in this approach you would remove cy.wait() altogether, and focus on what the user sees on the page - do some assertions on the actual page content.
cy.wait() yields an object containing the HTTP request and response properties of the XHR. The error you're getting is because you're looking for property status in the XHR object, but it is a property of the Response Object. You first have to get to the Response Object:
cy.wait('#getSessionInfo').should(xhr => {
expect(xhr.response).to.have.property('status', 200);
});
Edit: Since our backend uses graphql, all calls use the single /graphql endpoint. So I had to come up with a solution to differentiate each call.
I did that by using the onResponse() method of cy.route() and accumulating the data in Cypress environment object:
cy.route({
method: 'GET',
url: '/file/converter/comm/session/info',
onResponse(xhr) {
if (xhr.status === 200) {
Cypress.env('sessionInfo200') = xhr;
}
}
})
You can then use it like this:
cy.wrap(Cypress.env()).should('have.property', 'sessionInfo200');
I wait like this:
const isOk = cy.wait("#getSessionInfo").then((xhr) => {
return (xhr.status === 200);
});

Mocha Chai Tests Pass buth shouldn't

Here's the current test:
describe('/POST Register Page', function() {
it('it should register new user', function(/*done*/) {
chai.request(server)
.post('/auth/register')
.send(new_user_data)
.end(function(res) {
expect(res).to.have.status(2017);
// done();
})
})
})
The last I checked, there's no http code as 2017, however, it still passes:
Registration
Get register page
GET /auth/register 200 6.989 ms - 27
✓ it should get register page
/POST Register Page
✓ it should register new user
2 passing (147ms)
I want to simply post something, then get a response back, and play with the response.
If I include the done(), I get the timeout error:
1) Registration /POST Register Page it should register new user:
Error: Timeout of 3000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I can't get anything to work, as for whether wrong or right, the tests pass.
Although this get request is passing as expected:
describe('Get register page', function() {
it('it should get register page', function(done) {
chai.request(server)
.get('/auth/register')
.end(function(err, res) {
expect(err).to.be.null;
expect(res).to.have.status(200);
done();
})
})
})
I'm new at this mocha-cum-chai-chai-http thing, and the experience so far is weird.
Thanks.
Your POST request is probably taking longer than 3 seconds to complete, therefore mocha throws the timeout error.
You can try setting the timeout to a larger value like:
describe('/POST Register Page', function() {
// timeout in milliseconds
this.timeout(15000);
// test case
it('it should register new user', function(done) {
chai.request(server)
.post('/auth/register')
.send(new_user_data)
.end(function(res) {
expect(res).to.have.status(200);
done();
})
})
})
With some trial, you can figure out an optimum value of timeout to set in your tests.
When you don't use the done() callback, mocha simply skips the assertions without waiting for the actual response to arrive. Since the assertions in .end() block never get executed, mocha passes the test as it faces no assertions. I had faced something similar when I first started out with TDD, which I learned about the hard way.
Reference:
Because the end function is passed a callback, assertions are run
asynchronously. Therefore, a mechanism must be used to notify the
testing framework that the callback has completed. Otherwise, the test
will pass before the assertions are checked.

Resources