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
9.1 KiB
TypeScript
200 lines
9.1 KiB
TypeScript
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", "company-e2e");
|
|
|
|
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
|
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
}
|
|
|
|
test.describe("Company E2E Full Flow", () => {
|
|
test("complete company registration → OTP → login → dashboard → profile → verification", async () => {
|
|
const testEmail = `e2ecompany${randomUUID().slice(0, 8)}@test.com`;
|
|
const testPassword = "TestPassword123!";
|
|
const testCompanyName = `Test Company ${randomUUID().slice(0, 6)}`;
|
|
|
|
console.log("📧 Email:", testEmail);
|
|
console.log("🏢 Company:", testCompanyName);
|
|
|
|
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: "John", last_name: "Doe", password: testPassword, intent: "company" });
|
|
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=COMPANY");
|
|
const page = await context.newPage();
|
|
|
|
// Set auth via addInitScript BEFORE any navigation
|
|
await page.addInitScript(({ token, email, userId }) => {
|
|
localStorage.setItem("nxtgauge_access_token", token);
|
|
localStorage.setItem("nxtgauge_user", JSON.stringify({
|
|
email, roleKey: "COMPANY", role: "COMPANY", active_role: "COMPANY",
|
|
selectedProfessionalRole: "COMPANY", name: "John Doe", fullName: "John Doe", id: userId
|
|
}));
|
|
localStorage.setItem("nxtgauge_auth_user", JSON.stringify({
|
|
email, roleKey: "COMPANY", role: "COMPANY", active_role: "COMPANY",
|
|
selectedProfessionalRole: "COMPANY", name: "John Doe", fullName: "John Doe", id: userId
|
|
}));
|
|
sessionStorage.setItem("nxtgauge_access_token", token);
|
|
}, { token: accessToken, email: testEmail, userId: regData.user_id });
|
|
|
|
try {
|
|
await page.goto("/dashboard?role=COMPANY", { 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");
|
|
}
|
|
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 company profile fields");
|
|
try {
|
|
// Company profile fields: company_name, company_email, location, state, address
|
|
|
|
// Use more flexible selectors - look for inputs by their associated labels
|
|
const inputs = page.locator("input");
|
|
const count = await inputs.count();
|
|
console.log(" Total inputs found:", count);
|
|
|
|
// Try filling first few inputs (likely company_name, company_email, etc.)
|
|
for (let i = 0; i < Math.min(count, 8); 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(() => "");
|
|
console.log(` Input ${i}: placeholder="${placeholder}" id="${id}" name="${name}"`);
|
|
}
|
|
}
|
|
|
|
// Company Name - find by label association
|
|
const companyNameInput = page.locator("input").filter({ hasText: "" }).first();
|
|
if (await companyNameInput.isVisible().catch(() => false)) {
|
|
await companyNameInput.fill(testCompanyName);
|
|
console.log(" ✅ Filled company name");
|
|
}
|
|
|
|
// Try finding by label text
|
|
const labels = await page.locator("label").all();
|
|
for (const label of labels) {
|
|
const text = await label.textContent().catch(() => "");
|
|
console.log(" Label:", text);
|
|
}
|
|
|
|
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 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");
|
|
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("🏢 Company:", testCompanyName);
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
await browser.close();
|
|
});
|
|
});
|