SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
12.1 KB · 253 lines javascript
Raw Blame History
1// Admin console QA: node apps/web/qa/admin/shoot-admin.mjs  (Playwright from ~/Desktop/uqo-eval)2// env: BASE (http://localhost:8310), TOKEN (dev-admin-token), DOC_DIFF (document id with ≥ 2 versions), SKIP_DEVTOOL=13import { createRequire } from "node:module";4import { mkdirSync } from "node:fs";5import { dirname, resolve } from "node:path";6import { fileURLToPath } from "node:url";78const require = createRequire("/Users/simon-pierreboucher/Desktop/uqo-eval/package.json");9const { chromium } = require("playwright");10const out = dirname(fileURLToPath(import.meta.url));11mkdirSync(out, { recursive: true });12const BASE = process.env.BASE ?? "http://localhost:8310";13const TOKEN = process.env.TOKEN ?? "dev-admin-token";14const DOC_DIFF = process.env.DOC_DIFF ?? "";15const SKIP_DEVTOOL = process.env.SKIP_DEVTOOL === "1";16const report = [];17const log = (...a) => console.log(new Date().toISOString().slice(11, 19), ...a);1819async function login(page) {20  await page.goto(`${BASE}/admin/login`, { waitUntil: "load" });21  await page.fill('input[name="token"]', TOKEN);22  await page.click('button[type="submit"]');23  await page.waitForURL((u) => u.pathname === "/admin", { timeout: 30000 });24}2526async function shot(page, name, w, opts = {}) {27  await page.waitForTimeout(opts.wait ?? 800);28  const overflow = await page.evaluate(() => {29    const doc = document.documentElement;30    return { scrollWidth: doc.scrollWidth, clientWidth: doc.clientWidth };31  });32  report.push({ w, name, overflow: overflow.scrollWidth > overflow.clientWidth ? `OVERFLOW ${overflow.scrollWidth}>${overflow.clientWidth}` : "ok" });33  await page.screenshot({ path: resolve(out, `${name}-${w}.png`), fullPage: opts.fullPage ?? true });34}3536async function api(page, path) {37  return page.evaluate(async (p) => {38    const r = await fetch(`/admin/api/${p}`, { credentials: "same-origin" });39    return { status: r.status, json: await r.json().catch(() => null) };40  }, path);41}4243const browser = await chromium.launch();4445// ------------------------------------------------------------------ desktop46{47  const w = 1280;48  const ctx = await browser.newContext({ viewport: { width: w, height: 900 }, deviceScaleFactor: 1 });49  const page = await ctx.newPage();50  page.on("dialog", (d) => d.accept());51  page.on("pageerror", (e) => log("PAGEERROR", String(e).slice(0, 200)));5253  // wrong token first54  await page.goto(`${BASE}/admin/login`);55  await page.fill('input[name="token"]', "nope");56  await page.click('button[type="submit"]');57  await page.waitForSelector('[role="alert"]', { timeout: 20000 });58  log("login error shown:", (await page.textContent('[role="alert"]')).trim());59  await shot(page, "login-error", w, { fullPage: false });6061  await login(page);62  log("logged in");63  await page.waitForSelector("text=Last 20 runs", { timeout: 30000 });64  await page.waitForTimeout(2500);65  await shot(page, "overview", w);6667  // connectors: run azure-regions68  await page.goto(`${BASE}/admin/connectors?q=azure`);69  await page.waitForSelector("text=azure-regions", { timeout: 30000 });70  const row = page.locator("tr", { hasText: "azure-regions" }).first();71  await row.getByRole("button", { name: "Run", exact: true }).click();72  await page.waitForSelector("text=Task");73  await page.selectOption("select", { label: "discover — sitemap / RSS / seeds" }).catch(() => {});74  await shot(page, "connectors-run-form", w);75  await page.locator("tr.bg-surface-2\\/60 + tr button", { hasText: "Run" }).last().click();76  await page.waitForSelector("text=Run enqueued", { timeout: 20000 });77  log("run enqueued toast visible");78  await page.goto(`${BASE}/admin/connectors`);79  await page.waitForSelector("text=Connector", { timeout: 30000 });80  await page.waitForTimeout(2000);81  await shot(page, "connectors", w);8283  await page.goto(`${BASE}/admin/connectors/azure-regions`);84  await page.waitForSelector("text=Health history", { timeout: 30000 });85  await page.waitForTimeout(1500);86  await shot(page, "connector-detail", w);87  await page.getByRole("tab", { name: "Config" }).click();88  await page.waitForTimeout(500);89  await shot(page, "connector-detail-config", w, { fullPage: false });9091  // documents92  await page.goto(`${BASE}/admin/documents`);93  await page.waitForSelector("table tbody tr", { timeout: 30000 });94  await shot(page, "documents", w);95  const firstDoc = await page.locator("table tbody tr td a").first().getAttribute("href");96  await page.goto(`${BASE}${firstDoc}`);97  await page.waitForSelector("text=Metadata", { timeout: 30000 });98  await page.locator("iframe[title^='Rendered']").or(page.getByText("Raw body unavailable")).or(page.getByText("No stored versions")).first().waitFor({ timeout: 30000 }).catch(() => {});99  await page.waitForTimeout(2500);100  await shot(page, "document-inspect", w);101  if (DOC_DIFF) {102    await page.goto(`${BASE}/admin/documents/${DOC_DIFF}`);103    await page.waitForSelector("text=Diff", { timeout: 30000 });104    await page.getByText("Stored diff summary").or(page.getByText("No line differences")).first().waitFor({ timeout: 60000 });105    await page.waitForTimeout(1500);106    await shot(page, "document-diff", w);107    log("diff rendered for", DOC_DIFF);108  }109110  // matches: reject the lowest-scoring pending111  await page.goto(`${BASE}/admin/matches`);112  await page.waitForSelector("text=Entity matches", { timeout: 30000 });113  await page.waitForTimeout(2500);114  await shot(page, "matches", w);115  const pending = await api(page, "matches?status=pending&per_page=200");116  const list = pending.json?.data ?? [];117  if (list.length) {118    const target = list[list.length - 1];119    log(`rejecting lowest-score pending match ${target.id} (${target.candidate?.name} → ${target.matched?.name}, score ${target.score})`);120    const card = page.locator("article", { hasText: target.candidateKey }).first();121    if (await card.count()) {122      await card.getByRole("button", { name: "Reject" }).click();123      await page.waitForSelector("text=Match rejected", { timeout: 20000 });124      await shot(page, "matches-rejected", w, { fullPage: false });125      const after = await api(page, `matches?status=rejected&per_page=200`);126      log("rejected now visible in rejected tab:", (after.json?.data ?? []).some((m) => m.id === target.id));127    } else log("target card not on first page; skipped UI reject");128  } else log("no pending matches to decide");129130  // events131  await page.goto(`${BASE}/admin/events`);132  await page.waitForSelector("text=Events review", { timeout: 30000 });133  await page.waitForTimeout(2500);134  await shot(page, "events", w);135136  // facility edit (no save)137  const fac = (pending.json?.data ?? []).find((m) => m.matched)?.matched?.id ?? "fac_2ezznz6uhn5b";138  await page.goto(`${BASE}/admin/facilities/${fac}/edit`);139  await page.waitForSelector("text=Curation note", { timeout: 30000 });140  await page.waitForTimeout(1000);141  await shot(page, "facility-edit", w);142143  // sources144  await page.goto(`${BASE}/admin/sources`);145  await page.waitForSelector("table tbody tr", { timeout: 30000 });146  await shot(page, "sources", w);147148  // dev tool149  if (!SKIP_DEVTOOL) {150    await page.goto(`${BASE}/admin/devtool`);151    await page.waitForSelector("text=Connector development tool", { timeout: 30000 });152    await page.evaluate(() => window.localStorage.removeItem("dci:devtool:draft:v1"));153    await page.reload();154    await page.waitForSelector("text=Source URL and fetch options", { timeout: 30000 });155    await page.fill('input[placeholder^="https://www.example.com"]', "https://www.digitalrealty.com/data-centers");156    await page.getByRole("button", { name: "Fetch sample" }).click();157    await page.waitForSelector("text=HTML source", { timeout: 120000 });158    await page.waitForTimeout(1500);159    log("devtool fetch done");160    await shot(page, "devtool-1-fetch", w);161    await page.getByRole("tab", { name: "HTML source" }).click();162    await page.fill('input[placeholder^="Find"]', "h1");163    await page.waitForTimeout(600);164    await shot(page, "devtool-1-source-search", w, { fullPage: false });165166    await page.getByRole("button", { name: /Extraction rules →/ }).click();167    await page.waitForSelector("text=YAML preview", { timeout: 20000 });168    await shot(page, "devtool-2-rules", w);169170    await page.getByRole("button", { name: /Preview fields →/ }).click();171    await page.waitForSelector("text=Preview extraction", { timeout: 20000 });172    await page.getByRole("button", { name: "Preview fields", exact: true }).click();173    await page.waitForSelector("text=Records (raw extraction)", { timeout: 120000 });174    await page.waitForTimeout(1000);175    log("devtool preview done");176    await shot(page, "devtool-3-preview", w);177178    await page.getByRole("button", { name: /Save connector →/ }).click();179    await page.waitForSelector("text=Connector YAML", { timeout: 20000 });180    await page.fill('input[placeholder="digitalrealty"]', "devtool-test");181    await page.getByRole("button", { name: "Regenerate from steps" }).click();182    await page.waitForTimeout(400);183    await page.getByRole("button", { name: "Validate" }).click();184    await page.waitForSelector("text=valid", { timeout: 30000 });185    await shot(page, "devtool-4-validated", w);186    await page.getByRole("button", { name: "Save connector", exact: true }).click();187    await page.waitForSelector("text=Saved config", { timeout: 30000 }).catch(() => {});188    await page.waitForSelector("text=/Saved .*devtool-test\\.yaml/", { timeout: 30000 });189    log("devtool save-config done");190    await shot(page, "devtool-4-saved", w);191192    await page.getByRole("button", { name: /Run on pages →/ }).click();193    await page.waitForSelector("text=Run on multiple pages", { timeout: 20000 });194    await page.fill("textarea", "https://www.digitalrealty.com/data-centers\nhttps://www.digitalrealty.com/data-centers/americas");195    await page.getByRole("button", { name: "Run sample" }).click();196    await page.waitForSelector("text=Comparison — first entity per URL", { timeout: 280000 });197    await page.waitForTimeout(1000);198    log("devtool run-sample done");199    await shot(page, "devtool-5-sample", w);200201    await page.getByRole("button", { name: /Compare & deploy →/ }).click();202    await page.waitForSelector("text=Compare with the saved connector", { timeout: 20000 });203    await page.waitForSelector("text=Saved parser version", { timeout: 30000 });204    await page.waitForTimeout(800);205    await shot(page, "devtool-6-compare", w);206    await page.getByLabel(/enqueue reprocess/).uncheck();207    await page.getByRole("button", { name: "Deploy", exact: true }).click();208    await page.waitForSelector("text=save-config", { timeout: 60000 });209    await page.waitForTimeout(1500);210    log("devtool deploy done");211    await shot(page, "devtool-6-deployed", w);212  }213  await ctx.close();214}215216// ------------------------------------------------------------------ phone217{218  const w = 390;219  const ctx = await browser.newContext({ viewport: { width: w, height: 844 }, deviceScaleFactor: 1, isMobile: true, hasTouch: true });220  const page = await ctx.newPage();221  page.on("pageerror", (e) => log("PAGEERROR(m)", String(e).slice(0, 200)));222  await page.goto(`${BASE}/admin/login`);223  await shot(page, "login", w, { fullPage: false });224  await login(page);225  await page.waitForSelector("text=Last 20 runs", { timeout: 30000 });226  await page.waitForTimeout(2500);227  await shot(page, "overview", w);228  for (const [name, path, waitFor] of [229    ["connectors", "/admin/connectors", "text=Connector"],230    ["connector-detail", "/admin/connectors/azure-regions", "text=Health history"],231    ["documents", "/admin/documents", "table tbody tr"],232    ["matches", "/admin/matches", "text=Entity matches"],233    ["events", "/admin/events", "text=Events review"],234    ["sources", "/admin/sources", "table tbody tr"],235    ["devtool", "/admin/devtool", "text=Source URL"],236  ]) {237    await page.goto(`${BASE}${path}`);238    await page.waitForSelector(waitFor, { timeout: 30000 }).catch(() => {});239    await page.waitForTimeout(2000);240    await shot(page, name, w);241  }242  if (DOC_DIFF) {243    await page.goto(`${BASE}/admin/documents/${DOC_DIFF}`);244    await page.waitForSelector("text=Metadata", { timeout: 30000 });245    await page.waitForTimeout(3000);246    await shot(page, "document-inspect", w);247  }248  await ctx.close();249}250251await browser.close();252for (const r of report) console.log(r.w, r.name, r.overflow);253