tamash-playwright 0.7.0-beta.2 → 0.7.0-beta.4
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 +62 -5
- package/dist/bindings/locator.binding.d.ts +1 -0
- package/dist/bindings/locator.binding.d.ts.map +1 -1
- package/dist/bindings/locator.binding.js +99 -10
- package/dist/bindings/locator.binding.js.map +1 -1
- package/dist/cli/applyHeals.d.ts +1 -0
- package/dist/cli/applyHeals.d.ts.map +1 -1
- package/dist/cli/applyHeals.js +122 -23
- package/dist/cli/applyHeals.js.map +1 -1
- package/dist/healer/heal-log.d.ts +4 -0
- package/dist/healer/heal-log.d.ts.map +1 -1
- package/dist/healer/heal-log.js.map +1 -1
- package/dist/healer/index.d.ts.map +1 -1
- package/dist/healer/index.js +313 -52
- package/dist/healer/index.js.map +1 -1
- package/dist/healer/providers/prompt.d.ts +1 -1
- package/dist/healer/providers/prompt.d.ts.map +1 -1
- package/dist/healer/providers/prompt.js +17 -1
- package/dist/healer/providers/prompt.js.map +1 -1
- package/dist/healer/providers/types.d.ts +15 -0
- package/dist/healer/providers/types.d.ts.map +1 -1
- package/dist/healer/vision.d.ts +6 -1
- package/dist/healer/vision.d.ts.map +1 -1
- package/dist/healer/vision.js +3 -9
- package/dist/healer/vision.js.map +1 -1
- package/package.json +3 -1
- package/usage.md +157 -5
package/README.md
CHANGED
|
@@ -170,6 +170,16 @@ test('login test using CSS Selectors', async ({ page }) => {
|
|
|
170
170
|
|
|
171
171
|
This step is optional, but recommended — without it, the healer has to guess purely from a broken CSS selector, which gives it a lot less to work with.
|
|
172
172
|
|
|
173
|
+
## Local vs CI, at a glance
|
|
174
|
+
|
|
175
|
+
Everything below applies identically in both places — same package, same behavior — but a few things are worth knowing up front before you get to the details:
|
|
176
|
+
|
|
177
|
+
| | Locally | In CI |
|
|
178
|
+
| --- | --- | --- |
|
|
179
|
+
| **Healing** | Runs automatically on every `npx playwright test`, using the `.env` file from Step 2. | Runs automatically the same way — set the same variables as CI secrets/environment variables on your test job instead of a `.env` file (there's a real example in [Running `apply-heals` in CI](#running-apply-heals-in-ci-sharded-or-not)). |
|
|
180
|
+
| **Caching** ([below](#not-paying-for-the-same-heal-twice)) | Persists on disk across every run — a real, ongoing saving the longer you keep working. | Only helps *within* one run — most runners start from a fresh checkout each time, so it doesn't carry over between separate CI runs. `apply-heals` landing the real fix is what actually stops repeat AI calls in CI, not the cache. |
|
|
181
|
+
| **`apply-heals`** ([below](#making-a-heal-permanent-apply-heals)) | Run manually whenever you want, preview with `--dry-run`, review the diff yourself, commit when ready. | Runs automatically after your test job and opens a PR for review — nothing ever auto-commits to your branch. |
|
|
182
|
+
|
|
173
183
|
## What else it heals — no extra setup needed
|
|
174
184
|
|
|
175
185
|
Beyond a single broken `click`/`fill`/`getByRole` on the main page, all of this works automatically once you've done Steps 1–2:
|
|
@@ -178,6 +188,20 @@ Beyond a single broken `click`/`fill`/`getByRole` on the main page, all of this
|
|
|
178
188
|
- **Elements inside `<iframe>`s.** `page.frameLocator('#my-iframe')` and anything chained off it heals the same way, scoped correctly to the iframe's own document.
|
|
179
189
|
- **Most of the Playwright API surface**, not just clicks and fills — `check`, `selectOption`, `dragTo`, `dispatchEvent`, read methods like `textContent`/`getAttribute`/`isChecked`, `screenshot`, and more. Methods that can't be safely healed by guessing a replacement element (`dragTo`, `drop`) are still reported honestly on failure, they're just never silently retried with a different element.
|
|
180
190
|
|
|
191
|
+
## When there's no name to match: position-based healing
|
|
192
|
+
|
|
193
|
+
Sometimes the broken element has no useful identity of its own — a plain `<input>` with no name, no working placeholder, and a label that's visually right next to it but never actually linked (no `<label for>`, no `aria-labelledby`). A human finds it instantly by sight; matching purely on accessible name has nothing to grab onto.
|
|
194
|
+
|
|
195
|
+
For exactly these cases, `tamash-playwright` can also identify an element by its relationship to something nearby that *does* have an identity:
|
|
196
|
+
|
|
197
|
+
- **Near a label** — an unlabeled field sitting next to visible text (e.g. "Employee Id" as plain text beside an anonymous textbox).
|
|
198
|
+
- **Scoped to a container** — a name that exists but isn't unique page-wide (a "Yes" button that shows up in several dialogs), disambiguated by which named container it's actually inside.
|
|
199
|
+
- **Containing specific text** — the target is itself a container (a table row, say) identified by content inside it, not its own name.
|
|
200
|
+
|
|
201
|
+
You don't configure any of this — it's tried automatically, after normal name-based matching, only when nothing simpler applies. Same rules as everywhere else in this package: the AI only ever identifies *what* to look for and how it relates to nearby elements, never writes locator code directly, and a result only counts as found if it resolves to exactly one element — an ambiguous match is treated as "not found," never a guess.
|
|
202
|
+
|
|
203
|
+
The AI doesn't always get the strategy choice right on the first try — a model can mistake "text near an unlabeled field" for a real `<label>` association, especially smaller/cheaper models. If it suggests matching by label and that resolves to nothing, `tamash-playwright` automatically retries the same text as a "near" match before giving up — no extra AI call, since the action itself (`fill`, `check`, `selectOption`, …) already tells it what kind of control to look for.
|
|
204
|
+
|
|
181
205
|
## When text alone isn't enough: vision fallback
|
|
182
206
|
|
|
183
207
|
Sometimes an element has nothing useful to match on by text — an icon-only button with no label, or several visually distinct elements that all look identical in the accessibility tree. If your configured model supports image input (e.g. `gpt-4o`, `claude-haiku-4-5`, `gemini-2.0-flash`), `tamash-playwright` automatically falls back to a screenshot-based search after the normal text-based attempt fails — no separate setup, it just uses the same provider and API key from Step 2. Run `npx tamash-playwright doctor` to check whether your configured model is expected to support this.
|
|
@@ -190,7 +214,9 @@ Occasionally a locator heals correctly — the AI found the right element — bu
|
|
|
190
214
|
|
|
191
215
|
Once a locator heals successfully, `tamash-playwright` remembers the fix in `.tamash-playwright/heals.jsonl` — the same file `apply-heals` reads (see below). The next time that exact locator breaks the same way, it tries the previously-confirmed selector *first*, with no ARIA snapshot and no AI call. Only if that no longer works (the page changed again) does it fall through to a fresh snapshot-and-AI-call, exactly as before — so there's no correctness risk in trying the cached selector, only a cost/time saving when it still works.
|
|
192
216
|
|
|
193
|
-
|
|
217
|
+
**Locally**, this is a real, ongoing saving: `.tamash-playwright/heals.jsonl` lives on your disk and persists across every `npx playwright test` you run, for as long as you keep working on that checkout — the same broken locator only ever costs one AI call, no matter how many times you re-run your tests afterward.
|
|
218
|
+
|
|
219
|
+
**In CI, this only helps *within* one run**, not across separate ones — most CI runners start from a fresh checkout every time, so `.tamash-playwright/` (gitignored, never committed) doesn't carry over from yesterday's run to today's. What actually eliminates repeat AI calls in CI is `apply-heals` merging the real fix into your source — once that lands, the locator isn't broken anymore and healing never needs to run for it again. The cache still earns its keep within a single run, though: if the same broken locator shows up in several tests in one run (a shared Page Object method, say), only the first one pays for a fresh AI call — every other occurrence in that same run reuses it for free. You'll see it in the console line as `provider=cache` with no token count, instead of the real provider name:
|
|
194
220
|
|
|
195
221
|
```
|
|
196
222
|
[self-healer] tests/sampletest.spec.ts:13 — locator.fill "User Name Textbox (placeholder "xyz")" -> HEALED [provider=cache, vision=no, actionRecovery=no, suggested="role:textbox:Username"] — locator.fill: Timeout 8000ms exceeded.
|
|
@@ -243,7 +269,15 @@ A GitHub Actions example — each test job uploads its own log as an artifact; a
|
|
|
243
269
|
```yaml
|
|
244
270
|
jobs:
|
|
245
271
|
test:
|
|
246
|
-
# ...your existing test job(s), sharded or not
|
|
272
|
+
# ...your existing test job(s), sharded or not — the part that matters here is that healing
|
|
273
|
+
# needs the same variables from Step 2, set as CI secrets/environment variables instead of a
|
|
274
|
+
# local .env file (which won't exist on the runner and shouldn't be committed anyway).
|
|
275
|
+
runs-on: ubuntu-latest
|
|
276
|
+
env:
|
|
277
|
+
HEALER_ENABLED: true
|
|
278
|
+
HEALER_PROVIDER: ollama
|
|
279
|
+
OLLAMA_MODEL: gpt-oss:120b
|
|
280
|
+
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} # set via `gh secret set OLLAMA_API_KEY`
|
|
247
281
|
steps:
|
|
248
282
|
- run: npx playwright test
|
|
249
283
|
- uses: actions/upload-artifact@v4
|
|
@@ -257,6 +291,12 @@ jobs:
|
|
|
257
291
|
needs: test
|
|
258
292
|
if: ${{ !cancelled() && github.event_name == 'push' }} # not pull_request — see note below
|
|
259
293
|
runs-on: ubuntu-latest
|
|
294
|
+
# Two pushes close together would otherwise race on the same heal branch/PR — whichever
|
|
295
|
+
# finished last could clobber the other mid-update. Queues instead (never cancels an
|
|
296
|
+
# in-progress run) so each one always starts from a clean, fully-finished state.
|
|
297
|
+
concurrency:
|
|
298
|
+
group: apply-heals-${{ github.ref }}
|
|
299
|
+
cancel-in-progress: false
|
|
260
300
|
permissions:
|
|
261
301
|
contents: write
|
|
262
302
|
pull-requests: write
|
|
@@ -280,10 +320,22 @@ jobs:
|
|
|
280
320
|
# HEALER_ENABLED=false here is deliberate: this proves the *written* fix works standalone —
|
|
281
321
|
# leaving healing on could let a still-broken selector get silently re-healed at runtime
|
|
282
322
|
# again, reporting green without ever proving the applied source fix was actually correct.
|
|
323
|
+
#
|
|
324
|
+
# affectedTests (in the JSON report) lists exactly which tests actually exercised a fixed
|
|
325
|
+
# location this run, as `file:line` — scoping the re-run to just those instead of the whole
|
|
326
|
+
# suite. Falls back to a full `npx playwright test` when it's empty (older heals.jsonl
|
|
327
|
+
# entries from before this field existed, or test.info() wasn't available for some reason) —
|
|
328
|
+
# safer to over-verify than to silently under-check.
|
|
283
329
|
- name: Verify the healed selectors work on their own
|
|
284
330
|
id: verify
|
|
285
331
|
if: steps.check.outputs.changed == 'true'
|
|
286
|
-
run:
|
|
332
|
+
run: |
|
|
333
|
+
AFFECTED=$(jq -r '.affectedTests | join(" ")' .tamash-playwright/apply-heals-report.json)
|
|
334
|
+
if [ -n "$AFFECTED" ]; then
|
|
335
|
+
npx playwright test $AFFECTED
|
|
336
|
+
else
|
|
337
|
+
npx playwright test
|
|
338
|
+
fi
|
|
287
339
|
continue-on-error: true
|
|
288
340
|
env:
|
|
289
341
|
HEALER_ENABLED: false
|
|
@@ -317,14 +369,17 @@ jobs:
|
|
|
317
369
|
cat .tamash-playwright/apply-heals-report.md
|
|
318
370
|
} > .tamash-playwright/pr-body.md
|
|
319
371
|
|
|
372
|
+
# branch is suffixed with the ref so two different branches healing at the same time (if
|
|
373
|
+
# your trigger isn't restricted to a single branch) never fight over one physical heal
|
|
374
|
+
# branch — the concurrency group above only serializes runs on the *same* ref.
|
|
320
375
|
- name: Open PR with healed selectors
|
|
321
376
|
if: steps.check.outputs.changed == 'true'
|
|
322
377
|
uses: peter-evans/create-pull-request@v6
|
|
323
378
|
with:
|
|
324
379
|
commit-message: "fix: apply self-healed selectors from CI"
|
|
325
|
-
title: "Apply self-healed selectors (verification ${{ steps.verify.outcome == 'success' && 'passed' || 'FAILED' }})"
|
|
380
|
+
title: "Apply self-healed selectors on ${{ github.ref_name }} (verification ${{ steps.verify.outcome == 'success' && 'passed' || 'FAILED' }})"
|
|
326
381
|
body-path: .tamash-playwright/pr-body.md
|
|
327
|
-
branch: tamash-playwright/apply-heals
|
|
382
|
+
branch: tamash-playwright/apply-heals-${{ github.ref_name }}
|
|
328
383
|
delete-branch: true
|
|
329
384
|
|
|
330
385
|
- name: Fail the job if verification didn't pass
|
|
@@ -340,6 +395,8 @@ A few choices worth calling out:
|
|
|
340
395
|
- **The PR is opened either way**, verification passed or failed — a failed verification is still worth a human's attention (maybe the fix is right and something else was flaky); it just gets an honest label instead of a silent false-positive. The final step fails the *job* itself when verification fails, so CI status stays truthful even though the PR still exists for review.
|
|
341
396
|
- [`peter-evans/create-pull-request`](https://github.com/peter-evans/create-pull-request) is a no-op if there's nothing to commit, so the job is safe to run on every push — it only ever opens a PR when there's an actual fix to review, and reuses the same branch/PR on subsequent runs rather than piling up duplicates.
|
|
342
397
|
|
|
398
|
+
**Want a browsable dashboard, not just PR diffs and CI logs?** You can add a third job that publishes a combined report — the initial run, what got healed, and the post-fix verification, plus every past run archived and browsable — to GitHub Pages. See the "Publishing a healing dashboard to GitHub Pages" section of [usage.md](usage.md) for the full recipe.
|
|
399
|
+
|
|
343
400
|
## Checking what actually happened
|
|
344
401
|
|
|
345
402
|
Every healing attempt — whether it succeeded or not — shows up in Playwright's own HTML report (`npx playwright show-report`), no separate report to check:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserContext, Frame, FrameLocator, Locator, Page } from '@playwright/test';
|
|
2
2
|
type BindingKind = 'locator' | 'page' | 'frame' | 'frameLocator';
|
|
3
|
+
export declare function extractVariableName(sourceLocation: string): string | undefined;
|
|
3
4
|
export declare function bindTargetActions<T extends Locator | Page | Frame | FrameLocator>(target: T, kind?: BindingKind, description?: string, frameScope?: FrameLocator, selector?: string, callSite?: Error): T;
|
|
4
5
|
export declare function bindLocatorActions(locator: Locator): Locator;
|
|
5
6
|
export declare function bindPageActions(page: Page): Page;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locator.binding.d.ts","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"locator.binding.d.ts","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AA2E3F,KAAK,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,cAAc,CAAC;AA0EjE,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA4B9E;AAmDD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,YAAY,EAC/E,MAAM,EAAE,CAAC,EACT,IAAI,GAAE,WAAuB,EAC7B,WAAW,CAAC,EAAE,MAAM,EAKpB,UAAU,CAAC,EAAE,YAAY,EAMzB,QAAQ,CAAC,EAAE,MAAM,EAKjB,QAAQ,CAAC,EAAE,KAAK,GACf,CAAC,CAyIH;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAEhD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAEpD;AAYD,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAmCnE"}
|
|
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractVariableName = extractVariableName;
|
|
6
7
|
exports.bindTargetActions = bindTargetActions;
|
|
7
8
|
exports.bindLocatorActions = bindLocatorActions;
|
|
8
9
|
exports.bindPageActions = bindPageActions;
|
|
9
10
|
exports.bindFrameActions = bindFrameActions;
|
|
10
11
|
exports.bindContext = bindContext;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
13
|
const path_1 = __importDefault(require("path"));
|
|
12
14
|
const healer_1 = require("../healer");
|
|
13
15
|
const ACTIONS = [
|
|
@@ -136,6 +138,79 @@ function resolveCallerLocation(callSite) {
|
|
|
136
138
|
}
|
|
137
139
|
return undefined;
|
|
138
140
|
}
|
|
141
|
+
// Third-tier fallback for the AI's element description, behind an explicit `.describe()` and
|
|
142
|
+
// ahead of the raw selector text: `this.txtUserName = page.getByPlaceholder(...)` or
|
|
143
|
+
// `let txtUserName = page.locator(...)` — most teams already name locator variables after what
|
|
144
|
+
// the element is, even when they never call `.describe()`. Reads the exact source line
|
|
145
|
+
// `sourceLocation` (from resolveCallerLocation, so already resolved by the time this runs — see
|
|
146
|
+
// its own comment for why this is only ever done on a genuine failure, never per locator) and
|
|
147
|
+
// takes whatever identifier sits immediately before the line's first real `=` — keyword-agnostic
|
|
148
|
+
// on purpose, so `this.x =`, `let x =`, `const x: Locator =`, and a bare `x =` reassignment all
|
|
149
|
+
// resolve the same way, without hardcoding which declaration style a given team uses.
|
|
150
|
+
function extractVariableName(sourceLocation) {
|
|
151
|
+
const separatorIndex = sourceLocation.lastIndexOf(':');
|
|
152
|
+
if (separatorIndex === -1) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const filePath = sourceLocation.slice(0, separatorIndex);
|
|
156
|
+
const lineNumber = Number(sourceLocation.slice(separatorIndex + 1));
|
|
157
|
+
if (!Number.isFinite(lineNumber)) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const lines = fs_1.default.readFileSync(path_1.default.resolve(process.cwd(), filePath), 'utf-8').split('\n');
|
|
162
|
+
const line = lines[lineNumber - 1];
|
|
163
|
+
if (!line) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const eqIndex = findAssignmentEquals(line);
|
|
167
|
+
if (eqIndex === -1) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const match = line.slice(0, eqIndex).match(/(\w+)\s*(?::\s*[\w.<>[\]]+\s*)?$/);
|
|
171
|
+
return match?.[1];
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Same best-effort stance as the rest of this file — a source-read failure (file moved,
|
|
175
|
+
// permissions, whatever) must never break healing, just fall through to the next tier.
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// The naive `line.search(/=(?!=)/)` this replaced had a real false-positive: a CSS attribute
|
|
180
|
+
// selector like `input[name="wrongname"]` contains a completely unrelated `=` inside the string
|
|
181
|
+
// literal, which got misread as an assignment and produced garbage ("name" extracted instead of
|
|
182
|
+
// nothing). String-aware so any `=` inside `'...'`/`"..."`/`` `...` `` is correctly ignored, and
|
|
183
|
+
// also skips `==`, `===`, `!=`, `!==`, `<=`, `>=`, and `=>` — none of those are an assignment.
|
|
184
|
+
function findAssignmentEquals(line) {
|
|
185
|
+
let inString = null;
|
|
186
|
+
for (let i = 0; i < line.length; i++) {
|
|
187
|
+
const ch = line[i];
|
|
188
|
+
if (inString) {
|
|
189
|
+
if (ch === '\\') {
|
|
190
|
+
i++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === inString) {
|
|
194
|
+
inString = null;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
199
|
+
inString = ch;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (ch !== '=') {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const prev = line[i - 1];
|
|
206
|
+
const next = line[i + 1];
|
|
207
|
+
if (next === '=' || prev === '=' || prev === '!' || prev === '<' || prev === '>' || next === '>') {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
return i;
|
|
211
|
+
}
|
|
212
|
+
return -1;
|
|
213
|
+
}
|
|
139
214
|
// Marks every proxy this module creates so a target that's already wrapped is never wrapped
|
|
140
215
|
// again. This matters concretely for the primary test `page`: Playwright's own built-in `page`
|
|
141
216
|
// fixture is implemented as `context.newPage()`, and since bindContext() (below) already wraps
|
|
@@ -237,14 +312,30 @@ callSite) {
|
|
|
237
312
|
return await target[prop](...callArgs);
|
|
238
313
|
}
|
|
239
314
|
catch (error) {
|
|
315
|
+
// Only resolved now, on a genuine failure — not at every locator creation. See
|
|
316
|
+
// resolveCallerLocation's own comment for why. Computed before effectiveDescription
|
|
317
|
+
// below since extractVariableName needs it too.
|
|
318
|
+
const resolvedSourceLocation = callSite ? resolveCallerLocation(callSite) : undefined;
|
|
240
319
|
// description defaults to the selector text at the factory call, so the two are
|
|
241
|
-
// identical until `.describe()` overrides description —
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
320
|
+
// identical until `.describe()` overrides description — that equality is exactly
|
|
321
|
+
// how an explicit `.describe()` is told apart from "never called". Priority for what
|
|
322
|
+
// the AI actually sees: explicit .describe() > inferred variable name > raw selector
|
|
323
|
+
// text, and — deliberately — never combined with the broken selector once either of
|
|
324
|
+
// the first two is available. Combining used to be the default (showing both), but
|
|
325
|
+
// real testing showed this backfires: a model can pattern-match on the old,
|
|
326
|
+
// already-broken value in the description and just parrot it back as its own
|
|
327
|
+
// "suggestion" instead of reasoning fresh from the ARIA snapshot — demonstrated
|
|
328
|
+
// concretely with a smaller model that otherwise picked the correct strategy when
|
|
329
|
+
// given the same description without the selector appended. The AI should find the
|
|
330
|
+
// element from context (the snapshot), not from a hint about what used to be there.
|
|
331
|
+
let effectiveDescription;
|
|
332
|
+
if (description && selector && description !== selector) {
|
|
333
|
+
effectiveDescription = description;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
const variableName = resolvedSourceLocation ? extractVariableName(resolvedSourceLocation) : undefined;
|
|
337
|
+
effectiveDescription = variableName ?? description ?? selector ?? `${kind} action ${String(prop)}`;
|
|
338
|
+
}
|
|
248
339
|
const healing = await (0, healer_1.healActionFailure)({
|
|
249
340
|
action: String(prop),
|
|
250
341
|
kind,
|
|
@@ -255,9 +346,7 @@ callSite) {
|
|
|
255
346
|
target: target,
|
|
256
347
|
callArgs,
|
|
257
348
|
frameScope,
|
|
258
|
-
|
|
259
|
-
// resolveCallerLocation's own comment for why.
|
|
260
|
-
sourceLocation: callSite ? resolveCallerLocation(callSite) : undefined,
|
|
349
|
+
sourceLocation: resolvedSourceLocation,
|
|
261
350
|
});
|
|
262
351
|
// NOTE: most Playwright actions (click/fill/check/hover/...) resolve to `undefined`
|
|
263
352
|
// on success, so `healing.recovered` alone is the correct success signal here —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locator.binding.js","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"locator.binding.js","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AAExB,sCAA8C;AAE9C,MAAM,OAAO,GAAG;IACd,OAAO;IACP,UAAU;IACV,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,SAAS;IACT,cAAc;IACd,eAAe;IACf,QAAQ;IACR,8FAA8F;IAC9F,8FAA8F;IAC9F,+FAA+F;IAC/F,+FAA+F;IAC/F,mEAAmE;IACnE,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,mBAAmB;IACnB,YAAY;IACZ,wBAAwB;IACxB,eAAe;IACf,SAAS;IACT,2FAA2F;IAC3F,4FAA4F;IAC5F,2FAA2F;IAC3F,8FAA8F;IAC9F,wFAAwF;IACxF,uEAAuE;IACvE,aAAa;IACb,WAAW;IACX,WAAW;IACX,cAAc;IACd,YAAY;IACZ,WAAW;IACX,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,eAAe;IACf,cAAc;CACN,CAAC;AAEX,MAAM,uBAAuB,GAAG;IAC9B,SAAS;IACT,WAAW;IACX,YAAY;IACZ,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,YAAY;IACZ,aAAa;CACL,CAAC;AAEX,gGAAgG;AAChG,iGAAiG;AACjG,4DAA4D;AAC5D,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAE5C,qFAAqF;AACrF,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,8BAA8B;AAC9B,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAU,CAAC;AAI3F,SAAS,mBAAmB,CAAC,IAAY,EAAE,IAAe;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAE7B,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,GAAI,MAAyC,EAAE,IAAI,CAAC;YAC9D,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACpF,CAAC;QACD,KAAK,YAAY;YACf,OAAO,UAAU,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACpC,KAAK,kBAAkB;YACrB,OAAO,gBAAgB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QAC1C,KAAK,WAAW;YACd,OAAO,SAAS,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACnC,KAAK,cAAc;YACjB,OAAO,aAAa,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACvC,KAAK,YAAY;YACf,OAAO,UAAU,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACpC,KAAK,aAAa;YAChB,OAAO,WAAW,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACrC,KAAK,SAAS;YACZ,OAAO,aAAa,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACvC,KAAK,cAAc;YACjB,OAAO,WAAW,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACrC;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,gGAAgG;AAChG,6FAA6F;AAC7F,8FAA8F;AAC9F,iGAAiG;AACjG,8BAA8B;AAC9B,EAAE;AACF,kGAAkG;AAClG,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,+FAA+F;AAC/F,qDAAqD;AACrD,SAAS,qBAAqB,CAAC,QAAe;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACrC,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACzE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC;QACvC,OAAO,GAAG,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,IAAI,UAAU,EAAE,CAAC;IACnE,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,6FAA6F;AAC7F,qFAAqF;AACrF,+FAA+F;AAC/F,uFAAuF;AACvF,gGAAgG;AAChG,8FAA8F;AAC9F,iGAAiG;AACjG,gGAAgG;AAChG,sFAAsF;AACtF,6BAAoC,cAAsB;IACxD,MAAM,cAAc,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAE,CAAC,YAAY,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1F,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;YACnB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC/E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,wFAAwF;QACxF,uFAAuF;QACvF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,6FAA6F;AAC7F,gGAAgG;AAChG,gGAAgG;AAChG,iGAAiG;AACjG,+FAA+F;AAC/F,SAAS,oBAAoB,CAAC,IAAY;IACxC,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACpB,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAC3C,QAAQ,GAAG,EAAE,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjG,SAAS;QACX,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,4FAA4F;AAC5F,+FAA+F;AAC/F,+FAA+F;AAC/F,oFAAoF;AACpF,+FAA+F;AAC/F,gGAAgG;AAChG,gGAAgG;AAChG,4FAA4F;AAC5F,4FAA4F;AAC5F,0FAA0F;AAC1F,kCAAkC;AAClC,MAAM,YAAY,GAAG,IAAI,OAAO,EAAU,CAAC;AAE3C,2BACE,MAAS,EACT,IAAI,GAAgB,SAAS,EAC7B,WAAoB;AACpB,2FAA2F;AAC3F,wFAAwF;AACxF,8FAA8F;AAC9F,oFAAoF;AACpF,UAAyB;AACzB,wFAAwF;AACxF,4FAA4F;AAC5F,gGAAgG;AAChG,gGAAgG;AAChG,oCAAoC;AACpC,QAAiB;AACjB,wFAAwF;AACxF,0FAA0F;AAC1F,+FAA+F;AAC/F,+FAA+F;AAC/F,QAAgB;IAEhB,IAAI,YAAY,CAAC,GAAG,CAAC,MAA2B,CAAC,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE;QAC9B,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,YAAY,GAAG,MAA4C,CAAC;gBAElE,IAAI,IAAI,KAAK,oBAAoB,EAAE,CAAC;oBAClC,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;wBAC5B,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;wBACnC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;4BAClC,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,GAAG,CAAC,CAAC;wBACtE,CAAC;wBACD,MAAM,iBAAiB,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAiB,CAAC;wBACtE,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;wBACtD,qFAAqF;wBACrF,OAAO,iBAAiB,CAAC,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;oBAC/G,CAAC,CAAC;gBACJ,CAAC;gBAED,yFAAyF;gBACzF,uFAAuF;gBACvF,uFAAuF;gBACvF,oFAAoF;gBACpF,0BAA0B;gBAC1B,IAAI,uBAAuB,CAAC,QAAQ,CAAC,IAAgD,CAAC,EAAE,CAAC;oBACvF,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;wBAC5B,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;wBACnC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;4BAClC,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,IAAI,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;wBAC7F,CAAC;wBACD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAY,CAAC;wBACvD,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;wBACpD,gFAAgF;wBAChF,uEAAuE;wBACvE,MAAM,WAAW,GAAG,IAAI,KAAK,EAAE,CAAC;wBAChC,kFAAkF;wBAClF,iFAAiF;wBACjF,sDAAsD;wBACtD,OAAO,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;oBAClG,CAAC,CAAC;gBACJ,CAAC;gBAED,IAAI,IAAI,KAAK,SAAS,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAsC,CAAC,EAAE,CAAC;oBACzF,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;wBAC5B,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;wBACvC,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;4BACtC,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,CAAC,IAAI,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;wBAC5F,CAAC;wBACD,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;wBAC/C,MAAM,eAAe,GAAG,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;wBAClF,oFAAoF;wBACpF,qFAAqF;wBACrF,oFAAoF;wBACpF,QAAQ;wBACR,OAAO,iBAAiB,CAAC,MAAiB,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBAC1G,CAAC,CAAC;gBACJ,CAAC;gBAED,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAgC,CAAC,EAAE,CAAC;oBACvD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;oBAClC,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;wBACjC,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,IAAI,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;oBACpF,CAAC;oBAED,OAAO,KAAK,EAAE,GAAG,QAAmB,EAAE,EAAE;wBACtC,IAAI,CAAC;4BACH,wEAAwE;4BACxE,4EAA4E;4BAC5E,oFAAoF;4BACpF,iFAAiF;4BACjF,qFAAqF;4BACrF,gEAAgE;4BAChE,OAAO,MAAO,MAAkF,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;wBACtH,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,+EAA+E;4BAC/E,oFAAoF;4BACpF,gDAAgD;4BAChD,MAAM,sBAAsB,GAAG,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;4BAEtF,gFAAgF;4BAChF,iFAAiF;4BACjF,qFAAqF;4BACrF,qFAAqF;4BACrF,oFAAoF;4BACpF,mFAAmF;4BACnF,4EAA4E;4BAC5E,6EAA6E;4BAC7E,gFAAgF;4BAChF,kFAAkF;4BAClF,mFAAmF;4BACnF,oFAAoF;4BACpF,IAAI,oBAA4B,CAAC;4BACjC,IAAI,WAAW,IAAI,QAAQ,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;gCACxD,oBAAoB,GAAG,WAAW,CAAC;4BACrC,CAAC;iCAAM,CAAC;gCACN,MAAM,YAAY,GAAG,sBAAsB,CAAC,CAAC,CAAC,mBAAmB,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gCACtG,oBAAoB,GAAG,YAAY,IAAI,WAAW,IAAI,QAAQ,IAAI,GAAG,IAAI,WAAW,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;4BACrG,CAAC;4BAED,MAAM,OAAO,GAAG,MAAM,IAAA,0BAAiB,EAAC;gCACtC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;gCACpB,IAAI;gCACJ,WAAW,EAAE,oBAAoB;gCACjC,KAAK;gCACL,8EAA8E;gCAC9E,kEAAkE;gCAClE,MAAM,EAAE,MAAgC;gCACxC,QAAQ;gCACR,UAAU;gCACV,cAAc,EAAE,sBAAsB;6BACvC,CAAC,CAAC;4BAEH,oFAAoF;4BACpF,gFAAgF;4BAChF,iFAAiF;4BACjF,+CAA+C;4BAC/C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gCACtB,OAAO,OAAO,CAAC,MAAM,CAAC;4BACxB,CAAC;4BAED,MAAM,KAAK,CAAC;wBACd,CAAC;oBACH,CAAC,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAClD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAClE,CAAC;KACF,CAAM,CAAC;IAER,YAAY,CAAC,GAAG,CAAC,KAA0B,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4BAAmC,OAAgB;IACjD,OAAO,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED,yBAAgC,IAAU;IACxC,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,0BAAiC,KAAY;IAC3C,OAAO,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,8FAA8F;AAC9F,8FAA8F;AAC9F,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,kGAAkG;AAClG,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,4FAA4F;AAC5F,qBAA4B,OAAuB;IACjD,IAAI,YAAY,CAAC,GAAG,CAAC,OAA4B,CAAC,EAAE,CAAC;QACnD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE;QAC/B,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,OAAO,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;oBAClC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAI,IAA8C,CAAC,CAAC;oBACtF,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;gBAC/B,CAAC,CAAC;YACJ,CAAC;YAED,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACrC,OAAO,CAAC,KAAa,EAAE,OAAqC,EAAE,EAAE;oBAC9D,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;wBAC1C,MAAM,cAAc,GAAG,CAAC,IAAU,EAAE,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;wBACtE,OAAQ,MAAM,CAAC,IAAI,CAA4D,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;oBACzG,CAAC;oBACD,OAAQ,MAAM,CAAC,IAAI,CAAoE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC1G,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAClD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAClE,CAAC;KACF,CAAC,CAAC;IAEH,YAAY,CAAC,GAAG,CAAC,KAA0B,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/cli/applyHeals.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type FixOutcome = {
|
|
|
10
10
|
export declare function planFixes(cwd?: string, rawEntries?: HealLogEntry[]): {
|
|
11
11
|
outcomes: FixOutcome[];
|
|
12
12
|
fileContents: Map<string, string>;
|
|
13
|
+
affectedTests: string[];
|
|
13
14
|
};
|
|
14
15
|
export declare function runApplyHeals(args?: string[]): Promise<void>;
|
|
15
16
|
//# sourceMappingURL=applyHeals.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyHeals.d.ts","sourceRoot":"","sources":["../../src/cli/applyHeals.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwF,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"applyHeals.d.ts","sourceRoot":"","sources":["../../src/cli/applyHeals.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwF,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AA+K7I,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAqBF,wBAAgB,SAAS,CAAC,GAAG,GAAE,MAAsB,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,GAAG;IAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA2F1K;AAmFD,wBAAsB,aAAa,CAAC,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAoEtE"}
|
package/dist/cli/applyHeals.js
CHANGED
|
@@ -26,6 +26,24 @@ function generateReplacementCall(suggestion) {
|
|
|
26
26
|
return `getByPlaceholder(${JSON.stringify(suggestion.placeholder)})`;
|
|
27
27
|
case 'css':
|
|
28
28
|
return `locator(${JSON.stringify(suggestion.css)})`;
|
|
29
|
+
case 'near': {
|
|
30
|
+
// parentLevels is set by resolveNearLocator (healer/index.ts) at heal time, from whichever
|
|
31
|
+
// candidate in its ladder actually resolved to exactly one live element — this file has no
|
|
32
|
+
// live page to re-test against, so it reuses that proven depth rather than guessing. Falls
|
|
33
|
+
// back to 1 only for a malformed/legacy entry that somehow lacks it, since 1 level is the
|
|
34
|
+
// more common real-world shape (see resolveNearLocator's own comment).
|
|
35
|
+
const xpath = suggestion.parentLevels === 2 ? '../..' : '..';
|
|
36
|
+
return `getByText(${JSON.stringify(suggestion.anchorText)}).locator(${JSON.stringify(`xpath=${xpath}`)}).getByRole(${JSON.stringify(suggestion.role)})`;
|
|
37
|
+
}
|
|
38
|
+
case 'scoped': {
|
|
39
|
+
const containerArgs = suggestion.containerName ? `${JSON.stringify(suggestion.containerRole)}, { name: ${JSON.stringify(suggestion.containerName)} }` : JSON.stringify(suggestion.containerRole);
|
|
40
|
+
const targetArgs = suggestion.name ? `${JSON.stringify(suggestion.role)}, { name: ${JSON.stringify(suggestion.name)} }` : JSON.stringify(suggestion.role);
|
|
41
|
+
return `getByRole(${containerArgs}).getByRole(${targetArgs})`;
|
|
42
|
+
}
|
|
43
|
+
case 'containing':
|
|
44
|
+
// hasText, not has: <nested locator> — see resolveContainingLocator's comment for why: no
|
|
45
|
+
// safe way to know whether the receiver here should be `page.` or `this.page.` etc.
|
|
46
|
+
return `getByRole(${JSON.stringify(suggestion.role)}).filter({ hasText: ${JSON.stringify(suggestion.anchorText)} })`;
|
|
29
47
|
default:
|
|
30
48
|
return undefined;
|
|
31
49
|
}
|
|
@@ -52,21 +70,11 @@ function isRegexLiteralStart(content, index) {
|
|
|
52
70
|
}
|
|
53
71
|
return !/[\w$)\]`'"]/.test(content[j]);
|
|
54
72
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let lineStartOffset = 0;
|
|
61
|
-
for (let i = 0; i < targetLine - 1; i++) {
|
|
62
|
-
lineStartOffset += lines[i].length + 1; // +1 for the '\n' each split() consumed
|
|
63
|
-
}
|
|
64
|
-
const match = lines[targetLine - 1].match(FACTORY_METHOD_PATTERN);
|
|
65
|
-
if (!match || match.index === undefined) {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
const dotIndex = lineStartOffset + match.index;
|
|
69
|
-
const openParenIndex = dotIndex + match[0].length - 1;
|
|
73
|
+
// Balances parens starting from `openParenIndex` (the `(` itself) and returns the index just past
|
|
74
|
+
// its matching `)`, or undefined if the argument list contains something unbalanced or otherwise
|
|
75
|
+
// unsafe to track. Shared by findFactoryCallOnLine below for both the first call it finds and any
|
|
76
|
+
// chained continuations after it, so both use identical string/regex-literal-aware logic.
|
|
77
|
+
function scanBalancedParens(content, openParenIndex) {
|
|
70
78
|
let depth = 1;
|
|
71
79
|
let i = openParenIndex + 1;
|
|
72
80
|
// Tracks whichever of `"`, `'`, `` ` ``, or `/` (regex) we're currently inside — parens found
|
|
@@ -98,12 +106,63 @@ function findFactoryCallOnLine(content, targetLine) {
|
|
|
98
106
|
}
|
|
99
107
|
i++;
|
|
100
108
|
}
|
|
101
|
-
|
|
109
|
+
return depth === 0 ? i : undefined;
|
|
110
|
+
}
|
|
111
|
+
// Matches specifically the `near` strategy's chain shape — `.locator("xpath=...")` immediately
|
|
112
|
+
// after the first call — so it can be recognized and consumed whole on a later re-heal, instead of
|
|
113
|
+
// only replacing the first call and leaving `.locator("xpath=...").getByRole(...)` dangling after
|
|
114
|
+
// whatever replaces it.
|
|
115
|
+
//
|
|
116
|
+
// Deliberately NOT done for `scoped` (`.getByRole(...).getByRole(...)`) or `containing`
|
|
117
|
+
// (`.getByRole(...).filter({hasText: ...})`): both of those continuation shapes are completely
|
|
118
|
+
// plausible things a person would write by hand for their own reasons — filter({hasText}) chained
|
|
119
|
+
// onto a role lookup is literally Playwright's own documented recommended pattern (see
|
|
120
|
+
// other-locators.md). Auto-consuming either on an unrelated heal risks silently discarding a
|
|
121
|
+
// user's own real filtering logic, which is a worse failure mode than the narrower one this only
|
|
122
|
+
// partially fixes. `xpath=...` is a safe, narrow exception — implausible for anyone to have
|
|
123
|
+
// written by hand outside of this exact generated shape.
|
|
124
|
+
const NEAR_XPATH_CONTINUATION_PATTERN = /^\.locator\(\s*(["'`])xpath=/;
|
|
125
|
+
const ROLE_CONTINUATION_PATTERN = /^\.getByRole\(/;
|
|
126
|
+
function findFactoryCallOnLine(content, targetLine) {
|
|
127
|
+
const lines = content.split('\n');
|
|
128
|
+
if (targetLine < 1 || targetLine > lines.length) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
let lineStartOffset = 0;
|
|
132
|
+
for (let i = 0; i < targetLine - 1; i++) {
|
|
133
|
+
lineStartOffset += lines[i].length + 1; // +1 for the '\n' each split() consumed
|
|
134
|
+
}
|
|
135
|
+
const match = lines[targetLine - 1].match(FACTORY_METHOD_PATTERN);
|
|
136
|
+
if (!match || match.index === undefined) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const dotIndex = lineStartOffset + match.index;
|
|
140
|
+
const openParenIndex = dotIndex + match[0].length - 1;
|
|
141
|
+
let callEnd = scanBalancedParens(content, openParenIndex);
|
|
142
|
+
if (callEnd === undefined) {
|
|
102
143
|
// Unbalanced, or the argument list contains something this scanner can't safely track — bail
|
|
103
144
|
// out rather than guess at a range that might be wrong.
|
|
104
145
|
return undefined;
|
|
105
146
|
}
|
|
106
|
-
|
|
147
|
+
// See NEAR_XPATH_CONTINUATION_PATTERN's comment for why only this specific shape is safe to
|
|
148
|
+
// auto-consume — `.locator("xpath=...")` immediately followed by `.getByRole(...)`, exactly
|
|
149
|
+
// `near`'s own generated chain and nothing broader.
|
|
150
|
+
const xpathContinuation = content.slice(callEnd).match(NEAR_XPATH_CONTINUATION_PATTERN);
|
|
151
|
+
if (xpathContinuation) {
|
|
152
|
+
const xpathOpenParenIndex = callEnd + xpathContinuation[0].indexOf('(');
|
|
153
|
+
const afterXpath = scanBalancedParens(content, xpathOpenParenIndex);
|
|
154
|
+
if (afterXpath !== undefined) {
|
|
155
|
+
const roleContinuation = content.slice(afterXpath).match(ROLE_CONTINUATION_PATTERN);
|
|
156
|
+
if (roleContinuation) {
|
|
157
|
+
const roleOpenParenIndex = afterXpath + roleContinuation[0].length - 1;
|
|
158
|
+
const afterRole = scanBalancedParens(content, roleOpenParenIndex);
|
|
159
|
+
if (afterRole !== undefined) {
|
|
160
|
+
callEnd = afterRole;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { dotIndex, callEnd };
|
|
107
166
|
}
|
|
108
167
|
// Only the most recent entry per file:line survives — later runs supersede earlier ones, and
|
|
109
168
|
// there's nothing useful about applying the same location twice.
|
|
@@ -124,7 +183,8 @@ function latestPerLocation(entries) {
|
|
|
124
183
|
// any subsequent chained calls like `.describe(...)` are left completely untouched since they sit
|
|
125
184
|
// outside the replaced range entirely.
|
|
126
185
|
function planFixes(cwd = process.cwd(), rawEntries) {
|
|
127
|
-
const
|
|
186
|
+
const allEntries = rawEntries ?? (0, heal_log_1.readHealLog)(cwd);
|
|
187
|
+
const entries = latestPerLocation(allEntries);
|
|
128
188
|
const byFile = new Map();
|
|
129
189
|
for (const entry of entries) {
|
|
130
190
|
const list = byFile.get(entry.file) ?? [];
|
|
@@ -172,7 +232,36 @@ function planFixes(cwd = process.cwd(), rawEntries) {
|
|
|
172
232
|
fileContents.set(fullPath, content);
|
|
173
233
|
}
|
|
174
234
|
}
|
|
175
|
-
|
|
235
|
+
// Every test observed exercising a location that actually got fixed — not just the entry
|
|
236
|
+
// latestPerLocation picked for its suggestion text. A location can be exercised by many tests in
|
|
237
|
+
// one run; only one of them pays for the fresh AI call (see usedCache in healer/index.ts), but
|
|
238
|
+
// every one of them needs re-verifying once the line changes under them. Each entry becomes
|
|
239
|
+
// "<testFile>:<testLine>" — ready to hand straight to `npx playwright test` to run exactly that
|
|
240
|
+
// test, not the whole file it lives in — unless some occurrence for that file couldn't be pinned
|
|
241
|
+
// to a line (an older log entry, or test.info() unavailable), in which case the whole file is
|
|
242
|
+
// used as a safe fallback rather than silently missing a test that needs re-verifying.
|
|
243
|
+
const appliedLocations = new Set(outcomes.filter((o) => o.applied).map((o) => `${o.file}:${o.line}`));
|
|
244
|
+
const linesByTestFile = new Map();
|
|
245
|
+
for (const entry of allEntries) {
|
|
246
|
+
if (!entry.testFile || !appliedLocations.has(`${entry.file}:${entry.line}`)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const lines = linesByTestFile.get(entry.testFile) ?? new Set();
|
|
250
|
+
lines.add(entry.testLine);
|
|
251
|
+
linesByTestFile.set(entry.testFile, lines);
|
|
252
|
+
}
|
|
253
|
+
const affectedTests = [];
|
|
254
|
+
for (const [file, lines] of linesByTestFile) {
|
|
255
|
+
if (lines.has(undefined)) {
|
|
256
|
+
affectedTests.push(file);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
for (const line of [...lines].sort((a, b) => a - b)) {
|
|
260
|
+
affectedTests.push(`${file}:${line}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
affectedTests.sort();
|
|
264
|
+
return { outcomes, fileContents, affectedTests };
|
|
176
265
|
}
|
|
177
266
|
const REPORT_DIR = '.tamash-playwright';
|
|
178
267
|
// Filesystem-safe version of an ISO timestamp (`:` isn't valid in a Windows filename) — shared
|
|
@@ -191,7 +280,7 @@ function timestampLabel() {
|
|
|
191
280
|
// always "the latest run"), and once archived under history/<timestamp>.* — otherwise a second
|
|
192
281
|
// apply-heals run silently erases the first's report with no trace, and since .tamash-playwright/
|
|
193
282
|
// is gitignored, that's not recoverable from git either.
|
|
194
|
-
function writeReports(outcomes, dryRun, cwd, label) {
|
|
283
|
+
function writeReports(outcomes, affectedTests, dryRun, cwd, label) {
|
|
195
284
|
const dir = path_1.default.join(cwd, REPORT_DIR);
|
|
196
285
|
const historyDir = path_1.default.join(dir, 'history');
|
|
197
286
|
if (!fs_1.default.existsSync(historyDir)) {
|
|
@@ -200,7 +289,7 @@ function writeReports(outcomes, dryRun, cwd, label) {
|
|
|
200
289
|
const applied = outcomes.filter((o) => o.applied);
|
|
201
290
|
const skipped = outcomes.filter((o) => !o.applied);
|
|
202
291
|
const timestamp = new Date().toISOString();
|
|
203
|
-
const jsonContent = JSON.stringify({ timestamp, dryRun, applied: applied.length, skipped: skipped.length, fixes: outcomes }, null, 2);
|
|
292
|
+
const jsonContent = JSON.stringify({ timestamp, dryRun, applied: applied.length, skipped: skipped.length, fixes: outcomes, affectedTests }, null, 2);
|
|
204
293
|
const jsonPath = path_1.default.join(dir, 'apply-heals-report.json');
|
|
205
294
|
fs_1.default.writeFileSync(jsonPath, jsonContent, 'utf-8');
|
|
206
295
|
fs_1.default.writeFileSync(path_1.default.join(historyDir, `${label}.apply-heals-report.json`), jsonContent, 'utf-8');
|
|
@@ -223,6 +312,13 @@ function writeReports(outcomes, dryRun, cwd, label) {
|
|
|
223
312
|
}
|
|
224
313
|
lines.push('');
|
|
225
314
|
}
|
|
315
|
+
if (affectedTests.length > 0) {
|
|
316
|
+
lines.push('## Tests to re-verify', '', 'Every test observed exercising a location that got fixed above — pass these straight to `npx playwright test` to re-run exactly them (not the whole suite, and not even the whole file unless the exact test couldn\'t be pinned to a line) with healing disabled, to prove the fix works standalone:', '');
|
|
317
|
+
for (const test of affectedTests) {
|
|
318
|
+
lines.push(`- \`${test}\``);
|
|
319
|
+
}
|
|
320
|
+
lines.push('');
|
|
321
|
+
}
|
|
226
322
|
const markdownContent = lines.join('\n');
|
|
227
323
|
const markdownPath = path_1.default.join(dir, 'apply-heals-report.md');
|
|
228
324
|
fs_1.default.writeFileSync(markdownPath, markdownContent, 'utf-8');
|
|
@@ -242,7 +338,7 @@ async function runApplyHeals(args = []) {
|
|
|
242
338
|
// --logs-dir supports the sharded-CI pattern: a merge job downloads every shard's heals.jsonl
|
|
243
339
|
// artifact into one directory and points here instead of relying on a single local log file.
|
|
244
340
|
const rawEntries = logsDir ? (0, heal_log_1.readHealLogsFromDir)(path_1.default.resolve(process.cwd(), logsDir)) : undefined;
|
|
245
|
-
const { outcomes, fileContents } = planFixes(process.cwd(), rawEntries);
|
|
341
|
+
const { outcomes, fileContents, affectedTests } = planFixes(process.cwd(), rawEntries);
|
|
246
342
|
if (outcomes.length === 0) {
|
|
247
343
|
console.log(logsDir ? `No eligible heals found under ${logsDir}.` : 'No eligible heals found in .tamash-playwright/heals.jsonl.');
|
|
248
344
|
console.log('(Only text/ARIA-based heals with a known source location are eligible — run your tests first.)');
|
|
@@ -260,9 +356,12 @@ async function runApplyHeals(args = []) {
|
|
|
260
356
|
}
|
|
261
357
|
console.log('');
|
|
262
358
|
const label = timestampLabel();
|
|
263
|
-
const { markdownPath } = writeReports(outcomes, dryRun, process.cwd(), label);
|
|
359
|
+
const { markdownPath } = writeReports(outcomes, affectedTests, dryRun, process.cwd(), label);
|
|
264
360
|
console.log(`Report written to ${path_1.default.relative(process.cwd(), markdownPath)} (and the matching .json).`);
|
|
265
361
|
console.log(`Archived under .tamash-playwright/history/${label}.* — kept even after the next run.`);
|
|
362
|
+
console.log(affectedTests.length > 0
|
|
363
|
+
? `Tests to re-verify: ${affectedTests.join(' ')}`
|
|
364
|
+
: 'Tests to re-verify: (none recorded — pre-existing heals.jsonl entries or no test.info() available; re-run the full suite to be safe.)');
|
|
266
365
|
if (dryRun) {
|
|
267
366
|
console.log(`${applied.length} fix(es) would be applied, ${skipped.length} skipped. Re-run without --dry-run to write them.`);
|
|
268
367
|
return;
|