chrisp_68
chrisp_68

Reputation: 1781

How do you reliably wait for page idle in cypress.io test

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

Answers (3)

chrisp_68
chrisp_68

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

Rui Marques
Rui Marques

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.

Cypress docs on wait Alias.

Upvotes: 6

Benoît
Benoît

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.

  • In your src/main.ts, add the following shortcut method to the window object :
    /**
     * 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;
  • In your Cypress commands.ts file :
    function waitForAngularIdle() { 
        cy.window().invoke('ngIsStable').should('be.true')
    }
    Cypress.Commands.add('waitForAngularIdle', waitForAngularIdle);
  • In your Cypress index file index.d.ts (for typescript compilation)
    declare namespace Cypress {
      interface Chainable<Subject = any> {
        waitForAngularIdle()
      }
    }
  • In your Cypress specs when you need Cypress to wait for your angular application to stabilize :
    cy.waitForAngularIdle()

Upvotes: 4

Related Questions