dev1/tests/e2e/07-education-handoff.spec.mjs

179 lines
7.1 KiB
JavaScript

// tests/e2e/07-education-handoff.spec.mjs
// @ts-check
import { test, expect } from '@playwright/test';
import { loadTestUser } from '../utils/testUser.js';
test.describe('@p1 Education/Skills handoff', () => {
test.setTimeout(20000);
test('add to comparison → Plan your Education/Skills → navigates with selectedCareer stored', async ({ page }) => {
const user = loadTestUser();
const TIME = {
overlayAppear: 2000,
cache: 60000,
tile: 8000,
confirm: 7000,
route: 20000,
};
// 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 with neutral '3' (or first non-empty)
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');
else {
const opts = sel.locator('option');
const n = await opts.count().catch(() => 0);
for (let j = 0; j < n; j++) {
const v = await opts.nth(j).getAttribute('value');
if (v) { await sel.selectOption(v); break; }
}
}
}
// 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|Continue|Done|OK)/i });
const cancel = dlg.getByRole('button', { name: /(Cancel|Close)/i });
if (await save.isVisible({ timeout: 500 }).catch(() => false)) await save.click();
else if (await cancel.isVisible({ timeout: 500 }).catch(() => false)) await cancel.click();
else await page.keyboard.press('Escape');
await overlay.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {});
}
async function waitForSuggestionsCache() {
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: TIME.cache, message: 'careerSuggestionsCache not populated' })
.toBeGreaterThan(0);
}
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 closeAnyOverlay();
await reloadBtn.click();
// Let overlay mount if it appears (we don't require 100%)
const overlayText = page.getByText(/Loading Career Suggestions/i).first();
await overlayText.isVisible({ timeout: TIME.overlayAppear }).catch(() => {});
await waitForSuggestionsCache();
}
// 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();
// Make sure we have suggestions
await ensureSuggestions();
// Open a suggestion tile → CareerModal
const firstTile = page.locator('div.grid button').first();
await expect(firstTile).toBeVisible({ timeout: TIME.tile });
let clickedTitle = (await firstTile.textContent())?.replace('⚠️', '').trim() || null;
await firstTile.click();
// Prefer modal H2 for authoritative title
const modalH2 = page.getByRole('heading', { level: 2 });
if (await modalH2.isVisible({ timeout: TIME.confirm }).catch(() => false)) {
const t = await modalH2.first().textContent().catch(() => null);
if (t) clickedTitle = t.trim();
}
// Add to Comparison
await expect(page.getByRole('button', { name: /Add to Comparison/i })).toBeVisible({ timeout: TIME.confirm });
await page.getByRole('button', { name: /Add to Comparison/i }).click();
// Ratings modal → Save (must persist the row)
{
const overlay = page.locator('div.fixed.inset-0');
if (await overlay.isVisible({ timeout: 2000 }).catch(() => false)) {
const dlg = overlay.locator('div[role="dialog"], div.bg-white').first();
// Fill selects to '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 saveBtn = dlg.getByRole('button', { name: /^(Save|Save Ratings|Confirm|Done)$/i });
if (await saveBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
await saveBtn.click();
} else {
await page.keyboard.press('Enter');
}
await overlay.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {});
}
}
// Find the row for clickedTitle (fallback: first row)
const table = page.locator('table');
await table.waitFor({ state: 'attached', timeout: 5000 }).catch(() => {});
let targetRow;
if (clickedTitle) {
const cell = table.getByText(new RegExp(clickedTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')).first();
if (await cell.isVisible().catch(() => false)) {
targetRow = cell.locator('xpath=ancestor::tr').first();
}
}
if (!targetRow) {
targetRow = table.locator('tbody tr').first();
}
// Click “Plan your Education/Skills” with confirm
page.once('dialog', d => d.accept());
await targetRow.getByRole('button', { name: /Plan your Education\/Skills/i }).click();
// Route change
await page.waitForURL('**/educational-programs**', { timeout: TIME.route });
// Assert selectedCareer is stored with expected shape
const selected = await page.evaluate(() => {
try {
return JSON.parse(localStorage.getItem('selectedCareer') || 'null');
} catch { return null; }
});
expect(selected).toBeTruthy();
expect(typeof selected.soc_code).toBe('string');
expect(Array.isArray(selected.cip_code)).toBeTruthy();
});
});