Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
75 lines
2.3 KiB
JavaScript
75 lines
2.3 KiB
JavaScript
import { chromium } from "playwright";
|
|
import { mkdirSync } from "node:fs";
|
|
|
|
const BASE = process.env.QA_BASE ?? "http://localhost:3111";
|
|
const OUT = "runs/qa";
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
|
|
async function shot(name, { fullPage = false } = {}) {
|
|
await page.waitForTimeout(350);
|
|
await page.screenshot({ path: `${OUT}/${name}.png`, fullPage });
|
|
console.log(`✓ ${name}`);
|
|
}
|
|
|
|
console.log("console errors will be collected at the end");
|
|
const errors = [];
|
|
page.on("console", (msg) => {
|
|
if (msg.type() === "error") errors.push(msg.text().slice(0, 200));
|
|
});
|
|
page.on("pageerror", (err) => errors.push(`PAGEERROR: ${String(err).slice(0, 200)}`));
|
|
|
|
await page.goto(`${BASE}/streams`);
|
|
await shot("01-streams");
|
|
|
|
await page.keyboard.press("j");
|
|
await page.keyboard.press("j");
|
|
await page.keyboard.press("j");
|
|
await shot("02-streams-selected");
|
|
|
|
await page.keyboard.press("r");
|
|
await shot("03-streams-repost-toast");
|
|
|
|
await page.keyboard.press("q");
|
|
await shot("04-streams-quote-popover");
|
|
await page.keyboard.press("Escape");
|
|
|
|
await page.keyboard.press("j");
|
|
await page.keyboard.press("k");
|
|
await page.keyboard.press("k");
|
|
await shot("05-streams-keyboard-moved");
|
|
|
|
await page.goto(`${BASE}/compose`);
|
|
await page.locator(".ProseMirror").click();
|
|
await page.keyboard.type("Shipping the visual QA pass — capability-gated amplify, keyboard-first streams.");
|
|
await shot("06-compose-typed");
|
|
|
|
await page.goto(`${BASE}/connections`);
|
|
await shot("07-connections");
|
|
|
|
await page.goto(`${BASE}/inbox`);
|
|
await shot("08-inbox");
|
|
|
|
await page.goto(`${BASE}/team`);
|
|
await shot("09-team");
|
|
|
|
await page.goto(`${BASE}/audit`);
|
|
await shot("10-audit");
|
|
await page.locator("tr", { hasText: "credential.rotated" }).first().click();
|
|
await shot("11-audit-diff-open");
|
|
|
|
await page.goto(`${BASE}/onboarding`);
|
|
await shot("12-onboarding-step1");
|
|
await page.getByPlaceholder("Studio, Newsroom, Acme Social…").fill("Studio");
|
|
await page.getByRole("button", { name: "Continue" }).click();
|
|
await shot("13-onboarding-step2");
|
|
|
|
await page.goto(`${BASE}/streams`);
|
|
await page.keyboard.press("Meta+k");
|
|
await shot("14-command-palette");
|
|
|
|
await browser.close();
|
|
console.log(`\nconsole errors: ${errors.length}`);
|
|
for (const e of errors.slice(0, 10)) console.log(` - ${e}`);
|