152 lines
6.6 KiB
JavaScript
152 lines
6.6 KiB
JavaScript
import { test, expect } from '@playwright/test';
|
|
import { loadTestUser } from '../utils/testUser.js';
|
|
|
|
test.describe('@p0 Career Explorer — Reload Suggestions', () => {
|
|
test.setTimeout(40000);
|
|
|
|
test('clears cache → Reload → cache & tiles repopulated (submit_answers fired)', async ({ page }) => {
|
|
const user = loadTestUser();
|
|
const TIME = {
|
|
overlayAppear: 2000, // overlay should mount quickly
|
|
cache: 30000, // allow cold path to populate cache
|
|
tile: 8000, // find a tile soon after cache
|
|
};
|
|
|
|
// Track server calls (prove reload hits submit_answers)
|
|
let sawSubmitAnswers = false;
|
|
page.on('response', (r) => {
|
|
if (r.request().method() === 'POST' && r.url().includes('/api/onet/submit_answers')) {
|
|
sawSubmitAnswers = true;
|
|
}
|
|
});
|
|
|
|
// Helper: close any blocking overlay (priorities/meaning) by saving neutral defaults.
|
|
async function closeAnyOverlay() {
|
|
const overlay = page.locator('div.fixed.inset-0');
|
|
if (!(await overlay.isVisible({ timeout: 500 }).catch(() => false))) return;
|
|
|
|
const dialog = overlay.locator('div[role="dialog"], div.bg-white').first();
|
|
|
|
// Select first non-empty option in each <select> (if any)
|
|
const selects = dialog.locator('select');
|
|
const sc = await selects.count().catch(() => 0);
|
|
for (let i = 0; i < sc; i++) {
|
|
const sel = selects.nth(i);
|
|
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 any text/number inputs with neutral '3'
|
|
const inputs = dialog.locator('input[type="number"], input[type="text"], [role="textbox"]');
|
|
const ic = await inputs.count().catch(() => 0);
|
|
for (let i = 0; i < ic; i++) {
|
|
const inp = inputs.nth(i);
|
|
if (await inp.isVisible().catch(() => false)) {
|
|
const val = (await inp.inputValue().catch(() => '')) || '';
|
|
if (!val) await inp.fill('3');
|
|
}
|
|
}
|
|
|
|
// Save/Continue; else Cancel/Close; else Escape
|
|
const save = dialog.getByRole('button', { name: /(Save|Continue|Done|OK)/i });
|
|
const cancel = dialog.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(() => {});
|
|
}
|
|
|
|
// Helper: wait for cache > 0 (source of truth for suggestions readiness)
|
|
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);
|
|
}
|
|
|
|
// Helper: complete Interest Inventory quickly if server demands it
|
|
async function completeInventoryIfNeeded() {
|
|
// If a dialog alert fires (Playwright 'dialog' event), we cannot peek msg here reliably,
|
|
// so we proactively check the inventory route if the reload yields no cache.
|
|
await page.goto('/interest-inventory', { waitUntil: 'networkidle' });
|
|
await expect(page.getByRole('heading', { name: /Interest Inventory/i })).toBeVisible();
|
|
const randomizeBtn = page.getByRole('button', { name: /Randomize Answers/i });
|
|
if (await randomizeBtn.isVisible().catch(() => false)) {
|
|
await randomizeBtn.click();
|
|
await expect(page.getByText(/60\s*\/\s*60\s*answered/i)).toBeVisible();
|
|
} else {
|
|
for (let p = 0; p < 10; p++) {
|
|
const selects = page.locator('select');
|
|
const n = await selects.count();
|
|
for (let i = 0; i < n; i++) await selects.nth(i).selectOption('3');
|
|
if (p < 9) await page.getByRole('button', { name: /^Next$/ }).click();
|
|
}
|
|
}
|
|
await page.getByRole('button', { name: /^Submit$/ }).click();
|
|
await page.waitForURL('**/career-explorer**', { timeout: 20000 });
|
|
await closeAnyOverlay();
|
|
}
|
|
|
|
// 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 });
|
|
|
|
// Go to Career Explorer
|
|
await page.goto('/career-explorer', { waitUntil: 'networkidle' });
|
|
await expect(page.getByRole('heading', { name: /Explore Careers - use these tools/i })).toBeVisible();
|
|
await closeAnyOverlay();
|
|
|
|
// 1) Clear cache and reload page → grid should have no tiles
|
|
await page.evaluate(() => localStorage.removeItem('careerSuggestionsCache'));
|
|
await page.reload({ waitUntil: 'networkidle' });
|
|
await closeAnyOverlay();
|
|
const tile = page.locator('div.grid button').first();
|
|
// Short check: no tile should be visible yet
|
|
const tileVisiblePre = await tile.isVisible({ timeout: 1000 }).catch(() => false);
|
|
expect(tileVisiblePre).toBeFalsy();
|
|
|
|
// 2) Click Reload Career Suggestions
|
|
const reloadBtn = page.getByRole('button', { name: /Reload Career Suggestions/i });
|
|
await expect(reloadBtn).toBeVisible();
|
|
await reloadBtn.click();
|
|
|
|
// If overlay mounts, let it mount; we don't require 100% display
|
|
const overlayText = page.getByText(/Loading Career Suggestions/i).first();
|
|
await overlayText.isVisible({ timeout: TIME.overlayAppear }).catch(() => {});
|
|
|
|
// 3) Wait for suggestions cache
|
|
try {
|
|
await waitForSuggestionsCache();
|
|
} catch {
|
|
// If cache didn't populate, complete inventory once and retry reload
|
|
await completeInventoryIfNeeded();
|
|
await expect(reloadBtn).toBeVisible();
|
|
await reloadBtn.click();
|
|
await overlayText.isVisible({ timeout: TIME.overlayAppear }).catch(() => {});
|
|
await waitForSuggestionsCache();
|
|
}
|
|
|
|
// 4) Tiles should now be present
|
|
await expect(tile).toBeVisible({ timeout: TIME.tile });
|
|
|
|
// 5) Assert submit_answers POST fired during reload
|
|
expect(sawSubmitAnswers).toBeTruthy();
|
|
});
|
|
});
|