mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 02:28:59 +00:00
Prefer explicit Playwright browser base URL
This commit is contained in:
parent
367208d489
commit
659c5f5988
5 changed files with 175 additions and 10 deletions
|
|
@ -109,7 +109,13 @@ server-side update execution surfaces.
|
|||
browser base URL precedence so repo-root browser proofs attach to the live
|
||||
managed hot-dev shell or runtime-state browser URL instead of silently
|
||||
falling back to the embedded `:7655` frontend when a managed browser shell
|
||||
is already the active truth.
|
||||
is already the active truth. When both `PLAYWRIGHT_BASE_URL` and
|
||||
`PULSE_BASE_URL` are present, browser attachment must prefer
|
||||
`PLAYWRIGHT_BASE_URL` while backend-oriented setup and health helpers may
|
||||
still use `PULSE_BASE_URL`. That shared helper must also honor
|
||||
`PULSE_E2E_REPO_ROOT` for runtime-state and managed-session discovery so
|
||||
isolated verification harnesses can relocate managed runtime state without
|
||||
mutating the live repo root.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
@ -440,6 +446,11 @@ explicit base URL, runtime-state file, and any active managed `hot-dev`
|
|||
session. If `hot-dev-bg` is already running, ad hoc Playwright/browser helpers
|
||||
must prefer the managed shell on `http://127.0.0.1:5173` instead of silently
|
||||
teaching the backend port as the default local browser target.
|
||||
When both `PLAYWRIGHT_BASE_URL` and `PULSE_BASE_URL` are set, the shared
|
||||
browser helper must treat `PLAYWRIGHT_BASE_URL` as the browser truth and leave
|
||||
`PULSE_BASE_URL` available for backend-oriented health checks and setup
|
||||
traffic, so split browser/backend proof can target fresh frontend code without
|
||||
rewiring the API-side contract.
|
||||
That defaulting rule must live in one shared integration helper rather than
|
||||
being duplicated between config and helper files, so future browser-target
|
||||
changes cannot leave Playwright navigation and browser/API helper calls
|
||||
|
|
|
|||
|
|
@ -700,6 +700,9 @@ test_integration_readme_uses_managed_backend_restart_wrapper() {
|
|||
assert_contains "integration readme names the owner process" "${output}" "owner process"
|
||||
assert_contains "integration readme documents recovery layout proof" "${output}" "tests/17-recovery-layout.spec.ts"
|
||||
assert_contains "integration readme documents patrol runtime-state proof" "${output}" "tests/18-patrol-runtime-state.spec.ts"
|
||||
assert_contains "integration readme documents explicit browser override precedence" "${output}" "PLAYWRIGHT_BASE_URL"
|
||||
assert_contains "integration readme documents backend base split" "${output}" "backend-oriented base"
|
||||
assert_contains "integration readme documents split browser/backend example" "${output}" "PLAYWRIGHT_BASE_URL='http://127.0.0.1:4174'"
|
||||
assert_not_contains "integration readme no longer documents raw backend restart script" "${output}" "./scripts/hot-dev-bg.sh backend-restart"
|
||||
}
|
||||
|
||||
|
|
@ -709,7 +712,7 @@ test_integration_quick_start_distinguishes_embedded_frontend_from_hot_dev() {
|
|||
|
||||
assert_contains "integration quick start labels 7655 as embedded frontend" "${output}" "Pulse test UI (embedded frontend)"
|
||||
assert_contains "integration quick start points local managed dev runtime to 5173" "${output}" "http://127.0.0.1:5173"
|
||||
assert_contains "integration quick start names hot-dev browser shell" "${output}" "canonical hot-dev browser shell"
|
||||
assert_contains "integration quick start names hot-dev browser shell" "${output}" "hot-dev browser shell"
|
||||
}
|
||||
|
||||
test_acceptance_doc_distinguishes_embedded_frontend_from_hot_dev() {
|
||||
|
|
@ -754,9 +757,10 @@ test_integration_helpers_prefer_managed_hot_dev_runtime() {
|
|||
local output
|
||||
output="$(cat "${ROOT_DIR}/tests/integration/tests/helpers.ts")"
|
||||
|
||||
assert_contains "integration helpers import shared browser defaults" "${output}" "import { preferredBrowserBaseURL, readRuntimeState } from './runtime-defaults';"
|
||||
assert_contains "integration helpers import preferred browser helper" "${output}" "preferredBrowserBaseURL, readRuntimeState"
|
||||
assert_contains "integration helpers import shared browser defaults" "${output}" "runtime-defaults"
|
||||
assert_contains "integration helpers delegate browser context base url to shared helper" "${output}" "baseURL: preferredBrowserBaseURL(),"
|
||||
assert_contains "integration helpers delegate api request base url to shared helper" "${output}" "const baseURL = preferredBrowserBaseURL().replace(/\\/+\$/, '');"
|
||||
assert_contains "integration helpers delegate api request base url to shared helper" "${output}" "const baseURL = preferredBrowserBaseURL().replace"
|
||||
}
|
||||
|
||||
test_integration_runtime_defaults_centralize_managed_browser_detection() {
|
||||
|
|
@ -769,6 +773,68 @@ test_integration_runtime_defaults_centralize_managed_browser_detection() {
|
|||
assert_contains "runtime defaults expose preferred browser base url" "${output}" "export const preferredBrowserBaseURL ="
|
||||
}
|
||||
|
||||
test_integration_runtime_defaults_prefer_explicit_browser_override() {
|
||||
local output
|
||||
output="$(
|
||||
ROOT_DIR="${ROOT_DIR}" \
|
||||
node --input-type=module <<'EOF'
|
||||
import path from 'node:path';
|
||||
|
||||
const rootDir = process.env.ROOT_DIR;
|
||||
const modulePath = path.join(rootDir, 'tests', 'integration', 'tests', 'runtime-defaults.ts');
|
||||
const { preferredBrowserBaseURL } = await import(`file://${modulePath}`);
|
||||
|
||||
console.log(
|
||||
preferredBrowserBaseURL({
|
||||
PLAYWRIGHT_BASE_URL: 'http://127.0.0.1:4174',
|
||||
PULSE_BASE_URL: 'http://127.0.0.1:7655',
|
||||
}),
|
||||
);
|
||||
EOF
|
||||
)"
|
||||
|
||||
assert_contains "runtime defaults prefer explicit Playwright browser override" "${output}" "http://127.0.0.1:4174"
|
||||
assert_not_contains "runtime defaults do not reuse backend base as browser target when override exists" "${output}" "http://127.0.0.1:7655"
|
||||
}
|
||||
|
||||
test_integration_runtime_defaults_honor_repo_root_override() {
|
||||
local output
|
||||
output="$(
|
||||
ROOT_DIR="${ROOT_DIR}" \
|
||||
node --input-type=module <<'EOF'
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const rootDir = process.env.ROOT_DIR;
|
||||
const modulePath = path.join(rootDir, 'tests', 'integration', 'tests', 'runtime-defaults.ts');
|
||||
const { managedDevBrowserBaseURL, runtimeStatePath } = await import(`file://${modulePath}`);
|
||||
|
||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pulse-runtime-defaults-'));
|
||||
await fs.mkdir(path.join(repoRoot, 'tmp'), { recursive: true });
|
||||
await fs.writeFile(path.join(repoRoot, 'tmp', 'hot-dev.bg.pid'), `${process.pid}\n`, 'utf8');
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`stateMatches=${runtimeStatePath({ PULSE_E2E_REPO_ROOT: repoRoot }) === path.join(repoRoot, 'tmp', 'e2e-runtime-state.json')}`,
|
||||
);
|
||||
console.log(
|
||||
`managed=${managedDevBrowserBaseURL({
|
||||
PULSE_E2E_REPO_ROOT: repoRoot,
|
||||
FRONTEND_DEV_HOST: '127.0.0.1',
|
||||
FRONTEND_DEV_PORT: '4174',
|
||||
})}`,
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(repoRoot, { recursive: true, force: true });
|
||||
}
|
||||
EOF
|
||||
)"
|
||||
|
||||
assert_contains "runtime defaults resolve runtime-state path from overridden repo root" "${output}" "stateMatches=true"
|
||||
assert_contains "runtime defaults resolve managed dev browser url from overridden repo root" "${output}" "managed=http://127.0.0.1:4174"
|
||||
}
|
||||
|
||||
test_clean_mock_alerts_prefers_managed_runtime() {
|
||||
local test_dir fake_bin alert_history fake_hot_dev_bg action_log output
|
||||
test_dir="$(mktemp -d)"
|
||||
|
|
@ -1012,7 +1078,14 @@ main() {
|
|||
test_hot_dev_bg_script_advertises_managed_entrypoint
|
||||
test_hot_dev_bg_usage_prefers_managed_wrappers
|
||||
test_integration_readme_uses_managed_backend_restart_wrapper
|
||||
test_integration_quick_start_distinguishes_embedded_frontend_from_hot_dev
|
||||
test_acceptance_doc_distinguishes_embedded_frontend_from_hot_dev
|
||||
test_playwright_defaults_prefer_managed_hot_dev_runtime
|
||||
test_root_playwright_wrapper_prefers_managed_browser_runtime
|
||||
test_integration_helpers_prefer_managed_hot_dev_runtime
|
||||
test_integration_runtime_defaults_centralize_managed_browser_detection
|
||||
test_integration_runtime_defaults_prefer_explicit_browser_override
|
||||
test_integration_runtime_defaults_honor_repo_root_override
|
||||
test_clean_mock_alerts_prefers_managed_runtime
|
||||
test_dev_check_uses_managed_runtime_status
|
||||
test_backend_restart_requires_managed_runtime
|
||||
|
|
|
|||
|
|
@ -149,6 +149,20 @@ npm test -- tests/16-dev-runtime-recovery.spec.ts tests/17-recovery-layout.spec.
|
|||
This mode attaches Playwright to the canonical dev browser entrypoint on `http://127.0.0.1:5173`, uses the repo-root managed runtime wrappers as the control surface, and writes browser runtime connection state for Playwright instead of targeting the backend port directly. Those wrappers are backed by `scripts/hot-dev-bg.sh`, but the wrapper surface is the canonical operator contract.
|
||||
When you run the wrapper as `npm run dev:verify`, the managed launcher hands a verification-lock path into the integration runner, and the runner holds that lock for the actual pretest, Playwright, and posttest lifetime so unrelated backend source churn does not invalidate the recovery proofs mid-run.
|
||||
When no explicit `PULSE_BASE_URL`, `PLAYWRIGHT_BASE_URL`, or runtime-state file is present, the shared integration browser-default helper now prefers the managed hot-dev browser shell on `http://127.0.0.1:5173` whenever `hot-dev-bg` is already running, and only falls back to the embedded frontend on `:7655` when there is no managed dev session to attach to.
|
||||
When both are set, `PLAYWRIGHT_BASE_URL` now wins for the browser target while
|
||||
`PULSE_BASE_URL` remains the backend-oriented base for pretest health checks,
|
||||
explicit API helpers, and non-browser setup work. Use that split when you need
|
||||
Playwright on fresh frontend code but still want backend provisioning or
|
||||
health checks against the canonical Pulse API port.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
cd tests/integration
|
||||
PULSE_E2E_SKIP_DOCKER=1 \
|
||||
PULSE_BASE_URL='http://127.0.0.1:7655' \
|
||||
PLAYWRIGHT_BASE_URL='http://127.0.0.1:4174' \
|
||||
npm test -- tests/30-setup-platform-connections-handoff.spec.ts --project=chromium
|
||||
```
|
||||
|
||||
If the managed runtime is not already running, the harness starts it. If you need the harness to reclaim existing unmanaged `5173`/`7655` listeners first, add `PULSE_E2E_HOT_DEV_TAKEOVER=1`.
|
||||
|
||||
|
|
|
|||
61
tests/integration/scripts/runtime-defaults.test.mjs
Normal file
61
tests/integration/scripts/runtime-defaults.test.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
managedDevBrowserBaseURL,
|
||||
preferredBrowserBaseURL,
|
||||
runtimeStatePath,
|
||||
} from '../tests/runtime-defaults.ts';
|
||||
|
||||
test('preferredBrowserBaseURL lets PLAYWRIGHT_BASE_URL override the browser target', () => {
|
||||
const resolved = preferredBrowserBaseURL({
|
||||
PLAYWRIGHT_BASE_URL: 'http://127.0.0.1:4174',
|
||||
PULSE_BASE_URL: 'http://127.0.0.1:7655',
|
||||
});
|
||||
|
||||
assert.equal(resolved, 'http://127.0.0.1:4174');
|
||||
});
|
||||
|
||||
test('preferredBrowserBaseURL still falls back to PULSE_BASE_URL when no browser override exists', () => {
|
||||
const resolved = preferredBrowserBaseURL({
|
||||
PULSE_BASE_URL: 'http://127.0.0.1:7655',
|
||||
});
|
||||
|
||||
assert.equal(resolved, 'http://127.0.0.1:7655');
|
||||
});
|
||||
|
||||
test('runtimeStatePath resolves the default path from an overridden integration repo root', () => {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-runtime-defaults-'));
|
||||
|
||||
try {
|
||||
assert.equal(
|
||||
runtimeStatePath({
|
||||
PULSE_E2E_REPO_ROOT: repoRoot,
|
||||
}),
|
||||
path.join(repoRoot, 'tmp', 'e2e-runtime-state.json'),
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(repoRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('managedDevBrowserBaseURL keeps the configured host and port when a managed session exists', () => {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-managed-dev-runtime-'));
|
||||
fs.mkdirSync(path.join(repoRoot, 'tmp'), { recursive: true });
|
||||
fs.writeFileSync(path.join(repoRoot, 'tmp', 'hot-dev.bg.pid'), `${process.pid}\n`);
|
||||
|
||||
try {
|
||||
const resolved = managedDevBrowserBaseURL({
|
||||
FRONTEND_DEV_HOST: '127.0.0.1',
|
||||
FRONTEND_DEV_PORT: '4174',
|
||||
PULSE_E2E_REPO_ROOT: repoRoot,
|
||||
});
|
||||
|
||||
assert.equal(resolved, 'http://127.0.0.1:4174');
|
||||
} finally {
|
||||
fs.rmSync(repoRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
@ -3,8 +3,6 @@ import path from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, '..', '..', '..');
|
||||
const managedHotDevPidPath = path.join(repoRoot, 'tmp', 'hot-dev.bg.pid');
|
||||
|
||||
export type RuntimeState = {
|
||||
baseURL?: string;
|
||||
|
|
@ -13,12 +11,20 @@ export type RuntimeState = {
|
|||
|
||||
const trim = (value: unknown): string => String(value ?? '').trim();
|
||||
|
||||
const repoRootFromEnv = (env: NodeJS.ProcessEnv = process.env): string =>
|
||||
trim(env.PULSE_E2E_REPO_ROOT) || path.resolve(__dirname, '..', '..', '..');
|
||||
|
||||
const managedHotDevPidPath = (env: NodeJS.ProcessEnv = process.env): string =>
|
||||
path.join(repoRootFromEnv(env), 'tmp', 'hot-dev.bg.pid');
|
||||
|
||||
export const runtimeStatePath = (env: NodeJS.ProcessEnv = process.env): string => {
|
||||
const configuredPath = trim(env.PULSE_E2E_RUNTIME_STATE_PATH);
|
||||
if (configuredPath === '') {
|
||||
return path.resolve(repoRoot, 'tmp', 'e2e-runtime-state.json');
|
||||
return path.resolve(repoRootFromEnv(env), 'tmp', 'e2e-runtime-state.json');
|
||||
}
|
||||
return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(repoRoot, configuredPath);
|
||||
return path.isAbsolute(configuredPath)
|
||||
? configuredPath
|
||||
: path.resolve(repoRootFromEnv(env), configuredPath);
|
||||
};
|
||||
|
||||
export const readRuntimeState = (
|
||||
|
|
@ -45,7 +51,7 @@ export const managedDevBrowserBaseURL = (
|
|||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string | null => {
|
||||
try {
|
||||
const pid = Number.parseInt(fs.readFileSync(managedHotDevPidPath, 'utf8').trim(), 10);
|
||||
const pid = Number.parseInt(fs.readFileSync(managedHotDevPidPath(env), 'utf8').trim(), 10);
|
||||
if (!Number.isInteger(pid) || pid <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -61,8 +67,8 @@ export const managedDevBrowserBaseURL = (
|
|||
export const preferredBrowserBaseURL = (
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string =>
|
||||
trim(env.PULSE_BASE_URL) ||
|
||||
trim(env.PLAYWRIGHT_BASE_URL) ||
|
||||
trim(env.PULSE_BASE_URL) ||
|
||||
loadRuntimeBaseURL(env) ||
|
||||
managedDevBrowserBaseURL(env) ||
|
||||
'http://localhost:7655';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue