How to manage dynamic Text in a label with Playwright?

169 Views Asked by At

I have the following code in a Playwright Typescript test.

await page.goto('https://demoqa.com/');
await page.getByLabel('Optionen verwalten', { exact: true }).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (32' }).locator('span').nth(2).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (49' }).locator('span').nth(2).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (16' }).locator('span').nth(2).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (22' }).locator('span').nth(2).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (40' }).locator('span').nth(2).click();
await page.locator('label').filter({ hasText: 'Berechtigtes Interesse (3 Anbieter' }).locator('span').nth(2).click();
await page.getByRole('button', { name: 'Auswahl bestätigen' }).click();

There are multiple labels, below are two html examples:

<label class="fc-preference-slider-container fc-consent-preference-container"><span class="fc-preference-slider-label">Einwilligung (77&nbsp;Anbieter)</span><span class="fc-preference-slider"><input type="checkbox" role="button" aria-label="Einwilligung (77&nbsp;Anbieter), Verwendung reduzierter Daten zur Auswahl von Werbeanzeigen" aria-pressed="false" tabindex="0" class="fc-preference-consent purpose" data-id="2"><span class="fc-slider-el"></span></span></label>
<label class="fc-preference-slider-container fc-legitimate-interest-preference-container" for="fc-preference-slider-purpose-2"><span class="fc-preference-slider-label">Berechtigtes Interesse (32&nbsp;Anbieter)<button class="fc-help-tip" data-title="Was bedeutet ein berechtigtes Interesse?" data-full-info="Einige Anbieter bitten Sie nicht um Ihre Einwilligung, sondern nutzen Ihre personenbezogenen Daten auf Grundlage ihres berechtigten Interesses." role="tooltip" aria-label="Einige Anbieter bitten Sie nicht um Ihre Einwilligung, sondern nutzen Ihre personenbezogenen Daten auf Grundlage ihres berechtigten Interesses." tabindex="0"><i class="material-icons" data-title="Was bedeutet ein berechtigtes Interesse?" data-full-info="Einige Anbieter bitten Sie nicht um Ihre Einwilligung, sondern nutzen Ihre personenbezogenen Daten auf Grundlage ihres berechtigten Interesses." translate="no">help_outline</i></button></span><span class="fc-preference-slider"><input type="checkbox" role="button" aria-label="Berechtigtes Interesse (32&nbsp;Anbieter), Verwendung reduzierter Daten zur Auswahl von Werbeanzeigen" aria-pressed="true" tabindex="0" class="fc-preference-legitimate-interest purpose" data-id="2" id="fc-preference-slider-purpose-2" checked=""><span class="fc-slider-el"></span></span></label>

I want to deselect all the options which are aria-pressed="true"

The problem i have is with finding a loactor which works. The label i'm using i.e. 'Berechtigtes Interesse (32' may change, and i would like to make the code more robust.

I can use the unique locator id:

const slider2 = page.locator('[id$="fc-preference-slider-purpose-2"]');

but how do i then adjust the code to click on the associated slider?

2

There are 2 best solutions below

8
ggorlen On

getByLabel works on a substring basis, which may work for your use case:

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

const html = `<!DOCTYPE html><html><body>
<input
  type="checkbox"
  role="button"
  aria-label="Berechtigtes Interesse (32&nbsp;Anbieter), Verwendung reduzierter Daten zur Auswahl von Werbeanzeigen"
  aria-pressed="true"
  tabindex="0"
  class="fc-preference-legitimate-interest purpose"
  data-id="2"
  id="fc-preference-slider-purpose-2"
  checked=""
>
</body></html>`;

test("input is selectable via partial label substring", async ({page}) => {
  await page.setContent(html);
  const label = "Verwendung reduzierter Daten zur Auswahl von Werbeanzeigen";
  await expect(page.getByLabel(label)).toBeVisible();
});

If you need more precision, another option is a CSS attribute selector $ to search based on the end of the attribute value:

// ...
  const input = page.locator(
    '[aria-label$="Verwendung reduzierter Daten zur Auswahl von Werbeanzeigen"]'
  );
  await expect(input).toBeVisible();
// ...

I'm not sure why you're using .locator('span').nth(2) since that doesn't reflect your HTML, but bear in mind that this isn't generally considered best practice, since it doesn't test user-visible behavior. You may want to share more context to potentially avoid this.

0
Mike Stop Continues On

To start off, I would consider using regex to target the label. From what I can tell, you have a pattern that looks like {text} ({number} {text}):

await page.locator('label')
  .filter({ hasText: /^[\w ]+\(\d+ [\w ]+\)$/ })

However, if you have control over the HTML, it would be much better to add a data-testid="your-id" attribute, then use a simplified query:

await page.getByTestId('your-id')