// @ts-nocheck import { test, expect } from '@playwright/test'; import { loadTestUser } from '../utils/testUser.js'; const j = (o) => JSON.stringify(o); test.describe('@p1 CareerCoach — Quick Actions (49)', () => { test.setTimeout(20000); test('Networking Plan + Interview Help show coach replies', async ({ page, request }) => { const u = loadTestUser(); // ── Premium/user-profile gate (include area/state so downstream fetches are happy) await page.route( /\/api\/user-profile\?fields=.*(firstname|is_premium|is_pro_premium|area|state).*/i, r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ firstname: 'Tester', is_premium: 1, is_pro_premium: 0, area: 'Atlanta-Sandy Springs-Roswell, GA', state: 'GA', career_situation: 'planning', key_skills: 'javascript, communication' }) }) ); // ── Sign in (no storage-state bypass) 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: 15000 }); // ── Seed a scenario profile server-side const scen = await request.post('/api/premium/career-profile', { data: { career_name: 'Software Developers', status: 'planned', start_date: '2025-01-01' } }); const { career_profile_id } = await scen.json(); // ── Minimal CareerRoadmap deps await page.route(new RegExp(`/api/premium/career-profile/${career_profile_id}$`, 'i'), r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ id: career_profile_id, career_name: 'Software Developers', scenario_title: 'Software Developers', soc_code: '15-1252.00', status: 'planned', career_goals: '1. Improve networking\n2. Prepare for interviews', start_date: '2025-01-01', college_enrollment_status: 'not_enrolled' }) }) ); await page.route(/\/api\/premium\/financial-profile$/i, r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ current_salary: 90000, additional_income: 0, monthly_expenses: 2500, monthly_debt_payments: 300, retirement_savings: 10000, emergency_fund: 3000, retirement_contribution: 300, emergency_contribution: 200, extra_cash_emergency_pct: 50, extra_cash_retirement_pct: 50 }) })); await page.route(new RegExp(`/api/premium/college-profile\\?careerProfileId=${career_profile_id}$`, 'i'), r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ college_enrollment_status: 'not_enrolled' }) }) ); // Salary/projections to keep UI calm (not central to this test) await page.route(/\/api\/salary\?*/i, r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ regional: null, national: null }) })); await page.route(/\/api\/projections\/15-1252\?state=.*/i, r => r.fulfill({ status: 200, contentType: 'application/json', body: j({}) })); // Milestones list empty (not the focus) await page.route(new RegExp(`/api/premium/milestones\\?careerProfileId=${career_profile_id}$`, 'i'), r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ milestones: [] }) }) ); // ── Coach thread plumbing let coachThreadId = 't-123'; await page.route(/\/api\/premium\/coach\/chat\/threads$/i, async r => { if (r.request().method() === 'GET') { return r.fulfill({ status: 200, contentType: 'application/json', body: j({ threads: [] }) }); } if (r.request().method() === 'POST') { return r.fulfill({ status: 200, contentType: 'application/json', body: j({ id: coachThreadId }) }); } return r.fallback(); }); await page.route(new RegExp(`/api/premium/coach/chat/threads/${coachThreadId}$`, 'i'), r => r.fulfill({ status: 200, contentType: 'application/json', body: j({ messages: [] }) }) ); // POST messages — simulate small OpenAI delay, return deterministic reply await page.route(new RegExp(`/api/premium/coach/chat/threads/${coachThreadId}/messages$`, 'i'), async r => { // tiny delay to surface “Coach is typing…” await new Promise(res => setTimeout(res, 350)); let body = {}; try { body = await r.request().postDataJSON(); } catch {} // Always return a deterministic but generic reply (we won't assert exact text) return r.fulfill({ status: 200, contentType: 'application/json', body: j({ reply: 'Coach reply.' }) }); }); // ── Go to Roadmap await page.goto(`/career-roadmap/${career_profile_id}`, { waitUntil: 'domcontentloaded' }); // Quick-action buttons visible await expect(page.getByRole('button', { name: 'Networking Plan' })).toBeVisible({ timeout: 6000 }); await expect(page.getByRole('button', { name: 'Job-Search Plan' })).toBeVisible({ timeout: 6000 }); await expect(page.getByRole('button', { name: 'Interview Help' })).toBeVisible({ timeout: 6000 }); await expect(page.getByRole('button', { name: /Grow Career with AI/i })).toBeVisible({ timeout: 6000 }); await expect(page.getByRole('button', { name: /Edit Goals/i })).toBeVisible({ timeout: 6000 }); // ===== Networking Plan flow ===== // Count assistant bubbles before (assistant bubbles use the gray bg class) const coachArea = page.locator('div.overflow-y-auto.border.rounded'); const assistantBubbles = coachArea.locator('div.bg-gray-200'); const beforeCount = await assistantBubbles.count(); await page.getByRole('button', { name: 'Networking Plan' }).click(); // Note message appears immediately (from component) await expect(page.getByText(/create a Networking roadmap/i)).toBeVisible({ timeout: 4000 }); // “Coach is typing…” shows then disappears after reply await expect(page.getByText('Coach is typing…')).toBeVisible({ timeout: 1200 }); await expect(page.getByText('Coach is typing…')).toHaveCount(0, { timeout: 6000 }); // Assistant bubbles increased by 1 after reply await expect(async () => { const after = await assistantBubbles.count(); expect(after).toBeGreaterThan(beforeCount); }).toPass({ timeout: 6000 }); // ===== Interview Help flow ===== const beforeInterview = await assistantBubbles.count(); await page.getByRole('button', { name: 'Interview Help' }).click(); // Intro note await expect(page.getByText(/Starting mock interview/i)).toBeVisible({ timeout: 4000 }); // Typing indicator await expect(page.getByText('Coach is typing…')).toBeVisible({ timeout: 1200 }); await expect(page.getByText('Coach is typing…')).toHaveCount(0, { timeout: 6000 }); // Assistant bubbles increased again after the interview reply await expect(async () => { const after = await assistantBubbles.count(); expect(after).toBeGreaterThan(beforeInterview); }).toPass({ timeout: 6000 }); }); });