Why is this an issue?

End-to-end and integration tests need to synchronize with asynchronous work such as UI updates, network requests, or background jobs. Pausing for a fixed amount of time using framework APIs like Cypress' cy.wait(<number>) or page.waitForTimeout(<number>) in Playwright and legacy Puppeteer releases before v22.0.0 is a brittle way to do this:

Each modern end-to-end framework ships dedicated synchronization primitives that wait for an observable condition instead of a duration. Replace the fixed wait with one of these primitives so the test moves on as soon as the application is ready, and fails quickly when it is not.

Code examples

Noncompliant code example

// Cypress
it('saves a user', () => {
  cy.get('button.save').click();
  cy.wait(1000); // Noncompliant: fixed wait
  cy.contains('Saved').should('be.visible');
});

Compliant solution

// Cypress
it('saves a user', () => {
  cy.intercept('POST', '/users').as('saveUser');
  cy.get('button.save').click();
  cy.wait('@saveUser');
  cy.contains('Saved').should('be.visible');
});

Noncompliant code example

// Playwright
test('saves a user', async ({ page }) => {
  await page.getByRole('button', { name: 'Save' }).click();
  await page.waitForTimeout(1000); // Noncompliant: fixed wait
  await expect(page.getByText('Saved')).toBeVisible();
});

Compliant solution

// Playwright
test('saves a user', async ({ page }) => {
  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByText('Saved')).toBeVisible();
});

Noncompliant code example

// Puppeteer (<22.0.0)
test('saves a user', async () => {
  await page.click('button.save');
  await page.waitForTimeout(1000); // Noncompliant: fixed wait
  expect(await page.$('.toast')).not.toBeNull();
});

Compliant solution

// Puppeteer
test('saves a user', async () => {
  await page.click('button.save');
  await page.waitForSelector('.toast');
  expect(await page.$eval('.toast', node => node.textContent)).toContain('Saved');
});

Resources

Documentation

Related rules