How do you locate the parent of an element with Playwright?
In this article I'll cover 5 different ways to locate a parent element!
One common challenge with UI automation is trying to access data from or control an element on a webpage that doesn't have a unique identifier. For example, given the below Challenging DOM page, finding and interacting with the edit button isn't the most straight forward task. With at least 10 different edit buttons on the page we can't just rely on clicking on a link with edit text.
We first need to identify something unique on the page we can interact with that will be consistent in the DOM. In typical UI tests, where you are verifying some sort of data within a table or on a grid like this you typically have some sort of unique identifier. For our purposes I will be using Adipisci5
as my unique identifier. I will walk through a few different ways to interact with the edit link for the row that container Adipisci5
. Below is an image of the HTML found on the page.
Our goal will be to find the data cell <td> with text
, then find the full row (highlighted above), then click on the edit link. I'll show off a few different ways you can accomplish getting the parent row <tr> in this example.Adipisci5
Playwright locator with the option: has
This first example utilizes the has
option on the locator class.
const row = page.locator("tr", { has: page.locator('text="Adipisci5"') });
CSS: matching by text
This example utilzes the built in css matching by text that Playwright has built in.
const row = page.locator('tr:has-text("Adipisci5")');
getByRole with accessible name
This example utilizes the newly released getByRole utilizing the name option to find the unique identifer.
const row = page.getByRole("row", { name: "Adipisci5" });
getByRole with filter() method
This is a similar way above but we use the .filter() method which is really powerful as it allows you to also utilize regex in the hasText section (see 2nd example).
const row = page.getByRole("row").filter({ hasText: "Adipisci5" });
const row = page.getByRole("row").filter({ hasText: /Adi.*ci5/ });
Using xpath locator.("..")
This works, it can be really useful and a quick way to grab a parent element. Overall I'm not a huge fan of xpaths in my code, but this is the one xpath I will allow.
const row = page.locator("text=Adipisci5").locator("..");
Below is a full example utilizing the different ways to access parent elements, but then use those parent elements to click on the edit link in the selected row, with an assertion on the url to confirm the edit button was clicked.
import { test, expect } from "@playwright/test";
test.describe("Challenging DOM", async () => {
test.beforeEach(async ({ page }) => {
await page.goto("https://the-internet.herokuapp.com/challenging_dom");
});
test("Find parent element with has option in Playwright locator", async ({
page,
}) => {
await page
.locator("tr", { has: page.locator('text="Adipisci5"') })
.getByRole("link", { name: "edit" })
.click();
await expect(page).toHaveURL(/.*#edit/);
});
test("Find parent element with has-text", async ({ page }) => {
await page.locator('tr:has-text("Adipisci5")').locator("text=edit").click();
await expect(page).toHaveURL(/.*#edit/);
});
test("Find parent element with getByRole locator and accessible name", async ({
page,
}) => {
await page
.getByRole("row", { name: "Adipisci5" })
.getByRole("link", { name: "edit" })
.click();
await expect(page).toHaveURL(/.*#edit/);
});
test("Find parent element with getByRole locator and filter with regex", async ({
page,
}) => {
await page
.getByRole("row")
.filter({ hasText: /Ad.*sci5/ })
.getByRole("link", { name: "edit" })
.click();
await expect(page).toHaveURL(/.*#edit/);
});
test("Find parent element with xpath", async ({ page }) => {
await page
.locator("text=Adipisci5")
.locator("..")
.locator("text=edit")
.click();
await expect(page).toHaveURL(/.*#edit/);
});
test("Find parent element with xpath broken down", async ({ page }) => {
const cell = page.locator("text=Adipisci5");
const row = cell.locator("..");
const editLink = row.locator("text=edit");
await editLink.click();
await expect(page).toHaveURL(/.*#edit/);
});
});
As you can see Playwright has a ton of flexibility when it comes to locating and working with parent elements. If there is something I'm missing please reach out and let me know on LinkedIn.
Thanks for reading! If you found this helpful, reach out and let me know on LinkedIn or consider buying me a cup of coffee. If you want more content delivered to you in your inbox subscribe below.