Reputation: 3272
I'm using playwright nodejs. I've written myself a little dynamic selector function to select the page number button on a dataTable.
pageNumberButton(page, table_id, page_number) {
page.locator(`[aria-controls=${table_id}]`, {hasText: page_number});
}
I've also tried:
pageNumberButton(page, table_id, page_number) {
page.locator(`[aria-controls=${table_id}] text=${page_number}`);
}
However, I can't seem to make it do an exact match.
Suppose my dataTable has 13 pages:
and I wish to click on page 1. so I issue the following command: await pageNumberButton(page, "resultsTable", "1").click();
But I get a strict-mode error, since there are two results: 1 and 13.
What would be the best, or good, way to create this little selector dynamically so that I can do an exact match for the button?
Upvotes: 11
Views: 50777
Reputation: 56965
Exact matching is possible with {exact: true}
. See this example from the docs:
await expect(page.getByText('Welcome, John', { exact: true })).toBeVisible();
Note that this does normalize whitespace, which is probably the desired behavior in most cases, so it's not quite "exact" as advertised.
Applied to your case:
page
.locator(`[aria-controls="${tableId}"]`)
.getByText(pageNumber, {exact: true});
It's possible to use the text=
syntax in the locator as you've attempted, as long as you separate the CSS and text=
locators into two calls and use quotes inside the text=""
selector. Applied to your example:
page
.locator(`[aria-controls="${table_id}"]`)
.locator(`text="${page_number}"`);
Here's a complete, runnable demonstration:
import {expect, test} from "@playwright/test"; // ^1.39.0
const html = `<!DOCTYPE html><html><body>
<div aria-controls="foo">1</div>
<div aria-controls="foo">13</div>
</body>
</html>`;
const pageNumberButton = (page, tableId, pageNumber) => {
return page
.locator(`[aria-controls="${tableId}"]`)
.locator(`text="${pageNumber}"`);
};
test("aria-controls='foo' with text '1' is visible", async ({page}) => {
await page.setContent(html);
await expect(pageNumberButton(page, "foo", 1)).toBeVisible();
});
Forgetting quotes in the text=""
syntax is a subtle gotcha, which is why I prefer the more explicit getByText("...", {exact: true})
.
Use quotes on the [aria-controls="..."]
CSS selector as well as the text=""
selector.
Don't forget to return
the locator from your pageNumberButton
function.
Yet another approach is the :text-is("")
pseudoselector which "matches the smallest element with exact text. Exact matching is case-sensitive, trims whitespace and searches for the full string" according to the docs:
page.locator(`[aria-controls="${tableId}"]:text-is("${pageNumber}")`);
Further remarks:
.
are treated differently and wind up with subtle bugs..filter()
should be avoided when possible. The syntax is less clear than a single chain of locators.Upvotes: 9
Reputation: 41
I recommend text-is
instead of hasText
.
https://playwright.dev/docs/other-locators
Upvotes: 4
Reputation: 14618
Playwright allows chaining locators. You can make a locator that solves your issue and also is simple and readable - no regex required. First you find the aria-controls
element, then you find the correct button inside it:
function pageNumberButton(page, table_id, page_number) {
return page.locator(`[aria-controls=${table_id}]`)
.getByText(`${page_number}`, { exact: true });
}
await pageNumberButton(page, "resultsTable", "1").click();
should work fine after that.
Upvotes: 2
Reputation: 116
text body can be escaped with single or double quotes to search for a text node with exact content
pageNumberButton(page, table_id, page_number) {
page.locator(`[aria-controls=${table_id}]`)
.filter({ has: page.locator(`text="${page_number}"`) });
}
Upvotes: 10
Reputation: 3272
I found an option. Create a regex object and pass that in for hasText.
pageNumberButton(page, table_id, page_number) {
const regexNumber = new RegExp(`^${page_number}$`);
page.locator(`[aria-controls=${table_id}]`, {hasText: regexNumber});
}`
Upvotes: 10