Story E2E (Playwright) — an admin authors a role, puts someone on it, watches it bite, then deletes it with a reassign; and records the acceptance video
The story's verification_recipe, automated in a browser, and the receipt a reviewer watches to accept it. A new spec beside tests/e2e/acceptance-roles-permissions.spec.ts, which covers the read-only screens and stays as it is.
The flow, in chapters
- Author. As a project admin: Project settings → Access → Roles & permissions →
Create role. Start from Member, name it Contributor, untick two permissions, watch the pinned bar's count fall, save. - Read it back. The list shows Contributor with its
Customchip, itsN of M permissionsand0 members; drilling in showsBased on Member · −2. - Assign. Project settings → Members: change a teammate to Contributor. The row's chip becomes Contributor, and the role list now shows
1 member. - It bites. Sign in as that teammate and reach the surface one of the removed permissions governs: the control refuses. Then do something the role KEPT — edit a work item, comment — and it succeeds. Both halves matter: a role that takes everything away is not evidence the model works.
- Built-ins are safe. As the admin, open Admin: no
Edit, noDelete, a lock instead. - Delete with a reassign. Delete Contributor: the dialog names 1 member and requires a destination. Choose Viewer, confirm. The list no longer has Contributor, and the teammate's row reads Viewer — never blank.
Discipline
- Wait on authoritative signals — the persisted row, the toast, the re-rendered chip — never a fixed timeout, and never a
waitForTimeoutstanding in for a save. - Cover the states a happy path skips: the editor's empty name refusal and its duplicate-name refusal, and the role list at zero custom roles.
- The video is watched by a person. Use the harness's paced
chapter()so the clip is legible at human speed — a spec that races through meets the letter and produces a receipt nobody can review. The publish step refuses an unwatchable clip; the card says it so the pacing is planned rather than discovered from a red step. - Each of the three sign-ins uses the workspace-cookie pinning the shipped specs already do, so the run cannot drift onto another workspace's project.
Scope boundary
In: the spec, its fixtures, the recorded and published acceptance video, and any data-testid the surfaces need — added to the components in this PR. Out: unit and integration coverage, which the vitest gate owns; any production behaviour change — a failing step is a bug card, not an edited assertion; the shipped acceptance-roles-permissions.spec.ts, which keeps covering the read screens.
Acceptance criteria
tests/e2e/acceptance-custom-roles.spec.tsdrives all six chapters above against a real server and a real database, and passes on a cold run.- Chapter 4 asserts both directions: the removed permission's control refuses, and a kept permission's action succeeds.
- Chapter 6 asserts the teammate holds the destination role afterwards, read off the members surface — the criterion is that nobody is left without a role, so asserting the deletion alone does not discharge it.
- The empty-name and duplicate-name refusals are covered, and the role list's zero-custom-roles state renders.
- Every wait is on an authoritative signal; the spec contains no fixed-duration sleep.
- The run records and publishes the acceptance video to the story, paced with
chapter()so it is watchable, and the publish step accepts it. - The spec is deterministic across three consecutive runs and does not depend on data another spec leaves behind.
Context refs
tests/e2e/acceptance-roles-permissions.spec.ts— the sibling spec for the read screens: its sign-in helpers, workspace pinning and chapter pacing.tests/e2e/per-domain-admin-permissions.spec.ts·tests/e2e/member-facing-permissions.spec.ts— the shipped multi-role permission specs and how they assert a refusal.playwright.config.ts(testDir: 'tests/e2e') and the acceptance-video harness the shipped specs use.motir-core/CLAUDE.md— the E2E discipline: authoritative waits, no fixed sleeps.- The story's
verification_recipe, which this automates; the three surfaces it drives — the editor, the delete flow, the assignment.