@andwhy/e2e-pipeline 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/bin/init.js +5 -1
- package/dist/utils/copy.js +26 -0
- package/package.json +1 -1
- package/templates/claude/rules/playwright-workflow.md +13 -0
- package/templates/claude/skills/playwright-test-pipeline/SKILL.md +78 -20
- package/templates/github/prompts/checkly-heal.md +141 -0
- package/templates/github/workflows/checkly-heal.yml +317 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Re-running `init --force` in an existing project is the way to pick up a new ver
|
|
|
35
35
|
- `.claude/skills/playwright-test-pipeline/` — guided walkthrough skill; triggers when someone asks to create or fix tests and escorts them through setup check, Plan → Generate → Heal, preview validation, and deployment
|
|
36
36
|
- `.mcp.json` — Playwright MCP server config (project root)
|
|
37
37
|
- `playwright.mcp.config.{ts,mjs}` — config the MCP server loads: extends `playwright.config` and clears `testIgnore`, so the planner/generator can execute seeds that normal runs ignore
|
|
38
|
+
- `.github/prompts/checkly-heal.md` — instructions for the auto-heal agent the `checkly-heal` workflow runs
|
|
38
39
|
|
|
39
40
|
**Seeded once** — starting points your project then owns. Written only when absent and never overwritten, `--force` included:
|
|
40
41
|
|
|
@@ -43,10 +44,12 @@ Re-running `init --force` in an existing project is the way to pick up a new ver
|
|
|
43
44
|
- `tests/e2e/seed.spec.{ts,js}` — seed file for Playwright MCP `setup_page`
|
|
44
45
|
- `tests/e2e/helpers/preview-consent-setup.{ts,js}` — `prepareVercelPreviewPage` (Vercel deployment-protection bypass); projects extend it with consent logic
|
|
45
46
|
- `.github/workflows/pr-playwright-tests.yml` — runs the suite against the PR's Vercel preview and comments the result (plus `.github/scripts/playwright-pr-comment.mjs`)
|
|
46
|
-
- `.github/workflows/checkly-deploy.yml` — deploys monitors
|
|
47
|
+
- `.github/workflows/checkly-deploy.yml` — deploys monitors when a production deployment succeeds
|
|
48
|
+
- `.github/workflows/checkly-heal.yml` — a failing deployed monitor triggers a triage-and-heal agent: production regressions escalate to Slack immediately; stale tests get fixed and flaky tests hardened — including behavior-neutral test hooks (`data-testid`) in app source when needed — and opened as a human-review PR, whose own preview run validates the app-side hooks. Inert until a Checkly webhook alert channel dispatches to it — the skill walks through that setup.
|
|
47
49
|
|
|
48
50
|
The workflows need repository secrets: `CHECKLY_API_KEY`, `CHECKLY_ACCOUNT_ID`,
|
|
49
|
-
and `
|
|
51
|
+
`VERCEL_AUTOMATION_BYPASS_SECRET`, and for auto-heal `ANTHROPIC_API_KEY` and
|
|
52
|
+
`SLACK_WEBHOOK_URL` (values in 1Password).
|
|
50
53
|
|
|
51
54
|
E2E specs live under **`tests/e2e/`** so a repo can keep unit tests elsewhere under
|
|
52
55
|
`tests/` without Playwright trying to run them. Seeds sit in the same tree and are
|
package/dist/bin/init.js
CHANGED
|
@@ -107,9 +107,13 @@ const run = async () => {
|
|
|
107
107
|
console.log([
|
|
108
108
|
"\nGitHub workflows created. They need repository secrets before they run",
|
|
109
109
|
"(values in 1Password):",
|
|
110
|
-
" CHECKLY_API_KEY, CHECKLY_ACCOUNT_ID — deploy monitors
|
|
110
|
+
" CHECKLY_API_KEY, CHECKLY_ACCOUNT_ID — deploy monitors, auto-heal",
|
|
111
111
|
" VERCEL_AUTOMATION_BYPASS_SECRET — PR tests through Vercel",
|
|
112
112
|
" deployment protection",
|
|
113
|
+
" ANTHROPIC_API_KEY — Checkly auto-heal agent",
|
|
114
|
+
" SLACK_WEBHOOK_URL — auto-heal notifications",
|
|
115
|
+
"The auto-heal workflow stays inert until a Checkly webhook alert",
|
|
116
|
+
"channel dispatches to it — the skill walks through that setup.",
|
|
113
117
|
].join("\n"));
|
|
114
118
|
}
|
|
115
119
|
installDeps(projectRoot, fileFormat);
|
package/dist/utils/copy.js
CHANGED
|
@@ -77,6 +77,9 @@ const GITIGNORE_LINES = [
|
|
|
77
77
|
// Session artifacts (traces, snapshots) the Playwright MCP server writes
|
|
78
78
|
// while the planner/generator drive the browser.
|
|
79
79
|
".playwright-mcp/",
|
|
80
|
+
// Working directory of the Checkly auto-heal agent (context, outcome,
|
|
81
|
+
// downloaded results) — CI artifacts, never committed.
|
|
82
|
+
".checkly-heal/",
|
|
80
83
|
];
|
|
81
84
|
// Lines earlier scaffold versions added and this one must take back: seeds
|
|
82
85
|
// were once regenerable root-level scaffolding, but since 0.5.0 the planner
|
|
@@ -228,6 +231,10 @@ export const scaffold = async (templatesRoot, projectRoot, { force = false, file
|
|
|
228
231
|
path.join("workflows", "pr-playwright-tests.yml"),
|
|
229
232
|
path.join(".github", "workflows", "pr-playwright-tests.yml"),
|
|
230
233
|
],
|
|
234
|
+
[
|
|
235
|
+
path.join("workflows", "checkly-heal.yml"),
|
|
236
|
+
path.join(".github", "workflows", "checkly-heal.yml"),
|
|
237
|
+
],
|
|
231
238
|
[
|
|
232
239
|
path.join("scripts", "playwright-pr-comment.mjs"),
|
|
233
240
|
path.join(".github", "scripts", "playwright-pr-comment.mjs"),
|
|
@@ -244,6 +251,25 @@ export const scaffold = async (templatesRoot, projectRoot, { force = false, file
|
|
|
244
251
|
result.created.push(destPath);
|
|
245
252
|
}
|
|
246
253
|
}
|
|
254
|
+
// The heal prompt is package-owned like the agent definitions: it carries
|
|
255
|
+
// agent behavior, not project config, so --force updates it.
|
|
256
|
+
const githubOwnedMappings = [
|
|
257
|
+
[
|
|
258
|
+
path.join("prompts", "checkly-heal.md"),
|
|
259
|
+
path.join(".github", "prompts", "checkly-heal.md"),
|
|
260
|
+
],
|
|
261
|
+
];
|
|
262
|
+
for (const [src, dest] of githubOwnedMappings) {
|
|
263
|
+
const destPath = path.join(projectRoot, dest);
|
|
264
|
+
if (!force && (await fs.pathExists(destPath))) {
|
|
265
|
+
result.skipped.push(destPath);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
269
|
+
await fs.copy(path.join(githubSrc, src), destPath);
|
|
270
|
+
result.created.push(destPath);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
247
273
|
await appendGitignore(projectRoot, result);
|
|
248
274
|
return result;
|
|
249
275
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andwhy/e2e-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Scaffolds the full e2e testing pipeline: agent-assisted Playwright tests, CI runs against Vercel previews, and Checkly monitors that follow production deploys",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -201,3 +201,16 @@ If any tests fail:
|
|
|
201
201
|
- **Plan files** are saved at `tests/e2e/<feature>/<name>.plan.md`.
|
|
202
202
|
- **Test files** follow **Project test rules** §7–8 — typically `tests/e2e/<feature>/<group>.spec.js`
|
|
203
203
|
or `.spec.ts` with one `describe` and multiple tests.
|
|
204
|
+
|
|
205
|
+
## Checkly auto-heal (CI, not this session)
|
|
206
|
+
|
|
207
|
+
Failing deployed monitors are handled by `.github/workflows/checkly-heal.yml`:
|
|
208
|
+
it triages first (production regression → Slack alert; stale test → fix; flaky
|
|
209
|
+
test → harden), may add behavior-neutral test hooks (`data-testid`) to app
|
|
210
|
+
source when the robust fix needs them, and opens a `checkly-heal` PR a human
|
|
211
|
+
must review — app-side hooks are validated by the PR's own preview run. Its
|
|
212
|
+
agent instructions live in `.github/prompts/checkly-heal.md` (package-owned —
|
|
213
|
+
update via `init --force`, don't hand-edit). If a user asks about a
|
|
214
|
+
`checkly-heal` PR, that workflow authored it; reviewing one means verifying the
|
|
215
|
+
updated test still describes correct production behavior, not that it merely
|
|
216
|
+
passes.
|
|
@@ -76,7 +76,7 @@ The scaffold writes templates, not a working setup. Walk these with the user:
|
|
|
76
76
|
page, and pointing it at an empty or unreachable app produces a plan that
|
|
77
77
|
asserts nothing useful.
|
|
78
78
|
4. **Repository secrets for the workflows**: if the scaffold just created
|
|
79
|
-
`.github/workflows/`, tell the user now — not at merge time — to add
|
|
79
|
+
`.github/workflows/`, tell the user now — not at merge time — to add the
|
|
80
80
|
repository secrets (GitHub → Settings → Secrets and variables → Actions).
|
|
81
81
|
You cannot add them; a person must. Without them the first PR push and the
|
|
82
82
|
first merge fail CI in ways that look like pipeline bugs.
|
|
@@ -88,6 +88,12 @@ The scaffold writes templates, not a working setup. Walk these with the user:
|
|
|
88
88
|
- `VERCEL_AUTOMATION_BYPASS_SECRET` — Vercel → project → Settings →
|
|
89
89
|
Deployment Protection → Protection Bypass for Automation. Per project —
|
|
90
90
|
never copied from another repo.
|
|
91
|
+
- `ANTHROPIC_API_KEY` — 1Password ("Anthropic"); powers the Checkly
|
|
92
|
+
auto-heal agent. Optional until Phase 5 wires the alert webhook, but
|
|
93
|
+
cheapest to add alongside the others.
|
|
94
|
+
- `SLACK_WEBHOOK_URL` — a Slack incoming webhook for the project's
|
|
95
|
+
notification channel; the auto-heal workflow posts its alerts there.
|
|
96
|
+
Ask which channel before creating one.
|
|
91
97
|
5. **Existing tests under `tests/`** (unit, a11y — the scaffold lists them as
|
|
92
98
|
"outside tests/e2e"): leave `testDir` at `./tests/e2e` and give that suite
|
|
93
99
|
its own Playwright project with its own `testDir` if it should run under
|
|
@@ -148,7 +154,7 @@ until after merge.
|
|
|
148
154
|
|
|
149
155
|
## Phase 5 — Merge and deployment
|
|
150
156
|
|
|
151
|
-
The scaffold ships
|
|
157
|
+
The scaffold ships three workflows (seeded once — existing ones are never
|
|
152
158
|
overwritten):
|
|
153
159
|
|
|
154
160
|
- `.github/workflows/pr-playwright-tests.yml` — runs the suite against the
|
|
@@ -161,13 +167,22 @@ overwritten):
|
|
|
161
167
|
merge that adds tests for new `data-testid`s would go red until the release
|
|
162
168
|
carrying them is live — with gated releases that gap can be days. A
|
|
163
169
|
`workflow_dispatch` fallback allows manual runs.
|
|
170
|
+
- `.github/workflows/checkly-heal.yml` — a failing deployed monitor
|
|
171
|
+
dispatches here (via the webhook alert channel below). An agent triages
|
|
172
|
+
first: a production regression or anything needing a behavioral app change
|
|
173
|
+
alerts Slack immediately; a stale test gets fixed and a flaky test gets
|
|
174
|
+
hardened — including behavior-neutral test hooks (`data-testid`) in app
|
|
175
|
+
source when the robust fix needs them — and opened as a PR that a human
|
|
176
|
+
must review. Test-only fixes are verified against production and on
|
|
177
|
+
Checkly's infrastructure; app-side hooks are validated by the PR's own
|
|
178
|
+
preview run (pr-playwright-tests). Inert until the webhook exists.
|
|
164
179
|
|
|
165
180
|
**Before the first merge, the repo needs secrets** (values in 1Password):
|
|
166
|
-
`CHECKLY_API_KEY` and `CHECKLY_ACCOUNT_ID` for the deploy,
|
|
181
|
+
`CHECKLY_API_KEY` and `CHECKLY_ACCOUNT_ID` for the deploy and auto-heal,
|
|
167
182
|
`VERCEL_AUTOMATION_BYPASS_SECRET` for the PR run through deployment
|
|
168
|
-
protection
|
|
169
|
-
|
|
170
|
-
pipeline bug.
|
|
183
|
+
protection, `ANTHROPIC_API_KEY` and `SLACK_WEBHOOK_URL` for auto-heal. If
|
|
184
|
+
the scaffold just created the workflows, say so explicitly and list the
|
|
185
|
+
secrets — a merge without them fails CI and looks like a pipeline bug.
|
|
171
186
|
|
|
172
187
|
After merge, monitors update once the production deployment goes live — the
|
|
173
188
|
user verifies in the Checkly dashboard (https://app.checklyhq.com/); nothing
|
|
@@ -175,20 +190,63 @@ to run by hand. Manual fallback if a repo deleted the deploy workflow:
|
|
|
175
190
|
`pnpm checkly test --record`, then `pnpm checkly deploy` (requires the
|
|
176
191
|
Phase 4 login).
|
|
177
192
|
|
|
178
|
-
**Alert notifications are not automatic — walk the user through
|
|
179
|
-
failing monitor only shows in the Checkly dashboard
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
**Alert notifications are not automatic — walk the user through the
|
|
194
|
+
auto-heal webhook.** A failing monitor only shows in the Checkly dashboard
|
|
195
|
+
until an alert channel exists. The alert channel for these suites is a
|
|
196
|
+
**webhook into the auto-heal workflow**, not a direct Slack channel: the
|
|
197
|
+
team's Slack message is written by the pipeline *after* triage (regression
|
|
198
|
+
vs. stale test), so it carries a diagnosis and, for stale tests, the fix PR.
|
|
199
|
+
Set up once per project:
|
|
200
|
+
|
|
201
|
+
1. Confirm the `ANTHROPIC_API_KEY` and `SLACK_WEBHOOK_URL` repository
|
|
202
|
+
secrets from Phase 0 exist — the workflow escalates nothing without them.
|
|
203
|
+
2. A GitHub token for Checkly to call `repository_dispatch`: fine-grained
|
|
204
|
+
PAT, scoped to this repo only, **Contents: read and write** permission,
|
|
205
|
+
the longest expiry policy allows — note the renewal date wherever the team
|
|
206
|
+
tracks credentials, an expired token silences the trigger. Store it in
|
|
207
|
+
Checkly as an environment variable (Checkly → Environment variables),
|
|
208
|
+
e.g. `GH_DISPATCH_TOKEN` — never in code or `checkly.config`.
|
|
209
|
+
3. Checkly → Alert channels → "Add more channels" → **Webhook**:
|
|
210
|
+
- Method `POST`, URL
|
|
211
|
+
`https://api.github.com/repos/<org>/<repo>/dispatches`
|
|
212
|
+
- Headers: `Authorization: Bearer {{GH_DISPATCH_TOKEN}}` and
|
|
213
|
+
`Accept: application/vnd.github+json`
|
|
214
|
+
- Body (Checkly's editor lists the available variables — confirm the
|
|
215
|
+
names there):
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"event_type": "checkly-alert",
|
|
220
|
+
"client_payload": {
|
|
221
|
+
"check_name": "{{CHECK_NAME}}",
|
|
222
|
+
"check_id": "{{CHECK_ID}}",
|
|
223
|
+
"result_id": "{{CHECK_RESULT_ID}}",
|
|
224
|
+
"result_link": "{{RESULT_LINK}}",
|
|
225
|
+
"alert_type": "{{ALERT_TYPE}}"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
- Notify on **failure**; enable **reminders** while a check stays failing
|
|
231
|
+
— a reminder re-fires the webhook, the workflow dedupes repeats, and a
|
|
232
|
+
lost first delivery gets retried by the next one. Recovery/degraded
|
|
233
|
+
notifications are ignored by the workflow either way.
|
|
234
|
+
4. Subscriptions: **only the current check**, and **uncheck** "Automatically
|
|
235
|
+
subscribe newly created checks to this alert channel".
|
|
236
|
+
5. **Do not also subscribe a Slack alert channel to these suites.** The
|
|
237
|
+
pipeline is the single alert path; a parallel raw Checkly alert double-pings
|
|
238
|
+
the channel and trains people to ignore one of them.
|
|
239
|
+
6. Dry run: GitHub → Actions → "Checkly auto-heal" → Run workflow, with the
|
|
240
|
+
suite's check name. Confirm the run completes and the Slack message
|
|
241
|
+
arrives (a passing check should end as "not reproducible" — summary only,
|
|
242
|
+
no ping — so a real dry run is easiest right after any genuine failure).
|
|
243
|
+
|
|
244
|
+
What the user should expect afterwards: a failing monitor produces either a
|
|
245
|
+
`checkly-heal` PR plus a Slack note (stale or flaky test — **review
|
|
246
|
+
required, the PR is never merged automatically**; PRs carrying app-side test
|
|
247
|
+
hooks say so and point at their preview run for validation), or a Slack
|
|
248
|
+
escalation (regression, fix needs a behavioral app change, agent failed). A
|
|
249
|
+
transient blip that doesn't reproduce stays silent by design.
|
|
192
250
|
|
|
193
251
|
## Conventions to uphold
|
|
194
252
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Checkly monitor auto-heal
|
|
2
|
+
|
|
3
|
+
You are running unattended in CI because a deployed Checkly monitor failed
|
|
4
|
+
against production. Your job is to triage the failure first, and only then —
|
|
5
|
+
when the test is stale or flaky rather than the site broken — fix it. A
|
|
6
|
+
workflow wraps this run: it commits, opens the pull request, and notifies the
|
|
7
|
+
team. You never push, never open PRs, never send notifications.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
- `.checkly-heal/context.json` — the failing check's name, the alert type, a
|
|
12
|
+
link to the Checkly result, and the paths of any downloaded artifacts.
|
|
13
|
+
- `.checkly-heal/result.json` — the raw Checkly check result when the API call
|
|
14
|
+
succeeded: error message, failing test and step, stack trace. May be absent;
|
|
15
|
+
proceed without it if so.
|
|
16
|
+
- `.claude/agents/playwright-test-healer.md` — debugging practice and
|
|
17
|
+
robustness rules. Follow its methodology. The overrides below take
|
|
18
|
+
precedence over anything it says.
|
|
19
|
+
|
|
20
|
+
## Overrides for this run
|
|
21
|
+
|
|
22
|
+
These invert or forbid parts of the normal healer behavior. They are not
|
|
23
|
+
negotiable:
|
|
24
|
+
|
|
25
|
+
1. **`BASE_URL` pins to production, not localhost.** The monitor asserts
|
|
26
|
+
against production, so production is the reproduction and verification
|
|
27
|
+
target. Resolve the URL from `checkly.config.{ts,mjs}`: find the
|
|
28
|
+
`playwrightChecks` entry whose `name` (or `logicalId`) matches the failing
|
|
29
|
+
check name from the context, and use its `BASE_URL` environment variable.
|
|
30
|
+
2. **Never `test.fixme()`, never skip, disable, or delete a test.** A skipped
|
|
31
|
+
test is a silently disabled production monitor. If you cannot fix it,
|
|
32
|
+
classify accordingly and stop.
|
|
33
|
+
3. **App source changes are limited to behavior-neutral test hooks.** You may
|
|
34
|
+
add or restore attributes that exist for tests — `data-testid`, `aria-*`
|
|
35
|
+
where genuinely correct — on components the failing test targets. You may
|
|
36
|
+
not change logic, markup structure, styling, content, dependencies, or
|
|
37
|
+
configuration. If only a behavioral app change can fix the failure, that is
|
|
38
|
+
the `needs-app-change` classification, not something you attempt. Never
|
|
39
|
+
touch `.github/`, `.claude/`, `package.json`, lockfiles, or any
|
|
40
|
+
`*.config.*` file.
|
|
41
|
+
4. **Never run git commit, push, or any GitHub CLI/API command.**
|
|
42
|
+
|
|
43
|
+
## Procedure
|
|
44
|
+
|
|
45
|
+
### 1. Orient
|
|
46
|
+
|
|
47
|
+
Read the context and result files. Identify the failing spec file, test, and
|
|
48
|
+
step. Read the spec. Resolve the production `BASE_URL` (override 1).
|
|
49
|
+
|
|
50
|
+
### 2. Reproduce
|
|
51
|
+
|
|
52
|
+
Run only the failing spec against production:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
BASE_URL=<production-url> pnpm playwright test <failing-spec> --project=Chrome
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **Fails** → continue to triage.
|
|
59
|
+
- **Passes** → run it again with `--repeat-each=3`. All green means the
|
|
60
|
+
failure was transient on Checkly's side: classification `not-reproducible`,
|
|
61
|
+
stop, change nothing. Intermittent failures mean the test is **flaky** —
|
|
62
|
+
continue to triage with that hypothesis.
|
|
63
|
+
|
|
64
|
+
### 3. Triage — before any edit
|
|
65
|
+
|
|
66
|
+
Decide which of these is true, using the failure output, a fresh trace, and
|
|
67
|
+
the live page:
|
|
68
|
+
|
|
69
|
+
- **The site is broken.** The user-visible outcome the test asserts genuinely
|
|
70
|
+
does not work on production — a purchase flow erroring, a page failing to
|
|
71
|
+
render, content missing that should exist. Classification `regression`.
|
|
72
|
+
Stop immediately. Do not touch the test: adjusting a test to accept broken
|
|
73
|
+
behavior hides an outage.
|
|
74
|
+
- **The test is flaky.** The outcome works, but the test fails
|
|
75
|
+
intermittently — races, missing waits for dynamic content, animation
|
|
76
|
+
timing, order-dependent assertions. Classification `flaky` — proceed to
|
|
77
|
+
step 4 and fix the root cause per the robustness rules; blanket timeout
|
|
78
|
+
increases are not a fix.
|
|
79
|
+
- **The test is stale.** Production changed legitimately (new markup, changed
|
|
80
|
+
copy, moved element) and the test's selectors or assertions no longer
|
|
81
|
+
describe it, while the actual user outcome still works. Classification
|
|
82
|
+
`healed` — proceed to step 4.
|
|
83
|
+
- **The fix needs a behavioral app change.** The failure is rooted in app
|
|
84
|
+
code beyond a test hook — or in a pending release that will resolve it.
|
|
85
|
+
Classification `needs-app-change`. Stop and say precisely what a developer
|
|
86
|
+
must change, or which release resolves it.
|
|
87
|
+
|
|
88
|
+
When genuinely uncertain between regression and any fixable case, choose
|
|
89
|
+
`regression`. A false alarm costs a developer minutes; a masked outage costs
|
|
90
|
+
much more.
|
|
91
|
+
|
|
92
|
+
### 4. Heal (stale or flaky)
|
|
93
|
+
|
|
94
|
+
Fix the test following the healer methodology and robustness rules: prefer
|
|
95
|
+
resilient locators, fix root causes over timeouts, keep edits minimal.
|
|
96
|
+
|
|
97
|
+
When the robust fix needs a stable hook the app no longer (or never) provides,
|
|
98
|
+
add the minimal behavior-neutral attribute to the component (override 3) and
|
|
99
|
+
target it from the test — do not fall back to a brittle text or CSS selector
|
|
100
|
+
just to avoid touching app source. Keep such changes to the fewest attributes
|
|
101
|
+
that make the test robust.
|
|
102
|
+
|
|
103
|
+
### 5. Verify
|
|
104
|
+
|
|
105
|
+
- **Test-only fixes:** re-run the spec against production until green — for a
|
|
106
|
+
flaky fix, prove stability with `--repeat-each=5`, not a single pass. Then
|
|
107
|
+
run the full e2e suite once the same way to catch collateral damage, and
|
|
108
|
+
validate on Checkly's infrastructure:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm checkly pw-test --env BASE_URL="<production-url>" -- <failing-spec>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Credentials are in the environment. If cloud verification cannot run, say
|
|
115
|
+
so in the outcome details rather than claiming it.
|
|
116
|
+
- **Fixes that add app-side test hooks:** production does not carry your new
|
|
117
|
+
attributes yet, so a full production run cannot pass — that is expected.
|
|
118
|
+
Verify everything verifiable (the spec compiles, unrelated tests still pass
|
|
119
|
+
against production, the selector logic matches the component you edited)
|
|
120
|
+
and state exactly what remains for the preview run: the pull request the
|
|
121
|
+
workflow opens gets its own Vercel preview deployment, and the repo's
|
|
122
|
+
`pr-playwright-tests` workflow runs the full suite against it — that run is
|
|
123
|
+
the authoritative validation for app-side hooks. Say this in the details so
|
|
124
|
+
the reviewer knows to check it.
|
|
125
|
+
|
|
126
|
+
### 6. Write the outcome — required on every path
|
|
127
|
+
|
|
128
|
+
Write `.checkly-heal/outcome.json` before finishing, whatever happened:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"classification": "healed | flaky | regression | needs-app-change | not-reproducible",
|
|
133
|
+
"summary": "One sentence for the Slack notification.",
|
|
134
|
+
"details": "Markdown for the PR body or alert: what failed, the root cause, what changed — or exactly what a developer needs to do. For app-side hooks: which components gained which attributes, and that the PR's preview run validates them."
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For `healed` and `flaky`, the details become the PR description a human
|
|
139
|
+
reviews — include what broke, why, what you changed, and how it was verified.
|
|
140
|
+
If you finish without writing this file, the workflow treats the run as failed
|
|
141
|
+
and escalates to a developer, so an honest partial outcome always beats none.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
name: Checkly auto-heal
|
|
2
|
+
|
|
3
|
+
# A failing Checkly monitor dispatches here (webhook alert channel →
|
|
4
|
+
# repository_dispatch). The agent triages first: a stale or flaky test gets
|
|
5
|
+
# fixed and opened as a human-review PR; a real production regression, a fix
|
|
6
|
+
# needing behavioral app code, or any agent failure escalates to Slack
|
|
7
|
+
# instead. Fixes may include behavior-neutral test hooks in app source
|
|
8
|
+
# (data-testid) — the PR's own preview run (pr-playwright-tests.yml)
|
|
9
|
+
# validates those; a diff guard aborts changes to protected paths
|
|
10
|
+
# (.github, .claude, configs, lockfiles). The agent never pushes: branch,
|
|
11
|
+
# commit, PR, and Slack are workflow steps.
|
|
12
|
+
# Needs repo secrets: ANTHROPIC_API_KEY, CHECKLY_API_KEY, CHECKLY_ACCOUNT_ID,
|
|
13
|
+
# SLACK_WEBHOOK_URL (values in 1Password). Setup docs: the
|
|
14
|
+
# playwright-test-pipeline skill walks through the Checkly webhook channel.
|
|
15
|
+
on:
|
|
16
|
+
repository_dispatch:
|
|
17
|
+
types: [checkly-alert]
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
inputs:
|
|
20
|
+
check_name:
|
|
21
|
+
description: "Failing check name (as in checkly.config)"
|
|
22
|
+
required: true
|
|
23
|
+
check_id:
|
|
24
|
+
description: "Checkly check ID (optional, enables result download)"
|
|
25
|
+
required: false
|
|
26
|
+
result_id:
|
|
27
|
+
description: "Checkly check result ID (optional)"
|
|
28
|
+
required: false
|
|
29
|
+
|
|
30
|
+
run-name: "Checkly auto-heal · ${{ github.event.client_payload.check_name || inputs.check_name }}"
|
|
31
|
+
|
|
32
|
+
permissions:
|
|
33
|
+
contents: write
|
|
34
|
+
pull-requests: write
|
|
35
|
+
issues: write
|
|
36
|
+
|
|
37
|
+
# One heal at a time; queued alerts (e.g. an outage failing several checks)
|
|
38
|
+
# run serially and the dedup below makes repeats cheap no-ops.
|
|
39
|
+
concurrency:
|
|
40
|
+
group: checkly-heal
|
|
41
|
+
cancel-in-progress: false
|
|
42
|
+
|
|
43
|
+
env:
|
|
44
|
+
# The e2e test root. Changes outside it (behavior-neutral test hooks in app
|
|
45
|
+
# source) are allowed but flagged on the PR and in Slack.
|
|
46
|
+
# Match your playwright.config testDir.
|
|
47
|
+
E2E_ROOT: tests/e2e
|
|
48
|
+
# Paths the agent must never change — anything here aborts and escalates.
|
|
49
|
+
# Extended regex against the changed-file list.
|
|
50
|
+
PROTECTED_PATHS: '^(\.github/|\.claude/|\.mcp\.json$|.*\.config\.(ts|mjs|js|cjs)$|package\.json$|pnpm-lock\.yaml$|package-lock\.json$|yarn\.lock$|\.env)'
|
|
51
|
+
# How long a merged heal PR blocks a second silent attempt on the same check.
|
|
52
|
+
REPEAT_WINDOW_DAYS: 7
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
heal:
|
|
56
|
+
# Recovery/degraded notifications are not work; only failures are.
|
|
57
|
+
if: >-
|
|
58
|
+
github.event_name == 'workflow_dispatch' ||
|
|
59
|
+
contains(github.event.client_payload.alert_type || 'ALERT_FAILURE', 'FAILURE')
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 45
|
|
62
|
+
|
|
63
|
+
steps:
|
|
64
|
+
- name: Check out the default branch
|
|
65
|
+
uses: actions/checkout@v5
|
|
66
|
+
with:
|
|
67
|
+
fetch-depth: 0
|
|
68
|
+
|
|
69
|
+
# Dedup and repeat-offender guard. An open heal PR for this check means
|
|
70
|
+
# the alert is already handled. A heal PR merged recently means the fix
|
|
71
|
+
# did not hold — a second silent attempt would mask instability, so that
|
|
72
|
+
# escalates to a human instead.
|
|
73
|
+
- name: Guard against duplicate and repeated heals
|
|
74
|
+
id: guard
|
|
75
|
+
env:
|
|
76
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
77
|
+
CHECK_NAME: ${{ github.event.client_payload.check_name || inputs.check_name }}
|
|
78
|
+
run: |
|
|
79
|
+
slug=$(printf '%s' "$CHECK_NAME" | tr -c 'a-zA-Z0-9' '-' | tr -s '-' | sed 's/^-//;s/-$//' | cut -c1-40 | tr '[:upper:]' '[:lower:]')
|
|
80
|
+
echo "slug=${slug:-check}" >> "$GITHUB_OUTPUT"
|
|
81
|
+
|
|
82
|
+
open=$(gh pr list --state open --label checkly-heal --json headRefName \
|
|
83
|
+
--jq "[.[] | select(.headRefName | startswith(\"checkly-heal/$slug\"))] | length")
|
|
84
|
+
if [ "$open" -gt 0 ]; then
|
|
85
|
+
echo "mode=skip" >> "$GITHUB_OUTPUT"
|
|
86
|
+
echo "A heal PR for this check is already open — nothing to do." >> "$GITHUB_STEP_SUMMARY"
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
cutoff=$(date -u -d "$REPEAT_WINDOW_DAYS days ago" +%Y-%m-%dT%H:%M:%SZ)
|
|
91
|
+
recent=$(gh pr list --state merged --label checkly-heal --json headRefName,mergedAt \
|
|
92
|
+
--jq "[.[] | select((.headRefName | startswith(\"checkly-heal/$slug\")) and .mergedAt > \"$cutoff\")] | length")
|
|
93
|
+
if [ "$recent" -gt 0 ]; then
|
|
94
|
+
echo "mode=repeat" >> "$GITHUB_OUTPUT"
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
echo "mode=heal" >> "$GITHUB_OUTPUT"
|
|
98
|
+
|
|
99
|
+
- name: Set up pnpm
|
|
100
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
101
|
+
uses: pnpm/action-setup@v4
|
|
102
|
+
|
|
103
|
+
- name: Set up Node
|
|
104
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
105
|
+
uses: actions/setup-node@v5
|
|
106
|
+
with:
|
|
107
|
+
node-version: 22
|
|
108
|
+
cache: pnpm
|
|
109
|
+
|
|
110
|
+
# --ignore-scripts: only the tests run here; project postinstall hooks
|
|
111
|
+
# are not needed and may want CMS tokens CI does not have.
|
|
112
|
+
- name: Install dependencies
|
|
113
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
114
|
+
run: pnpm install --frozen-lockfile --ignore-scripts
|
|
115
|
+
|
|
116
|
+
- name: Install Playwright browsers
|
|
117
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
118
|
+
run: pnpm playwright install --with-deps chromium
|
|
119
|
+
|
|
120
|
+
# Checkly already diagnosed the failure — error, failing step, trace.
|
|
121
|
+
# Best effort: the agent reproduces from scratch when this is missing.
|
|
122
|
+
- name: Fetch the Checkly result and write agent context
|
|
123
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
124
|
+
env:
|
|
125
|
+
CHECKLY_API_KEY: ${{ secrets.CHECKLY_API_KEY }}
|
|
126
|
+
CHECKLY_ACCOUNT_ID: ${{ secrets.CHECKLY_ACCOUNT_ID }}
|
|
127
|
+
CHECK_NAME: ${{ github.event.client_payload.check_name || inputs.check_name }}
|
|
128
|
+
CHECK_ID: ${{ github.event.client_payload.check_id || inputs.check_id }}
|
|
129
|
+
RESULT_ID: ${{ github.event.client_payload.result_id || inputs.result_id }}
|
|
130
|
+
RESULT_LINK: ${{ github.event.client_payload.result_link }}
|
|
131
|
+
ALERT_TYPE: ${{ github.event.client_payload.alert_type }}
|
|
132
|
+
run: |
|
|
133
|
+
mkdir -p .checkly-heal
|
|
134
|
+
if [ -n "$CHECK_ID" ] && [ -n "$RESULT_ID" ]; then
|
|
135
|
+
curl -sf --max-time 30 \
|
|
136
|
+
-H "Authorization: Bearer $CHECKLY_API_KEY" \
|
|
137
|
+
-H "X-Checkly-Account: $CHECKLY_ACCOUNT_ID" \
|
|
138
|
+
"https://api.checklyhq.com/v1/check-results/$CHECK_ID/$RESULT_ID" \
|
|
139
|
+
-o .checkly-heal/result.json \
|
|
140
|
+
|| echo "Checkly result fetch failed — agent reproduces from scratch."
|
|
141
|
+
fi
|
|
142
|
+
jq -n \
|
|
143
|
+
--arg check_name "$CHECK_NAME" \
|
|
144
|
+
--arg alert_type "${ALERT_TYPE:-ALERT_FAILURE}" \
|
|
145
|
+
--arg result_link "${RESULT_LINK:-}" \
|
|
146
|
+
--arg result_file "$([ -f .checkly-heal/result.json ] && echo .checkly-heal/result.json)" \
|
|
147
|
+
'{check_name: $check_name, alert_type: $alert_type, result_link: $result_link, result_file: $result_file}' \
|
|
148
|
+
> .checkly-heal/context.json
|
|
149
|
+
|
|
150
|
+
# The agent gets test-fixing tools and Checkly credentials (for
|
|
151
|
+
# pw-test verification) — deliberately no GitHub token: it cannot push,
|
|
152
|
+
# comment, or open PRs even if it tries.
|
|
153
|
+
- name: Run the triage-and-heal agent
|
|
154
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
155
|
+
id: agent
|
|
156
|
+
timeout-minutes: 35
|
|
157
|
+
env:
|
|
158
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
159
|
+
CHECKLY_API_KEY: ${{ secrets.CHECKLY_API_KEY }}
|
|
160
|
+
CHECKLY_ACCOUNT_ID: ${{ secrets.CHECKLY_ACCOUNT_ID }}
|
|
161
|
+
run: |
|
|
162
|
+
npm install -g @anthropic-ai/claude-code@2
|
|
163
|
+
claude -p "$(cat .github/prompts/checkly-heal.md)" \
|
|
164
|
+
--model claude-opus-5 \
|
|
165
|
+
--allowedTools "Bash,Read,Edit,Write,Glob,Grep" \
|
|
166
|
+
--permission-mode dontAsk \
|
|
167
|
+
--max-turns 150 \
|
|
168
|
+
--output-format json \
|
|
169
|
+
> .checkly-heal/agent-output.json || true
|
|
170
|
+
|
|
171
|
+
# The guard is deterministic on purpose: the PR path requires an outcome
|
|
172
|
+
# file claiming a fix AND a diff clear of protected paths. App-source
|
|
173
|
+
# changes (test hooks) are allowed but derived from the diff, not the
|
|
174
|
+
# agent's claim, and flagged downstream. Everything else — protected
|
|
175
|
+
# paths, missing outcome, other classifications — escalates.
|
|
176
|
+
- name: Read the outcome and guard the diff
|
|
177
|
+
if: steps.guard.outputs.mode == 'heal'
|
|
178
|
+
id: outcome
|
|
179
|
+
run: |
|
|
180
|
+
classification="agent-failed"
|
|
181
|
+
summary="The heal agent did not produce an outcome."
|
|
182
|
+
if [ -f .checkly-heal/outcome.json ] && jq -e . .checkly-heal/outcome.json > /dev/null 2>&1; then
|
|
183
|
+
classification=$(jq -r '.classification // "agent-failed"' .checkly-heal/outcome.json)
|
|
184
|
+
summary=$(jq -r '.summary // ""' .checkly-heal/outcome.json)
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
files=$(git status --porcelain | awk '{print $NF}' | grep -v "^\.checkly-heal/" || true)
|
|
188
|
+
protected=$(printf '%s' "$files" | grep -Ec "$PROTECTED_PATHS" || true)
|
|
189
|
+
changed=$(printf '%s' "$files" | grep -c . || true)
|
|
190
|
+
app_change=$(printf '%s' "$files" | grep -cv "^$E2E_ROOT/" || true)
|
|
191
|
+
|
|
192
|
+
if [ "$classification" = "healed" ] || [ "$classification" = "flaky" ]; then
|
|
193
|
+
if [ "$protected" -gt 0 ]; then
|
|
194
|
+
classification="agent-failed"
|
|
195
|
+
summary="Diff guard tripped: protected files (.github, .claude, configs, lockfiles) were changed. Nothing was pushed."
|
|
196
|
+
elif [ "$changed" -eq 0 ]; then
|
|
197
|
+
classification="agent-failed"
|
|
198
|
+
summary="The agent reported a fix but changed no files."
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
echo "app_change=$([ "$app_change" -gt 0 ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
203
|
+
echo "classification=$classification" >> "$GITHUB_OUTPUT"
|
|
204
|
+
{
|
|
205
|
+
echo "summary<<CHECKLY_HEAL_EOF"
|
|
206
|
+
echo "$summary"
|
|
207
|
+
echo "CHECKLY_HEAL_EOF"
|
|
208
|
+
} >> "$GITHUB_OUTPUT"
|
|
209
|
+
echo "## Checkly auto-heal: $classification" >> "$GITHUB_STEP_SUMMARY"
|
|
210
|
+
echo "$summary" >> "$GITHUB_STEP_SUMMARY"
|
|
211
|
+
|
|
212
|
+
- name: Open the heal PR for human review
|
|
213
|
+
if: >-
|
|
214
|
+
steps.guard.outputs.mode == 'heal' &&
|
|
215
|
+
(steps.outcome.outputs.classification == 'healed' ||
|
|
216
|
+
steps.outcome.outputs.classification == 'flaky')
|
|
217
|
+
id: pr
|
|
218
|
+
env:
|
|
219
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
220
|
+
CHECK_NAME: ${{ github.event.client_payload.check_name || inputs.check_name }}
|
|
221
|
+
RESULT_LINK: ${{ github.event.client_payload.result_link }}
|
|
222
|
+
SLUG: ${{ steps.guard.outputs.slug }}
|
|
223
|
+
CLASSIFICATION: ${{ steps.outcome.outputs.classification }}
|
|
224
|
+
APP_CHANGE: ${{ steps.outcome.outputs.app_change }}
|
|
225
|
+
run: |
|
|
226
|
+
branch="checkly-heal/$SLUG-${{ github.run_id }}"
|
|
227
|
+
git config user.name "github-actions[bot]"
|
|
228
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
229
|
+
git checkout -b "$branch"
|
|
230
|
+
git add -A -- ':(exclude).checkly-heal'
|
|
231
|
+
git commit -m "Heal Checkly check: $CHECK_NAME"
|
|
232
|
+
git push -u origin "$branch"
|
|
233
|
+
|
|
234
|
+
gh label create checkly-heal --force \
|
|
235
|
+
--description "Automated fix for a failing Checkly monitor" \
|
|
236
|
+
--color BFD4F2 || true
|
|
237
|
+
|
|
238
|
+
case "$CLASSIFICATION" in
|
|
239
|
+
flaky) kind="a flaky test (hardened, stability proven with repeated runs)" ;;
|
|
240
|
+
*) kind="a stale test" ;;
|
|
241
|
+
esac
|
|
242
|
+
{
|
|
243
|
+
echo "A Checkly monitor failed and the auto-heal agent classified it as $kind. **Human review required — do not merge unread.**"
|
|
244
|
+
echo ""
|
|
245
|
+
echo "- Check: \`$CHECK_NAME\`"
|
|
246
|
+
[ -n "$RESULT_LINK" ] && echo "- Failing result: $RESULT_LINK"
|
|
247
|
+
echo "- Heal run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
248
|
+
if [ "$APP_CHANGE" = "true" ]; then
|
|
249
|
+
echo ""
|
|
250
|
+
echo "> [!IMPORTANT]"
|
|
251
|
+
echo "> This fix includes app-source changes (behavior-neutral test hooks such as \`data-testid\`). Production does not carry them yet, so the authoritative validation is **this PR's own preview run** (pr-playwright-tests) — check it before merging."
|
|
252
|
+
fi
|
|
253
|
+
echo ""
|
|
254
|
+
jq -r '.details // ""' .checkly-heal/outcome.json
|
|
255
|
+
} > pr-body.md
|
|
256
|
+
|
|
257
|
+
url=$(gh pr create \
|
|
258
|
+
--title "Heal Checkly check: $CHECK_NAME" \
|
|
259
|
+
--body-file pr-body.md \
|
|
260
|
+
--label checkly-heal)
|
|
261
|
+
echo "url=$url" >> "$GITHUB_OUTPUT"
|
|
262
|
+
|
|
263
|
+
# The single alert path: every outcome that needs eyes lands in Slack,
|
|
264
|
+
# written after triage. Runs on step failure too (always() + the guard
|
|
265
|
+
# conditions), so a crashed agent still alerts.
|
|
266
|
+
- name: Notify Slack
|
|
267
|
+
if: always() && steps.guard.outputs.mode != 'skip'
|
|
268
|
+
env:
|
|
269
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
270
|
+
CHECK_NAME: ${{ github.event.client_payload.check_name || inputs.check_name }}
|
|
271
|
+
RESULT_LINK: ${{ github.event.client_payload.result_link }}
|
|
272
|
+
MODE: ${{ steps.guard.outputs.mode }}
|
|
273
|
+
CLASSIFICATION: ${{ steps.outcome.outputs.classification }}
|
|
274
|
+
SUMMARY: ${{ steps.outcome.outputs.summary }}
|
|
275
|
+
APP_CHANGE: ${{ steps.outcome.outputs.app_change }}
|
|
276
|
+
PR_URL: ${{ steps.pr.outputs.url }}
|
|
277
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
278
|
+
run: |
|
|
279
|
+
case "${MODE}:${CLASSIFICATION}" in
|
|
280
|
+
repeat:*)
|
|
281
|
+
text=":rotating_light: *Checkly: \`$CHECK_NAME\` is failing again* — a heal PR for this check merged within the last $REPEAT_WINDOW_DAYS days, so no second automatic fix was attempted. This needs a developer." ;;
|
|
282
|
+
*:healed)
|
|
283
|
+
text=":wrench: *Checkly: \`$CHECK_NAME\` failed — fix ready for review.* $SUMMARY\nPR: $PR_URL" ;;
|
|
284
|
+
*:flaky)
|
|
285
|
+
text=":wrench: *Checkly: \`$CHECK_NAME\` was flaky — test hardened, fix ready for review.* $SUMMARY\nPR: $PR_URL" ;;
|
|
286
|
+
*:regression)
|
|
287
|
+
text=":rotating_light: *Checkly: \`$CHECK_NAME\` failed — production regression.* The test is correct; the site is broken. $SUMMARY" ;;
|
|
288
|
+
*:needs-app-change)
|
|
289
|
+
text=":warning: *Checkly: \`$CHECK_NAME\` failed — fix needs an app code change.* $SUMMARY" ;;
|
|
290
|
+
*:not-reproducible)
|
|
291
|
+
# Transient blip, nothing to act on — summary only, no ping.
|
|
292
|
+
echo "Not reproducible — no Slack notification." >> "$GITHUB_STEP_SUMMARY"
|
|
293
|
+
exit 0 ;;
|
|
294
|
+
*)
|
|
295
|
+
text=":rotating_light: *Checkly: \`$CHECK_NAME\` failed and the auto-heal run did not finish cleanly.* ${SUMMARY:-Check the run logs.} This needs a developer." ;;
|
|
296
|
+
esac
|
|
297
|
+
[ "$APP_CHANGE" = "true" ] && text="$text\nIncludes app-side test hooks — the PR's preview run validates them."
|
|
298
|
+
[ -n "$RESULT_LINK" ] && text="$text\nCheckly result: $RESULT_LINK"
|
|
299
|
+
text="$text\nRun: $RUN_URL"
|
|
300
|
+
|
|
301
|
+
if [ -z "$SLACK_WEBHOOK_URL" ]; then
|
|
302
|
+
echo "SLACK_WEBHOOK_URL is not set — notification skipped:" >> "$GITHUB_STEP_SUMMARY"
|
|
303
|
+
printf '%b\n' "$text" >> "$GITHUB_STEP_SUMMARY"
|
|
304
|
+
exit 0
|
|
305
|
+
fi
|
|
306
|
+
jq -n --arg text "$(printf '%b' "$text")" '{text: $text}' \
|
|
307
|
+
| curl -sf --max-time 15 -X POST -H "Content-Type: application/json" -d @- "$SLACK_WEBHOOK_URL" \
|
|
308
|
+
|| echo "Slack notification failed — see summary." >> "$GITHUB_STEP_SUMMARY"
|
|
309
|
+
|
|
310
|
+
- name: Upload agent artifacts
|
|
311
|
+
if: always() && steps.guard.outputs.mode == 'heal'
|
|
312
|
+
uses: actions/upload-artifact@v4
|
|
313
|
+
with:
|
|
314
|
+
name: checkly-heal-${{ github.run_id }}
|
|
315
|
+
path: .checkly-heal/
|
|
316
|
+
if-no-files-found: ignore
|
|
317
|
+
retention-days: 14
|