Say you have a web app which displays a table of dynamic data that looks like this:
And the DOM looks like:
<table> <theader> <tr> <th>Status</th> <th>Name</th> <th>Actions</th> </tr> </theader> <tbody> <tr> <td>OPEN</td> <td>Cactus</td> <td> <div> <button>CLICK ME</button> </div> </td> </tr> <tr> <td>CLOSED</td> <td>Succulent</td> <td> <div> <button>CLICK ME</button> </div> </td> </tr> <tr> <td>OPEN</td> <td>Aloe</td> <td> <div> <button>CLICK ME</button> </div> </td> </tr> <tr> <td>OPEN</td> <td>Agave</td> <td> <div> <button>CLICK ME</button> </div> </td> </tr> </tbody> </table>
Say you’re writing an e2e test that adds an item to the table dynamically (with a new name each time) and you need to click the button on the corresponding line to complete your e2e test.
How do you select the button based upon the name of the item you just created? Let’s save
I feel like the best possible approach is to add a
data- attribute to the buttons so you can know exactly which one to click. Something like
But if you can’t add that value to each row – what’s the next best thing to do?
Playwright has a pretty neat test recorder (even though I don’t like test recorders) – so let’s see what it generates
I launch Playwright Inspector (which has the Record function) with
npx playwright open https://webdriverjsdemo.github.io/table/
After clicking Record, and then clicking the four buttons on the table in order from top-bottom, the Inspector tool gives me:
await page.click('text=CLICK ME'); await page.click('text=Succulent CLOSED CLICK ME >> button'); await page.click('text=Aloe OPEN CLICK ME >> button'); await page.click('text=Agave OPEN CLICK ME >> button');
The issue with this generated code is it relies on the status values which can change, also if the status column was removed in a future change it would fail which makes the tests brittle.
We can use the Playwright Inspector “Explore” feature to enter a selector value and see whether it finds what we’re looking for in the DOM.
If we took one of the selectors above and tried to remove the bit we don’t care about from the text you can see it doesn’t find what we want:
Doing some research I found there’s an option to find any element containing some text, we can choose the row containing the text “Agave” (or whatever the name of the record we created is) and then choose the button belonging to that row:
The important thing to note here is we are selecting the row, even though the cell contains the text we’re looking for.
I think this is probably the best option if we don’t have ability to change the DOM and add data attributes – it’s the least brittle as any additional columns could be added to the table and it would still work.
Some other options I investigated were locators based on position
For example, you can write a selector that finds the buttons to the right of the text we’re looking for:
But as you can see from the image above it locates all the buttons as they’re all technically to the right of the text – just not immediately to the right.
Whilst it’s good practice to add testability features to your app, Playwright also offers powerful ways to select elements based on text and DOM layout if you can’t. You can read all about the Playwright selectors here.