playwriter 0.0.34 → 0.0.38

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.
@@ -0,0 +1,68 @@
1
+ import type { Page, Locator, ElementHandle } from 'playwright-core';
2
+ export interface AriaRef {
3
+ role: string;
4
+ name: string;
5
+ ref: string;
6
+ }
7
+ export interface AriaSnapshotResult {
8
+ snapshot: string;
9
+ refToElement: Map<string, {
10
+ role: string;
11
+ name: string;
12
+ }>;
13
+ refHandles: Array<{
14
+ ref: string;
15
+ handle: ElementHandle;
16
+ }>;
17
+ getRefsForLocators: (locators: Array<Locator | ElementHandle>) => Promise<Array<AriaRef | null>>;
18
+ getRefForLocator: (locator: Locator | ElementHandle) => Promise<AriaRef | null>;
19
+ getRefStringForLocator: (locator: Locator | ElementHandle) => Promise<string | null>;
20
+ }
21
+ /**
22
+ * Get an accessibility snapshot with utilities to look up aria refs for elements.
23
+ * Uses Playwright's internal aria-ref selector engine.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const { snapshot, getRefsForLocators } = await getAriaSnapshot({ page })
28
+ * const refs = await getRefsForLocators([page.locator('button'), page.locator('a')])
29
+ * // refs[0].ref is e.g. "e5" - use page.locator('aria-ref=e5') to select
30
+ * ```
31
+ */
32
+ export declare function getAriaSnapshot({ page }: {
33
+ page: Page;
34
+ }): Promise<AriaSnapshotResult>;
35
+ /**
36
+ * Show Vimium-style labels on interactive elements.
37
+ * Labels are yellow badges positioned above each element showing the aria ref (e.g., "e1", "e2").
38
+ * Use with screenshots so agents can see which elements are interactive.
39
+ *
40
+ * Labels auto-hide after 5 seconds to prevent stale labels remaining on the page.
41
+ * Call this function again if the page HTML changes to get fresh labels.
42
+ *
43
+ * By default, only shows labels for truly interactive roles (button, link, textbox, etc.)
44
+ * to reduce visual clutter. Set `interactiveOnly: false` to show all elements with refs.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const { snapshot, labelCount } = await showAriaRefLabels({ page })
49
+ * await page.screenshot({ path: '/tmp/screenshot.png' })
50
+ * // Agent sees [e5] label on "Submit" button
51
+ * await page.locator('aria-ref=e5').click()
52
+ * // Labels auto-hide after 5 seconds, or call hideAriaRefLabels() manually
53
+ * ```
54
+ */
55
+ export declare function showAriaRefLabels({ page, interactiveOnly }: {
56
+ page: Page;
57
+ interactiveOnly?: boolean;
58
+ }): Promise<{
59
+ snapshot: string;
60
+ labelCount: number;
61
+ }>;
62
+ /**
63
+ * Remove all aria ref labels from the page.
64
+ */
65
+ export declare function hideAriaRefLabels({ page }: {
66
+ page: Page;
67
+ }): Promise<void>;
68
+ //# sourceMappingURL=aria-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-snapshot.d.ts","sourceRoot":"","sources":["../src/aria-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEnE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,UAAU,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,CAAC,CAAA;IACzD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,GAAG,aAAa,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;IAChG,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IAC/E,sBAAsB,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACrF;AA8ED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwF3F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,IAAI,EAAE,eAAsB,EAAE,EAAE;IACxE,IAAI,EAAE,IAAI,CAAA;IACV,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC,CA8MD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAc/E"}
@@ -0,0 +1,359 @@
1
+ const LABELS_CONTAINER_ID = '__playwriter_labels__';
2
+ // Roles that represent truly interactive elements (can be clicked, typed into, etc.)
3
+ const INTERACTIVE_ROLES = new Set([
4
+ 'button',
5
+ 'link',
6
+ 'textbox',
7
+ 'combobox',
8
+ 'searchbox',
9
+ 'checkbox',
10
+ 'radio',
11
+ 'slider',
12
+ 'spinbutton',
13
+ 'switch',
14
+ 'menuitem',
15
+ 'menuitemcheckbox',
16
+ 'menuitemradio',
17
+ 'option',
18
+ 'tab',
19
+ 'treeitem',
20
+ ]);
21
+ // Color categories for different role types - warm color scheme
22
+ // Format: [gradient-top, gradient-bottom, border]
23
+ const ROLE_COLORS = {
24
+ // Links - yellow (Vimium-style)
25
+ link: ['#FFF785', '#FFC542', '#E3BE23'],
26
+ // Buttons - orange
27
+ button: ['#FFE0B2', '#FFCC80', '#FFB74D'],
28
+ // Text inputs - coral/red
29
+ textbox: ['#FFCDD2', '#EF9A9A', '#E57373'],
30
+ combobox: ['#FFCDD2', '#EF9A9A', '#E57373'],
31
+ searchbox: ['#FFCDD2', '#EF9A9A', '#E57373'],
32
+ spinbutton: ['#FFCDD2', '#EF9A9A', '#E57373'],
33
+ // Checkboxes/Radios/Switches - warm pink
34
+ checkbox: ['#F8BBD0', '#F48FB1', '#EC407A'],
35
+ radio: ['#F8BBD0', '#F48FB1', '#EC407A'],
36
+ switch: ['#F8BBD0', '#F48FB1', '#EC407A'],
37
+ // Sliders - peach
38
+ slider: ['#FFCCBC', '#FFAB91', '#FF8A65'],
39
+ // Menu items - salmon
40
+ menuitem: ['#FFAB91', '#FF8A65', '#FF7043'],
41
+ menuitemcheckbox: ['#FFAB91', '#FF8A65', '#FF7043'],
42
+ menuitemradio: ['#FFAB91', '#FF8A65', '#FF7043'],
43
+ // Tabs/Options - amber
44
+ tab: ['#FFE082', '#FFD54F', '#FFC107'],
45
+ option: ['#FFE082', '#FFD54F', '#FFC107'],
46
+ treeitem: ['#FFE082', '#FFD54F', '#FFC107'],
47
+ };
48
+ // Default yellow for unknown roles
49
+ const DEFAULT_COLORS = ['#FFF785', '#FFC542', '#E3BE23'];
50
+ // Use String.raw for CSS syntax highlighting in editors
51
+ const css = String.raw;
52
+ const LABEL_STYLES = css `
53
+ .__pw_label__ {
54
+ position: absolute;
55
+ font: bold 11px Helvetica, Arial, sans-serif;
56
+ padding: 1px 4px;
57
+ border-radius: 3px;
58
+ color: black;
59
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
60
+ white-space: nowrap;
61
+ }
62
+ `;
63
+ const CONTAINER_STYLES = css `
64
+ position: absolute;
65
+ left: 0;
66
+ top: 0;
67
+ z-index: 2147483647;
68
+ pointer-events: none;
69
+ `;
70
+ /**
71
+ * Get an accessibility snapshot with utilities to look up aria refs for elements.
72
+ * Uses Playwright's internal aria-ref selector engine.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const { snapshot, getRefsForLocators } = await getAriaSnapshot({ page })
77
+ * const refs = await getRefsForLocators([page.locator('button'), page.locator('a')])
78
+ * // refs[0].ref is e.g. "e5" - use page.locator('aria-ref=e5') to select
79
+ * ```
80
+ */
81
+ export async function getAriaSnapshot({ page }) {
82
+ const snapshotMethod = page._snapshotForAI;
83
+ if (!snapshotMethod) {
84
+ throw new Error('_snapshotForAI not available. Ensure you are using Playwright.');
85
+ }
86
+ const snapshot = await snapshotMethod.call(page);
87
+ const snapshotStr = typeof snapshot === 'string' ? snapshot : (snapshot.full || JSON.stringify(snapshot, null, 2));
88
+ // Discover refs by probing aria-ref=e1, e2, e3... until 10 consecutive misses
89
+ const refToElement = new Map();
90
+ const refHandles = [];
91
+ let consecutiveMisses = 0;
92
+ let refNum = 1;
93
+ while (consecutiveMisses < 10) {
94
+ const ref = `e${refNum++}`;
95
+ try {
96
+ const locator = page.locator(`aria-ref=${ref}`);
97
+ if (await locator.count() === 1) {
98
+ consecutiveMisses = 0;
99
+ const [info, handle] = await Promise.all([
100
+ locator.evaluate((el) => ({
101
+ role: el.getAttribute('role') || {
102
+ a: el.hasAttribute('href') ? 'link' : 'generic',
103
+ button: 'button', input: { button: 'button', checkbox: 'checkbox', radio: 'radio',
104
+ text: 'textbox', search: 'searchbox', number: 'spinbutton', range: 'slider',
105
+ }[el.type] || 'textbox', select: 'combobox', textarea: 'textbox', img: 'img',
106
+ nav: 'navigation', main: 'main', header: 'banner', footer: 'contentinfo',
107
+ }[el.tagName.toLowerCase()] || 'generic',
108
+ name: el.getAttribute('aria-label') || el.textContent?.trim() || el.placeholder || '',
109
+ })),
110
+ locator.elementHandle({ timeout: 1000 }),
111
+ ]);
112
+ refToElement.set(ref, info);
113
+ if (handle) {
114
+ refHandles.push({ ref, handle });
115
+ }
116
+ }
117
+ else {
118
+ consecutiveMisses++;
119
+ }
120
+ }
121
+ catch {
122
+ consecutiveMisses++;
123
+ }
124
+ }
125
+ // Find refs for multiple locators in a single evaluate call
126
+ const getRefsForLocators = async (locators) => {
127
+ if (locators.length === 0 || refHandles.length === 0) {
128
+ return locators.map(() => null);
129
+ }
130
+ const targetHandles = await Promise.all(locators.map(async (loc) => {
131
+ try {
132
+ return 'elementHandle' in loc
133
+ ? await loc.elementHandle({ timeout: 1000 })
134
+ : loc;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }));
140
+ const matchingRefs = await page.evaluate(({ targets, candidates }) => targets.map((target) => {
141
+ if (!target)
142
+ return null;
143
+ return candidates.find(({ element }) => element === target)?.ref ?? null;
144
+ }), { targets: targetHandles, candidates: refHandles.map(({ ref, handle }) => ({ ref, element: handle })) });
145
+ return matchingRefs.map((ref) => {
146
+ if (!ref)
147
+ return null;
148
+ const info = refToElement.get(ref);
149
+ return info ? { ...info, ref } : null;
150
+ });
151
+ };
152
+ return {
153
+ snapshot: snapshotStr,
154
+ refToElement,
155
+ refHandles,
156
+ getRefsForLocators,
157
+ getRefForLocator: async (loc) => (await getRefsForLocators([loc]))[0],
158
+ getRefStringForLocator: async (loc) => (await getRefsForLocators([loc]))[0]?.ref ?? null,
159
+ };
160
+ }
161
+ /**
162
+ * Show Vimium-style labels on interactive elements.
163
+ * Labels are yellow badges positioned above each element showing the aria ref (e.g., "e1", "e2").
164
+ * Use with screenshots so agents can see which elements are interactive.
165
+ *
166
+ * Labels auto-hide after 5 seconds to prevent stale labels remaining on the page.
167
+ * Call this function again if the page HTML changes to get fresh labels.
168
+ *
169
+ * By default, only shows labels for truly interactive roles (button, link, textbox, etc.)
170
+ * to reduce visual clutter. Set `interactiveOnly: false` to show all elements with refs.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * const { snapshot, labelCount } = await showAriaRefLabels({ page })
175
+ * await page.screenshot({ path: '/tmp/screenshot.png' })
176
+ * // Agent sees [e5] label on "Submit" button
177
+ * await page.locator('aria-ref=e5').click()
178
+ * // Labels auto-hide after 5 seconds, or call hideAriaRefLabels() manually
179
+ * ```
180
+ */
181
+ export async function showAriaRefLabels({ page, interactiveOnly = true }) {
182
+ const { snapshot, refHandles, refToElement } = await getAriaSnapshot({ page });
183
+ // Filter to only interactive elements if requested
184
+ const filteredRefs = interactiveOnly
185
+ ? refHandles.filter(({ ref }) => {
186
+ const info = refToElement.get(ref);
187
+ return info && INTERACTIVE_ROLES.has(info.role);
188
+ })
189
+ : refHandles;
190
+ // Build refs with role info for color coding
191
+ const refsWithRoles = filteredRefs.map(({ ref, handle }) => ({
192
+ ref,
193
+ element: handle,
194
+ role: refToElement.get(ref)?.role || 'generic',
195
+ }));
196
+ // Single evaluate call: create container, styles, and all labels
197
+ // ElementHandles get unwrapped to DOM elements in browser context
198
+ // Using 'any' types here since this code runs in browser context
199
+ const labelCount = await page.evaluate(
200
+ // Using 'any' for browser types since this runs in browser context
201
+ ({ refs, containerId, containerStyles, labelStyles, roleColors, defaultColors }) => {
202
+ const doc = globalThis.document;
203
+ const win = globalThis;
204
+ // Cancel any pending auto-hide timer from previous call
205
+ const timerKey = '__playwriter_labels_timer__';
206
+ if (win[timerKey]) {
207
+ win.clearTimeout(win[timerKey]);
208
+ win[timerKey] = null;
209
+ }
210
+ // Remove existing labels if present (idempotent)
211
+ doc.getElementById(containerId)?.remove();
212
+ // Create container - absolute positioned, max z-index, no pointer events
213
+ const container = doc.createElement('div');
214
+ container.id = containerId;
215
+ container.style.cssText = containerStyles;
216
+ // Inject base label CSS
217
+ const style = doc.createElement('style');
218
+ style.textContent = labelStyles;
219
+ container.appendChild(style);
220
+ // Track placed label rectangles for overlap detection
221
+ // Each rect is { left, top, right, bottom } in viewport coordinates
222
+ const placedLabels = [];
223
+ // Estimate label dimensions (11px font + padding)
224
+ const LABEL_HEIGHT = 16;
225
+ const LABEL_CHAR_WIDTH = 7; // approximate width per character
226
+ // Parse alpha from rgb/rgba color string (getComputedStyle always returns these formats)
227
+ const getColorAlpha = (color) => {
228
+ if (color === 'transparent')
229
+ return 0;
230
+ // Match rgba(r, g, b, a) or rgb(r, g, b)
231
+ const match = color.match(/rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(?:,\s*([\d.]+)\s*)?\)/);
232
+ if (match) {
233
+ return match[1] !== undefined ? parseFloat(match[1]) : 1;
234
+ }
235
+ return 1; // Default to opaque for unrecognized formats
236
+ };
237
+ // Check if an element has an opaque background that would block elements behind it
238
+ const isOpaqueElement = (el) => {
239
+ const style = win.getComputedStyle(el);
240
+ // Check element opacity
241
+ const opacity = parseFloat(style.opacity);
242
+ if (opacity < 0.1)
243
+ return false;
244
+ // Check background-color alpha
245
+ const bgAlpha = getColorAlpha(style.backgroundColor);
246
+ if (bgAlpha > 0.1)
247
+ return true;
248
+ // Check if has background-image (usually opaque)
249
+ if (style.backgroundImage !== 'none')
250
+ return true;
251
+ return false;
252
+ };
253
+ // Check if element is visible (not covered by opaque overlay)
254
+ const isElementVisible = (element, rect) => {
255
+ const centerX = rect.left + rect.width / 2;
256
+ const centerY = rect.top + rect.height / 2;
257
+ // Get all elements at this point, from top to bottom
258
+ const stack = doc.elementsFromPoint(centerX, centerY);
259
+ // Find our target element in the stack
260
+ const targetIndex = stack.findIndex((el) => element.contains(el) || el.contains(element));
261
+ // Element not in stack at all - not visible
262
+ if (targetIndex === -1)
263
+ return false;
264
+ // Check if any opaque element is above our target
265
+ for (let i = 0; i < targetIndex; i++) {
266
+ const el = stack[i];
267
+ // Skip our own overlay container
268
+ if (el.id === containerId)
269
+ continue;
270
+ // Skip pointer-events: none elements (decorative overlays)
271
+ if (win.getComputedStyle(el).pointerEvents === 'none')
272
+ continue;
273
+ // If this element is opaque, our target is blocked
274
+ if (isOpaqueElement(el))
275
+ return false;
276
+ }
277
+ return true;
278
+ };
279
+ // Check if two rectangles overlap
280
+ const rectsOverlap = (a, b) => {
281
+ return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
282
+ };
283
+ // Create label for each interactive element
284
+ let count = 0;
285
+ for (const { ref, role, element } of refs) {
286
+ const rect = element.getBoundingClientRect();
287
+ // Skip elements with no size (hidden)
288
+ if (rect.width === 0 || rect.height === 0) {
289
+ continue;
290
+ }
291
+ // Skip elements that are covered by opaque overlays
292
+ if (!isElementVisible(element, rect)) {
293
+ continue;
294
+ }
295
+ // Calculate label position and dimensions
296
+ const labelWidth = ref.length * LABEL_CHAR_WIDTH + 8; // +8 for padding
297
+ const labelLeft = rect.left;
298
+ const labelTop = Math.max(0, rect.top - LABEL_HEIGHT);
299
+ const labelRect = {
300
+ left: labelLeft,
301
+ top: labelTop,
302
+ right: labelLeft + labelWidth,
303
+ bottom: labelTop + LABEL_HEIGHT,
304
+ };
305
+ // Skip if this label would overlap with any already-placed label
306
+ const overlaps = placedLabels.some((placed) => rectsOverlap(labelRect, placed));
307
+ if (overlaps) {
308
+ continue;
309
+ }
310
+ // Get colors for this role
311
+ const [gradTop, gradBottom, border] = roleColors[role] || defaultColors;
312
+ // Place the label
313
+ const label = doc.createElement('div');
314
+ label.className = '__pw_label__';
315
+ label.textContent = ref;
316
+ label.style.background = `linear-gradient(to bottom, ${gradTop} 0%, ${gradBottom} 100%)`;
317
+ label.style.border = `1px solid ${border}`;
318
+ // Position above element, accounting for scroll
319
+ label.style.left = `${win.scrollX + labelLeft}px`;
320
+ label.style.top = `${win.scrollY + labelTop}px`;
321
+ container.appendChild(label);
322
+ placedLabels.push(labelRect);
323
+ count++;
324
+ }
325
+ doc.documentElement.appendChild(container);
326
+ // Auto-hide labels after 5 seconds to prevent stale labels
327
+ // Store timer ID so it can be cancelled if showAriaRefLabels is called again
328
+ win[timerKey] = win.setTimeout(() => {
329
+ doc.getElementById(containerId)?.remove();
330
+ win[timerKey] = null;
331
+ }, 5000);
332
+ return count;
333
+ }, {
334
+ refs: refsWithRoles.map(({ ref, role, element }) => ({ ref, role, element })),
335
+ containerId: LABELS_CONTAINER_ID,
336
+ containerStyles: CONTAINER_STYLES,
337
+ labelStyles: LABEL_STYLES,
338
+ roleColors: ROLE_COLORS,
339
+ defaultColors: DEFAULT_COLORS,
340
+ });
341
+ return { snapshot, labelCount };
342
+ }
343
+ /**
344
+ * Remove all aria ref labels from the page.
345
+ */
346
+ export async function hideAriaRefLabels({ page }) {
347
+ await page.evaluate((id) => {
348
+ const doc = globalThis.document;
349
+ const win = globalThis;
350
+ // Cancel any pending auto-hide timer
351
+ const timerKey = '__playwriter_labels_timer__';
352
+ if (win[timerKey]) {
353
+ win.clearTimeout(win[timerKey]);
354
+ win[timerKey] = null;
355
+ }
356
+ doc.getElementById(id)?.remove();
357
+ }, LABELS_CONTAINER_ID);
358
+ }
359
+ //# sourceMappingURL=aria-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-snapshot.js","sourceRoot":"","sources":["../src/aria-snapshot.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cdp-relay.d.ts","sourceRoot":"","sources":["../src/cdp-relay.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAA0D,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAoB/G,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,EAAE,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IACrF,GAAG,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;CACvF,CAAA;AAED,wBAAsB,6BAA6B,CAAC,EAAE,IAAY,EAAE,IAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;KAAE,CAAA;CAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CA4tBzP"}
1
+ {"version":3,"file":"cdp-relay.d.ts","sourceRoot":"","sources":["../src/cdp-relay.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAA0D,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAoB/G,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,EAAE,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IACrF,GAAG,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;CACvF,CAAA;AAED,wBAAsB,6BAA6B,CAAC,EAAE,IAAY,EAAE,IAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;KAAE,CAAA;CAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CA+yBzP"}
package/dist/cdp-relay.js CHANGED
@@ -122,6 +122,34 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
122
122
  });
