Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions .github/workflows/universe-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,189 @@ jobs:
scripts/universe/visual-qa/artifacts-broad
scripts/universe/visual-qa/artifacts-chain-states
retention-days: 30

# The mobile gate runs beside the visual matrix rather than inside it.
#
# The matrix job is already 60 minutes of its 100 minute cap, and 78 percent
# of that is the deep pass. Adding widths, orientations and device emulation
# to it is how a gate stops finishing, which is the fault the split between
# the broad and deep passes exists to prevent. So this is a job of its own on
# a second runner, and the wall clock of the workflow does not move: it
# finishes in roughly a fifth of the time the matrix takes, on hardware that
# would otherwise be idle while the matrix runs.
#
# It builds its own copy rather than taking an artifact from the job beside
# it. That costs one extra production build and buys two things worth more
# than it: the two gates cannot make each other fail or wait, and this one
# can be read, run and debugged on its own.
#
# Measured at about nine minutes, of which six are the build.
mobile:
name: Mobile and adaptive layout
runs-on: [self-hosted, linux-ultra]
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24.19.0'
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install
working-directory: frontend
run: npm ci

- name: Production build
working-directory: frontend
run: npm run build:universe

- name: Install the visual QA harness
working-directory: scripts/universe/visual-qa
run: npm ci

# Same reasoning as the matrix job: a fixed port once measured another
# job's build, so each run takes a free one and refuses to continue
# unless the process answering is the one it started.
- name: Serve the built frontend
run: |
GATEWAY_PORT=$(node -e "const s=require('net').createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})")
echo "GATEWAY_PORT=$GATEWAY_PORT" >> "$GITHUB_ENV"
UNIVERSE_GATEWAY_PORT=$GATEWAY_PORT UNIVERSE_GATEWAY_ROOT=frontend/dist/mempool/browser node scripts/universe/gateway.mjs &
GATEWAY_PID=$!
for i in $(seq 1 30); do
curl -fsS -o /dev/null "http://127.0.0.1:$GATEWAY_PORT/" && break
sleep 1
done
kill -0 "$GATEWAY_PID"
curl -fsS "http://127.0.0.1:$GATEWAY_PORT/__gateway/health"

# Eleven routes across seven window sizes, with a coarse pointer, a
# software keyboard, a display cutout and a rotation. Seventy-seven page
# loads, and every one of them is measured about twelve different ways,
# which is why this is cheap: the assertions are free and only the page
# loads cost anything.
- name: Every window a phone can be, measured with a thumb
working-directory: scripts/universe/visual-qa
run: node mobile-check.mjs --base=http://127.0.0.1:$GATEWAY_PORT --out=./artifacts-mobile

# What the shell costs a phone, and what it makes the page do while it
# arrives. The gated numbers are the compressed payload, which is a
# property of the build and identical on any machine, and layout shift,
# which is a property of the stylesheet. Largest paint is printed and not
# gated: this runner builds, serves and drives browsers at once, and the
# same commit measured twelve seconds and twenty on two routes whose
# shells are identical, so it would fail honest changes and pass slow
# ones depending on what else was running.
- name: What the shell costs on a phone
working-directory: scripts/universe/visual-qa
run: >-
node mobile-perf.mjs --base=http://127.0.0.1:$GATEWAY_PORT
--out=./artifacts-perf

- name: Keep the mobile report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: universe-mobile-qa
path: |
scripts/universe/visual-qa/artifacts-mobile
scripts/universe/visual-qa/artifacts-perf
retention-days: 30

# The two engines the fleet cannot run.
#
# WebKit needs a set of system libraries a Chromium-only host has never had,
# and it does not degrade without them: it refuses to launch and prints the
# list. Installing them is one apt command, and the fleet's runner user has
# no passwordless sudo, so `playwright install --with-deps` fails there with
# "sudo: a password is required". Measured on universe-linux-ultra-03.
#
# So this one job runs on a hosted runner, where that command works. It is
# the exception to using the fleet, and it is here rather than folded into
# the job above so the exception is visible and so a hosted outage cannot
# take the Chromium gate down with it.
#
# It is worth the exception. WebKit is what Safari and Chrome on iOS are both
# built on, and dvh, safe-area insets and the pointer media feature are
# exactly the rules where engines disagree: a Chromium-only run proves
# nothing about the platform half this work was written for.
#
# When the fleet's image gains those libraries, delete this job and put the
# two steps back in the one above, which is where they belong.
mobile-engines:
name: WebKit and Firefox
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '24.19.0'
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install
working-directory: frontend
run: npm ci

