It is indeed possible to achieve what you are asking for:
public getNestedElementByTestIds(testIds: Array<string>) {
if (testIds.length === 0) {
throw Error("testIds must contain at least one element");
}
let locator = this.page.getByTestId(testIds[0]);
for (const testId of testIds.slice(1)) {
locator = locator.getByTestId(testId);
}
return locator;
}
Here is a runnable demonstration:
const playwright = require("playwright"); // ^1.30.1
const html = `
<div data-testid="foo">
ignore this
<div data-testid="bar">
ignore this
<div data-testid="baz">
ignore this
<div data-testid="quux">
PRINT ME
</div>
</div>
</div>
</div>`;
const getNestedElementByTestIds = (page, testIds) => {
if (testIds.length === 0) {
throw Error("testIds must contain at least one element");
}
let locator = page.getByTestId(testIds[0]);
for (const testId of testIds.slice(1)) {
locator = locator.getByTestId(testId);
}
return locator;
}
let browser;
(async () => {
browser = await playwright.chromium.launch();
const page = await browser.newPage();
await page.setContent(html);
console.log((await getNestedElementByTestIds(page, [
"foo", "bar", "baz", "quux"
]).textContent()).trim()); // => PRINT ME
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
However, keep in mind that this approach may be overly complex. It is recommended to limit the chaining of getByTestIds
in the calling code, or employ a more reliable selection method rather than relying solely on test ids and emphasizing direct, user-visible properties.
In essence, it's best not to abstract Playwright calls into helpers unnecessarily, even if it means duplicating some code across tests. Playwright commands are already quite high-level, and anything beyond a simple POM setup could pose challenges in terms of maintenance for anyone other than the original author.