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
74 changes: 53 additions & 21 deletions bin/e2e
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
#!/bin/bash
#
# Run the viewer's end-to-end suite against one or more deployed targets.
#
# ./bin/e2e # default: cross-consistency check (pages + workers)
# ./bin/e2e pages # the live Cloudflare Pages site
# ./bin/e2e workers # the live Cloudflare Workers site
# ./bin/e2e graphs # the graphs companion Worker
# ./bin/e2e preview # a Workers preview (set PREVIEW_URL=...)
# ./bin/e2e https://... # an arbitrary URL (treated as a preview)
#
# Targets resolve their base URL + data mode (live) in cypress.config.ts; these
# runs hit each deployment's real data bundle. Use `pnpm --filter viewer cy:run`
# (CYPRESS_ENV=local) for the hermetic, fixture-backed suite.

function run() {
url=$(getUrl "$1")
echo "$url"
set -euo pipefail

# wait for up to 5 minutes for the site to be available
for i in {1..30}; do
status=$(curl --write-out %{http_code} --silent --output /dev/null https://$url)
if [ "$status" -eq 200 ]; then
break
fi
sleep 10
done
targets=("$@")
if [ ${#targets[@]} -eq 0 ]; then
targets=(pages workers)
fi

pnpm --filter viewer run cy:run --config baseUrl=https://$url
# Mirror cypress.config.ts so we can health-check a target before running.
function baseUrl() {
case "$1" in
pages | production) echo "https://topology.pi-base.org" ;;
workers) echo "https://pi-base-topology.fragrant-boat-7068.workers.dev" ;;
graphs) echo "https://pi-base-graphs.fragrant-boat-7068.workers.dev" ;;
preview) echo "${PREVIEW_URL:-}" ;;
*) echo "" ;;
esac
}

