EN VI

Typescript - Is there a way to create a custom css selector in Playwright to check for both a data-testId and a css style at the same time?

2024-03-14 07:00:05
Typescript - Is there a way to create a custom css selector in Playwright to check for both a data-testId and a css style at the same time?

I have a UI element with both a data-testid and a css attribute of style='display: table-row;. Right now, I use getByTestId to grab that test id and then chain a locator to it to check for the style attribute. However, that looks for an element with that style nested within an element that has the data-testid.

I'm wondering if there's a way to create a custom css selector to match for both here.

My current LoC is

this.formFieldError = page.getByTestId("sign-up-error").locator("[style='display: table-row']")

The HTML for the element in question is:

<tr data-testid="sign-up-error" class="formvalidate_error password_error" style="display: table-row;">
<td></td>
<td class="error_msg">Password must be at least 10 characters long</td>
</tr>

and I'm trying to get the row element. It shares the same data-testid as other rows, the difference is that when the element is displayed, the style attribute changes.

Solution:

I would add a test id to the <td> with the error in it so you can select it directly. This is probably the easiest approach.

If it's not feasible to change the markup, I'd select by the text:

const playwright = require("playwright"); // ^1.42.1

const html = `<!DOCTYPE html><html><body><table>
<tr data-testid="sign-up-error" class="formvalidate_error password_error" style="display: table-row;">
  <td></td>
  <td class="error_msg">Password must be at least 10 characters long</td>
</tr></table></body></html>`;

let browser;
(async () => {
  browser = await playwright.firefox.launch();
  const page = await browser.newPage();
  await page.setContent(html);
  await page
    .getByTestId("sign-up-error")
    .getByRole("cell")
    .getByText("Password must be at least 10 characters long")
    .waitFor();
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

Or by the child class, if you can't specify the text (even partially):

await page
  .getByTestId("sign-up-error")
  .locator("td.error_msg")
  .waitFor();

Or using the :scope pseudoselector to include the cell <td> itself:

await page
  .getByTestId("sign-up-error")
  .getByRole("cell")
  .locator(":scope.error_msg")
  .waitFor();

A selector like "[style='display: table-row']" is really brittle and strongly discouraged. Even adding a semicolon to the inline style attribute causes the selector to fail the ultra-strict exact match:

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

const htmlWithoutSemi = `<!DOCTYPE html><html><body><table>
<tr style="display: table-row"><td>asdf</td></tr>
</table></body></html>`;

const htmlWithSemi = `<!DOCTYPE html><html><body><table>
<tr style="display: table-row;"><td>asdf</td></tr>
</table></body></html>`;

test("works when there's no semicolon", async ({page}) => {
  await page.setContent(htmlWithoutSemi);
  await expect(page.locator("[style='display: table-row']")).toBeVisible();
});

test("fails when there's a semicolon", async ({page}) => {
  test.fail(); // we expect this to fail, unfortunately
  await page.setContent(htmlWithSemi);
  await expect(page.locator("[style='display: table-row']")).toBeVisible();
});

You can use attribute selector variants like *= and ~= to avoid these failures, but these can open up other problems. Generally speaking, it's better to avoid CSS attributes regardless of whether you're testing or web scraping, particularly style= attributes, as these can be very free-text, tend to change often and can be (correctly) moved to stylesheets without notice.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login