// batch10-anonpull.mjs — the drive half of val/10-anonpull. // // Go tests cover NarrowToPullOnly and the auth matrix covers the API. What // neither can see is the class of bug this batch is most likely to produce in // the UI: a logged-out repo page that 500s, renders as a blank panel, or // renders as a cheerful empty state that looks like "no tags" rather than // "you may not see this". // // Logged-out checks run in an EPHEMERAL context, not the persistent profile in // lib.mjs — that profile is signed in, and clearing its cookies would cost an // interactive re-login for everything else. // // HOLD public=true → node test/e2e/batch10-anonpull.mjs // HOLD public=false → node test/e2e/batch10-anonpull.mjs (runs the denied half) import { chromium } from '@playwright/test'; import { APPVIEW } from './lib.mjs'; const HOLD = process.env.ATCR_HOLD_URL ?? 'http://127.0.0.1:8080'; const HANDLE = process.env.ATCR_E2E_HANDLE ?? 'evan.jarrett.net'; const REPO = process.env.ATCR_E2E_REPO ?? 'valtest'; // Defaults to headed, which is how these are meant to be watched. // ATCR_E2E_HEADLESS=1 for unattended runs. const HEADLESS = process.env.ATCR_E2E_HEADLESS === '1'; const captain = await fetch( `${HOLD}/xrpc/com.atproto.repo.listRecords?repo=did%3Aweb%3Alocalhost%253A8080&collection=io.atcr.hold.captain`, ).then((r) => r.json()); const isPublic = captain.records[0].value.public === true; console.log(`hold captain.public = ${isPublic}\n`); // A persistent context on a THROWAWAY profile dir: same launch path the other // e2e scripts use (a plain chromium.launch() does not come up here), but a // clean cookie jar, so this is genuinely logged out without disturbing the // signed-in profile lib.mjs uses. const ANON_PROFILE = process.env.ATCR_E2E_ANON_PROFILE ?? '/tmp/atcr-e2e-anon-profile'; const ctx = await chromium.launchPersistentContext(ANON_PROFILE, { headless: HEADLESS, viewport: null, args: ['--window-size=1400,1000'], }); const page = ctx.pages()[0] ?? (await ctx.newPage()); let pass = 0, fail = 0; const check = (label, ok, detail = '') => { console.log(` ${ok ? 'ok ' : 'FAIL'} ${label}${detail ? ' — ' + detail : ''}`); ok ? pass++ : fail++; }; // Anything 5xx is a failure regardless of what the page then renders: a 500 // dressed as an empty state is exactly what this script exists to catch. const statuses = []; page.on('response', (r) => { const u = new URL(r.url()); if (u.origin === new URL(APPVIEW).origin) statuses.push([r.status(), u.pathname]); }); // The repo page is /r/{handle}/* — NOT /{handle}/{repo}, which is a 404 // ("Lost at Sea") and will happily look like a denial if you don't check. const url = `${APPVIEW}/r/${HANDLE}/${REPO}`; console.log(`==> logged out, GET ${url}`); const resp = await page.goto(url, { waitUntil: 'networkidle' }); const body = (await page.locator('body').innerText()).replace(/\s+/g, ' ').trim(); check('no 5xx on any subrequest', !statuses.some(([s]) => s >= 500), statuses.filter(([s]) => s >= 500).map(([s, p]) => `${s} ${p}`).join(', ') || 'none'); check('page is not blank', body.length > 40, `${body.length} chars of text`); console.log(` top status ${resp.status()}`); console.log(` text: ${body.slice(0, 180)}`); if (isPublic) { // Tags live in a