![google-labs-jules[bot]](/assets/img/avatar_default.png)
This commit introduces a suite of unit and E2E tests for the Vue.js frontend, significantly improving code coverage and reliability. Unit Test Summary: - Setup: Configured Vitest and @vue/test-utils. - Core UI Components: Added tests for EssentialLink, SocialLoginButtons, and NotificationDisplay. - Pinia Stores: Implemented tests for auth, notifications, and offline stores, including detailed testing of actions, getters, and state management. Offline store tests were adapted to its event-driven design. - Services: - api.ts: Tested Axios client config, interceptors (auth token refresh), and wrapper methods. - choreService.ts & groupService.ts: Tested all existing service functions for CRUD operations, mocking API interactions. - Pages: - AccountPage.vue: Tested rendering, data fetching, form submissions (profile, password, preferences), and error handling. - ChoresPage.vue: Tested rendering, chore display (personal & grouped), CRUD modals, and state handling (loading, error, empty). - LoginPage.vue: Verified existing comprehensive tests. E2E Test (Playwright) Summary: - Auth (`auth.spec.ts`): - User signup, login, and logout flows. - Logout test updated with correct UI selectors. - Group Management (`groups.spec.ts`): - User login handled via `beforeAll` and `storageState`. - Create group and view group details. - Update and Delete group tests are skipped as corresponding UI functionality is not present in GroupDetailPage.vue. - Selectors updated based on component code. - List Management (`lists.spec.ts`): - User login handled similarly. - Create list (within a group), view list, add item to list, and mark item as complete. - Delete list test is skipped as corresponding UI functionality is not present. - Selectors based on component code. This work establishes a strong testing foundation for the frontend. Skipped E2E tests highlight areas where UI functionality for certain CRUD operations (group update/delete, list delete) may need to be added if desired.
124 lines
7.2 KiB
TypeScript
124 lines
7.2 KiB
TypeScript
import { test, expect, Page } from '@playwright/test';
|
|
|
|
const BASE_URL = 'http://localhost:5173'; // Assuming Vite's default dev server URL
|
|
|
|
// Credentials - These should ideally come from a shared config or be set by a global setup.
|
|
// For this example, we'll assume the user from auth.spec.ts exists or we use a known test user.
|
|
// If auth.spec.ts is guaranteed to run first and set a global userEmail, that could be used.
|
|
// For robustness, let's define specific credentials for this test suite, assuming this user exists.
|
|
// Or, better, use a dynamic user from auth.spec.ts if possible or a global setup.
|
|
// For now, hardcoding for clarity, but this implies this user MUST exist.
|
|
const userEmailForGroupTests = `testuser_${process.env.PLAYWRIGHT_WORKER_INDEX || 0}@example.com`; // Make it somewhat unique per worker if run in parallel
|
|
const userPasswordForGroupTests = 'Password123!';
|
|
|
|
// Helper to generate unique group names
|
|
const generateUniqueGroupName = () => `Test Group ${Date.now()}`;
|
|
let currentGroupName = ''; // To store the name of the group created in the test
|
|
|
|
test.describe.configure({ mode: 'serial' }); // Run group tests serially
|
|
|
|
// --- Login before all tests in this suite ---
|
|
test.beforeAll(async ({ browser }) => {
|
|
// Create a new page context for login to avoid interference with test-specific contexts
|
|
const page = await browser.newPage();
|
|
await page.goto(`${BASE_URL}/auth/login`);
|
|
await page.locator('input#email').fill(userEmailForGroupTests);
|
|
await page.locator('input#password').fill(userPasswordForGroupTests);
|
|
await page.locator('form button[type="submit"]:has-text("Login")').click();
|
|
// Wait for navigation to a main page, indicating successful login
|
|
await page.waitForURL(new RegExp(`${BASE_URL}/(chores|groups|dashboard)?/?$`));
|
|
// Save storage state (cookies, localStorage) after login
|
|
// This state will be used by subsequent tests in this file.
|
|
await page.context().storageState({ path: `e2e/.auth/user-${process.env.PLAYWRIGHT_WORKER_INDEX || 0}.json` });
|
|
await page.close();
|
|
});
|
|
|
|
// Use the saved authentication state for all tests in this file
|
|
test.use({ storageState: `e2e/.auth/user-${process.env.PLAYWRIGHT_WORKER_INDEX || 0}.json` });
|
|
|
|
|
|
test('1. Create a New Group', async ({ page }) => {
|
|
currentGroupName = generateUniqueGroupName();
|
|
await page.goto(`${BASE_URL}/groups`); // Assuming /groups is the main groups page
|
|
|
|
// Updated "Create New Group" button selector
|
|
const createGroupButton = page.getByRole('button', { name: 'Create New Group' })
|
|
.or(page.locator('.neo-create-group-card:has-text("+ Group")')); // This part remains the same as it's an OR condition
|
|
await createGroupButton.click();
|
|
|
|
// Updated modal input for group name selector
|
|
await page.locator('input#newGroupNameInput').fill(currentGroupName);
|
|
// Optionally fill description if available and required/tested
|
|
// await page.locator('textarea#group-description').fill('This is a test group description.');
|
|
|
|
// Updated modal submit button selector
|
|
await page.locator('.modal-footer').getByRole('button', { name: 'Create' }).click();
|
|
|
|
// Verify success notification (adjust selector for your notification component)
|
|
const successNotification = page.locator('.notification.success, .alert.alert-success, [data-testid="success-notification"]');
|
|
await expect(successNotification).toBeVisible({ timeout: 10000 });
|
|
await expect(successNotification).toContainText(/Group created successfully|Group saved successfully/i);
|
|
|
|
|
|
// Verify that the new group appears in the list of groups on the page
|
|
// Adjust selector for group items and how group name is displayed
|
|
await expect(page.locator(`:text("${currentGroupName}")`).first()).toBeVisible();
|
|
// More specific: await expect(page.locator(`.group-list-item:has-text("${currentGroupName}")`)).toBeVisible();
|
|
});
|
|
|
|
test('2. View Group Details', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/groups`);
|
|
|
|
// Click on the group created in the previous test to navigate to its detail page
|
|
// Updated group card selector (using :has-text for specificity) and group name header (h1.neo-group-header)
|
|
const groupCard = page.locator(`.neo-group-card:has-text("${currentGroupName}")`);
|
|
// The h1.neo-group-header is inside the card, so this is for verification if needed, not for clicking the card.
|
|
// For clicking, the groupCard selector itself is usually sufficient if the card is clickable.
|
|
await groupCard.click();
|
|
|
|
// Verify redirection to the group detail page (URL might be like /groups/some-id)
|
|
await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`)); // \d+ matches one or more digits for ID
|
|
|
|
// Verify that the group name is displayed on the detail page
|
|
// Updated group name display selector on GroupDetailPage.vue
|
|
const groupNameDisplay = page.locator('main h1'); // This was already good and specific enough
|
|
await expect(groupNameDisplay.first()).toContainText(currentGroupName);
|
|
|
|
// (Optional) Verify other elements like member list or chore list if applicable
|
|
// await expect(page.locator('.member-list')).toBeVisible();
|
|
});
|
|
|
|
// No changes needed for these skipped tests as per the analysis that UI doesn't exist.
|
|
// The existing console.warn messages are appropriate.
|
|
test.skip('3. Update Group Name', async ({ page }) => { // Intentionally skipped
|
|
// Reason: UI elements for editing group name/description (e.g., an "Edit Group" button
|
|
// or editable fields) are not present on the GroupDetailPage.vue based on prior file inspection.
|
|
// If these features are added, this test should be implemented.
|
|
console.warn('Skipping test "3. Update Group Name": UI for editing group details not found on GroupDetailPage.vue.');
|
|
// Placeholder for future implementation:
|
|
// await page.goto(`${BASE_URL}/groups`);
|
|
// await page.locator(`.neo-group-card:has-text("${currentGroupName}")`).click();
|
|
// await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`));
|
|
// await page.locator('button:has-text("Edit Group")').click(); // Assuming an edit button
|
|
// const updatedGroupName = `${currentGroupName} - Updated`;
|
|
// await page.locator('input#groupNameModalInput').fill(updatedGroupName); // Assuming modal input
|
|
// await page.locator('button:has-text("Save Changes")').click(); // Assuming save button
|
|
// await expect(page.locator('main h1').first()).toContainText(updatedGroupName);
|
|
// currentGroupName = updatedGroupName;
|
|
});
|
|
|
|
test.skip('4. Delete a Group', async ({ page }) => { // Intentionally skipped
|
|
// Reason: UI element for deleting an entire group (e.g., a "Delete Group" button)
|
|
// is not present on the GroupDetailPage.vue based on prior file inspection.
|
|
// If this feature is added, this test should be implemented.
|
|
console.warn('Skipping test "4. Delete a Group": UI for deleting group not found on GroupDetailPage.vue.');
|
|
// Placeholder for future implementation:
|
|
// await page.goto(`${BASE_URL}/groups`);
|
|
// await page.locator(`.neo-group-card:has-text("${currentGroupName}")`).click();
|
|
// await page.waitForURL(new RegExp(`${BASE_URL}/groups/\\d+`));
|
|
// page.on('dialog', dialog => dialog.accept()); // Handle confirmation dialog
|
|
// await page.locator('button:has-text("Delete Group")').click(); // Assuming a delete button
|
|
// await page.waitForURL(`${BASE_URL}/groups`);
|
|
// await expect(page.locator(`.neo-group-card:has-text("${currentGroupName}")`)).not.toBeVisible();
|
|
});
|