playwright-locator-healer 0.1.3

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +338 -0
  4. package/USAGE.md +217 -0
  5. package/dist/healing/audit-log.d.ts +9 -0
  6. package/dist/healing/audit-log.d.ts.map +1 -0
  7. package/dist/healing/audit-log.js +22 -0
  8. package/dist/healing/audit-log.js.map +1 -0
  9. package/dist/healing/auto-fixture.d.ts +14 -0
  10. package/dist/healing/auto-fixture.d.ts.map +1 -0
  11. package/dist/healing/auto-fixture.js +430 -0
  12. package/dist/healing/auto-fixture.js.map +1 -0
  13. package/dist/healing/cache.d.ts +11 -0
  14. package/dist/healing/cache.d.ts.map +1 -0
  15. package/dist/healing/cache.js +128 -0
  16. package/dist/healing/cache.js.map +1 -0
  17. package/dist/healing/circuit-breaker.d.ts +11 -0
  18. package/dist/healing/circuit-breaker.d.ts.map +1 -0
  19. package/dist/healing/circuit-breaker.js +34 -0
  20. package/dist/healing/circuit-breaker.js.map +1 -0
  21. package/dist/healing/cost-tracker.d.ts +30 -0
  22. package/dist/healing/cost-tracker.d.ts.map +1 -0
  23. package/dist/healing/cost-tracker.js +101 -0
  24. package/dist/healing/cost-tracker.js.map +1 -0
  25. package/dist/healing/dom-extractor.d.ts +9 -0
  26. package/dist/healing/dom-extractor.d.ts.map +1 -0
  27. package/dist/healing/dom-extractor.js +268 -0
  28. package/dist/healing/dom-extractor.js.map +1 -0
  29. package/dist/healing/errors.d.ts +33 -0
  30. package/dist/healing/errors.d.ts.map +1 -0
  31. package/dist/healing/errors.js +40 -0
  32. package/dist/healing/errors.js.map +1 -0
  33. package/dist/healing/heal/caller-context.d.ts +8 -0
  34. package/dist/healing/heal/caller-context.d.ts.map +1 -0
  35. package/dist/healing/heal/caller-context.js +46 -0
  36. package/dist/healing/heal/caller-context.js.map +1 -0
  37. package/dist/healing/heal/orchestrator-helpers.d.ts +84 -0
  38. package/dist/healing/heal/orchestrator-helpers.d.ts.map +1 -0
  39. package/dist/healing/heal/orchestrator-helpers.js +117 -0
  40. package/dist/healing/heal/orchestrator-helpers.js.map +1 -0
  41. package/dist/healing/heal/orchestrator.d.ts +47 -0
  42. package/dist/healing/heal/orchestrator.d.ts.map +1 -0
  43. package/dist/healing/heal/orchestrator.js +644 -0
  44. package/dist/healing/heal/orchestrator.js.map +1 -0
  45. package/dist/healing/heal/playwright-code.d.ts +26 -0
  46. package/dist/healing/heal/playwright-code.d.ts.map +1 -0
  47. package/dist/healing/heal/playwright-code.js +134 -0
  48. package/dist/healing/heal/playwright-code.js.map +1 -0
  49. package/dist/healing/heal/preflight.d.ts +8 -0
  50. package/dist/healing/heal/preflight.d.ts.map +1 -0
  51. package/dist/healing/heal/preflight.js +77 -0
  52. package/dist/healing/heal/preflight.js.map +1 -0
  53. package/dist/healing/heal/prompt.d.ts +9 -0
  54. package/dist/healing/heal/prompt.d.ts.map +1 -0
  55. package/dist/healing/heal/prompt.js +22 -0
  56. package/dist/healing/heal/prompt.js.map +1 -0
  57. package/dist/healing/heal/report.d.ts +11 -0
  58. package/dist/healing/heal/report.d.ts.map +1 -0
  59. package/dist/healing/heal/report.js +28 -0
  60. package/dist/healing/heal/report.js.map +1 -0
  61. package/dist/healing/heal/source-trace.d.ts +17 -0
  62. package/dist/healing/heal/source-trace.d.ts.map +1 -0
  63. package/dist/healing/heal/source-trace.js +59 -0
  64. package/dist/healing/heal/source-trace.js.map +1 -0
  65. package/dist/healing/index.d.ts +6 -0
  66. package/dist/healing/index.d.ts.map +1 -0
  67. package/dist/healing/index.js +3 -0
  68. package/dist/healing/index.js.map +1 -0
  69. package/dist/healing/llm-client.d.ts +58 -0
  70. package/dist/healing/llm-client.d.ts.map +1 -0
  71. package/dist/healing/llm-client.js +258 -0
  72. package/dist/healing/llm-client.js.map +1 -0
  73. package/dist/healing/logger.d.ts +18 -0
  74. package/dist/healing/logger.d.ts.map +1 -0
  75. package/dist/healing/logger.js +38 -0
  76. package/dist/healing/logger.js.map +1 -0
  77. package/dist/healing/models.d.ts +26 -0
  78. package/dist/healing/models.d.ts.map +1 -0
  79. package/dist/healing/models.js +109 -0
  80. package/dist/healing/models.js.map +1 -0
  81. package/dist/healing/overlay/bridge.d.ts +46 -0
  82. package/dist/healing/overlay/bridge.d.ts.map +1 -0
  83. package/dist/healing/overlay/bridge.js +2 -0
  84. package/dist/healing/overlay/bridge.js.map +1 -0
  85. package/dist/healing/overlay/dock.css.d.ts +2 -0
  86. package/dist/healing/overlay/dock.css.d.ts.map +1 -0
  87. package/dist/healing/overlay/dock.css.js +448 -0
  88. package/dist/healing/overlay/dock.css.js.map +1 -0
  89. package/dist/healing/overlay/dock.html.d.ts +46 -0
  90. package/dist/healing/overlay/dock.html.d.ts.map +1 -0
  91. package/dist/healing/overlay/dock.html.js +248 -0
  92. package/dist/healing/overlay/dock.html.js.map +1 -0
  93. package/dist/healing/overlay/drag.d.ts +17 -0
  94. package/dist/healing/overlay/drag.d.ts.map +1 -0
  95. package/dist/healing/overlay/drag.js +68 -0
  96. package/dist/healing/overlay/drag.js.map +1 -0
  97. package/dist/healing/overlay/inject.d.ts +41 -0
  98. package/dist/healing/overlay/inject.d.ts.map +1 -0
  99. package/dist/healing/overlay/inject.js +277 -0
  100. package/dist/healing/overlay/inject.js.map +1 -0
  101. package/dist/healing/overlay/keybinds.d.ts +33 -0
  102. package/dist/healing/overlay/keybinds.d.ts.map +1 -0
  103. package/dist/healing/overlay/keybinds.js +105 -0
  104. package/dist/healing/overlay/keybinds.js.map +1 -0
  105. package/dist/healing/prompts/heal-v21.d.ts +8 -0
  106. package/dist/healing/prompts/heal-v21.d.ts.map +1 -0
  107. package/dist/healing/prompts/heal-v21.js +204 -0
  108. package/dist/healing/prompts/heal-v21.js.map +1 -0
  109. package/dist/healing/retry-policy.d.ts +37 -0
  110. package/dist/healing/retry-policy.d.ts.map +1 -0
  111. package/dist/healing/retry-policy.js +46 -0
  112. package/dist/healing/retry-policy.js.map +1 -0
  113. package/dist/healing/session-cost.d.ts +44 -0
  114. package/dist/healing/session-cost.d.ts.map +1 -0
  115. package/dist/healing/session-cost.js +95 -0
  116. package/dist/healing/session-cost.js.map +1 -0
  117. package/dist/healing/test-fixture.d.ts +9 -0
  118. package/dist/healing/test-fixture.d.ts.map +1 -0
  119. package/dist/healing/test-fixture.js +37 -0
  120. package/dist/healing/test-fixture.js.map +1 -0
  121. package/dist/healing/types.d.ts +399 -0
  122. package/dist/healing/types.d.ts.map +1 -0
  123. package/dist/healing/types.js +162 -0
  124. package/dist/healing/types.js.map +1 -0
  125. package/dist/healing/validator.d.ts +64 -0
  126. package/dist/healing/validator.d.ts.map +1 -0
  127. package/dist/healing/validator.js +286 -0
  128. package/dist/healing/validator.js.map +1 -0
  129. package/dist/scripts/heal-report.d.ts +23 -0
  130. package/dist/scripts/heal-report.d.ts.map +1 -0
  131. package/dist/scripts/heal-report.js +106 -0
  132. package/dist/scripts/heal-report.js.map +1 -0
  133. package/dist/scripts/init.d.ts +3 -0
  134. package/dist/scripts/init.d.ts.map +1 -0
  135. package/dist/scripts/init.js +132 -0
  136. package/dist/scripts/init.js.map +1 -0
  137. package/package.json +84 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to `playwright-locator-healer` are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ---
