import path from 'path' import { fileURLToPath } from 'url' import { expect, test, Page } from 'playwright/test' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const runId = new Date().toISOString().replace(/[-:.TZ]/g, '') const spaceName = `E2E Space ${runId}` const projectName = `E2E Project ${runId}` const taskOneTitle = `E2E Task One ${runId}` const taskTwoTitle = `E2E Task Two ${runId}` const subtaskTitle = `E2E Subtask ${runId}` const commentText = `E2E Comment ${runId}` const customNumberField = `sp_${runId}` const customFormulaField = `double_sp_${runId}` const attachmentPath = path.join(__dirname, 'fixtures', 'attachment.txt') const formatDateInput = (date: Date) => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } const setEnglishLocale = async (page: Page) => { await page.addInitScript(() => { localStorage.setItem('i18nextLng', 'en') }) } const openProjectFromSpaces = async (page: Page) => { await page.goto('/spaces') await expect(page.getByRole('heading', { name: 'Spaces' })).toBeVisible() await page.getByRole('button', { name: `Spaces: ${spaceName}` }).click() await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible() await page.getByRole('button', { name: `Projects: ${projectName}` }).click() await expect(page.getByRole('heading', { name: 'Tasks' })).toBeVisible() } test.describe.serial('admin e2e flow', () => { test.beforeEach(async ({ page }) => { await setEnglishLocale(page) }) test('login and dashboard', async ({ page }) => { await page.goto('/') await expect(page.getByRole('heading', { name: /Welcome back/i })).toBeVisible() await expect(page.getByRole('heading', { name: 'Quick Actions' })).toBeVisible() }) test('create space and project', async ({ page }) => { await page.goto('/spaces') await expect(page.getByRole('heading', { name: 'Spaces' })).toBeVisible() await page.getByRole('button', { name: /Create Space/i }).click() const spaceModal = page.getByRole('dialog', { name: 'Create Space' }) await spaceModal.getByLabel('Name').fill(spaceName) await spaceModal.getByLabel('Description').fill('E2E space description') await spaceModal.getByRole('button', { name: 'Create' }).click() await expect(page.getByRole('button', { name: `Spaces: ${spaceName}` })).toBeVisible() await page.getByRole('button', { name: `Spaces: ${spaceName}` }).click() await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible() await page.getByRole('button', { name: /New Project/i }).click() const projectModal = page.getByRole('dialog', { name: 'Create Project' }) await projectModal.getByText('Blank Project', { exact: true }).click() await projectModal.locator('#project-title').fill(projectName) await projectModal.locator('#project-description').fill('E2E project description') await projectModal.getByRole('button', { name: 'Create' }).click() await expect(page.getByRole('button', { name: `Projects: ${projectName}` })).toBeVisible() await page.getByRole('button', { name: `Projects: ${projectName}` }).click() await expect(page.getByRole('heading', { name: 'Tasks' })).toBeVisible() }) test('project settings: members and custom fields', async ({ page }) => { await openProjectFromSpaces(page) await page.getByRole('button', { name: 'Settings' }).click() await expect(page.getByRole('heading', { name: 'Project Settings' })).toBeVisible() await page.getByRole('button', { name: 'Members' }).click() await expect(page.getByText('Member Management')).toBeVisible() await expect(page.getByText('User')).toBeVisible() await page.getByRole('button', { name: 'Custom Fields' }).click() await expect(page.getByRole('button', { name: /Add Field/i })).toBeVisible() await page.getByRole('button', { name: /Add Field/i }).click() const customModal = page.getByRole('dialog', { name: 'Create Field' }) await customModal.getByPlaceholder('e.g., Story Points, Sprint Number').fill(customNumberField) await customModal.getByText('Number', { exact: true }).click() await customModal.getByRole('button', { name: 'Create Field' }).click() await expect(page.getByText(customNumberField)).toBeVisible() await page.getByRole('button', { name: /Add Field/i }).click() const formulaModal = page.getByRole('dialog', { name: 'Create Field' }) await formulaModal.getByPlaceholder('e.g., Story Points, Sprint Number').fill(customFormulaField) await formulaModal.getByText('Formula', { exact: true }).click() await formulaModal.getByPlaceholder('e.g., {time_spent} / {original_estimate} * 100') .fill(`{${customNumberField}} * 2`) await formulaModal.getByRole('button', { name: 'Create Field' }).click() await expect(page.getByText(customFormulaField)).toBeVisible() }) test('tasks flow: create, views, detail, attachments, comments, subtasks, dependencies', async ({ page }) => { await openProjectFromSpaces(page) const today = new Date() const startDate = formatDateInput(today) const dueDate = formatDateInput(new Date(today.getTime() + 3 * 24 * 60 * 60 * 1000)) await page.getByRole('button', { name: /Create Task/i }).click() const createModal = page.getByRole('dialog', { name: 'Create Task' }) await createModal.locator('#task-title').fill(taskOneTitle) await createModal.locator('#task-description').fill('E2E task description') await createModal.locator('select').first().selectOption('low') const dateInputs = createModal.locator('input[type="date"]') await dateInputs.nth(0).fill(startDate) await dateInputs.nth(1).fill(dueDate) const customFieldContainer = createModal.locator('label', { hasText: customNumberField }).locator('..') await customFieldContainer.locator('input[type="number"]').fill('5') await createModal.getByRole('button', { name: 'Create' }).click() await expect(page.getByText(taskOneTitle)).toBeVisible() await page.getByRole('button', { name: /Create Task/i }).click() const createModalTwo = page.getByRole('dialog', { name: 'Create Task' }) await createModalTwo.locator('#task-title').fill(taskTwoTitle) await createModalTwo.locator('#task-description').fill('E2E task description 2') const dateInputsTwo = createModalTwo.locator('input[type="date"]') await dateInputsTwo.nth(0).fill(startDate) await dateInputsTwo.nth(1).fill(dueDate) await createModalTwo.getByRole('button', { name: 'Create' }).click() await expect(page.getByText(taskTwoTitle)).toBeVisible() await expect(page.getByText(`${customNumberField}: 5`, { exact: true })).toBeVisible() await page.getByRole('button', { name: 'Kanban' }).click() await expect(page.getByText(taskOneTitle)).toBeVisible() await page.getByRole('button', { name: 'Calendar' }).click() await expect(page.getByText(new RegExp(taskOneTitle))).toBeVisible() await page.getByRole('button', { name: 'Gantt' }).click() await expect(page.getByText('Task Dependencies')).toBeVisible() await page.getByRole('button', { name: 'Manage Dependencies' }).first().click() const depsHeading = page.getByRole('heading', { name: /Manage Dependencies for/i, }) await expect(depsHeading).toBeVisible() const depsDialog = depsHeading.locator('..') const dependencySelect = depsDialog.locator('select').first() await dependencySelect.selectOption({ index: 1 }) await depsDialog.getByRole('button', { name: 'Add Dependency' }).click() await expect(page.locator('text=Depends on:').first()).toBeVisible() await page.getByRole('button', { name: 'List' }).click() await page.getByText(taskOneTitle).first().click() const taskModal = page.getByRole('dialog', { name: taskOneTitle }) await expect(taskModal.getByRole('heading', { name: taskOneTitle })).toBeVisible() await page.getByRole('button', { name: 'Edit' }).click() const descriptionField = page.locator('label', { hasText: 'Description' }).locator('..').locator('textarea') await descriptionField.fill('Updated task description') await page.getByRole('button', { name: 'Save' }).click() await expect(page.getByText('Updated task description')).toBeVisible() await page.getByPlaceholder('Add a comment... Use @name to mention someone').fill(commentText) await page.getByRole('button', { name: 'Post Comment' }).click() await expect(page.getByText(commentText)).toBeVisible() await page.locator('input[type="file"]').setInputFiles(attachmentPath) await expect(page.getByText('attachment.txt')).toBeVisible() await page.getByRole('button', { name: /Add Subtask/i }).click() await page.locator('#new-subtask-title').fill(subtaskTitle) await page.getByRole('button', { name: 'Add' }).click() await expect(page.getByText(subtaskTitle)).toBeVisible() await page.getByLabel('Close').click() }) test('notifications and my settings', async ({ page }) => { await page.goto('/') await page.getByRole('button', { name: 'Notifications' }).click() await expect(page.getByRole('heading', { name: 'Notifications' })).toBeVisible() await page.goto('/my-settings') await expect(page.getByRole('heading', { name: 'My Settings' })).toBeVisible() const capacityInput = page.locator('input[type="number"]').first() const currentCapacity = await capacityInput.inputValue() const nextCapacity = String((Number(currentCapacity) || 40) + 1) await capacityInput.fill(nextCapacity) await page.getByRole('button', { name: 'Save' }).click() await capacityInput.fill(currentCapacity || '40') await page.getByRole('button', { name: 'Save' }).click() const weeklyCard = page.getByRole('heading', { name: 'Weekly Report' }).locator('..') const subscriptionToggle = weeklyCard.locator('input[type="checkbox"]') await expect(subscriptionToggle).toBeEnabled() const isChecked = await subscriptionToggle.isChecked() await subscriptionToggle.click() if (isChecked) { await expect(subscriptionToggle).not.toBeChecked() } else { await expect(subscriptionToggle).toBeChecked() } await subscriptionToggle.click() }) test('workload, project health, audit pages', async ({ page }) => { await page.goto('/workload') await expect(page.getByRole('heading', { name: 'Workload' })).toBeVisible() await page.getByRole('button', { name: 'Next week' }).click() await page.goto('/project-health') await expect(page.getByRole('heading', { name: 'Project Health Dashboard' })).toBeVisible() await page.getByLabel('Sort by:').selectOption('name') await page.goto('/audit') await expect(page.getByRole('heading', { name: 'Audit Log' })).toBeVisible() }) test('cleanup: delete project and space', async ({ page }) => { await page.goto('/spaces') await expect(page.getByRole('heading', { name: 'Spaces' })).toBeVisible() await page.getByRole('button', { name: `Spaces: ${spaceName}` }).click() await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible() const projectCard = page.getByRole('button', { name: `Projects: ${projectName}` }) await projectCard.getByRole('button', { name: 'Delete Project' }).click() const deleteProjectModal = page.getByRole('dialog', { name: 'Delete Project' }) await deleteProjectModal.getByRole('button', { name: 'Delete' }).click() await expect(page.getByRole('button', { name: `Projects: ${projectName}` })).toHaveCount(0) await page.goto('/spaces') await expect(page.getByRole('heading', { name: 'Spaces' })).toBeVisible() const spaceCard = page.getByRole('button', { name: `Spaces: ${spaceName}` }) await spaceCard.getByRole('button', { name: 'Delete Space' }).click() const deleteSpaceModal = page.getByRole('dialog', { name: 'Delete Space' }) await deleteSpaceModal.getByRole('button', { name: 'Delete' }).click() await expect(page.getByRole('button', { name: `Spaces: ${spaceName}` })).toHaveCount(0) }) })