import { test, expect, Page } from '@playwright/test'; const BASE_URL = 'http://localhost:5173'; // Credentials & Group Info - These should align with what groups.spec.ts uses/creates // or be from a dedicated test setup. For serial execution, we can assume groups.spec.ts ran. // However, for robustness, especially if these files can run independently or in different orders, // this shared state is problematic. // A better approach: // 1. Global setup that creates a user and a group, storing their IDs/names. // 2. This file fetches that info or uses fixed, known test data. // For this task, we'll assume groups.spec.ts has run and created a group. // We'll need to know the name of that group. // Let's define them here, assuming this user and group exist. const userEmailForListTests = `testuser_${process.env.PLAYWRIGHT_WORKER_INDEX || 0}@example.com`; // Must match user from groups.spec.ts's login const userPasswordForListTests = 'Password123!'; // Must match let groupNameForListTests = `Test Group ${process.env.PLAYWRIGHT_GROUP_TIMESTAMP || Date.now()}`; // This needs to be the group name from groups.spec.ts let createdListId = ''; // To store the ID of the list created let createdListName = ''; // Helper to generate unique list/item names const generateUniqueName = (prefix: string) => `${prefix} ${Date.now()}`; test.describe.configure({ mode: 'serial' }); // --- Login before all tests in this suite --- // This re-uses the login logic from groups.spec.ts. // It's better to have a shared login function or rely on global setup. test.beforeAll(async ({ browser }) => { const page = await browser.newPage(); await page.goto(`${BASE_URL}/auth/login`); await page.locator('input#email').fill(userEmailForListTests); await page.locator('input#password').fill(userPasswordForListTests); await page.locator('form button[type="submit"]:has-text("Login")').click(); await page.waitForURL(new RegExp(`${BASE_URL}/(chores|groups|dashboard)?/?$`)); await page.context().storageState({ path: `e2e/.auth/list-user-${process.env.PLAYWRIGHT_WORKER_INDEX || 0}.json` }); // After login, ensure the target group exists or create it. // For simplicity, we'll assume groups.spec.ts created a group. // We need its name. A robust way is to query the API or have a fixed test group. // For now, we'll try to use a dynamically set groupName during the first test if possible, // or rely on a known naming pattern if groups.spec.ts uses Date.now() in a predictable way. // This is a known limitation of inter-file dependencies without a proper global setup. // The `groupNameForListTests` will be updated by the first list test if it creates a new group for lists. // Alternatively, fetch the first group name from the page. await page.goto(`${BASE_URL}/groups`); const firstGroupCard = page.locator('.neo-group-card h1.neo-group-header').first(); if (await firstGroupCard.isVisible()) { const name = await firstGroupCard.textContent(); if (name) groupNameForListTests = name.trim(); else console.warn("Could not determine group name for list tests, using default or generated."); } else { console.warn("No groups found for list tests, creating a new one might be needed or tests will fail."); // If no groups, these tests might not be able to proceed correctly. // For now, we'll assume `groupNameForListTests` is somewhat valid or will be set. } console.log(`Using group: "${groupNameForListTests}" for list tests.`); await page.close(); }); test.use({ storageState: `e2e/.auth/list-user-${process.env.PLAYWRIGHT_WORKER_INDEX || 0}.json` }); test('1. Create a New List within a Group', async ({ page }) => { createdListName = generateUniqueName('My Test List'); await page.goto(`${BASE_URL}/groups`); // Find the specific group card const groupCard = page.locator(`.neo-group-card:has-text("${groupNameForListTests}")`); if (!(await groupCard.isVisible())) { throw new Error(`Group card for "${groupNameForListTests}" not found. Ensure this group exists and was created by groups.spec.ts or a setup step.`); } // Click the "List" button on that group card to open CreateListModal await groupCard.getByRole('button', { name: 'List' }).click(); // CreateListModal.vue interaction await expect(page.locator('input#listName')).toBeVisible(); await page.locator('input#listName').fill(createdListName); // Group should be pre-selected if modal is opened from group context. // We can verify this if the select#selectedGroup is disabled or has the correct value. // For now, assume it's correctly handled by the component. await page.locator('.modal-footer button[type="submit"]:has-text("Create")').click(); // Verify success notification const successNotification = page.locator('.notification.success, .alert.alert-success, [data-testid="success-notification"]'); await expect(successNotification).toBeVisible({ timeout: 10000 }); await expect(successNotification).toContainText(/List created successfully/i); // Verify the new list appears on the GroupDetailPage.vue (within the embedded ListsPage.vue section) // After list creation, user is usually redirected to the group detail page or the list page itself. // Let's assume redirection to Group Detail Page, then find the list. // The URL should be /groups/:id await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); // ListsPage.vue is embedded. We need to find the list name. // Assuming ListsPage renders list names within elements having a class like '.list-name' or similar. // Or, if ListsPage uses cards similar to GroupsPage: const newListInGroupPage = page.locator(`.list-card-link:has-text("${createdListName}"), .list-group-item:has-text("${createdListName}")`).first(); await expect(newListInGroupPage).toBeVisible(); // Store list ID for next tests by capturing it from URL or an element attribute // For example, if clicking the list navigates to /groups/:gid/lists/:listId await newListInGroupPage.click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+/lists/\\d+`)); const url = page.url(); const match = url.match(/lists\/(\d+)/); if (match && match[1]) { createdListId = match[1]; } else { throw new Error('Could not extract list ID from URL after creation.'); } expect(createdListId).toBeTruthy(); }); test('2. View a List and its (empty) items', async ({ page }) => { expect(createdListId, "List ID must be set from previous test").toBeTruthy(); expect(createdListName, "List Name must be set from previous test").toBeTruthy(); // Navigate directly to the list page (or click through from group detail if preferred) await page.goto(`${BASE_URL}/groups/some-group-id/lists/${createdListId}`); // some-group-id needs to be the actual group ID // Since group ID isn't easily passed from previous test, we'll navigate from groups page: await page.goto(`${BASE_URL}/groups`); const groupCard = page.locator(`.neo-group-card:has-text("${groupNameForListTests}")`); await groupCard.click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); const listLink = page.locator(`.list-card-link:has-text("${createdListName}"), .list-group-item:has-text("${createdListName}")`).first(); await listLink.click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+/lists/${createdListId}`)); // Verify the list name is displayed (from ListDetailPage.vue) const listNameDisplay = page.locator('h1.neo-title'); await expect(listNameDisplay).toContainText(createdListName); // Verify items within the list are displayed (should be empty initially) // From ListDetailPage.vue: "No Items Yet!" message await expect(page.locator('.neo-empty-state:has-text("No Items Yet!")')).toBeVisible(); }); test('3. Add an Item to a List', async ({ page }) => { expect(createdListId).toBeTruthy(); expect(createdListName).toBeTruthy(); // Navigate to the list detail page await page.goto(`${BASE_URL}/groups/some-group-id/lists/${createdListId}`); // Replace some-group-id // Simplified navigation: await page.goto(`${BASE_URL}/groups`); await page.locator(`.neo-group-card:has-text("${groupNameForListTests}")`).click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); await page.locator(`.list-card-link:has-text("${createdListName}"), .list-group-item:has-text("${createdListName}")`).first().click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+/lists/${createdListId}`)); const newItemName = generateUniqueName('Test Item'); // Add item using the form (from ListDetailPage.vue) await page.locator('input.neo-new-item-input[placeholder="Add a new item"]').fill(newItemName); await page.locator('button.neo-add-button:has-text("Add")').click(); // Verify success notification (if any - ListDetailPage adds item directly to list.value.items) // The component doesn't seem to show a notification for item add, but for other actions. // We'll verify the item appears. // Verify the item appears in the list const newItemInList = page.locator(`.neo-item .neo-item-name:has-text("${newItemName}")`); await expect(newItemInList).toBeVisible(); }); test('4. Mark an Item as Completed', async ({ page }) => { expect(createdListId).toBeTruthy(); expect(createdListName).toBeTruthy(); // Navigate to the list detail page await page.goto(`${BASE_URL}/groups`); await page.locator(`.neo-group-card:has-text("${groupNameForListTests}")`).click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); await page.locator(`.list-card-link:has-text("${createdListName}"), .list-group-item:has-text("${createdListName}")`).first().click(); await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+/lists/${createdListId}`)); // Assuming the item added in the previous test is the first one. const firstItem = page.locator('.neo-item').first(); const itemNameElement = firstItem.locator('.neo-item-name'); const itemName = await itemNameElement.textContent(); // Get name for confirmation dialog const checkbox = firstItem.locator('input[type="checkbox"]'); await checkbox.check(); // This will trigger @change which calls confirmUpdateItem // Handle confirmation dialog (from ListDetailPage.vue) await expect(page.locator('.modal-container.confirm-modal')).toBeVisible(); await expect(page.locator('.modal-body')).toContainText(`Mark "${itemName}" as complete?`); await page.locator('.modal-footer button:has-text("Confirm")').click(); // Verify the item's appearance changes (e.g., strikethrough, class 'neo-item-complete') await expect(firstItem).toHaveClass(/neo-item-complete/); // Also check if checkbox is now checked (it should be due to optimistic update + confirmation) await expect(checkbox).toBeChecked(); // Optionally, verify success notification if one is shown for item update. // The component's updateItem doesn't show a notification on success currently. }); test('5. Delete a List', async ({ page }) => { // Based on ListDetailPage.vue analysis, there is no "Delete List" button. // This test needs to be skipped unless the UI for deleting a list is found elsewhere // or added to ListDetailPage.vue. test.skip(true, "UI for deleting a list is not present on ListDetailPage.vue or ListsPage.vue based on current analysis."); console.warn('Skipping test "5. Delete a List": UI for deleting a list not found.'); // Placeholder for future implementation: // expect(createdListId).toBeTruthy(); // expect(createdListName).toBeTruthy(); // Navigate to where delete button would be (e.g., group detail page or list detail page) // await page.goto(`${BASE_URL}/groups`); // Or directly to list page if delete is there // ... click through to the list or group page ... // const deleteButton = page.locator('button:has-text("Delete List")'); // Selector for delete list button // await deleteButton.click(); // Handle confirmation dialog // page.on('dialog', dialog => dialog.accept()); // For browser confirm // Or: await page.locator('.confirm-delete-modal button:has-text("Confirm")').click(); // For custom modal // Verify success notification // const successNotification = page.locator('.notification.success, .alert.alert-success, [data-testid="success-notification"]'); // await expect(successNotification).toBeVisible({ timeout: 10000 }); // await expect(successNotification).toContainText(/List deleted successfully/i); // Verify the list is removed (e.g., from GroupDetailPage or main lists page) // await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); // Assuming redirect to group detail // await expect(page.locator(`.list-card-link:has-text("${createdListName}")`)).not.toBeVisible(); });