158 lines
7.2 KiB
JavaScript
158 lines
7.2 KiB
JavaScript
// @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 });
|
|
});
|
|
});
|