mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Add public browser smoke proof to demo workflows
This commit is contained in:
@@ -79,7 +79,8 @@ Optional environment variables:
|
||||
7. **Update**: SSHs to the selected demo host and runs the tag-matched root installer from that exact git tag
|
||||
8. **Host identity check**: Verifies the SSH target reports the governed expected hostname before running installer or deploy steps
|
||||
9. **Verify**: Checks that the new version is running, mock mode is active, and the public demo HTML serves the same frontend entry asset as the target service
|
||||
10. **Cleanup**: Removes SSH key from runner
|
||||
10. **Browser smoke**: Uses the governed Playwright helper to prove the public demo still renders the login shell in a real browser
|
||||
11. **Cleanup**: Removes SSH key from runner
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -122,6 +123,8 @@ environment without changing the governed release workflow.
|
||||
- Verifies the SSH target reports the governed expected hostname before deploy
|
||||
- Verifies that the public demo shell serves the same frontend entry asset that
|
||||
was built and deployed
|
||||
- Uses `scripts/run_demo_public_browser_smoke.sh` to prove the public demo
|
||||
still renders the login shell in Chromium after deploy/update verification
|
||||
|
||||
## Helm CI
|
||||
|
||||
|
||||
@@ -316,6 +316,20 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Verify public browser smoke
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$DEMO_PUBLIC_HEALTH_URL" in
|
||||
*/api/health)
|
||||
export PULSE_PUBLIC_SITE_URL="${DEMO_PUBLIC_HEALTH_URL%/api/health}/"
|
||||
;;
|
||||
*)
|
||||
echo "::error::DEMO_PUBLIC_HEALTH_URL must end with /api/health for browser verification."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
./scripts/run_demo_public_browser_smoke.sh
|
||||
|
||||
- name: Cleanup SSH material
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/id_ed25519 ~/.ssh/known_hosts
|
||||
|
||||
@@ -385,7 +385,6 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Verify frontend parity
|
||||
if: steps.current.outputs.skip_current != 'true'
|
||||
env:
|
||||
DEMO_SERVER_HOST: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
DEMO_SERVER_USER: ${{ secrets.DEMO_SERVER_USER }}
|
||||
@@ -426,6 +425,20 @@ jobs:
|
||||
set -euo pipefail
|
||||
curl -fsS "$DEMO_PUBLIC_HEALTH_URL"
|
||||
|
||||
- name: Verify public browser smoke
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$DEMO_PUBLIC_HEALTH_URL" in
|
||||
*/api/health)
|
||||
export PULSE_PUBLIC_SITE_URL="${DEMO_PUBLIC_HEALTH_URL%/api/health}/"
|
||||
;;
|
||||
*)
|
||||
echo "::error::DEMO_PUBLIC_HEALTH_URL must end with /api/health for browser verification."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
./scripts/run_demo_public_browser_smoke.sh
|
||||
|
||||
- name: Cleanup SSH material
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/id_ed25519 ~/.ssh/known_hosts /tmp/pulse-install.sh
|
||||
|
||||
@@ -58,15 +58,17 @@ server-side update execution surfaces.
|
||||
36. `scripts/pulse-auto-update.sh`
|
||||
37. `scripts/release_control/resolve_release_promotion.py`
|
||||
38. `scripts/release_ldflags.sh`
|
||||
39. `scripts/trigger-release-dry-run.sh`
|
||||
40. `scripts/trigger-release.sh`
|
||||
41. `scripts/toggle-mock.sh`
|
||||
42. `tests/integration/playwright.config.ts`
|
||||
43. `tests/integration/QUICK_START.md`
|
||||
44. `tests/integration/README.md`
|
||||
45. `tests/integration/scripts/managed-dev-runtime.mjs`
|
||||
46. `tests/integration/tests/helpers.ts`
|
||||
47. `tests/integration/tests/runtime-defaults.ts`
|
||||
39. `scripts/run_demo_public_browser_smoke.sh`
|
||||
40. `scripts/demo_public_browser_smoke.cjs`
|
||||
41. `scripts/trigger-release-dry-run.sh`
|
||||
42. `scripts/trigger-release.sh`
|
||||
43. `scripts/toggle-mock.sh`
|
||||
44. `tests/integration/playwright.config.ts`
|
||||
45. `tests/integration/QUICK_START.md`
|
||||
46. `tests/integration/README.md`
|
||||
47. `tests/integration/scripts/managed-dev-runtime.mjs`
|
||||
48. `tests/integration/tests/helpers.ts`
|
||||
49. `tests/integration/tests/runtime-defaults.ts`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
@@ -81,7 +83,7 @@ server-side update execution surfaces.
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, or governed promotion metadata resolution through `scripts/build-release.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/resolve_release_promotion.py`, `.dockerignore`, `Dockerfile`, `docs/releases/V6_PRERELEASE_RUNBOOK.md`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
|
||||
3. Add or change shell installer, Windows installer, container-agent installer, or auto-update script behavior through `scripts/install.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, and `scripts/pulse-auto-update.sh`
|
||||
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
|
||||
6. Add or change governed release-promotion workflow inputs, operator-facing promotion metadata, artifact publication lineage enforcement, or stable-promotion rehearsal summaries through `.github/workflows/create-release.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, `docs/releases/V6_PRERELEASE_RUNBOOK.md`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/trigger-release.sh`, and `scripts/trigger-release-dry-run.sh`
|
||||
7. Preserve release-matched installer and Helm operator documentation links through `scripts/install.sh`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-helm-chart.yml`, and the chart metadata itself so deployment guidance and packaged chart metadata do not drift back to branch-tip `main` docs when a release line or promoted tag already exists.
|
||||
8. Add or change operator-facing hosted tenant runtime canary rollout, batch runtime contract reconciliation, canonical hosted route/public URL generation, or control-plane runtime-registry reconciliation through `cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`, `internal/cloudcp/docker/labels.go`, and `internal/cloudcp/tenant_runtime_rollout.go`
|
||||
@@ -196,6 +198,13 @@ network path. They must establish the canonical Tailscale connectivity step
|
||||
before SSH setup so stable or preview targets may stay on governed private
|
||||
hostnames or Tailscale IPs, rather than silently depending on public SSH
|
||||
reachability from GitHub-hosted runners.
|
||||
Those same workflows also own customer-visible browser truth for the public
|
||||
demo shell. Health checks and entry-asset parity are necessary but not
|
||||
sufficient; after those checks pass, the governed helpers
|
||||
`scripts/run_demo_public_browser_smoke.sh` and
|
||||
`scripts/demo_public_browser_smoke.cjs` must exercise the public demo in a real
|
||||
Chromium session and prove the login shell actually renders instead of failing
|
||||
open on API-only reachability.
|
||||
Those same governed release workflows also own the operator-facing wording for
|
||||
that promotion metadata. Human-visible workflow inputs, summaries, and error
|
||||
messages must describe the path as a prerelease or preview flow rather than
|
||||
|
||||
@@ -2355,6 +2355,7 @@
|
||||
"scripts/check-workflow-dispatch-inputs.py",
|
||||
"scripts/clean-mock-alerts.sh",
|
||||
"scripts/com.pulse.hot-dev.plist.template",
|
||||
"scripts/demo_public_browser_smoke.cjs",
|
||||
"scripts/dev-check.sh",
|
||||
"scripts/dev-launchd-setup.sh",
|
||||
"scripts/dev-launchd-wrapper.sh",
|
||||
@@ -2366,6 +2367,7 @@
|
||||
"scripts/pulse-auto-update.sh",
|
||||
"scripts/release_control/resolve_release_promotion.py",
|
||||
"scripts/release_ldflags.sh",
|
||||
"scripts/run_demo_public_browser_smoke.sh",
|
||||
"scripts/toggle-mock.sh",
|
||||
"scripts/trigger-release-dry-run.sh",
|
||||
"scripts/trigger-release.sh",
|
||||
@@ -2453,6 +2455,21 @@
|
||||
"scripts/installtests/release_ldflags_test.go"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "demo-public-browser-smoke-runtime",
|
||||
"label": "demo public browser smoke proof",
|
||||
"match_prefixes": [],
|
||||
"match_files": [
|
||||
"scripts/demo_public_browser_smoke.cjs",
|
||||
"scripts/run_demo_public_browser_smoke.sh"
|
||||
],
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"scripts/installtests/build_release_assets_test.go",
|
||||
"scripts/release_control/release_promotion_policy_test.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "deployment-script-runtime",
|
||||
"label": "deployment script runtime proof",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const siteUrl = String(process.env.PULSE_PUBLIC_SITE_URL || '').trim();
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
assert(siteUrl, 'PULSE_PUBLIC_SITE_URL is required');
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
try {
|
||||
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
|
||||
const response = await page.goto(siteUrl, {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 120000,
|
||||
});
|
||||
assert(response, `No response received for ${siteUrl}`);
|
||||
assert(response.ok(), `Unexpected status ${response.status()} loading ${siteUrl}`);
|
||||
|
||||
await page.getByLabel('Username').waitFor();
|
||||
await page.getByLabel('Password').waitFor();
|
||||
await page.getByRole('button', { name: 'Sign in to Pulse' }).waitFor();
|
||||
|
||||
const title = normalizeText(await page.title());
|
||||
assert(title === 'Pulse', `Unexpected page title: ${title}`);
|
||||
|
||||
const bodyText = normalizeText(await page.locator('body').innerText());
|
||||
for (const expected of ['Welcome to Pulse', 'Username', 'Password', 'Sign in to Pulse']) {
|
||||
assert(bodyText.includes(expected), `Public demo body missing ${JSON.stringify(expected)}`);
|
||||
}
|
||||
|
||||
console.log(`public demo browser smoke passed for ${siteUrl}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
const message = error instanceof Error ? error.stack || error.message : String(error);
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -127,6 +127,8 @@ func TestDeployDemoWorkflowFailsClosedForPreviewAndVerifiesFrontendParity(t *tes
|
||||
`Preview demo deployments must not target the stable pulse service.`,
|
||||
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
||||
`Verify frontend parity`,
|
||||
`Verify public browser smoke`,
|
||||
`./scripts/run_demo_public_browser_smoke.sh`,
|
||||
`extract_entry_asset()`,
|
||||
`<script\b[^>]*\bsrc=\"(/assets/index-[^\"]*\.js)\"`,
|
||||
`Remote service is serving $REMOTE_ASSET but the build expected $EXPECTED_ASSET.`,
|
||||
@@ -152,6 +154,8 @@ func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) {
|
||||
`authkey: ${{ secrets.TS_AUTHKEY }}`,
|
||||
`Verify target host identity`,
|
||||
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
||||
`Verify public browser smoke`,
|
||||
`./scripts/run_demo_public_browser_smoke.sh`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(workflow, needle) {
|
||||
|
||||
@@ -237,6 +237,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
self.assertIn("Verify target host identity", demo)
|
||||
self.assertIn("Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.", demo)
|
||||
self.assertIn("Verify frontend parity", demo)
|
||||
self.assertIn("Verify public browser smoke", demo)
|
||||
self.assertIn("./scripts/run_demo_public_browser_smoke.sh", demo)
|
||||
self.assertIn("extract_entry_asset()", demo)
|
||||
self.assertIn(r'<script\b[^>]*\bsrc=\"(/assets/index-[^\"]*\.js)\"', demo)
|
||||
self.assertIn("Public demo is serving $PUBLIC_ASSET but the target service is serving $REMOTE_ASSET.", demo)
|
||||
@@ -246,6 +248,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
self.assertIn("Verify target host identity", preview_deploy)
|
||||
self.assertIn("Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.", preview_deploy)
|
||||
self.assertIn("Verify frontend parity", preview_deploy)
|
||||
self.assertIn("Verify public browser smoke", preview_deploy)
|
||||
self.assertIn("./scripts/run_demo_public_browser_smoke.sh", preview_deploy)
|
||||
self.assertIn("extract_entry_asset()", preview_deploy)
|
||||
self.assertIn(r'<script\b[^>]*\bsrc=\"(/assets/index-[^\"]*\.js)\"', preview_deploy)
|
||||
self.assertIn("Public demo is serving $PUBLIC_ASSET but the build expected $EXPECTED_ASSET.", preview_deploy)
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "node is required for demo browser smoke" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "npx is required for demo browser smoke" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${PULSE_PUBLIC_SITE_URL:-}" ]; then
|
||||
echo "PULSE_PUBLIC_SITE_URL is required for demo browser smoke" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-${RUNNER_TEMP:-/tmp}/pw-browsers}"
|
||||
|
||||
mkdir -p "$BROWSERS_PATH"
|
||||
|
||||
export PLAYWRIGHT_BROWSERS_PATH="$BROWSERS_PATH"
|
||||
npx --yes -p playwright playwright install --with-deps chromium
|
||||
|
||||
playwright_bin="$(npx --yes -p playwright -c 'which playwright')"
|
||||
playwright_node_path="$(cd "$(dirname "$playwright_bin")/.." && pwd)"
|
||||
|
||||
NODE_PATH="$playwright_node_path${NODE_PATH:+:$NODE_PATH}" \
|
||||
node "$SCRIPT_DIR/demo_public_browser_smoke.cjs"
|
||||
Reference in New Issue
Block a user