import { expect, test } from '@playwright/test'; import { DOC, person, signIn, TEST_DOC } from './helpers'; test('C1 — l’accueil : l’intro, les quatre sections, le menu complet', async ({ page }) => { await page.goto('/'); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Rendre possible la décision collective'); await expect(page.getByText('en fonction du contexte, processus et modalités, boîte à outils')).toBeVisible(); const nav = page.getByRole('navigation', { name: 'Boîte à outils' }); for (const item of ['Décisions', 'Docs de référence', 'Mandats', 'Protocoles']) { await expect(nav.locator('.tool', { hasText: item })).toBeVisible(); } await expect(nav.locator('.tool.disabled')).toHaveCount(3); await expect(page.getByRole('link', { name: 'Se connecter avec sa clé Ğ1' })).toBeVisible(); }); test('C2 — les groupes de la même clé ; pas l’Atelier du canal', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto('/groupes'); for (const g of ['Monnaie libre', 'Axiom', 'Librodrome', 'Groupe local Vallée Drôme Crestois']) { await expect(page.getByText(g, { exact: true })).toBeVisible(); } await expect(page.getByText('Atelier du canal')).toHaveCount(0); }); test('C3 — le texte en vigueur : préambule et deux engagements, seed a minima', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(DOC); await expect(page.getByRole('heading', { level: 1 })).toHaveText("Acte d'engagement Certification"); const units = page.locator('section.text article.unit'); await expect(units).toHaveCount(3); await expect(units.nth(0).locator('h2')).toHaveText('Préambule'); await expect(units.nth(1).locator('h2')).toHaveText('Unicité du compte'); await expect(units.nth(2).locator('h2')).toHaveText('Réciprocité'); await expect(page.getByText('seed a minima · 0 vote')).toHaveCount(3); await expect(page.getByRole('heading', { name: 'Propositions en cours' })).toBeVisible(); await expect(page.locator('ul.deep button.row')).toHaveCount(32); }); test('C4 — les actions sont contextuelles, le vote s’ouvre en panneau', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(DOC); const first = page.locator('section.text article.unit').first(); await first.hover(); await first.getByRole('button', { name: 'Le vote' }).click(); const panel = page.locator('aside.panel'); await expect(panel.getByText('Seuil : 50 voix pour')).toBeVisible(); await expect(panel.getByText('Il manque 50 voix pour.')).toBeVisible(); await panel.getByText('La formule du seuil').click(); await expect(panel.getByText(/W — électorat/)).toBeVisible(); // The panel lives in the URL. await expect(page).toHaveURL(/onglet=vote/); await page.keyboard.press('Escape'); await expect(panel).toHaveCount(0); }); test('C5 — une proposition remonte en vigueur quand elle atteint son seuil (groupe d’essai)', async ({ browser }) => { const yvv = await person(browser, 'Yvv'); const chiara = await person(browser, 'Chiara'); for (const p of [yvv, chiara]) { await p.goto(TEST_DOC); await p.locator('ul.deep button.row', { hasText: 'Les deux garanties réciproques' }).click(); await p.locator('aside.panel').getByRole('button', { name: 'Pour', exact: true }).click(); await expect(p.getByText('Vote enregistré.')).toBeVisible(); } await expect(chiara.getByText('« Les deux garanties réciproques » est entré en vigueur.')).toBeVisible(); await chiara.keyboard.press('Escape'); await expect(chiara.locator('section.text article.unit')).toHaveCount(4); }); test('C6 — une coquille se corrige sans vote', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(TEST_DOC); const preamble = page.locator('section.text article.unit').first(); await preamble.hover(); await preamble.getByRole('button', { name: 'Le vote' }).click(); const panel = page.locator('aside.panel'); await panel.getByText('Corriger une coquille').click(); const body = panel.getByLabel('Texte', { exact: true }); await body.fill((await body.inputValue()).replace('latransmission', 'la transmission')); await panel.getByRole('button', { name: 'Enregistrer la correction' }).click(); await expect(page.getByText('Coquille corrigée, sans toucher aux votes.')).toBeVisible(); await expect(preamble).toContainText('la transmission de ce document'); // A change of meaning is turned away. await panel.getByText('Corriger une coquille').click(); await body.fill('Tout autre chose, qui change le sens du préambule.'); await panel.getByRole('button', { name: 'Enregistrer la correction' }).click(); await expect(panel.getByText('Ce changement touche au sens : propose-le comme modification.')).toBeVisible(); }); test('C7 — l’assistant propose de découper deux décisions', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(TEST_DOC); await page.getByRole('button', { name: 'Proposer un nouvel article' }).click(); const panel = page.locator('aside.panel'); await panel.getByLabel('Titre').fill('Joignabilité et vérification'); await panel.getByLabel('Texte').fill( "Je m'engage à rester joignable par mes certifieurs.\n\nJe m'engage à vérifier la clé publique avec la personne.", ); await expect(panel.getByText('Ce texte semble porter 2 décisions.', { exact: false })).toBeVisible(); await panel.getByRole('button', { name: 'Découper en 2 brouillons' }).click(); await expect(page.getByText('Découpé en 2 brouillons.')).toBeVisible(); await expect(page.getByRole('heading', { name: 'Tes brouillons' })).toBeVisible(); }); test('C8 — multilingue : le texte et l’interface en anglais et en espagnol', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(DOC); await page.getByRole('radiogroup', { name: 'Langue du texte' }).getByRole('radio', { name: 'EN' }).click(); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Certification Commitment Act'); await expect(page.getByText('Open proposals', { exact: false }).first()).toBeVisible(); await page.getByRole('radiogroup', { name: 'Text language' }).getByRole('radio', { name: 'ES' }).click(); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Acta de compromiso de Certificación'); }); test('C9 — les aperçus : bien cotées, et tout', async ({ page }) => { await signIn(page, 'Chiara'); await page.goto(DOC); await page.getByRole('radio', { name: 'Si tout passe' }).click(); await expect(page.getByText("Aperçu : le texte tel qu'il serait si toutes les propositions", { exact: false })).toBeVisible(); await expect(page.locator('section.text article.unit')).toHaveCount(33); await page.getByRole('radio', { name: 'Si les bien cotées passent' }).click(); await expect(page.locator('section.text article.unit')).toHaveCount(3); }); test('C10 — trustinWallet : la gestion des accès est réservée', async ({ browser }) => { const chiara = await person(browser, 'Chiara'); await chiara.goto('/acces'); await expect(chiara.getByText('La gestion des accès est réservée aux administrateurs de trustinWallet.')).toBeVisible(); const yvv = await person(browser, 'Yvv', true); await yvv.goto('/acces'); await expect(yvv.getByRole('heading', { name: 'trustinWallet — accès' })).toBeVisible(); await yvv.getByLabel('Pour qui').fill('Invitée'); await yvv.getByRole('button', { name: "Créer le lien d'invitation" }).click(); await expect(yvv.locator('.link code')).toContainText('/invitation#'); }); test('C11 — une personne, une voix : dans Monnaie libre, seuls les membres de la toile votent', async ({ browser }) => { const yvv = await person(browser, 'Yvv', true); const r = await yvv.request.post('/api/access/invitations', { data: { label: 'Invitée', groups: ['monnaie-libre', 'essai'] }, headers: { 'x-requested-with': 'libredecision' }, }); const { link } = (await r.json()) as { link: string }; const ctx = await browser.newContext({ locale: 'fr-FR' }); const invited = await ctx.newPage(); await invited.goto(`/invitation#${link.split('#')[1]}`); await expect(invited).toHaveURL(/\/groupes$/); await invited.goto(DOC); await invited.locator('ul.deep button.row', { hasText: 'Connaissance suffisante' }).first().click(); const panel = invited.locator('aside.panel'); await expect(panel.getByText('Ici, seuls les membres de la toile de confiance Ğ1 votent', { exact: false })).toBeVisible(); await expect(panel.getByRole('button', { name: 'Pour', exact: true })).toHaveCount(0); // In the test group, the same person votes. await invited.goto(TEST_DOC); await invited.locator('ul.deep button.row', { hasText: 'Connaissance suffisante' }).first().click(); await expect(invited.locator('aside.panel').getByRole('button', { name: 'Pour', exact: true })).toBeVisible(); }); test('C12 — lien d’équipe : chaque personne qui l’ouvre a sa propre identité', async ({ browser }) => { const yvv = await person(browser, 'Yvv', true); await yvv.goto('/acces'); const team = yvv.locator('section', { has: yvv.getByRole('heading', { name: "Liens d'équipe" }) }); await team.getByLabel('Pour quelle équipe').fill('Équipe de test'); await team.getByLabel("Groupe d'essai").check(); await team.getByRole('button', { name: "Créer le lien d'équipe" }).click(); const link = (await team.locator('.link code').textContent()) ?? ''; expect(link).toContain('/equipe#'); for (const name of ['Luna', 'Paul']) { const ctx = await browser.newContext({ locale: 'fr-FR' }); const page = await ctx.newPage(); await page.goto(`/equipe#${link.split('#')[1]}`); await expect(page.getByText('« Équipe de test » t’ouvre', { exact: false })).toBeVisible(); await page.getByLabel('Ton nom').fill(name); await page.getByRole('button', { name: 'Rejoindre' }).click(); await expect(page).toHaveURL(/\/groupes$/); await ctx.close(); } // A shared browser: the person signed in steps aside, the next one gives a name. const shared = await person(browser, 'Zoé'); await shared.goto(`/equipe#${link.split('#')[1]}`); await expect(shared.getByText('Ta session est ouverte sous le nom Zoé', { exact: false })).toBeVisible(); await shared.getByRole('button', { name: "Ce n'est pas moi : me déconnecter" }).click(); await shared.getByLabel('Ton nom').fill('luna'); await shared.getByRole('button', { name: 'Rejoindre' }).click(); await expect(shared.getByRole('alert')).toContainText('Ce nom est déjà pris'); await yvv.reload(); await expect(team.getByText('2 / 30 personnes', { exact: false })).toBeVisible(); await expect(yvv.getByText('venue par « Équipe de test »')).toHaveCount(2); });