diff --git a/packages/host/app/lib/externals.ts b/packages/host/app/lib/externals.ts index ffd74cf6ddc..c9e13737ead 100644 --- a/packages/host/app/lib/externals.ts +++ b/packages/host/app/lib/externals.ts @@ -188,60 +188,26 @@ export function shimExternals(virtualNetwork: VirtualNetwork) { shimHostCommands(virtualNetwork); } -// Shims test-only module IDs into the virtual network as throwing stubs so -// realm cards that colocate test imports can load in any environment without -// executing test code. live-test.js overrides these at the loader level with -// the real implementations when a test runner is present. +// Shims test-only module IDs into the virtual network as empty fallbacks so +// realm cards that co-locate test imports (e.g. *.test.gts) can load in any +// environment without crashing. These are never actually called in production +// — they just prevent import resolution errors. +// +// In live-test runs, live-test.js overrides these at the *realm loader* level +// (via loader.shimModule) with the real implementations before importing test +// modules. The loader-level shim takes precedence over this network-level +// fallback, so the real helpers are used during test execution. export function shimModulesForLiveTests(virtualNetwork: VirtualNetwork) { - const windowQUnit = (globalThis as any).QUnit; - - const testOnlyStub = (moduleId: string) => - new Proxy( - {}, - { - get: () => { - throw new Error( - `${moduleId} is only available in a test environment.`, - ); - }, - }, - ); - - // Use real @ember/test-helpers only when QUnit is running; stub otherwise. - virtualNetwork.shimModule( - '@ember/test-helpers', - windowQUnit ? emberTestHelpers : testOnlyStub('@ember/test-helpers'), - ); - - // Always stub host test helpers here — live-test.js shimModule() on the - // realm loader overrides these with real implementations at test time. - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers', - testOnlyStub('@cardstack/host/tests/helpers'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/mock-matrix', - testOnlyStub('@cardstack/host/tests/helpers/mock-matrix'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/setup', - testOnlyStub('@cardstack/host/tests/helpers/setup'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/adapter', - testOnlyStub('@cardstack/host/tests/helpers/adapter'), - ); + virtualNetwork.shimModule('@ember/test-helpers', emberTestHelpers); + virtualNetwork.shimModule('@cardstack/host/tests/helpers', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/mock-matrix', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/setup', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/adapter', {}); virtualNetwork.shimModule( '@cardstack/host/tests/helpers/render-component', - testOnlyStub('@cardstack/host/tests/helpers/render-component'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/base-realm', - testOnlyStub('@cardstack/host/tests/helpers/base-realm'), - ); - virtualNetwork.shimModule( - '@universal-ember/test-support', - testOnlyStub('@universal-ember/test-support'), + {}, ); - virtualNetwork.shimModule('@ember/owner', testOnlyStub('@ember/owner')); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/base-realm', {}); + virtualNetwork.shimModule('@universal-ember/test-support', {}); + virtualNetwork.shimModule('@ember/owner', {}); } diff --git a/packages/host/docs/live-tests.md b/packages/host/docs/live-tests.md new file mode 100644 index 00000000000..6fe8e796fc2 --- /dev/null +++ b/packages/host/docs/live-tests.md @@ -0,0 +1,26 @@ +# Live Tests (Card-Based) + +Live tests run directly against a realm server. Test modules are `*.test.gts` files inside a realm that export a `runTests()` function — files must follow this naming pattern to be auto-discovered via the realm's `_mtimes` endpoint. + +## Run in Browser + +Requires servers to already be running. + +- Experiments realm: `http://localhost:4200/tests/index.html?liveTest=true&realmURL=http://localhost:4201/experiments/&hidepassed` +- Catalog realm: `http://localhost:4200/tests/index.html?liveTest=true&realmURL=http://localhost:4201/catalog/&hidepassed` + +## Run as a Script + +Requires realm servers to be running (experiments + catalog). If you already have `pnpm start:all` running, that is sufficient. + +```sh +# Terminal 1 — start realm servers if not already running +mise run test-services:host + +# Terminal 2 — run the default live test suite (catalog realm) +cd packages/host +pnpm test:live + +# Or target a specific realm via the REALM_URL env var (trailing slash optional) +REALM_URL=http://localhost:4201/experiments/ pnpm test:live +``` diff --git a/packages/host/scripts/live-test-wait-for-servers.sh b/packages/host/scripts/live-test-wait-for-servers.sh index fc4587ce41c..f2b5bf5664c 100755 --- a/packages/host/scripts/live-test-wait-for-servers.sh +++ b/packages/host/scripts/live-test-wait-for-servers.sh @@ -2,13 +2,27 @@ READY_PATH="_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson" BASE_REALM_READY="http-get://localhost:4201/base/${READY_PATH}" -CATALOG_REALM_READY="http-get://localhost:4201/catalog/${READY_PATH}" SYNAPSE_URL="http://localhost:8008" SMTP_4_DEV_URL="http://localhost:5001" -READY_URLS="$BASE_REALM_READY|$CATALOG_REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +if [ -n "$REALM_URL" ]; then + REALM_HOST="$REALM_URL" + case "$REALM_HOST" in + http://*) REALM_HOST="${REALM_HOST#http://}" ;; + https://*) REALM_HOST="${REALM_HOST#https://}" ;; + esac + case "$REALM_HOST" in + */) ;; + *) REALM_HOST="${REALM_HOST}/" ;; + esac + REALM_READY="http-get://${REALM_HOST}${READY_PATH}" + READY_URLS="$BASE_REALM_READY|$REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +else + CATALOG_REALM_READY="http-get://localhost:4201/catalog/${READY_PATH}" + READY_URLS="$BASE_REALM_READY|$CATALOG_REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +fi -WAIT_ON_TIMEOUT=600000 NODE_NO_WARNINGS=1 start-server-and-test \ +WAIT_ON_TIMEOUT=600000 NODE_NO_WARNINGS=1 REALM_URL="${REALM_URL:-}" start-server-and-test \ 'pnpm run wait' \ "$READY_URLS" \ 'ember test --config-file testem-live.js --path ./dist' diff --git a/packages/host/testem-live.js b/packages/host/testem-live.js index 696942ad6e3..4306fbd48f0 100644 --- a/packages/host/testem-live.js +++ b/packages/host/testem-live.js @@ -6,9 +6,17 @@ const XunitReporter = require('testem/lib/reporters/xunit_reporter'); const fs = require('fs'); const path = require('path'); +const DEFAULT_REALM_URLS = ['http://localhost:4201/catalog/']; + +const realmURLs = process.env.REALM_URL + ? [process.env.REALM_URL] + : DEFAULT_REALM_URLS; + const config = { - test_page: - 'tests/index.html?liveTest=true&realmURL=http://localhost:4201/catalog/&hidepassed', + test_page: realmURLs.map( + (url) => + `tests/index.html?liveTest=true&realmURL=${encodeURIComponent(url)}&hidepassed`, + ), disable_watching: true, browser_timeout: 120, browser_no_activity_timeout: 120, diff --git a/packages/host/tests/live-test.js b/packages/host/tests/live-test.js index 5e13a1f8b52..fcd54bc16a2 100644 --- a/packages/host/tests/live-test.js +++ b/packages/host/tests/live-test.js @@ -1,7 +1,7 @@ import * as QUnit from 'qunit'; /** - * Discovers all .gts/.ts module URLs in a realm using the _mtimes endpoint, + * Discovers all *.test.gts module URLs in a realm using the _mtimes endpoint, * which returns a flat map of every file URL in the realm in one request. * Only modules that export a `runTests` function will actually register tests. * @@ -12,6 +12,11 @@ async function discoverTestModules(realmURL) { const resp = await fetch(`${realmURL}_mtimes`, { headers: { Accept: 'application/vnd.api+json' }, }); + if (!resp.ok) { + throw new Error( + `Cannot access realm ${realmURL} (HTTP ${resp.status}). Check that the realm is publicly readable.`, + ); + } const { data: { attributes: { mtimes }, @@ -19,8 +24,8 @@ async function discoverTestModules(realmURL) { } = await resp.json(); return Object.keys(mtimes) - .filter((url) => url.endsWith('.gts') || url.endsWith('.ts')) - .map((url) => url.replace(/\.(gts|ts)$/, '')); + .filter((url) => url.endsWith('.test.gts')) + .map((url) => url.slice(0, -'.gts'.length)); } // eslint-disable-next-line ember/no-test-import-export