Files
redefined-designs/frontend/tests/e2e/pages/AdminTaxonomy.ts
T
bermudalambandClaude Opus 5 94b2dfd186 fix(e2e): match the modal OK button exactly so a random suffix cannot collide (#253)
Playwright matches an accessible name case-insensitively and as a substring unless exact is passed, so `getByRole('button', { name: 'OK' })` matched any button whose name merely contained "ok".

Caught on a full serial run of main:

    strict mode violation: getByRole('button', { name: 'OK' }) resolved to 2 elements:
        1) aka getByRole('button', { name: 'Freed rmtiq9okg22k9e' })
        2) aka getByRole('button', { name: 'OK', exact: true })

A leftover "Freed …" toast was still on screen and the random base36 suffix happened to contain "ok". Roughly one suffix in a few hundred does, which is the profile of a test that fails occasionally and reproduces for nobody.

This is not the contention #241 addressed. It reproduced with workers: 1, serially, on a fresh database — it needs only a stale toast and an unlucky suffix. It is at least part of what #245 skipped a real test to work around.

Also makes the taxonomy modal's OK and the sold-filter 'All' radio exact, the only other targets short enough to appear inside a random suffix. The dozen or so remaining short names are left alone deliberately: they would need a collision in deliberately-chosen test data rather than a random one, and substring matching is load-bearing in some of them.

Verified with two consecutive full e2e passes, 155 of 155 both times.

Closes #253

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 09:42:37 -05:00

51 lines
1.9 KiB
TypeScript

import { Locator, Page } from '@playwright/test';
/**
* The Categories and Tags tabs, which share a shape: a table or tree, an
* "Add X" button, and an antd Modal with a single Name field behind it.
*
* Categories are a tree because they nest arbitrarily deep; tags are flat. The
* tree is what makes "Add child" meaningful, and it is mounted before a new
* branch exists — so a child is only visible if expansion follows newly created
* nodes rather than the state captured at first render, which is the thing the
* spec here pins down.
*/
export class AdminTaxonomy {
readonly addCategoryButton: Locator;
readonly addTagButton: Locator;
readonly name: Locator;
readonly confirmButton: Locator;
readonly tagAddedNotice: Locator;
constructor(private readonly page: Page) {
this.addCategoryButton = page.getByRole('button', { name: 'Add Category' });
this.addTagButton = page.getByRole('button', { name: 'Add Tag' });
this.name = page.getByLabel('Name');
// Substring matching would collide with random suffixes. See #253.
this.confirmButton = page.getByRole('button', { name: 'OK', exact: true });
this.tagAddedNotice = page.getByText('Tag added');
}
categoryNode(name: string): Locator {
return this.page.getByRole('treeitem').filter({ hasText: name });
}
async addCategory(name: string): Promise<void> {
await this.addCategoryButton.click();
await this.name.fill(name);
await this.confirmButton.click();
}
async addChildCategory(parentName: string, childName: string): Promise<void> {
await this.categoryNode(parentName).getByRole('button', { name: 'Add child' }).click();
await this.name.fill(childName);
await this.confirmButton.click();
}
async addTag(name: string): Promise<void> {
await this.addTagButton.click();
await this.name.fill(name);
await this.confirmButton.click();
}
}