diff --git a/apps/web/e2e/fasting-tracking.spec.ts b/apps/web/e2e/fasting-tracking.spec.ts new file mode 100644 index 0000000..4367d71 --- /dev/null +++ b/apps/web/e2e/fasting-tracking.spec.ts @@ -0,0 +1,78 @@ +import { expect, test, type Page } from '@playwright/test'; + +function uniqueEmail(prefix: string): string { + const ts = Date.now(); + const rand = Math.floor(Math.random() * 1e6); + return `e2e-${prefix}-${ts}-${rand}@ketopath.test`; +} + +async function signupOnboarded(page: Page, email: string): Promise { + const password = 'Password!123'; + await page.goto('/sign-up'); + await page.getByLabel('Come ti chiami').fill('FT E2E'); + await page.getByLabel('Email').fill(email); + await page.getByLabel('Password').fill(password); + await page.getByRole('button', { name: 'Crea account' }).click(); + await page.waitForURL('/'); + + await page.goto('/welcome'); + await page.getByText(/Confermo di essere maggiorenne/).click(); + await page.getByText(/Dichiaro di non avere condizioni mediche escludenti/).click(); + await page.getByText(/Accetto che KetoPath non sostituisce/).click(); + await page.getByRole('button', { name: 'Accetto e proseguo' }).click(); + await page.waitForURL('/onboarding'); + await page.getByRole('button', { name: /Perdere peso/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + await page.getByLabel('Età').fill('45'); + await page.getByLabel('Sesso').click(); + await page.getByRole('option', { name: 'Maschio' }).click(); + await page.getByLabel('Altezza (cm)').fill('175'); + await page.getByLabel('Iniziale').fill('80'); + await page.getByLabel('Attuale').fill('80'); + await page.getByLabel('Obiettivo').fill('75'); + await page.getByLabel('Livello di attività').click(); + await page.getByRole('option', { name: /Sedentario/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); +} + +test.describe('fasting flow', () => { + test('start a 16:8 fast then abort it', async ({ page }) => { + test.setTimeout(60_000); + await signupOnboarded(page, uniqueEmail('fasting')); + + await page.goto('/fasting'); + // Default protocollo è 16:8 — clicca "Avvia digiuno" + await page.getByRole('button', { name: 'Avvia digiuno' }).click(); + + // Live timer dovrebbe apparire (00:00:0X) + await expect(page.locator('text=/^\\d\\d:\\d\\d:\\d\\d$/').first()).toBeVisible(); + // Diario sintomi presente + await expect(page.getByText('Come ti senti?')).toBeVisible(); + + // Annulla la sessione (dialog confirm → accept) + page.once('dialog', (d) => { + void d.accept(); + }); + await page.getByRole('button', { name: 'Annulla sessione' }).click(); + // Dopo l'abort si torna alla schermata di avvio + await expect(page.getByRole('button', { name: 'Avvia digiuno' })).toBeVisible(); + }); +}); + +test.describe('weight tracking flow', () => { + test('inserire una pesata + apparire nella history', async ({ page }) => { + test.setTimeout(60_000); + await signupOnboarded(page, uniqueEmail('tracking')); + + await page.goto('/tracking'); + + // Form pesata + await page.getByLabel('Peso (kg)').fill('80'); + await page.getByRole('button', { name: 'Registra', exact: true }).click(); + + // "Peso attuale" + "80.0kg" appaiono nella history + await expect(page.getByText('Peso attuale')).toBeVisible(); + await expect(page.getByText(/80\.0\s*kg/).first()).toBeVisible(); + }); +}); diff --git a/apps/web/e2e/onboarding.spec.ts b/apps/web/e2e/onboarding.spec.ts new file mode 100644 index 0000000..036464c --- /dev/null +++ b/apps/web/e2e/onboarding.spec.ts @@ -0,0 +1,63 @@ +import { expect, test } from '@playwright/test'; + +function uniqueEmail(): string { + const ts = Date.now(); + const rand = Math.floor(Math.random() * 1e6); + return `e2e-onboarding-${ts}-${rand}@ketopath.test`; +} + +test.describe('onboarding flow', () => { + test('signup → welcome → onboarding goal+profile → BMR/TDEE saved', async ({ page }) => { + const email = uniqueEmail(); + const password = 'Password!123'; + const name = 'Michele E2E'; + + // Sign-up + await page.goto('/sign-up'); + await page.getByLabel('Come ti chiami').fill(name); + await page.getByLabel('Email').fill(email); + await page.getByLabel('Password').fill(password); + await page.getByRole('button', { name: 'Crea account' }).click(); + await page.waitForURL('/'); + + // Apri "Profilo & calcoli" → redirige a /welcome (disclaimer) + await page.getByRole('link', { name: 'Profilo & calcoli' }).click(); + await page.waitForURL('/welcome'); + + // Disclaimer medico (3 checkbox + accetta) + await page.getByText(/Confermo di essere maggiorenne/).click(); + await page.getByText(/Dichiaro di non avere condizioni mediche escludenti/).click(); + await page.getByText(/Accetto che KetoPath non sostituisce/).click(); + await page.getByRole('button', { name: 'Accetto e proseguo' }).click(); + + // Welcome accept → /onboarding + await page.waitForURL('/onboarding'); + + // Step I: goal — scegli "Perdere peso" + await page.getByRole('button', { name: /Perdere peso/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + + // Step II: profile (form Michele del PRD) + await page.getByLabel('Età').fill('49'); + await page.getByLabel('Sesso').click(); + await page.getByRole('option', { name: 'Maschio' }).click(); + await page.getByLabel('Altezza (cm)').fill('170'); + await page.getByLabel('Iniziale').fill('76'); + await page.getByLabel('Attuale').fill('76'); + await page.getByLabel('Obiettivo').fill('70'); + await page.getByLabel('Livello di attività').click(); + await page.getByRole('option', { name: /Sedentario/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + + // Step III: condizioni mediche — niente da selezionare, vai avanti + await page.getByRole('button', { name: 'Salva e continua' }).click(); + + // Verifica che il profilo sia stato persistito visitando /profile + await page.goto('/profile'); + await expect(page.getByText('Calcolo personalizzato')).toBeVisible(); + // BMR Michele Mifflin-St Jeor: 10*76 + 6.25*170 - 5*49 + 5 = 1582.5 → 1583 + await expect(page.getByText('1583', { exact: true })).toBeVisible(); + // TDEE = 1582.5 * 1.2 (sedentario) = 1899 + await expect(page.getByText('1899', { exact: true })).toBeVisible(); + }); +}); diff --git a/apps/web/e2e/plan.spec.ts b/apps/web/e2e/plan.spec.ts new file mode 100644 index 0000000..91d71d3 --- /dev/null +++ b/apps/web/e2e/plan.spec.ts @@ -0,0 +1,77 @@ +import { expect, test, type Page } from '@playwright/test'; + +function uniqueEmail(): string { + const ts = Date.now(); + const rand = Math.floor(Math.random() * 1e6); + return `e2e-plan-${ts}-${rand}@ketopath.test`; +} + +async function createOnboardedUser(page: Page): Promise<{ email: string }> { + const email = uniqueEmail(); + const password = 'Password!123'; + await page.goto('/sign-up'); + await page.getByLabel('Come ti chiami').fill('Plan E2E'); + await page.getByLabel('Email').fill(email); + await page.getByLabel('Password').fill(password); + await page.getByRole('button', { name: 'Crea account' }).click(); + await page.waitForURL('/'); + + await page.goto('/welcome'); + await page.getByText(/Confermo di essere maggiorenne/).click(); + await page.getByText(/Dichiaro di non avere condizioni mediche escludenti/).click(); + await page.getByText(/Accetto che KetoPath non sostituisce/).click(); + await page.getByRole('button', { name: 'Accetto e proseguo' }).click(); + await page.waitForURL('/onboarding'); + + await page.getByRole('button', { name: /Perdere peso/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + await page.getByLabel('Età').fill('49'); + await page.getByLabel('Sesso').click(); + await page.getByRole('option', { name: 'Maschio' }).click(); + await page.getByLabel('Altezza (cm)').fill('170'); + await page.getByLabel('Iniziale').fill('76'); + await page.getByLabel('Attuale').fill('76'); + await page.getByLabel('Obiettivo').fill('70'); + await page.getByLabel('Livello di attività').click(); + await page.getByRole('option', { name: /Sedentario/ }).click(); + await page.getByRole('button', { name: 'Salva e continua' }).click(); + // Step condizioni mediche + await page.getByRole('button', { name: 'Salva e continua' }).click(); + return { email }; +} + +test.describe('weekly meal plan', () => { + test('genera piano + verifica presenza pasti + macros giornalieri', async ({ page }) => { + test.setTimeout(60_000); + await createOnboardedUser(page); + + await page.goto('/plan'); + await page.getByRole('button', { name: 'Genera piano' }).click(); + + // Dopo generazione, l'header del piano mostra "Lista della spesa" link + await expect(page.getByRole('link', { name: /Lista della spesa/ })).toBeVisible(); + + // Almeno un pasto Colazione è presente + await expect(page.getByText('Colazione').first()).toBeVisible(); + await expect(page.getByText('Pranzo').first()).toBeVisible(); + await expect(page.getByText('Cena').first()).toBeVisible(); + + // Header giornaliero include "kcal · P · G · C" + await expect(page.locator('text=/\\d+ kcal · P \\d+ · G \\d+ · C \\d+/').first()).toBeVisible(); + }); + + test('aderenza badge appare dopo aver segnato un pasto come consumato', async ({ page }) => { + test.setTimeout(60_000); + await createOnboardedUser(page); + await page.goto('/plan'); + await page.getByRole('button', { name: 'Genera piano' }).click(); + // Wait for plan to render + await expect(page.getByText('Colazione').first()).toBeVisible(); + + // Almeno un toggle ✓ esiste in pagina + const consumeButtons = page.getByRole('button', { + name: /Segna come consumato|Annulla consumato/, + }); + await expect(consumeButtons.first()).toBeVisible(); + }); +}); diff --git a/apps/web/e2e/profile.spec.ts b/apps/web/e2e/profile.spec.ts deleted file mode 100644 index b1bfd12..0000000 --- a/apps/web/e2e/profile.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect, test } from '@playwright/test'; - -function uniqueEmail(): string { - const ts = Date.now(); - const rand = Math.floor(Math.random() * 1e6); - return `e2e-profile-${ts}-${rand}@ketopath.test`; -} - -test('profile flow — submit returns BMR/TDEE for the Michele PRD persona', async ({ page }) => { - const email = uniqueEmail(); - const password = 'Password!123'; - const name = 'Michele E2E'; - - // Sign-up gives us an authenticated session - await page.goto('/sign-up'); - await page.getByLabel('Come ti chiami').fill(name); - await page.getByLabel('Email').fill(email); - await page.getByLabel('Password').fill(password); - await page.getByRole('button', { name: 'Crea account' }).click(); - await page.waitForURL('/'); - - // Open profile from home — first time we'll be redirected through /welcome - await page.getByRole('link', { name: 'Profilo & calcoli' }).click(); - await page.waitForURL('/welcome'); - - // Accept the medical disclaimer (3 mandatory checkboxes + submit) - await page.getByText(/Confermo di essere maggiorenne/).click(); - await page.getByText(/Dichiaro di non avere condizioni mediche escludenti/).click(); - await page.getByText(/Accetto che KetoPath non sostituisce/).click(); - await page.getByRole('button', { name: 'Accetto e proseguo' }).click(); - await page.waitForURL('/profile'); - - // Fill the form with the "Michele" PRD persona values - await page.getByLabel('Età').fill('49'); - await page.getByLabel('Sesso').click(); - await page.getByRole('option', { name: 'Maschio' }).click(); - await page.getByLabel('Altezza (cm)').fill('170'); - await page.getByLabel('Iniziale').fill('76'); - await page.getByLabel('Attuale').fill('76'); - await page.getByLabel('Obiettivo').fill('70'); - await page.getByLabel('Livello di attività').click(); - await page.getByRole('option', { name: /Sedentario/ }).click(); - await page.getByRole('button', { name: 'Salva profilo' }).click(); - - // BMR/TDEE summary panel becomes visible after a successful save - await expect(page.getByText('Calcolo personalizzato')).toBeVisible(); - // Numbers are rendered in monospace next to a separate "kcal" unit span, - // so check for the bare numeric values. - await expect(page.getByText('1583', { exact: true })).toBeVisible(); - await expect(page.getByText('1899', { exact: true })).toBeVisible(); -});