# The pages used as people use them, as `just e2e` runs them: in Chromium, # logging in through the PDS's own approval page, then every write the # pages make, each read back on the page and from the PDS # (harness/e2e/flows.spec.mjs), over a world seeded for the run as the # shots' is. Beside it, a private repository's, over another on the spaces # alpha, each read back on the page and from its space, and as not there # to anyone else (private.spec.mjs). The shots show what the pages draw; # this, that what they do lands. Each flow that fails leaves the page as # it was then among the check's outputs, as a picture. # # Node, the packages, Chromium, the libraries it needs, the alpha and the # build are kept between runs, as main's runs leave them: nothing is fetched again # until what it is made from changes. image: rust:1.98 # Every push to main, and pulls into it or a feature/ branch, at their # heads merged with their base, that change the pages, the server and # client they log in to and write through, the world and harness they run # over, or this file. A pull changing none passes at once, noted as # skipped. on: push: {refs: [refs/heads/main]} pull: base: [refs/heads/main, refs/heads/feature/*] paths: [web/**, lexicons/**, crates/knotless/**, crates/knotless-serve/**, crates/knotless-mcp/**, Cargo.toml, Cargo.lock, harness/e2e/**, harness/shots/world.mjs, harness/shots/fixture/**, harness/pds.mjs, harness/tls-proxy.mjs, harness/package.json, harness/package-lock.json, .knotless/workflows/e2e.yml, .knotless/uses.lock] cache: [target, /usr/local/cargo/registry, /usr/local/rustup, /opt/node, /opt/browsers, /opt/chromium-debs, /opt/atproto, web/node_modules, harness/node_modules] env: PLAYWRIGHT_BROWSERS_PATH: /opt/browsers # The spaces alpha the private flows run over, as test.yml pins it: the # two move together. ATPROTO_COMMIT: 11c5d98cd50261c5e1201fedc5c6c05a7b8d1ccf ALPHA_BUILD: "2" steps: - uses: chadtmiller.com/steps/node@1 with: {version: 24.22.1} - uses: chadtmiller.com/steps/npm-ci@1 name: web with: {dir: web} - uses: chadtmiller.com/steps/npm-ci@1 name: harness with: {dir: harness} - name: chromium run: | export PATH=/opt/node/bin:$PATH cd harness # The browser, into /opt/browsers once for each Playwright version. npx playwright install chromium # The libraries it needs, which `install-deps` would fetch with apt on # every run: their .debs are kept, fetched again only when this # Playwright or this Debian asks for others, and installed offline. pkgs=$(npx playwright install-deps --dry-run chromium | sed -n "s/.*--no-install-recommends \([^\"']*\).*/\1/p") [ -n "$pkgs" ] || { echo "no packages in: $(npx playwright install-deps --dry-run chromium)"; exit 1; } debs=/opt/chromium-debs want="$(. /etc/os-release && echo "$VERSION_ID") $pkgs" if [ "$(cat $debs/.want 2>/dev/null)" != "$want" ]; then rm -rf "${debs:?}"/* && mkdir -p $debs/partial apt-get update -qq apt-get install -y -qq --no-install-recommends --download-only -o Dir::Cache::archives=$debs $pkgs echo "$want" > $debs/.want fi dpkg -i $debs/*.deb > /dev/null - name: spaces alpha run: | [ "$(cat /opt/atproto/.built 2>/dev/null)" = "$ATPROTO_COMMIT $ALPHA_BUILD" ] && exit 0 export PATH=/opt/node/bin:$PATH COREPACK_ENABLE_DOWNLOAD_PROMPT=0 COREPACK_HOME=/opt/atproto/.corepack find /opt/atproto -mindepth 1 -delete cd /opt/atproto git init -q . && git fetch -q --depth 1 https://github.com/bluesky-social/atproto.git "$ATPROTO_COMMIT" && git checkout -q FETCH_HEAD mkdir .bin && corepack enable --install-directory /opt/atproto/.bin export PATH=/opt/atproto/.bin:$PATH pnpm install --frozen-lockfile --ignore-scripts pnpm run build:tooling --force pnpm --filter '@atproto/dev-env^...' --filter @atproto/dev-env run --if-present prebuild pnpm exec tsc --build packages/dev-env/tsconfig.build.json # The PDS serves the OAuth sign-in pages from this bundle. pnpm --filter @atproto/oauth-provider-ui run build:ui echo "$ATPROTO_COMMIT $ALPHA_BUILD" > /opt/atproto/.built - name: sqlite run: | # The one install script the PDS needs, which the install above # skips: SQLite's native module, as better-sqlite3's own script builds # it (a prebuilt one where there is one). export PATH=/opt/node/bin:$PATH cd "$(dirname "$(node -p "require.resolve('better-sqlite3/package.json', { paths: ['/opt/atproto/packages/pds'] })")")" [ -f build/Release/better_sqlite3.node ] && exit 0 npm run install - name: build run: | export PATH=/opt/node/bin:$PATH # The binaries and the pages side by side, as neither needs the other. cargo build --bins -p knotless -p knotless-serve & cargo=$! (cd web && npm run build) || { kill $cargo; exit 1; } wait $cargo - name: flows run: | export PATH=/opt/node/bin:$PATH status=0 # The public flows over the stock PDS, and the private ones over a # world of their own on the alpha, side by side: they share nothing # but ports, which SHOTS_PORT sets apart. Each one's log is shown # whole once it ends. (cd harness && SHOTS_PORT=29100 E2E=1 npx playwright test -c e2e/playwright.config.mjs) > /tmp/e2e-public.log 2>&1 & public=$! (cd harness && SHOTS_PORT=29200 KNOTLESS_DEV_ENV=/opt/atproto/packages/dev-env E2E=1 npx playwright test -c e2e/playwright.config.mjs) > /tmp/e2e-private.log 2>&1 & private=$! wait $public || status=$? echo "==> public flows"; cat /tmp/e2e-public.log wait $private || status=$? echo "==> private flows"; cat /tmp/e2e-private.log # The page each failed flow ended on, named for the flow. if [ -n "${KNOTLESS_RUN_OUTPUTS:-}" ] && [ -d target/e2e ]; then for shot in target/e2e/*/*/test-failed-*.png; do [ -f "$shot" ] && cp "$shot" "$KNOTLESS_RUN_OUTPUTS/$(basename "$(dirname "$shot")").png" done fi exit $status