Skip to content
Draft
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
18 changes: 12 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,18 @@ VITE_SENTRY_DSN=
# Sentry sourcemap upload token.
SENTRY_AUTH_TOKEN=

# E2E (apps/host/e2e) — pairing tests against mission-control signer bot.
# Service-account token issued by mission-control with `signer:provision`.
SIGNER_BOT_SVC_TOKEN=
SIGNER_BOT_BASE_URL=https://signing-bot-dev.novasama-tech.org/
# E2E (apps/host/tests/e2e) — pairing and signing via the truapi-host CLI
# from paritytech/host-rust-core. Install it with the repo's installer script
# or set SIGNING_HOST_BIN to a locally built binary (defaults to PATH lookup).
SIGNING_HOST_BIN=
# MUST match the host's `defaultNetwork()` in `packages/config/src/network.ts`.
# Drift produces a silent "pair OK, user-badge never appears" failure: the
# bot attests on one chain, the host listens on another, and the chain-side
# CLI attests on one chain, the host listens on another, and the chain-side
# handshake never reaches the host.
SIGNER_BOT_NETWORK=paseo-next-v2
SIGNING_HOST_NETWORK=paseo-next-v2
# CLI state dir (accounts, sessions). Defaults to apps/host/tests/e2e/.auth/
# signing-host so repeat runs reuse one test account. Override to isolate.
SIGNING_HOST_BASE_PATH=
# Optional: sign as a specific account instead of an auto-registered
# dotlitest user. Also read from the shell environment if exported.
HOST_CLI_SIGNER_MNEMONIC=
54 changes: 32 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ jobs:
fi

# E2E run of host-playground product tests against the local preview server,
# with pairing handled by mission-control's signer bot (no Android emulator,
# no Maestro).
# with pairing and signing handled headlessly by the truapi-host CLI from
# paritytech/host-rust-core (no Android emulator, no Maestro).
e2e-product:
name: E2E
runs-on: ubuntu-latest
Expand All @@ -95,12 +95,12 @@ jobs:
timeout-minutes: 35
if: ${{ github.event.sender.type != 'Bot' }}
env:
SIGNER_BOT_BASE_URL: https://signing-bot-dev.novasama-tech.org/
# Must match the host's `defaultNetwork()` in
# `packages/config/src/network.ts`. Setting explicitly so a host
# network bump can't silently leave the bot on the wrong chain.
SIGNER_BOT_NETWORK: paseo-next-v2
SIGNER_BOT_SVC_TOKEN: ${{ secrets.SIGNER_BOT_SVC_TOKEN }}
# network bump can't silently leave the CLI on the wrong chain.
SIGNING_HOST_NETWORK: paseo-next-v2
# The CLI self-updates by default; keep CI runs deterministic.
TRUAPI_HOST_NO_UPDATE: "1"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -110,6 +110,15 @@ jobs:
with:
bun-version: "1.3.13"

# Installs the current stable prebuilt binary (release pointer
# `truapi-host-cli-stable` in host-rust-core). Pin with
# TRUAPI_HOST_VERSION=x.y.z if a release ever breaks the suite.
- name: Install truapi-host CLI
run: |
curl -fsSL https://raw.githubusercontent.com/paritytech/host-rust-core/main/scripts/truapi-host-installer.sh | bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/truapi-host" --version

- name: Build
env:
# Required build-time config (see packages/config/src/network.ts).
Expand All @@ -119,10 +128,10 @@ jobs:
VITE_NETWORKS: paseo-next-v2,previewnet
run: bunx --bun turbo run build

# Exit code 99 from playwright = Nova bot unavailable (signal raised
# by apps/host/tests/e2e/global-setup.ts). Treat as soft-pass with a clear
# annotation: this is a Nova-side outage, not a dot.li test failure,
# and shouldn't block a PR.
# Exit code 99 from playwright = pairing/signing infrastructure down
# (signal raised by apps/host/tests/e2e/global-setup.ts after retries:
# testnet chains or the identity backend, not dot.li). Treat as
# soft-pass with a clear annotation so it doesn't block a PR.
- name: Run E2E
id: e2e
working-directory: apps/host
Expand All @@ -131,8 +140,8 @@ jobs:
bunx --bun playwright test --config=tests/e2e/playwright.config.ts
code=$?
if [ "$code" -eq 99 ]; then
echo "::warning::Signer bot unavailable — E2E Product suite skipped (Nova-side outage, not a dot.li failure)."
echo "bot_unavailable=true" >> "$GITHUB_OUTPUT"
echo "::warning::Signing infrastructure unavailable — E2E Product suite skipped (testnet/identity-backend outage, not a dot.li failure)."
echo "signing_unavailable=true" >> "$GITHUB_OUTPUT"
exit 0
fi
exit $code
Expand Down Expand Up @@ -199,26 +208,27 @@ jobs:
header: e2e-product
delete: true

