nxtgauge-frontend-solid/tests/e2e/job-seeker-e2e-full.spec.ts
Ashwin Kumar Sivakumar 8801440459
All checks were successful
build-and-release / build (push) Successful in 2m25s
fix(e2e): use env-aware URLs, captcha-solving, and real Redis OTP retrieval
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>
2026-08-14 00:51:55 +05:30

224 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect, chromium } from "@playwright/test";
import { randomUUID } from "crypto";
import * as fs from "fs";
import * as path from "path";
import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
import { APP_BASE_URL } from "./helpers/env";
const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "job-seeker-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}
test.describe("Job Seeker E2E Full Flow", () => {
test("complete job seeker registration → OTP → login → dashboard → profile → verification", async () => {
const testEmail = `e2ejobseeker${randomUUID().slice(0, 8)}@test.com`;
const testPassword = "TestPassword123!";
console.log("📧 Email:", testEmail);
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== STEP 1: REGISTER VIA API ====================
console.log("\n📝 STEP 1: Register via API");
let regData: any;
try {
regData = await apiRegister({ email: testEmail, first_name: "Jane", last_name: "Smith", password: testPassword, intent: "job_seeker" });
expect(regData.user_id).toBeTruthy();
console.log(" ✅ PASS: Registration successful, user_id:", regData.user_id);
} catch (e: any) {
console.log(" ❌ FAIL: Registration failed -", e.message);
throw e;
}
// ==================== STEP 2+3: OTP VIA REDIS + VERIFY VIA API ====================
console.log("\n🔐 STEP 2+3: OTP via Redis, verify via API");
await new Promise(r => setTimeout(r, 500));
try {
const verified = await apiVerifyEmail(testEmail, regData.user_id);
expect(verified).toBe(true);
console.log(" ✅ PASS: OTP retrieved and verified!");
} catch (e: any) {
console.log(" ❌ FAIL: OTP verification failed -", e.message);
throw e;
}
// ==================== STEP 4: LOGIN VIA API ====================
console.log("\n🔑 STEP 4: Login via API");
let accessToken = "";
try {
const loginData = await apiLogin(testEmail, testPassword);
accessToken = loginData?.access_token || "";
expect(accessToken).toBeTruthy();
console.log(" ✅ PASS: Login successful, token length:", accessToken.length);
} catch (e: any) {
console.log(" ❌ FAIL: Login failed -", e.message);
throw e;
}
// ==================== STEP 5: DASHBOARD ====================
console.log("\n🌐 STEP 5: Navigate to dashboard?role=JOB_SEEKER");
const page = await context.newPage();
await page.addInitScript(({ token, email, userId }) => {
localStorage.setItem("nxtgauge_access_token", token);
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
}));
sessionStorage.setItem("nxtgauge_access_token", token);
}, { token: accessToken, email: testEmail, userId: regData.user_id });
try {
await page.goto("/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step05_dashboard.png`, fullPage: true });
console.log(" ✅ PASS: Dashboard loaded");
} catch (e: any) {
console.log(" ❌ FAIL: Dashboard load failed -", e.message);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step05_dashboard_FAIL.png`, fullPage: true });
throw e;
}
// ==================== STEP 6: PROFILE FORM ====================
console.log("\n📋 STEP 6: Navigate to profile form (click 'My Profile')");
try {
const profileBtn = page.getByRole("button", { name: /my profile/i });
const isVisible = await profileBtn.isVisible().catch(() => false);
if (isVisible) {
await profileBtn.click();
await page.waitForTimeout(3000);
} else {
console.log(" ⚠️ My Profile button not visible, trying direct navigation");
await page.goto("/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
await page.waitForTimeout(3000);
}
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06_profile_form.png`, fullPage: true });
console.log(" ✅ PASS: Profile form displayed");
} catch (e: any) {
console.log(" ⚠️ WARN: Profile navigation -", e.message);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06_profile_form.png`, fullPage: true });
}
// ==================== STEP 6B: FILL PROFILE FORM ====================
console.log("\n📝 STEP 6b: Fill job seeker profile fields");
try {
const inputs = page.locator("input");
const count = await inputs.count();
console.log(" Total inputs found:", count);
// Log all inputs with their attributes
for (let i = 0; i < Math.min(count, 15); i++) {
const input = inputs.nth(i);
const isVisible = await input.isVisible().catch(() => false);
const isDisabled = await input.isDisabled().catch(() => false);
if (isVisible && !isDisabled) {
const placeholder = await input.getAttribute("placeholder").catch(() => "");
const id = await input.getAttribute("id").catch(() => "");
const name = await input.getAttribute("name").catch(() => "");
const type = await input.getAttribute("type").catch(() => "");
console.log(` Input ${i}: type="${type}" placeholder="${placeholder}" id="${id}" name="${name}"`);
}
}
// Try to find and fill common job seeker fields
const nameFields = ["first_name", "firstName", "First Name", "first-name"];
const lastNameFields = ["last_name", "lastName", "Last Name", "last-name"];
const emailField = page.locator('input[name="email"], input[id="email"]').first();
const phoneField = page.locator('input[name="phone"], input[id="phone"], input[placeholder*="phone" i]').first();
const locationField = page.locator('input[name="location"], input[id="location"], input[placeholder*="location" i]').first();
if (await emailField.isVisible().catch(() => false)) {
console.log(" Email field already has:", await emailField.inputValue().catch(() => ""));
}
// Fill phone
if (await phoneField.isVisible().catch(() => false)) {
await phoneField.fill("9876543210");
console.log(" ✅ Filled phone");
}
// Fill location
if (await locationField.isVisible().catch(() => false)) {
await locationField.fill("Chennai");
console.log(" ✅ Filled location");
}
// Try textareas too
const textareas = page.locator("textarea");
const textareaCount = await textareas.count();
console.log(" Total textareas found:", textareaCount);
for (let i = 0; i < textareaCount; i++) {
const ta = textareas.nth(i);
const isVisible = await ta.isVisible().catch(() => false);
if (isVisible) {
const placeholder = await ta.getAttribute("placeholder").catch(() => "");
const name = await ta.getAttribute("name").catch(() => "");
console.log(` Textarea ${i}: placeholder="${placeholder}" name="${name}"`);
}
}
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06b_profile_inputs.png`, fullPage: true });
console.log(" ✅ Profile form analyzed");
} catch (e: any) {
console.log(" ⚠️ WARN: Could not fill profile -", e.message);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06b_profile_fill_FAIL.png`, fullPage: true });
}
// ==================== STEP 7: SUBMIT VERIFICATION ====================
console.log("\n📤 STEP 7: Submit verification");
try {
const submitBtn = page.getByRole("button", { name: /submit for verification/i });
const btnVisible = await submitBtn.isVisible().catch(() => false);
if (btnVisible) {
const isDisabled = await submitBtn.isDisabled().catch(() => true);
if (isDisabled) {
console.log(" ⚠️ INFO: Submit button disabled - profile needs more fields filled");
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_submit_disabled.png`, fullPage: true });
// Try Documents tab
const docsTab = page.getByRole("tab", { name: /documents/i }).first();
if (await docsTab.isVisible().catch(() => false)) {
await docsTab.click();
await page.waitForTimeout(2000);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_documents_tab.png`, fullPage: true });
console.log(" ✅ Switched to Documents tab");
}
} else {
await submitBtn.click();
await page.waitForTimeout(3000);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_verification_submitted.png`, fullPage: true });
console.log(" ✅ PASS: Verification submitted!");
}
} else {
console.log(" ⚠️ INFO: Submit button not found - checking verification status");
const statusText = await page.locator("body").innerText().catch(() => "");
if (statusText.includes("Submitted") || statusText.includes("verification")) {
console.log(" ✅ Already on verification page or already submitted");
}
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_submit_notfound.png`, fullPage: true });
}
} catch (e: any) {
console.log(" ⚠️ WARN: Verification submit -", e.message);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_verification_FAIL.png`, fullPage: true });
}
await page.screenshot({ path: `${SCREENSHOT_DIR}/step99_final.png`, fullPage: true });
console.log("\n========== TEST COMPLETE ==========");
console.log("📸 Screenshots:", SCREENSHOT_DIR);
console.log("📧 Test email:", testEmail);
console.log("🔑 Password:", testPassword);
console.log("👤 Name: Jane Smith");
await new Promise(r => setTimeout(r, 1000));
await browser.close();
});
});