Automation is primarily focused on the user flow rather than just what the user sees. It involves guiding the user through completing tasks that align with the product logic, utilizing user resources such as browsers.
Rendered components are typically validated using visual (screenshot) tests, and this should be a small part of overall test coverage according to the Testing Pyramid concept, where end-to-end testing can be divided into functional and visual aspects.
In most cases, the focus is on whether an element exists, is rendered, visible, clickable, and has certain styles and properties, rather than the text itself. Text is usually defined by content keys, negating the need to test it. Units can verify that an element has a key and that the key has a value, but this isn't the main purpose of end-to-end testing.
- If you have access to the source code and can modify it, it's advisable to assign unique locators to elements for use in end-to-end testing.
For example:
[data-testid=heading]
or page.getByDataTestId('heading')
[data-hook=heading]
[data-aid=heading]
If you cannot add attributes yourself but see existing ones, it's better to leverage them as they are likely intended for automation purposes and are less likely to change.
You can then rely on the element's id
, ensuring it is unique and not auto-generated.
Rely on attribute values representing the type or purpose of elements, such as input[type=search]
, [role=heading]
.
Rely on classes that appear unique or combinations of attributes and classes like .headingContainer
, div.headingContainer
.
Utilize custom tags like name
, title
, etc., bearing in mind they may vary between different localizations.
Explore unique tags like those used by YouTube, e.g., ytb-video-container
, ytb-video-player
.
Rely on the href or src attribute, for instance, img[src*=imgur]
, [href*=imgur]
, if other options are exhausted.
Only consider using text in locators as a last resort. Text can easily change due to product logic adjustments and localization differences.
When constructing locators, aim for simplicity whenever possible, preferring single-level structures. If not feasible, utilize component containers to access child elements.
Example:
const posts = await page.locator('#blogPostContainer').all();
for(const post of posts) {
await post.getByRole('heading').click();
// do something
}