# Bot-unavailable surfaces as a distinct sticky comment so the
# Signing-unavailable surfaces as a distinct sticky comment so the
# reviewer can tell at a glance that the suite didn't actually run.
# Separate header from the failure note so the two can coexist
# without overwriting each other across runs.
- name: Post bot-unavailable note on PR
if: steps.e2e.outputs.bot_unavailable == 'true' && github.event_name == 'pull_request'
- name: Post signing-unavailable note on PR
if: steps.e2e.outputs.signing_unavailable == 'true' && github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: e2e-product-bot-unavailable
header: e2e-product-signing-unavailable
message: |
⚠️ **E2E Product suite skipped** — Nova signing bot is unavailable.
⚠️ **E2E Product suite skipped** — pairing/signing infrastructure is unavailable.

This is a Nova-side outage and **not a dot.li test failure**. Re-run
from the Actions tab once the bot recovers.
The truapi-host CLI could not complete pairing (testnet chains or
identity backend outage). This is **not a dot.li test failure**.
Re-run from the Actions tab once the infrastructure recovers.

Logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

- name: Clear stale bot-unavailable note on PR
if: success() && steps.e2e.outputs.bot_unavailable != 'true' && github.event_name == 'pull_request'
- name: Clear stale signing-unavailable note on PR
if: success() && steps.e2e.outputs.signing_unavailable != 'true' && github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: e2e-product-bot-unavailable
header: e2e-product-signing-unavailable
delete: true
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,13 @@ Local development uses wildcard subdomains:

