E2E — interactive #66
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Hub-Client E2E Tests | |
| # Label runs by mode in the Actions UI. `run-name` is evaluated at workflow | |
| # top level with only the `github`/`inputs` contexts available, so it branches | |
| # purely on the trigger. Order matters — first truthy arm wins: | |
| # - recreate-all-snapshots dispatch → baseline regeneration | |
| # - schedule → nightly (interactive + smoke-all) | |
| # - run-smoke-all dispatch → on-demand interactive + smoke-all | |
| # - everything else (push/PR) → the fast interactive gate | |
| run-name: >- | |
| ${{ | |
| inputs.recreate-all-snapshots && 'E2E — recreate baselines' | |
| || github.event_name == 'schedule' && 'E2E — interactive + smoke-all (nightly)' | |
| || inputs.run-smoke-all && 'E2E — interactive + smoke-all' | |
| || 'E2E — interactive' | |
| }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| recreate-all-snapshots: | |
| description: 'Delete and recreate ALL visual regression baselines' | |
| type: boolean | |
| default: false | |
| run-smoke-all: | |
| description: 'Also run smoke-all E2E tests (slow, ~80 extra tests, opt-in only)' | |
| type: boolean | |
| default: false | |
| schedule: | |
| - cron: '0 7 * * *' # 2am EST / 3am EDT, runs on main | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| e2e-tests: | |
| runs-on: ubuntu-latest | |
| name: Hub-Client E2E Tests | |
| if: github.repository == 'quarto-dev/q2' | |
| # Needed for the "Commit new baselines" step to push back to the | |
| # workflow branch. Default GITHUB_TOKEN is read-only. | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| # Fix mtimes for build caching | |
| - name: Restore file modification times | |
| shell: bash | |
| run: | | |
| git ls-files | while read file; do | |
| time=$(git log -1 --format='@%ct' -- "$file" 2>/dev/null || echo '@0') | |
| [ "$time" != "@0" ] && touch -d "$time" "$file" 2>/dev/null || true | |
| done | |
| # Rust toolchain for WASM build. Use the named `@nightly` ref | |
| # (not `@master`, which hard-errors without an explicit `toolchain:` | |
| # input) — it installs the nightly toolchain; cargo then reads the | |
| # pinned dated nightly from rust-toolchain.toml (bd-at72) at build | |
| # time. Matches the pattern Carlos established for ts-test-suite.yml | |
| # on main. | |
| - name: Set up Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| targets: wasm32-unknown-unknown | |
| components: rust-src | |
| - name: Set up Clang | |
| uses: egor-tensin/setup-clang@v2 | |
| with: | |
| version: latest | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-on-failure: true | |
| - name: Install wasm-pack | |
| run: cargo install wasm-pack | |
| # build-wasm.js verifies wasm-bindgen CLI matches the version | |
| # pinned in Cargo.lock; install that exact version. | |
| - name: Install wasm-bindgen-cli | |
| run: | | |
| VERSION=$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/version = |"/, ""); print; exit}' Cargo.lock) | |
| echo "Installing wasm-bindgen-cli $VERSION" | |
| cargo install -f wasm-bindgen-cli --version "$VERSION" | |
| # tree-sitter for grammar builds | |
| - name: Set up tree-sitter CLI | |
| run: | | |
| curl -LO https://github.com/tree-sitter/tree-sitter/releases/download/v0.25.8/tree-sitter-linux-x86.gz | |
| gunzip tree-sitter-linux-x86.gz | |
| chmod +x tree-sitter-linux-x86 | |
| sudo mv tree-sitter-linux-x86 /usr/local/bin/tree-sitter | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24' | |
| cache: 'npm' | |
| - name: Install npm dependencies | |
| run: npm ci | |
| # WASM artifacts must exist before any package's `vite build` runs | |
| # (hub-client and q2-demos/* all import the WASM module). | |
| - name: Build WASM | |
| run: | | |
| cd hub-client | |
| npm run build:wasm | |
| # Build only the workspaces the e2e tests actually exercise. | |
| # q2-demos/* currently fail their vite build (they import an | |
| # absolute path "/src/wasm-js-bridge/cache.js" that only exists | |
| # in hub-client/), and trace-viewer is not under test here. | |
| # | |
| # VITE_E2E=1 on the hub-client build pulls in src/test-hooks.ts so | |
| # the Playwright suite can reach internal services via | |
| # `window.__quartoTest` (production bundles don't expose source | |
| # paths the way `vite dev` did). The flag is consumed only by the | |
| # `if (import.meta.env.VITE_E2E === '1')` branch in src/main.tsx; | |
| # production-user builds leave it unset and tree-shake the hooks out. | |
| - name: Build TypeScript packages (ts-packages + hub-client) | |
| env: | |
| VITE_E2E: '1' | |
| run: | | |
| for pkg in ts-packages/*/; do | |
| if [ -f "$pkg/package.json" ]; then | |
| npm run build --workspace "$pkg" --if-present | |
| fi | |
| done | |
| npm run build --workspace hub-client --if-present | |
| # globalSetup launches the hub via `cargo run --bin hub` with a 120s | |
| # readiness timeout. On a cold CI runner the compile alone exceeds | |
| # that, so pre-build it here. | |
| - name: Pre-build hub binary | |
| run: cargo build --bin hub | |
| # Install Playwright browsers | |
| - name: Install Playwright | |
| run: | | |
| cd hub-client | |
| npx playwright install --with-deps chromium | |
| # Delete all snapshots if recreating from scratch | |
| - name: Delete all snapshots (recreate mode) | |
| if: inputs.recreate-all-snapshots == true | |
| run: find hub-client/e2e -type d -name '*-snapshots' -exec rm -rf {} + || true | |
| # Run E2E tests — custom specs only (smoke-all excluded by testIgnore) | |
| - name: Run E2E tests | |
| run: | | |
| cd hub-client | |
| npx playwright test | |
| # Smoke-all: full render pipeline matrix (~80 tests). Runs nightly | |
| # (schedule trigger) or on demand (workflow_dispatch with run-smoke-all=true). | |
| # Skipped for every push/PR. Use this to verify render correctness after | |
| # changes to the WASM pipeline or theme resolution. | |
| - name: Run smoke-all E2E tests (nightly + opt-in) | |
| if: inputs.run-smoke-all == true || github.event_name == 'schedule' | |
| run: | | |
| cd hub-client | |
| npx playwright test --config playwright.smoke-all.config.ts | |
| # Run visual regression tests | |
| - name: Run visual tests | |
| id: visual | |
| continue-on-error: true | |
| run: | | |
| cd hub-client | |
| npx playwright test --config playwright.visual.config.ts | |
| # If visual tests failed, retry with --update-snapshots=missing | |
| # This only creates baselines for NEW tests; existing mismatches still fail | |
| - name: Retry visual tests (add missing baselines) | |
| id: visual-retry | |
| if: steps.visual.outcome == 'failure' | |
| run: | | |
| cd hub-client | |
| npx playwright test --config playwright.visual.config.ts --update-snapshots=missing | |
| # If retry passed (only missing baselines), commit them back. | |
| # Use `find` instead of a `**` glob — bash globstar isn't enabled | |
| # by default and visual specs sit directly under hub-client/e2e/, | |
| # not one directory deeper. | |
| - name: Commit new baselines | |
| if: steps.visual.outcome == 'failure' && steps.visual-retry.outcome == 'success' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| find hub-client/e2e -type d -name '*-snapshots' -exec git add -f {} + | |
| git diff --cached --quiet || git commit -m "Add missing Playwright visual regression baselines" | |
| git push | |
| # Fail the workflow if visual retry also failed (real regression) | |
| - name: Fail on visual regression | |
| if: steps.visual-retry.outcome == 'failure' | |
| run: exit 1 | |
| # Upload test artifacts on failure | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v7 | |
| if: failure() | |
| with: | |
| name: playwright-report | |
| path: hub-client/playwright-report/ | |
| retention-days: 7 |