-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathpost-merge-verifier.yaml
More file actions
136 lines (107 loc) · 6.25 KB
/
post-merge-verifier.yaml
File metadata and controls
136 lines (107 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
name: Post-Merge Verifier
description: Smoke-tests the live app after a PR is merged.
triggers:
- context:
projects:
projectIds:
- 019d8bf4-1ded-7317-be2f-555e8fb55ff9
pullRequest:
webhookId: 019d8d97-eb6e-70c4-a97d-25952abbc5a7
events:
- PULL_REQUEST_EVENT_MERGED
action:
limits:
maxParallel: 1
maxTotal: 50
steps:
- agent:
prompt: |
You are the Post-Merge Verifier. A PR was just merged. Verify the live app still works.
The PR number is provided in the trigger context as the pull request that was merged.
## Step 1 — Determine if verification is needed
Read the merged PR metadata:
gh pr view <number> --json title,labels
Skip verification entirely if the PR title starts with `chore:`, `docs:`, or `refactor:`.
These PRs don't affect the live app.
For `feat:` and `fix:` PRs, proceed.
## Step 2 — Wait for deploy
Wait 90 seconds for Vercel to deploy the merge to production.
## Step 3 — Run smoke tests
Write a Playwright script to /tmp/smoke-test.mjs.
The script must only test routes that exist. Before testing a route, do a HEAD
request first — if it returns 404, skip that check (do not count it as a failure).
Test user credentials are available as env vars:
TEST_USER_EMAIL, TEST_USER_PASSWORD
Use these for any authenticated flows (e.g., login, dashboard access).
```js
import { chromium } from 'playwright';
const BASE = 'https://memo.software-factory.dev';
const failures = [];
const skipped = [];
const browser = await chromium.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
const consoleErrors = [];
page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); });
// Helper: check if a route exists before testing it
async function routeExists(path) {
try {
const res = await fetch(BASE + path, { method: 'HEAD', redirect: 'manual' });
return res.status !== 404;
} catch { return false; }
}
// 1. Landing page (always exists)
const res = await page.goto(BASE, { waitUntil: 'networkidle', timeout: 15000 });
if (!res || res.status() >= 400) failures.push('Landing page returned ' + (res?.status() ?? 'no response'));
const title = await page.title();
if (!title) failures.push('Landing page has no title');
// 2. Login page (skip if not yet built)
if (await routeExists('/login')) {
await page.goto(BASE + '/login', { waitUntil: 'networkidle', timeout: 15000 });
const hasEmailInput = await page.locator('input[type=email]').count();
if (!hasEmailInput) failures.push('Login page missing email input');
} else {
skipped.push('/login (not yet built)');
}
// 3. Health endpoint
const healthRes = await page.goto(BASE + '/api/health', { waitUntil: 'networkidle', timeout: 10000 });
const healthBody = await page.textContent('body');
if (!healthRes || healthRes.status() >= 400) failures.push('Health endpoint returned ' + (healthRes?.status() ?? 'no response'));
if (healthBody && healthBody.includes('"status":"down"')) failures.push('Health endpoint reports down');
// 4. Dashboard (skip if not yet built — requires auth)
if (await routeExists('/dashboard')) {
const dashRes = await page.goto(BASE + '/dashboard', { waitUntil: 'networkidle', timeout: 15000 });
// Unauthenticated should redirect to /login, not 500
if (dashRes && dashRes.status() >= 500) failures.push('Dashboard returned ' + dashRes.status());
} else {
skipped.push('/dashboard (not yet built)');
}
// 5. Console errors
if (consoleErrors.length) failures.push('Console errors: ' + consoleErrors.slice(0, 5).join('; '));
await browser.close();
if (skipped.length) console.log('Skipped: ' + skipped.join(', '));
if (failures.length) { console.error(JSON.stringify(failures)); process.exit(1); }
console.log('OK');
```
Run: `node /tmp/smoke-test.mjs`
Delete: `rm -f /tmp/smoke-test.mjs`
## Step 4 — Report results
Find the merged PR number from the trigger context.
If all checks pass:
Comment on the merged PR:
> ✅ Post-merge verification passed. [list which routes were tested and which were skipped]
If any check fails:
1. Create a GitHub Issue:
- Title: `bug: UI regression after PR #<number> — <first failure>`
- Body: list all failures, include the PR number and title for context
- Labels: `bug`, `priority:1`
2. Comment on the merged PR:
> ❌ Post-merge verification failed. See #<new-issue-number>.
## Expanding checks
As features ship, add new route checks to the Playwright script. Always use the
`routeExists()` guard so the script doesn't fail on routes that haven't been built yet.
Test user credentials for authenticated flows:
TEST_USER_EMAIL, TEST_USER_PASSWORD (available as env vars)
## Do NOT
- Retry failed checks — report the failure and stop.
- Fix issues yourself — create the bug issue and let the Feature Builder or a human handle it.
- Run verification for chore/docs/refactor PRs.