The product E2E suite can load the source checkout directly through dotli's
localhost proxy instead of resolving the published `host-playground.dot` CID.
By default it expects the product at `../../../host-playground` relative to this
repository and the local signing bot at `http://localhost:3737/`:
By default it expects the product at `../../../host-playground` relative to
this repository, and the `truapi-host` CLI from
[host-rust-core](https://github.com/paritytech/host-rust-core) on `PATH`:

```bash
curl -fsSL https://raw.githubusercontent.com/paritytech/host-rust-core/main/scripts/truapi-host-installer.sh | bash
```

```bash
bun run test:e2e:local
Expand All @@ -227,13 +232,17 @@ flow through either light-client backend:
E2E_CHAIN_BACKEND=smoldot-shared-worker bun run test:e2e:local
```

The signer can run natively or in Docker; set `SIGNER_BOT_BASE_URL`,
`SIGNER_BOT_SVC_TOKEN`, and `SIGNER_BOT_NETWORK` when its published port or
credentials differ from the defaults.
Set `SIGNING_HOST_BIN` to a locally built binary (e.g.
`../host-rust-core/target/debug/truapi-host`) instead of installing, and
`SIGNING_HOST_NETWORK` when testing against a non-default network. The CLI
keeps its account state under `apps/host/tests/e2e/.auth/signing-host`, so
repeat runs reuse one test account; the first run registers a fresh lite
username on-chain and can take a few minutes.

The command builds dotli with its debug-only localhost proxy enabled, starts
both preview servers through Playwright, pairs once through the signer bot, and
runs the same host-product suite used in CI.
both preview servers through Playwright, extracts the login QR deeplink, pairs
a headless `truapi-host signing-host` process that auto-signs for the rest of
the run, and runs the same host-product suite used in CI.

### Running an approved build

Expand Down
2 changes: 1 addition & 1 deletion apps/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build:prod": "tsc && bunx --bun vite build && bun ../../scripts/compress-dist.ts",
"test:functional": "playwright test --config=tests/functional/playwright.config.ts tests/functional/loading.spec.ts tests/functional/resolution.spec.ts tests/functional/navigation.spec.ts",
"test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
"test:e2e:local": "E2E_PRODUCT_URL=${E2E_PRODUCT_URL:-http://localhost:5199} SIGNER_BOT_BASE_URL=${SIGNER_BOT_BASE_URL:-http://localhost:3737/} SIGNER_BOT_SVC_TOKEN=${SIGNER_BOT_SVC_TOKEN:-admin} SIGNER_BOT_NETWORK=${SIGNER_BOT_NETWORK:-paseo-next-v2} playwright test --config=tests/e2e/playwright.config.ts",
"test:e2e:local": "E2E_PRODUCT_URL=${E2E_PRODUCT_URL:-http://localhost:5199} SIGNING_HOST_NETWORK=${SIGNING_HOST_NETWORK:-paseo-next-v2} playwright test --config=tests/e2e/playwright.config.ts",
"test:perf": "PERF_RUNS=10 PERF_DOMAIN_A=explore PERF_DOMAIN_B=host-playground npx playwright test --config=tests/performance/playwright.config.ts tests/performance/cold-start.spec.ts",
"test:perf:base": "PERF_RUNS=20 PERF_SAVE_BASE=1 PERF_DOMAIN_A=explore PERF_DOMAIN_B=host-playground npx playwright test --config=tests/performance/playwright.config.ts tests/performance/cold-start.spec.ts",
"test:perf:compare": "bun tests/performance/compare.ts",
Expand Down
20 changes: 10 additions & 10 deletions apps/host/tests/e2e/fixtures/paired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ const PORT = process.env.PORT ?? "5173";
const HOST = process.env.E2E_HOST ?? "host-playground";
const PRODUCT_URL = process.env.E2E_PRODUCT_URL;

// Restored-session badge wait. The bot was paired once in globalSetup, the
// storageState restores the host's auth on every context, so seeing the
// user-badge should be near-instant. A tight cap surfaces a broken bot or
// host fast instead of running out the workflow clock.
// Restored-session badge wait. The signing host was paired once in
// globalSetup, the storageState restores the host's auth on every context,
// so seeing the user-badge should be near-instant. A tight cap surfaces a
// broken signer or host fast instead of running out the workflow clock.
const USER_BADGE_TIMEOUT_MS = 15_000;
const PRODUCT_IFRAME_TIMEOUT_MS = 20_000;

Expand Down Expand Up @@ -87,13 +87,13 @@ async function waitForHostPlaygroundFrame(

/**
* Worker-scoped fixtures: open a fresh page that inherits the
* once-per-run bot pairing via `storageState` written by globalSetup.
* No QR scan, no bot pair API call here. If the badge doesn't appear
* inside 15 s the worker fails fast. The bot is either down or the
* host can't restore auth from the saved state.
* once-per-run signing-host pairing via `storageState` written by
* globalSetup. No QR scan, no CLI spawn here. If the badge doesn't appear
* inside 15 s the worker fails fast. The signing host is either dead or
* the host can't restore auth from the saved state.
*
* State sharing: every worker reads the same `.auth/state.json`, so all
* tests across the run share one bot user. This matches the prior
* tests across the run share one signer account. This matches the prior
* behavior under `workers: 1` (worker-scope pairing) and avoids the
* re-pair cascade that previously timed out CI on a single test failure.
*/
Expand All @@ -107,7 +107,7 @@ export const test = base.extend<
if (!existsSync(STATE_FILE)) {
throw new Error(
`pairedPage: ${STATE_FILE} missing — globalSetup must run first. ` +
`If you ran the test directly, ensure SIGNER_BOT_SVC_TOKEN is set ` +
`If you ran the test directly, ensure SIGNING_HOST_NETWORK is set ` +
`and re-run via \`bun run test:e2e\`.`,
);
}
Expand Down
12 changes: 7 additions & 5 deletions apps/host/tests/e2e/fixtures/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

import { resolve } from "node:path";

// Shared file paths so globalSetup, the worker fixture, and globalTeardown
// all agree on where the once-per-run pairing artifacts live. The directory
// is gitignored and gets recreated on every `playwright test` invocation.
// Shared paths so globalSetup, the worker fixture, and globalTeardown agree
// on where the once-per-run pairing artifacts live. Gitignored.
export const AUTH_DIR = resolve(import.meta.dirname, "..", ".auth");
export const STATE_FILE = resolve(AUTH_DIR, "state.json");
export const SESSION_FILE = resolve(AUTH_DIR, "session.json");
// Persists across runs so local runs reuse one test account instead of
// registering a new lite username (and burning allowance slots) each time.
export const SIGNING_HOST_STATE_DIR = resolve(AUTH_DIR, "signing-host");

export interface PersistedSession {
sessionId: string;
pid: number;
username: string;
network: string;
botBase: string;
basePath: string;
}
Loading
Loading