253 lines
12 KiB
TypeScript
253 lines
12 KiB
TypeScript
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)
|
|
})
|
|
})
|