Skip to content

fix(viewer): stop stackless DOMExceptions reaching PostHog with zero frames #1615

fix(viewer): stop stackless DOMExceptions reaching PostHog with zero frames

fix(viewer): stop stackless DOMExceptions reaching PostHog with zero frames #1615

Workflow file for this run

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# Advisory viewer performance benchmark. Runs the real viewer (production
# build + real WASM pipeline) against the two small fixtures and reports
# per-metric deltas vs tests/benchmark/baseline.json as a PR comment + step
# summary. It never fails the PR — regressions are surfaced, not blocking
# (promote to blocking only once the runner-to-runner noise is understood).
#
# The committed baseline is CI-recorded so this job diffs like-for-like:
# dispatch this workflow with `record_baseline` to produce a refreshed
# baseline.json as the `benchmark-baseline` artifact, then commit it via a
# normal PR (see tests/benchmark/README.md). Local machines are a different
# speed class — never commit locally recorded numbers.
name: Benchmark
on:
pull_request:
branches: [main]
# Only paths that can plausibly move viewer load performance. The job is
# advisory (not a required check), so skipping it entirely on unrelated
# PRs is safe and free.
paths:
- 'rust/**'
- 'packages/**'
- 'apps/viewer/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/build-wasm.sh'
- 'scripts/check-benchmark-regression.js'
- 'scripts/update-benchmark-baseline.mjs'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'turbo.json'
- 'package.json'
- 'playwright.config.ts'
- 'tests/benchmark/**'
- 'tests/models/manifest.json'
- '.github/workflows/benchmark.yml'
workflow_dispatch:
inputs:
record_baseline:
description: 'Upload a refreshed baseline.json (CI-recorded numbers) as the benchmark-baseline artifact'
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
benchmark:
name: Viewer benchmark (advisory)
# Free GitHub-hosted runner (public repo). The benchmark measures a noisy
# shared VM anyway; the +50% thresholds in benchmark:check absorb the
# jitter, and the job is advisory. When Rust changed we compile wasm32
# from source here too — slower than Depot but free, and correctness of
# the measurement requires the PR's own WASM.
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
lfs: false
persist-credentials: false
# Same fast path as test.yml's build job: when the WASM source is
# byte-identical to the published release tag, fetch the prebuilt
# bundle and skip the Rust toolchain entirely.
- name: Resolve prebuilt-WASM eligibility
id: wasm
run: echo "eligible=$(bash scripts/ci-wasm-prebuilt-eligible.sh)" >> "$GITHUB_OUTPUT"
- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: pnpm
- name: Setup WASM build toolchain
if: steps.wasm.outputs.eligible != 'true'
uses: ./.github/actions/setup-wasm-build
with:
cache-prefix: benchmark
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Fetch prebuilt WASM
if: steps.wasm.outputs.eligible == 'true'
run: |
for attempt in 1 2 3; do
if node scripts/fetch-prebuilt-wasm.mjs; then exit 0; fi
echo "::warning::prebuilt WASM fetch attempt ${attempt} failed; retrying…"
sleep $((attempt * 5))
done
echo "::error::Could not fetch the prebuilt @ifc-lite/wasm bundle after 3 attempts."
exit 1
- name: Build WASM
if: steps.wasm.outputs.eligible != 'true'
run: bash scripts/build-wasm.sh
# Only the two small benchmark fixtures (~11 MB), not the full ~994 MiB
# fixture tree — namespace the cache away from `ci-fixtures-…`.
- name: Cache benchmark fixtures
id: fixtures-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: tests/models
key: benchmark-fixtures-${{ runner.os }}-${{ hashFiles('tests/models/manifest.json') }}
- name: Fetch benchmark fixtures
if: steps.fixtures-cache.outputs.cache-hit != 'true'
run: node scripts/fixtures/fetch-fixtures.mjs "ara3d/AC20-FZK-Haus.ifc" "various/01_Snowdon_Towers_Sample_Structural(1).ifc"
# Production build: `vite preview` (the benchmark's webServer) serves
# apps/viewer/dist. Turbo builds the workspace deps it needs.
- name: Build viewer app
run: pnpm turbo build --filter=@ifc-lite/viewer
# benchmark:check reads every viewer-*.json in the results dir, so make
# sure only this run's output is present (results are gitignored run
# artifacts, but clear defensively in case one ever gets committed).
- name: Clear stale benchmark results
run: rm -f tests/benchmark/benchmark-results/viewer-*.json tests/benchmark/benchmark-results/viewer-*.console.log
- name: Run viewer benchmark
env:
VIEWER_BENCHMARK_FILES: 'tests/models/ara3d/AC20-FZK-Haus.ifc,tests/models/various/01_Snowdon_Towers_Sample_Structural(1).ifc'
# Advisory: the spec logs threshold violations instead of failing;
# the verdict lives in benchmark:check + the PR comment.
VIEWER_BENCHMARK_ADVISORY: '1'
VIEWER_BENCHMARK_BUILD_MODE: 'production'
# Divert the json reporter to a file so the step log stays readable.
PLAYWRIGHT_JSON_OUTPUT_NAME: ${{ runner.temp }}/playwright-benchmark.json
run: pnpm test:benchmark:viewer:ci
- name: Check against baseline (advisory)
run: |
node scripts/check-benchmark-regression.js --advisory --markdown "$RUNNER_TEMP/benchmark-report.md"
cat "$RUNNER_TEMP/benchmark-report.md" >> "$GITHUB_STEP_SUMMARY"
# Sticky comment: update the existing benchmark comment if present,
# else create one. Best-effort — fork PRs get a read-only token, and
# the step summary above already carries the same report.
- name: Comment benchmark report on PR
if: github.event_name == 'pull_request'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
marker='<!-- viewer-benchmark-report -->'
body_file="$RUNNER_TEMP/benchmark-report.md"
# --paginate applies --jq per page, so a multi-page comment list can
# emit one line per page — keep only the first id.
existing=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --paginate \
--jq "[.[] | select(.body | contains(\"$marker\")) | .id] | first // empty" | head -n1)
if [ -n "$existing" ]; then
gh api --method PATCH "repos/${{ github.repository }}/issues/comments/${existing}" -F body=@"$body_file"
else
gh api --method POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" -F body=@"$body_file"
fi
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-results
path: |
tests/benchmark/benchmark-results/viewer-*.json
tests/benchmark/benchmark-results/viewer-*.console.log
retention-days: 30
if-no-files-found: error
# Baseline refresh (workflow_dispatch with record_baseline): merge this
# run's numbers into baseline.json and upload it as an artifact. A human
# (or agent) commits it via a normal PR so the change is reviewed like
# any other — this job intentionally has no push access.
- name: Record refreshed baseline
if: github.event_name == 'workflow_dispatch' && inputs.record_baseline
run: node scripts/update-benchmark-baseline.mjs --environment "github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build"
- name: Upload refreshed baseline
if: github.event_name == 'workflow_dispatch' && inputs.record_baseline
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-baseline
path: tests/benchmark/baseline.json
retention-days: 30
if-no-files-found: error