playwriter 0.0.89 → 0.0.103
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/aria-snapshot.d.ts +7 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +76 -18
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +94 -0
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/bippy.js +1 -1
- package/dist/browser-config.d.ts +12 -1
- package/dist/browser-config.d.ts.map +1 -1
- package/dist/browser-config.js +101 -38
- package/dist/browser-config.js.map +1 -1
- package/dist/browser-launch.d.ts +17 -0
- package/dist/browser-launch.d.ts.map +1 -0
- package/dist/browser-launch.js +44 -0
- package/dist/browser-launch.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +34 -10
- package/dist/cdp-relay.js.map +1 -1
- package/dist/channel-owner-inspect.test.d.ts +2 -0
- package/dist/channel-owner-inspect.test.d.ts.map +1 -0
- package/dist/channel-owner-inspect.test.js +75 -0
- package/dist/channel-owner-inspect.test.js.map +1 -0
- package/dist/chrome-discovery.d.ts +65 -0
- package/dist/chrome-discovery.d.ts.map +1 -0
- package/dist/chrome-discovery.js +165 -0
- package/dist/chrome-discovery.js.map +1 -0
- package/dist/chrome-discovery.test.d.ts +2 -0
- package/dist/chrome-discovery.test.d.ts.map +1 -0
- package/dist/chrome-discovery.test.js +41 -0
- package/dist/chrome-discovery.test.js.map +1 -0
- package/dist/cli.js +357 -53
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +44 -11
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +121 -55
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +29 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension/background.js +1704 -0
- package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
- package/dist/extension/icons/icon-black-128.png +0 -0
- package/dist/extension/icons/icon-black-16.png +0 -0
- package/dist/extension/icons/icon-black-32.png +0 -0
- package/dist/extension/icons/icon-black-48.png +0 -0
- package/dist/extension/icons/icon-gray-128.png +0 -0
- package/dist/extension/icons/icon-gray-16.png +0 -0
- package/dist/extension/icons/icon-gray-32.png +0 -0
- package/dist/extension/icons/icon-gray-48.png +0 -0
- package/dist/extension/icons/icon-green-128.png +0 -0
- package/dist/extension/icons/icon-green-16.png +0 -0
- package/dist/extension/icons/icon-green-32.png +0 -0
- package/dist/extension/icons/icon-green-48.png +0 -0
- package/dist/extension/icons/playwriter-icon-black.png +0 -0
- package/dist/extension/icons/playwriter-icon-gray-disabled.png +0 -0
- package/dist/extension/icons/playwriter-icon-gray-disabled.svg +1 -0
- package/dist/extension/manifest.json +40 -0
- package/dist/extension/offscreen.js +214 -0
- package/dist/extension/src/offscreen.html +9 -0
- package/dist/extension/src/prism-bash.min.js +1 -0
- package/dist/extension/src/prism.min.js +1 -0
- package/dist/extension/src/welcome.html +399 -0
- package/dist/kitty-graphics.d.ts +22 -0
- package/dist/kitty-graphics.d.ts.map +1 -0
- package/dist/kitty-graphics.js +71 -0
- package/dist/kitty-graphics.js.map +1 -0
- package/dist/kitty-graphics.test.d.ts +2 -0
- package/dist/kitty-graphics.test.d.ts.map +1 -0
- package/dist/kitty-graphics.test.js +125 -0
- package/dist/kitty-graphics.test.js.map +1 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +76 -12
- package/dist/mcp.js.map +1 -1
- package/dist/package-paths.d.ts +3 -0
- package/dist/package-paths.d.ts.map +1 -0
- package/dist/package-paths.js +29 -0
- package/dist/package-paths.js.map +1 -0
- package/dist/prompt.md +4 -2
- package/dist/readability.js +1 -1
- package/dist/relay-core.test.js +14 -4
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-navigation.test.d.ts.map +1 -1
- package/dist/relay-navigation.test.js +95 -1
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/scoped-fs.d.ts +5 -4
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js +11 -9
- package/dist/scoped-fs.js.map +1 -1
- package/dist/scoped-fs.test.d.ts +5 -0
- package/dist/scoped-fs.test.d.ts.map +1 -0
- package/dist/scoped-fs.test.js +70 -0
- package/dist/scoped-fs.test.js.map +1 -0
- package/dist/screen-recording.d.ts +3 -6
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +7 -21
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +3 -4
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/test-utils.d.ts +3 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +24 -12
- package/dist/test-utils.js.map +1 -1
- package/package.json +6 -5
- package/src/aria-snapshot.test.ts +110 -0
- package/src/aria-snapshot.ts +86 -19
- package/src/aria-snapshots/contenteditable-interactive.txt +7 -0
- package/src/aria-snapshots/contenteditable-raw.txt +14 -0
- package/src/aria-snapshots/github-interactive.txt +0 -2
- package/src/aria-snapshots/github-raw.txt +0 -4
- package/src/aria-snapshots/hackernews-interactive.txt +240 -237
- package/src/aria-snapshots/hackernews-raw.txt +262 -258
- package/src/aria-snapshots/prosemirror-interactive.txt +63 -0
- package/src/aria-snapshots/prosemirror-raw.txt +141 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/browser-config.ts +143 -37
- package/src/browser-launch.ts +75 -0
- package/src/cdp-relay.ts +46 -11
- package/src/channel-owner-inspect.test.ts +94 -0
- package/src/chrome-discovery.test.ts +50 -0
- package/src/chrome-discovery.ts +211 -0
- package/src/cli.ts +432 -59
- package/src/executor.ts +171 -72
- package/src/executor.unit.test.ts +32 -1
- package/src/kitty-graphics.test.ts +139 -0
- package/src/kitty-graphics.ts +79 -0
- package/src/mcp.ts +82 -12
- package/src/package-paths.ts +36 -0
- package/src/relay-core.test.ts +14 -4
- package/src/relay-navigation.test.ts +106 -1
- package/src/scoped-fs.test.ts +77 -0
- package/src/scoped-fs.ts +12 -9
- package/src/screen-recording.ts +7 -22
- package/src/skill.md +32 -2
- package/src/snapshot-tools.test.ts +3 -4
- package/src/snapshots/shadcn-ui-accessibility-full.md +181 -183
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +119 -121
- package/src/test-utils.ts +25 -11
package/src/aria-snapshot.ts
CHANGED
|
@@ -79,7 +79,7 @@ export type AriaSnapshotNode = {
|
|
|
79
79
|
export interface ScreenshotResult {
|
|
80
80
|
path: string
|
|
81
81
|
base64: string
|
|
82
|
-
mimeType: 'image/
|
|
82
|
+
mimeType: 'image/png'
|
|
83
83
|
snapshot: string
|
|
84
84
|
labelCount: number
|
|
85
85
|
}
|
|
@@ -108,13 +108,15 @@ export interface ResizeImageOptions {
|
|
|
108
108
|
fit?: 'inside' | 'cover' | 'contain' | 'fill'
|
|
109
109
|
/** JPEG quality 1-100. Default 80 */
|
|
110
110
|
quality?: number
|
|
111
|
+
/** Output format. Default 'jpeg'. Use 'png' for lossless output (e.g. Kitty Graphics). */
|
|
112
|
+
format?: 'jpeg' | 'png'
|
|
111
113
|
/** Output file path. Defaults to overwriting the input file (when input is a path) */
|
|
112
114
|
output?: string
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export interface ResizeImageResult {
|
|
116
118
|
buffer: Buffer
|
|
117
|
-
mimeType: 'image/jpeg'
|
|
119
|
+
mimeType: 'image/png' | 'image/jpeg'
|
|
118
120
|
/** Only set if output path was provided */
|
|
119
121
|
path?: string
|
|
120
122
|
}
|
|
@@ -128,7 +130,7 @@ export interface ResizeImageResult {
|
|
|
128
130
|
*
|
|
129
131
|
* Explicit width/height: resizes to those dimensions using the fit strategy.
|
|
130
132
|
*/
|
|
131
|
-
export async function
|
|
133
|
+
export async function resizeImageForAgent(options: ResizeImageOptions): Promise<ResizeImageResult> {
|
|
132
134
|
const sharp = await sharpPromise
|
|
133
135
|
if (!sharp) {
|
|
134
136
|
throw new Error('sharp is not installed — install it with: pnpm add sharp')
|
|
@@ -163,10 +165,9 @@ export async function resizeImage(options: ResizeImageOptions): Promise<ResizeIm
|
|
|
163
165
|
}
|
|
164
166
|
})()
|
|
165
167
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.toBuffer()
|
|
168
|
+
const fmt = options.format ?? 'png'
|
|
169
|
+
const pipeline = sharp(inputBuffer).resize(resizeOpts)
|
|
170
|
+
const buffer = await (fmt === 'png' ? pipeline.png() : pipeline.jpeg({ quality })).toBuffer()
|
|
170
171
|
|
|
171
172
|
// Default: overwrite input file. When input is a Buffer, no file is written
|
|
172
173
|
// unless output is explicitly set.
|
|
@@ -184,9 +185,10 @@ export async function resizeImage(options: ResizeImageOptions): Promise<ResizeIm
|
|
|
184
185
|
fs.writeFileSync(outputPath, buffer)
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
const mimeType: 'image/png' | 'image/jpeg' = fmt === 'png' ? 'image/png' : 'image/jpeg'
|
|
187
189
|
return {
|
|
188
190
|
buffer,
|
|
189
|
-
mimeType
|
|
191
|
+
mimeType,
|
|
190
192
|
...(outputPath ? { path: outputPath } : {}),
|
|
191
193
|
}
|
|
192
194
|
}
|
|
@@ -338,14 +340,24 @@ function buildBaseLocator({
|
|
|
338
340
|
role,
|
|
339
341
|
name,
|
|
340
342
|
stable,
|
|
343
|
+
isPromotedContentEditable,
|
|
341
344
|
}: {
|
|
342
345
|
role: string
|
|
343
346
|
name: string
|
|
344
347
|
stable: { value: string; attr: string } | null
|
|
348
|
+
isPromotedContentEditable?: boolean
|
|
345
349
|
}): string {
|
|
346
350
|
if (stable) {
|
|
347
351
|
return buildLocatorFromStable(stable)
|
|
348
352
|
}
|
|
353
|
+
// For promoted contenteditable elements (bare <div contenteditable="true"> without
|
|
354
|
+
// role="textbox"), use CSS attribute selector. Playwright's role=textbox selector
|
|
355
|
+
// won't match these because Chrome doesn't assign an implicit textbox role to
|
|
356
|
+
// contenteditable divs. Elements that already had role="textbox" use the normal
|
|
357
|
+
// role-based locator since Playwright matches those correctly.
|
|
358
|
+
if (isPromotedContentEditable) {
|
|
359
|
+
return `[contenteditable="true"]`
|
|
360
|
+
}
|
|
349
361
|
const trimmedName = name.trim()
|
|
350
362
|
if (trimmedName.length > 0) {
|
|
351
363
|
const escapedName = escapeLocatorValue(trimmedName)
|
|
@@ -486,6 +498,7 @@ export function filterInteractiveSnapshotTree(options: {
|
|
|
486
498
|
labelContext: boolean
|
|
487
499
|
refFilter?: (entry: { role: string; name: string }) => boolean
|
|
488
500
|
domByBackendId: Map<Protocol.DOM.BackendNodeId, DomNodeInfo>
|
|
501
|
+
promotedContentEditableIds?: Set<Protocol.DOM.BackendNodeId>
|
|
489
502
|
createRefForNode: (options: {
|
|
490
503
|
backendNodeId?: Protocol.DOM.BackendNodeId
|
|
491
504
|
role: string
|
|
@@ -507,6 +520,7 @@ export function filterInteractiveSnapshotTree(options: {
|
|
|
507
520
|
labelContext: nextLabelContext,
|
|
508
521
|
refFilter: options.refFilter,
|
|
509
522
|
domByBackendId: options.domByBackendId,
|
|
523
|
+
promotedContentEditableIds: options.promotedContentEditableIds,
|
|
510
524
|
createRefForNode: options.createRefForNode,
|
|
511
525
|
})
|
|
512
526
|
})
|
|
@@ -575,7 +589,8 @@ export function filterInteractiveSnapshotTree(options: {
|
|
|
575
589
|
if (includeInteractive) {
|
|
576
590
|
const domInfo = options.node.backendNodeId ? options.domByBackendId.get(options.node.backendNodeId) : undefined
|
|
577
591
|
const stable = domInfo ? getStableRefFromAttributes(domInfo.attributes) : null
|
|
578
|
-
|
|
592
|
+
const isPromoted = options.node.backendNodeId != null && (options.promotedContentEditableIds?.has(options.node.backendNodeId) ?? false)
|
|
593
|
+
baseLocator = buildBaseLocator({ role, name, stable, isPromotedContentEditable: isPromoted })
|
|
579
594
|
ref = options.createRefForNode({ backendNodeId: options.node.backendNodeId, role, name })
|
|
580
595
|
}
|
|
581
596
|
|
|
@@ -599,6 +614,7 @@ export function filterFullSnapshotTree(options: {
|
|
|
599
614
|
ancestorNames: string[]
|
|
600
615
|
refFilter?: (entry: { role: string; name: string }) => boolean
|
|
601
616
|
domByBackendId: Map<Protocol.DOM.BackendNodeId, DomNodeInfo>
|
|
617
|
+
promotedContentEditableIds?: Set<Protocol.DOM.BackendNodeId>
|
|
602
618
|
createRefForNode: (options: {
|
|
603
619
|
backendNodeId?: Protocol.DOM.BackendNodeId
|
|
604
620
|
role: string
|
|
@@ -616,6 +632,7 @@ export function filterFullSnapshotTree(options: {
|
|
|
616
632
|
ancestorNames: nextAncestors,
|
|
617
633
|
refFilter: options.refFilter,
|
|
618
634
|
domByBackendId: options.domByBackendId,
|
|
635
|
+
promotedContentEditableIds: options.promotedContentEditableIds,
|
|
619
636
|
createRefForNode: options.createRefForNode,
|
|
620
637
|
})
|
|
621
638
|
})
|
|
@@ -673,7 +690,8 @@ export function filterFullSnapshotTree(options: {
|
|
|
673
690
|
if (includeInteractive) {
|
|
674
691
|
const domInfo = options.node.backendNodeId ? options.domByBackendId.get(options.node.backendNodeId) : undefined
|
|
675
692
|
const stable = domInfo ? getStableRefFromAttributes(domInfo.attributes) : null
|
|
676
|
-
|
|
693
|
+
const isPromoted = options.node.backendNodeId != null && (options.promotedContentEditableIds?.has(options.node.backendNodeId) ?? false)
|
|
694
|
+
baseLocator = buildBaseLocator({ role, name, stable, isPromotedContentEditable: isPromoted })
|
|
677
695
|
ref = options.createRefForNode({ backendNodeId: options.node.backendNodeId, role, name })
|
|
678
696
|
}
|
|
679
697
|
|
|
@@ -853,6 +871,17 @@ function isTextRole(role: string): boolean {
|
|
|
853
871
|
return role === 'statictext' || role === 'inlinetextbox'
|
|
854
872
|
}
|
|
855
873
|
|
|
874
|
+
// Per HTML spec, contenteditable is editable when the attribute is present
|
|
875
|
+
// with value "true", "" (empty string), or "plaintext-only". Bare attribute
|
|
876
|
+
// (no value) is also treated as empty string by the browser DOM parser.
|
|
877
|
+
function isContentEditable(value: string | undefined | null): boolean {
|
|
878
|
+
if (value == null) {
|
|
879
|
+
return false
|
|
880
|
+
}
|
|
881
|
+
const v = value.trim().toLowerCase()
|
|
882
|
+
return v === '' || v === 'true' || v === 'plaintext-only'
|
|
883
|
+
}
|
|
884
|
+
|
|
856
885
|
function isSubstringOfAny(needle: string, haystack: Set<string>): boolean {
|
|
857
886
|
for (const str of haystack) {
|
|
858
887
|
if (str.includes(needle)) {
|
|
@@ -1017,11 +1046,41 @@ export async function getAriaSnapshot({
|
|
|
1017
1046
|
axById.set(node.nodeId, node)
|
|
1018
1047
|
}
|
|
1019
1048
|
|
|
1049
|
+
// Index AX nodes by backendDOMNodeId for O(1) lookups during promotion
|
|
1050
|
+
// and root finding (instead of repeated O(n) axNodes.find() calls)
|
|
1051
|
+
const axByBackendId = new Map<Protocol.DOM.BackendNodeId, Protocol.Accessibility.AXNode>()
|
|
1052
|
+
for (const node of axNodes) {
|
|
1053
|
+
if (node.backendDOMNodeId) {
|
|
1054
|
+
axByBackendId.set(node.backendDOMNodeId, node)
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Promote contenteditable elements that Chrome's AX tree doesn't classify as
|
|
1059
|
+
// interactive. Rich text editors (ProseMirror, Tiptap, Slate, Lexical, etc.) use
|
|
1060
|
+
// bare <div contenteditable="true"> without role="textbox", so Chrome reports
|
|
1061
|
+
// them as "generic" and they become invisible in the snapshot. We detect these
|
|
1062
|
+
// via the DOM tree and override the AX role to "textbox" so they appear as
|
|
1063
|
+
// interactive elements the AI can target.
|
|
1064
|
+
const promotedContentEditableIds = new Set<Protocol.DOM.BackendNodeId>()
|
|
1065
|
+
for (const [, domInfo] of domByBackendId) {
|
|
1066
|
+
if (!isContentEditable(domInfo.attributes.get('contenteditable'))) {
|
|
1067
|
+
continue
|
|
1068
|
+
}
|
|
1069
|
+
const axNode = axByBackendId.get(domInfo.backendNodeId)
|
|
1070
|
+
if (!axNode) {
|
|
1071
|
+
continue
|
|
1072
|
+
}
|
|
1073
|
+
const currentRole = getAxRole(axNode)
|
|
1074
|
+
if (INTERACTIVE_ROLES.has(currentRole)) {
|
|
1075
|
+
continue
|
|
1076
|
+
}
|
|
1077
|
+
axNode.role = { type: 'role', value: 'textbox' }
|
|
1078
|
+
promotedContentEditableIds.add(domInfo.backendNodeId)
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1020
1081
|
const findRootAxNodeId = (): Protocol.Accessibility.AXNodeId | null => {
|
|
1021
1082
|
if (scopeRootBackendId) {
|
|
1022
|
-
const scoped =
|
|
1023
|
-
return node.backendDOMNodeId === scopeRootBackendId
|
|
1024
|
-
})
|
|
1083
|
+
const scoped = axByBackendId.get(scopeRootBackendId)
|
|
1025
1084
|
if (scoped) {
|
|
1026
1085
|
return scoped.nodeId
|
|
1027
1086
|
}
|
|
@@ -1075,6 +1134,12 @@ export async function getAriaSnapshot({
|
|
|
1075
1134
|
if (stable && count === 0) {
|
|
1076
1135
|
selector = buildLocatorFromStable(stable)
|
|
1077
1136
|
}
|
|
1137
|
+
// For promoted contenteditable elements without a stable selector, store
|
|
1138
|
+
// [contenteditable="true"] so getSelectorForRef() doesn't fall back to
|
|
1139
|
+
// role=textbox which Playwright can't match on bare contenteditable divs.
|
|
1140
|
+
if (!selector && options.backendNodeId != null && promotedContentEditableIds.has(options.backendNodeId)) {
|
|
1141
|
+
selector = '[contenteditable="true"]'
|
|
1142
|
+
}
|
|
1078
1143
|
|
|
1079
1144
|
refs.push({ ref, role: options.role, name: options.name, selector, backendNodeId: options.backendNodeId })
|
|
1080
1145
|
return ref
|
|
@@ -1111,6 +1176,7 @@ export async function getAriaSnapshot({
|
|
|
1111
1176
|
labelContext: false,
|
|
1112
1177
|
refFilter,
|
|
1113
1178
|
domByBackendId,
|
|
1179
|
+
promotedContentEditableIds,
|
|
1114
1180
|
createRefForNode,
|
|
1115
1181
|
}).nodes
|
|
1116
1182
|
}
|
|
@@ -1119,6 +1185,7 @@ export async function getAriaSnapshot({
|
|
|
1119
1185
|
ancestorNames: [],
|
|
1120
1186
|
refFilter,
|
|
1121
1187
|
domByBackendId,
|
|
1188
|
+
promotedContentEditableIds,
|
|
1122
1189
|
createRefForNode,
|
|
1123
1190
|
}).nodes
|
|
1124
1191
|
})
|
|
@@ -1538,7 +1605,7 @@ export async function screenshotWithAccessibilityLabels({
|
|
|
1538
1605
|
// Generate unique filename with timestamp
|
|
1539
1606
|
const timestamp = Date.now()
|
|
1540
1607
|
const random = Math.random().toString(36).slice(2, 6)
|
|
1541
|
-
const filename = `playwriter-screenshot-${timestamp}-${random}.
|
|
1608
|
+
const filename = `playwriter-screenshot-${timestamp}-${random}.png`
|
|
1542
1609
|
|
|
1543
1610
|
// Use ./tmp folder (gitignored) instead of system temp
|
|
1544
1611
|
const tmpDir = path.join(process.cwd(), 'tmp')
|
|
@@ -1560,11 +1627,11 @@ export async function screenshotWithAccessibilityLabels({
|
|
|
1560
1627
|
const clipWidth = sharp ? viewport.width : Math.min(viewport.width, LLM_MAX_DIMENSION)
|
|
1561
1628
|
const clipHeight = sharp ? viewport.height : Math.min(viewport.height, LLM_MAX_DIMENSION)
|
|
1562
1629
|
|
|
1563
|
-
// Take viewport screenshot
|
|
1630
|
+
// Take viewport screenshot as PNG for Kitty Graphics Protocol compatibility.
|
|
1631
|
+
// PNG is lossless and the only format extracted by kitty-graphics-agent (f=100).
|
|
1564
1632
|
const screenshotStart = Date.now()
|
|
1565
1633
|
const rawBuffer = await page.screenshot({
|
|
1566
|
-
type: '
|
|
1567
|
-
quality: 80,
|
|
1634
|
+
type: 'png',
|
|
1568
1635
|
scale: 'css',
|
|
1569
1636
|
clip: { x: 0, y: 0, width: clipWidth, height: clipHeight },
|
|
1570
1637
|
})
|
|
@@ -1580,7 +1647,7 @@ export async function screenshotWithAccessibilityLabels({
|
|
|
1580
1647
|
return rawBuffer
|
|
1581
1648
|
}
|
|
1582
1649
|
try {
|
|
1583
|
-
const result = await
|
|
1650
|
+
const result = await resizeImageForAgent({ input: rawBuffer, format: 'png' })
|
|
1584
1651
|
return result.buffer
|
|
1585
1652
|
} catch (err) {
|
|
1586
1653
|
logger?.error?.('[playwriter] sharp resize failed, using raw buffer:', err)
|
|
@@ -1604,7 +1671,7 @@ export async function screenshotWithAccessibilityLabels({
|
|
|
1604
1671
|
collector.push({
|
|
1605
1672
|
path: screenshotPath,
|
|
1606
1673
|
base64,
|
|
1607
|
-
mimeType: 'image/
|
|
1674
|
+
mimeType: 'image/png',
|
|
1608
1675
|
snapshot,
|
|
1609
1676
|
labelCount,
|
|
1610
1677
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
- heading "Rich Text Editor Test"
|
|
2
|
+
- labeltext:
|
|
3
|
+
- text: "Editor:"
|
|
4
|
+
- textbox "Main editor" [id="editor1"]:
|
|
5
|
+
- paragraph:
|
|
6
|
+
- text: "This is some initial content in a contenteditable editor."
|
|
7
|
+
- role=textbox >> nth=0:
|
|
8
|
+
- paragraph:
|
|
9
|
+
- text: "Second editor with no label or id."
|
|
10
|
+
- role=textbox >> nth=1:
|
|
11
|
+
- paragraph:
|
|
12
|
+
- text: "Third editor simulating ProseMirror class."
|
|
13
|
+
- role=textbox[name="Normal text input for comparison"]
|
|
14
|
+
- role=textbox[name="Normal textarea for comparison"]
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
- role=link[name="Skip to content"]
|
|
2
|
-
- alert "Announcement":
|
|
3
|
-
- text: "Inside GitHub’s roadmap for agentic development —"
|
|
4
|
-
- role=link[name="join the product roadmap webinar"]
|
|
5
|
-
- role=button[name="Close"]
|
|
6
2
|
- banner:
|
|
7
3
|
- heading "Navigation Menu"
|
|
8
4
|
- role=link[name="Homepage"]
|