0

So now in Playwright, we have the list of expect assertions, which can retry up till the timeout limit is reached. Now in that list, I couldn't find anything which can just assert a text.

This is my test:

page.on('dialog', async (dialog) => {
  expect(dialog.message()).toContain('I am a JS Alert')
  await dialog.accept()
})
await page.locator('text=Click for JS Alert').click()

I am looking to replace this expect(dialog.message()).toContain('I am a JS Alert') with something that can retry until a timeout is reached.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Alapan Das
  • 17,144
  • 3
  • 29
  • 52

3 Answers3

2

You have a timeout for a single assertion in case you are expectig locator:

  await expect(page.locator(selector)).toHaveText('Sign in', { timeout: 10000 });

As well you can set expect timeout on global level.

// playwright.config.ts
import { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
  expect: {
    timeout: 10 * 1000,
  },
};
export default config;

In case of dialog and method that you are using, there is no timeout since dialog.message() returns pure string, because it listen for dialog event.

By my knowlage, best you can do here is to go with try catch, or while loop until dialog message contins desired message (with watchDog).

Gaj Julije
  • 1,538
  • 15
  • 32
2

The latest version of playwright 1.21 allows you to do this:

New method expect.poll to wait for an arbitrary condition:

// Poll the method until it returns an expected result.
await expect.poll(async () => {
  const response = await page.request.get('https://api.example.com');
  return response.status();
}).toBe(200);

expect.poll supports most synchronous matchers, like .toBe(), .toContain(), etc.

OrdinaryKing
  • 411
  • 1
  • 7
  • 16
0

There are a few ways to do this, depending on your needs.

Assuming you only have one dialog popping up at a time, you can use a promisified dialog handler and retries:

import {expect, test} from "@playwright/test"; // ^1.30.0

// Warning: doesn't have a timeout (relies on test timeout)
const acceptNextDialog = page =>
  new Promise(resolve =>
    page.once("dialog", async dialog => {
      await dialog.accept();
      resolve(await dialog.message());
    })
  );

test.beforeEach(async ({page}) => {
  await page.setContent(`
<button>Click to generate random number from 0 to 9 inclusive</button>
<script>
document.querySelector("button").addEventListener("click", e => {
  alert(~~(Math.random() * 10));
});
</script>
  `);
});

test("eventually shows '1' in the dialog", async ({page}) => {
  await expect(async () => {
    const message = acceptNextDialog(page);
    await page.getByRole("button").click();
    expect(await message).toBe("1");
  }).toPass({timeout: 20_000});
});

The example page under test has a button that triggers a dialog that displays a random number from 0-9 in it. We assert that eventually the dialog shows the number "1".

Change the test to check for the text "asdf" and you'll see it fails as expected.

Polling is also possible, and probably a bit more precise than retrying:

await expect.poll(async () => {
  const message = acceptNextDialog(page);
  await page.getByRole("button").click();
  return message;
}).toBe("1");
ggorlen
  • 44,755
  • 7
  • 76
  • 106