- name: Production build
working-directory: frontend
run: npm run build:universe

- name: Install the visual QA harness
working-directory: scripts/universe/visual-qa
run: npm ci

- name: Fetch the engines and what they need to start
working-directory: scripts/universe/visual-qa
run: npx playwright install --with-deps webkit firefox

- name: Serve the built frontend
run: |
GATEWAY_PORT=$(node -e "const s=require('net').createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})")
echo "GATEWAY_PORT=$GATEWAY_PORT" >> "$GITHUB_ENV"
UNIVERSE_GATEWAY_PORT=$GATEWAY_PORT UNIVERSE_GATEWAY_ROOT=frontend/dist/mempool/browser node scripts/universe/gateway.mjs &
GATEWAY_PID=$!
for i in $(seq 1 30); do
curl -fsS -o /dev/null "http://127.0.0.1:$GATEWAY_PORT/" && break
sleep 1
done
kill -0 "$GATEWAY_PID"
curl -fsS "http://127.0.0.1:$GATEWAY_PORT/__gateway/health"

# The full window set, on the engine Safari is built on.
- name: Every window a phone can be, on WebKit
working-directory: scripts/universe/visual-qa
run: >-
node mobile-check.mjs --base=http://127.0.0.1:$GATEWAY_PORT
--browser=webkit --out=./artifacts-mobile-webkit

# Firefox cannot be put into mobile emulation, so it reports a fine
# pointer and the pointer-conditional rules do not apply to it. What it
# still measures is every window size, the overflow, the fixed layers,
# the safe areas and the rotation, which is worth having on a third
# engine. A subset of routes, because this is the smoke check and the two
# thorough passes are elsewhere.
- name: A third engine, on the checks that do not need a thumb
working-directory: scripts/universe/visual-qa
run: >-
node mobile-check.mjs --base=http://127.0.0.1:$GATEWAY_PORT
--browser=firefox --routes=home,tx,address,blocks,docs
--out=./artifacts-mobile-firefox

- name: Keep the engine reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: universe-mobile-engines
path: |
scripts/universe/visual-qa/artifacts-mobile-webkit
scripts/universe/visual-qa/artifacts-mobile-firefox
retention-days: 30
28 changes: 27 additions & 1 deletion docs/operations/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,33 @@ suite can see that.
coverage dimensions, no whole snapshot identifier in the primary interface
and the whole one filed under the technical details, next actions that
resolve, and the frontend commit this release expected.
8. Keep the previous release directory until the stability window closes.

8. Check the shell on a phone against the public origin, not only in CI.

```bash
node scripts/universe/visual-qa/mobile-check.mjs --base=https://explorer.bitcoinuniverse.io
```

It walks eleven routes across seven window sizes with a coarse pointer, a
simulated display cutout and a rotation, and answers every request from
fixtures, so what it measures is the deployed shell rather than the chain.
Add `--browser=webkit` for the engine Safari is built on.

This is worth running against production and not only against the build,
because the two things it is most likely to catch are things CI cannot see:
a gateway serving a stale `index.html`, which shows up immediately as the
viewport meta losing `viewport-fit=cover`, and a configuration difference
that changes which destinations the bottom bar carries.

Then look at it by hand on a phone. The gate is emulation, and emulation is
not a device: it cannot tell you whether a thumb reaches the bottom bar
one-handed, whether the software keyboard covers the result you were
reading, or whether the page zooms when you tap the search field. Open the
header and search, switch chain, search with the keyboard up, open a
transaction, scroll a table, rotate, go back, and watch a live update
arrive. Current Safari on iPhone and iPad, Chrome on Android, Samsung
Internet, and Chrome on iPhone.
9. Keep the previous release directory until the stability window closes.

Rolling back to a release from before the socket handover needs the port back,
because such a gateway opens 8099 itself and dies on bind while systemd holds
Expand Down
Loading
Loading