tamash-playwright 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/README.md +52 -1
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +3 -0
- package/dist/bindings/index.js.map +1 -1
- package/dist/bindings/locator.binding.d.ts +4 -3
- package/dist/bindings/locator.binding.d.ts.map +1 -1
- package/dist/bindings/locator.binding.js +197 -7
- package/dist/bindings/locator.binding.js.map +1 -1
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +126 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/scanDescribe.d.ts +11 -0
- package/dist/cli/scanDescribe.d.ts.map +1 -0
- package/dist/cli/scanDescribe.js +84 -0
- package/dist/cli/scanDescribe.js.map +1 -0
- package/dist/healer/action-recovery.d.ts +14 -0
- package/dist/healer/action-recovery.d.ts.map +1 -0
- package/dist/healer/action-recovery.js +102 -0
- package/dist/healer/action-recovery.js.map +1 -0
- package/dist/healer/index.d.ts +10 -2
- package/dist/healer/index.d.ts.map +1 -1
- package/dist/healer/index.js +297 -72
- package/dist/healer/index.js.map +1 -1
- package/dist/healer/providers/anthropic.d.ts.map +1 -1
- package/dist/healer/providers/anthropic.js +70 -0
- package/dist/healer/providers/anthropic.js.map +1 -1
- package/dist/healer/providers/gemini.d.ts.map +1 -1
- package/dist/healer/providers/gemini.js +99 -0
- package/dist/healer/providers/gemini.js.map +1 -1
- package/dist/healer/providers/ollama.d.ts.map +1 -1
- package/dist/healer/providers/ollama.js +113 -0
- package/dist/healer/providers/ollama.js.map +1 -1
- package/dist/healer/providers/openai.d.ts.map +1 -1
- package/dist/healer/providers/openai.js +99 -0
- package/dist/healer/providers/openai.js.map +1 -1
- package/dist/healer/providers/prompt.d.ts +7 -1
- package/dist/healer/providers/prompt.d.ts.map +1 -1
- package/dist/healer/providers/prompt.js +95 -1
- package/dist/healer/providers/prompt.js.map +1 -1
- package/dist/healer/providers/types.d.ts +36 -0
- package/dist/healer/providers/types.d.ts.map +1 -1
- package/dist/healer/providers/vision-models.d.ts +3 -0
- package/dist/healer/providers/vision-models.d.ts.map +1 -0
- package/dist/healer/providers/vision-models.js +40 -0
- package/dist/healer/providers/vision-models.js.map +1 -0
- package/dist/healer/replay-action.d.ts +3 -0
- package/dist/healer/replay-action.d.ts.map +1 -0
- package/dist/healer/replay-action.js +83 -0
- package/dist/healer/replay-action.js.map +1 -0
- package/dist/healer/vision.d.ts +25 -0
- package/dist/healer/vision.d.ts.map +1 -0
- package/dist/healer/vision.js +75 -0
- package/dist/healer/vision.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/.env.example
CHANGED
|
@@ -6,6 +6,11 @@ HEALER_ENABLED=true
|
|
|
6
6
|
# a failed action just fails normally, since there's no AI model configured to recover it.
|
|
7
7
|
HEALER_PROVIDER=ollama
|
|
8
8
|
|
|
9
|
+
# Optional, off by default. If a locator gets healed but the action on it still fails (e.g. the
|
|
10
|
+
# element is covered or needs scrolling into view), the AI can pick a recovery tactic (scroll,
|
|
11
|
+
# force, wait, or dispatch the DOM event directly) instead of giving up immediately.
|
|
12
|
+
# HEALER_ACTION_RECOVERY_ENABLED=true
|
|
13
|
+
|
|
9
14
|
# --- Ollama Cloud (https://ollama.com) ---
|
|
10
15
|
OLLAMA_MODEL=gpt-oss:120b
|
|
11
16
|
OLLAMA_API_KEY=
|
package/README.md
CHANGED
|
@@ -37,6 +37,9 @@ HEALER_ENABLED=true
|
|
|
37
37
|
# Pick one: ollama | openai | anthropic | gemini
|
|
38
38
|
HEALER_PROVIDER=ollama
|
|
39
39
|
|
|
40
|
+
# Optional, off by default — see "Action recovery" below.
|
|
41
|
+
# HEALER_ACTION_RECOVERY_ENABLED=true
|
|
42
|
+
|
|
40
43
|
# --- Ollama Cloud (https://ollama.com) ---
|
|
41
44
|
OLLAMA_MODEL=gpt-oss:120b
|
|
42
45
|
OLLAMA_API_KEY=
|
|
@@ -74,7 +77,25 @@ OLLAMA_API_KEY=paste_your_key_here
|
|
|
74
77
|
|
|
75
78
|
That's all you need — no other variables required.
|
|
76
79
|
|
|
77
|
-
## Step 3:
|
|
80
|
+
## Step 3: Check your setup
|
|
81
|
+
|
|
82
|
+
Run the built-in doctor command to confirm everything's wired up correctly before you rely on it:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
npx tamash-playwright doctor
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It checks:
|
|
89
|
+
|
|
90
|
+
1. **AI connectivity** — confirms `HEALER_ENABLED`/`HEALER_PROVIDER` are set correctly and actually calls your configured provider to make sure the API key and model work.
|
|
91
|
+
2. **Action recovery status** — whether `HEALER_ACTION_RECOVERY_ENABLED` is on (see below).
|
|
92
|
+
3. **Vision capability** — whether your configured model is expected to support the screenshot-based fallback (see below), based on its name.
|
|
93
|
+
4. **Missing `.describe()` labels** — scans your test files (`tests/` by default, or pass `--dir <path>`) for locators that don't have a `.describe('...')` label, and flags the ones most worth fixing (raw CSS/XPath selectors first).
|
|
94
|
+
5. **Locators written directly in test files** — flags any locator defined inline in a test rather than inside a Page Object class, which is a Playwright best practice regardless of self-healing: it keeps tests readable and means a UI change only needs a fix in one place.
|
|
95
|
+
|
|
96
|
+
If it finds issues, the fastest fix is to open the project in an AI coding assistant (Claude Code, Cursor, GitHub Copilot, etc.) and ask it to address what it flagged — add `.describe()` calls, or extract locators into Page Object classes. You can also add a standing rule to that assistant's instructions/skill file (e.g. `CLAUDE.md`, `.cursor/rules`, `.github/copilot-instructions.md`) so it follows both practices automatically on any new test code going forward.
|
|
97
|
+
|
|
98
|
+
## Step 4: Use it in your tests
|
|
78
99
|
|
|
79
100
|
Change one line at the top of your test file — everything else about how you write tests stays exactly the same:
|
|
80
101
|
|
|
@@ -129,6 +150,36 @@ test('login test using CSS Selectors', async ({ page }) => {
|
|
|
129
150
|
|
|
130
151
|
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.
|
|
131
152
|
|
|
153
|
+
## What else it heals — no extra setup needed
|
|
154
|
+
|
|
155
|
+
Beyond a single broken `click`/`fill`/`getByRole` on the main page, all of this works automatically once you've done Steps 1–2:
|
|
156
|
+
|
|
157
|
+
- **Popups and extra tabs.** A page opened via `context.newPage()`, `window.open`, or a `target="_blank"` link is just as healing-aware as your main `page` — no manual wrapping needed.
|
|
158
|
+
- **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.
|
|
159
|
+
- **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.
|
|
160
|
+
|
|
161
|
+
## When text alone isn't enough: vision fallback
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
## Action recovery (optional)
|
|
166
|
+
|
|
167
|
+
Occasionally a locator heals correctly — the AI found the right element — but the *action* on it still fails, e.g. it's covered by an overlay or needs scrolling into view first. Set `HEALER_ACTION_RECOVERY_ENABLED=true` to let the AI pick a recovery tactic from a fixed, safe set (scroll into view, retry bypassing actionability checks, wait briefly and retry, or dispatch the DOM event directly) before giving up. It's off by default since it's a second, more speculative layer of intervention beyond selector healing — the AI only ever picks from that fixed menu, it never decides how to interact with the page on its own.
|
|
168
|
+
|
|
169
|
+
## Checking what actually happened
|
|
170
|
+
|
|
171
|
+
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:
|
|
172
|
+
|
|
173
|
+
- An annotation on the test summarizing what happened, e.g. `Recovered using ollama:gpt-oss:120b (role:button:Submit)`.
|
|
174
|
+
- A JSON attachment with the full detail: which provider was used, whether the vision or action-recovery fallback was involved, the AI's suggested selector, token cost, and — if it didn't heal — which stage it stopped at (e.g. `ai_declined`, `replay_failed`).
|
|
175
|
+
- Exactly where in your own code the locator was created — a test file or a Page Object class, whichever it really is — so you know which line to go fix even if you never look at the healing report again.
|
|
176
|
+
|
|
177
|
+
The same detail is also printed to the console as it happens, one line per attempt:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
[self-healer] src/pages/loginpage.ts:11 — locator.fill "Username Textbox" -> HEALED [provider=ollama:gpt-oss:120b, vision=no, actionRecovery=no, suggested="role:textbox:Username", 620 tokens (489 input + 131 output)] — locator.fill: Timeout 8000ms exceeded.
|
|
181
|
+
```
|
|
182
|
+
|
|
132
183
|
## License
|
|
133
184
|
|
|
134
185
|
Free to use, including commercially. The source code may not be copied, modified, redistributed, or resold without prior written permission. See the LICENSE file included in this package for the full terms.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGvE,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,KAAK,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGvE,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,KAAK,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,IAAI,0PAcf,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/bindings/index.js
CHANGED
|
@@ -4,6 +4,9 @@ exports.expect = exports.test = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const locator_binding_1 = require("./locator.binding");
|
|
6
6
|
exports.test = test_1.test.extend({
|
|
7
|
+
context: async ({ context }, use) => {
|
|
8
|
+
await use((0, locator_binding_1.bindContext)(context));
|
|
9
|
+
},
|
|
7
10
|
page: async ({ page }, use) => {
|
|
8
11
|
await use((0, locator_binding_1.bindPageActions)(page));
|
|
9
12
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":";;;AAAA,2CAAuE;AACvE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":";;;AAAA,2CAAuE;AACvE,uDAAmF;AAOtE,QAAA,IAAI,GAAG,WAAI,CAAC,MAAM,CAAkB;IAC/C,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE;QAClC,MAAM,GAAG,CAAC,IAAA,6BAAW,EAAC,OAAO,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;QAC5B,MAAM,GAAG,CAAC,IAAA,iCAAe,EAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;QACjC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IACD,UAAU,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,MAAM,GAAG,CAAC,IAAA,kCAAgB,EAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;CACF,CAAC,CAAC;AAEH,yCAA0C;AAAjC,8FAAA,MAAM,OAAA"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Frame, Locator, Page } from '@playwright/test';
|
|
2
|
-
type BindingKind = 'locator' | 'page' | 'frame';
|
|
3
|
-
export declare function bindTargetActions<T extends Locator | Page | Frame>(target: T, kind?: BindingKind, description?: string): T;
|
|
1
|
+
import type { BrowserContext, Frame, FrameLocator, Locator, Page } from '@playwright/test';
|
|
2
|
+
type BindingKind = 'locator' | 'page' | 'frame' | 'frameLocator';
|
|
3
|
+
export declare function bindTargetActions<T extends Locator | Page | Frame | FrameLocator>(target: T, kind?: BindingKind, description?: string, frameScope?: FrameLocator, selector?: string, callSite?: Error): T;
|
|
4
4
|
export declare function bindLocatorActions(locator: Locator): Locator;
|
|
5
5
|
export declare function bindPageActions(page: Page): Page;
|
|
6
6
|
export declare function bindFrameActions(frame: Frame): Frame;
|
|
7
|
+
export declare function bindContext(context: BrowserContext): BrowserContext;
|
|
7
8
|
export {};
|
|
8
9
|
//# sourceMappingURL=locator.binding.d.ts.map
|
|
@@ -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":"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,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.bindTargetActions = bindTargetActions;
|
|
4
7
|
exports.bindLocatorActions = bindLocatorActions;
|
|
5
8
|
exports.bindPageActions = bindPageActions;
|
|
6
9
|
exports.bindFrameActions = bindFrameActions;
|
|
10
|
+
exports.bindContext = bindContext;
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
7
12
|
const healer_1 = require("../healer");
|
|
8
13
|
const ACTIONS = [
|
|
9
14
|
'click',
|
|
@@ -18,8 +23,40 @@ const ACTIONS = [
|
|
|
18
23
|
'selectOption',
|
|
19
24
|
'setInputFiles',
|
|
20
25
|
'dragTo',
|
|
26
|
+
// `drop` validates its `{files, data}` payload *before* ever touching the locator (confirmed:
|
|
27
|
+
// an empty payload throws "At least one of files or data must be provided" instantly, with no
|
|
28
|
+
// "Timeout ... exceeded" resolving-the-locator phase at all), so like `dragTo` it's caught and
|
|
29
|
+
// reported here but excluded from replayAction — a two-sided drag/drop pairing can't be safely
|
|
30
|
+
// healed by guessing a substitute locator for just one side of it.
|
|
31
|
+
'drop',
|
|
21
32
|
'focus',
|
|
22
33
|
'blur',
|
|
34
|
+
'type',
|
|
35
|
+
'pressSequentially',
|
|
36
|
+
'setChecked',
|
|
37
|
+
'scrollIntoViewIfNeeded',
|
|
38
|
+
'dispatchEvent',
|
|
39
|
+
'waitFor',
|
|
40
|
+
// Read methods below don't act on the page — they raise when the locator can't be resolved
|
|
41
|
+
// (confirmed empirically: they throw the same "Timeout ... exceeded" shape as click/fill do
|
|
42
|
+
// against a zero-match locator), so a broken selector here is just as healable as a broken
|
|
43
|
+
// selector on a click. `isVisible`/`isHidden`/`count`/`all`/`allInnerTexts`/`allTextContents`
|
|
44
|
+
// are deliberately excluded — confirmed they degrade gracefully (false/0/[]) instead of
|
|
45
|
+
// throwing, so there's no exception here for the healer to ever catch.
|
|
46
|
+
'textContent',
|
|
47
|
+
'innerText',
|
|
48
|
+
'innerHTML',
|
|
49
|
+
'getAttribute',
|
|
50
|
+
'inputValue',
|
|
51
|
+
'isChecked',
|
|
52
|
+
'isEnabled',
|
|
53
|
+
'isDisabled',
|
|
54
|
+
'isEditable',
|
|
55
|
+
'selectText',
|
|
56
|
+
'screenshot',
|
|
57
|
+
'boundingBox',
|
|
58
|
+
'elementHandle',
|
|
59
|
+
'ariaSnapshot',
|
|
23
60
|
];
|
|
24
61
|
const LOCATOR_FACTORY_METHODS = [
|
|
25
62
|
'locator',
|
|
@@ -31,6 +68,10 @@ const LOCATOR_FACTORY_METHODS = [
|
|
|
31
68
|
'getByTitle',
|
|
32
69
|
'getByTestId',
|
|
33
70
|
];
|
|
71
|
+
// Handled separately from LOCATOR_FACTORY_METHODS: frameLocator() returns a FrameLocator, not a
|
|
72
|
+
// Locator, so it needs its own BindingKind and its own frameScope propagation (see below) rather
|
|
73
|
+
// than being folded into the generic factory-method branch.
|
|
74
|
+
const FRAME_LOCATOR_METHOD = 'frameLocator';
|
|
34
75
|
// Methods that return a *new* Locator derived from the current one. Playwright's own
|
|
35
76
|
// implementations are immutable (they don't mutate `target`), so if we don't intercept these
|
|
36
77
|
// and re-wrap the result, the returned Locator silently loses its healing Proxy. This mattered
|
|
@@ -58,23 +99,111 @@ function describeFactoryCall(prop, args) {
|
|
|
58
99
|
return `testid "${String(first)}"`;
|
|
59
100
|
case 'locator':
|
|
60
101
|
return `selector "${String(first)}"`;
|
|
102
|
+
case 'frameLocator':
|
|
103
|
+
return `iframe "${String(first)}"`;
|
|
61
104
|
default:
|
|
62
105
|
return undefined;
|
|
63
106
|
}
|
|
64
107
|
}
|
|
65
|
-
|
|
66
|
-
|
|
108
|
+
// Resolves the real, human-meaningful call site of a locator — the line in the consumer's own
|
|
109
|
+
// test file or Page Object that actually wrote `.locator(...)`/`.getByRole(...)`/etc. — from an
|
|
110
|
+
// Error captured at that exact call. Only ever invoked on a genuine failure (see the ACTIONS
|
|
111
|
+
// branch below), never on every locator creation: constructing an Error is cheap, but reading
|
|
112
|
+
// `.stack` is not (V8 computes it lazily on first access), so this cost is deliberately deferred
|
|
113
|
+
// until it's actually needed.
|
|
114
|
+
//
|
|
115
|
+
// Filters by checking whether a frame's file path contains "locator.binding" rather than an exact
|
|
116
|
+
// path/`__filename` comparison — verified empirically that this matters: Node resolves stack
|
|
117
|
+
// frames through this package's own source maps, so a frame belonging to this file shows up as
|
|
118
|
+
// `.../src/bindings/locator.binding.ts` in the formatted stack string even when the code actually
|
|
119
|
+
// executing is the compiled `dist/bindings/locator.binding.js` — an exact `__filename` compare
|
|
120
|
+
// would silently never match and filter nothing out.
|
|
121
|
+
function resolveCallerLocation(callSite) {
|
|
122
|
+
const stack = callSite.stack;
|
|
123
|
+
if (!stack) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
for (const line of stack.split('\n')) {
|
|
127
|
+
if (line.includes('locator.binding')) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const match = line.match(/^\s*at\s+(?:.*\s+\()?(.+):(\d+):(\d+)\)?\s*$/);
|
|
131
|
+
if (!match) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const [, filePath, lineNumber] = match;
|
|
135
|
+
return `${path_1.default.relative(process.cwd(), filePath)}:${lineNumber}`;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
// Marks every proxy this module creates so a target that's already wrapped is never wrapped
|
|
140
|
+
// again. This matters concretely for the primary test `page`: Playwright's own built-in `page`
|
|
141
|
+
// fixture is implemented as `context.newPage()`, and since bindContext() (below) already wraps
|
|
142
|
+
// whatever newPage() returns, the fixture override in bindings/index.ts receives an
|
|
143
|
+
// already-wrapped page as its `page` dependency. Without this check, wrapping it a second time
|
|
144
|
+
// would nest two independent healing layers around the same actions — every failed action would
|
|
145
|
+
// then be caught and reported twice (the inner layer reports, rethrows; the outer layer catches
|
|
146
|
+
// that rethrow and reports again), even though only one real action ever ran. Keying by the
|
|
147
|
+
// object actually passed in (rather than tracking raw targets) is what makes this work: the
|
|
148
|
+
// second call receives the *proxy* from the first call, not the original raw object, so a
|
|
149
|
+
// raw-object cache would miss it.
|
|
150
|
+
const knownProxies = new WeakSet();
|
|
151
|
+
function bindTargetActions(target, kind = 'locator', description,
|
|
152
|
+
// The owning FrameLocator, when `target` (or anything derived from it via chaining/factory
|
|
153
|
+
// calls) lives inside an <iframe> — threaded through to healActionFailure so the healer
|
|
154
|
+
// snapshots and rebuilds a replacement locator *inside that iframe*, not the outer page where
|
|
155
|
+
// the element doesn't exist. ariaSnapshot() never descends into iframes on its own.
|
|
156
|
+
frameScope,
|
|
157
|
+
// The selector text captured once at the factory call (e.g. `selector "#foo"`), tracked
|
|
158
|
+
// independently of `description` so a later `.describe("Submit button")` overrides only the
|
|
159
|
+
// human-readable label without erasing what the original selector actually was — both are worth
|
|
160
|
+
// showing the AI/report, since "Submit button (selector \"#foo\")" is more useful for debugging
|
|
161
|
+
// a bad selector than either alone.
|
|
162
|
+
selector,
|
|
163
|
+
// An Error captured at the factory call that created this Locator — its birthplace, not
|
|
164
|
+
// wherever it's later chained/acted on. Threaded through exactly like `selector`: freshly
|
|
165
|
+
// captured on every factory call, propagated unchanged through CHAIN_METHODS. Only ever turned
|
|
166
|
+
// into an actual file:line (via resolveCallerLocation) on a genuine failure, not eagerly here.
|
|
167
|
+
callSite) {
|
|
168
|
+
if (knownProxies.has(target)) {
|
|
169
|
+
return target;
|
|
170
|
+
}
|
|
171
|
+
const proxy = new Proxy(target, {
|
|
67
172
|
get(target, prop, receiver) {
|
|
68
173
|
if (typeof prop === 'string') {
|
|
69
174
|
const targetObject = target;
|
|
70
|
-
if (
|
|
175
|
+
if (prop === FRAME_LOCATOR_METHOD) {
|
|
176
|
+
return (...args) => {
|
|
177
|
+
const factory = targetObject[prop];
|
|
178
|
+
if (typeof factory !== 'function') {
|
|
179
|
+
throw new Error(`frameLocator() is not available on this ${kind}.`);
|
|
180
|
+
}
|
|
181
|
+
const childFrameLocator = factory.apply(target, args);
|
|
182
|
+
const frameSelector = describeFactoryCall(prop, args);
|
|
183
|
+
// The FrameLocator itself becomes the frameScope for everything resolved through it.
|
|
184
|
+
return bindTargetActions(childFrameLocator, 'frameLocator', frameSelector, childFrameLocator, frameSelector);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Previously gated on `kind !== 'locator'`, which meant chaining a factory method off an
|
|
188
|
+
// already-obtained Locator (e.g. `container.getByRole('button')`, a common pattern for
|
|
189
|
+
// scoping a search) silently returned a raw, unwrapped Locator with no healing at all.
|
|
190
|
+
// Locator/Page/Frame/FrameLocator all expose these methods, so there's no kind this
|
|
191
|
+
// should be excluded for.
|
|
192
|
+
if (LOCATOR_FACTORY_METHODS.includes(prop)) {
|
|
71
193
|
return (...args) => {
|
|
72
194
|
const factory = targetObject[prop];
|
|
73
195
|
if (typeof factory !== 'function') {
|
|
74
196
|
throw new Error(`The locator factory "${String(prop)}" is not available on this ${kind}.`);
|
|
75
197
|
}
|
|
76
198
|
const locator = factory.apply(target, args);
|
|
77
|
-
|
|
199
|
+
const newSelector = describeFactoryCall(prop, args);
|
|
200
|
+
// Captured fresh at every factory call, same as newSelector — this call is this
|
|
201
|
+
// Locator's birthplace, regardless of where `target` itself came from.
|
|
202
|
+
const newCallSite = new Error();
|
|
203
|
+
// description starts out equal to the selector text (nothing else to show yet); a
|
|
204
|
+
// later `.describe()` call overrides description independently, without touching
|
|
205
|
+
// `newSelector` — see the CHAIN_METHODS branch below.
|
|
206
|
+
return bindTargetActions(locator, 'locator', newSelector, frameScope, newSelector, newCallSite);
|
|
78
207
|
};
|
|
79
208
|
}
|
|
80
209
|
if (kind === 'locator' && CHAIN_METHODS.includes(prop)) {
|
|
@@ -85,7 +214,11 @@ function bindTargetActions(target, kind = 'locator', description) {
|
|
|
85
214
|
}
|
|
86
215
|
const result = chainMethod.apply(target, args);
|
|
87
216
|
const nextDescription = prop === 'describe' ? String(args[0] ?? '') : description;
|
|
88
|
-
|
|
217
|
+
// `selector` and `callSite` are deliberately NOT touched here, for any chain method
|
|
218
|
+
// including `describe()` — they always stay the original factory-call selector/site,
|
|
219
|
+
// so a human description added later doesn't erase where this Locator actually came
|
|
220
|
+
// from.
|
|
221
|
+
return bindTargetActions(result, 'locator', nextDescription, frameScope, selector, callSite);
|
|
89
222
|
};
|
|
90
223
|
}
|
|
91
224
|
if (ACTIONS.includes(prop)) {
|
|
@@ -104,13 +237,27 @@ function bindTargetActions(target, kind = 'locator', description) {
|
|
|
104
237
|
return await target[prop](...callArgs);
|
|
105
238
|
}
|
|
106
239
|
catch (error) {
|
|
240
|
+
// 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)}`);
|
|
107
248
|
const healing = await (0, healer_1.healActionFailure)({
|
|
108
249
|
action: String(prop),
|
|
109
250
|
kind,
|
|
110
|
-
description:
|
|
251
|
+
description: effectiveDescription,
|
|
111
252
|
error,
|
|
112
|
-
|
|
253
|
+
// FrameLocator never reaches this branch (it has no ACTIONS methods, only the
|
|
254
|
+
// locator-factory ones handled above), so this narrowing is safe.
|
|
255
|
+
target: target,
|
|
113
256
|
callArgs,
|
|
257
|
+
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,
|
|
114
261
|
});
|
|
115
262
|
// NOTE: most Playwright actions (click/fill/check/hover/...) resolve to `undefined`
|
|
116
263
|
// on success, so `healing.recovered` alone is the correct success signal here —
|
|
@@ -128,6 +275,8 @@ function bindTargetActions(target, kind = 'locator', description) {
|
|
|
128
275
|
return typeof value === 'function' ? value.bind(target) : value;
|
|
129
276
|
},
|
|
130
277
|
});
|
|
278
|
+
knownProxies.add(proxy);
|
|
279
|
+
return proxy;
|
|
131
280
|
}
|
|
132
281
|
function bindLocatorActions(locator) {
|
|
133
282
|
return bindTargetActions(locator, 'locator');
|
|
@@ -138,4 +287,45 @@ function bindPageActions(page) {
|
|
|
138
287
|
function bindFrameActions(frame) {
|
|
139
288
|
return bindTargetActions(frame, 'frame');
|
|
140
289
|
}
|
|
290
|
+
// A second/third page (a popup, a manually-opened tab, target="_blank" navigation) is just as
|
|
291
|
+
// much "what the test did" as the primary page — but unlike Python's monkey-patching approach
|
|
292
|
+
// (where mutating the real instance means every reference to it sees the patch, however it was
|
|
293
|
+
// obtained), a Proxy only affects code that actually holds a reference to *that* Proxy. Passively
|
|
294
|
+
// listening via `context.on('page', ...)` and discarding the result would never reach the test:
|
|
295
|
+
// `const popup = await context.newPage()` returns Playwright's own raw object directly, bypassing
|
|
296
|
+
// any side-effecting listener entirely. So this wraps the *methods that hand pages back to the
|
|
297
|
+
// caller* instead — newPage(), pages(), and on('page'/'popup', handler) (rewriting the handler so
|
|
298
|
+
// whatever it's called with is the wrapped page, not the raw one) — which is what actually gets
|
|
299
|
+
// the healing-aware Proxy into the test's hands regardless of how it obtained the new page.
|
|
300
|
+
function bindContext(context) {
|
|
301
|
+
if (knownProxies.has(context)) {
|
|
302
|
+
return context;
|
|
303
|
+
}
|
|
304
|
+
const proxy = new Proxy(context, {
|
|
305
|
+
get(target, prop, receiver) {
|
|
306
|
+
if (prop === 'newPage') {
|
|
307
|
+
return async (...args) => {
|
|
308
|
+
const page = await target.newPage(...args);
|
|
309
|
+
return bindPageActions(page);
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (prop === 'pages') {
|
|
313
|
+
return () => target.pages().map((page) => bindPageActions(page));
|
|
314
|
+
}
|
|
315
|
+
if (prop === 'on' || prop === 'once') {
|
|
316
|
+
return (event, handler) => {
|
|
317
|
+
if (event === 'page' || event === 'popup') {
|
|
318
|
+
const wrappedHandler = (page) => handler(bindPageActions(page));
|
|
319
|
+
return target[prop](event, wrappedHandler);
|
|
320
|
+
}
|
|
321
|
+
return target[prop](event, handler);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const value = Reflect.get(target, prop, receiver);
|
|
325
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
knownProxies.add(proxy);
|
|
329
|
+
return proxy;
|
|
330
|
+
}
|
|
141
331
|
//# sourceMappingURL=locator.binding.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locator.binding.js","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"locator.binding.js","sourceRoot":"","sources":["../../src/bindings/locator.binding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAyHA,wBAAsB,SAAS,CAAC,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDoctor = runDoctor;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
const providers_1 = require("../healer/providers");
|
|
10
|
+
const healer_1 = require("../healer");
|
|
11
|
+
const scanDescribe_1 = require("./scanDescribe");
|
|
12
|
+
dotenv_1.default.config({ path: path_1.default.resolve(process.cwd(), '.env') });
|
|
13
|
+
const CONNECTIVITY_TIMEOUT_MS = 15000;
|
|
14
|
+
const CONNECTIVITY_SNAPSHOT = '- generic "tamash-playwright doctor check":\n - button "OK"';
|
|
15
|
+
async function checkProviderConnectivity() {
|
|
16
|
+
console.log(`HEALER_ENABLED: ${process.env.HEALER_ENABLED ?? '(unset — defaults to true)'}`);
|
|
17
|
+
if (!(0, healer_1.isHealingEnabled)()) {
|
|
18
|
+
console.log(' Healing is currently OFF. Set HEALER_ENABLED=true (or remove the line) to turn it back on.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(`HEALER_ACTION_RECOVERY_ENABLED: ${process.env.HEALER_ACTION_RECOVERY_ENABLED ?? '(unset — defaults to false)'}`);
|
|
22
|
+
if ((0, healer_1.isActionRecoveryEnabled)()) {
|
|
23
|
+
console.log(' Action recovery is ON — if a locator heals but the action on it still fails, the AI may retry');
|
|
24
|
+
console.log(' it (scroll into view, force, wait, or dispatch the DOM event directly) before giving up.');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(' Action recovery is OFF (opt-in, off by default). Set HEALER_ACTION_RECOVERY_ENABLED=true to turn it on.');
|
|
28
|
+
}
|
|
29
|
+
console.log('');
|
|
30
|
+
const providerName = process.env.HEALER_PROVIDER;
|
|
31
|
+
if (!providerName) {
|
|
32
|
+
console.log('[WARN] HEALER_PROVIDER is not set. No AI provider is configured, so self-healing will never');
|
|
33
|
+
console.log(' recover a failed action — it will just fail normally.');
|
|
34
|
+
console.log(' Set HEALER_PROVIDER (ollama | openai | anthropic | gemini) plus its API key/model in .env.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const provider = (0, providers_1.getHealProvider)();
|
|
38
|
+
if (!provider) {
|
|
39
|
+
console.log(`[FAIL] HEALER_PROVIDER=${providerName}, but its required model/API key env vars are missing.`);
|
|
40
|
+
console.log(' Check the matching section for it in your .env file.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Based on a model-name heuristic (see providers/vision-models.ts), not a live check — kept
|
|
44
|
+
// that way deliberately so `doctor` doesn't spend an extra API call confirming something the
|
|
45
|
+
// name pattern already answers; if the heuristic is ever wrong for a given model, that surfaces
|
|
46
|
+
// the first time healing actually needs the vision fallback, not here.
|
|
47
|
+
if (provider.supportsVision) {
|
|
48
|
+
console.log(`Vision-capable model: yes — ${provider.name} may be used for the screenshot-based fallback when text-based healing fails.`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(`Vision-capable model: no — ${provider.name} does not look vision-capable by name, so the screenshot-based fallback will never be attempted.`);
|
|
52
|
+
}
|
|
53
|
+
console.log(`Testing connectivity to ${provider.name}...`);
|
|
54
|
+
const result = await provider.suggestSelector({
|
|
55
|
+
action: 'click',
|
|
56
|
+
description: 'tamash-playwright doctor connectivity check',
|
|
57
|
+
ariaSnapshot: CONNECTIVITY_SNAPSHOT,
|
|
58
|
+
timeoutMs: CONNECTIVITY_TIMEOUT_MS,
|
|
59
|
+
});
|
|
60
|
+
if (result) {
|
|
61
|
+
console.log(`[OK] Connected to ${provider.name} successfully.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(`[FAIL] Could not get a valid response from ${provider.name}.`);
|
|
65
|
+
console.log(' Check the warning above (if any), your API key, model name, and network connection.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function checkDescribeUsage(occurrences) {
|
|
69
|
+
const missingDescribe = occurrences.filter((o) => !o.hasDescribe);
|
|
70
|
+
if (missingDescribe.length === 0) {
|
|
71
|
+
console.log('[OK] No locators found without a .describe() label.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log(`[WARN] Found ${missingDescribe.length} locator(s) without .describe():\n`);
|
|
75
|
+
for (const finding of missingDescribe) {
|
|
76
|
+
const tag = finding.priority === 'high' ? '[HIGH]' : '[LOW] ';
|
|
77
|
+
const relativeFile = path_1.default.relative(process.cwd(), finding.file);
|
|
78
|
+
console.log(` ${tag} ${relativeFile}:${finding.line} ${finding.snippet}`);
|
|
79
|
+
}
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log('Recommendation: add .describe(\'...\') to these locators — especially the [HIGH] ones (raw CSS/XPath');
|
|
82
|
+
console.log('selectors), since those give the self-healer the least to go on if they break. Example:');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(' page.locator(\'input[name="username"]\').describe(\'Username Textbox\')');
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log('Fastest way to fix these: open this project in an AI coding assistant (Claude Code, Cursor, GitHub');
|
|
87
|
+
console.log('Copilot, etc.) and ask it to add .describe() to the locators flagged above.');
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log('To make this automatic going forward, add a standing rule to your AI IDE\'s instructions/skill file');
|
|
90
|
+
console.log('(e.g. CLAUDE.md, .cursor/rules, .github/copilot-instructions.md) along the lines of:');
|
|
91
|
+
console.log(' "Whenever creating or editing a Playwright locator, chain .describe(\'...\') onto it with a short,');
|
|
92
|
+
console.log(' human-readable label of what the element is."');
|
|
93
|
+
}
|
|
94
|
+
// Any locator this scan finds lives in a *.spec.ts/*.test.ts file by construction (that's the
|
|
95
|
+
// glob it scans), which means it's written directly into the test rather than inside a Page
|
|
96
|
+
// Object — hence this fires whenever there's at least one occurrence, regardless of .describe().
|
|
97
|
+
function checkPageObjectUsage(occurrences) {
|
|
98
|
+
if (occurrences.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const fileCount = new Set(occurrences.map((o) => o.file)).size;
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(`[INFO] Found ${occurrences.length} locator(s) defined directly inside ${fileCount} test file(s).`);
|
|
104
|
+
console.log(' Best practice: don\'t write locators straight into your tests — move them into Page Object');
|
|
105
|
+
console.log(' classes instead (e.g. a pages/LoginPage.ts with a login() method that internally holds the');
|
|
106
|
+
console.log(' page.locator(...) calls). Tests then call page-object methods, not raw locators. This keeps');
|
|
107
|
+
console.log(' tests readable and means a UI change only needs a fix in one place.');
|
|
108
|
+
}
|
|
109
|
+
function parseTestDir(args) {
|
|
110
|
+
const flagIndex = args.indexOf('--dir');
|
|
111
|
+
const value = flagIndex !== -1 ? args[flagIndex + 1] : undefined;
|
|
112
|
+
return path_1.default.resolve(process.cwd(), value ?? 'tests');
|
|
113
|
+
}
|
|
114
|
+
async function runDoctor(args = []) {
|
|
115
|
+
console.log('tamash-playwright doctor\n');
|
|
116
|
+
await checkProviderConnectivity();
|
|
117
|
+
console.log('');
|
|
118
|
+
const testDir = parseTestDir(args);
|
|
119
|
+
const relativeDir = path_1.default.relative(process.cwd(), testDir) || testDir;
|
|
120
|
+
console.log(`Scanning ${relativeDir} for locators...`);
|
|
121
|
+
console.log('(static text scan, not a real parser — review these before acting on them)\n');
|
|
122
|
+
const occurrences = (0, scanDescribe_1.scanTestDirectory)(testDir);
|
|
123
|
+
checkDescribeUsage(occurrences);
|
|
124
|
+
checkPageObjectUsage(occurrences);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const doctor_1 = require("./doctor");
|
|
5
|
+
async function main() {
|
|
6
|
+
const [, , command, ...rest] = process.argv;
|
|
7
|
+
switch (command) {
|
|
8
|
+
case 'doctor':
|
|
9
|
+
await (0, doctor_1.runDoctor)(rest);
|
|
10
|
+
break;
|
|
11
|
+
case undefined:
|
|
12
|
+
case '--help':
|
|
13
|
+
case '-h':
|
|
14
|
+
console.log('Usage: tamash-playwright doctor [--dir <path>]');
|
|
15
|
+
break;
|
|
16
|
+
default:
|
|
17
|
+
console.log(`Unknown command: ${command}`);
|
|
18
|
+
console.log('Usage: tamash-playwright doctor [--dir <path>]');
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
main().catch((error) => {
|
|
23
|
+
console.error(error);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;AACA,qCAAqC;AAErC,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,EAAE,AAAD,EAAG,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAE5C,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,MAAM,IAAA,kBAAS,EAAC,IAAI,CAAC,CAAC;YACtB,MAAM;QACR,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;YAC9D,MAAM;QACR;YACE,OAAO,CAAC,GAAG,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;YAC9D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|