123
123
  });
124
124
  }
125
+ // Auto-create initial tab when PLAYWRITER_AUTO_ENABLE is set and no targets exist.
126
+ // This allows Playwright to connect and immediately have a page to work with.
127
+ async function maybeAutoCreateInitialTab() {
128
+ if (!process.env.PLAYWRITER_AUTO_ENABLE) {
129
+ return;
130
+ }
131
+ if (!extensionWs) {
132
+ return;
133
+ }
134
+ if (connectedTargets.size > 0) {
135
+ return;
136
+ }
137
+ try {
138
+ logger?.log(chalk.blue('Auto-creating initial tab for Playwright client'));
139
+ const result = await sendToExtension({ method: 'createInitialTab', timeout: 10000 });
140
+ if (result.success && result.sessionId && result.targetInfo) {
141
+ connectedTargets.set(result.sessionId, {
142
+ sessionId: result.sessionId,
143
+ targetId: result.targetInfo.targetId,
144
+ targetInfo: result.targetInfo
145
+ });
146
+ logger?.log(chalk.blue(`Auto-created tab, now have ${connectedTargets.size} targets, url: ${result.targetInfo.url}`));
147
+ }
148
+ }
149
+ catch (e) {
150
+ logger?.error('Failed to auto-create initial tab:', e);
151
+ }
152
+ }
125
153
  async function routeCdpCommand({ method, params, sessionId }) {
126
154
  switch (method) {
127
155
  case 'Browser.getVersion': {
@@ -136,10 +164,14 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
136
164
  case 'Browser.setDownloadBehavior': {
137
165
  return {};
138
166
  }
167
+ // Target.setAutoAttach is a CDP command Playwright sends on first connection.
168
+ // We use it as the hook to auto-create an initial tab. If Playwright changes
169
+ // its initialization sequence in the future, this could be moved to a different command.
139
170
  case 'Target.setAutoAttach': {
140
171
  if (sessionId) {
141
172
  break;
142
173
  }
174
+ await maybeAutoCreateInitialTab();
143
175
  return {};
144
176
  }
145
177
  case 'Target.setDiscoverTargets': {
@@ -253,7 +285,30 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
253
285
  return c.json({ ok: false }, 400);
254
286
  }
255
287
  });
288
+ // Validate Origin header for WebSocket connections to prevent cross-origin attacks.
289
+ // Browsers always send Origin header for WebSocket connections, but Node.js clients don't.
290
+ // We only allow our specific extension IDs to prevent malicious websites or extensions
291
+ // from connecting to the local WebSocket server.
292
+ const ALLOWED_EXTENSION_IDS = [
293
+ 'jfeammnjpkecdekppnclgkkffahnhfhe', // Production extension (Chrome Web Store)
294
+ 'elnnakgjclnapgflmidlpobefkdmapdm', // Dev extension (loaded unpacked)
295
+ ];
296
+ function isAllowedOrigin(origin) {
297
+ if (!origin) {
298
+ return true; // Node.js clients don't send Origin
299
+ }
300
+ if (origin.startsWith('chrome-extension://')) {
301
+ const extensionId = origin.replace('chrome-extension://', '');
302
+ return ALLOWED_EXTENSION_IDS.includes(extensionId);
303
+ }
304
+ return false; // Reject browser origins (http://, https://, etc.)
305
+ }
256
306
  app.get('/cdp/:clientId?', (c, next) => {
307
+ const origin = c.req.header('origin');
308
+ if (!isAllowedOrigin(origin)) {
309
+ logger?.log(chalk.red(`Rejecting /cdp WebSocket from origin: ${origin}`));
310
+ return c.text('Forbidden', 403);
311
+ }
257
312
  if (token) {
258
313
  const url = new URL(c.req.url, 'http://localhost');
259
314
  const providedToken = url.searchParams.get('token');
@@ -265,14 +320,15 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
265
320
  }, upgradeWebSocket((c) => {
266
321
  const clientId = c.req.param('clientId') || 'default';
267
322
  return {
268
- onOpen(_event, ws) {
323
+ async onOpen(_event, ws) {
269
324
  if (playwrightClients.has(clientId)) {
270
325
  logger?.log(chalk.red(`Rejecting duplicate client ID: ${clientId}`));
271
326
  ws.close(1000, 'Client ID already connected');
272
327
  return;
273
328
  }
329
+ // Add client first so it can receive Target.attachedToTarget events
274
330
  playwrightClients.set(clientId, { id: clientId, ws });
275
- logger?.log(chalk.green(`Playwright client connected: ${clientId} (${playwrightClients.size} total)`));
331
+ logger?.log(chalk.green(`Playwright client connected: ${clientId} (${playwrightClients.size} total) (extension? ${!!extensionWs}) (${connectedTargets.size} pages)`));
276
332
  },
277
333
  async onMessage(event, ws) {
278
334
  let message;
@@ -317,6 +373,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
317
373
  waitingForDebugger: false
318
374
  }
319
375
  };
376
+ if (!target.targetInfo.url) {
377
+ logger?.error(chalk.red('[Server] WARNING: Target.attachedToTarget sent with empty URL!'), JSON.stringify(attachedPayload));
378
+ }
320
379
  logger?.log(chalk.magenta('[Server] Target.attachedToTarget full payload:'), JSON.stringify(attachedPayload));
321
380
  sendToPlaywright({
322
381
  message: attachedPayload,
@@ -336,6 +395,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
336
395
  }
337
396
  }
338
397
  };
398
+ if (!target.targetInfo.url) {
399
+ logger?.error(chalk.red('[Server] WARNING: Target.targetCreated sent with empty URL!'), JSON.stringify(targetCreatedPayload));
400
+ }
339
401
  logger?.log(chalk.magenta('[Server] Target.targetCreated full payload:'), JSON.stringify(targetCreatedPayload));
340
402
  sendToPlaywright({
341
403
  message: targetCreatedPayload,
@@ -359,6 +421,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
359
421
  waitingForDebugger: false
360
422
  }
361
423
  };
424
+ if (!target.targetInfo.url) {
425
+ logger?.error(chalk.red('[Server] WARNING: Target.attachedToTarget (from attachToTarget) sent with empty URL!'), JSON.stringify(attachedPayload));
426
+ }
362
427
  logger?.log(chalk.magenta('[Server] Target.attachedToTarget (from attachToTarget) payload:'), JSON.stringify(attachedPayload));
363
428
  sendToPlaywright({
364
429
  message: attachedPayload,
@@ -391,7 +456,14 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
391
456
  }
392
457
  };
393
458
  }));
394
- app.get('/extension', upgradeWebSocket(() => {
459
+ app.get('/extension', (c, next) => {
460
+ const origin = c.req.header('origin');
461
+ if (!isAllowedOrigin(origin)) {
462
+ logger?.log(chalk.red(`Rejecting /extension WebSocket from origin: ${origin}`));
463
+ return c.text('Forbidden', 403);
464
+ }
465
+ return next();
466
+ }, upgradeWebSocket(() => {
395
467
  return {
396
468
  onOpen(_event, ws) {
397
469
  if (extensionWs) {
@@ -460,6 +532,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
460
532
  emitter.emit('cdp:event', { event: cdpEvent, sessionId });
461
533
  if (method === 'Target.attachedToTarget') {
462
534
  const targetParams = params;
535
+ if (!targetParams.targetInfo.url) {
536
+ logger?.error(chalk.red('[Extension] WARNING: Target.attachedToTarget received with empty URL!'), JSON.stringify({ method, params: targetParams, sessionId }));
537
+ }
463
538
  logger?.log(chalk.yellow('[Extension] Target.attachedToTarget full payload:'), JSON.stringify({ method, params: targetParams, sessionId }));
464
539
  // Check if we already sent this target to clients (e.g., from Target.setAutoAttach response)
465
540
  const alreadyConnected = connectedTargets.has(targetParams.sessionId);