|
| 1 | +/** Red team: 10 live packs, no house pin, real save + share. */ |
| 2 | +import { chromium } from "playwright"; |
| 3 | + |
| 4 | +const BASE = process.env.APP_URL || "http://127.0.0.1:8080"; |
| 5 | +const HOUSE = "v1.OKC.positionless.even.51.chet~dort~hartenstein~jalenw~sga"; |
| 6 | + |
| 7 | +const browser = await chromium.launch({ args: ["--no-sandbox", "--disable-gpu"] }); |
| 8 | +const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); |
| 9 | +page.setDefaultTimeout(40000); |
| 10 | + |
| 11 | +const teams = []; |
| 12 | +const walks = []; |
| 13 | + |
| 14 | +try { |
| 15 | + await page.goto(BASE + "/about", { waitUntil: "networkidle" }); |
| 16 | + const body = await page.locator("main").innerText(); |
| 17 | + if (/Thunder foil\. Same pack every time/i.test(body)) throw new Error("about still teaches the house pack"); |
| 18 | + if (/certifcate/i.test(body)) throw new Error("typo: certifcate"); |
| 19 | + const pinned = await page.getByText("Pinned walk").count(); |
| 20 | + if (pinned) throw new Error("Thunder house widget is still on the rail"); |
| 21 | + |
| 22 | + await page.goto(BASE + "/games/82-0", { waitUntil: "networkidle" }); |
| 23 | + |
| 24 | + for (let i = 0; i < 10; i += 1) { |
| 25 | + if (i > 0) { |
| 26 | + const again = page.getByRole("button", { name: /Rip again|Pull again/ }); |
| 27 | + if (await again.count()) await again.first().click(); |
| 28 | + else await page.goto(BASE + "/games/82-0", { waitUntil: "networkidle" }); |
| 29 | + } |
| 30 | + const pack = page.locator(".rip-pack"); |
| 31 | + await pack.waitFor({ state: "visible" }); |
| 32 | + await page.waitForFunction(() => { |
| 33 | + const el = document.querySelector(".rip-pack"); |
| 34 | + const label = el?.getAttribute("aria-label") || ""; |
| 35 | + return Boolean(label) && !/house prospect/i.test(label); |
| 36 | + }); |
| 37 | + const label = (await pack.getAttribute("aria-label")) || ""; |
| 38 | + const team = /Rip the (.+) prospect pack/.exec(label)?.[1] || ""; |
| 39 | + if (!team) throw new Error(`pack ${i + 1} has no franchise`); |
| 40 | + teams.push(team); |
| 41 | + await pack.click(); |
| 42 | + const cards = page.locator(".pack-grid button"); |
| 43 | + await cards.first().waitFor({ state: "visible" }); |
| 44 | + const n = await cards.count(); |
| 45 | + if (n !== 10) throw new Error(`${team} dealt ${n} cards`); |
| 46 | + const stacked = await page.locator(".card-face.is-down").count(); |
| 47 | + if (stacked > 0) throw new Error(`${team} stacked faces`); |
| 48 | + for (let c = 0; c < 5; c += 1) await cards.nth(c).click(); |
| 49 | + await page.getByRole("button", { name: "Lock five" }).click(); |
| 50 | + const skip = page.getByRole("button", { name: /Skip to the card|The card/ }); |
| 51 | + if (await skip.first().isVisible().catch(() => false)) await skip.first().click(); |
| 52 | + await page.getByRole("link", { name: "Save the card" }).waitFor(); |
| 53 | + const href = await page.getByRole("link", { name: "Save the card" }).first().getAttribute("href"); |
| 54 | + if (!href?.startsWith("data:image/png")) throw new Error(`${team} save is not a png`); |
| 55 | + await page.getByRole("button", { name: "Share" }).click(); |
| 56 | + await page.getByRole("link", { name: "X" }).waitFor(); |
| 57 | + await page.getByRole("button", { name: "TikTok" }).waitFor(); |
| 58 | + await page.getByRole("button", { name: "Discord" }).waitFor(); |
| 59 | + const tweet = await page.getByRole("link", { name: "X" }).getAttribute("href"); |
| 60 | + if (!tweet?.includes("x.com/intent/tweet")) throw new Error(`${team} X is not an intent`); |
| 61 | + const walk = (await page.locator("text=/\\/walk\\/v1\\./").first().textContent()) ?? ""; |
| 62 | + const id = walk.replace(/^[\s\S]*\/walk\//, "").trim(); |
| 63 | + if (!id.startsWith("v1.")) throw new Error(`${team} has no walk`); |
| 64 | + if (id === HOUSE) throw new Error(`${team} collapsed to the house pin`); |
| 65 | + walks.push(id); |
| 66 | + await page.keyboard.press("Escape"); |
| 67 | + console.log(JSON.stringify({ i: i + 1, team, walk: id })); |
| 68 | + } |
| 69 | + |
| 70 | + const unique = new Set(teams); |
| 71 | + if (unique.size < 4) throw new Error(`only ${unique.size} franchises in 10 pulls: ${[...unique].join(", ")}`); |
| 72 | + if (teams.every((t) => t === "Thunder")) throw new Error("all 10 packs were Thunder"); |
| 73 | + if (new Set(walks).size !== walks.length) throw new Error("duplicate walks — mixer stuck"); |
| 74 | + console.log(JSON.stringify({ ok: true, teams, unique: unique.size, walks: walks.length })); |
| 75 | +} catch (err) { |
| 76 | + console.error("RED TEAM FAIL", err); |
| 77 | + await page.screenshot({ path: "/tmp/fbs-red-fail.png", fullPage: true }).catch(() => {}); |
| 78 | + process.exitCode = 1; |
| 79 | +} finally { |
| 80 | + await browser.close(); |
| 81 | +} |
0 commit comments