spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
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