6
+
7
+ ## [0.1.0] — 2026-05-16
8
+
9
+ First public release. Inaugural npm publish under the `@dhana009` scope.
10
+
11
+ ### Added — heal flow
12
+
13
+ - Zero-modification **auto-fixture** at `playwright-locator-healer/auto`. Drop-in replacement for `@playwright/test`: any locator action or web-first assertion that times out triggers heal automatically; the action retries against the healed locator in the same run.
14
+ - Explicit **opt-in fixture** at `playwright-locator-healer/test` for incremental Page Object Model migration.
15
+ - Interactive in-browser overlay (Shadow DOM dock) for picking the correct element when a locator breaks. VS Code-style slate palette, draggable, keyboard-driven (A / R / E / B / L / P / Esc).
16
+ - Pre-flight free probe stage: id, data-testid, aria-label, role+name on the picked node. Resolves ~30% of heals at zero LLM cost.
17
+ - Progressive LLM rounds (up to 4): silent auto-accept first valid, then user-gated "broaden?" prompts, then a JS-eval escape hatch.
18
+ - Per-spec selector cache at `.healed-locators/<spec>.json`. Strict-schema reads with malformed entries quarantined and surfaced on stderr.
19
+ - Filter-chain depth enforcement: validator rejects locator chains deeper than 2 levels.
20
+ - Per-page heal serialization so concurrent racing actions queue and second-callers hit the cache.
21
+
22
+ ### Added — reliability (v3 wave 1)
23
+
24
+ - **Circuit breaker** in `healing/circuit-breaker.ts`. After `HEAL_CIRCUIT_THRESHOLD` (default 3) consecutive LLM failures, the next heal short-circuits with `HealingError` category `circuit-open` instead of paying for another doomed call. Automatically resets on the next successful call.
25
+ - **Strict cache schema** validation on every read. Manual edits, partial writes, or schema drift surface `[HEAL] cache entry rejected for X: <reason>` and the heal proceeds fresh instead of returning garbage.
26
+ - **Filter-chain depth guard** rejects 3+-level chains at validator-build time with `HealingError` category `invalid-filter-chain`.
27
+ - **Jittered exponential backoff** on LLM retries. `HEALING_BACKOFF_MS=1000,2000,4000` becomes `1000±20%, 2000±20%, 4000±20%` to break up thundering-herd retries against OpenAI.
28
+ - **No-silent-swallow** policy enforced in `healing/heal/orchestrator.ts`. Best-effort writes that previously did `.catch(() => {})` now log via `healing/audit-log.ts` and `healing/logger.ts`.
29
+ - New `HealingError` categories: `circuit-open`, `invalid-cache`, `invalid-filter-chain`.
30
+
31
+ ### Added — observability
32
+
33
+ - **Level-gated logger** in `healing/logger.ts`. `HEAL_LOG_LEVEL` env (default `warn`) controls verbosity (`debug` / `info` / `warn` / `error` / `silent`).
34
+ - **Audit log helper** in `healing/audit-log.ts`. Surfaces append failures instead of swallowing them.
35
+ - **Cache-hit-rate tracking** in `healing/cost-tracker.ts` via `getCacheHitRate()`. Daily `cachedCalls` is now persisted to `.healed-locators/cost.daily.json`.
36
+ - **`heal-report` CLI** (`scripts/heal-report.ts`, exposed as `npx heal-report`). Renders a markdown table of date, calls, cost, cache-hit-rate, cumulative cost across the lifetime of the cost log.
37
+
38
+ ### Added — model + cost
39
+
40
+ - **Multi-model registry** in `healing/models.ts`. Switch model via `HEAL_MODEL` (`gpt-4.1-mini` default, plus `gpt-4.1`, `gpt-4.1-nano`, `gpt-5.4-mini`, `gpt-5.4-nano`).
41
+ - **Runtime price overrides** via `HEAL_PRICE_INPUT_PER_M`, `HEAL_PRICE_CACHED_PER_M`, `HEAL_PRICE_OUTPUT_PER_M` for surviving OpenAI rate changes without a library release.
42
+ - **Per-call raw usage audit log** at `.healed-locators/llm-calls.ndjson` for independent cross-verification against OpenAI billing.
43
+ - **Prompt-cache aware session summary** at process exit (calls, fresh + cached input tokens, output tokens, cost, savings).
44
+
45
+ ### Added — packaging
46
+
47
+ - Published as `playwright-locator-healer` with `peerDependencies` on `@playwright/test ^1.59.0`.
48
+ - Exports map: `.`, `./auto`, `./test`.
49
+ - `bin` entry `heal-report` exposing the cost-report CLI.
50
+ - `files` allow-list ensures the published tarball is minimal (`dist/`, `scripts/heal-report.ts`, `README.md`, `USAGE.md`, `CHANGELOG.md`, `LICENSE`).
51
+ - CI workflow at `.github/workflows/ci.yml`: Node 20 + 22 matrix, `tsc --noEmit`, `vitest run`, headless-chromium integration. LLM-dependent tests are gated behind `OPENAI_API_KEY` presence.
52
+
53
+ ### Verified working
54
+
55
+ - 121 unit tests in `healing/__tests__/`
56
+ - 3 integration flows in `tests/integration/heal-e2e-auto.spec.ts`
57
+ - 3 zero-mod auto-fixture flows in `tests/integration/heal-auto-fixture.spec.ts`
58
+ - `npx tsc --noEmit` zero errors
59
+ - OpenAI prompt cache verified against live billing dashboard (77% input tokens cached on multi-call sessions)
60
+ - Median per-heal cost target: $0 to $0.0007 — achieved
61
+
62
+ ### Known limitations (deferred to v0.2+)
63
+
64
+ - No keep-alive ping — sporadic users may see frequent prompt-cache cold starts.
65
+ - No diff view before the user accepts a proposed locator.
66
+ - No dock-position memory across sessions.
67
+ - Status-bar elapsed timer renders `0s` (cosmetic).
68
+ - Only OpenAI-compatible models supported; multi-provider abstraction is on the roadmap.
69
+ - Shadow-DOM piercing, cross-origin iframes, CAPTCHA handling, and source-rewrite-into-test-file are explicitly out of scope.
70
+
71
+ [0.1.0]: https://github.com/Dhana009/playwright-locator-healer/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhanunjaya Madharapakam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,338 @@
1
+ <div align="center">
2
+
3
+ # 🩹 playwright-locator-healer
4
+
5
+ ### *Self-healing Playwright locators — pick, confirm, cached forever.*
6
+
7
+ [![npm version](https://img.shields.io/npm/v/playwright-locator-healer.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/playwright-locator-healer)
8
+ [![npm downloads](https://img.shields.io/npm/dm/playwright-locator-healer.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/playwright-locator-healer)
9
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
10
+ [![node](https://img.shields.io/node/v/playwright-locator-healer.svg?style=flat-square&color=339933)](https://nodejs.org)
11
+ [![Playwright](https://img.shields.io/badge/playwright-≥1.59-2EAD33.svg?style=flat-square)](https://playwright.dev)
12
+ [![CI](https://img.shields.io/github/actions/workflow/status/Dhana009/playwright-locator-healer/ci.yml?branch=main&style=flat-square)](https://github.com/Dhana009/playwright-locator-healer/actions)
13
+
14
+ **Stop fixing locators by hand. Let your tests fix themselves.**
15
+
16
+ [Quickstart](#-quickstart) · [How it works](#-how-it-works) · [Cost](#-cost-economics) · [FAQ](#-faq)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## 😖 The problem
23
+
24
+ ```ts
25
+ await page.locator('#login-btn').click(); // ❌ TimeoutError — class changed yesterday
26
+ ```
27
+
28
+ Your test suite breaks **5–20 times a week** — not because the feature is broken, but because a class got renamed, an id grew a hash suffix, or a button moved one level deeper. You sigh. You open DevTools. You hunt for a new selector. You re-run. Repeat tomorrow.
29
+
30
+ ## 🩹 The fix
31
+
32
+ ```diff
33
+ - import { test, expect } from '@playwright/test';
34
+ + import { test, expect } from 'playwright-locator-healer/auto';
35
+ ```
36
+
37
+ Next time a locator fails:
38
+
39
+ | # | What happens |
40
+ |:-:|---|
41
+ | 1 | 🖼️ An overlay opens in your live browser |
42
+ | 2 | 👆 You click the element the test meant to target |
43
+ | 3 | 🤖 An LLM names a stable selector (`role=button[name="Login"]`, `[data-testid=submit]`, …) |
44
+ | 4 | ✅ You hit Accept. Test continues. |
45
+ | 5 | 🚀 Every subsequent run uses the cached selector — **no overlay, no LLM call, no cost** |
46
+
47
+ Commit `.healed-locators/` to git. CI runs against cached selectors. Interactive heal happens **only locally**, only when something actually breaks.
48
+
49
+ ---
50
+
51
+ ## ✨ Why it's different
52
+
53
+ | | Manual fix | Other healers | playwright-locator-healer |
54
+ |---|:-:|:-:|:-:|
55
+ | Zero-mod integration | ❌ | ⚠️ | ✅ |
56
+ | Human-in-the-loop pick | — | ❌ | ✅ |
57
+ | Stable-tier selector ranking | ❌ | ⚠️ | ✅ |
58
+ | Cached drift detection | — | ⚠️ | ✅ |
59
+ | Free in CI (cache-only) | — | ❌ | ✅ |
60
+ | Median cost per heal | ∞ time | $0.005+ | **$0.0007** |
61
+
62
+ ---
63
+
64
+ ## 🎬 See it work
65
+
66
+ > 📸 Screenshots will live in `docs/screenshots/`. Capture them with `npx playwright test --headed` against a broken selector and drop the PNG/GIF files in. See `docs/screenshots/README.md` for the playbook.
67
+
68
+ ```
69
+ ┌──────────────────────────────────────────────────────────┐
70
+ │ 🩹 HEAL · pick the element _ □ ✕ │
71
+ │ ─────────────────────────────────────────────────────── │
72
+ │ Broken: #login-btn │
73
+ │ Action: click │
74
+ │ Intent: [ type a short description... ] │
75
+ │ ─────────────────────────────────────────────────────── │
76
+ │ > Hover any element on the page. Click to pick. │
77
+ └──────────────────────────────────────────────────────────┘
78
+ ↓ pick
79
+ ┌──────────────────────────────────────────────────────────┐
80
+ │ 🩹 HEAL · you picked │
81
+ │ Tag: <button> │
82
+ │ Role: button │
83
+ │ Accessible name: Login │
84
+ │ Stable handle: #submit │
85
+ │ ─────────────────────────────────────────────────────── │
86
+ │ [ ↩ re-pick ] [ Enter continue ] [ Esc cancel ] │
87
+ └──────────────────────────────────────────────────────────┘
88
+ ↓ continue
89
+ ┌──────────────────────────────────────────────────────────┐
90
+ │ 🩹 HEAL · proposed │
91
+ │ role=button[name="Login"] tier 1 ★★★★★ │
92
+ │ ─────────────────────────────────────────────────────── │
93
+ │ [ ✅ Accept ] [ ✏️ Reject ] [ 🛟 Broaden ] [ Cancel ] │
94
+ └──────────────────────────────────────────────────────────┘
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 🚀 Quickstart
100
+
101
+ ```bash
102
+ npm install --save-dev playwright-locator-healer
103
+ npx playwright-heal init
104
+ ```
105
+
106
+ `init` is idempotent. It writes `.env.example`, creates `.healed-locators/.gitkeep`, appends safe `.gitignore` entries (logs and screenshots only — cache JSON stays tracked), and prints next steps.
107
+
108
+ Add your key (only needed when the LLM stage actually runs):
109
+
110
+ ```bash
111
+ echo "OPENAI_API_KEY=sk-..." >> .env
112
+ ```
113
+
114
+ Swap one import line — your tests now self-heal:
115
+
116
+ ```ts
117
+ // Before
118
+ import { test, expect } from '@playwright/test';
119
+
120
+ // After — no other changes needed
121
+ import { test, expect } from 'playwright-locator-healer/auto';
122
+ ```
123
+
124
+ Run headed:
125
+
126
+ ```bash
127
+ npx playwright test --headed
128
+ ```
129
+
130
+ When a locator fails, an overlay opens in the live browser. Pick the element, type a short intent, accept the proposal. On every subsequent run the cached selector is used — no overlay, no LLM call.
131
+
132
+ ---
133
+
134
+ ## ⚙️ How it works
135
+
136
+ ```
137
+ heal(originalSelector, action)
138
+
139
+ ├─ Stage 0 Live re-check
140
+ │ page.locator(orig).count() === 1 → no heal needed
141
+
142
+ ├─ Stage 1 Cache hit
143
+ │ .healed-locators/<spec>.json has entry + live validates → return
144
+
145
+ ├─ Stage 2 Open overlay
146
+ │ Shadow-DOM dock. User picks element + types intent.
147
+
148
+ ├─ Stage 3 Pre-flight (free, no LLM)
149
+ │ id / data-testid / aria-label / role+name on picked node.
150
+ │ Unique + visible → cache + return. ~30% hit rate, $0.
151
+
152
+ ├─ Stage 4 Progressive LLM rounds (up to 4)
153
+ │ R1 silent auto-accept first valid, tiers 1-4
154
+ │ R2 user-gated "broaden?", tiers 1-5
155
+ │ R3 user-gated, tiers 1-6 (xpath allowed)
156
+ │ R4 escape hatch (js eval, never cached, audit-only)
157
+
158
+ └─ Stage 5 Exhausted → audit log + rethrow original error
159
+ ```
160
+
161
+ Every LLM suggestion is validated (`count === 1` and visible) before it reaches the confirm UI. The LLM never auto-applies anything — you accept or reject each candidate.
162
+
163
+ ---
164
+
165
+ ## 🔌 Two integration modes
166
+
167
+ ### Auto-fixture (zero test modifications) — recommended
168
+
169
+ ```ts
170
+ // tests/checkout.spec.ts
171
+ import { test, expect } from 'playwright-locator-healer/auto';
172
+
173
+ test('checkout flow', async ({ page }) => {
174
+ await page.goto('https://example.com/cart');
175
+ await page.locator('#checkout-btn').click();
176
+ await expect(page.locator('.order-confirm')).toBeVisible();
177
+ });
178
+ ```
179
+
180
+ `page` and `expect` are transparently proxied. Any locator action (`click`, `fill`, `check`, etc.) or web-first assertion (`toBeVisible`, `toHaveText`, etc.) that times out triggers heal. The action is retried against the healed locator in the same run. Existing Playwright tests need only the import swap.
181
+
182
+ ### Opt-in fixture (explicit)
183
+
184
+ Use when you want full control over when heal fires, or when migrating a Page Object Model incrementally.
185
+
186
+ ```ts
187
+ import { test } from 'playwright-locator-healer/test';
188
+
189
+ test('signup', async ({ page, heal }) => {
190
+ await page.goto('/signup');
191
+
192
+ const entry = await heal({
193
+ originalSelector: '#submit-btn',
194
+ action: 'click',
195
+ description: 'submit button on signup form',
196
+ });
197
+
198
+ await page.locator(entry.healedSelector).click();
199
+ });
200
+ ```
201
+
202
+ `heal()` returns a `HealEntryV2` with `healedSelector`, `selectorType`, and `tier`. On every future run the call resolves instantly from cache.
203
+
204
+ ---
205
+
206
+ ## Environment variables
207
+
208
+ | Variable | Required | Default | Description |
209
+ |---|---|---|---|
210
+ | `OPENAI_API_KEY` | Yes (LLM stage only) | — | OpenAI API key. Not read until Stage 4 fires. |
211
+ | `HEAL_MODEL` | No | `gpt-4.1-mini` | Model ID. See model table below. |
212
+ | `HEAL_HEADLESS` | No | `0` | `1` enables CI mode: cache hits return, misses rethrow without LLM. |
213
+ | `HEAL_LOG_LEVEL` | No | `warn` | `debug` / `info` / `warn` / `error` / `silent`. |
214
+ | `HEALING_TIMEOUT_LLM` | No | `15000` | Per-call LLM wall-clock timeout (ms). |
215
+ | `HEALING_BACKOFF_MS` | No | `1000,2000,4000` | Comma-separated retry backoff schedule (ms). Each delay is jittered ±20%. |
216
+ | `HEAL_CIRCUIT_THRESHOLD` | No | `3` | Consecutive LLM failures before circuit opens (fast-fail). |
217
+ | `HEAL_PRICE_INPUT_PER_M` | No | model default | Override fresh input token price (USD per 1M tokens). |
218
+ | `HEAL_PRICE_CACHED_PER_M` | No | model default | Override cached input token price. |
219
+ | `HEAL_PRICE_OUTPUT_PER_M` | No | model default | Override output token price. |
220
+
221
+ ---
222
+
223
+ ## Model picker
224
+
225
+ | `HEAL_MODEL` | Input (fresh) | Input (cached) | Output | Notes |
226
+ |---|---|---|---|---|
227
+ | `gpt-4.1-mini` (default) | $0.40 / 1M | $0.10 / 1M | $1.60 / 1M | Best cost/quality for heal workloads. |
228
+ | `gpt-4.1` | $2.00 / 1M | $0.50 / 1M | $8.00 / 1M | Higher locator quality on complex UIs. |
229
+ | `gpt-4.1-nano` | $0.10 / 1M | $0.025 / 1M | $0.40 / 1M | Cheapest; weaker on ambiguous picks. |
230
+ | `gpt-5.4-mini` | $0.30 / 1M | $0.075 / 1M | $1.20 / 1M | Alternative mini (placeholder pricing). |
231
+ | `gpt-5.4-nano` | $0.075 / 1M | $0.019 / 1M | $0.30 / 1M | Alternative nano (placeholder pricing). |
232
+
233
+ Switch at any time:
234
+
235
+ ```bash
236
+ HEAL_MODEL=gpt-4.1 npx playwright test --headed
237
+ ```
238
+
239
+ Override pricing when OpenAI rates shift without a library release:
240
+
241
+ ```bash
242
+ HEAL_PRICE_INPUT_PER_M=0.30 HEAL_PRICE_CACHED_PER_M=0.075 npx playwright test --headed
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Cost economics
248
+
249
+ | Path | Cost |
250
+ |---|---|
251
+ | Cache hit (selector already healed in a prior run) | $0 |
252
+ | Pre-flight hit (id / testid / aria-label on picked node) | $0 |
253
+ | LLM Round 1 — first call of session (cold prompt cache) | ~$0.0016 |
254
+ | LLM Round 1 — subsequent call in session (cache warm) | ~$0.0007 |
255
+ | LLM Round 2 — with previous-attempt context | ~$0.0009 |
256
+ | LLM Round 3 | ~$0.0010 |
257
+
258
+ **Prompt caching.** The system prompt (~1200 tokens) is byte-identical across heal calls. OpenAI caches it server-side after the first call; subsequent calls pay 75% less on the input portion. Cache persists 5–10 min in-memory, up to 24 h on supported models.
259
+
260
+ **Typical session.** Pre-flight handles ~30% of heals for free. Cache hits cover the rest after the first run. Median per-heal cost: **$0 to $0.0007**.
261
+
262
+ **Daily budget.** 100 heals across mixed paths: $0.05–$0.10. Annual cost for a daily user: ~$15–$30 after week-1 warm-up. First week may run $1–2 while cache entries accumulate.
263
+
264
+ A session summary is printed to stderr at process exit:
265
+
266
+ ```
267
+ [HEAL] SESSION LLM COST SUMMARY
268
+ [HEAL] calls: 4
269
+ [HEAL] tokens: 6,842in (5,200 cached, 76%) + 948out
270
+ [HEAL] cost: $0.003204 USD
271
+ [HEAL] savings from prompt cache: $0.001740 USD
272
+ ```
273
+
274
+ For per-day rollups, run the bundled CLI:
275
+
276
+ ```bash
277
+ npx heal-report
278
+ ```
279
+
280
+ Output: a markdown table of date, calls, cost, cache-hit-rate, cumulative cost across the lifetime of `.healed-locators/cost.daily.json`.
281
+
282
+ ---
283
+
284
+ ## Known limitations
285
+
286
+ - **No iframe support.** The auto-fixture proxies `page.locator` / `page.getBy*` but not `page.frameLocator(...).locator(...)`. Tests that target DOM inside an iframe (third-party widgets, embedded payment forms, TinyMCE editors, etc.) will not heal. Workaround: refactor the iframe locator into a Page Object Method that catches its own timeout, OR add a non-iframe alternative selector in the test source.
287
+ - **No closed-shadow-DOM support.** Closed shadow roots cannot be pierced from outside the host; the pick handler surfaces a clear `HealingError('shadow-unsupported')` instead of opening the overlay against the wrong element. Open shadow roots work fine via `composedPath()`.
288
+ - **OpenAI only (v0.1).** Multi-provider support (Anthropic, Gemini, local) is on the v1.x roadmap.
289
+ - **No auto-rewriting of test source.** The library writes the cache; it never modifies your `.spec.ts` files. After a heal session, `report.md` lists what changed so you can fold the healed selector back into source manually when convenient.
290
+ - **Local-dev tool.** CI cannot run the interactive overlay. Setting `HEAL_HEADLESS=1` (or `CI=1`) puts the library in cache-only mode: hits return, misses fast-fail with a clear `no-operator-in-ci` error. Always commit `.healed-locators/` so CI starts warm.
291
+
292
+ ---
293
+
294
+ ## FAQ
295
+
296
+ **Q: Does this work in CI?**
297
+
298
+ Not interactively. The overlay requires a headed browser. In CI, set `HEAL_HEADLESS=1`: cache hits return silently, cache misses rethrow the original error without calling the LLM. Commit `.healed-locators/` to version control so CI always starts with warm cache.
299
+
300
+ **Q: Where is the cache stored?**
301
+
302
+ `.healed-locators/<spec-filename>.json` in your project root — one file per test file. Plain JSON, safe to commit, human-readable. Alongside it: `audit.ndjson` (one line per heal event), `llm-calls.ndjson` (raw OpenAI usage for independent cost verification), and `cost.daily.json` (per-day rollup the `heal-report` CLI reads).
303
+
304
+ **Q: What happens if `OPENAI_API_KEY` is not set?**
305
+
306
+ Stages 0–3 run normally (live re-check, cache, overlay pick, pre-flight). Stage 4 throws `HealingError` with category `config` and a clear message pointing at the missing env var. Tests that reach Stage 4 without a key fail fast.
307
+
308
+ **Q: The healed locator is not being applied back to my test source — why?**
309
+
310
+ By design. The library writes the cache (`.healed-locators/`) and an append-only `report.md`. It does **not** rewrite test source code. After a heal session, open `report.md`: it lists the spec file, line number, broken selector, and healed selector so you can apply the change manually if you wish.
311
+
312
+ **Q: Can I use a different LLM provider?**
313
+
314
+ Not in v0.1. Only OpenAI-compatible models via `HEAL_MODEL` are supported. Multi-provider abstraction is on the v1.x roadmap.
315
+
316
+ **Q: Does it heal locators inside iframes?**
317
+
318
+ No (see Known limitations). Tests targeting DOM inside `frameLocator(...)` will not heal. Use a non-iframe alternative selector in the test, or wrap the iframe interaction in a try/catch that falls back to a manual locator.
319
+
320
+ **Q: How big is the install?**
321
+
322
+ Tarball ~120 kB on disk after npm install. Runtime peer dep: `@playwright/test ^1.59.0`. Runtime deps: `openai`, `zod`, `dotenv`.
323
+
324
+ **Q: My team is worried about an LLM editing test code.**
325
+
326
+ It doesn't. The LLM only proposes a selector string; you click "accept" before any cache is written; the test source itself is never touched. The healed selector lives in `.healed-locators/<spec>.json` next to your tests — plain JSON, hand-editable, schema-validated on read.
327
+
328
+ **Q: What about parallel test runs writing the same cache file?**
329
+
330
+ Atomic writes (tmpfile + rename) at the cache layer. Per-page heal lock prevents two concurrent heals on the same page from racing the overlay. For full isolation across Playwright projects, set `HEAL_CACHE_DIR=$(pwd)/.healed-locators-${project}`.
331
+
332
+ ---
333
+
334
+ ## License
335
+
336
+ MIT — see [LICENSE](LICENSE).
337
+
338
+ For installation patterns, CI recipes, troubleshooting, and advanced configuration, see [USAGE.md](USAGE.md). For contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). For the version history, see [CHANGELOG.md](CHANGELOG.md).
package/USAGE.md ADDED
@@ -0,0 +1,217 @@
1
+ # Usage Guide — `playwright-locator-healer`
2
+
3
+ Drop-in self-healing for an existing Playwright suite. Most projects need a one-line import swap.
4
+
5
+ ---
6
+
7
+ ## 1. Install
8
+
9
+ ```bash
10
+ npm install --save-dev playwright-locator-healer
11
+ ```
12
+
13
+ Requires:
14
+ - Node 20+
15
+ - `@playwright/test` 1.59+ (declared as `peerDependencies`)
16
+ - OpenAI API key — only when the LLM stage runs (cache hits + pre-flight are free)
17
+
18
+ ```bash
19
+ # .env in your project root
20
+ OPENAI_API_KEY=sk-...
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 2. Two integration modes — pick one per test file
26
+
27
+ ### Mode A — Auto-fixture (recommended, zero test changes)
28
+
29
+ Swap the import line; everything else stays the same.
30
+
31
+ ```ts
32
+ // tests/login.spec.ts
33
+ import { test, expect } from 'playwright-locator-healer/auto';
34
+
35
+ test('login', async ({ page }) => {
36
+ await page.goto('https://example.com');
37
+ await page.locator('#submit').click(); // heals on TimeoutError
38
+ await expect(page.locator('.welcome')).toBeVisible(); // assertion also heals
39
+ });
40
+ ```
41
+
42
+ The fixture proxies `page` (and chained locators) plus `expect`. Any locator action or web-first assertion that hits `TimeoutError` opens the heal overlay. The action is retried against the healed locator and execution continues from the same step.
43
+
44
+ Wrapped action methods: `click`, `dblclick`, `fill`, `check`, `uncheck`, `hover`, `type`, `press`, `selectOption`, `tap`, `focus`, `blur`, `clear`, `dragTo`, `setInputFiles`, `pressSequentially`, `scrollIntoViewIfNeeded`.
45
+
46
+ Wrapped chain methods: `locator`, `getByRole`, `getByText`, `getByLabel`, `getByTestId`, `getByPlaceholder`, `getByTitle`, `getByAltText`, `filter`, `first`, `last`, `nth`, `and`, `or`.
47
+
48
+ Plus `expect(loc).to*`, `expect.soft(loc).to*`, and `.not.to*` matchers.
49
+
50
+ ### Mode B — Opt-in fixture (legacy / explicit)
51
+
52
+ Useful for incremental migration of a Page Object Model, or when you want to gate exactly which calls can heal.
53
+
54
+ ```ts
55
+ import { test, expect } from 'playwright-locator-healer/test';
56
+
57
+ test('signup', async ({ page, heal }) => {
58
+ await page.goto('/signup');
59
+
60
+ try {
61
+ await page.locator('#submit-btn').click({ timeout: 5_000 });
62
+ } catch {
63
+ const { healedSelector } = await heal({
64
+ originalSelector: '#submit-btn',
65
+ action: 'click',
66
+ description: 'submit button on signup form',
67
+ });
68
+ await page.locator(healedSelector).click();
69
+ }
70
+ });
71
+ ```
72
+
73
+ `heal()` returns a `HealEntryV2` with `healedSelector`, `selectorType` (`role` / `testid` / `css` / `xpath` / `text`), and the `tier` the LLM landed on. On subsequent runs the cache makes the call essentially free.
74
+
75
+ ---
76
+
77
+ ## 3. Running tests
78
+
79
+ ```bash
80
+ # Day-to-day — headed so the overlay can open when locators break
81
+ npx playwright test --headed
82
+
83
+ # CI — cache hits return, misses rethrow without any LLM call
84
+ HEAL_HEADLESS=1 npx playwright test
85
+
86
+ # Fresh heal (wipe cache)
87
+ rm -rf .healed-locators && npx playwright test --headed
88
+
89
+ # Switch model for one session
90
+ HEAL_MODEL=gpt-4.1 npx playwright test --headed
91
+
92
+ # Per-day cost + cache-hit-rate rollup
93
+ npx heal-report
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 4. Configuration
99
+
100
+ See README's "Environment variables" table for the full list. Most-touched:
101
+
102
+ | Variable | Purpose |
103
+ |---|---|
104
+ | `OPENAI_API_KEY` | Required when an LLM round actually runs. |
105
+ | `HEAL_HEADLESS=1` | CI mode. Cache-hit-or-fail. No overlay, no LLM. |
106
+ | `HEAL_MODEL` | `gpt-4.1-mini` (default), `gpt-4.1`, `gpt-4.1-nano`, `gpt-5.4-mini`, `gpt-5.4-nano`. |
107
+ | `HEAL_LOG_LEVEL` | `debug` / `info` / `warn` (default) / `error` / `silent`. |
108
+ | `HEAL_CIRCUIT_THRESHOLD` | Consecutive LLM failures before fast-fail kicks in (default 3). |
109
+
110
+ ---
111
+
112
+ ## 5. Cache directory layout
113
+
114
+ ```
115
+ .healed-locators/
116
+ <spec-filename>.json # selector cache, keyed by original selector + action
117
+ audit.ndjson # one JSON line per heal event
118
+ llm-calls.ndjson # raw OpenAI usage per call (cost cross-verification)
119
+ cost.daily.json # per-day + all-time totals (npx heal-report reads this)
120
+ report.md # human-readable heal log (spec + line + before/after)
121
+ screenshots/ # failure screenshots when Stage 5 exhausts
122
+ ```
123
+
124
+ **Commit `.healed-locators/` to git.** CI then starts with warm cache and pays $0. Skip it in `.gitignore` and every CI run pays for fresh heals.
125
+
126
+ Cache entry shape (v2):
127
+
128
+ ```json
129
+ {
130
+ "version": 2,
131
+ "entries": {
132
+ "#submit-btn|click": {
133
+ "originalSelector": "#submit-btn",
134
+ "action": "click",
135
+ "healedSelector": "Submit",
136
+ "selectorType": "text",
137
+ "intent": "submit button on signup form",
138
+ "callSite": "tests/signup.spec.ts:42:18",
139
+ "healedAt": "2026-05-16T14:22:03Z"
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## 6. Writing good `description` / intent strings
148
+
149
+ For the opt-in API: pass `description`. For the auto-fixture: type the intent in the overlay. A strong description directly improves locator stability.
150
+
151
+ Bad — derived from the broken selector:
152
+
153
+ ```ts
154
+ description: `element with selector ${sel}`
155
+ ```
156
+
157
+ Good — purpose + location + region:
158
+
159
+ ```ts
160
+ description: 'the Submit button at the bottom of the login form, below the password input'
161
+ ```
162
+
163
+ Include:
164
+ - **Kind:** button, link, input, dropdown
165
+ - **Purpose:** "submits the form", "opens search modal"
166
+ - **Location:** "top navbar", "left sidebar Getting Started section", "inside the modal"
167
+
168
+ ---
169
+
170
+ ## 7. CI patterns
171
+
172
+ ```bash
173
+ # Pattern A — committed cache (recommended). $0 LLM cost in CI.
174
+ HEAL_HEADLESS=1 npx playwright test
175
+ ```
176
+
177
+ The recommended workflow: dev heals locally headed, commits the updated `.healed-locators/<spec>.json`, CI consumes the warm cache via `HEAL_HEADLESS=1`.
178
+
179
+ ---
180
+
181
+ ## 8. Troubleshooting
182
+
183
+ | Symptom | Cause + fix |
184
+ |---|---|
185
+ | `HealingError: API key not set` | `.env` not loaded. `import 'dotenv/config'` at the top of `playwright.config.ts`, or pass `OPENAI_API_KEY=...` inline. |
186
+ | `[heal] LLM circuit breaker open` | 3+ consecutive LLM failures (network / 429). Wait; the breaker auto-resets on the next successful call. Bump `HEAL_CIRCUIT_THRESHOLD` for more tolerance. |
187
+ | `[HEAL] cache entry rejected for X` | Malformed entry in `.healed-locators/<spec>.json`. Heal proceeds fresh. Delete the line or the file to silence. |
188
+ | Heal lands on the wrong element | Vague description / intent. Be more specific in the overlay or in the `description` field. |
189
+ | Tests pass locally but fail in CI | CI lacks `OPENAI_API_KEY` and `.healed-locators/` is not committed. Pick one: commit cache (free) or set the CI secret. |
190
+ | `Stage 5 exhausted` on a known-good element | LLM couldn't disambiguate. Inspect `audit.ndjson` last entry — `suggestionsTried` shows what failed. Strengthen the description with a parent landmark. |
191
+
192
+ ---
193
+
194
+ ## 9. Errors
195
+
196
+ | Class | When | Test outcome |
197
+ |---|---|---|
198
+ | `HealCancelledError` | User pressed Esc in the overlay | Fails |
199
+ | `HealExhaustedError` | All LLM rounds produced no valid locator | Fails |
200
+ | `HealLLMError` | OpenAI API failure (network / auth / 429) | Fails |
201
+ | `HealingError` (category `circuit-open`) | Circuit breaker has tripped | Fast-fail |
202
+ | `HealingError` (category `invalid-cache`) | Malformed cache entry rejected at read | Heal proceeds fresh |
203
+ | `HealingError` (category `invalid-filter-chain`) | LLM emitted a too-deep filter chain | Suggestion skipped |
204
+ | `HealingError` (category `config`) | Missing `OPENAI_API_KEY` etc. | Fails |
205
+
206
+ All errors carry `context: { originalSelector, action, callSite, attempts? }`.
207
+
208
+ ---
209
+
210
+ ## 10. Minimum drop-in diff
211
+
212
+ ```diff
213
+ - import { test, expect } from '@playwright/test';
214
+ + import { test, expect } from 'playwright-locator-healer/auto';
215
+ ```
216
+
217
+ That's it. Run normally. The next time a selector breaks, the overlay opens; pick it once and every future run is free.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Append one JSON line to an audit ndjson file. Failures are logged to
3
+ * stderr (with the failing entry's keys, never the values) and re-thrown
4
+ * so callers can decide to swallow or propagate. The previous
5
+ * `.catch(() => {})` silent-swallow pattern hid I/O issues (disk full,
6
+ * permission denied, read-only mount, etc.).
7
+ */
8
+ export declare function appendAuditLine(filePath: string, entry: Record<string, unknown>): Promise<void>;
9
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../healing/audit-log.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAYf"}