240 lines
9.3 KiB
JavaScript
240 lines
9.3 KiB
JavaScript
// @ts-check
|
||
import { test, expect } from '@playwright/test';
|
||
import { loadTestUser } from '../utils/testUser.js';
|
||
|
||
test.describe('@p1 Profile Editors — Career, Financial, and College', () => {
|
||
test.setTimeout(90_000);
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
const u = loadTestUser();
|
||
|
||
// Premium gate that App.js / PremiumRoute actually uses
|
||
await page.route(
|
||
/\/api\/user-profile\?fields=.*(firstname|is_premium|is_pro_premium).*/i,
|
||
async route => {
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({ firstname: 'Tester', is_premium: 1, is_pro_premium: 0 })
|
||
});
|
||
}
|
||
);
|
||
|
||
// Real sign-in (single session per test)
|
||
await page.context().clearCookies();
|
||
await page.goto('/signin', { waitUntil: 'domcontentloaded' });
|
||
await page.getByPlaceholder('Username', { exact: true }).fill(u.username);
|
||
await page.getByPlaceholder('Password', { exact: true }).fill(u.password);
|
||
await page.getByRole('button', { name: /^Sign In$/ }).click();
|
||
await page.waitForURL('**/signin-landing**', { timeout: 15_000 });
|
||
});
|
||
|
||
// -------- Career profile (edit) --------
|
||
test('CareerProfileList → edit first profile title and save', async ({ page }) => {
|
||
// Ensure at least one
|
||
await page.request.post('/api/premium/career-profile', {
|
||
data: { career_name: 'QA Scenario', status: 'planned', start_date: '2025-09-01' }
|
||
});
|
||
|
||
await page.goto('/profile/careers', { waitUntil: 'domcontentloaded' });
|
||
await expect(page.getByRole('heading', { name: 'Career Profiles' })).toBeVisible();
|
||
|
||
const editLinks = page.locator('a', { hasText: /^edit$/i });
|
||
await editLinks.first().click();
|
||
|
||
await expect(page.getByRole('heading', { name: /Edit Career Profile|New Career Profile/i }))
|
||
.toBeVisible({ timeout: 15_000 });
|
||
|
||
const title = page.getByLabel(/Scenario Title/i);
|
||
const prev = await title.inputValue().catch(() => 'Scenario');
|
||
await title.fill((prev || 'Scenario') + ' — test');
|
||
await page.getByRole('button', { name: /^Save$/ }).click();
|
||
|
||
await expect(page).toHaveURL(/\/career-roadmap\/[a-z0-9-]+$/i, { timeout: 20_000 });
|
||
});
|
||
|
||
// -------- Financial profile (save) --------
|
||
test('FinancialProfileForm saves and returns', async ({ page }) => {
|
||
await page.goto('/profile/financial', { waitUntil: 'domcontentloaded' });
|
||
await expect(page.getByRole('heading', { name: /Financial Profile/i })).toBeVisible({ timeout: 10_000 });
|
||
|
||
await page.getByLabel(/Current.*Annual.*Salary/i).fill('55000');
|
||
await page.getByLabel(/Monthly.*Living.*Expenses/i).fill('1800');
|
||
await page.getByLabel(/Monthly.*Debt.*Payments/i).fill('200');
|
||
await page.getByLabel(/To.*Emergency.*\(%\)/i).fill('40'); // 60 to retirement auto-computed
|
||
|
||
await page.getByRole('button', { name: /^Save$/ }).click();
|
||
await expect(page).not.toHaveURL(/\/profile\/financial$/i, { timeout: 15_000 });
|
||
});
|
||
|
||
// Helper: commit autosuggest that sometimes needs two selects
|
||
async function commitAutosuggest(page, input, text) {
|
||
await input.click();
|
||
await input.fill(text);
|
||
await page.keyboard.press('ArrowDown').catch(() => {});
|
||
await page.keyboard.press('Enter').catch(() => {});
|
||
await input.blur();
|
||
const v1 = (await input.inputValue()).trim();
|
||
if (v1.toLowerCase() === text.toLowerCase()) {
|
||
// Some nested lists require a second confirmation
|
||
await input.click();
|
||
await page.keyboard.press('ArrowDown').catch(() => {});
|
||
await page.keyboard.press('Enter').catch(() => {});
|
||
await input.blur();
|
||
}
|
||
}
|
||
|
||
// ---- College helpers (put near the top of the file) ----
|
||
async function commitAutosuggest(page, input, text) {
|
||
// Type and try first commit
|
||
await input.click();
|
||
await input.fill(text);
|
||
await page.keyboard.press('ArrowDown').catch(() => {});
|
||
await page.keyboard.press('Enter').catch(() => {});
|
||
await input.blur();
|
||
|
||
// If typed value still equals exactly what we wrote, some lists need a second confirm
|
||
const v1 = (await input.inputValue()).trim();
|
||
if (v1.toLowerCase() === text.toLowerCase()) {
|
||
await input.click();
|
||
await page.keyboard.press('ArrowDown').catch(() => {});
|
||
await page.keyboard.press('Enter').catch(() => {});
|
||
await input.blur();
|
||
}
|
||
}
|
||
|
||
async function pickDegree(page) {
|
||
const select = page.getByLabel(/Degree Type/i);
|
||
try {
|
||
await select.selectOption({ label: "Bachelor's Degree" });
|
||
return;
|
||
} catch {}
|
||
const second = select.locator('option').nth(1); // skip placeholder
|
||
await second.waitFor({ state: 'attached', timeout: 10_000 });
|
||
const val = await second.getAttribute('value');
|
||
if (val) await select.selectOption(val);
|
||
}
|
||
|
||
// Known-good school/program pair from your dataset
|
||
const SCHOOL = 'Alabama A & M University';
|
||
const PROGRAM = 'Agriculture, General.';
|
||
|
||
// ---- CollegeProfileForm — create NEW plan (autosuggests must appear, then save) ----
|
||
test('CollegeProfileForm — create new plan (autosuggests + degree + save)', async ({ page, request }) => {
|
||
// Fail the test if any unexpected alert shows
|
||
page.on('dialog', d => {
|
||
throw new Error(`Unexpected dialog: "${d.message()}"`);
|
||
});
|
||
|
||
// Create a scenario to attach the college plan
|
||
const scen = await request.post('/api/premium/career-profile', {
|
||
data: { career_name: 'QA College Plan (new)', status: 'planned', start_date: '2025-09-01' }
|
||
});
|
||
const { career_profile_id } = await scen.json();
|
||
|
||
await page.goto(`/profile/college/${career_profile_id}/new`, { waitUntil: 'domcontentloaded' });
|
||
await expect(page.getByRole('heading', { name: /College Plan|Edit College Plan|New College/i }))
|
||
.toBeVisible({ timeout: 15_000 });
|
||
|
||
const SCHOOL = 'Alabama A & M University';
|
||
const PROGRAM = 'Agriculture, General.';
|
||
|
||
const schoolBox = page.getByLabel(/School Name/i);
|
||
const programBox = page.getByLabel(/Major.*Program.*Name/i);
|
||
|
||
// Type partial and assert autosuggest options exist (prove the dropdown is presented)
|
||
await schoolBox.fill('Alabama A &');
|
||
await expect.poll(async () => {
|
||
return await page.locator('#school-suggestions option').count();
|
||
}, { timeout: 5000 }).toBeGreaterThan(0);
|
||
|
||
|
||
// Commit school programmatically with double-confirm helper
|
||
await commitAutosuggest(page, schoolBox, SCHOOL);
|
||
|
||
// Program autosuggest must be present too
|
||
await programBox.fill('Agri');
|
||
await expect.poll(async () => {
|
||
return await page.locator('#school-suggestions option').count();
|
||
}, { timeout: 5000 }).toBeGreaterThan(0);
|
||
|
||
|
||
// Commit program
|
||
await commitAutosuggest(page, programBox, PROGRAM);
|
||
|
||
// Pick degree + set expected graduation
|
||
await pickDegree(page);
|
||
const grad = page.getByLabel(/Expected Graduation Date/i);
|
||
if (await grad.isVisible().catch(() => false)) await grad.fill('2027-06-01');
|
||
|
||
// Save
|
||
await page.getByRole('button', { name: /^Save$/i }).click();
|
||
|
||
// Should not remain stuck on /new (and no alerts were raised)
|
||
await expect(page).not.toHaveURL(/\/new$/i, { timeout: 10_000 });
|
||
});
|
||
|
||
// ---- CollegeProfileForm — EDIT existing plan (autosuggests + degree + save) ----
|
||
test('CollegeProfileForm — edit existing plan (autosuggests + degree + save)', async ({ page, request }) => {
|
||
// Fail the test if any unexpected alert shows
|
||
page.on('dialog', d => {
|
||
throw new Error(`Unexpected dialog: "${d.message()}"`);
|
||
});
|
||
|
||
// Create a scenario and seed a minimal college plan
|
||
const scen = await request.post('/api/premium/career-profile', {
|
||
data: { career_name: 'QA College Plan (edit)', status: 'planned', start_date: '2025-09-01' }
|
||
});
|
||
const { career_profile_id } = await scen.json();
|
||
|
||
const SCHOOL = 'Alabama A & M University';
|
||
const PROGRAM = 'Agriculture, General.';
|
||
|
||
await request.post('/api/premium/college-profile', {
|
||
data: {
|
||
career_profile_id,
|
||
selected_school: SCHOOL,
|
||
selected_program: PROGRAM,
|
||
program_type: "Bachelor's Degree",
|
||
expected_graduation: '2027-06-01',
|
||
academic_calendar: 'semester',
|
||
credit_hours_per_year: 30,
|
||
interest_rate: 5.5,
|
||
loan_term: 10
|
||
}
|
||
});
|
||
|
||
await page.goto(`/profile/college/${career_profile_id}/edit`, { waitUntil: 'domcontentloaded' });
|
||
await expect(page.getByRole('heading', { name: /College Plan|Edit College Plan/i }))
|
||
.toBeVisible({ timeout: 15_000 });
|
||
|
||
const schoolBox = page.getByLabel(/School Name/i);
|
||
const programBox = page.getByLabel(/Major.*Program.*Name/i);
|
||
|
||
// Assert autosuggest presents options on partial typing
|
||
await schoolBox.fill('Alabama A &');
|
||
await expect.poll(async () => {
|
||
return await page.locator('#school-suggestions option').count();
|
||
}, { timeout: 5000 }).toBeGreaterThan(0);
|
||
|
||
// Recommit the same school/program (exercise autosuggest again)
|
||
await commitAutosuggest(page, schoolBox, SCHOOL);
|
||
await programBox.fill('Agri');
|
||
await expect.poll(async () => {
|
||
return await page.locator('#school-suggestions option').count();
|
||
}, { timeout: 5000 }).toBeGreaterThan(0);
|
||
await commitAutosuggest(page, programBox, PROGRAM);
|
||
|
||
await pickDegree(page);
|
||
const grad = page.getByLabel(/Expected Graduation Date/i);
|
||
if (await grad.isVisible().catch(() => false)) await grad.fill('2028-05-01');
|
||
|
||
await page.getByRole('button', { name: /^Save$/i }).click();
|
||
|
||
// No error popup was allowed; we just assert we’re not showing the error text
|
||
await expect(page.getByText(/Please pick a school from the list/i))
|
||
.toHaveCount(0, { timeout: 2000 })
|
||
.catch(() => {});
|
||
});
|
||
});
|