Add more e2e tests (#299)
* Add inbox tests * Add tasks e2e tests * Add projects e2e tests * Add area e2e tests * Add note e2e tests * Add tags e2e tests
This commit is contained in:
parent
699a118bb6
commit
7d34694c83
7 changed files with 1018 additions and 0 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -38,3 +38,7 @@ public/assets/
|
|||
|
||||
# Webpack cache
|
||||
.webpack/
|
||||
|
||||
# Playwright test results
|
||||
test-results/
|
||||
.last-run.json
|
||||
|
|
|
|||
146
e2e/tests/area.spec.ts
Normal file
146
e2e/tests/area.spec.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToAreas(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to areas page
|
||||
await page.goto(appUrl + '/areas');
|
||||
await expect(page).toHaveURL(/\/areas/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create an area via the sidebar button
|
||||
async function createArea(page, areaName, areaDescription = '') {
|
||||
// Find the "Add Area" button in the sidebar
|
||||
const addAreaButton = page.locator('button[aria-label*="Area"]');
|
||||
await expect(addAreaButton).toBeVisible();
|
||||
|
||||
// Click the Add Area button
|
||||
await addAreaButton.click();
|
||||
|
||||
// Wait for the Area Modal to appear
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Fill in the area name
|
||||
await page.locator('input[name="name"]').fill(areaName);
|
||||
|
||||
// Fill in the area description if provided
|
||||
if (areaDescription) {
|
||||
await page.locator('textarea[name="description"]').fill(areaDescription);
|
||||
}
|
||||
|
||||
// Save the area
|
||||
await page.getByRole('button', { name: /create.*area|save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Wait for area creation to complete
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
test('user can create a new area and verify it appears in the areas list', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToAreas(page, baseURL);
|
||||
|
||||
// Create a unique test area
|
||||
const timestamp = Date.now();
|
||||
const areaName = `Test Area ${timestamp}`;
|
||||
const areaDescription = `This is test description for area ${timestamp}`;
|
||||
await createArea(page, areaName, areaDescription);
|
||||
|
||||
// Verify the area appears in the areas list
|
||||
await expect(page.getByText(areaName)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('user can update an existing area', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToAreas(page, baseURL);
|
||||
|
||||
// Create an initial area
|
||||
const timestamp = Date.now();
|
||||
const originalAreaName = `Test area to edit ${timestamp}`;
|
||||
const originalAreaDescription = `Original description ${timestamp}`;
|
||||
await createArea(page, originalAreaName, originalAreaDescription);
|
||||
|
||||
// Find the area container and hover to show dropdown
|
||||
const areaContainer = page.getByText(originalAreaName).locator('..');
|
||||
await areaContainer.hover();
|
||||
|
||||
// Click the three dots menu
|
||||
await areaContainer.locator('button[aria-label*="dropdown"]').click();
|
||||
|
||||
// Click Edit in the dropdown
|
||||
await page.getByText('Edit').click();
|
||||
|
||||
// Wait for the Area Modal to appear with the area data
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
|
||||
// Verify the area name field is pre-filled
|
||||
const areaNameInput = page.locator('input[name="name"]').first();
|
||||
await expect(areaNameInput).toHaveValue(originalAreaName);
|
||||
|
||||
// Edit the area name and description
|
||||
const editedAreaName = `Edited test area ${timestamp}`;
|
||||
const editedAreaDescription = `Edited description ${timestamp}`;
|
||||
await areaNameInput.clear();
|
||||
await areaNameInput.fill(editedAreaName);
|
||||
|
||||
const areaDescriptionTextarea = page.locator('textarea[name="description"]').first();
|
||||
await areaDescriptionTextarea.clear();
|
||||
await areaDescriptionTextarea.fill(editedAreaDescription);
|
||||
|
||||
// Save the changes
|
||||
await page.getByRole('button', { name: /save|update/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited area appears in the areas list
|
||||
await expect(page.getByText(editedAreaName)).toBeVisible();
|
||||
|
||||
// Verify the original area name is no longer visible
|
||||
await expect(page.getByText(originalAreaName)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an existing area', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToAreas(page, baseURL);
|
||||
|
||||
// Create an initial area
|
||||
const timestamp = Date.now();
|
||||
const areaName = `Test area to delete ${timestamp}`;
|
||||
const areaDescription = `Description to delete ${timestamp}`;
|
||||
await createArea(page, areaName, areaDescription);
|
||||
|
||||
// Find the area container and hover to show dropdown
|
||||
const areaContainer = page.getByText(areaName).locator('..');
|
||||
await areaContainer.hover();
|
||||
|
||||
// Click the three dots menu
|
||||
await areaContainer.locator('button[aria-label*="dropdown"]').click();
|
||||
|
||||
// Click Delete in the dropdown
|
||||
await page.getByText('Delete').click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Area')).toBeVisible();
|
||||
// Click the confirm button in the confirmation dialog
|
||||
await page.getByRole('button', { name: /confirm|delete/i }).click();
|
||||
|
||||
// Verify the area is no longer visible in the areas list
|
||||
await expect(page.getByText(areaName)).not.toBeVisible();
|
||||
});
|
||||
239
e2e/tests/inbox.spec.ts
Normal file
239
e2e/tests/inbox.spec.ts
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToInbox(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to inbox page
|
||||
await page.goto(appUrl + '/inbox');
|
||||
await expect(page).toHaveURL(/\/inbox$/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create an inbox item
|
||||
async function createInboxItem(page, content) {
|
||||
// Click the Quick Inbox Capture button in the navbar
|
||||
await page.getByRole('button', { name: 'Quick Inbox Capture' }).click();
|
||||
|
||||
// Wait for the InboxModal to appear
|
||||
await expect(page.locator('input[name="text"]')).toBeVisible();
|
||||
|
||||
// Add the test item
|
||||
await page.locator('input[name="text"]').fill(content);
|
||||
|
||||
// Submit the form by pressing Enter
|
||||
await page.locator('input[name="text"]').press('Enter');
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="text"]')).not.toBeVisible();
|
||||
|
||||
// Verify the item appears in the inbox list
|
||||
await expect(page.locator('text=' + content)).toBeVisible();
|
||||
}
|
||||
|
||||
test('user can add a new inbox item and verify it has been added', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Add a unique test item
|
||||
const testContent = `Test inbox item ${Date.now()}`;
|
||||
await createInboxItem(page, testContent);
|
||||
});
|
||||
|
||||
test('user can edit an inbox item', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Create an initial item
|
||||
const timestamp = Date.now();
|
||||
const originalContent = `Test item to edit ${timestamp}`;
|
||||
await createInboxItem(page, originalContent);
|
||||
|
||||
// Find the inbox item container and hover to show edit button
|
||||
const inboxItemContainer = page.locator('.rounded-lg.shadow-sm').filter({ hasText: originalContent });
|
||||
await inboxItemContainer.hover();
|
||||
|
||||
// Click the edit button (pencil icon) - it has title="Edit"
|
||||
await inboxItemContainer.locator('button[title="Edit"]').click();
|
||||
|
||||
// Wait for the edit modal to appear
|
||||
await expect(page.locator('input[name="text"]')).toBeVisible();
|
||||
|
||||
// Edit the content
|
||||
const editedContent = `Edited test item ${timestamp}`;
|
||||
await page.locator('input[name="text"]').clear();
|
||||
await page.locator('input[name="text"]').fill(editedContent);
|
||||
await page.locator('input[name="text"]').press('Enter');
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="text"]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited content appears
|
||||
await expect(page.locator('text=' + editedContent)).toBeVisible();
|
||||
|
||||
// Verify the original content is no longer visible
|
||||
await expect(page.locator('text=' + originalContent)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an inbox item', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Create an initial item
|
||||
const timestamp = Date.now();
|
||||
const testContent = `Test item to delete ${timestamp}`;
|
||||
await createInboxItem(page, testContent);
|
||||
|
||||
// Find the inbox item container and hover to show delete button
|
||||
const inboxItemContainer = page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent });
|
||||
await inboxItemContainer.hover();
|
||||
|
||||
// Click the delete button (trash icon) - it has title="Delete"
|
||||
await inboxItemContainer.locator('button[title="Delete"]').click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Item')).toBeVisible();
|
||||
// Click the red "Delete" button in the confirmation dialog
|
||||
await page.locator('.bg-red-500.text-white').click();
|
||||
|
||||
// Verify the item is no longer visible
|
||||
await expect(page.locator('text=' + testContent)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can create task from inbox item', async ({ page, baseURL }) => {
|
||||
const appUrl = await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Create an initial item
|
||||
const timestamp = Date.now();
|
||||
const testContent = `Test item to convert to task ${timestamp}`;
|
||||
await createInboxItem(page, testContent);
|
||||
|
||||
// Find the inbox item container and hover to show convert buttons
|
||||
const inboxItemContainer = page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent });
|
||||
await inboxItemContainer.hover();
|
||||
|
||||
// Click the "Convert to Task" button (clipboard icon with title="Create task")
|
||||
await inboxItemContainer.locator('button[title="Create task"]').click();
|
||||
|
||||
// Wait for the Task Modal to appear
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify the task name field is pre-filled with the inbox item content
|
||||
const taskNameInput = page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]').first();
|
||||
await expect(taskNameInput).toHaveValue(testContent);
|
||||
|
||||
// Save the task - Use a more specific selector within the modal
|
||||
await page.locator('.bg-blue-600.text-white').filter({ hasText: 'Save' }).click();
|
||||
|
||||
// Wait for success message or modal to close
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Navigate back to inbox to verify the item was processed
|
||||
await page.goto(appUrl + '/inbox');
|
||||
|
||||
// Verify the original inbox item is no longer in the inbox (successfully converted to task)
|
||||
await expect(page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent })).not.toBeVisible();
|
||||
|
||||
// Navigate to tasks page to verify the task was created there
|
||||
await page.goto(appUrl + '/tasks');
|
||||
await expect(page).toHaveURL(/\/tasks$/);
|
||||
|
||||
// Verify the created task appears in the tasks list using the task-item-wrapper class
|
||||
await expect(page.locator('.task-item-wrapper').filter({ hasText: testContent })).toBeVisible();
|
||||
});
|
||||
|
||||
test('user can create project from inbox item', async ({ page, baseURL }) => {
|
||||
const appUrl = await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Create an initial item
|
||||
const timestamp = Date.now();
|
||||
const testContent = `Test project from inbox ${timestamp}`;
|
||||
await createInboxItem(page, testContent);
|
||||
|
||||
// Find the inbox item container and hover to show convert buttons
|
||||
const inboxItemContainer = page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent });
|
||||
await inboxItemContainer.hover();
|
||||
|
||||
// Click the "Create project" button
|
||||
await inboxItemContainer.locator('button[title="Create project"]').click();
|
||||
|
||||
// Wait for the Project Modal to appear
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="project" i], input[placeholder*="name" i]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify the project name field is pre-filled with the inbox item content
|
||||
const projectNameInput = page.locator('input[name="name"], input[placeholder*="project" i], input[placeholder*="name" i]').first();
|
||||
await expect(projectNameInput).toHaveValue(testContent);
|
||||
|
||||
// Save the project - Use a more generic approach for the submit button
|
||||
await page.getByRole('button', { name: /create.*project|save/i }).click();
|
||||
|
||||
// Wait for success message or modal to close
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="project" i], input[placeholder*="name" i]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Navigate back to inbox to verify the item was processed
|
||||
await page.goto(appUrl + '/inbox');
|
||||
|
||||
// Verify the original inbox item is no longer in the inbox (successfully converted to project)
|
||||
await expect(page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent })).not.toBeVisible();
|
||||
|
||||
// Navigate to projects page to verify the project was created there
|
||||
await page.goto(appUrl + '/projects');
|
||||
await expect(page).toHaveURL(/\/projects$/);
|
||||
|
||||
// Verify the created project appears in the projects list
|
||||
await expect(page.locator('text=' + testContent)).toBeVisible();
|
||||
});
|
||||
|
||||
test('user can create note from inbox item', async ({ page, baseURL }) => {
|
||||
const appUrl = await loginAndNavigateToInbox(page, baseURL);
|
||||
|
||||
// Create an initial item
|
||||
const timestamp = Date.now();
|
||||
const testContent = `Test note from inbox ${timestamp}`;
|
||||
await createInboxItem(page, testContent);
|
||||
|
||||
// Find the inbox item container and hover to show convert buttons
|
||||
const inboxItemContainer = page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent });
|
||||
await inboxItemContainer.hover();
|
||||
|
||||
// Click the "Create note" button
|
||||
await inboxItemContainer.locator('button[title="Create note"]').click();
|
||||
|
||||
// Wait for the Note Modal to appear
|
||||
await expect(page.locator('input[name="title"], input[placeholder*="note" i], input[placeholder*="title" i]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify the note title field is pre-filled with the inbox item content
|
||||
const noteTitleInput = page.locator('input[name="title"], input[placeholder*="note" i], input[placeholder*="title" i]').first();
|
||||
await expect(noteTitleInput).toHaveValue(testContent);
|
||||
|
||||
// Save the note - Use a more generic approach for the submit button
|
||||
await page.getByRole('button', { name: /create.*note|save/i }).click();
|
||||
|
||||
// Wait for success message or modal to close
|
||||
await expect(page.locator('input[name="title"], input[placeholder*="note" i], input[placeholder*="title" i]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Navigate back to inbox to verify the item was processed
|
||||
await page.goto(appUrl + '/inbox');
|
||||
|
||||
// Verify the original inbox item is no longer in the inbox (successfully converted to note)
|
||||
await expect(page.locator('.rounded-lg.shadow-sm').filter({ hasText: testContent })).not.toBeVisible();
|
||||
|
||||
// Navigate to notes page to verify the note was created there
|
||||
await page.goto(appUrl + '/notes');
|
||||
await expect(page).toHaveURL(/\/notes$/);
|
||||
|
||||
// Verify the created note appears in the notes list - use a more specific selector to avoid strict mode
|
||||
await expect(page.locator('.note-item, .rounded-lg, .border').filter({ hasText: testContent })).toBeVisible();
|
||||
});
|
||||
140
e2e/tests/note.spec.ts
Normal file
140
e2e/tests/note.spec.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToNotes(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to notes page
|
||||
await page.goto(appUrl + '/notes');
|
||||
await expect(page).toHaveURL(/\/notes/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create a note via the sidebar button
|
||||
async function createNote(page, noteTitle, noteContent = '') {
|
||||
// Find the "Add Note" button in the sidebar
|
||||
const addNoteButton = page.locator('button[aria-label="Add Note"]');
|
||||
await expect(addNoteButton).toBeVisible();
|
||||
|
||||
// Click the Add Note button
|
||||
await addNoteButton.click();
|
||||
|
||||
// Wait for the Note Modal to appear
|
||||
await expect(page.locator('input[name="title"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Fill in the note title
|
||||
await page.locator('input[name="title"]').fill(noteTitle);
|
||||
|
||||
// Fill in the note content if provided
|
||||
if (noteContent) {
|
||||
await page.locator('textarea[name="content"]').fill(noteContent);
|
||||
}
|
||||
|
||||
// Save the note
|
||||
await page.getByRole('button', { name: /create.*note|save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="title"]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Wait for note creation to complete
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
test('user can create a new note and verify it appears in the notes list', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToNotes(page, baseURL);
|
||||
|
||||
// Create a unique test note
|
||||
const timestamp = Date.now();
|
||||
const noteTitle = `Test Note ${timestamp}`;
|
||||
const noteContent = `This is test content for note ${timestamp}`;
|
||||
await createNote(page, noteTitle, noteContent);
|
||||
|
||||
// Verify the note appears in the notes list
|
||||
await expect(page.getByText(noteTitle)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('user can update an existing note', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToNotes(page, baseURL);
|
||||
|
||||
// Create an initial note
|
||||
const timestamp = Date.now();
|
||||
const originalNoteTitle = `Test note to edit ${timestamp}`;
|
||||
const originalNoteContent = `Original content ${timestamp}`;
|
||||
await createNote(page, originalNoteTitle, originalNoteContent);
|
||||
|
||||
// Find and click the note to edit it
|
||||
const noteContainer = page.getByText(originalNoteTitle).locator('..');
|
||||
await noteContainer.hover();
|
||||
|
||||
// Click the edit button (pencil icon)
|
||||
await noteContainer.locator('button[title="Edit"], button').filter({ hasText: '' }).first().click();
|
||||
|
||||
// Wait for the Note Modal to appear with the note data
|
||||
await expect(page.locator('input[name="title"]')).toBeVisible();
|
||||
|
||||
// Verify the note title field is pre-filled
|
||||
const noteTitleInput = page.locator('input[name="title"]').first();
|
||||
await expect(noteTitleInput).toHaveValue(originalNoteTitle);
|
||||
|
||||
// Edit the note title and content
|
||||
const editedNoteTitle = `Edited test note ${timestamp}`;
|
||||
const editedNoteContent = `Edited content ${timestamp}`;
|
||||
await noteTitleInput.clear();
|
||||
await noteTitleInput.fill(editedNoteTitle);
|
||||
|
||||
const noteContentTextarea = page.locator('textarea[name="content"]').first();
|
||||
await noteContentTextarea.clear();
|
||||
await noteContentTextarea.fill(editedNoteContent);
|
||||
|
||||
// Save the changes
|
||||
await page.getByRole('button', { name: /save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="title"]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited note appears in the notes list
|
||||
await expect(page.getByText(editedNoteTitle)).toBeVisible();
|
||||
|
||||
// Verify the original note title is no longer visible
|
||||
await expect(page.getByText(originalNoteTitle)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an existing note', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToNotes(page, baseURL);
|
||||
|
||||
// Create an initial note
|
||||
const timestamp = Date.now();
|
||||
const noteTitle = `Test note to delete ${timestamp}`;
|
||||
const noteContent = `Content to delete ${timestamp}`;
|
||||
await createNote(page, noteTitle, noteContent);
|
||||
|
||||
// Find the note container and hover to show action buttons
|
||||
const noteContainer = page.getByText(noteTitle).locator('..');
|
||||
await noteContainer.hover();
|
||||
|
||||
// Click the delete button (trash icon)
|
||||
await noteContainer.locator('button[title="Delete"], button').filter({ hasText: '' }).last().click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Note')).toBeVisible();
|
||||
// Click the red "Delete" button in the confirmation dialog
|
||||
await page.locator('.bg-red-500.text-white').click();
|
||||
|
||||
// Verify the note is no longer visible in the notes list
|
||||
await expect(page.getByText(noteTitle)).not.toBeVisible();
|
||||
});
|
||||
218
e2e/tests/project.spec.ts
Normal file
218
e2e/tests/project.spec.ts
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToProjects(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to projects page
|
||||
await page.goto(appUrl + '/projects');
|
||||
await expect(page).toHaveURL(/\/projects/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create a project via the sidebar button
|
||||
async function createProject(page, projectName) {
|
||||
// Find the "Add Project" button in the sidebar
|
||||
const addProjectButton = page.locator('button[aria-label="Add Project"]');
|
||||
await expect(addProjectButton).toBeVisible();
|
||||
|
||||
// Click the Add Project button
|
||||
await addProjectButton.click();
|
||||
|
||||
// Wait for the Project Modal to appear
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Fill in the project name
|
||||
await page.locator('input[name="name"]').fill(projectName);
|
||||
|
||||
// Save the project
|
||||
await page.getByRole('button', { name: /create.*project|save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Wait for project creation to complete
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
test('user can create a new project and verify it appears in the projects list', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToProjects(page, baseURL);
|
||||
|
||||
// Create a unique test project
|
||||
const timestamp = Date.now();
|
||||
const projectName = `Test Project ${timestamp}`;
|
||||
await createProject(page, projectName);
|
||||
|
||||
// Verify the project appears in the projects list
|
||||
await expect(page.getByText(projectName)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('user can update an existing project', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToProjects(page, baseURL);
|
||||
|
||||
// Create an initial project
|
||||
const timestamp = Date.now();
|
||||
const originalProjectName = `Test project to edit ${timestamp}`;
|
||||
await createProject(page, originalProjectName);
|
||||
|
||||
// Find and click the project to open its details or edit it
|
||||
// Look for the project card/item and find its edit button
|
||||
const projectContainer = page.getByText(originalProjectName).locator('..');
|
||||
await projectContainer.hover();
|
||||
|
||||
// Click the edit button (pencil icon)
|
||||
await projectContainer.locator('button[title="Edit"], button').filter({ hasText: '' }).first().click();
|
||||
|
||||
// Wait for the Project Modal to appear with the project data
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
|
||||
// Verify the project name field is pre-filled
|
||||
const projectNameInput = page.locator('input[name="name"]').first();
|
||||
await expect(projectNameInput).toHaveValue(originalProjectName);
|
||||
|
||||
// Edit the project name
|
||||
const editedProjectName = `Edited test project ${timestamp}`;
|
||||
await projectNameInput.clear();
|
||||
await projectNameInput.fill(editedProjectName);
|
||||
|
||||
// Save the changes
|
||||
await page.getByRole('button', { name: /save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited project appears in the projects list
|
||||
await expect(page.getByText(editedProjectName)).toBeVisible();
|
||||
|
||||
// Verify the original project name is no longer visible
|
||||
await expect(page.getByText(originalProjectName)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an existing project', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToProjects(page, baseURL);
|
||||
|
||||
// Create an initial project
|
||||
const timestamp = Date.now();
|
||||
const projectName = `Test project to delete ${timestamp}`;
|
||||
await createProject(page, projectName);
|
||||
|
||||
// Find the project container and hover to show action buttons
|
||||
const projectContainer = page.getByText(projectName).locator('..');
|
||||
await projectContainer.hover();
|
||||
|
||||
// Click the delete button (trash icon)
|
||||
await projectContainer.locator('button[title="Delete"], button').filter({ hasText: '' }).last().click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Project')).toBeVisible();
|
||||
// Click the red "Delete" button in the confirmation dialog
|
||||
await page.locator('.bg-red-500.text-white').click();
|
||||
|
||||
// Verify the project is no longer visible in the projects list
|
||||
await expect(page.getByText(projectName)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can add a task to a project via ProjectDetails view', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToProjects(page, baseURL);
|
||||
|
||||
// Create an initial project
|
||||
const timestamp = Date.now();
|
||||
const projectName = `Test project for tasks ${timestamp}`;
|
||||
await createProject(page, projectName);
|
||||
|
||||
// Click on the project to open its details view
|
||||
await page.getByText(projectName).click();
|
||||
|
||||
// Wait for the project details page to load
|
||||
await expect(page).toHaveURL(/\/project\//);
|
||||
|
||||
// Find the task creation input field within the project details
|
||||
const taskInput = page.locator('input[placeholder="Προσθήκη Νέας Εργασίας"]').first();
|
||||
|
||||
// Wait for the input to be visible
|
||||
await expect(taskInput).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Create a task within this project
|
||||
const taskName = `Test task in project ${timestamp}`;
|
||||
await taskInput.fill(taskName);
|
||||
await taskInput.press('Enter');
|
||||
|
||||
// Verify task creation by checking that the input field is cleared
|
||||
await expect(taskInput).toHaveValue('');
|
||||
|
||||
// Wait for the task to be created and appear in the project's task list
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Verify the task appears in the project's task list
|
||||
// Use a more general approach since the exact structure might vary
|
||||
await expect(page.getByText(taskName)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('user can delete a project with tasks - tasks should survive', async ({ page, baseURL }) => {
|
||||
const appUrl = await loginAndNavigateToProjects(page, baseURL);
|
||||
|
||||
// Create an initial project
|
||||
const timestamp = Date.now();
|
||||
const projectName = `Test project with tasks ${timestamp}`;
|
||||
await createProject(page, projectName);
|
||||
|
||||
// Click on the project to open its details view
|
||||
await page.getByText(projectName).click();
|
||||
|
||||
// Wait for the project details page to load
|
||||
await expect(page).toHaveURL(/\/project\//);
|
||||
|
||||
// Add a task to this project
|
||||
const taskInput = page.locator('input[placeholder="Προσθήκη Νέας Εργασίας"]').first();
|
||||
await expect(taskInput).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const taskName = `Task that should survive project deletion ${timestamp}`;
|
||||
await taskInput.fill(taskName);
|
||||
await taskInput.press('Enter');
|
||||
await expect(taskInput).toHaveValue('');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Navigate back to projects list
|
||||
await page.goto(appUrl + '/projects');
|
||||
await expect(page).toHaveURL(/\/projects/);
|
||||
|
||||
// Delete the project
|
||||
const projectContainer = page.getByText(projectName).locator('..');
|
||||
await projectContainer.hover();
|
||||
await projectContainer.locator('button[title="Delete"], button').filter({ hasText: '' }).last().click();
|
||||
|
||||
// Handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Project')).toBeVisible();
|
||||
await page.locator('.bg-red-500.text-white').click();
|
||||
|
||||
// Verify the project is deleted
|
||||
await expect(page.getByText(projectName)).not.toBeVisible();
|
||||
|
||||
// Verify the task still exists - navigate to tasks page
|
||||
await page.goto(appUrl + '/tasks');
|
||||
await expect(page).toHaveURL(/\/tasks/);
|
||||
|
||||
// Wait for tasks to load
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// The task should still exist but without the project association
|
||||
// This is the expected behavior based on backend implementation:
|
||||
// - project.destroy() doesn't cascade to tasks
|
||||
// - tasks have project_id set to NULL when project is deleted
|
||||
await expect(page.getByText(taskName)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
127
e2e/tests/tag.spec.ts
Normal file
127
e2e/tests/tag.spec.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToTags(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to tags page
|
||||
await page.goto(appUrl + '/tags');
|
||||
await expect(page).toHaveURL(/\/tags/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create a tag via the sidebar button
|
||||
async function createTag(page, tagName) {
|
||||
// Find the "Add Tag" button in the sidebar
|
||||
const addTagButton = page.locator('button[aria-label*="Tag"]');
|
||||
await expect(addTagButton).toBeVisible();
|
||||
|
||||
// Click the Add Tag button
|
||||
await addTagButton.click();
|
||||
|
||||
// Wait for the Tag Modal to appear
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Fill in the tag name
|
||||
await page.locator('input[name="name"]').fill(tagName);
|
||||
|
||||
// Save the tag
|
||||
await page.getByRole('button', { name: /create.*tag|save/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Wait for tag creation to complete
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
test('user can create a new tag and verify it appears in the tags list', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTags(page, baseURL);
|
||||
|
||||
// Create a unique test tag
|
||||
const timestamp = Date.now();
|
||||
const tagName = `TestTag${timestamp}`;
|
||||
await createTag(page, tagName);
|
||||
|
||||
// Verify the tag appears in the tags list
|
||||
await expect(page.getByText(tagName)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('user can update an existing tag', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTags(page, baseURL);
|
||||
|
||||
// Create an initial tag
|
||||
const timestamp = Date.now();
|
||||
const originalTagName = `TestTagEdit${timestamp}`;
|
||||
await createTag(page, originalTagName);
|
||||
|
||||
// Find the tag container and hover to show edit button
|
||||
const tagContainer = page.getByText(originalTagName).locator('..');
|
||||
await tagContainer.hover();
|
||||
|
||||
// Click the edit button (pencil icon)
|
||||
await tagContainer.locator('button[aria-label*="Edit"], button[title*="Edit"]').click();
|
||||
|
||||
// Wait for the Tag Modal to appear with the tag data
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
|
||||
// Verify the tag name field is pre-filled
|
||||
const tagNameInput = page.locator('input[name="name"]').first();
|
||||
await expect(tagNameInput).toHaveValue(originalTagName);
|
||||
|
||||
// Edit the tag name
|
||||
const editedTagName = `EditedTestTag${timestamp}`;
|
||||
await tagNameInput.clear();
|
||||
await tagNameInput.fill(editedTagName);
|
||||
|
||||
// Save the changes
|
||||
await page.getByRole('button', { name: /save|update/i }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited tag appears in the tags list
|
||||
await expect(page.getByText(editedTagName)).toBeVisible();
|
||||
|
||||
// Verify the original tag name is no longer visible
|
||||
await expect(page.getByText(originalTagName)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an existing tag', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTags(page, baseURL);
|
||||
|
||||
// Create an initial tag
|
||||
const timestamp = Date.now();
|
||||
const tagName = `TestTagDelete${timestamp}`;
|
||||
await createTag(page, tagName);
|
||||
|
||||
// Find the tag container and hover to show delete button
|
||||
const tagContainer = page.getByText(tagName).locator('..');
|
||||
await tagContainer.hover();
|
||||
|
||||
// Click the delete button (trash icon)
|
||||
await tagContainer.locator('button[aria-label*="Delete"], button[title*="Delete"]').click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Tag')).toBeVisible();
|
||||
// Click the confirm button in the confirmation dialog
|
||||
await page.getByRole('button', { name: /confirm|delete/i }).click();
|
||||
|
||||
// Verify the tag is no longer visible in the tags list
|
||||
await expect(page.getByText(tagName)).not.toBeVisible();
|
||||
});
|
||||
144
e2e/tests/task.spec.ts
Normal file
144
e2e/tests/task.spec.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Shared login function
|
||||
async function loginAndNavigateToTasks(page, baseURL) {
|
||||
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
|
||||
|
||||
// Go directly to login page first
|
||||
await page.goto(appUrl + '/login');
|
||||
|
||||
// Fill credentials and login
|
||||
const email = process.env.E2E_EMAIL || 'test@tududi.com';
|
||||
const password = process.env.E2E_PASSWORD || 'password123';
|
||||
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
|
||||
// Wait for redirect to Today view
|
||||
await expect(page).toHaveURL(/\/today$/);
|
||||
|
||||
// Navigate to tasks page
|
||||
await page.goto(appUrl + '/tasks');
|
||||
await expect(page).toHaveURL(/\/tasks/);
|
||||
|
||||
return appUrl;
|
||||
}
|
||||
|
||||
// Shared function to create a task via the inline input field
|
||||
async function createTask(page, taskName) {
|
||||
// Find task input - try multiple selectors to be more robust
|
||||
let taskInput;
|
||||
|
||||
// Find the NewTask input field more specifically to avoid the search field
|
||||
// Look for input with the exact placeholder text from NewTask component
|
||||
try {
|
||||
taskInput = page.locator('input[placeholder="Προσθήκη Νέας Εργασίας"]').first();
|
||||
await expect(taskInput).toBeVisible({ timeout: 5000 });
|
||||
} catch {
|
||||
// Fallback: look for input within the NewTask component structure
|
||||
// NewTask has a container with rounded-lg shadow-sm and a PlusCircleIcon
|
||||
taskInput = page.locator('.rounded-lg.shadow-sm').filter({ has: page.locator('svg') }).locator('input[type="text"]').first();
|
||||
await expect(taskInput).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
|
||||
// Clear and fill in the task name
|
||||
await taskInput.clear();
|
||||
await taskInput.fill(taskName);
|
||||
|
||||
// Press Enter to create the task
|
||||
await taskInput.press('Enter');
|
||||
|
||||
// Verify task creation by checking that the input field is cleared
|
||||
// (this is simpler and more reliable than trying to find the created task in the UI)
|
||||
await expect(taskInput).toHaveValue('');
|
||||
|
||||
// Wait for the task creation API call to complete
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
test('user can create a new task and verify it appears in the task list', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTasks(page, baseURL);
|
||||
|
||||
// Create a unique test task
|
||||
const timestamp = Date.now();
|
||||
const taskName = `Test task ${timestamp}`;
|
||||
await createTask(page, taskName);
|
||||
});
|
||||
|
||||
test('user can update an existing task', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTasks(page, baseURL);
|
||||
|
||||
// Create an initial task
|
||||
const timestamp = Date.now();
|
||||
const originalTaskName = `Test task to edit ${timestamp}`;
|
||||
await createTask(page, originalTaskName);
|
||||
|
||||
// Find the task and click on it to open the edit modal
|
||||
const taskContainer = page.locator('.task-item-wrapper').filter({ hasText: originalTaskName });
|
||||
await taskContainer.click();
|
||||
|
||||
// Wait for the Task Modal to appear with the task data
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]')).toBeVisible();
|
||||
|
||||
// Verify the task name field is pre-filled
|
||||
const taskNameInput = page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]').first();
|
||||
await expect(taskNameInput).toHaveValue(originalTaskName);
|
||||
|
||||
// Edit the task name
|
||||
const editedTaskName = `Edited test task ${timestamp}`;
|
||||
await taskNameInput.clear();
|
||||
await taskNameInput.fill(editedTaskName);
|
||||
|
||||
// Save the changes
|
||||
await page.locator('.bg-blue-600.text-white').filter({ hasText: 'Save' }).click();
|
||||
|
||||
// Wait for the modal to close
|
||||
await expect(page.locator('input[name="name"], input[placeholder*="task" i], input[placeholder*="name" i]')).not.toBeVisible();
|
||||
|
||||
// Verify the edited task appears in the task list
|
||||
await expect(page.locator('.task-item-wrapper').filter({ hasText: editedTaskName })).toBeVisible();
|
||||
|
||||
// Verify the original task name is no longer visible
|
||||
await expect(page.locator('.task-item-wrapper').filter({ hasText: originalTaskName })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete an existing task', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTasks(page, baseURL);
|
||||
|
||||
// Create an initial task
|
||||
const timestamp = Date.now();
|
||||
const taskName = `Test task to delete ${timestamp}`;
|
||||
await createTask(page, taskName);
|
||||
|
||||
// Find the task container and hover to show action buttons
|
||||
const taskContainer = page.locator('.task-item-wrapper').filter({ hasText: taskName });
|
||||
await taskContainer.hover();
|
||||
|
||||
// Click the delete button (trash icon)
|
||||
await taskContainer.locator('button[title="Delete"], button').filter({ hasText: '' }).last().click();
|
||||
|
||||
// Wait for and handle the confirmation dialog
|
||||
await expect(page.locator('text=Delete Task')).toBeVisible();
|
||||
// Click the red "Delete" button in the confirmation dialog
|
||||
await page.locator('.bg-red-500.text-white').click();
|
||||
|
||||
// Verify the task is no longer visible in the task list
|
||||
await expect(page.locator('.task-item-wrapper').filter({ hasText: taskName })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('user can mark a task as complete', async ({ page, baseURL }) => {
|
||||
await loginAndNavigateToTasks(page, baseURL);
|
||||
|
||||
// Create an initial task
|
||||
const timestamp = Date.now();
|
||||
const taskName = `Test task to complete ${timestamp}`;
|
||||
await createTask(page, taskName);
|
||||
|
||||
// Find the task container and click the checkbox to mark it as complete
|
||||
const taskContainer = page.locator('.task-item-wrapper').filter({ hasText: taskName });
|
||||
await taskContainer.locator('input[type="checkbox"], button[role="checkbox"]').click();
|
||||
|
||||
// Verify the task is marked as completed (usually with strikethrough or different styling)
|
||||
await expect(taskContainer.locator('.line-through, .completed, .opacity-50')).toBeVisible();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue