import { test, expect } from '@playwright/test'; import { resetDatabase } from './helpers'; test.describe('Full MVP Specification Compliance Journey (@spec.md Section 6)', () => { test.beforeEach(async () => { await resetDatabase(); }); test('end-to-end acceptance flow validating all Section 6 criteria', async ({ page }) => { const timestamp = Date.now(); const userEmail = `auditor_${timestamp}@example.com`; const userName = `Auditor ${timestamp}`; const groupName = `Audited Home Group ${timestamp}`; const renewalName = `Vehicle Fleet Registration ${timestamp}`; const maintenanceName = `Solar Inverter Servicing ${timestamp}`; const warrantyName = `Heat Pump Warranty ${timestamp}`; // ---------------------------------------------------- // 1. Register and log in with email and password // ---------------------------------------------------- await page.goto('/'); await page.waitForLoadState('networkidle'); // Open user menu -> Switch or Sign In User const userBtn = page.locator('header').locator('button').filter({ hasText: /Alex Taylor|Sarah|Account/i }).first(); await userBtn.click(); await page.getByRole('button', { name: /Switch or Sign In User/i }).click(); const authModal = page.locator('.fixed.inset-0').filter({ hasText: /Sign In|Create New Account/i }); await expect(authModal).toBeVisible(); // Sign up await authModal.getByRole('button', { name: 'Sign Up', exact: true }).click(); await authModal.locator('#auth-name').fill(userName); await authModal.locator('#auth-email').fill(userEmail); await authModal.locator('#auth-password').fill('pass12345'); await authModal.getByRole('button', { name: 'Create Account' }).click(); await expect(authModal).toBeHidden(); await expect(page.locator('header').getByText(userName)).toBeVisible(); // ---------------------------------------------------- // 2. Create a group and invite members by email // ---------------------------------------------------- await page.getByRole('button', { name: 'Groups' }).click(); await expect(page.getByRole('heading', { name: 'Groups & Shared Access' })).toBeVisible(); const groupInput = page.locator('input[placeholder="e.g. Oakridge Family & Home"]'); await groupInput.fill(groupName); await page.getByRole('button', { name: 'Create Group' }).click(); const groupCard = page.locator('.bg-white').filter({ hasText: groupName }); await expect(groupCard).toBeVisible(); // Invite member const invitee = `technician_${timestamp}@example.com`; await groupCard.locator('input[type="email"]').fill(invitee); await groupCard.getByRole('button', { name: 'Invite' }).click(); await expect(groupCard.getByText(invitee)).toBeVisible(); // ---------------------------------------------------- // 3. Create renewal, maintenance, and warranty items using a guided form // with common fields (Name, Type, Due date, Status, Group) // ---------------------------------------------------- // 3a. Create Renewal (due in 15 days -> upcoming) await page.getByRole('button', { name: /Track Item/i }).click(); const createModal = page.locator('.fixed.inset-0').filter({ hasText: /Step [12]:/i }); await expect(createModal).toBeVisible(); await createModal.getByRole('button', { name: /Renewal/i }).click(); await page.locator('#item-name').fill(renewalName); const date15 = new Date(); date15.setDate(date15.getDate() + 15); await page.locator('#item-due-date').fill(date15.toISOString().slice(0, 10)); await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click(); await expect(createModal).toBeHidden(); // 3b. Create Maintenance (due 5 days ago -> overdue) await page.getByRole('button', { name: /Track Item/i }).click(); await createModal.getByRole('button', { name: /Maintenance/i }).click(); await page.locator('#item-name').fill(maintenanceName); const datePast = new Date(); datePast.setDate(datePast.getDate() - 5); await page.locator('#item-due-date').fill(datePast.toISOString().slice(0, 10)); // Assign to group await page.locator('#item-group').selectOption({ label: `Group: ${groupName}` }); await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click(); await expect(createModal).toBeHidden(); // 3c. Create Warranty without due date (Decision 29) await page.getByRole('button', { name: /Track Item/i }).click(); await createModal.getByRole('button', { name: /Warranty/i }).click(); await page.locator('#item-name').fill(warrantyName); await page.locator('.fixed.inset-0 input[type="checkbox"]').uncheck(); await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click(); await expect(createModal).toBeHidden(); // ---------------------------------------------------- // 4. View items in lists separated by lifecycle type // ---------------------------------------------------- // Renewals view await page.getByRole('button', { name: 'Renewals' }).click(); await expect(page.locator(`text=${renewalName}`).first()).toBeVisible(); // Maintenance view await page.getByRole('button', { name: 'Maintenance' }).click(); await expect(page.locator(`text=${maintenanceName}`).first()).toBeVisible(); // Warranties view await page.getByRole('button', { name: 'Warranties' }).click(); await expect(page.locator(`text=${warrantyName}`).first()).toBeVisible(); // ---------------------------------------------------- // 5. See upcoming actions and overdue items on Dashboard // ---------------------------------------------------- await page.getByRole('button', { name: 'Dashboard' }).click(); // Overdue section must contain Maintenance const overdueSection = page.locator('section').filter({ hasText: /Overdue Items/i }); await expect(overdueSection.getByText(maintenanceName)).toBeVisible(); // Upcoming section must contain Renewal const upcomingSection = page.locator('section').filter({ hasText: /Upcoming Actions/i }); await expect(upcomingSection.getByText(renewalName)).toBeVisible(); // ---------------------------------------------------- // 6. Mark an item complete while retaining it in the main list // ---------------------------------------------------- const overdueRow = overdueSection.locator('.group').filter({ hasText: maintenanceName }); await overdueRow.getByRole('button', { name: /Mark as complete/i }).click(); // Retained in main list under Completed Records section with line-through const completedSection = page.locator('section').filter({ hasText: /Completed Records/i }); await expect(completedSection.getByText(maintenanceName)).toBeVisible(); await expect(completedSection.locator('.group').filter({ hasText: maintenanceName }).locator('span.line-through')).toContainText(maintenanceName); // ---------------------------------------------------- // 7. Receive in-app reminders and daily email digest // ---------------------------------------------------- // In-app reminders const bellBtn = page.getByRole('button', { name: /Open Reminders/i }); await bellBtn.click(); const reminderDrawer = page.locator('.fixed.inset-0').filter({ hasText: /In-App Reminders/i }); await expect(reminderDrawer).toBeVisible(); await expect(reminderDrawer.getByText(renewalName)).toBeVisible(); await reminderDrawer.getByRole('button', { name: /Close drawer/i }).click(); // Daily digest await page.getByRole('button', { name: 'Daily Digest', exact: true }).click(); await expect(page.getByText('Dual Channel Enabled')).toBeVisible(); // Configure delivery time await page.locator('button').filter({ hasText: 'Option C' }).first().click(); await expect(page.getByText('Saved')).toBeVisible(); // Simulate digest await page.getByRole('button', { name: /Simulate \/ Send Digest Now/i }).click(); await expect(page.getByText('Email Digest Preview')).toBeVisible(); await expect(page.getByText(/\[Action Required\]|\[Upcoming Actions\]|\[Lifecycle Tracker\]/i).first()).toBeVisible(); // ---------------------------------------------------- // 8. Permanently delete item, group, and account // ---------------------------------------------------- // Delete item await page.getByRole('button', { name: 'Renewals' }).click(); const renewalRow = page.locator('.group').filter({ hasText: renewalName }); page.once('dialog', async (dialog) => { await dialog.accept(); }); await renewalRow.getByRole('button', { name: /Permanently delete item/i }).click(); await expect(page.locator('.group').filter({ hasText: renewalName })).toHaveCount(0); // Delete group await page.getByRole('button', { name: 'Groups' }).click(); const groupToDelete = page.locator('.bg-white').filter({ hasText: groupName }); page.once('dialog', async (dialog) => { await dialog.accept(); }); await groupToDelete.getByRole('button', { name: 'Delete group' }).click(); await expect(page.locator('.bg-white').filter({ hasText: groupName })).toHaveCount(0); // Delete account const finalProfileBtn = page.locator('header').locator('button').filter({ hasText: userName }); await finalProfileBtn.click(); page.once('dialog', async (dialog) => { await dialog.accept(); }); await page.getByRole('button', { name: /Delete Account/i }).click(); // Verified account deletion completed and redirected/reset await expect(page.locator('header').getByText(userName)).toHaveCount(0); }); });