2026-04-26 23:58:43 +02:00
|
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
|
|
|
|
|
|
type SignupScenario = {
|
|
|
|
|
name: string;
|
|
|
|
|
signupUrl: string;
|
|
|
|
|
dashboardRole: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const portfolioPrefixByRole: Record<string, string> = {
|
|
|
|
|
PHOTOGRAPHER: "photographers",
|
|
|
|
|
MAKEUP_ARTIST: "makeup-artists",
|
|
|
|
|
TUTOR: "tutors",
|
|
|
|
|
DEVELOPER: "developers",
|
|
|
|
|
VIDEO_EDITOR: "video-editors",
|
|
|
|
|
UGC_CONTENT_CREATOR: "ugc-content-creators",
|
|
|
|
|
GRAPHIC_DESIGNER: "graphic-designers",
|
|
|
|
|
SOCIAL_MEDIA_MANAGER: "social-media-managers",
|
|
|
|
|
FITNESS_TRAINER: "fitness-trainers",
|
|
|
|
|
CATERING_SERVICES: "catering-services",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const scenarios: SignupScenario[] = [
|
|
|
|
|
{ name: "company", signupUrl: "/signup?intent=company", dashboardRole: "COMPANY" },
|
|
|
|
|
{ name: "customer", signupUrl: "/signup?intent=customer", dashboardRole: "CUSTOMER" },
|
|
|
|
|
{ name: "job seeker", signupUrl: "/signup?intent=job_seeker", dashboardRole: "JOB_SEEKER" },
|
|
|
|
|
{ name: "photographer", signupUrl: "/signup?intent=professional&role=photographer", dashboardRole: "PHOTOGRAPHER" },
|
|
|
|
|
{ name: "makeup artist", signupUrl: "/signup?intent=professional&role=makeup_artist", dashboardRole: "MAKEUP_ARTIST" },
|
|
|
|
|
{ name: "tutor", signupUrl: "/signup?intent=professional&role=tutor", dashboardRole: "TUTOR" },
|
|
|
|
|
{ name: "developer", signupUrl: "/signup?intent=professional&role=developer", dashboardRole: "DEVELOPER" },
|
|
|
|
|
{ name: "video editor", signupUrl: "/signup?intent=professional&role=video_editor", dashboardRole: "VIDEO_EDITOR" },
|
|
|
|
|
{ name: "ugc content creator", signupUrl: "/signup?intent=professional&role=ugc_content_creator", dashboardRole: "UGC_CONTENT_CREATOR" },
|
|
|
|
|
{ name: "graphic designer", signupUrl: "/signup?intent=professional&role=graphic_designer", dashboardRole: "GRAPHIC_DESIGNER" },
|
|
|
|
|
{ name: "social media manager", signupUrl: "/signup?intent=professional&role=social_media_manager", dashboardRole: "SOCIAL_MEDIA_MANAGER" },
|
|
|
|
|
{ name: "fitness trainer", signupUrl: "/signup?intent=professional&role=fitness_trainer", dashboardRole: "FITNESS_TRAINER" },
|
|
|
|
|
{ name: "catering services", signupUrl: "/signup?intent=professional&role=catering_services", dashboardRole: "CATERING_SERVICES" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const profileSeedComplete: Record<string, string> = {
|
|
|
|
|
first_name: "Ari",
|
|
|
|
|
last_name: "Tester",
|
|
|
|
|
email: "ari.tester@nxtgauge.test",
|
|
|
|
|
phone: "9999999999",
|
|
|
|
|
location: "Chennai",
|
|
|
|
|
city: "Chennai",
|
|
|
|
|
area: "Anna Nagar",
|
|
|
|
|
state: "Tamil Nadu",
|
|
|
|
|
address_line_1: "No 12, Lake View",
|
|
|
|
|
company_name: "Nxtgauge QA Labs",
|
|
|
|
|
business_name: "Nxtgauge Catering",
|
|
|
|
|
owner_name: "Ari Tester",
|
|
|
|
|
company_email: "ops@nxtgauge.test",
|
|
|
|
|
registration_doc: "company-registration.pdf",
|
|
|
|
|
aadhar_doc: "id-proof.pdf",
|
|
|
|
|
address_proof: "address-proof.pdf",
|
|
|
|
|
portfolio_ownership_proof: "portfolio-proof.pdf",
|
|
|
|
|
qualification_proof: "qualification-proof.pdf",
|
|
|
|
|
professional_certifications: "certifications.pdf",
|
|
|
|
|
certification_doc: "trainer-certification.pdf",
|
|
|
|
|
fssai_license: "fssai.pdf",
|
|
|
|
|
gst_doc: "gst-certificate.pdf",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const requiredDocKeyByRole: Record<string, string> = {
|
|
|
|
|
COMPANY: "registration_doc",
|
|
|
|
|
PHOTOGRAPHER: "aadhar_doc",
|
|
|
|
|
MAKEUP_ARTIST: "aadhar_doc",
|
|
|
|
|
TUTOR: "aadhar_doc",
|
|
|
|
|
FITNESS_TRAINER: "aadhar_doc",
|
|
|
|
|
CATERING_SERVICES: "aadhar_doc",
|
|
|
|
|
CUSTOMER: "aadhar_doc",
|
|
|
|
|
JOB_SEEKER: "aadhar_doc",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function missingDocProfile(role: string): Record<string, string> {
|
|
|
|
|
const next = { ...profileSeedComplete };
|
|
|
|
|
const key = requiredDocKeyByRole[role] || "aadhar_doc";
|
|
|
|
|
delete next[key];
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function wireApiMock(
|
|
|
|
|
page: any,
|
|
|
|
|
scenario: SignupScenario,
|
|
|
|
|
unique: number,
|
|
|
|
|
profileData: Record<string, string>,
|
|
|
|
|
submitRef: { count: number },
|
|
|
|
|
portfolioReady: boolean
|
|
|
|
|
) {
|
|
|
|
|
const portfolioPrefix = portfolioPrefixByRole[scenario.dashboardRole];
|
|
|
|
|
|
|
|
|
|
await page.route("**/api/**", async (route: any) => {
|
|
|
|
|
const url = new URL(route.request().url());
|
|
|
|
|
const path = url.pathname;
|
|
|
|
|
|
|
|
|
|
if (path.endsWith("/api/auth/check-email")) {
|
|
|
|
|
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ exists: false }) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.endsWith("/api/auth/register")) {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify({ success: true, user: { id: `${scenario.dashboardRole}-${unique}` } }),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.endsWith("/api/auth/verify-email")) {
|
|
|
|
|
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true }) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/runtime-config")) {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
role: scenario.dashboardRole,
|
|
|
|
|
dashboard_config: {
|
|
|
|
|
sidebar_items: ["My Dashboard", "My Profile", "Verification Status", "Help Center"],
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/api/me/verification-status")) {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify({ status: "NOT_SUBMITTED", document_request: null }),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/api/profile/submit-for-verification")) {
|
|
|
|
|
submitRef.count += 1;
|
|
|
|
|
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/api/profile") && route.request().method() === "GET") {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify({ profile_data: profileData }),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/api/profile") && route.request().method() === "PATCH") {
|
|
|
|
|
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true }) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path.includes("/api/jobseeker/profile/me") && route.request().method() === "GET") {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
custom_data: {
|
|
|
|
|
job_seeker_portfolio: portfolioReady
|
|
|
|
|
? {
|
|
|
|
|
headline: "Frontend Engineer",
|
|
|
|
|
summary: "Builds product-grade web apps",
|
|
|
|
|
education: "B.Tech CSE",
|
|
|
|
|
workExperience: "3 years in SaaS",
|
|
|
|
|
skills: "SolidJS, TypeScript",
|
|
|
|
|
}
|
|
|
|
|
: {},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (portfolioPrefix && path.includes(`/api/${portfolioPrefix}/portfolio/me`)) {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
status: 200,
|
|
|
|
|
contentType: "application/json",
|
|
|
|
|
body: JSON.stringify(
|
|
|
|
|
portfolioReady
|
|
|
|
|
? [{ id: `portfolio-${unique}`, title: "Showcase Project", description: "Media sample" }]
|
|
|
|
|
: []
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ data: [] }) });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runSignup(page: any, scenario: SignupScenario, email: string) {
|
|
|
|
|
await page.goto(`http://localhost:3000${scenario.signupUrl}`);
|
|
|
|
|
|
|
|
|
|
await page.fill("#first-name", "Ari");
|
|
|
|
|
await page.fill("#last-name", "Tester");
|
|
|
|
|
await page.fill("#email", email);
|
|
|
|
|
await page.fill("#password", "TestPassword123!");
|
|
|
|
|
await page.fill("#confirm-password", "TestPassword123!");
|
|
|
|
|
await page.fill("#captcha", "AAAAAA");
|
|
|
|
|
await page.check('input[type="checkbox"]');
|
|
|
|
|
|
|
|
|
|
await page.click("button.auth-submit-btn");
|
|
|
|
|
await expect(page.getByText("Verify Email")).toBeVisible();
|
|
|
|
|
|
|
|
|
|
await page.fill("#otp-0", "1");
|
|
|
|
|
await page.fill("#otp-1", "2");
|
|
|
|
|
await page.fill("#otp-2", "3");
|
|
|
|
|
await page.fill("#otp-3", "4");
|
|
|
|
|
await page.fill("#otp-4", "5");
|
|
|
|
|
await page.fill("#otp-5", "6");
|
|
|
|
|
|
|
|
|
|
await page.click("button.auth-submit-btn");
|
|
|
|
|
await page.waitForURL("**/login?verified=1");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function seedSession(page: any, scenario: SignupScenario, email: string) {
|
|
|
|
|
await page.evaluate(
|
|
|
|
|
({ role, mail }) => {
|
|
|
|
|
sessionStorage.setItem("nxtgauge_access_token", "pw-test-token");
|
|
|
|
|
const payload = {
|
|
|
|
|
email: mail,
|
|
|
|
|
roleKey: role,
|
|
|
|
|
role: role,
|
|
|
|
|
selectedProfessionalRole: role,
|
|
|
|
|
fullName: "Ari Tester",
|
|
|
|
|
name: "Ari Tester",
|
|
|
|
|
};
|
|
|
|
|
localStorage.setItem("nxtgauge_signup_profile_v1", JSON.stringify(payload));
|
|
|
|
|
localStorage.setItem("nxtgauge_auth_user", JSON.stringify(payload));
|
|
|
|
|
localStorage.setItem("nxtgauge_user", JSON.stringify(payload));
|
|
|
|
|
|
|
|
|
|
const professionalRoles = [
|
|
|
|
|
"PHOTOGRAPHER",
|
|
|
|
|
"MAKEUP_ARTIST",
|
|
|
|
|
"TUTOR",
|
|
|
|
|
"DEVELOPER",
|
|
|
|
|
"VIDEO_EDITOR",
|
|
|
|
|
"UGC_CONTENT_CREATOR",
|
|
|
|
|
"GRAPHIC_DESIGNER",
|
|
|
|
|
"SOCIAL_MEDIA_MANAGER",
|
|
|
|
|
"FITNESS_TRAINER",
|
|
|
|
|
"CATERING_SERVICES",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (professionalRoles.includes(role)) {
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
`nxtgauge_portfolio_meta_${String(role).toLowerCase()}`,
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
about: "Experienced professional with strong delivery track record.",
|
|
|
|
|
services: [{ name: "Core Service", pricingType: "Fixed", amount: "₹10,000", details: "End-to-end delivery" }],
|
|
|
|
|
experience: [{ year: "2024", description: "Handled projects across multiple clients" }],
|
|
|
|
|
faqs: [{ question: "Do you provide revisions?", answer: "Yes, revisions are included." }],
|
|
|
|
|
tools: ["Industry Tool"],
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ role: scenario.dashboardRole, mail: email }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test.describe("Signup and verification submission by role", () => {
|
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
|
|
|
await page.addInitScript(() => {
|
|
|
|
|
Math.random = () => 0;
|
Update components (CaptchaCanvas, DashboardLayout, MyDashboardPage, PortfolioPage, ProfilePage), form-validation, routes (dashboard, login, signup), app.css, add e2e tests and helpers, add manual test files and config
2026-05-08 15:34:49 +02:00
|
|
|
window.__testMode = true;
|
2026-04-26 23:58:43 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const scenario of scenarios) {
|
|
|
|
|
test(`complete profile submits verification for ${scenario.name}`, async ({ page }) => {
|
|
|
|
|
const unique = Date.now();
|
|
|
|
|
const email = `qa_${scenario.dashboardRole.toLowerCase()}_${unique}@nxtgauge.test`;
|
|
|
|
|
const submitRef = { count: 0 };
|
|
|
|
|
|
|
|
|
|
await wireApiMock(page, scenario, unique, profileSeedComplete, submitRef, true);
|
|
|
|
|
await runSignup(page, scenario, email);
|
|
|
|
|
await seedSession(page, scenario, email);
|
|
|
|
|
|
|
|
|
|
await page.goto(`http://localhost:3000/dashboard?role=${scenario.dashboardRole}`);
|
|
|
|
|
await page.getByRole("button", { name: /my profile/i }).click();
|
|
|
|
|
|
|
|
|
|
const submitButton = page.getByRole("button", { name: /submit for verification/i });
|
|
|
|
|
await expect(submitButton).toBeVisible();
|
|
|
|
|
await expect(submitButton).toBeEnabled();
|
|
|
|
|
await submitButton.click();
|
|
|
|
|
|
|
|
|
|
await expect(page.getByText(/Submitted! We will review your profile/i)).toBeVisible();
|
|
|
|
|
expect(submitRef.count).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test(`missing document blocks verification submit for ${scenario.name}`, async ({ page }) => {
|
|
|
|
|
const unique = Date.now() + 1000;
|
|
|
|
|
const email = `qa_${scenario.dashboardRole.toLowerCase()}_missing_${unique}@nxtgauge.test`;
|
|
|
|
|
const submitRef = { count: 0 };
|
|
|
|
|
|
|
|
|
|
await wireApiMock(page, scenario, unique, missingDocProfile(scenario.dashboardRole), submitRef, false);
|
|
|
|
|
await runSignup(page, scenario, email);
|
|
|
|
|
await seedSession(page, scenario, email);
|
|
|
|
|
|
|
|
|
|
await page.goto(`http://localhost:3000/dashboard?role=${scenario.dashboardRole}`);
|
|
|
|
|
await page.getByRole("button", { name: /my profile/i }).click();
|
|
|
|
|
|
|
|
|
|
const submitButton = page.getByRole("button", { name: /submit for verification/i });
|
|
|
|
|
await expect(submitButton).toBeVisible();
|
|
|
|
|
await expect(submitButton).toBeDisabled();
|
|
|
|
|
await expect(page.getByRole("button", { name: /go to documents/i })).toBeVisible();
|
|
|
|
|
expect(submitRef.count).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|