test(e2e) : initialise la suite Playwright (login + sidebar RBAC)

- 11 tests couvrant le login (3) et la visibilite sidebar par RBAC (8)
- 6 personas seedes via la commande app:seed-e2e, miroir cote front
  dans frontend/tests/e2e/_fixtures/personas.ts
- Page Objects (LoginPage, SidebarComponent) avec selecteurs stables
  par href + loginAs programmatique via cookie BEARER
- Targets Makefile : seed-e2e, test-e2e, test-e2e-ui, install-e2e-deps
- CLAUDE.md + README.md : workflow E2E + regle d'or "un E2E par bug
  prod uniquement" pour garder la suite maintenable dans la duree

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 17:27:05 +02:00
parent 99c77eb7b6
commit 4603ab2832
15 changed files with 786 additions and 3 deletions

View File

@@ -0,0 +1,32 @@
import type { Locator, Page } from '@playwright/test'
/**
* Page Object du formulaire de login (/login).
*
* Selecteurs : on s'appuie sur les labels/roles accessibles (stable vs les
* changements de CSS/Tailwind). Le jour ou on veut un selecteur plus dur,
* on ajoute des `data-testid` sur login.vue.
*/
export class LoginPage {
readonly page: Page
readonly usernameInput: Locator
readonly passwordInput: Locator
readonly submitButton: Locator
constructor(page: Page) {
this.page = page
this.usernameInput = page.getByLabel("Nom d'utilisateur")
this.passwordInput = page.getByLabel('Mot de passe')
this.submitButton = page.getByRole('button', { name: 'Se connecter' })
}
async goto(): Promise<void> {
await this.page.goto('/login')
}
async fillAndSubmit(username: string, password: string): Promise<void> {
await this.usernameInput.fill(username)
await this.passwordInput.fill(password)
await this.submitButton.click()
}
}

View File

@@ -0,0 +1,33 @@
import type { Locator, Page } from '@playwright/test'
export type AdminLinkSlug = 'users' | 'roles' | 'sites' | 'audit-log'
/**
* Page Object de la sidebar (MalioSidebar), scope sur les items "admin".
*
* Strategie selecteur : `a[href=...]` plutot que le texte i18n. Le slug de
* route ne change pas quand on retraduit ou renomme une entree — c'est le
* selecteur le plus stable pour cette suite.
*
* Si un jour la sidebar change et les slugs bougent, on met a jour CE
* fichier uniquement ; les specs continuent de passer.
*/
export class SidebarComponent {
readonly page: Page
constructor(page: Page) {
this.page = page
}
adminLink(slug: AdminLinkSlug): Locator {
return this.page.locator(`a[href="/admin/${slug}"]`)
}
accountDashboardLink(): Locator {
return this.page.locator('a[href="/"]').first()
}
logoutLink(): Locator {
return this.page.locator('a[href="/logout"]')
}
}