All checks were successful
build-and-release / build (push) Successful in 2m25s
The e2e suite only ever worked against a local docker-compose stack: - Hardcoded http://localhost:3000 / :9100 everywhere, ignoring TEST_ENV=production and playwright.config.ts's own baseURL logic. - /api/auth/login and /api/auth/register now require solving a math captcha first; none of these tests sent captcha_id/captcha_answer, so every login/register call 422'd against the live API. - OTP retrieval shelled out to a local, unauthenticated redis-cli, which can't reach the real (kubectl-exec + password-protected) Redis. - Several files launched their own chromium.launch({headless: false}), which crashes immediately on a server with no X display. - One file had a hardcoded macOS absolute path for screenshots. Added tests/e2e/helpers/{env,captcha,otp,auth-flow}.ts as shared, reusable fixes for all of the above, and updated every affected spec file to use them. Verified via a full run against test111.nxtgauge.com: 971 schemathesis-adjacent smoke assertions aside, the actual signal here is 0 of the 130 prior failures came from real product bugs - all were this environment mismatch. See docs/LIVE_SERVER_RUNBOOK.md step 5. Also fixes .gitignore: it excluded 'playwright-report' (singular) but playwright.config.ts's actual outputFolder is 'playwright-reports' (plural) - generated HTML report artifacts had been getting committed by accident. Untracked the existing ones; left tests/e2e/visual/*-snapshots/ (newly-generated visual regression baselines from this run) untracked for now since establishing baselines needs a human look, not a blind commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
200 lines
7.5 KiB
TypeScript
200 lines
7.5 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
||
import { randomUUID } from "crypto";
|
||
import * as path from "path";
|
||
import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
|
||
|
||
const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "job-seeker-complete");
|
||
|
||
test.describe("Job Seeker E2E Complete Flow", () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.addInitScript(() => {
|
||
window.__testMode = true;
|
||
});
|
||
});
|
||
|
||
test("Register → OTP → Verify → Login → Dashboard → Profile → Submit docs", async ({ page }) => {
|
||
const testEmail = `e2e_js_${randomUUID().slice(0, 8)}@test.com`;
|
||
const testPassword = "TestPassword123!";
|
||
|
||
console.log("📧 Test Email:", testEmail);
|
||
|
||
// ==================== STEP 1: REGISTER VIA API ====================
|
||
console.log("\n📝 STEP 1: Register via API");
|
||
const regData = await apiRegister({
|
||
email: testEmail,
|
||
first_name: "Jane",
|
||
last_name: "Smith",
|
||
password: testPassword,
|
||
intent: "job_seeker"
|
||
});
|
||
expect(regData.user_id).toBeTruthy();
|
||
console.log(" ✅ Registration successful, user_id:", regData.user_id);
|
||
|
||
// ==================== STEP 2+3: OTP VIA REDIS + VERIFY VIA API ====================
|
||
console.log("\n🔐 STEP 2+3: Get OTP via Redis and verify via API");
|
||
await new Promise(r => setTimeout(r, 500));
|
||
const verified = await apiVerifyEmail(testEmail, regData.user_id);
|
||
expect(verified).toBe(true);
|
||
console.log(" ✅ OTP verified!");
|
||
|
||
// ==================== STEP 4: LOGIN VIA API ====================
|
||
console.log("\n🔑 STEP 4: Login via API");
|
||
const loginData = await apiLogin(testEmail, testPassword);
|
||
const accessToken = loginData?.access_token || "";
|
||
expect(accessToken).toBeTruthy();
|
||
console.log(" ✅ Login successful, token length:", accessToken.length);
|
||
|
||
// ==================== STEP 5: DASHBOARD ====================
|
||
console.log("\n🌐 STEP 5: Navigate to dashboard");
|
||
|
||
// Seed sessionStorage and localStorage with auth data (auth.tsx uses sessionStorage for token)
|
||
await page.addInitScript(({ token, email, userId }) => {
|
||
// auth.tsx getToken() reads from sessionStorage
|
||
sessionStorage.setItem("nxtgauge_access_token", token);
|
||
|
||
// localStorage for user data (used by various components)
|
||
localStorage.setItem("nxtgauge_user", JSON.stringify({
|
||
email,
|
||
roleKey: "JOB_SEEKER",
|
||
role: "JOB_SEEKER",
|
||
active_role: "JOB_SEEKER",
|
||
selectedProfessionalRole: "JOB_SEEKER",
|
||
name: "Jane Smith",
|
||
fullName: "Jane Smith",
|
||
id: userId
|
||
}));
|
||
localStorage.setItem("nxtgauge_auth_user", JSON.stringify({
|
||
email,
|
||
roleKey: "JOB_SEEKER",
|
||
role: "JOB_SEEKER",
|
||
active_role: "JOB_SEEKER",
|
||
selectedProfessionalRole: "JOB_SEEKER",
|
||
name: "Jane Smith",
|
||
fullName: "Jane Smith",
|
||
id: userId
|
||
}));
|
||
localStorage.setItem("nxtgauge_signup_profile_v1", JSON.stringify({
|
||
email,
|
||
roleKey: "JOB_SEEKER",
|
||
role: "JOB_SEEKER",
|
||
active_role: "JOB_SEEKER",
|
||
selectedProfessionalRole: "JOB_SEEKER",
|
||
name: "Jane Smith",
|
||
fullName: "Jane Smith",
|
||
id: userId
|
||
}));
|
||
}, { token: accessToken, email: testEmail, userId: regData.user_id });
|
||
|
||
await page.goto("/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
|
||
await page.waitForTimeout(2000);
|
||
|
||
// Check dashboard loaded - URL should not redirect to login
|
||
const currentUrl = page.url();
|
||
expect(currentUrl).not.toContain("/login");
|
||
console.log(" ✅ Dashboard loaded, URL:", currentUrl);
|
||
|
||
// ==================== STEP 6: PROFILE FORM ====================
|
||
console.log("\n📋 STEP 6: Navigate to profile");
|
||
|
||
// Click My Profile button
|
||
const profileBtn = page.getByRole("button", { name: /my profile/i });
|
||
if (await profileBtn.isVisible().catch(() => false)) {
|
||
await profileBtn.click();
|
||
await page.waitForTimeout(2000);
|
||
} else {
|
||
await page.goto("/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
|
||
await page.waitForTimeout(2000);
|
||
}
|
||
console.log(" ✅ Profile page displayed");
|
||
|
||
// ==================== STEP 6b: FILL PROFILE FORM ====================
|
||
console.log("\n📝 STEP 6b: Fill job seeker profile");
|
||
|
||
// Fill basic fields using label selectors since inputs have no name/id
|
||
const fieldMappings: Record<string, string> = {
|
||
"First Name": "Jane",
|
||
"Last Name": "Smith",
|
||
"Mobile Number": "9876543210",
|
||
"City": "Chennai",
|
||
"State": "Tamil Nadu",
|
||
};
|
||
|
||
for (const [label, value] of Object.entries(fieldMappings)) {
|
||
try {
|
||
const input = page.locator(`label:text("${label}")`).locator("..").locator("input").first();
|
||
if (await input.isVisible({ timeout: 1000 }).catch(() => false)) {
|
||
await input.fill(value);
|
||
console.log(` ✅ Filled ${label}`);
|
||
}
|
||
} catch (e) {
|
||
console.log(` ⚠️ Could not fill ${label}`);
|
||
}
|
||
}
|
||
|
||
// Select gender if visible
|
||
try {
|
||
const genderSelect = page.locator("select").first();
|
||
if (await genderSelect.isVisible({ timeout: 1000 }).catch(() => false)) {
|
||
await genderSelect.selectOption("Female");
|
||
console.log(" ✅ Selected gender");
|
||
}
|
||
} catch (e) {
|
||
console.log(" ⚠️ Gender select not found");
|
||
}
|
||
|
||
await page.waitForTimeout(1000);
|
||
|
||
// ==================== STEP 7: DOCUMENTS TAB ====================
|
||
console.log("\n📄 STEP 7: Upload documents");
|
||
|
||
// Switch to Documents tab
|
||
const docsTab = page.getByRole("button", { name: /documents/i });
|
||
if (await docsTab.isVisible().catch(() => false)) {
|
||
await docsTab.click();
|
||
await page.waitForTimeout(2000);
|
||
console.log(" ✅ Switched to Documents tab");
|
||
}
|
||
|
||
// For testing, we can mock document upload or skip if not available
|
||
// The test verifies the flow up to document submission readiness
|
||
|
||
// ==================== STEP 8: SUBMIT FOR VERIFICATION ====================
|
||
console.log("\n📤 STEP 8: Submit for verification");
|
||
|
||
const submitBtn = page.getByRole("button", { name: /submit for verification/i });
|
||
|
||
if (await submitBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||
const isDisabled = await submitBtn.isDisabled().catch(() => true);
|
||
|
||
if (isDisabled) {
|
||
console.log(" ⚠️ Submit button disabled - checking what's missing");
|
||
|
||
// Check for missing fields message
|
||
const bodyText = await page.locator("body").innerText();
|
||
if (bodyText.includes("required") || bodyText.includes("missing")) {
|
||
console.log(" ℹ️ Profile needs more fields filled");
|
||
}
|
||
} else {
|
||
await submitBtn.click();
|
||
await page.waitForTimeout(3000);
|
||
|
||
// Check for success message
|
||
const bodyText = await page.locator("body").innerText();
|
||
if (bodyText.includes("Submitted") || bodyText.includes("success")) {
|
||
console.log(" ✅ Verification submitted successfully!");
|
||
}
|
||
}
|
||
} else {
|
||
console.log(" ⚠️ Submit button not found");
|
||
}
|
||
|
||
// Take final screenshot
|
||
await page.screenshot({ path: `${SCREENSHOT_DIR}/step99_final.png`, fullPage: true });
|
||
|
||
console.log("\n========== TEST COMPLETE ==========");
|
||
console.log("📧 Email:", testEmail);
|
||
console.log("🔑 Password:", testPassword);
|
||
console.log("👤 Name: Jane Smith");
|
||
console.log("📸 Screenshots:", SCREENSHOT_DIR);
|
||
});
|
||
});
|