Validate that environment configuration controls navigation targets and that test code does not hardcode
domains or rely on implicit defaults. This runbook is mode-aware: default mode forbids localhost; recording
mode (CI_NAT_MODE=1) allows an explicit localhost exception.
Objective: Verify that the target environment is provided by the runtime environment
(WEB_BASE_URL/API_BASE_URL, with optional BASE_URL compatibility)
and that missing or incorrect values produce fast, diagnosable failures.
Rule: Targets are explicit and logged at runtime. Missing values fail fast with a clear message before any UI navigation.
Default boundary (CI_NAT_MODE!=1): localhost/loopback is forbidden for SUT HTTP. No port forwarding, no SSH tunnels, no “localhost as environment.”
Recording exception (CI_NAT_MODE=1): localhost is allowed only when explicitly set
(e.g., http://127.0.0.1:3000) for sanitized recordings.
Success criteria:
WEB_BASE_URL/API_BASE_URL (or compatibility BASE_URL) are unset, validation fails immediately with a clear error
Prefer WEB_BASE_URL and API_BASE_URL. Use BASE_URL only as a compatibility
alias (set it from WEB_BASE_URL if needed).
# Default (bridged/LAN)
export CI_NAT_MODE="0"
export WEB_BASE_URL="http://sut.testlab:3000"
export API_BASE_URL="http://sut.testlab:3001/api"
# Optional compatibility
export BASE_URL="${WEB_BASE_URL}"
# Temporary for sanitized recordings
export CI_NAT_MODE="1"
export WEB_BASE_URL="http://127.0.0.1:3000"
export API_BASE_URL="http://127.0.0.1:3001/api"
export BASE_URL="${WEB_BASE_URL}"
node -e '
console.log("CI_NAT_MODE=", process.env.CI_NAT_MODE || "0");
console.log("WEB_BASE_URL=", process.env.WEB_BASE_URL || "");
console.log("API_BASE_URL=", process.env.API_BASE_URL || "");
console.log("BASE_URL=", process.env.BASE_URL || "");
'
node -e '
const nat = process.env.CI_NAT_MODE === "1";
const web = process.env.WEB_BASE_URL || process.env.BASE_URL || "";
const api = process.env.API_BASE_URL || "";
if (!web) { console.error("CONFIG ERROR: WEB_BASE_URL (or BASE_URL) must be set"); process.exit(2); }
if (!api) { console.error("CONFIG ERROR: API_BASE_URL must be set"); process.exit(2); }
if (!nat) {
const bad = /(localhost|127\.0\.0\.1|0\.0\.0\.0)/i;
if (bad.test(web)) { console.error("CONFIG ERROR: WEB_BASE_URL cannot be localhost in default mode"); process.exit(2); }
if (bad.test(api)) { console.error("CONFIG ERROR: API_BASE_URL cannot be localhost in default mode"); process.exit(2); }
}
console.log("CONFIG OK:", { CI_NAT_MODE: nat ? "1" : "0", WEB_BASE_URL: web, API_BASE_URL: api });
'
unset WEB_BASE_URL API_BASE_URL BASE_URL
node -e 'console.log("WEB_BASE_URL=", process.env.WEB_BASE_URL || ""); console.log("API_BASE_URL=", process.env.API_BASE_URL || ""); console.log("BASE_URL=", process.env.BASE_URL || "")'
# run the validation block above; it must exit non-zero with clear messaging
export CI_NAT_MODE="0"
export WEB_BASE_URL="http://sut.testlab:3999"
export API_BASE_URL="http://sut.testlab:3001/api"
export BASE_URL="${WEB_BASE_URL}"
# Optional: prove reachability fails outside Playwright
curl -sv --max-time 3 "${WEB_BASE_URL}/" || true
# Then run a single proof test
npx playwright test -g "RW\.NET\.001" --project=chromium --reporter=line
export CI_NAT_MODE="0"
export WEB_BASE_URL="http://sut.testlab:3000"
export API_BASE_URL="http://sut.testlab:3001/api"
export BASE_URL="${WEB_BASE_URL}"
curl -sS -o /dev/null -D - --max-time 3 "${WEB_BASE_URL}/" | head -n 12
curl -sS -o /dev/null -D - --max-time 3 "${API_BASE_URL}/tags" | head -n 12
npx playwright test -g "RW\.NET\.001" --project=chromium --reporter=line
export CI_NAT_MODE="1"
export WEB_BASE_URL="http://127.0.0.1:3000"
export API_BASE_URL="http://127.0.0.1:3001/api"
export BASE_URL="${WEB_BASE_URL}"
curl -sS -o /dev/null -D - --max-time 3 "${WEB_BASE_URL}/" | head -n 12
curl -sS -o /dev/null -D - --max-time 3 "${API_BASE_URL}/tags" | head -n 12
npx playwright test -g "RW\.NET\.001" --project=chromium --reporter=line
# These should return nothing (or only documentation references)
grep -R "http://sut\.testlab" -n pages tests fixtures playwright.config.* || true
grep -R "http://127\.0\.0\.1" -n pages tests fixtures playwright.config.* || true
grep -R "localhost:" -n pages tests fixtures playwright.config.* || true
If the project still uses a legacy variable (e.g., BASE_URL_TODO), document it here and map it to
WEB_BASE_URL explicitly rather than adding more implicit fallback behavior.
| Targets unset | Fail immediately with a configuration error. No UI navigation required. |
| Targets incorrect | Failure occurs at DNS/connection/timeout layer; logs clearly show the target used. |
| Targets correct | Navigation hits the intended SUT host and proceeds into app-level assertions. |
| Default boundary | When CI_NAT_MODE!=1, localhost targets are rejected. |
| Recording exception | When CI_NAT_MODE=1, localhost targets are allowed and succeed. |
| Evidence to capture | Console echo of vars + fail-fast output + Playwright artifacts if enabled. |
Sanitize hostnames and LAN IPs if publishing outputs.
Symptom: tests run using an implicit default target when vars are missing.
Diagnosis: inspect config and logs for any default navigation target.
grep -R "baseURL" -n playwright.config.* fixtures pages tests || true
grep -R "process\.env\." -n fixtures pages tests playwright.config.*
Symptom: you set vars but framework reads something else.
Diagnosis: search for environment reads and standardize on WEB_BASE_URL/API_BASE_URL.
grep -R "process\.env\." -n fixtures pages tests playwright.config.*
Symptom: config looks right; network errors occur.
Diagnosis: validate reachability outside Playwright.
curl -sv --max-time 3 "${WEB_BASE_URL}/" || true
curl -sv --max-time 3 "${API_BASE_URL}/tags" || true
nslookup sut.testlab || true
Environment-driven targeting prevents configuration drift. It enables deterministic execution across developer machines, CI runners, and staged environments without code changes, improving diagnosability and reducing “works on my machine” artifacts. Mode-aware rules prevent accidental boundary bypass while still supporting sanitized recordings.