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.
Files changed (76) hide show
  1. package/dist/bippy.js +5 -5
  2. package/dist/cdp-log.d.ts +4 -1
  3. package/dist/cdp-log.d.ts.map +1 -1
  4. package/dist/cdp-log.js +39 -2
  5. package/dist/cdp-log.js.map +1 -1
  6. package/dist/cdp-log.test.d.ts +2 -0
  7. package/dist/cdp-log.test.d.ts.map +1 -0
  8. package/dist/cdp-log.test.js +109 -0
  9. package/dist/cdp-log.test.js.map +1 -0
  10. package/dist/cdp-relay.d.ts.map +1 -1
  11. package/dist/cdp-relay.js +120 -11
  12. package/dist/cdp-relay.js.map +1 -1
  13. package/dist/cli-help.test.js +22 -0
  14. package/dist/cli-help.test.js.map +1 -1
  15. package/dist/cli.js +69 -25
  16. package/dist/cli.js.map +1 -1
  17. package/dist/executor.d.ts +4 -0
  18. package/dist/executor.d.ts.map +1 -1
  19. package/dist/executor.js +140 -33
  20. package/dist/executor.js.map +1 -1
  21. package/dist/extension/background.js +343 -62
  22. package/dist/extension/manifest.json +1 -1
  23. package/dist/mcp.d.ts.map +1 -1
  24. package/dist/mcp.js +6 -1
  25. package/dist/mcp.js.map +1 -1
  26. package/dist/performance-examples.d.ts +5 -0
  27. package/dist/performance-examples.d.ts.map +1 -0
  28. package/dist/performance-examples.js +112 -0
  29. package/dist/performance-examples.js.map +1 -0
  30. package/dist/performance-profiling.md +417 -0
  31. package/dist/prompt.md +51 -18
  32. package/dist/react-source.d.ts +44 -0
  33. package/dist/react-source.d.ts.map +1 -1
  34. package/dist/react-source.js +207 -20
  35. package/dist/react-source.js.map +1 -1
  36. package/dist/readability.js +1 -1
  37. package/dist/relay-client.d.ts +11 -0
  38. package/dist/relay-client.d.ts.map +1 -1
  39. package/dist/relay-client.js +46 -1
  40. package/dist/relay-client.js.map +1 -1
  41. package/dist/relay-core.test.js +10 -6
  42. package/dist/relay-core.test.js.map +1 -1
  43. package/dist/relay-session.test.js +43 -7
  44. package/dist/relay-session.test.js.map +1 -1
  45. package/dist/relay-state.test.js +57 -1
  46. package/dist/relay-state.test.js.map +1 -1
  47. package/dist/screen-recording.d.ts.map +1 -1
  48. package/dist/screen-recording.js +19 -4
  49. package/dist/screen-recording.js.map +1 -1
  50. package/dist/selector-generator.js +1 -1
  51. package/dist/start-relay-server.d.ts +1 -1
  52. package/dist/start-relay-server.d.ts.map +1 -1
  53. package/dist/start-relay-server.js +23 -1
  54. package/dist/start-relay-server.js.map +1 -1
  55. package/dist/utils.d.ts +2 -1
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/utils.js +4 -1
  58. package/dist/utils.js.map +1 -1
  59. package/package.json +3 -3
  60. package/src/cdp-log.test.ts +131 -0
  61. package/src/cdp-log.ts +44 -2
  62. package/src/cdp-relay.ts +127 -10
  63. package/src/cli-help.test.ts +22 -0
  64. package/src/cli.ts +74 -24
  65. package/src/executor.ts +166 -39
  66. package/src/mcp.ts +6 -1
  67. package/src/performance-examples.ts +186 -0
  68. package/src/react-source.ts +310 -24
  69. package/src/relay-client.ts +62 -5
  70. package/src/relay-core.test.ts +10 -6
  71. package/src/relay-session.test.ts +45 -11
  72. package/src/relay-state.test.ts +67 -1
  73. package/src/screen-recording.ts +20 -4
  74. package/src/skill.md +62 -19
  75. package/src/start-relay-server.ts +22 -1
  76. 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 multiple channels:
106
+ **Deeper observation** — when snapshots aren't enough to understand what happened, combine snapshot with filtered logs:
92
107
 
93
108
  ```js
94
- // Check console for errors after an action
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 console errors, `state.page.url()` for navigation, screenshots only for visual layout issues.
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(`/tmp/${download.suggestedFilename()}`)
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, cleared on navigation):
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
- // Examples:
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: './screenshot.png' })`. Also accepts `width`, `height`, `maxDimension`, `quality`, `format` (default: `'png'`), `output`. Alias: `resizeImage`.
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: './recording.mp4',
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.
@@ -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;AAcD,wBAAsB,cAAc,CAAC,EACnC,OAAO,EACP,GAAG,EAAE,UAAU,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,GAAG,aAAa,CAAA;IAChC,GAAG,EAAE,WAAW,CAAA;CACjB,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CA6DtC"}
1
+ {"version":3,"file":"react-source.d.ts","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"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"}
@@ -11,22 +11,44 @@ function getBippyCode() {
11
11
  bippyCode = fs.readFileSync(bippyPath, 'utf-8');
12
12
  return bippyCode;
13
13
  }
14
- export async function getReactSource({ locator, cdp: cdpSession, }) {
15
- const cdp = cdpSession;
16
- const page = 'page' in locator && typeof locator.page === 'function' ? locator.page() : locator._page;
17
- if (!page) {
18
- throw new Error('Could not get page from locator');
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
- const hasBippy = await page.evaluate(() => !!globalThis.__bippy);
21
- if (!hasBippy) {
22
- const code = getBippyCode();
23
- await cdp.send('Runtime.evaluate', { expression: code });
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 result = await locator.evaluate(async (el) => {
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 ? bippy.normalizeFileName(source.fileName) : null,
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: bippy.normalizeFileName(frame.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
- if (result && '_notFound' in result) {
57
- if (result._notFound === 'fiber') {
58
- console.warn('[getReactSource] no fiber found - is this a React element?');
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
- else {
61
- console.warn('[getReactSource] no source location found - is this a React dev build?');
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 null;
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 result;
252
+ return await locator.evaluate(evaluateReactComponentInfo);
66
253
  }
67
254
  //# sourceMappingURL=react-source.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-source.js","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-source.js","sourceRoot":"","sources":["../src/react-source.ts"],"names":[],"mappings":"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"}
@@ -1668,7 +1668,7 @@
1668
1668
  };
1669
1669
  });
1670
1670
 
1671
- // dist/_readability-entry-39342-1776333719875.js
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
  })();
@@ -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;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAoEzG"}
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"}