// @ts-check import { test, expect } from '@playwright/test'; import { loadTestUser } from '../utils/testUser.js'; test.describe('@p0 Comparison: add → duplicate blocked → remove → persists', () => { test.setTimeout(15000); test('add one, block duplicate, remove and persist', async ({ page }) => { const user = loadTestUser(); // Helpers async function closeAnyOverlay() { const overlay = page.locator('div.fixed.inset-0'); if (!(await overlay.isVisible({ timeout: 500 }).catch(() => false))) return; const dlg = overlay.locator('div[role="dialog"], div.bg-white').first(); // Fill selects (neutral '3') if present const selects = dlg.locator('select'); const sc = await selects.count().catch(() => 0); for (let i = 0; i < sc; i++) { const sel = selects.nth(i); const has3 = await sel.locator('option[value="3"]').count().catch(() => 0); if (has3) await sel.selectOption('3'); } // Fill textbox if present const tb = dlg.locator('input, textarea, [role="textbox"]').first(); if (await tb.isVisible().catch(() => false)) await tb.fill('3'); const save = dlg.getByRole('button', { name: /^(Save|Save Ratings|Confirm|Done|OK)$/i }); if (await save.isVisible({ timeout: 500 }).catch(() => false)) await save.click(); else await page.keyboard.press('Enter'); await overlay.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {}); } async function ensureSuggestions() { const firstTile = page.locator('div.grid button').first(); if (await firstTile.isVisible({ timeout: 1500 }).catch(() => false)) return; const reloadBtn = page.getByRole('button', { name: /Reload Career Suggestions/i }); await expect(reloadBtn).toBeVisible(); await reloadBtn.click(); // Wait for cache await expect .poll(async () => { return await page.evaluate(() => { try { const s = localStorage.getItem('careerSuggestionsCache'); const arr = s ? JSON.parse(s) : []; return Array.isArray(arr) ? arr.length : 0; } catch { return 0; } }); }, { timeout: 60000, message: 'careerSuggestionsCache not populated' }) .toBeGreaterThan(0); await expect(firstTile).toBeVisible({ timeout: 10000 }); } // Sign in await page.context().clearCookies(); await page.goto('/signin', { waitUntil: 'networkidle' }); await page.getByPlaceholder('Username', { exact: true }).fill(user.username); await page.getByPlaceholder('Password', { exact: true }).fill(user.password); await page.getByRole('button', { name: /^Sign In$/ }).click(); await page.waitForURL('**/signin-landing**', { timeout: 15000 }); // Explorer await page.goto('/career-explorer', { waitUntil: 'networkidle' }); await expect(page.getByRole('heading', { name: /Explore Careers - use these tools/i })).toBeVisible(); await closeAnyOverlay(); // Ensure at least one tile is present await ensureSuggestions(); // Open first suggestion -> modal const firstTile = page.locator('div.grid button').first(); await expect(firstTile).toBeVisible({ timeout: 8000 }); const tileTitle = (await firstTile.textContent())?.replace('⚠️', '').trim() || null; await firstTile.click(); // Add to Comparison (then ratings modal Save) await expect(page.getByRole('button', { name: /Add to Comparison/i })).toBeVisible({ timeout: 15000 }); await page.getByRole('button', { name: /Add to Comparison/i }).click(); await closeAnyOverlay(); // Table row must exist for that title (fallback: any row exists) const table = page.locator('table'); await table.waitFor({ state: 'attached', timeout: 8000 }).catch(() => {}); let row = null; if (tileTitle) { const cell = table.getByText(new RegExp(tileTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')).first(); if (await cell.isVisible().catch(() => false)) row = cell.locator('xpath=ancestor::tr').first(); } if (!row) row = table.locator('tbody tr').first(); await expect(row).toBeVisible({ timeout: 8000 }); // Try to add the same career again → expect duplicate alert let sawDuplicate = false; page.once('dialog', async d => { if (/already in comparison/i.test(d.message())) sawDuplicate = true; await d.accept(); }); // Open the same modal again (either by clicking the same tile or by search commit) if (tileTitle) await firstTile.click(); else await page.locator('div.grid button').first().click(); await expect(page.getByRole('button', { name: /Add to Comparison/i })).toBeVisible({ timeout: 15000 }); await page.getByRole('button', { name: /Add to Comparison/i }).click(); await closeAnyOverlay(); expect(sawDuplicate).toBeTruthy(); // Remove the row await row.getByRole('button', { name: /^Remove$/ }).click(); // Row should disappear await expect(row).toBeHidden({ timeout: 8000 }); // Reload page → row should still be gone (persisted) await page.reload({ waitUntil: 'networkidle' }); await table.waitFor({ state: 'attached', timeout: 8000 }).catch(() => {}); if (tileTitle) { const cellAgain = table.getByText(new RegExp(tileTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')); expect(await cellAgain.isVisible().catch(() => false)).toBeFalsy(); } }); });