Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 255
chrisp_68 Avatar asked May 25 '18 08:05

chrisp_68


People also ask

What is the best way to wait and test the page in Cypress?

Wait for API response wait() command. Test will only continue once that command is finished. Finding the right request to intercept is a great way to make sure that Cypress will wait until page loads with all the right data loaded.

How do I make Cypress wait for page load?

You should just do a cy. get() and . should() to make Cypress wait for something to appear on the page.

How do you wait for an element in Cypress?

If you are waiting for some resources to be loaded in your app, you can intercept a request and then create an alias for it. That alias will then be used with . wait() command. Test will only continue once that command is finished.

What is the default wait time in Cypress?

wait() goes through two separate "waiting" periods. The first period waits for a matching request to leave the browser. This duration is configured by the requestTimeout option - which has a default of 5000 ms.


Video Answer


3 Answers

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;
    }
}
like image 68
chrisp_68 Avatar answered Nov 10 '22 09:11

chrisp_68


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.

like image 42
Rui Marques Avatar answered Nov 10 '22 08:11

Rui Marques


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()
like image 42
Benoît Avatar answered Nov 10 '22 08:11

Benoît