import { test, expect } from '@playwright/test'; const BASE_URL = process.env.BASE_URL || process.env.FRONTEND_URL || 'http://frontend:3000'; const ADMIN_USERNAME = 'admin'; const ADMIN_PASSWORD = 'admin123'; test.describe('Task & Comment Flow', () => { test('login -> create project -> create milestone -> create task -> create issue -> comment -> logout', async ({ page }) => { const TS = Date.now(); // ── Step 1: Login ────────────────────────────────────────────────── console.log('🔐 Logging in...'); const MAX_LOGIN_RETRIES = 3; for (let attempt = 1; attempt <= MAX_LOGIN_RETRIES; attempt++) { console.log(`Login attempt ${attempt}/${MAX_LOGIN_RETRIES}`); await page.goto(`${BASE_URL}/login`); await page.waitForLoadState('networkidle'); await page.fill('input[type="text"], input[name="username"]', ADMIN_USERNAME); await page.fill('input[type="password"], input[name="password"]', ADMIN_PASSWORD); const loginPromise = page.waitForResponse( (r) => r.url().includes('/auth/token') && r.request().method() === 'POST', { timeout: 15000 } ).catch(() => null); await page.click('button[type="submit"], button:has-text("Sign in")'); const loginResp = await loginPromise; if (loginResp && loginResp.status() === 200) { await page.waitForURL(`${BASE_URL}/**`, { timeout: 10000 }).catch(() => {}); await page.waitForLoadState('networkidle'); break; } if (attempt < MAX_LOGIN_RETRIES) { console.log('Retrying login in 3s...'); await page.waitForTimeout(3000); } } const token = await page.evaluate(() => localStorage.getItem('token')); expect(token, 'Login failed').toBeTruthy(); await page.waitForSelector('a:has-text("📁 Projects")', { timeout: 10000 }); console.log('✅ Logged in'); // ── Step 2: Create Project ───────────────────────────────────────── const projectName = `Task Test Project ${TS}`; console.log(`📁 Creating project: ${projectName}`); await page.click('a:has-text("📁 Projects")'); await page.waitForLoadState('networkidle'); await page.waitForSelector('button:has-text("+ New")', { timeout: 10000 }); await page.click('button:has-text("+ New")'); await page.waitForSelector('input[placeholder="Project name"]', { timeout: 10000 }); await page.fill('input[placeholder="Project name"]', projectName); await page.fill('input[placeholder="Description (optional)"]', 'Project for task & comment testing'); await page.click('button:has-text("Create")'); await page.waitForTimeout(2000); await page.waitForSelector(`.project-card:has-text("${projectName}")`, { timeout: 10000 }); console.log('✅ Project created'); // ── Step 3: Open project → Create Milestone ──────────────────────── console.log('📌 Opening project & creating milestone...'); await page.click(`.project-card:has-text("${projectName}")`); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1500); // Scroll to milestone section and click "+ New" await page.evaluate(() => window.scrollTo(0, 500)); await page.waitForTimeout(500); await page.click('button:has-text("+ New")'); await page.waitForSelector('.modal', { timeout: 5000 }); const milestoneName = `Task Test Milestone ${TS}`; console.log(`Milestone name: ${milestoneName}`); await page.fill('input[placeholder="Milestone title"]', milestoneName); await page.waitForTimeout(500); await page.locator('.modal button.btn-primary').click(); await page.waitForTimeout(2000); await page.waitForSelector('.modal', { state: 'detached', timeout: 5000 }).catch(() => {}); // Verify milestone created const headingText = await page.locator('h3').filter({ hasText: /Milestones/i }).textContent(); expect(headingText).not.toContain('(0)'); console.log('✅ Milestone created'); // ── Step 4: Enter Milestone Detail → Create Task ─────────────────── console.log('📝 Entering milestone detail...'); await page.click(`.milestone-item:has-text("${milestoneName}")`); await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); // Wait for milestone detail page await page.waitForSelector('h2:has-text("🏁")', { timeout: 10000 }); console.log('Milestone detail loaded'); // Click "+ Create Task" await page.click('button:has-text("+ Create Task")'); await page.waitForTimeout(500); const taskTitle = `Test Task ${TS}`; console.log(`Task title: ${taskTitle}`); await page.fill('input[placeholder="Title"]', taskTitle); await page.fill('textarea[placeholder="Description (optional)"]', 'Task created by automated test'); await page.waitForTimeout(300); // Click Create button in the card form await page.click('.card button.btn-primary'); await page.waitForTimeout(2000); // Verify task appears in the Tasks tab await page.click('.tab:has-text("Tasks")'); await page.waitForTimeout(1000); const taskRow = page.locator(`td.issue-title:has-text("${taskTitle}")`); await expect(taskRow).toBeVisible({ timeout: 10000 }); console.log('✅ Task created and verified'); // ── Step 5: Create Issue (for comment testing) ───────────────────── // Tasks don't have a detail page with comments, so we create an Issue // of type "task" to test the comment flow. console.log('📋 Creating issue for comment testing...'); await page.goto(`${BASE_URL}/issues/new`); await page.waitForLoadState('networkidle'); const issueTitle = `Test Issue for Comment ${TS}`; await page.fill('label:has-text("Title") input', issueTitle); await page.fill('label:has-text("Description") textarea', 'Issue created for comment testing'); // Select our project await page.selectOption('label:has-text("Projects") select', { label: projectName }); // Select type "Task" await page.selectOption('label:has-text("Type") select', 'task'); await page.waitForTimeout(300); await page.click('button.btn-primary:has-text("Create")'); await page.waitForURL(`${BASE_URL}/issues`, { timeout: 10000 }); await page.waitForLoadState('networkidle'); console.log('✅ Issue created'); // ── Step 6: Open Issue → Add Comment ─────────────────────────────── console.log('💬 Opening issue to add comment...'); await page.click(`text=${issueTitle}`); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); // Verify we're on issue detail await page.waitForSelector(`h2:has-text("${issueTitle}")`, { timeout: 10000 }); const commentText = `Automated test comment ${TS}`; console.log(`Comment: ${commentText}`); await page.fill('textarea[placeholder="Add a comment..."]', commentText); await page.click('button:has-text("Submit comment")'); await page.waitForTimeout(2000); // Verify comment appears const commentEl = page.locator(`.comment p:has-text("${commentText}")`); await expect(commentEl).toBeVisible({ timeout: 10000 }); console.log('✅ Comment added and verified'); // ── Step 7: Logout (do NOT delete the project) ───────────────────── console.log('🚪 Logging out...'); await page.click('button:has-text("Log out")'); await page.waitForFunction(() => !localStorage.getItem('token'), { timeout: 10000 }); await page.waitForSelector('button:has-text("Log in")', { timeout: 10000 }); console.log('✅ Logged out'); console.log('🎉 All task & comment tests passed!'); }); });