testing-library / cypress-testing-library

🐅 Simple and complete custom Cypress commands and utilities that encourage good testing practices.
http://npm.im/@testing-library/cypress
MIT License
1.8k stars 152 forks source link

Test case fails when a query from Testing Library is used #215

Closed tyczynski closed 1 year ago

tyczynski commented 2 years ago

What you did:

export default function HomePage() {
  return (
    <div>
      <h1>Hello World!</h1>
      <h2>Next.js starter</h2>
    </div>
  );
}

Test

describe('Homepage', () => {
  it('should contain "Hello World!" title', () => {
    cy.visit('/')
      .findByText(/hello world!/i)
      .should('exist');
  });
});

What happened:

  1) Homepage
       should contain "Hello World!" title:
     TypeError: Timed out retrying after 4000ms: Expected container to be an Element, a Document or a DocumentFragment but got Window.
      at checkContainerType (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:1112:11)
      at queryAllByText (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:2528:3)
      at eval (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:2221:24)
      at eval (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:2196:24)
      at eval (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:2249:25)
      at baseCommandImpl (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:591:16)
      at commandImpl (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:594:40)
      at getValue (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:618:23)
      at resolveValue (http://localhost:3000/__cypress/tests?p=cypress/support/index.js:658:35)

Reproduction repository:

https://github.com/tyczynski/nextjs-boilerplate/tree/ctl-error

Problem description:

I wrote a simple test that should find the "Hello World!" heading. I used .findByText(/hello world!/i), but this query works when test is asynchronous:

describe('Homepage', () => {
  it('should contain "Hello World!" title', async () => {
    cy.visit('/')
      .findByText(/hello world!/i)
      .should('exist');
  });
});

Suggested solution: The test should not be asynchronous to work according to the documentation and examples. https://github.com/testing-library/cypress-testing-library/blob/97939da7d4707a71049884c0324c0eda56e26fc2/cypress/integration/find.spec.js

NicholasBoll commented 1 year ago

This is not a supported use-case. The queries in this plugin are like the cy.find command from Cypress. We chose find* instead of get* to both align with the async nature of other Testing Library libraries like React Testing Library and to align with the previous subject of the cy.find command. This allows the following to work:

cy.findByRole('dialog')
  .findByRole('button') // button within the dialog

For example, the following will fail with the same message you have:

describe('Homepage', () => {
  it('should contain "Hello World!" title', async () => {
    cy.visit('/')
      .find('anySelector')
      .should('exist');
  });
});

cy.visit yields the window object which is not a valid element. The error message comes from Cypress based on the previous subject requirements of our commands.

Your example must be rewritten as:

describe('Homepage', () => {
  it('should contain "Hello World!" title', async () => {
    cy.visit('/')

    cy.findByText(/hello world!/i)
      .should('exist');
  });
});

Think of Cypress commands as a sentence. Each command contains a subject. Within a sentence, the sentence can continue as long as the subject is narrowed. If the subject changes, it is a new sentence.

cy.findByRole('dialog')
  .findByRole('button') // only buttons within a dialog rather than all buttons on the page
  .click()