Domenico Sacino
Domenico Sacino

Reputation: 71

Is there a way in Playwright to select a specific button inside a dynamic table?

I'm new in developing and I'm facing a real problem with the creation of one e2e test.
Basically, I have a table with 2 or more rows, every row has 5 columns ( title, x, y,z button). How can I click the button on the correct row using the title? (This is a test to prove that the delete process of this table works). The application I'm testing is written with React framework, so all the tables change frequently and I need a way to trust the code and don't have any bugs. I need to click this element but it is not ever in the same position

HTML

<table>
    <tr>
     <td>Some Title</td>
     <td>x</td>
     <td>y</td>
     <td>
       <button>I need to click this</button>
    </td>
    </tr>
    <!--other rows--!>
</table>  

This is the solution I came across

const rows = await page.$$eval("tr", (row) =>
    row.map((e) => e.textContent)
);
const correctRowIndex = rows.findIndex((e) => e.includes(TITLE_I_KNOW));
await page.click(
    "//tr[normalize-space(.)='" + rows[correctRowIndex] + "']/td/button"
);

Desired behaviour

My code seems not to follow the best practice, I need a solution that makes this thing in 2 parts.
1 - Saving the correct row into a variable
2 - Click on the button contained in the saved row

Upvotes: 7

Views: 19910

Answers (2)

Anton Bessonov
Anton Bessonov

Reputation: 9813

I'm late to the party, but maybe helpful to someone else. There is a :has pseudo class exactly for such use cases. Try:

await page.click('tr:has(td:text("Some Title")) button')

Upvotes: 2

buzz
buzz

Reputation: 956

Quick answer

Use the following combination of selectors to reliably click the right button.

const row = await page.waitForSelector('*css=tr >> text=Some title');
const button = await row.$('button');
await button.click();

In-depth explanation

Playwright is an awesome tool and might be one of the best web automation tools so far. But it needs to be used correctly.

1. Selecting the correct row

const row = await page.waitForSelector('*css=tr >> text=Some title');

This line instructs Playwright to find a tr element in the page that contains an element which text contents matches the string Some title.

The expression is composed of two separate selectors, namely css=tr and text=Some title.

The first one will use the css selector engine and just targets all tr elements. The second one uses the text engine thus matching text content.

The >> instructs Playwright to combine both selectors into one. This works by evaluating first css=tr. On each of the matching elements of the first selector the second selector is applied.

Normally a combined selector returns the results of the rightmost selector: tr >> text=Some title would give you the element <td>Some Title</td>. But it is possible to mark the desired selector with * (while still narrowing the results using all combined selectors).

Note: The asterix * makes it mandatory to specify the selector engine explicitly (css=), otherwise Playwright would guess the wrong engine.

Here you can read more about Playwright selectors.

2. Selecting the button

const button = await row.$('button');

Once we have the right row, selecting the button is easier in this case. A simple query selector ($) is enough as it's the only button element existing in a row.

Calling the $ on row (instead of page) will match only elements within the DOM subtree of row. This works exactly like the combined matchers.

The next section explains why step 1 must use waitForSelector().


The right timing

Like your example, many websites are dynamic. This means you will have timing issues when using an automated script looking for content in your page: The table might not be rendered yet when Playwright searches for the desired content. Even worse, if it works on your computer, it might fail weeks later on another computer.

That's why it's important to look at each line interacting with the web page and decide if there are timing issues to account for.

Some Playwright methods will probe periodically for a selector to match and only fail after a certain timeout. Others won't wait and fail instantly.

Sometimes methods indicate in their name if they are waiting (e.g. page.waitForSelector()). But this is not always the case (e.g. elementHandle.click() is actually waiting).

So it's mandatory to look at the API reference and read the descriptions carefully.

Now back to the example:
In step 1 we had to use a waiting method because we didn't know when the dynamic table is actually rendered to the page. In step 2 using a waiting method is not needed as we can be certain the table is present in the page after step 1 succeeded.

Upvotes: 12

Related Questions