playwriter 0.0.105 → 0.2.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/dist/bippy.js +5 -5
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +17 -5
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cli-help.test.d.ts +2 -0
- package/dist/cli-help.test.d.ts.map +1 -0
- package/dist/cli-help.test.js +53 -0
- package/dist/cli-help.test.js.map +1 -0
- package/dist/cli.js +74 -25
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +1 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +55 -12
- package/dist/executor.js.map +1 -1
- package/dist/extension/background.js +675 -27
- package/dist/extension/manifest.json +1 -1
- package/dist/ghost-cursor-client.js +170 -83
- package/dist/{recording-ghost-cursor.d.ts → ghost-cursor-controller.d.ts} +15 -10
- package/dist/ghost-cursor-controller.d.ts.map +1 -0
- package/dist/ghost-cursor-controller.js +98 -0
- package/dist/ghost-cursor-controller.js.map +1 -0
- package/dist/ghost-cursor.d.ts.map +1 -1
- package/dist/ghost-cursor.js +42 -26
- package/dist/ghost-cursor.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +6 -1
- package/dist/mcp.js.map +1 -1
- package/dist/on-mouse-action.test.js +25 -0
- package/dist/on-mouse-action.test.js.map +1 -1
- package/dist/performance-examples.d.ts +5 -0
- package/dist/performance-examples.d.ts.map +1 -0
- package/dist/performance-examples.js +112 -0
- package/dist/performance-examples.js.map +1 -0
- package/dist/performance-profiling.md +417 -0
- package/dist/prompt.md +22 -8
- package/dist/react-source.d.ts +44 -0
- package/dist/react-source.d.ts.map +1 -1
- package/dist/react-source.js +207 -20
- package/dist/react-source.js.map +1 -1
- package/dist/readability.js +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +101 -1
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-session.test.js +34 -6
- package/dist/relay-session.test.js.map +1 -1
- package/dist/screen-recording.d.ts +2 -2
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +19 -7
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/package.json +7 -7
- package/src/aria-snapshots/github-interactive.txt +5 -3
- package/src/aria-snapshots/github-raw.txt +8 -5
- package/src/aria-snapshots/hackernews-interactive.txt +241 -238
- package/src/aria-snapshots/hackernews-raw.txt +269 -265
- package/src/aria-snapshots/prosemirror-interactive.txt +3 -1
- package/src/aria-snapshots/prosemirror-raw.txt +4 -1
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/cdp-relay.ts +17 -5
- package/src/cli-help.test.ts +63 -0
- package/src/cli.ts +80 -28
- package/src/executor.ts +65 -15
- package/src/ghost-cursor-client.ts +221 -96
- package/src/{recording-ghost-cursor.ts → ghost-cursor-controller.ts} +50 -34
- package/src/ghost-cursor.ts +54 -41
- package/src/mcp.ts +6 -1
- package/src/on-mouse-action.test.ts +30 -0
- package/src/performance-examples.ts +186 -0
- package/src/react-source.ts +310 -24
- package/src/relay-core.test.ts +117 -0
- package/src/relay-session.test.ts +36 -10
- package/src/screen-recording.ts +23 -10
- package/src/skill.md +33 -9
- package/src/snapshots/shadcn-ui-accessibility-full.md +6 -3
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +2 -0
- package/dist/recording-ghost-cursor.d.ts.map +0 -1
- package/dist/recording-ghost-cursor.js +0 -79
- package/dist/recording-ghost-cursor.js.map +0 -1
package/src/skill.md
CHANGED
|
@@ -93,7 +93,7 @@ playwriter -s 1 -e 'await state.page.click("button")'
|
|
|
93
93
|
playwriter -s 1 -e 'await state.page.title()'
|
|
94
94
|
|
|
95
95
|
# Take a screenshot
|
|
96
|
-
playwriter -s 1 -e 'await state.page.screenshot({ path: "screenshot.png", scale: "css" })'
|
|
96
|
+
playwriter -s 1 -e 'await state.page.screenshot({ path: "/absolute/path/to/screenshot.png", scale: "css" })'
|
|
97
97
|
|
|
98
98
|
# Get accessibility snapshot
|
|
99
99
|
playwriter -s 1 -e 'await snapshot({ page: state.page })'
|
|
@@ -129,6 +129,16 @@ console.log({ title, url });
|
|
|
129
129
|
- **Heredoc** (`<<'EOF'`): best for multiline code. The quoted `'EOF'` delimiter disables all bash expansion. Any character works inside, including `$`, backticks, and single quotes.
|
|
130
130
|
- **`$'...'`**: allows `\'` escaping but `\n`, `\t`, `\\` become special — conflicts with JS regex patterns.
|
|
131
131
|
|
|
132
|
+
### Execute from file
|
|
133
|
+
|
|
134
|
+
For longer scripts, use `-f` instead of `-e` to execute JavaScript from a file:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
playwriter -s 1 -f script.js
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The file is read from disk and executed in the same sandbox as `-e`. All context variables (`state`, `page`, `context`, etc.) are available. `-e` and `-f` cannot be used together.
|
|
141
|
+
|
|
132
142
|
### Debugging playwriter issues
|
|
133
143
|
|
|
134
144
|
If some internal critical error happens you can read the relay server logs to understand the issue. The log file is located in the user home directory:
|
|
@@ -209,6 +219,7 @@ You can collaborate with the user - they can help with captchas, difficult eleme
|
|
|
209
219
|
- **Wait for load**: use `state.page.waitForLoadState('domcontentloaded')` not `state.page.waitForEvent('load')` - waitForEvent times out if already loaded
|
|
210
220
|
- **Minimize timeouts**: prefer proper waits (`waitForSelector`, `waitForPageLoad`) over `state.page.waitForTimeout()`. Short timeouts (1-2s) are acceptable for non-deterministic events like animations, tab opens, or async UI updates where no specific selector is available
|
|
211
221
|
- **Snapshot before screenshot**: always use `snapshot()` first to understand page state (text-based, fast, cheap). Only use `screenshot` when you specifically need visual/spatial information. Never take a screenshot just to check if a page loaded or to read text content — snapshot gives you that instantly without burning image tokens
|
|
222
|
+
- **Always use absolute file paths for Playwright artifact APIs**: for `page.screenshot({ path })`, `locator.screenshot({ path })`, `elementHandle.screenshot({ path })`, `page.pdf({ path })`, `download.saveAs(path)`, and `video.saveAs(path)`, always pass an absolute path. Relative paths are resolved by Playwright client internals, not the sandboxed `fs`, so they may use the relay server cwd instead of your session cwd.
|
|
212
223
|
- **Snapshot replaces page.evaluate() for inspection**: do NOT write `page.evaluate()` calls to manually query class names, bounding boxes, child counts, or visibility flags. `snapshot()` already shows every interactive element with its text, role, and a ready-to-use locator. If you catch yourself writing `document.querySelector` or `getBoundingClientRect` inside evaluate — stop and use `snapshot()` instead. Reserve `page.evaluate()` for actions that modify page state (e.g., `localStorage.clear()`, scroll manipulation) or extract non-DOM data (e.g., `window.__CONFIG__`)
|
|
213
224
|
|
|
214
225
|
## interaction feedback loop
|
|
@@ -601,7 +612,7 @@ Instead, use simpler alternatives (single download via `a.click()`, store data i
|
|
|
601
612
|
|
|
602
613
|
```js
|
|
603
614
|
const [download] = await Promise.all([state.page.waitForEvent('download'), state.page.click('button.download')])
|
|
604
|
-
await download.saveAs(`/
|
|
615
|
+
await download.saveAs(`/absolute/path/${download.suggestedFilename()}`)
|
|
605
616
|
```
|
|
606
617
|
|
|
607
618
|
**iFrames** - two approaches depending on what you need:
|
|
@@ -754,6 +765,19 @@ const source = await getReactSource({ locator: state.page.locator('[data-testid=
|
|
|
754
765
|
// => { fileName, lineNumber, columnNumber, componentName }
|
|
755
766
|
```
|
|
756
767
|
|
|
768
|
+
**getReactComponentInfo** - get best-effort React component info for an element. Returns `null` for non-React elements and never throws just because an element was not rendered by React. Source locations are usually only available in React dev builds. Props are sanitized and truncated so functions, DOM nodes, circular refs, and huge objects do not flood the output.
|
|
769
|
+
|
|
770
|
+
```js
|
|
771
|
+
const info = await getReactComponentInfo({ locator: state.page.locator('[data-testid="submit-btn"]') })
|
|
772
|
+
// => { componentName, source, hierarchy, props } | null
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**inspectPinnedElement** - inspect a Playwriter pinned element and print the element `outerHTML` plus React component info when available. Used by the in-page toolbar and right-click copy flow.
|
|
776
|
+
|
|
777
|
+
```js
|
|
778
|
+
await inspectPinnedElement('https://example.com', 'globalThis.playwriterPinnedElem1')
|
|
779
|
+
```
|
|
780
|
+
|
|
757
781
|
**getStylesForLocator** - inspect CSS styles applied to an element, like browser DevTools "Styles" panel. Useful for debugging styling issues, finding where a CSS property is defined (file:line), and checking inherited styles. Returns selector, source location, and declarations for each matching rule. ALWAYS fetch `https://playwriter.dev/resources/styles-api.md` first with curl or webfetch tool.
|
|
758
782
|
|
|
759
783
|
```js
|
|
@@ -806,7 +830,7 @@ await screenshotWithAccessibilityLabels({ page: state.page })
|
|
|
806
830
|
|
|
807
831
|
Labels are color-coded: yellow=links, orange=buttons, coral=inputs, pink=checkboxes, peach=sliders, salmon=menus, amber=tabs.
|
|
808
832
|
|
|
809
|
-
**resizeImageForAgent** - shrink an image so it consumes fewer tokens when read back into context. The resized image is automatically included in the response (visible to the LLM). `await resizeImageForAgent({ input: '
|
|
833
|
+
**resizeImageForAgent** - shrink an image so it consumes fewer tokens when read back into context. The resized image is automatically included in the response (visible to the LLM). `await resizeImageForAgent({ input: '/absolute/path/to/screenshot.png' })`. Also accepts `width`, `height`, `maxDimension`, `quality`, `format` (default: `'png'`), `output`. Alias: `resizeImage`.
|
|
810
834
|
|
|
811
835
|
**recording.start / recording.stop** - record the page as a video at native FPS (30-60fps). Uses `chrome.tabCapture` so **recording survives page navigation**. Auto-overlays a ghost cursor that follows mouse actions. Requires user to have clicked the Playwriter extension icon on the tab. Auto-resizes viewport to 16:9 (override with `aspectRatio: null`). Auto-stops after 15 min (override with `maxDurationMs`).
|
|
812
836
|
|
|
@@ -815,7 +839,7 @@ For demos, use interaction methods (`locator.click()`, `page.mouse.move()`) inst
|
|
|
815
839
|
```js
|
|
816
840
|
await recording.start({
|
|
817
841
|
page: state.page,
|
|
818
|
-
outputPath: '
|
|
842
|
+
outputPath: '/absolute/path/to/recording.mp4',
|
|
819
843
|
frameRate: 30, // default
|
|
820
844
|
audio: false, // default (tab audio)
|
|
821
845
|
videoBitsPerSecond: 2500000,
|
|
@@ -833,11 +857,11 @@ state.recordingResult = await recording.stop({ page: state.page })
|
|
|
833
857
|
// Other: recording.isRecording({ page }), recording.cancel({ page })
|
|
834
858
|
```
|
|
835
859
|
|
|
836
|
-
**ghostCursor.show / ghostCursor.hide** -
|
|
860
|
+
**ghostCursor.show / ghostCursor.hide** - the ghost cursor overlay is always on: the extension injects it on every Playwriter-attached tab and it stays visible at the last spot Playwright clicked or moved. These methods only matter if you want to change the cursor style or temporarily hide it:
|
|
837
861
|
|
|
838
862
|
```js
|
|
839
|
-
await ghostCursor.show({ page: state.page, style: '
|
|
840
|
-
await ghostCursor.hide({ page: state.page })
|
|
863
|
+
await ghostCursor.show({ page: state.page, style: 'screenstudio' }) // 'minimal' (default), 'dot', 'screenstudio'
|
|
864
|
+
await ghostCursor.hide({ page: state.page }) // hide until next show() or hard navigation
|
|
841
865
|
```
|
|
842
866
|
|
|
843
867
|
**createDemoVideo** - speeds up idle sections (time between execute() calls) while keeping interactions at normal speed. Requires `ffmpeg`/`ffprobe`. Timestamps are tracked automatically during recording and returned by `recording.stop()`. **Timeout**: can take 60–120+ seconds, always pass `--timeout 120000` or higher.
|
|
@@ -869,7 +893,7 @@ await el.click()
|
|
|
869
893
|
Always use `scale: 'css'` to avoid 2-4x larger images on high-DPI displays:
|
|
870
894
|
|
|
871
895
|
```js
|
|
872
|
-
await state.page.screenshot({ path: 'shot.png', scale: 'css' })
|
|
896
|
+
await state.page.screenshot({ path: '/absolute/path/to/shot.png', scale: 'css' })
|
|
873
897
|
```
|
|
874
898
|
|
|
875
899
|
If you want to read back the image file into context, resize it first so it consumes fewer tokens:
|
|
@@ -1048,7 +1072,7 @@ await state.page.setViewportSize({ width: 1280, height: 720 })
|
|
|
1048
1072
|
### region screenshot (zoom equivalent)
|
|
1049
1073
|
|
|
1050
1074
|
```js
|
|
1051
|
-
await state.page.screenshot({ path: 'region.png', scale: 'css', clip: { x: 100, y: 200, width: 400, height: 300 } })
|
|
1075
|
+
await state.page.screenshot({ path: '/absolute/path/to/region.png', scale: 'css', clip: { x: 100, y: 200, width: 400, height: 300 } })
|
|
1052
1076
|
```
|
|
1053
1077
|
|
|
1054
1078
|
Prefer locator-based actions over coordinates — locators are stable across scroll/resize, auto-wait for elements, and don't require screenshot round-trips that burn ~800 image tokens per cycle.
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
- textbox "Comments" [id="checkout-7j9-optional-comments"]
|
|
52
52
|
- role=button[name="Submit"]
|
|
53
53
|
- role=button[name="Cancel"] >> nth=0
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
- text: "CN"
|
|
55
|
+
- text: "LR"
|
|
56
|
+
- text: "ER"
|
|
57
57
|
- text: "No Team Members"
|
|
58
58
|
- text: "Invite your team to collaborate on this project."
|
|
59
59
|
- role=button[name="Invite Members"]
|
|
@@ -176,5 +176,8 @@
|
|
|
176
176
|
- role=link[name="GitHub"]
|
|
177
177
|
- text: "."
|
|
178
178
|
- region "Notifications alt+T"
|
|
179
|
+
- toolbar "Playwriter tools":
|
|
180
|
+
- role=button[name="Pin element — click any element to copy its Playwriter reference"]
|
|
181
|
+
- role=button[name="Close Playwriter toolbar"]
|
|
179
182
|
|
|
180
183
|
use refToLocator({ ref: 'e3' }) to get locators for ref strings.
|
|
@@ -115,5 +115,7 @@
|
|
|
115
115
|
- role=link[name="Vercel"]
|
|
116
116
|
- role=link[name="GitHub"]
|
|
117
117
|
- region "Notifications alt+T"
|
|
118
|
+
- role=button[name="Pin element — click any element to copy its Playwriter reference"]
|
|
119
|
+
- role=button[name="Close Playwriter toolbar"]
|
|
118
120
|
|
|
119
121
|
use refToLocator({ ref: 'e3' }) to get locators for ref strings.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recording-ghost-cursor.d.ts","sourceRoot":"","sources":["../src/recording-ghost-cursor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACnE,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAA;AAE1B,UAAU,0BAA0B;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CACpC;AAED,UAAU,sBAAsB;IAC9B,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,8BAA8B;IACzC,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA6C;IACvF,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqC;IAC5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;gBAEvC,OAAO,EAAE;QAAE,MAAM,EAAE,0BAA0B,CAAA;KAAE;IAI3D,0BAA0B,CAAC,OAAO,EAAE;QAClC,OAAO,EAAE,cAAc,CAAA;QACvB,WAAW,EAAE,IAAI,CAAA;QACjB,MAAM,CAAC,EAAE,sBAAsB,CAAA;KAChC,GAAG,IAAI;IAoBF,kBAAkB,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmC1D,mBAAmB,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3D,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,wBAAwB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtF,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAInD"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encapsulates ghost cursor lifecycle for recording sessions.
|
|
3
|
-
* Keeps onMouseAction chaining/restoration isolated from executor logic.
|
|
4
|
-
*/
|
|
5
|
-
import { applyGhostCursorMouseAction, disableGhostCursor, enableGhostCursor, } from './ghost-cursor.js';
|
|
6
|
-
export class RecordingGhostCursorController {
|
|
7
|
-
previousMouseActionByPage = new WeakMap();
|
|
8
|
-
cursorApplyQueueByPage = new WeakMap();
|
|
9
|
-
logger;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.logger = options.logger;
|
|
12
|
-
}
|
|
13
|
-
resolveRecordingTargetPage(options) {
|
|
14
|
-
const { context, defaultPage, target } = options;
|
|
15
|
-
if (target?.page) {
|
|
16
|
-
return target.page;
|
|
17
|
-
}
|
|
18
|
-
if (target?.sessionId) {
|
|
19
|
-
const pageForSession = context.pages().find((candidatePage) => {
|
|
20
|
-
return candidatePage.sessionId() === target.sessionId;
|
|
21
|
-
});
|
|
22
|
-
if (pageForSession) {
|
|
23
|
-
return pageForSession;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return defaultPage;
|
|
27
|
-
}
|
|
28
|
-
async enableForRecording(options) {
|
|
29
|
-
const { page } = options;
|
|
30
|
-
try {
|
|
31
|
-
await enableGhostCursor({ page });
|
|
32
|
-
if (!this.previousMouseActionByPage.has(page)) {
|
|
33
|
-
this.previousMouseActionByPage.set(page, page.onMouseAction);
|
|
34
|
-
}
|
|
35
|
-
const previousMouseAction = this.previousMouseActionByPage.get(page);
|
|
36
|
-
page.onMouseAction = async (event) => {
|
|
37
|
-
const pendingCursorApply = this.cursorApplyQueueByPage.get(page) || Promise.resolve();
|
|
38
|
-
const nextCursorApply = pendingCursorApply
|
|
39
|
-
.then(async () => {
|
|
40
|
-
await applyGhostCursorMouseAction({ page, event });
|
|
41
|
-
})
|
|
42
|
-
.catch((error) => {
|
|
43
|
-
this.logger.error('[playwriter] Failed to apply ghost cursor action', error);
|
|
44
|
-
});
|
|
45
|
-
this.cursorApplyQueueByPage.set(page, nextCursorApply);
|
|
46
|
-
if (!previousMouseAction) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
await previousMouseAction(event);
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
page.onMouseAction = this.previousMouseActionByPage.get(page) ?? null;
|
|
54
|
-
this.previousMouseActionByPage.delete(page);
|
|
55
|
-
this.logger.error('[playwriter] Failed to enable ghost cursor', error);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async disableForRecording(options) {
|
|
59
|
-
const { page } = options;
|
|
60
|
-
page.onMouseAction = this.previousMouseActionByPage.get(page) ?? null;
|
|
61
|
-
this.previousMouseActionByPage.delete(page);
|
|
62
|
-
this.cursorApplyQueueByPage.delete(page);
|
|
63
|
-
try {
|
|
64
|
-
await disableGhostCursor({ page });
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
this.logger.error('[playwriter] Failed to disable ghost cursor', error);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async show(options) {
|
|
71
|
-
const { page, cursorOptions } = options;
|
|
72
|
-
await enableGhostCursor({ page, cursorOptions });
|
|
73
|
-
}
|
|
74
|
-
async hide(options) {
|
|
75
|
-
const { page } = options;
|
|
76
|
-
await disableGhostCursor({ page });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
//# sourceMappingURL=recording-ghost-cursor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recording-ghost-cursor.js","sourceRoot":"","sources":["../src/recording-ghost-cursor.ts"],"names":[],"mappings":"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"}
|