Reputation: 1781
When using cypress.io to test an angular web page, whats the best / most reliable way to detect when the page is fully loaded and idle. Not just the onload event. Needs to include all XHR requests, angular digest cycles complete and all rendering complete including all animations complete.
The reason is that at this point I want to test that the page does NOT contain an element and cant test that until all the above is fully complete.
Upvotes: 20
Views: 8444
Reputation: 1781
For the record what we did for this (for AngularJS) was to add a new waitforpageidle
command that can be used like this:
cy.waitforpageidle();
Typescript is as follows:
Cypress.Commands.add(
"waitforpageidle",
() => {
console.warn("Waiting for page idle state");
const pageIdleDetector = new PageIdleDetector();
pageIdleDetector.WaitForPageToBeIdle();
}
);
Where the PageIdleDetector is as follows:
export class PageIdleDetector
{
defaultOptions: Object = { timeout: 60000 };
public WaitForPageToBeIdle(): void
{
this.WaitForPageToLoad();
this.WaitForAngularRequestsToComplete();
this.WaitForAngularDigestCycleToComplete();
this.WaitForAnimationsToStop();
}
public WaitForPageToLoad(options: Object = this.defaultOptions): void
{
cy.document(options).should((myDocument: any) =>
{
expect(myDocument.readyState, "WaitForPageToLoad").to.be.oneOf(["interactive", "complete"]);
});
}
public WaitForAngularRequestsToComplete(options: Object = this.defaultOptions): void
{
cy.window(options).should((myWindow: any) =>
{
if (!!myWindow.angular)
{
expect(this.NumberOfPendingAngularRequests(myWindow), "WaitForAngularRequestsToComplete").to.have.length(0);
}
});
}
public WaitForAngularDigestCycleToComplete(options: Object = this.defaultOptions): void
{
cy.window(options).should((myWindow: any) =>
{
if (!!myWindow.angular)
{
expect(this.AngularRootScopePhase(myWindow), "WaitForAngularDigestCycleToComplete").to.be.null;
}
});
}
public WaitForAnimationsToStop(options: Object = this.defaultOptions): void
{
cy.get(":animated", options).should("not.exist");
}
private getInjector(myWindow: any)
{
return myWindow.angular.element(myWindow.document.body).injector();
}
private NumberOfPendingAngularRequests(myWindow: any)
{
return this.getInjector(myWindow).get('$http').pendingRequests;
}
private AngularRootScopePhase(myWindow: any)
{
return this.getInjector(myWindow).get("$rootScope").$$phase;
}
}
Upvotes: 10
Reputation: 8914
You can make Cypress wait for any request to complete before it proceeds. So if you want to wait for all XHR of a certain page, you can do the following for each of them. How long it waits is defined by the responseTimeout configuration.
cy.server();
cy.route('**/api/getData').as('getData');
cy.visit('/home');
cy.wait('@getData');
Or to wait for several routes:
cy.server();
cy.route('**/api/getData').as('getDataX');
cy.route('**/api/getData').as('getDataY');
cy.visit('/home');
cy.wait(['@getDataX', '@getDataY']);
Cypress best practices: Unnecessary-Waiting.
Upvotes: 6
Reputation: 1100
For Angular (2+)
The previous answer relates to AngularJS. For Angular 2+, you can use this other solution.
Disclaimer : In most cases Cypress waiting mechanism is sufficient. But if you really need the application to stabilise (ie: that all requests and Async Tasks like debounces have fulfilled) you can use this command.
/**
* Used by Cypress.waitForAngularIdle() (in commands.ts) to wait for all Pending request to finish
*/
function ngIsStable() {
const test = (window as any).getAngularTestability(window.document.querySelector('body div')) as Testability // you need to select an element managed by Angular. Usually any div in your html body
const stable = test.isStable()
//if (!stable) { // this console.log block is just for information, you can remove it
console.log('Angular is ' + (stable ? '' : 'NOT ') + 'stable:', {
pendingRequestCount: test.getPendingRequestCount(),
_pendingCount: (test as any)._pendingCount,
pendingTasks: (test as any).getPendingTasks(),
})
// }
return stable
}
window['ngIsStable'] = ngIsStable;
function waitForAngularIdle() {
cy.window().invoke('ngIsStable').should('be.true')
}
Cypress.Commands.add('waitForAngularIdle', waitForAngularIdle);
declare namespace Cypress {
interface Chainable<Subject = any> {
waitForAngularIdle()
}
}
cy.waitForAngularIdle()
Upvotes: 4