visitor
tcmts.dev.devenv.visitor · #pqdpfpa6 · mjs file
// A visitor on the registry, in headless Chrome: sign in as maya.test (the real PDS's sign-in and consent pages), then
// star alice.test's geo and take the star back, saying what the page and the PDS show at each step.
import puppeteer from 'puppeteer-core'
import fs from 'node:fs'
import { CHROME, sayYes } from './yes.mjs'
const registry = process.argv[2] ?? 'http://127.0.0.1:7402'
const env = JSON.parse(fs.readFileSync(new URL('./env.json', import.meta.url), 'utf8'))
const maya = env.accounts['maya.test'].did
const alice = env.accounts['alice.test'].did
const step = (s) => console.log(`-- ${s}`)
const b = await puppeteer.launch({ executablePath: CHROME, headless: true })
const p = await b.newPage()
const errors = []
p.on('pageerror', (e) => errors.push(String(e)))
p.on('console', (m) => m.type() === 'error' && !/^Failed to load resource/.test(m.text()) && errors.push(m.text()))
p.on('response', async (r) => r.status() >= 400 && errors.push(`${r.status()} ${r.request().method()} ${r.url().split('?')[0]}: ${(await r.text().catch(() => '')).slice(0, 120)}`))
const text = (sel) => p.$eval(sel, (e) => e.textContent).catch(() => '')
const until = async (what, fn, ms = 30_000) => {
try { await p.waitForFunction(fn, { timeout: ms }) } catch (e) {
await p.screenshot({ path: `${new URL('./shots/', import.meta.url).pathname}visitor-${what.replace(/\W+/g, '-')}.png`, fullPage: true })
throw new Error(`${what}: not seen at ${p.url()} (${errors.join(' | ')})`)
}
}
const starRecord = async () => (await fetch(`${env.pds}/xrpc/com.atproto.repo.getRecord?repo=${maya}&collection=codes.tcm.star&rkey=${encodeURIComponent(`${alice}:geo`)}`)).status
try {
await p.goto(`${registry}/@alice.test/geo`, { waitUntil: 'networkidle0' })
await until('the sign-in button', () => document.querySelector('.account button')?.textContent === 'Sign in')
step('signing in as maya.test')
await p.click('.account button')
await p.waitForSelector('.signin-pop input')
await p.type('.signin-pop input', 'maya.test')
await p.click('.signin-pop button[type=submit]')
await p.waitForFunction((pds) => location.href.startsWith(pds), { timeout: 30_000 }, env.pds).catch(async () => {
throw new Error(`not sent to the PDS: at ${p.url()}: ${await text('.account')} (${errors.join(' | ')})`)
})
await sayYes(p, 'registry')
await until('signed in', () => /@maya\.test/.test(document.querySelector('.account .avatar-btn')?.getAttribute('aria-label') ?? ''))
step(`signed in, back at ${p.url()}: ${await p.$eval('.account .avatar-btn', (b) => b.getAttribute('aria-label'))}`)
step(`star button: ${await text('.star')}`)
await p.click('.star button')
await until('starred', () => /Starred|Couldn't/.test((document.querySelector('.star')?.textContent ?? '') + (document.querySelector('.toasts')?.textContent ?? '')))
step(`after starring: ${await text('.star')}; the PDS's getRecord: ${await starRecord()}`)
await p.click('.star button')
await until('unstarred', () => /☆ Star|Couldn't/.test((document.querySelector('.star')?.textContent ?? '') + (document.querySelector('.toasts')?.textContent ?? '')))
step(`after taking it back: ${await text('.star')}; the PDS's getRecord: ${await starRecord()}`)
await p.goto(`${registry}/?show=following`, { waitUntil: 'networkidle0' })
step(`People I follow: ${(await p.evaluate(() => document.querySelector('.narrow')?.innerText ?? '')).replace(/\s+/g, ' ').slice(0, 300)}`)
// libraries she uses: from a codebase she serves (app, which holds geo), given to the registry
const codebase = process.argv[3]
if (codebase) {
await p.goto(`${registry}/?show=mine`, { waitUntil: 'networkidle0' })
step(`Libraries I use, before: ${await p.$$eval('.ev', (x) => x.length)} item(s)`)
await p.type('.codebaseform input', codebase)
await p.click('.codebaseform button')
await until('libraries from her codebase', () => document.querySelectorAll('.ev').length > 0)
step(`Libraries I use, with ${codebase}: ${await p.$$eval('.ev', (x) => x.map((e) => e.innerText.split('\n')[0]).join(' | '))}`)
}
step(`page errors: ${errors.length ? errors.join(' | ') : 'none'}`)
} finally {
await b.close()
}
used by