function getUrl() {
if [ "$1" == "production" ]; then
echo "topology.pi-base.org"
elif [ -n "$1" ]; then
echo "$1.topology.pages.dev"
else
sha=$(git rev-parse --abbrev-ref HEAD | sed 's/\//-/g')
echo "$sha.topology.pages.dev"
# Wait up to 5 minutes for a URL to return 200 (preview/deploys may be warming).
function waitFor() {
local url="$1"
[ -z "$url" ] && return 0
for _ in {1..30}; do
if [ "$(curl --silent --output /dev/null --write-out '%{http_code}' "$url/")" = "200" ]; then
return 0
fi
sleep 10
done
echo "warning: $url did not return 200 within 5m; running anyway" >&2
}

run "$@"
status=0
for target in "${targets[@]}"; do
# A bare URL is run as a preview against that URL.
if [[ "$target" == http* ]]; then
export PREVIEW_URL="$target"
target=preview
fi

url=$(baseUrl "$target")
echo "▶ e2e against '$target'${url:+ ($url)}"
waitFor "$url"

CYPRESS_ENV="$target" pnpm --filter viewer run cy:run || status=1
done

exit $status
52 changes: 44 additions & 8 deletions doc/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,54 @@ for more details.
<root>/packages/viewer $ pnpm run cy:run --config baseUrl=<url>
```

## Fixture vs. live data

`CYPRESS_ENV` selects which deployment to test _and_ the data the suite runs
against:

- **`fixture`** (default, used by `local`/CI) intercepts the data bundle with
`cypress/fixtures/main.min.json`, so content assertions are deterministic.
- **`live`** (all deployed targets) lets the app fetch its real R2 bundle, so we
exercise the actual deployment end-to-end and confirm the sites stay
consistent.

| `CYPRESS_ENV` | URL | Mode |
| -------------------- | ------------------------------------ | ------- |
| `local` (default) | `http://localhost:5173` | fixture |
| `pages` | `https://topology.pi-base.org` | live |
| `workers` | `pi-base-topology.…workers.dev` | live |
| `graphs` | `pi-base-graphs.…workers.dev` | live |
| `preview` | `$PREVIEW_URL` | live |

The whole suite runs in both modes; assertions are written to hold against the
real data (stable IDs, math-free name prefixes, behavioral checks) rather than
exact HTML snapshots. Set `CYPRESS_MODE=fixture` to force the deterministic
fixture against a deployed URL while debugging.

The fixture (`cypress/fixtures/main.min.json`) is a hand-curated subset; keep its
tested entities (e.g. `S000001`, `S000004`, `P000001`) in sync with live data.
Note it predates a pi-base property-ID reorganization, so its property `uid`s are
**not** interchangeable with the current bundle's — refresh display fields per
entity, don't bulk-remap by `uid`.

## Remote End-to-End Testing

There is a `./bin/e2e` script to facilitate running the end-to-end test suite
against a few common external URLs:
The `./bin/e2e` script runs the suite against the deployed targets (live data),
health-checking each first:

```bash
# Run tests against the deployed production URL
<root> $ ./bin/e2e production
# Cross-consistency check across the live sites (default: pages + workers)
<root> $ ./bin/e2e

# Run tests against a Cloudflare Pages preview URL for a named branch
<root> $ ./bin/e2e <branch>
# A single live target
<root> $ ./bin/e2e pages
<root> $ ./bin/e2e workers
<root> $ ./bin/e2e graphs

# Run tests against a Cloudflare Pages preview URL for the current branch
<root> $ ./bin/e2e
# A Workers preview ("wrangler versions upload" prints a per-version URL)
<root> $ PREVIEW_URL=https://<version>-pi-base-topology.<subdomain>.workers.dev ./bin/e2e preview
<root> $ ./bin/e2e https://<version>-pi-base-topology.<subdomain>.workers.dev
```

Equivalently, per-target package scripts: `pnpm --filter viewer run
cy:run:pages` (or `:workers` / `:preview` / `:graphs`).
79 changes: 60 additions & 19 deletions packages/viewer/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,77 @@
import { defineConfig } from 'cypress'
import { execSync } from 'child_process'

function getCurrentBranch() {
try {
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim()
} catch (error) {
throw new Error(`Could not determine current git branch: ${error}`)
}
}
// The account's workers.dev subdomain for the viewer Workers (see
// doc/deployment.md). Used to address the deployed Workers directly.
const WORKERS_SUBDOMAIN = 'fragrant-boat-7068.workers.dev'

type Mode = 'fixture' | 'live'
type Target = { baseUrl: string; mode: Mode }

function baseUrl({ CYPRESS_ENV = 'local' } = process.env) {
switch (CYPRESS_ENV) {
// `CYPRESS_ENV` selects which deployment to test and, with it, the data source:
//
// fixture — intercept the data bundle with cypress/fixtures/main.min.json for
// deterministic, hermetic runs (local dev + CI).
// live — hit the deployment's real R2 bundle end-to-end, to confirm the
// actual sites render real data and stay consistent across Pages /
// Workers / previews.
//
// `CYPRESS_MODE` can override the data source for a target (e.g. run a deployed
// URL against the fixture while debugging).
function targetFor(name: string): Target {
switch (name) {
case 'local':
return 'http://localhost:5173'
case 'production':
return 'https://topology.pi-base.org'
case 'preview':
const branch = process.env.BRANCH || getCurrentBranch()
const domain = branch.toLowerCase().replaceAll('/', '-').slice(0, 28)
return `https://${domain}.topology.pages.dev`
return { baseUrl: 'http://localhost:5173', mode: 'fixture' }
case 'pages':
case 'production': // back-compat alias for the legacy Pages site
return { baseUrl: 'https://topology.pi-base.org', mode: 'live' }
case 'workers':
return {
baseUrl: `https://pi-base-topology.${WORKERS_SUBDOMAIN}`,
mode: 'live',
}
case 'graphs':
return {
baseUrl: `https://pi-base-graphs.${WORKERS_SUBDOMAIN}`,
mode: 'live',
}
case 'preview': {
// Workers preview URLs (`wrangler versions upload`) are per-version, not
// per-branch, so they can't be derived — pass the printed URL explicitly.
const url = process.env.PREVIEW_URL
if (!url) {
throw new Error(
'CYPRESS_ENV=preview requires PREVIEW_URL — the per-version URL from ' +
"`wrangler versions upload` (or the PR's Workers Builds preview).",
)
}
return { baseUrl: url, mode: 'live' }
}
default:
throw new Error(`Invalid environment: ${CYPRESS_ENV}`)
throw new Error(`Invalid CYPRESS_ENV: ${name}`)
}
}

function resolveTarget(
{ CYPRESS_ENV = 'local', CYPRESS_MODE } = process.env,
): Target {
const target = targetFor(CYPRESS_ENV)
return { ...target, mode: (CYPRESS_MODE as Mode) || target.mode }
}

const target = resolveTarget()

export default defineConfig({
projectId: 'bkb3p8',
chromeWebSecurity: false,
// Exposed to specs as `Cypress.env('mode')`; drives fixture vs. live data.
env: { mode: target.mode },
e2e: {
baseUrl: baseUrl(),
baseUrl: target.baseUrl,
experimentalRunAllSpecs: true,
specPattern: 'cypress/e2e/**/*.{ts,tsx}',
supportFile: false,
// Retry in CI/headless runs to absorb transient flake — network hiccups and
// async deduction when pointed at a live (non-intercepted) deployment.
retries: { runMode: 2, openMode: 0 },
},
})
8 changes: 6 additions & 2 deletions packages/viewer/cypress/e2e/deduction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { setup } from '../support'
import { deduce, setup } from '../support'

beforeEach(setup)

Expand All @@ -10,8 +10,12 @@ it('finds proofs for the first space', () => {

it('shows derived proofs', () => {
cy.visit('/spaces/S000004/properties/P000031')
// Wait for the client-side prover before asserting derived content.
deduce()

cy.contains('Indiscrete Topology on a Two-Point Set')
// Math-free prefix of the space name (the name embeds KaTeX), so this holds
// against both the fixture and live data.
cy.contains('Indiscrete topology on')
cy.contains('Metacompact')

cy.contains('Automatically deduced from the following', { timeout: 20000 })
Expand Down
15 changes: 10 additions & 5 deletions packages/viewer/cypress/e2e/typesetting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ it('renders internal links', () => {
`{{}S000001} is {{}P000001} as noted in {{}S000001|P000001}`,
)

cy.get('[data-testid=output]').contains(
// The T0T_0T0 rendering here is the text representation of the inner html of katex rendered math.
// We're effectively asserting that it _isn't_ still rendered as $T_0$
'Discrete topology on a two-point set is T0T_0T0​ as noted in Discrete topology on a two-point set is T0T_0T0​',
)
// The references should resolve to names and any embedded math should be
// typeset by KaTeX — not left as literal `{...}` or `$...$`. We assert the
// behavior rather than an exact HTML snapshot, so it holds against live data
// (whose names embed math) and across KaTeX versions.
cy.get('[data-testid=output]')
.should('not.contain', '{S000001}')
.and('not.contain', '$T_0$')
.and('contain', 'Discrete topology on')
.and('contain', 'as noted in')
cy.get('[data-testid=output] .katex').should('exist')
})
8 changes: 4 additions & 4 deletions packages/viewer/cypress/fixtures/main.min.json
Original file line number Diff line number Diff line change
Expand Up @@ -944,17 +944,17 @@
{
"uid": "S000001",
"counterexamples_id": 1,
"name": "Discrete topology on a two-point set",
"name": "Discrete topology on $\\{0,1\\}$",
"description": "-",
"aliases": ["Finite Discrete Topology"],
"aliases": ["Discrete topology on a two-point set", "Finite discrete topology"],
"refs": []
},
{
"uid": "S000004",
"counterexamples_id": 4,
"name": "Indiscrete Topology on a Two-Point Set",
"name": "Indiscrete topology on $\\{0,1\\}$",
"description": "-",
"aliases": ["Indiscrete Topology on a Two-Point Set"],
"aliases": ["Indiscrete topology on a two-point set"],
"refs": []
},
{
Expand Down
11 changes: 10 additions & 1 deletion packages/viewer/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
// Whether the current run uses the deterministic fixture or the deployment's
// real data bundle (see cypress.config.ts / CYPRESS_ENV).
const live = Cypress.env('mode') === 'live'

export function setup() {
cy.clearAllLocalStorage()
cy.intercept({ path: /main.json/ }, { fixture: 'main.min.json' })
// In fixture mode, serve deterministic data so content assertions are stable.
// In live mode, let the app fetch its real bundle so we exercise the actual
// deployment end-to-end (and verify cross-site consistency).
if (!live) {
cy.intercept({ path: /main.json/ }, { fixture: 'main.min.json' })
}
}

export function deduce() {
Expand Down
12 changes: 7 additions & 5 deletions packages/viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
"cf:preview:topology": "wrangler versions upload --env topology",
"cf:preview:graphs": "wrangler versions upload --env graphs",
"cy:open": "cypress open",
"cy:open:prev": "CYPRESS_ENV=preview cypress open",
"cy:open:prod": "CYPRESS_ENV=production cypress open",
"cy:open:pages": "CYPRESS_ENV=pages cypress open",
"cy:open:workers": "CYPRESS_ENV=workers cypress open",
"cy:run": "cypress run",
"cy:run:prev": "CYPRESS_ENV=preview cypress run $@",
"cy:run:prod": "CYPRESS_ENV=production cypress run $@",
"cy:run:pages": "CYPRESS_ENV=pages cypress run",
"cy:run:workers": "CYPRESS_ENV=workers cypress run",
"cy:run:preview": "CYPRESS_ENV=preview cypress run",
"cy:run:graphs": "CYPRESS_ENV=graphs cypress run",
"dev": "vite",
"preview": "vite preview",
"test": "vitest run",
Expand Down Expand Up @@ -52,7 +54,7 @@
"@types/katex": "^0.16.8",
"@types/page": "^1.11.9",
"@types/unist": "^2.0.10",
"cypress": "^14.3.2",
"cypress": "^15.18.0",
"svelte": "^4.2.20",
"svelte-check": "^3.6.3",
"svelte-preprocess": "^5.1.3",
Expand Down
Loading
Loading