playwriter 0.1.0 → 0.3.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-log.d.ts +4 -1
- package/dist/cdp-log.d.ts.map +1 -1
- package/dist/cdp-log.js +39 -2
- package/dist/cdp-log.js.map +1 -1
- package/dist/cdp-log.test.d.ts +2 -0
- package/dist/cdp-log.test.d.ts.map +1 -0
- package/dist/cdp-log.test.js +109 -0
- package/dist/cdp-log.test.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +120 -11
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cli-help.test.js +22 -0
- package/dist/cli-help.test.js.map +1 -1
- package/dist/cli.js +69 -25
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +4 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +140 -33
- package/dist/executor.js.map +1 -1
- package/dist/extension/background.js +343 -62
- package/dist/extension/manifest.json +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/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 +51 -18
- 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-client.d.ts +11 -0
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +46 -1
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.js +10 -6
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-session.test.js +43 -7
- package/dist/relay-session.test.js.map +1 -1
- package/dist/relay-state.test.js +57 -1
- package/dist/relay-state.test.js.map +1 -1
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +19 -4
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/dist/start-relay-server.d.ts +1 -1
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +23 -1
- package/dist/start-relay-server.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/cdp-log.test.ts +131 -0
- package/src/cdp-log.ts +44 -2
- package/src/cdp-relay.ts +127 -10
- package/src/cli-help.test.ts +22 -0
- package/src/cli.ts +74 -24
- package/src/executor.ts +166 -39
- package/src/mcp.ts +6 -1
- package/src/performance-examples.ts +186 -0
- package/src/react-source.ts +310 -24
- package/src/relay-client.ts +62 -5
- package/src/relay-core.test.ts +10 -6
- package/src/relay-session.test.ts +45 -11
- package/src/relay-state.test.ts +67 -1
- package/src/screen-recording.ts +20 -4
- package/src/skill.md +62 -19
- package/src/start-relay-server.ts +22 -1
- package/src/utils.ts +5 -0
package/dist/prompt.md
CHANGED
|
@@ -41,10 +41,19 @@ You can collaborate with the user - they can help with captchas, difficult eleme
|
|
|
41
41
|
- `page` - a default page (may be shared with other agents). Prefer creating your own page and storing it in `state` (see "working with pages")
|
|
42
42
|
- `context` - browser context, access all pages via `context.pages()`
|
|
43
43
|
- `require` - load Node.js modules (e.g., `const fs = require('node:fs')`). ESM `import` is not available in the sandbox
|
|
44
|
-
- Node.js globals: `setTimeout`, `setInterval`, `fetch`, `URL`, `Buffer`, `crypto`, etc.
|
|
44
|
+
- Node.js globals: `setTimeout`, `setInterval`, `fetch`, `URL`, `Buffer`, `crypto`, `process`, etc.
|
|
45
|
+
|
|
46
|
+
**Not available in the sandbox:** `__dirname`, `__filename`, `import`.
|
|
45
47
|
|
|
46
48
|
**Important:** `state` is **session-isolated** but pages are **shared** across all sessions. See "working with pages" for how to avoid interference.
|
|
47
49
|
|
|
50
|
+
**Sandboxed `fs` write restrictions:** `require('node:fs')` is scoped. Writes (writeFileSync, mkdirSync, etc.) only succeed in:
|
|
51
|
+
- The **directory where `playwriter` CLI was invoked** (the session's cwd)
|
|
52
|
+
- `/tmp`
|
|
53
|
+
- The OS temp directory (`os.tmpdir()`, e.g. `/var/folders/.../T/` on macOS)
|
|
54
|
+
|
|
55
|
+
Writing to any other path (e.g. `~/Downloads`, `~/Desktop`) throws `EPERM: operation not permitted, access outside allowed directories`. To save files elsewhere, write to a temp path first, then move the file using a shell command outside the sandbox.
|
|
56
|
+
|
|
48
57
|
## rules
|
|
49
58
|
|
|
50
59
|
- **Initialize state.page first**: see "working with pages" — at the start of a task, assign `state.page` (reuse `about:blank` or create one) and use `state.page` for all automation steps.
|
|
@@ -53,10 +62,12 @@ You can collaborate with the user - they can help with captchas, difficult eleme
|
|
|
53
62
|
- **No bringToFront**: never call unless user asks - it's disruptive and unnecessary, you can interact with background pages
|
|
54
63
|
- **Check state after actions**: always verify page state after clicking/submitting (see next section)
|
|
55
64
|
- **Clean up listeners**: call `state.page.removeAllListeners()` at end of message to prevent leaks
|
|
65
|
+
- **Always print page logs after every action**: call `getLatestLogs({ page: state.page, sinceLastCall: true })` after every goto, click, or submit to catch console errors and warnings. Do not manually collect `page.on('console')` events; manual listeners miss logs emitted before the listener is attached. The first `sinceLastCall` call returns all buffered logs including startup and hydration errors.
|
|
56
66
|
- **CDP sessions**: use `getCDPSession({ page: state.page })` not `state.page.context().newCDPSession()` - NEVER use `newCDPSession()` method, it doesn't work through playwriter relay
|
|
57
67
|
- **Wait for load**: use `state.page.waitForLoadState('domcontentloaded')` not `state.page.waitForEvent('load')` - waitForEvent times out if already loaded
|
|
58
68
|
- **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
|
|
59
69
|
- **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
|
|
70
|
+
- **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.
|
|
60
71
|
- **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__`)
|
|
61
72
|
|
|
62
73
|
## interaction feedback loop
|
|
@@ -64,18 +75,21 @@ You can collaborate with the user - they can help with captchas, difficult eleme
|
|
|
64
75
|
Every browser interaction must follow **observe → act → observe**. Never chain multiple actions blindly.
|
|
65
76
|
|
|
66
77
|
1. **Open page** — get or create your page, navigate to URL
|
|
67
|
-
2. **Observe** — print `state.page.url()` + `snapshot()`. Always print URL — pages can redirect unexpectedly.
|
|
78
|
+
2. **Observe** — print `state.page.url()` + `snapshot()` + `getLatestLogs({ sinceLastCall: true })`. Always print URL — pages can redirect unexpectedly.
|
|
68
79
|
3. **Check** — if page isn't ready (loading, wrong URL, content missing), wait and observe again
|
|
69
80
|
4. **Act** — perform one action (click, type, submit)
|
|
70
|
-
5. **Observe again** — print URL + snapshot to verify the action's effect
|
|
81
|
+
5. **Observe again** — print URL + snapshot + page logs to verify the action's effect
|
|
71
82
|
6. **Repeat** from step 3 until task is complete
|
|
72
83
|
|
|
84
|
+
**Always print page logs after every action** using `getLatestLogs({ sinceLastCall: true })`. This returns only new console messages and errors since the last call, so you catch hydration errors, failed network requests, and runtime exceptions without duplicates. The first call returns all buffered logs from the page, including logs emitted before your script started.
|
|
85
|
+
|
|
73
86
|
```js
|
|
74
87
|
// Each step should be a separate execute call:
|
|
75
88
|
// Step 1: navigate + observe
|
|
76
89
|
state.page = context.pages().find((p) => p.url() === 'about:blank') ?? (await context.newPage())
|
|
77
90
|
await state.page.goto('https://example.com', { waitUntil: 'domcontentloaded' })
|
|
78
91
|
console.log('URL:', state.page.url())
|
|
92
|
+
console.log('Page logs:', await getLatestLogs({ page: state.page, sinceLastCall: true }))
|
|
79
93
|
await snapshot({ page: state.page }).then(console.log)
|
|
80
94
|
```
|
|
81
95
|
|
|
@@ -83,25 +97,26 @@ await snapshot({ page: state.page }).then(console.log)
|
|
|
83
97
|
// Step 2: act + observe
|
|
84
98
|
await state.page.locator('button:has-text("Submit")').click()
|
|
85
99
|
console.log('URL:', state.page.url())
|
|
100
|
+
console.log('Page logs:', await getLatestLogs({ page: state.page, sinceLastCall: true }))
|
|
86
101
|
await snapshot({ page: state.page }).then(console.log)
|
|
87
102
|
```
|
|
88
103
|
|
|
89
104
|
If nothing changed after an action, try `waitForPageLoad({ page: state.page, timeout: 3000 })` or you may have clicked the wrong element.
|
|
90
105
|
|
|
91
|
-
**Deeper observation** — when snapshots aren't enough to understand what happened, combine
|
|
106
|
+
**Deeper observation** — when snapshots aren't enough to understand what happened, combine snapshot with filtered logs:
|
|
92
107
|
|
|
93
108
|
```js
|
|
94
|
-
//
|
|
109
|
+
// Search for specific errors in all logs (not just since last call)
|
|
95
110
|
const errors = await getLatestLogs({ page: state.page, search: /error|fail/i, count: 20 })
|
|
96
111
|
|
|
97
|
-
// Combine snapshot + logs for full picture
|
|
112
|
+
// Combine snapshot + filtered logs for full picture
|
|
98
113
|
const snap = await snapshot({ page: state.page, search: /dialog|error|message/ })
|
|
99
114
|
const logs = await getLatestLogs({ page: state.page, search: /error/i, count: 10 })
|
|
100
115
|
console.log('UI:', snap)
|
|
101
116
|
console.log('Logs:', logs)
|
|
102
117
|
```
|
|
103
118
|
|
|
104
|
-
Use `getLatestLogs()` for
|
|
119
|
+
Use `getLatestLogs({ sinceLastCall: true })` after every action, `getLatestLogs({ search })` for targeted debugging, `state.page.url()` for navigation, screenshots only for visual layout issues.
|
|
105
120
|
|
|
106
121
|
## common mistakes to avoid
|
|
107
122
|
|
|
@@ -449,7 +464,7 @@ Instead, use simpler alternatives (single download via `a.click()`, store data i
|
|
|
449
464
|
|
|
450
465
|
```js
|
|
451
466
|
const [download] = await Promise.all([state.page.waitForEvent('download'), state.page.click('button.download')])
|
|
452
|
-
await download.saveAs(`/
|
|
467
|
+
await download.saveAs(`/absolute/path/${download.suggestedFilename()}`)
|
|
453
468
|
```
|
|
454
469
|
|
|
455
470
|
**iFrames** - two approaches depending on what you need:
|
|
@@ -517,17 +532,22 @@ For carousels or lazy-loaded galleries, you may need to click navigation arrows
|
|
|
517
532
|
|
|
518
533
|
## utility functions
|
|
519
534
|
|
|
520
|
-
**getLatestLogs** - retrieve captured browser console logs (up to 5000 per page
|
|
535
|
+
**getLatestLogs** - retrieve captured browser console logs and page errors (up to 5000 per page):
|
|
536
|
+
|
|
537
|
+
Always use this helper when inspecting browser logs. Do not attach new `page.on('console')` listeners for debugging because they only see future events and can miss logs emitted during page startup or hydration.
|
|
538
|
+
|
|
539
|
+
Use `sinceLastCall: true` after every action to get only new logs since the previous call. The first call returns all buffered logs including pre-existing ones. Logs persist across navigations so you never miss errors from page transitions.
|
|
521
540
|
|
|
522
541
|
```js
|
|
523
|
-
await getLatestLogs({ page?, count?, search? })
|
|
524
|
-
//
|
|
542
|
+
await getLatestLogs({ page?, count?, search?, sinceLastCall? })
|
|
543
|
+
// After every action: get only new logs
|
|
544
|
+
const newLogs = await getLatestLogs({ page: state.page, sinceLastCall: true })
|
|
545
|
+
// Search all logs (ignores cursor):
|
|
525
546
|
const errors = await getLatestLogs({ search: /error/i, count: 50 })
|
|
526
|
-
const pageLogs = await getLatestLogs({ page: state.page })
|
|
547
|
+
const pageLogs = await getLatestLogs({ page: state.page, count: 100 })
|
|
548
|
+
const hydrationErrors = await getLatestLogs({ page: state.page, search: /hydration|pageerror|React/i })
|
|
527
549
|
```
|
|
528
550
|
|
|
529
|
-
For custom log collection across runs, store in state: `state.logs = []; state.page.on('console', m => state.logs.push(m.text()))`
|
|
530
|
-
|
|
531
551
|
**getCleanHTML** - get cleaned HTML from a locator or page, with search and diffing:
|
|
532
552
|
|
|
533
553
|
```js
|
|
@@ -602,6 +622,19 @@ const source = await getReactSource({ locator: state.page.locator('[data-testid=
|
|
|
602
622
|
// => { fileName, lineNumber, columnNumber, componentName }
|
|
603
623
|
```
|
|
604
624
|
|
|
625
|
+
**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.
|
|
626
|
+
|
|
627
|
+
```js
|
|
628
|
+
const info = await getReactComponentInfo({ locator: state.page.locator('[data-testid="submit-btn"]') })
|
|
629
|
+
// => { componentName, source, hierarchy, props } | null
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**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.
|
|
633
|
+
|
|
634
|
+
```js
|
|
635
|
+
await inspectPinnedElement('https://example.com', 'globalThis.playwriterPinnedElem1')
|
|
636
|
+
```
|
|
637
|
+
|
|
605
638
|
**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.
|
|
606
639
|
|
|
607
640
|
```js
|
|
@@ -654,7 +687,7 @@ await screenshotWithAccessibilityLabels({ page: state.page })
|
|
|
654
687
|
|
|
655
688
|
Labels are color-coded: yellow=links, orange=buttons, coral=inputs, pink=checkboxes, peach=sliders, salmon=menus, amber=tabs.
|
|
656
689
|
|
|
657
|
-
**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: '
|
|
690
|
+
**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`.
|
|
658
691
|
|
|
659
692
|
**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`).
|
|
660
693
|
|
|
@@ -663,7 +696,7 @@ For demos, use interaction methods (`locator.click()`, `page.mouse.move()`) inst
|
|
|
663
696
|
```js
|
|
664
697
|
await recording.start({
|
|
665
698
|
page: state.page,
|
|
666
|
-
outputPath: '
|
|
699
|
+
outputPath: '/absolute/path/to/recording.mp4',
|
|
667
700
|
frameRate: 30, // default
|
|
668
701
|
audio: false, // default (tab audio)
|
|
669
702
|
videoBitsPerSecond: 2500000,
|
|
@@ -717,7 +750,7 @@ await el.click()
|
|
|
717
750
|
Always use `scale: 'css'` to avoid 2-4x larger images on high-DPI displays:
|
|
718
751
|
|
|
719
752
|
```js
|
|
720
|
-
await state.page.screenshot({ path: 'shot.png', scale: 'css' })
|
|
753
|
+
await state.page.screenshot({ path: '/absolute/path/to/shot.png', scale: 'css' })
|
|
721
754
|
```
|
|
722
755
|
|
|
723
756
|
If you want to read back the image file into context, resize it first so it consumes fewer tokens:
|
|
@@ -896,7 +929,7 @@ await state.page.setViewportSize({ width: 1280, height: 720 })
|
|
|
896
929
|
### region screenshot (zoom equivalent)
|
|
897
930
|
|
|
898
931
|
```js
|
|
899
|
-
await state.page.screenshot({ path: 'region.png', scale: 'css', clip: { x: 100, y: 200, width: 400, height: 300 } })
|
|
932
|
+
await state.page.screenshot({ path: '/absolute/path/to/region.png', scale: 'css', clip: { x: 100, y: 200, width: 400, height: 300 } })
|
|
900
933
|
```
|
|
901
934
|
|
|
902
935
|
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.
|
package/dist/react-source.d.ts
CHANGED
|
@@ -6,8 +6,52 @@ export interface ReactSourceLocation {
|
|
|
6
6
|
columnNumber: number | null;
|
|
7
7
|
componentName: string | null;
|
|
8
8
|
}
|
|
9
|
+
export type ReactSerializedProp = string | number | boolean | null | ReactSerializedProp[] | {
|
|
10
|
+
[key: string]: ReactSerializedProp;
|
|
11
|
+
};
|
|
12
|
+
export interface ReactComponentHierarchyItem {
|
|
13
|
+
componentName: string | null;
|
|
14
|
+
source: Omit<ReactSourceLocation, 'componentName'> | null;
|
|
15
|
+
props: ReactSerializedProp;
|
|
16
|
+
}
|
|
17
|
+
export interface ReactComponentInfo {
|
|
18
|
+
componentName: string | null;
|
|
19
|
+
source: Omit<ReactSourceLocation, 'componentName'> | null;
|
|
20
|
+
hierarchy: ReactComponentHierarchyItem[];
|
|
21
|
+
props: ReactSerializedProp;
|
|
22
|
+
}
|
|
23
|
+
type ReactInspectableValue = string | number | boolean | bigint | symbol | null | undefined | object | ((...args: never[]) => ReactInspectableValue);
|
|
24
|
+
type BrowserElement = object;
|
|
25
|
+
interface BippySourceFrame {
|
|
26
|
+
fileName?: string | null;
|
|
27
|
+
lineNumber?: number | null;
|
|
28
|
+
columnNumber?: number | null;
|
|
29
|
+
functionName?: string | null;
|
|
30
|
+
}
|
|
31
|
+
interface BippyFiber {
|
|
32
|
+
return?: BippyFiber | null;
|
|
33
|
+
type?: ReactInspectableValue;
|
|
34
|
+
memoizedProps?: ReactInspectableValue;
|
|
35
|
+
}
|
|
36
|
+
interface BippyRuntime {
|
|
37
|
+
getFiberFromHostInstance(el: BrowserElement): BippyFiber | null;
|
|
38
|
+
getSource(fiber: BippyFiber): Promise<BippySourceFrame | null>;
|
|
39
|
+
getOwnerStack(fiber: BippyFiber): Promise<BippySourceFrame[]>;
|
|
40
|
+
getDisplayName(type: ReactInspectableValue): string | null;
|
|
41
|
+
isCompositeFiber(fiber: BippyFiber): boolean;
|
|
42
|
+
normalizeFileName(fileName: string): string;
|
|
43
|
+
isSourceFile(fileName: string): boolean;
|
|
44
|
+
}
|
|
45
|
+
declare global {
|
|
46
|
+
var __bippy: BippyRuntime | undefined;
|
|
47
|
+
}
|
|
9
48
|
export declare function getReactSource({ locator, cdp: cdpSession, }: {
|
|
10
49
|
locator: Locator | ElementHandle;
|
|
11
50
|
cdp: ICDPSession;
|
|
12
51
|
}): Promise<ReactSourceLocation | null>;
|
|
52
|
+
export declare function getReactComponentInfo({ locator, cdp: cdpSession, }: {
|
|
53
|
+
locator: Locator | ElementHandle;
|
|
54
|
+
cdp: ICDPSession;
|
|
55
|
+
}): Promise<ReactComponentInfo | null>;
|
|
56
|
+
export {};
|
|
13
57
|
//# sourceMappingURL=react-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-source.d.ts","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"react-source.d.ts","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"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"}
|
package/dist/react-source.js
CHANGED
|
@@ -11,22 +11,44 @@ function getBippyCode() {
|
|
|
11
11
|
bippyCode = fs.readFileSync(bippyPath, 'utf-8');
|
|
12
12
|
return bippyCode;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
async function getPageFromTarget(target) {
|
|
15
|
+
if ('page' in target) {
|
|
16
|
+
return target.page();
|
|
17
|
+
}
|
|
18
|
+
const frame = await target.ownerFrame();
|
|
19
|
+
if (!frame) {
|
|
20
|
+
throw new Error('Could not get frame from element handle');
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
return frame.page();
|
|
23
|
+
}
|
|
24
|
+
async function ensureBippy({ page, cdp }) {
|
|
25
|
+
const hasBippy = await page.evaluate(() => {
|
|
26
|
+
return !!globalThis.__bippy;
|
|
27
|
+
});
|
|
28
|
+
if (hasBippy) {
|
|
29
|
+
return;
|
|
24
30
|
}
|
|
25
|
-
const
|
|
31
|
+
const code = getBippyCode();
|
|
32
|
+
await cdp.send('Runtime.evaluate', { expression: code });
|
|
33
|
+
}
|
|
34
|
+
export async function getReactSource({ locator, cdp: cdpSession, }) {
|
|
35
|
+
const cdp = cdpSession;
|
|
36
|
+
const page = await getPageFromTarget(locator);
|
|
37
|
+
await ensureBippy({ page, cdp });
|
|
38
|
+
const evaluateReactSource = async (el) => {
|
|
26
39
|
const bippy = globalThis.__bippy;
|
|
27
40
|
if (!bippy) {
|
|
28
41
|
throw new Error('bippy not loaded');
|
|
29
42
|
}
|
|
43
|
+
// bippy.normalizeFileName strips "/app-pages-browser/" but not the parenthesized
|
|
44
|
+
// form "/(app-pages-browser)/" that Next.js webpack actually uses. This strips
|
|
45
|
+
// all webpack layer prefixes like (app-pages-browser), (ssr), (rsc), etc.
|
|
46
|
+
const cleanName = (name) => {
|
|
47
|
+
let f = bippy.normalizeFileName(name);
|
|
48
|
+
f = f.replace(/^\/?\([-\w]+\)\//, '');
|
|
49
|
+
f = f.replace(/^\.\//, '');
|
|
50
|
+
return f;
|
|
51
|
+
};
|
|
30
52
|
const fiber = bippy.getFiberFromHostInstance(el);
|
|
31
53
|
if (!fiber) {
|
|
32
54
|
return { _notFound: 'fiber' };
|
|
@@ -34,7 +56,7 @@ export async function getReactSource({ locator, cdp: cdpSession, }) {
|
|
|
34
56
|
const source = await bippy.getSource(fiber);
|
|
35
57
|
if (source) {
|
|
36
58
|
return {
|
|
37
|
-
fileName: source.fileName ?
|
|
59
|
+
fileName: source.fileName ? cleanName(source.fileName) : null,
|
|
38
60
|
lineNumber: source.lineNumber ?? null,
|
|
39
61
|
columnNumber: source.columnNumber ?? null,
|
|
40
62
|
componentName: source.functionName ?? bippy.getDisplayName(fiber.type) ?? null,
|
|
@@ -44,7 +66,7 @@ export async function getReactSource({ locator, cdp: cdpSession, }) {
|
|
|
44
66
|
for (const frame of ownerStack) {
|
|
45
67
|
if (frame.fileName && bippy.isSourceFile(frame.fileName)) {
|
|
46
68
|
return {
|
|
47
|
-
fileName:
|
|
69
|
+
fileName: cleanName(frame.fileName),
|
|
48
70
|
lineNumber: frame.lineNumber ?? null,
|
|
49
71
|
columnNumber: frame.columnNumber ?? null,
|
|
50
72
|
componentName: frame.functionName ?? null,
|
|
@@ -52,16 +74,181 @@ export async function getReactSource({ locator, cdp: cdpSession, }) {
|
|
|
52
74
|
}
|
|
53
75
|
}
|
|
54
76
|
return { _notFound: 'source' };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (result
|
|
58
|
-
|
|
77
|
+
};
|
|
78
|
+
const resolveResult = (result) => {
|
|
79
|
+
if (result?._notFound) {
|
|
80
|
+
if (result._notFound === 'fiber') {
|
|
81
|
+
console.warn('[getReactSource] no fiber found - is this a React element?');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.warn('[getReactSource] no source location found - is this a React dev build?');
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
59
87
|
}
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
if ('page' in locator) {
|
|
91
|
+
return resolveResult(await locator.evaluate(evaluateReactSource));
|
|
92
|
+
}
|
|
93
|
+
return resolveResult(await locator.evaluate(evaluateReactSource));
|
|
94
|
+
}
|
|
95
|
+
export async function getReactComponentInfo({ locator, cdp: cdpSession, }) {
|
|
96
|
+
const cdp = cdpSession;
|
|
97
|
+
const page = await getPageFromTarget(locator);
|
|
98
|
+
await ensureBippy({ page, cdp });
|
|
99
|
+
const evaluateReactComponentInfo = async (el) => {
|
|
100
|
+
const bippy = globalThis.__bippy;
|
|
101
|
+
if (!bippy) {
|
|
102
|
+
throw new Error('bippy not loaded');
|
|
103
|
+
}
|
|
104
|
+
// bippy.normalizeFileName strips "/app-pages-browser/" but not the parenthesized
|
|
105
|
+
// form "/(app-pages-browser)/" that Next.js webpack actually uses. This strips
|
|
106
|
+
// all webpack layer prefixes like (app-pages-browser), (ssr), (rsc), etc.
|
|
107
|
+
const cleanName = (name) => {
|
|
108
|
+
let f = bippy.normalizeFileName(name);
|
|
109
|
+
f = f.replace(/^\/?\([-\w]+\)\//, '');
|
|
110
|
+
f = f.replace(/^\.\//, '');
|
|
111
|
+
return f;
|
|
112
|
+
};
|
|
113
|
+
const serializeReactValue = (value, options) => {
|
|
114
|
+
if (value === null) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === 'string') {
|
|
118
|
+
return value.length > 300 ? `${value.slice(0, 300)}…[truncated]` : value;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === 'undefined') {
|
|
124
|
+
return '[undefined]';
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === 'function') {
|
|
127
|
+
return '[function]';
|
|
128
|
+
}
|
|
129
|
+
if (typeof value === 'symbol') {
|
|
130
|
+
return '[symbol]';
|
|
131
|
+
}
|
|
132
|
+
if (typeof value === 'bigint') {
|
|
133
|
+
return `${value.toString()}n`;
|
|
134
|
+
}
|
|
135
|
+
if (typeof value !== 'object') {
|
|
136
|
+
return `[${typeof value}]`;
|
|
137
|
+
}
|
|
138
|
+
const objectTag = Object.prototype.toString.call(value);
|
|
139
|
+
if (objectTag.includes('Element]') || objectTag === '[object Window]' || objectTag === '[object Document]') {
|
|
140
|
+
return '[dom-node]';
|
|
141
|
+
}
|
|
142
|
+
if (options.seen.has(value)) {
|
|
143
|
+
return '[circular]';
|
|
144
|
+
}
|
|
145
|
+
if (options.depth >= 3) {
|
|
146
|
+
return '[max-depth]';
|
|
147
|
+
}
|
|
148
|
+
options.seen.add(value);
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
const items = value.slice(0, 20).map((item) => {
|
|
151
|
+
return serializeReactValue(item, { depth: options.depth + 1, seen: options.seen });
|
|
152
|
+
});
|
|
153
|
+
if (value.length > 20) {
|
|
154
|
+
items.push(`…[${value.length - 20} more]`);
|
|
155
|
+
}
|
|
156
|
+
options.seen.delete(value);
|
|
157
|
+
return items;
|
|
158
|
+
}
|
|
159
|
+
const entries = Object.entries(value).slice(0, 20);
|
|
160
|
+
const result = Object.fromEntries(entries.map(([key, childValue]) => {
|
|
161
|
+
return [key, serializeReactValue(childValue, { depth: options.depth + 1, seen: options.seen })];
|
|
162
|
+
}));
|
|
163
|
+
const totalKeys = Object.keys(value).length;
|
|
164
|
+
if (totalKeys > 20) {
|
|
165
|
+
result['…'] = `[${totalKeys - 20} more keys]`;
|
|
166
|
+
}
|
|
167
|
+
options.seen.delete(value);
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
const getSourceForFiber = async (fiber) => {
|
|
171
|
+
try {
|
|
172
|
+
const source = await bippy.getSource(fiber);
|
|
173
|
+
if (source?.fileName) {
|
|
174
|
+
return {
|
|
175
|
+
fileName: cleanName(source.fileName),
|
|
176
|
+
lineNumber: source.lineNumber ?? null,
|
|
177
|
+
columnNumber: source.columnNumber ?? null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const ownerStack = await bippy.getOwnerStack(fiber);
|
|
181
|
+
const frame = ownerStack.find((ownerFrame) => {
|
|
182
|
+
return ownerFrame.fileName ? bippy.isSourceFile(ownerFrame.fileName) : false;
|
|
183
|
+
});
|
|
184
|
+
if (frame?.fileName) {
|
|
185
|
+
return {
|
|
186
|
+
fileName: cleanName(frame.fileName),
|
|
187
|
+
lineNumber: frame.lineNumber ?? null,
|
|
188
|
+
columnNumber: frame.columnNumber ?? null,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
};
|
|
197
|
+
let fiber = null;
|
|
198
|
+
try {
|
|
199
|
+
fiber = bippy.getFiberFromHostInstance(el);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
if (!fiber) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const componentFibers = [];
|
|
208
|
+
let current = fiber;
|
|
209
|
+
while (current && componentFibers.length < 20) {
|
|
210
|
+
try {
|
|
211
|
+
if (bippy.isCompositeFiber(current)) {
|
|
212
|
+
componentFibers.push(current);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Ignore malformed or unsupported fibers and keep walking upward.
|
|
217
|
+
}
|
|
218
|
+
current = current.return;
|
|
219
|
+
}
|
|
220
|
+
if (componentFibers.length === 0) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const hierarchy = await Promise.all(componentFibers.map(async (componentFiber) => {
|
|
224
|
+
const componentName = (() => {
|
|
225
|
+
try {
|
|
226
|
+
return componentFiber.type ? bippy.getDisplayName(componentFiber.type) : null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
return {
|
|
233
|
+
componentName,
|
|
234
|
+
source: await getSourceForFiber(componentFiber),
|
|
235
|
+
props: serializeReactValue(componentFiber.memoizedProps, { depth: 0, seen: new WeakSet() }),
|
|
236
|
+
};
|
|
237
|
+
}));
|
|
238
|
+
const nearest = hierarchy[0];
|
|
239
|
+
if (!nearest) {
|
|
240
|
+
return null;
|
|
62
241
|
}
|
|
63
|
-
return
|
|
242
|
+
return {
|
|
243
|
+
componentName: nearest.componentName,
|
|
244
|
+
source: nearest.source,
|
|
245
|
+
hierarchy,
|
|
246
|
+
props: nearest.props,
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
if ('page' in locator) {
|
|
250
|
+
return await locator.evaluate(evaluateReactComponentInfo);
|
|
64
251
|
}
|
|
65
|
-
return
|
|
252
|
+
return await locator.evaluate(evaluateReactComponentInfo);
|
|
66
253
|
}
|
|
67
254
|
//# sourceMappingURL=react-source.js.map
|
package/dist/react-source.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-source.js","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"react-source.js","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"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"}
|
package/dist/readability.js
CHANGED
|
@@ -1668,7 +1668,7 @@
|
|
|
1668
1668
|
};
|
|
1669
1669
|
});
|
|
1670
1670
|
|
|
1671
|
-
// dist/_readability-entry-
|
|
1671
|
+
// dist/_readability-entry-28588-1781364082573.js
|
|
1672
1672
|
var import_readability = __toESM(require_readability(), 1);
|
|
1673
1673
|
globalThis.__readability = { Readability: import_readability.Readability, isProbablyReaderable: import_readability.isProbablyReaderable };
|
|
1674
1674
|
})();
|
package/dist/relay-client.d.ts
CHANGED
|
@@ -15,6 +15,16 @@ export type ExtensionStatus = {
|
|
|
15
15
|
playwriterVersion: string | null;
|
|
16
16
|
};
|
|
17
17
|
export declare function getRelayServerVersion(port?: number): Promise<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Poll /version until a relay responds or timeout expires.
|
|
20
|
+
* Used during startup races where a relay may have bound the port
|
|
21
|
+
* but isn't serving HTTP yet (issue #75).
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForRelayVersion({ port, timeoutMs, intervalMs, }?: {
|
|
24
|
+
port?: number;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
intervalMs?: number;
|
|
27
|
+
}): Promise<string | null>;
|
|
18
28
|
export declare function getExtensionStatus(port?: number): Promise<{
|
|
19
29
|
connected: boolean;
|
|
20
30
|
activeTargets: number;
|
|
@@ -59,6 +69,7 @@ export interface EnsureRelayServerOptions {
|
|
|
59
69
|
/**
|
|
60
70
|
* Ensures the relay server is running. Starts it if not running.
|
|
61
71
|
* Optionally restarts on version mismatch.
|
|
72
|
+
* Concurrent calls within the same process are deduplicated.
|
|
62
73
|
*/
|
|
63
74
|
export declare function ensureRelayServer(options?: EnsureRelayServerOptions): Promise<true | undefined>;
|
|
64
75
|
//# sourceMappingURL=relay-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../src/relay-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,eAAO,MAAM,UAAU,QAA+C,CAAA;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC7C,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC,CAAA;AAED,wBAAsB,qBAAqB,CAAC,IAAI,GAAE,MAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7F;AAED,wBAAsB,kBAAkB,CACtC,IAAI,GAAE,MAAmB,GACxB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAYjG;AAED,wBAAsB,mBAAmB,CAAC,IAAI,GAAE,MAAmB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CA6C/F;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CACtC,GACL,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB5B;AAqBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAa9D;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,0BAA0B,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQhH;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;IAC1C,+EAA+E;IAC/E,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../src/relay-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,eAAO,MAAM,UAAU,QAA+C,CAAA;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC7C,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC,CAAA;AAED,wBAAsB,qBAAqB,CAAC,IAAI,GAAE,MAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7F;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,IAAiB,EACjB,SAAgB,EAChB,UAAgB,GACjB,GAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAU9B;AAED,wBAAsB,kBAAkB,CACtC,IAAI,GAAE,MAAmB,GACxB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAYjG;AAED,wBAAsB,mBAAmB,CAAC,IAAI,GAAE,MAAmB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CA6C/F;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CACtC,GACL,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB5B;AAqBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAa9D;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,0BAA0B,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQhH;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;IAC1C,+EAA+E;IAC/E,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7B;AAMD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAQzG"}
|