TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1/**2 * End-to-end smoke test with Playwright (uses a globally installed playwright if present).3 * BASE=https://www.spinza.dev node qa/e2e.mjs4 * Flow: landing → register → recovery code → lobby → open Neon Vault → spin → rewards → leaderboard → profile → mobile viewport.5 * Screenshots land in qa/screens/.6 */7import fs from "node:fs";8import path from "node:path";9import { createRequire } from "node:module";1011const require = createRequire(import.meta.url);12let chromium;13try {14 ({ chromium } = require("playwright"));15} catch {16 ({ chromium } = require(path.join(process.env.HOME ?? "", "Desktop/Projets/apps-web/fetcha/qa/node_modules/playwright")));17}1819const BASE = (process.env.BASE ?? "http://localhost:8230").replace(/\/$/, "");20const out = path.resolve("qa/screens");21fs.mkdirSync(out, { recursive: true });22const shot = (page, name) => page.screenshot({ path: path.join(out, `${name}.png`), fullPage: false });2324const username = `qa_${Date.now().toString(36).slice(-6)}`;25const password = "qa-password-123";26let failures = 0;27const step = async (name, fn) => {28 try {29 await fn();30 console.log(`✓ ${name}`);31 } catch (e) {32 failures++;33 console.log(`✗ ${name}: ${e.message}`);34 }35};3637const browser = await chromium.launch();38const ctx = await browser.newContext({ viewport: { width: 1380, height: 900 } });39const page = await ctx.newPage();40page.on("pageerror", (e) => console.log(" pageerror:", e.message));4142await step("landing renders", async () => {43 await page.goto(BASE, { waitUntil: "networkidle" });44 if (!(await page.getByText(/PLAY FREE/i).first().isVisible())) throw new Error("no CTA");45 await shot(page, "01-landing");46});4748await step("register + recovery code", async () => {49 await page.goto(`${BASE}/register`, { waitUntil: "networkidle" });50 await page.getByLabel(/username/i).fill(username);51 const pw = page.locator('input[type="password"]');52 await pw.nth(0).fill(password);53 await pw.nth(1).fill(password);54 await page.getByRole("checkbox").first().check();55 await page.getByRole("button", { name: /create account/i }).click();56 await page.getByText(/SPZ-[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}/).waitFor({ timeout: 15000 });57 await shot(page, "02-recovery-code");58 const cb = page.getByRole("checkbox");59 if (await cb.count()) await cb.last().check();60 await page.getByRole("button", { name: /continue/i }).click();61 await page.waitForURL(/\/(\?welcome=1)?$/, { timeout: 15000 });62});6364await step("lobby shows balance 10,000 SC", async () => {65 await page.waitForLoadState("networkidle");66 await page.getByText(/10,000/).first().waitFor({ timeout: 15000 });67 await shot(page, "03-lobby");68});6970await step("open Neon Vault and spin", async () => {71 await page.goto(`${BASE}/games/neon-vault`, { waitUntil: "networkidle" });72 const spin = page.getByRole("button", { name: /^spin$/i });73 await spin.waitFor({ state: "visible", timeout: 20000 });74 await page.waitForTimeout(1500);75 await spin.click();76 await page.waitForTimeout(4500);77 await shot(page, "04-game");78 const bal = await page.locator("text=/\\d{1,3}(,\\d{3})*/").first().textContent();79 if (!bal) throw new Error("no balance");80});8182await step("rewards page", async () => {83 await page.goto(`${BASE}/rewards`, { waitUntil: "networkidle" });84 await page.getByText(/daily/i).first().waitFor();85 await shot(page, "05-rewards");86});8788await step("leaderboard + profile", async () => {89 await page.goto(`${BASE}/leaderboard`, { waitUntil: "networkidle" });90 await shot(page, "06-leaderboard");91 await page.goto(`${BASE}/profile`, { waitUntil: "networkidle" });92 await page.getByText(username).first().waitFor();93 await shot(page, "07-profile");94});9596await step("mobile lobby + game", async () => {97 const m = await browser.newContext({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true, storageState: await ctx.storageState() });98 const mp = await m.newPage();99 await mp.goto(BASE, { waitUntil: "networkidle" });100 await shot(mp, "08-mobile-lobby");101 await mp.goto(`${BASE}/games/spinza-original`, { waitUntil: "networkidle" });102 await mp.waitForTimeout(2500);103 await shot(mp, "09-mobile-game");104 await m.close();105});106107await browser.close();108console.log(failures ? `${failures} step(s) failed` : "all steps passed");109process.exit(failures ? 1 : 0);110