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 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
- This persists across runs, not just within one since it reads from disk rather than an in-memory cache, it also helps the case that costs the most in practice: the same broken selector otherwise getting healed by AI on every single CI run until someone gets around to running `apply-heals`. You'll see it in the console line as `provider=cache` with no token count, instead of the real provider name:
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: npx playwright test
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":"AACA,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;AA8EjE,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,CA2HH;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"}
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 — only combine them once
242
- // they've actually diverged, so a plain (never-described) locator still reports a
243
- // single clean "selector \"#foo\"" instead of a redundant "selector \"#foo\"
244
- // (selector \"#foo\")".
245
- const effectiveDescription = description && selector && description !== selector
246
- ? `${description} (${selector})`
247
- : (description ?? selector ?? `${kind} action ${String(prop)}`);
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
- // Only resolved now, on a genuine failure — not at every locator creation. See
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":";;;;;;;;;;AAAA,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,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,gFAAgF;4BAChF,+EAA+E;4BAC/E,kFAAkF;4BAClF,6EAA6E;4BAC7E,wBAAwB;4BACxB,MAAM,oBAAoB,GAAG,WAAW,IAAI,QAAQ,IAAI,WAAW,KAAK,QAAQ;gCAC9E,CAAC,CAAC,GAAG,WAAW,KAAK,QAAQ,GAAG;gCAChC,CAAC,CAAC,CAAC,WAAW,IAAI,QAAQ,IAAI,GAAG,IAAI,WAAW,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;4BAElE,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,+EAA+E;gCAC/E,+CAA+C;gCAC/C,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;6BACvE,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"}
1
+ {"version":3,"file":"locator.binding.js","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"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"}
@@ -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;AAiH7I,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,CAAA;CAAE,CA2DjJ;AA2ED,wBAAsB,aAAa,CAAC,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DtE"}
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"}
@@ -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
- function findFactoryCallOnLine(content, targetLine) {
56
- const lines = content.split('\n');
57
- if (targetLine < 1 || targetLine > lines.length) {
58
- return undefined;
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
- if (depth !== 0) {
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
- return { dotIndex, callEnd: i };
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 entries = latestPerLocation(rawEntries ?? (0, heal_log_1.readHealLog)(cwd));
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
- return { outcomes, fileContents };
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;