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.
Files changed (79) hide show
  1. package/dist/bippy.js +5 -5
  2. package/dist/cdp-relay.d.ts.map +1 -1
  3. package/dist/cdp-relay.js +17 -5
  4. package/dist/cdp-relay.js.map +1 -1
  5. package/dist/cli-help.test.d.ts +2 -0
  6. package/dist/cli-help.test.d.ts.map +1 -0
  7. package/dist/cli-help.test.js +53 -0
  8. package/dist/cli-help.test.js.map +1 -0
  9. package/dist/cli.js +74 -25
  10. package/dist/cli.js.map +1 -1
  11. package/dist/executor.d.ts +1 -0
  12. package/dist/executor.d.ts.map +1 -1
  13. package/dist/executor.js +55 -12
  14. package/dist/executor.js.map +1 -1
  15. package/dist/extension/background.js +675 -27
  16. package/dist/extension/manifest.json +1 -1
  17. package/dist/ghost-cursor-client.js +170 -83
  18. package/dist/{recording-ghost-cursor.d.ts → ghost-cursor-controller.d.ts} +15 -10
  19. package/dist/ghost-cursor-controller.d.ts.map +1 -0
  20. package/dist/ghost-cursor-controller.js +98 -0
  21. package/dist/ghost-cursor-controller.js.map +1 -0
  22. package/dist/ghost-cursor.d.ts.map +1 -1
  23. package/dist/ghost-cursor.js +42 -26
  24. package/dist/ghost-cursor.js.map +1 -1
  25. package/dist/mcp.d.ts.map +1 -1
  26. package/dist/mcp.js +6 -1
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/on-mouse-action.test.js +25 -0
  29. package/dist/on-mouse-action.test.js.map +1 -1
  30. package/dist/performance-examples.d.ts +5 -0
  31. package/dist/performance-examples.d.ts.map +1 -0
  32. package/dist/performance-examples.js +112 -0
  33. package/dist/performance-examples.js.map +1 -0
  34. package/dist/performance-profiling.md +417 -0
  35. package/dist/prompt.md +22 -8
  36. package/dist/react-source.d.ts +44 -0
  37. package/dist/react-source.d.ts.map +1 -1
  38. package/dist/react-source.js +207 -20
  39. package/dist/react-source.js.map +1 -1
  40. package/dist/readability.js +1 -1
  41. package/dist/relay-core.test.d.ts.map +1 -1
  42. package/dist/relay-core.test.js +101 -1
  43. package/dist/relay-core.test.js.map +1 -1
  44. package/dist/relay-session.test.js +34 -6
  45. package/dist/relay-session.test.js.map +1 -1
  46. package/dist/screen-recording.d.ts +2 -2
  47. package/dist/screen-recording.d.ts.map +1 -1
  48. package/dist/screen-recording.js +19 -7
  49. package/dist/screen-recording.js.map +1 -1
  50. package/dist/selector-generator.js +1 -1
  51. package/package.json +7 -7
  52. package/src/aria-snapshots/github-interactive.txt +5 -3
  53. package/src/aria-snapshots/github-raw.txt +8 -5
  54. package/src/aria-snapshots/hackernews-interactive.txt +241 -238
  55. package/src/aria-snapshots/hackernews-raw.txt +269 -265
  56. package/src/aria-snapshots/prosemirror-interactive.txt +3 -1
  57. package/src/aria-snapshots/prosemirror-raw.txt +4 -1
  58. package/src/assets/aria-labels-hacker-news.png +0 -0
  59. package/src/assets/aria-labels-old-reddit.png +0 -0
  60. package/src/cdp-relay.ts +17 -5
  61. package/src/cli-help.test.ts +63 -0
  62. package/src/cli.ts +80 -28
  63. package/src/executor.ts +65 -15
  64. package/src/ghost-cursor-client.ts +221 -96
  65. package/src/{recording-ghost-cursor.ts → ghost-cursor-controller.ts} +50 -34
  66. package/src/ghost-cursor.ts +54 -41
  67. package/src/mcp.ts +6 -1
  68. package/src/on-mouse-action.test.ts +30 -0
  69. package/src/performance-examples.ts +186 -0
  70. package/src/react-source.ts +310 -24
  71. package/src/relay-core.test.ts +117 -0
  72. package/src/relay-session.test.ts +36 -10
  73. package/src/screen-recording.ts +23 -10
  74. package/src/skill.md +33 -9
  75. package/src/snapshots/shadcn-ui-accessibility-full.md +6 -3
  76. package/src/snapshots/shadcn-ui-accessibility-interactive.md +2 -0
  77. package/dist/recording-ghost-cursor.d.ts.map +0 -1
  78. package/dist/recording-ghost-cursor.js +0 -79
  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(`/tmp/${download.suggestedFilename()}`)
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: './screenshot.png' })`. Also accepts `width`, `height`, `maxDimension`, `quality`, `format` (default: `'png'`), `output`. Alias: `resizeImage`.
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: './recording.mp4',
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** - show/hide cursor overlay for screenshots and demos:
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: 'minimal' }) // 'minimal', 'dot', 'screenstudio'
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
- - image "@shadcn"
55
- - image "@maxleiter"
56
- - image "@evilrabbit"
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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"}