Skip to content

Instantly share code, notes, and snippets.

@cerebrl
Created October 14, 2022 22:21
Show Gist options
  • Save cerebrl/5ee22ae90be9f5c379658d1420ba0946 to your computer and use it in GitHub Desktop.
Save cerebrl/5ee22ae90be9f5c379658d1420ba0946 to your computer and use it in GitHub Desktop.
Explanation of async test and the use of an unused locator.

Playwright Test Issue Explained

Okay, I figured it out. Luckily, it’s quite simple (once it clicks). I’ll break down the test and why it fails line by line.

Here’s the test as a whole, and it fails every time due to line 22:

await page.goto('widget/modal?journey=LoginWithConfirmationAndChoice');

const loginButton = page.locator('button', { hasText: 'Open Login Modal' });
await loginButton.click();

await page.fill('text="User Name"', 'demouser');
await page.locator('button', { hasText: 'Next' }).click();

await page.fill('text=Password', 'j56eKtae*1');
await page.locator('button', { hasText: 'Next' }).click();

// Confirmation
expect(await page.locator('text="Are you human?"')).toContainText('Are you human?');
await page.selectOption('select', '0');
await page.locator('button', { hasText: 'Next' }).click();

// Choice
expect(await page.locator('text="Are you sure?"')).toContainText('Are you sure?');
await page.selectOption('select', '0'); <-- #### LINE 22 ###############
await page.locator('button', { hasText: 'Next' }).click();

const fullName = page.locator('#fullName');
const email = page.locator('#email');

expect(await fullName.innerText()).toBe('Full name: Demo User');
expect(await email.innerText()).toBe('Email: demo@user.com');

First, we have to understand how Playwright’s API works. To put it simply, locators are inert until you act on them, so they don’t return promises. The locator itself isn’t all that valuable until you do something with it, like click, fill, select, retrieve it’s containing HTML or text, etc. So, this code is incomplete:

console.log('start');
expect(await page.locator('text="Are you human?"')).toContainText('Are you human?');
console.log('end'

Although the above code looks like it would run in this order:

  1. Log “start”
  2. Wait for an element with the text “Are you human?”
  3. Assert that the text matches what’s expected
  4. Log “end”

… but, it doesn’t actually work like that. This is what happens:

  1. Log “start”
  2. Log “end”
  3. Element appears on page with text “Are you human?”
  4. Assert that text matches

There are two reasons for that. First, locators don’t return a Promise, so this:

await page.locator('text="Hello"');

… is incorrect, and it immediately returns. With that said, let’s look at the full line of code in question:

expect(await page.locator('text="Are you human?"')).toContainText('Are you human?');

… is the same thing as this:

expect(page.locator('text="Are you human?"')).toContainText('Are you human?');

Notice the issue? The code doesn’t pause execution on this line at all. It immediately continues, regardless of the locator’s completion. So, let’s look at a bit more code:

expect(await page.locator('text="Are you human?"').innerText()).toBe('Are you human?');
await page.selectOption('select', '0');

The first code pair for this works, but not for the reason we think. The reason this first pair works is the await page.selectOption(‘select’, ‘0’). Since there was no previous select element on the page, this wasn’t problematic (hint, hint).

But, once we introduce the second pair:

expect(await page.locator('text="Are you sure?"').innerText()).toBe('Are you sure?');
await page.selectOption('select', '0');
await page.locator('button', { hasText: 'Next' }).click();

… you may now be able to spot the issue. The await is ignored, so it continues on to the next line, the select. Since a select element DOES already exist on the page from the previous step, it uses it. What’s more, there’s also already a “Next” button, so it tries to click it.

But, the DOM refreshes because the response from the server is received, but the above code already executed, so it’s stuck. It’s already waiting on this line of code:

expect(await fullName.innerText()).toBe('Full name: Demo User');

To fix this, we can do a bit of chaining with something that DOES return a Promise. Since we are just wanting to wait for text to be rendered in the DOM, we can use this:

await page.locator('text="Are you human?"').innerText();

Let’s look at the pairing now:

expect(await page.locator('text="Are you sure?"').innerText()).toBe('Are you sure?');
await page.selectOption('select', '0');
await page.locator('button', { hasText: 'Next' }).click();

The above works because the innerText() returns a Promise, so that await is actually respected, giving us the intention we want: selecting the choice AFTER “Are you sure?” is found in the DOM.

So, here’s the whole piece of code that works:

await page.goto('widget/modal?journey=LoginWithConfirmationAndChoice');

const loginButton = page.locator('button', { hasText: 'Open Login Modal' });
await loginButton.click();

await page.fill('text="User Name"', 'demouser');
await page.locator('button', { hasText: 'Next' }).click();

await page.fill('text=Password', 'j56eKtae*1');
await page.locator('button', { hasText: 'Next' }).click();

// Confirmation
expect(await page.locator('text="Are you human?"').innerText()).toBe('Are you human?');
await page.selectOption('select', '0');
await page.locator('button', { hasText: 'Next' }).click();

// Choice
expect(await page.locator('text="Are you sure?"').innerText()).toBe('Are you sure?');
await page.selectOption('select', '0');
await page.locator('button', { hasText: 'Next' }).click();

const fullName = page.locator('#fullName');
const email = page.locator('#email');

expect(await fullName.innerText()).toBe('Full name: Demo User');
expect(await email.innerText()).toBe('Email: demo@user.com');

References:

Here are the APIs discussed above:

  1. Locators: page.locator | Playwright – notice how it returns Locator
  2. innnerText: locator.innerText | Playwright – notice how it return Promise<string>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment