@canaryai/cli 0.2.7 → 0.2.9
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/README.md +77 -92
- package/dist/chunk-C2PGZRYK.js +167 -0
- package/dist/chunk-C2PGZRYK.js.map +1 -0
- package/dist/{chunk-TEHABH2E.js → chunk-LC7ZVXPH.js} +2 -2
- package/dist/{chunk-6WWHXWCS.js → chunk-QLFSJG5O.js} +33 -5
- package/dist/chunk-QLFSJG5O.js.map +1 -0
- package/dist/{chunk-RL5Y6V3C.js → chunk-XGO62PO2.js} +2443 -1073
- package/dist/chunk-XGO62PO2.js.map +1 -0
- package/dist/{debug-workflow-ZFRF3JMY.js → debug-workflow-I3F36JBL.js} +57 -36
- package/dist/debug-workflow-I3F36JBL.js.map +1 -0
- package/dist/{docs-RPFT7ZJB.js → docs-REHST3YB.js} +2 -2
- package/dist/{feature-flag-2FDSKOVX.js → feature-flag-3HB5NTMY.js} +3 -2
- package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-3HB5NTMY.js.map} +1 -1
- package/dist/index.js +22 -9
- package/dist/index.js.map +1 -1
- package/dist/{issues-6ZDNDSD6.js → issues-YU57CHXS.js} +3 -2
- package/dist/{issues-6ZDNDSD6.js.map → issues-YU57CHXS.js.map} +1 -1
- package/dist/{knobs-MZRTYS3P.js → knobs-QJ4IBLCT.js} +3 -2
- package/dist/{knobs-MZRTYS3P.js.map → knobs-QJ4IBLCT.js.map} +1 -1
- package/dist/{local-browser-I2ANCFFH.js → local-browser-MKTJ36KY.js} +3 -3
- package/dist/{mcp-EOWUKFEB.js → mcp-ZOKM2AUE.js} +49 -238
- package/dist/mcp-ZOKM2AUE.js.map +1 -0
- package/dist/{record-TSF726OB.js → record-TNDBT3NY.js} +130 -28
- package/dist/record-TNDBT3NY.js.map +1 -0
- package/dist/session-RNLKFS2Z.js +751 -0
- package/dist/session-RNLKFS2Z.js.map +1 -0
- package/dist/skill-CZ7SHI3P.js +156 -0
- package/dist/skill-CZ7SHI3P.js.map +1 -0
- package/dist/{src-SCKO6YUB.js → src-2WSMYBMJ.js} +20 -2
- package/package.json +2 -2
- package/dist/chunk-6WWHXWCS.js.map +0 -1
- package/dist/chunk-RL5Y6V3C.js.map +0 -1
- package/dist/debug-workflow-ZFRF3JMY.js.map +0 -1
- package/dist/mcp-EOWUKFEB.js.map +0 -1
- package/dist/record-TSF726OB.js.map +0 -1
- /package/dist/{chunk-TEHABH2E.js.map → chunk-LC7ZVXPH.js.map} +0 -0
- /package/dist/{docs-RPFT7ZJB.js.map → docs-REHST3YB.js.map} +0 -0
- /package/dist/{local-browser-I2ANCFFH.js.map → local-browser-MKTJ36KY.js.map} +0 -0
- /package/dist/{src-SCKO6YUB.js.map → src-2WSMYBMJ.js.map} +0 -0
|
@@ -119,7 +119,7 @@ var require_omggif = __commonJS({
|
|
|
119
119
|
var min_code_size = 0;
|
|
120
120
|
while (num_colors >>= 1) ++min_code_size;
|
|
121
121
|
num_colors = 1 << min_code_size;
|
|
122
|
-
var
|
|
122
|
+
var delay2 = opts.delay === void 0 ? 0 : opts.delay;
|
|
123
123
|
var disposal = opts.disposal === void 0 ? 0 : opts.disposal;
|
|
124
124
|
if (disposal < 0 || disposal > 3)
|
|
125
125
|
throw new Error("Disposal out of range.");
|
|
@@ -131,13 +131,13 @@ var require_omggif = __commonJS({
|
|
|
131
131
|
if (transparent_index < 0 || transparent_index >= num_colors)
|
|
132
132
|
throw new Error("Transparent color index.");
|
|
133
133
|
}
|
|
134
|
-
if (disposal !== 0 || use_transparency ||
|
|
134
|
+
if (disposal !== 0 || use_transparency || delay2 !== 0) {
|
|
135
135
|
buf[p++] = 33;
|
|
136
136
|
buf[p++] = 249;
|
|
137
137
|
buf[p++] = 4;
|
|
138
138
|
buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
|
|
139
|
-
buf[p++] =
|
|
140
|
-
buf[p++] =
|
|
139
|
+
buf[p++] = delay2 & 255;
|
|
140
|
+
buf[p++] = delay2 >> 8 & 255;
|
|
141
141
|
buf[p++] = transparent_index;
|
|
142
142
|
buf[p++] = 0;
|
|
143
143
|
}
|
|
@@ -279,7 +279,7 @@ var require_omggif = __commonJS({
|
|
|
279
279
|
}
|
|
280
280
|
var no_eof = true;
|
|
281
281
|
var frames = [];
|
|
282
|
-
var
|
|
282
|
+
var delay2 = 0;
|
|
283
283
|
var transparent_index = null;
|
|
284
284
|
var disposal = 0;
|
|
285
285
|
var loop_count = null;
|
|
@@ -311,7 +311,7 @@ var require_omggif = __commonJS({
|
|
|
311
311
|
if (buf[p++] !== 4 || buf[p + 4] !== 0)
|
|
312
312
|
throw new Error("Invalid graphics extension block.");
|
|
313
313
|
var pf1 = buf[p++];
|
|
314
|
-
|
|
314
|
+
delay2 = buf[p++] | buf[p++] << 8;
|
|
315
315
|
transparent_index = buf[p++];
|
|
316
316
|
if ((pf1 & 1) === 0) transparent_index = null;
|
|
317
317
|
disposal = pf1 >> 2 & 7;
|
|
@@ -370,7 +370,7 @@ var require_omggif = __commonJS({
|
|
|
370
370
|
data_length: p - data_offset,
|
|
371
371
|
transparent_index,
|
|
372
372
|
interlaced: !!interlace_flag,
|
|
373
|
-
delay,
|
|
373
|
+
delay: delay2,
|
|
374
374
|
disposal
|
|
375
375
|
});
|
|
376
376
|
break;
|
|
@@ -3777,7 +3777,7 @@ var require_gifframe = __commonJS({
|
|
|
3777
3777
|
var require_gifutil = __commonJS({
|
|
3778
3778
|
"../../node_modules/.bun/gifwrap@0.10.1/node_modules/gifwrap/src/gifutil.js"(exports2) {
|
|
3779
3779
|
"use strict";
|
|
3780
|
-
var
|
|
3780
|
+
var fs5 = __require("fs");
|
|
3781
3781
|
var ImageQ = require_image_q();
|
|
3782
3782
|
var BitmapImage2 = require_bitmapimage();
|
|
3783
3783
|
var { GifFrame: GifFrame2 } = require_gifframe();
|
|
@@ -3892,14 +3892,14 @@ var require_gifutil = __commonJS({
|
|
|
3892
3892
|
jimpImage.bitmap.data = bitmapImageToShare.bitmap.data;
|
|
3893
3893
|
return jimpImage;
|
|
3894
3894
|
};
|
|
3895
|
-
exports2.write = function(
|
|
3895
|
+
exports2.write = function(path5, frames, spec, encoder) {
|
|
3896
3896
|
encoder = encoder || defaultCodec;
|
|
3897
|
-
const matches =
|
|
3897
|
+
const matches = path5.match(/\.[a-zA-Z]+$/);
|
|
3898
3898
|
if (matches !== null && INVALID_SUFFIXES.includes(matches[0].toLowerCase())) {
|
|
3899
|
-
throw new Error(`GIF '${
|
|
3899
|
+
throw new Error(`GIF '${path5}' has an unexpected suffix`);
|
|
3900
3900
|
}
|
|
3901
3901
|
return encoder.encodeGif(frames, spec).then((gif2) => {
|
|
3902
|
-
return _writeBinary(
|
|
3902
|
+
return _writeBinary(path5, gif2.buffer).then(() => {
|
|
3903
3903
|
return gif2;
|
|
3904
3904
|
});
|
|
3905
3905
|
});
|
|
@@ -3971,9 +3971,9 @@ var require_gifutil = __commonJS({
|
|
|
3971
3971
|
}
|
|
3972
3972
|
}
|
|
3973
3973
|
}
|
|
3974
|
-
function _readBinary(
|
|
3974
|
+
function _readBinary(path5) {
|
|
3975
3975
|
return new Promise((resolve2, reject2) => {
|
|
3976
|
-
|
|
3976
|
+
fs5.readFile(path5, (err, buffer) => {
|
|
3977
3977
|
if (err) {
|
|
3978
3978
|
return reject2(err);
|
|
3979
3979
|
}
|
|
@@ -3981,9 +3981,9 @@ var require_gifutil = __commonJS({
|
|
|
3981
3981
|
});
|
|
3982
3982
|
});
|
|
3983
3983
|
}
|
|
3984
|
-
function _writeBinary(
|
|
3984
|
+
function _writeBinary(path5, buffer) {
|
|
3985
3985
|
return new Promise((resolve2, reject2) => {
|
|
3986
|
-
|
|
3986
|
+
fs5.writeFile(path5, buffer, (err) => {
|
|
3987
3987
|
if (err) {
|
|
3988
3988
|
return reject2(err);
|
|
3989
3989
|
}
|
|
@@ -4383,7 +4383,7 @@ var require_src = __commonJS({
|
|
|
4383
4383
|
var require_encoder = __commonJS({
|
|
4384
4384
|
"../../node_modules/.bun/jpeg-js@0.4.4/node_modules/jpeg-js/lib/encoder.js"(exports2, module2) {
|
|
4385
4385
|
"use strict";
|
|
4386
|
-
var
|
|
4386
|
+
var btoa2 = btoa2 || function(buf) {
|
|
4387
4387
|
return Buffer.from(buf).toString("base64");
|
|
4388
4388
|
};
|
|
4389
4389
|
function JPEGEncoder(quality2) {
|
|
@@ -5404,7 +5404,7 @@ var require_encoder = __commonJS({
|
|
|
5404
5404
|
writeWord(65497);
|
|
5405
5405
|
if (typeof module2 === "undefined") return new Uint8Array(byteout);
|
|
5406
5406
|
return Buffer.from(byteout);
|
|
5407
|
-
var jpegDataUri = "data:image/jpeg;base64," +
|
|
5407
|
+
var jpegDataUri = "data:image/jpeg;base64," + btoa2(byteout.join(""));
|
|
5408
5408
|
byteout = [];
|
|
5409
5409
|
var duration = (/* @__PURE__ */ new Date()).getTime() - time_start;
|
|
5410
5410
|
return jpegDataUri;
|
|
@@ -5964,9 +5964,9 @@ var require_decoder = __commonJS({
|
|
|
5964
5964
|
return a < 0 ? 0 : a > 255 ? 255 : a;
|
|
5965
5965
|
}
|
|
5966
5966
|
constructor.prototype = {
|
|
5967
|
-
load: function load(
|
|
5967
|
+
load: function load(path5) {
|
|
5968
5968
|
var xhr = new XMLHttpRequest();
|
|
5969
|
-
xhr.open("GET",
|
|
5969
|
+
xhr.open("GET", path5, true);
|
|
5970
5970
|
xhr.responseType = "arraybuffer";
|
|
5971
5971
|
xhr.onload = (function() {
|
|
5972
5972
|
var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
|
|
@@ -18862,11 +18862,11 @@ var require_Mime = __commonJS({
|
|
|
18862
18862
|
}
|
|
18863
18863
|
}
|
|
18864
18864
|
};
|
|
18865
|
-
Mime.prototype.getType = function(
|
|
18866
|
-
|
|
18867
|
-
let last =
|
|
18865
|
+
Mime.prototype.getType = function(path5) {
|
|
18866
|
+
path5 = String(path5);
|
|
18867
|
+
let last = path5.replace(/^.*[/\\]/, "").toLowerCase();
|
|
18868
18868
|
let ext = last.replace(/^.*\./, "").toLowerCase();
|
|
18869
|
-
let hasPath = last.length <
|
|
18869
|
+
let hasPath = last.length < path5.length;
|
|
18870
18870
|
let hasDot = ext.length < last.length - 1;
|
|
18871
18871
|
return (hasDot || !hasPath) && this._types[ext] || null;
|
|
18872
18872
|
};
|
|
@@ -32623,9 +32623,9 @@ function createJimp({ plugins: pluginsArg, formats: formatsArg } = {}) {
|
|
|
32623
32623
|
* await image.write("test/output.png");
|
|
32624
32624
|
* ```
|
|
32625
32625
|
*/
|
|
32626
|
-
async write(
|
|
32627
|
-
const mimeType = import_lite.default.getType(
|
|
32628
|
-
await writeFile(
|
|
32626
|
+
async write(path5, options) {
|
|
32627
|
+
const mimeType = import_lite.default.getType(path5);
|
|
32628
|
+
await writeFile(path5, await this.getBuffer(mimeType, options));
|
|
32629
32629
|
}
|
|
32630
32630
|
/**
|
|
32631
32631
|
* Clone the image into a new Jimp instance.
|
|
@@ -37998,6 +37998,201 @@ var CdpScreencastManager = class {
|
|
|
37998
37998
|
}
|
|
37999
37999
|
};
|
|
38000
38000
|
|
|
38001
|
+
// ../browser-core/src/indexeddb-helpers.ts
|
|
38002
|
+
var MAX_TOTAL_SIZE_BYTES = 10 * 1024 * 1024;
|
|
38003
|
+
async function extractIndexedDB(page) {
|
|
38004
|
+
try {
|
|
38005
|
+
const data = await page.evaluate(async () => {
|
|
38006
|
+
if (!indexedDB.databases) return null;
|
|
38007
|
+
const dbInfos = await indexedDB.databases();
|
|
38008
|
+
if (!dbInfos || dbInfos.length === 0) return null;
|
|
38009
|
+
function serializeValue(value) {
|
|
38010
|
+
if (value instanceof ArrayBuffer) {
|
|
38011
|
+
return {
|
|
38012
|
+
__type: "ArrayBuffer",
|
|
38013
|
+
__data: btoa(String.fromCharCode(...new Uint8Array(value)))
|
|
38014
|
+
};
|
|
38015
|
+
}
|
|
38016
|
+
if (ArrayBuffer.isView(value)) {
|
|
38017
|
+
const buf = value.buffer instanceof ArrayBuffer ? value.buffer : value.buffer;
|
|
38018
|
+
return {
|
|
38019
|
+
__type: "TypedArray",
|
|
38020
|
+
__arrayType: value.constructor.name,
|
|
38021
|
+
__data: btoa(String.fromCharCode(...new Uint8Array(buf, value.byteOffset, value.byteLength)))
|
|
38022
|
+
};
|
|
38023
|
+
}
|
|
38024
|
+
if (value instanceof Blob) {
|
|
38025
|
+
return { __type: "Blob", __skipped: true };
|
|
38026
|
+
}
|
|
38027
|
+
if (value instanceof Date) {
|
|
38028
|
+
return { __type: "Date", __data: value.toISOString() };
|
|
38029
|
+
}
|
|
38030
|
+
if (Array.isArray(value)) {
|
|
38031
|
+
return value.map(serializeValue);
|
|
38032
|
+
}
|
|
38033
|
+
if (value !== null && typeof value === "object") {
|
|
38034
|
+
const result = {};
|
|
38035
|
+
for (const [k, v] of Object.entries(value)) {
|
|
38036
|
+
result[k] = serializeValue(v);
|
|
38037
|
+
}
|
|
38038
|
+
return result;
|
|
38039
|
+
}
|
|
38040
|
+
return value;
|
|
38041
|
+
}
|
|
38042
|
+
const databases = [];
|
|
38043
|
+
for (const dbInfo of dbInfos) {
|
|
38044
|
+
if (!dbInfo.name) continue;
|
|
38045
|
+
try {
|
|
38046
|
+
const db = await new Promise((resolve2, reject2) => {
|
|
38047
|
+
const request2 = indexedDB.open(dbInfo.name, dbInfo.version);
|
|
38048
|
+
request2.onsuccess = () => resolve2(request2.result);
|
|
38049
|
+
request2.onerror = () => reject2(request2.error);
|
|
38050
|
+
request2.onupgradeneeded = () => {
|
|
38051
|
+
request2.transaction?.abort();
|
|
38052
|
+
reject2(new Error("version_mismatch"));
|
|
38053
|
+
};
|
|
38054
|
+
});
|
|
38055
|
+
const objectStores = [];
|
|
38056
|
+
for (const storeName of Array.from(db.objectStoreNames)) {
|
|
38057
|
+
try {
|
|
38058
|
+
const tx = db.transaction(storeName, "readonly");
|
|
38059
|
+
const store = tx.objectStore(storeName);
|
|
38060
|
+
const entries = await new Promise(
|
|
38061
|
+
(resolve2, reject2) => {
|
|
38062
|
+
const items = [];
|
|
38063
|
+
const cursorReq = store.openCursor();
|
|
38064
|
+
cursorReq.onsuccess = () => {
|
|
38065
|
+
const cursor = cursorReq.result;
|
|
38066
|
+
if (cursor) {
|
|
38067
|
+
items.push({
|
|
38068
|
+
key: cursor.key,
|
|
38069
|
+
value: serializeValue(cursor.value)
|
|
38070
|
+
});
|
|
38071
|
+
cursor.continue();
|
|
38072
|
+
} else {
|
|
38073
|
+
resolve2(items);
|
|
38074
|
+
}
|
|
38075
|
+
};
|
|
38076
|
+
cursorReq.onerror = () => reject2(cursorReq.error);
|
|
38077
|
+
}
|
|
38078
|
+
);
|
|
38079
|
+
objectStores.push({
|
|
38080
|
+
name: storeName,
|
|
38081
|
+
keyPath: store.keyPath,
|
|
38082
|
+
autoIncrement: store.autoIncrement,
|
|
38083
|
+
entries
|
|
38084
|
+
});
|
|
38085
|
+
} catch {
|
|
38086
|
+
}
|
|
38087
|
+
}
|
|
38088
|
+
db.close();
|
|
38089
|
+
databases.push({
|
|
38090
|
+
name: dbInfo.name,
|
|
38091
|
+
version: dbInfo.version ?? 1,
|
|
38092
|
+
objectStores
|
|
38093
|
+
});
|
|
38094
|
+
} catch {
|
|
38095
|
+
}
|
|
38096
|
+
}
|
|
38097
|
+
return databases.length > 0 ? { databases } : null;
|
|
38098
|
+
});
|
|
38099
|
+
if (!data) return null;
|
|
38100
|
+
const serialized = JSON.stringify(data);
|
|
38101
|
+
if (serialized.length > MAX_TOTAL_SIZE_BYTES) {
|
|
38102
|
+
return null;
|
|
38103
|
+
}
|
|
38104
|
+
return data;
|
|
38105
|
+
} catch {
|
|
38106
|
+
return null;
|
|
38107
|
+
}
|
|
38108
|
+
}
|
|
38109
|
+
async function restoreIndexedDB(page, data) {
|
|
38110
|
+
if (!data.databases || data.databases.length === 0) return;
|
|
38111
|
+
await page.evaluate(async (databases) => {
|
|
38112
|
+
function deserializeValue(value) {
|
|
38113
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
38114
|
+
const obj = value;
|
|
38115
|
+
if (obj.__type === "ArrayBuffer" && typeof obj.__data === "string") {
|
|
38116
|
+
const binary = atob(obj.__data);
|
|
38117
|
+
const bytes = new Uint8Array(binary.length);
|
|
38118
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
38119
|
+
return bytes.buffer;
|
|
38120
|
+
}
|
|
38121
|
+
if (obj.__type === "TypedArray" && typeof obj.__data === "string") {
|
|
38122
|
+
const binary = atob(obj.__data);
|
|
38123
|
+
const bytes = new Uint8Array(binary.length);
|
|
38124
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
38125
|
+
return bytes.buffer;
|
|
38126
|
+
}
|
|
38127
|
+
if (obj.__type === "Date" && typeof obj.__data === "string") {
|
|
38128
|
+
return new Date(obj.__data);
|
|
38129
|
+
}
|
|
38130
|
+
if (obj.__type === "Blob" && obj.__skipped) {
|
|
38131
|
+
return null;
|
|
38132
|
+
}
|
|
38133
|
+
const result = {};
|
|
38134
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
38135
|
+
result[k] = deserializeValue(v);
|
|
38136
|
+
}
|
|
38137
|
+
return result;
|
|
38138
|
+
}
|
|
38139
|
+
if (Array.isArray(value)) {
|
|
38140
|
+
return value.map(deserializeValue);
|
|
38141
|
+
}
|
|
38142
|
+
return value;
|
|
38143
|
+
}
|
|
38144
|
+
for (const dbData of databases) {
|
|
38145
|
+
try {
|
|
38146
|
+
await new Promise((resolve2, reject2) => {
|
|
38147
|
+
const req = indexedDB.deleteDatabase(dbData.name);
|
|
38148
|
+
req.onsuccess = () => resolve2();
|
|
38149
|
+
req.onerror = () => reject2(req.error);
|
|
38150
|
+
req.onblocked = () => resolve2();
|
|
38151
|
+
});
|
|
38152
|
+
const db = await new Promise((resolve2, reject2) => {
|
|
38153
|
+
const request2 = indexedDB.open(dbData.name, dbData.version);
|
|
38154
|
+
request2.onupgradeneeded = () => {
|
|
38155
|
+
const db2 = request2.result;
|
|
38156
|
+
for (const storeData of dbData.objectStores) {
|
|
38157
|
+
if (!db2.objectStoreNames.contains(storeData.name)) {
|
|
38158
|
+
db2.createObjectStore(storeData.name, {
|
|
38159
|
+
keyPath: storeData.keyPath ?? void 0,
|
|
38160
|
+
autoIncrement: storeData.autoIncrement
|
|
38161
|
+
});
|
|
38162
|
+
}
|
|
38163
|
+
}
|
|
38164
|
+
};
|
|
38165
|
+
request2.onsuccess = () => resolve2(request2.result);
|
|
38166
|
+
request2.onerror = () => reject2(request2.error);
|
|
38167
|
+
});
|
|
38168
|
+
for (const storeData of dbData.objectStores) {
|
|
38169
|
+
if (storeData.entries.length === 0) continue;
|
|
38170
|
+
if (!db.objectStoreNames.contains(storeData.name)) continue;
|
|
38171
|
+
try {
|
|
38172
|
+
const tx = db.transaction(storeData.name, "readwrite");
|
|
38173
|
+
const store = tx.objectStore(storeData.name);
|
|
38174
|
+
for (const entry of storeData.entries) {
|
|
38175
|
+
const value = deserializeValue(entry.value);
|
|
38176
|
+
if (storeData.keyPath) {
|
|
38177
|
+
store.put(value);
|
|
38178
|
+
} else {
|
|
38179
|
+
store.put(value, entry.key);
|
|
38180
|
+
}
|
|
38181
|
+
}
|
|
38182
|
+
await new Promise((resolve2, reject2) => {
|
|
38183
|
+
tx.oncomplete = () => resolve2();
|
|
38184
|
+
tx.onerror = () => reject2(tx.error);
|
|
38185
|
+
});
|
|
38186
|
+
} catch {
|
|
38187
|
+
}
|
|
38188
|
+
}
|
|
38189
|
+
db.close();
|
|
38190
|
+
} catch {
|
|
38191
|
+
}
|
|
38192
|
+
}
|
|
38193
|
+
}, data.databases);
|
|
38194
|
+
}
|
|
38195
|
+
|
|
38001
38196
|
// ../browser-core/src/snapshot-analyzer.ts
|
|
38002
38197
|
var logger = consoleLogger;
|
|
38003
38198
|
function setSnapshotAnalyzerLogger(l) {
|
|
@@ -38343,7 +38538,7 @@ function normalizeIconLabelText(role, text) {
|
|
|
38343
38538
|
return ICON_ONLY_ROLES.has(role) ? "(icon)" : "";
|
|
38344
38539
|
}
|
|
38345
38540
|
|
|
38346
|
-
// ../browser-core/src/snapshot-formatter.ts
|
|
38541
|
+
// ../browser-core/src/snapshot-formatter-shared.ts
|
|
38347
38542
|
var MAX_SEARCH_MATCHES = 10;
|
|
38348
38543
|
var INTERACTIVE_LEAF_ROLES = /* @__PURE__ */ new Set([
|
|
38349
38544
|
"button",
|
|
@@ -38378,113 +38573,6 @@ var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
|
38378
38573
|
"section",
|
|
38379
38574
|
"iframe"
|
|
38380
38575
|
]);
|
|
38381
|
-
var VISUAL_HEADING_MAX_LENGTH = 60;
|
|
38382
|
-
function hasInteractiveDescendants(element) {
|
|
38383
|
-
if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
|
|
38384
|
-
for (const child of element.children) {
|
|
38385
|
-
if (hasInteractiveDescendants(child)) return true;
|
|
38386
|
-
}
|
|
38387
|
-
return false;
|
|
38388
|
-
}
|
|
38389
|
-
function isVisualHeading(element, siblings, siblingIndex) {
|
|
38390
|
-
if (element.role !== "generic" && element.role !== "paragraph") return false;
|
|
38391
|
-
if (isClickableByAttribute(element)) return false;
|
|
38392
|
-
const text = getVisualHeadingText(element);
|
|
38393
|
-
if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
|
|
38394
|
-
if (hasInteractiveDescendants(element)) return false;
|
|
38395
|
-
let interactiveCount = 0;
|
|
38396
|
-
for (let i = siblingIndex + 1; i < siblings.length; i++) {
|
|
38397
|
-
const sib = siblings[i];
|
|
38398
|
-
if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
|
|
38399
|
-
if (sib.role === "generic" || sib.role === "paragraph") {
|
|
38400
|
-
if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
|
|
38401
|
-
const sibText = getVisualHeadingText(sib);
|
|
38402
|
-
if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
|
|
38403
|
-
break;
|
|
38404
|
-
}
|
|
38405
|
-
}
|
|
38406
|
-
}
|
|
38407
|
-
if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
|
|
38408
|
-
interactiveCount++;
|
|
38409
|
-
}
|
|
38410
|
-
}
|
|
38411
|
-
return interactiveCount >= 3;
|
|
38412
|
-
}
|
|
38413
|
-
function getVisualHeadingText(element) {
|
|
38414
|
-
const direct = element.text;
|
|
38415
|
-
if (direct) return direct;
|
|
38416
|
-
for (const child of element.children) {
|
|
38417
|
-
if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
|
|
38418
|
-
}
|
|
38419
|
-
return void 0;
|
|
38420
|
-
}
|
|
38421
|
-
function findNextSectionBoundary(siblings, startIndex) {
|
|
38422
|
-
for (let i = startIndex; i < siblings.length; i++) {
|
|
38423
|
-
const sib = siblings[i];
|
|
38424
|
-
if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
|
|
38425
|
-
if (isVisualHeading(sib, siblings, i)) return i;
|
|
38426
|
-
}
|
|
38427
|
-
return siblings.length;
|
|
38428
|
-
}
|
|
38429
|
-
function isFieldLabelCandidate(element) {
|
|
38430
|
-
if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
|
|
38431
|
-
return false;
|
|
38432
|
-
}
|
|
38433
|
-
if (isClickableByAttribute(element)) return false;
|
|
38434
|
-
if (hasInteractiveDescendants(element)) return false;
|
|
38435
|
-
const text = getFieldLabelText(element);
|
|
38436
|
-
if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
|
|
38437
|
-
return true;
|
|
38438
|
-
}
|
|
38439
|
-
var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
38440
|
-
"generic",
|
|
38441
|
-
"paragraph",
|
|
38442
|
-
"text",
|
|
38443
|
-
"strong",
|
|
38444
|
-
"emphasis",
|
|
38445
|
-
"label",
|
|
38446
|
-
"heading"
|
|
38447
|
-
]);
|
|
38448
|
-
function getFieldLabelText(element) {
|
|
38449
|
-
if (element.text) return element.text.trim();
|
|
38450
|
-
if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
|
|
38451
|
-
for (const child of element.children) {
|
|
38452
|
-
if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
|
|
38453
|
-
if (child.text) return child.text.trim();
|
|
38454
|
-
if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
|
|
38455
|
-
}
|
|
38456
|
-
}
|
|
38457
|
-
return void 0;
|
|
38458
|
-
}
|
|
38459
|
-
function findFieldLabelFromSiblings(siblings, interactiveIndex) {
|
|
38460
|
-
for (let i = interactiveIndex - 1; i >= 0; i--) {
|
|
38461
|
-
const sib = siblings[i];
|
|
38462
|
-
if (INTERACTIVE_ROLES.has(sib.role) || isClickableByAttribute(sib) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
|
|
38463
|
-
break;
|
|
38464
|
-
}
|
|
38465
|
-
if (isFieldLabelCandidate(sib)) {
|
|
38466
|
-
return getFieldLabelText(sib);
|
|
38467
|
-
}
|
|
38468
|
-
if (sib.children.length >= 1 && sib.children.length <= 4) {
|
|
38469
|
-
const containerLabel = resolveContainerFieldLabel(sib);
|
|
38470
|
-
if (containerLabel) return containerLabel;
|
|
38471
|
-
}
|
|
38472
|
-
if (sib.children.length > 4) break;
|
|
38473
|
-
}
|
|
38474
|
-
return void 0;
|
|
38475
|
-
}
|
|
38476
|
-
function findFieldLabelInContext(_element, siblings, siblingIndex) {
|
|
38477
|
-
return findFieldLabelFromSiblings(siblings, siblingIndex);
|
|
38478
|
-
}
|
|
38479
|
-
function resolveContainerFieldLabel(container) {
|
|
38480
|
-
const kids = container.children;
|
|
38481
|
-
if (kids.length < 2 || kids.length > 4) return void 0;
|
|
38482
|
-
const firstChild = kids[0];
|
|
38483
|
-
if (isFieldLabelCandidate(firstChild)) {
|
|
38484
|
-
return getFieldLabelText(firstChild);
|
|
38485
|
-
}
|
|
38486
|
-
return void 0;
|
|
38487
|
-
}
|
|
38488
38576
|
var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
|
|
38489
38577
|
"generic",
|
|
38490
38578
|
"paragraph",
|
|
@@ -38495,12 +38583,11 @@ var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
|
|
|
38495
38583
|
"blockquote",
|
|
38496
38584
|
"caption"
|
|
38497
38585
|
]);
|
|
38586
|
+
var VISUAL_HEADING_MAX_LENGTH = 60;
|
|
38498
38587
|
var STATUS_WORDS = /* @__PURE__ */ new Set(["favorable", "unfavorable", "success", "error", "warning", "pending"]);
|
|
38499
38588
|
var BLANK_PATTERNS = [
|
|
38500
38589
|
/\s*\(Blank\)\s*/gi,
|
|
38501
|
-
// "(Blank)" in any case
|
|
38502
38590
|
/\s*\(blank\)\s*/gi
|
|
38503
|
-
// Redundant but explicit
|
|
38504
38591
|
];
|
|
38505
38592
|
var MENU_TRIGGER_PATTERNS = [
|
|
38506
38593
|
/^Open menu for /i,
|
|
@@ -38515,21 +38602,77 @@ var SECTION_BOUNDARY_ROLES = /* @__PURE__ */ new Set([
|
|
|
38515
38602
|
"article",
|
|
38516
38603
|
"section",
|
|
38517
38604
|
"complementary"
|
|
38518
|
-
// sidebars
|
|
38519
38605
|
]);
|
|
38606
|
+
var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
|
|
38520
38607
|
function normalizeSearchText(text) {
|
|
38521
38608
|
return text.toLowerCase().replace(/[-_.,;:!?'"()\[\]{}\/\\@#$%^&*+=<>~`|]/g, "").replace(/\s+/g, " ").trim();
|
|
38522
38609
|
}
|
|
38523
|
-
function
|
|
38524
|
-
|
|
38525
|
-
|
|
38526
|
-
|
|
38527
|
-
|
|
38528
|
-
|
|
38529
|
-
|
|
38610
|
+
function isActiveElement(element) {
|
|
38611
|
+
return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
|
|
38612
|
+
}
|
|
38613
|
+
function isDisabledElement(element) {
|
|
38614
|
+
return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
|
|
38615
|
+
}
|
|
38616
|
+
function isClickableByAttribute(element) {
|
|
38617
|
+
return element.attributes["cursor"] === "pointer";
|
|
38618
|
+
}
|
|
38619
|
+
function isActionableClickableGeneric(element) {
|
|
38620
|
+
return isClickableByAttribute(element) && !INTERACTIVE_ROLES.has(element.role);
|
|
38621
|
+
}
|
|
38622
|
+
function isSearchInteractiveElement(element) {
|
|
38623
|
+
return INTERACTIVE_LEAF_ROLES.has(element.role) || isActionableClickableGeneric(element);
|
|
38624
|
+
}
|
|
38625
|
+
function getElementText(el) {
|
|
38626
|
+
if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
|
|
38627
|
+
return el.inputValue;
|
|
38628
|
+
}
|
|
38629
|
+
return el.text || el.inputValue;
|
|
38630
|
+
}
|
|
38631
|
+
function composeLabel(element) {
|
|
38632
|
+
const directText = getElementText(element);
|
|
38633
|
+
if (directText) return directText;
|
|
38634
|
+
const textParts = [];
|
|
38635
|
+
function collectText(el, depth) {
|
|
38636
|
+
if (depth > 3) return;
|
|
38637
|
+
const text = getElementText(el);
|
|
38638
|
+
if (text && TEXT_CARRYING_ROLES.has(el.role)) {
|
|
38639
|
+
textParts.push(text);
|
|
38640
|
+
}
|
|
38641
|
+
for (const child of el.children) {
|
|
38642
|
+
if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
|
|
38643
|
+
collectText(child, depth + 1);
|
|
38644
|
+
}
|
|
38645
|
+
}
|
|
38646
|
+
}
|
|
38647
|
+
for (const child of element.children) {
|
|
38648
|
+
if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
|
|
38649
|
+
collectText(child, 0);
|
|
38650
|
+
}
|
|
38651
|
+
}
|
|
38652
|
+
return textParts.join(" | ") || element.role;
|
|
38653
|
+
}
|
|
38654
|
+
function getDisplayText(element, options) {
|
|
38655
|
+
const isClickable = isActionableClickableGeneric(element);
|
|
38656
|
+
const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
|
|
38657
|
+
return normalizeIconLabelText(element.role, rawTextSource);
|
|
38658
|
+
}
|
|
38659
|
+
function hasInteractiveDescendants(element) {
|
|
38660
|
+
if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
|
|
38661
|
+
for (const child of element.children) {
|
|
38662
|
+
if (hasInteractiveDescendants(child)) return true;
|
|
38663
|
+
}
|
|
38664
|
+
return false;
|
|
38665
|
+
}
|
|
38666
|
+
function subtreeContainsActive(element) {
|
|
38667
|
+
if (isActiveElement(element)) return true;
|
|
38668
|
+
for (const child of element.children) {
|
|
38669
|
+
if (subtreeContainsActive(child)) return true;
|
|
38530
38670
|
}
|
|
38531
38671
|
return false;
|
|
38532
38672
|
}
|
|
38673
|
+
function cleanLabel(text) {
|
|
38674
|
+
return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
|
|
38675
|
+
}
|
|
38533
38676
|
function isBlankValue(value) {
|
|
38534
38677
|
if (!value) return true;
|
|
38535
38678
|
const trimmed = value.trim().toLowerCase();
|
|
@@ -38569,160 +38712,15 @@ function extractDataValue(text) {
|
|
|
38569
38712
|
}
|
|
38570
38713
|
return { label: cleanLabel(cleaned), status };
|
|
38571
38714
|
}
|
|
38572
|
-
function
|
|
38573
|
-
if (
|
|
38574
|
-
|
|
38575
|
-
|
|
38576
|
-
|
|
38577
|
-
|
|
38578
|
-
|
|
38579
|
-
if (el.ref === activeElementRef) {
|
|
38580
|
-
return true;
|
|
38581
|
-
}
|
|
38582
|
-
for (const child of el.children) {
|
|
38583
|
-
if (findPath(child)) return true;
|
|
38584
|
-
}
|
|
38585
|
-
if (el.ref) path4.pop();
|
|
38586
|
-
return false;
|
|
38587
|
-
}
|
|
38588
|
-
for (const el of elements) {
|
|
38589
|
-
if (findPath(el)) break;
|
|
38590
|
-
}
|
|
38591
|
-
for (let i = path4.length - 1; i >= 0; i--) {
|
|
38592
|
-
if (SECTION_BOUNDARY_ROLES.has(path4[i].role)) {
|
|
38593
|
-
return path4[i].ref;
|
|
38594
|
-
}
|
|
38595
|
-
}
|
|
38596
|
-
return null;
|
|
38597
|
-
}
|
|
38598
|
-
function searchElements(elements, searchTerms, scopeRef = null) {
|
|
38599
|
-
const allMatches = [];
|
|
38600
|
-
const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
|
|
38601
|
-
if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
|
|
38602
|
-
function searchElement(el, inScope) {
|
|
38603
|
-
const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
|
|
38604
|
-
if (el.ref && nowInScope) {
|
|
38605
|
-
const searchText = [el.text, el.inputValue].filter(Boolean).join(" ");
|
|
38606
|
-
const normalizedSearchText = normalizeSearchText(searchText);
|
|
38607
|
-
for (const term of normalizedTerms) {
|
|
38608
|
-
if (normalizedSearchText.includes(term)) {
|
|
38609
|
-
const normalizedLabel = normalizeIconLabelText(el.role, el.text || "");
|
|
38610
|
-
allMatches.push({
|
|
38611
|
-
ref: el.ref,
|
|
38612
|
-
role: el.role,
|
|
38613
|
-
label: normalizedLabel || el.role,
|
|
38614
|
-
term,
|
|
38615
|
-
isInteractive: INTERACTIVE_LEAF_ROLES.has(el.role)
|
|
38616
|
-
});
|
|
38617
|
-
break;
|
|
38618
|
-
}
|
|
38619
|
-
}
|
|
38620
|
-
}
|
|
38621
|
-
for (const child of el.children) {
|
|
38622
|
-
searchElement(child, nowInScope);
|
|
38623
|
-
}
|
|
38624
|
-
}
|
|
38625
|
-
for (const el of elements) {
|
|
38626
|
-
searchElement(el, scopeRef === null);
|
|
38627
|
-
}
|
|
38628
|
-
const totalFound = allMatches.length;
|
|
38629
|
-
allMatches.sort((a, b) => {
|
|
38630
|
-
if (a.isInteractive && !b.isInteractive) return -1;
|
|
38631
|
-
if (!a.isInteractive && b.isInteractive) return 1;
|
|
38632
|
-
return 0;
|
|
38633
|
-
});
|
|
38634
|
-
return {
|
|
38635
|
-
matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
|
|
38636
|
-
totalFound
|
|
38637
|
-
};
|
|
38638
|
-
}
|
|
38639
|
-
function findElementInSections(sections, targetRef, ancestors = []) {
|
|
38640
|
-
for (const section of sections) {
|
|
38641
|
-
const newAncestors = [...ancestors, section];
|
|
38642
|
-
const path4 = newAncestors.map((s) => s.heading);
|
|
38643
|
-
if (section.elements.some((el) => el.ref === targetRef)) {
|
|
38644
|
-
return { section, ancestors: newAncestors, path: path4 };
|
|
38645
|
-
}
|
|
38646
|
-
const found = findElementInSections(section.subsections, targetRef, newAncestors);
|
|
38647
|
-
if (found) return found;
|
|
38648
|
-
}
|
|
38649
|
-
return null;
|
|
38650
|
-
}
|
|
38651
|
-
function populateSearchContext(matches, sections) {
|
|
38652
|
-
for (const match of matches) {
|
|
38653
|
-
const result = findElementInSections(sections, match.ref);
|
|
38654
|
-
if (result) {
|
|
38655
|
-
const { section, ancestors, path: path4 } = result;
|
|
38656
|
-
const inFocusedDialog = ancestors.some((s) => s.isFocused);
|
|
38657
|
-
const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
|
|
38658
|
-
if (inFocusedDialog) {
|
|
38659
|
-
match.context = "focused";
|
|
38660
|
-
} else if (inBackground) {
|
|
38661
|
-
match.context = "background";
|
|
38662
|
-
} else {
|
|
38663
|
-
match.context = "foreground";
|
|
38664
|
-
}
|
|
38665
|
-
const SKIP_HEADINGS = ["generic", "main", "iframe"];
|
|
38666
|
-
match.sectionPath = path4.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
|
|
38667
|
-
}
|
|
38668
|
-
}
|
|
38669
|
-
}
|
|
38670
|
-
function sortMatchesByContext(matches) {
|
|
38671
|
-
const contextOrder = {
|
|
38672
|
-
focused: 0,
|
|
38673
|
-
foreground: 1,
|
|
38674
|
-
background: 2
|
|
38675
|
-
};
|
|
38676
|
-
return [...matches].sort((a, b) => {
|
|
38677
|
-
const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
|
|
38678
|
-
const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
|
|
38679
|
-
if (aContext !== bContext) return aContext - bContext;
|
|
38680
|
-
if (a.isInteractive && !b.isInteractive) return -1;
|
|
38681
|
-
if (!a.isInteractive && b.isInteractive) return 1;
|
|
38682
|
-
return 0;
|
|
38683
|
-
});
|
|
38684
|
-
}
|
|
38685
|
-
function cleanLabel(text) {
|
|
38686
|
-
return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
|
|
38687
|
-
}
|
|
38688
|
-
function isActiveElement(element) {
|
|
38689
|
-
return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
|
|
38690
|
-
}
|
|
38691
|
-
function isDisabledElement(element) {
|
|
38692
|
-
return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
|
|
38693
|
-
}
|
|
38694
|
-
function isClickableByAttribute(element) {
|
|
38695
|
-
return element.attributes["cursor"] === "pointer";
|
|
38696
|
-
}
|
|
38697
|
-
var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
|
|
38698
|
-
function getElementText(el) {
|
|
38699
|
-
if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
|
|
38700
|
-
return el.inputValue;
|
|
38701
|
-
}
|
|
38702
|
-
return el.text || el.inputValue;
|
|
38703
|
-
}
|
|
38704
|
-
function composeLabel(element) {
|
|
38705
|
-
const directText = getElementText(element);
|
|
38706
|
-
if (directText) return directText;
|
|
38707
|
-
const textParts = [];
|
|
38708
|
-
function collectText(el, depth) {
|
|
38709
|
-
if (depth > 3) return;
|
|
38710
|
-
const text = getElementText(el);
|
|
38711
|
-
if (text && TEXT_CARRYING_ROLES.has(el.role)) {
|
|
38712
|
-
textParts.push(text);
|
|
38713
|
-
}
|
|
38714
|
-
for (const child of el.children) {
|
|
38715
|
-
if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
|
|
38716
|
-
collectText(child, depth + 1);
|
|
38717
|
-
}
|
|
38718
|
-
}
|
|
38719
|
-
}
|
|
38720
|
-
for (const child of element.children) {
|
|
38721
|
-
if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
|
|
38722
|
-
collectText(child, 0);
|
|
38723
|
-
}
|
|
38715
|
+
function isMenuTriggerNoise(element) {
|
|
38716
|
+
if (isActiveElement(element)) return false;
|
|
38717
|
+
if (element.role !== "button") return false;
|
|
38718
|
+
const text = element.text || "";
|
|
38719
|
+
if (!text.trim()) return false;
|
|
38720
|
+
for (const pattern of MENU_TRIGGER_PATTERNS) {
|
|
38721
|
+
if (pattern.test(text)) return true;
|
|
38724
38722
|
}
|
|
38725
|
-
return
|
|
38723
|
+
return false;
|
|
38726
38724
|
}
|
|
38727
38725
|
function countElementsWithRefs(element) {
|
|
38728
38726
|
let count = element.ref ? 1 : 0;
|
|
@@ -38731,74 +38729,23 @@ function countElementsWithRefs(element) {
|
|
|
38731
38729
|
}
|
|
38732
38730
|
return count;
|
|
38733
38731
|
}
|
|
38734
|
-
function
|
|
38735
|
-
|
|
38736
|
-
|
|
38737
|
-
|
|
38738
|
-
|
|
38739
|
-
if (STRUCTURAL_ROLES.has(child.role)) continue;
|
|
38740
|
-
if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
|
|
38741
|
-
result.push(formatElement(child));
|
|
38742
|
-
continue;
|
|
38743
|
-
}
|
|
38744
|
-
if (child.role === "listitem" && child.ref) {
|
|
38745
|
-
const text = child.text || composeLabel(child);
|
|
38746
|
-
if (text && text !== "listitem") {
|
|
38747
|
-
result.push(formatElement(child, { overrideLabel: text }));
|
|
38748
|
-
continue;
|
|
38749
|
-
}
|
|
38750
|
-
}
|
|
38751
|
-
if (isClickableByAttribute(child) && child.ref) {
|
|
38752
|
-
result.push(formatElement(child));
|
|
38732
|
+
function collectComboboxOptions(element) {
|
|
38733
|
+
const options = [];
|
|
38734
|
+
function walk(el) {
|
|
38735
|
+
if (el.role === "option" && el.text?.trim()) {
|
|
38736
|
+
options.push(el.text.trim());
|
|
38753
38737
|
}
|
|
38754
|
-
|
|
38755
|
-
|
|
38756
|
-
}
|
|
38757
|
-
function hasActionableChildren(element) {
|
|
38758
|
-
const children2 = [];
|
|
38759
|
-
collectExpandedChildren(element, children2);
|
|
38760
|
-
return children2.length > 0;
|
|
38761
|
-
}
|
|
38762
|
-
function isExpandedWithActionableChildren(element) {
|
|
38763
|
-
if (!isExpandedElement(element)) return false;
|
|
38764
|
-
if (element.role === "combobox") return false;
|
|
38765
|
-
return hasActionableChildren(element);
|
|
38766
|
-
}
|
|
38767
|
-
function composeTriggerLabel(element) {
|
|
38768
|
-
const ariaLabel = element.attributes["aria-label"];
|
|
38769
|
-
if (ariaLabel) return ariaLabel;
|
|
38770
|
-
const fullText = element.text || "";
|
|
38771
|
-
if (fullText) {
|
|
38772
|
-
let collectLeafTexts2 = function(el) {
|
|
38773
|
-
for (const child of el.children) {
|
|
38774
|
-
const text = child.text?.trim();
|
|
38775
|
-
if (text) {
|
|
38776
|
-
leafTexts.push(text);
|
|
38777
|
-
}
|
|
38778
|
-
collectLeafTexts2(child);
|
|
38779
|
-
}
|
|
38780
|
-
};
|
|
38781
|
-
var collectLeafTexts = collectLeafTexts2;
|
|
38782
|
-
const leafTexts = [];
|
|
38783
|
-
collectLeafTexts2(element);
|
|
38784
|
-
if (leafTexts.length > 0) {
|
|
38785
|
-
let stripped = fullText;
|
|
38786
|
-
for (const lt of leafTexts) {
|
|
38787
|
-
stripped = stripped.replace(lt, "");
|
|
38788
|
-
}
|
|
38789
|
-
stripped = stripped.replace(/\s+/g, " ").trim();
|
|
38790
|
-
if (stripped) return stripped;
|
|
38738
|
+
for (const child of el.children) {
|
|
38739
|
+
walk(child);
|
|
38791
38740
|
}
|
|
38792
38741
|
}
|
|
38793
|
-
return element.role;
|
|
38794
|
-
}
|
|
38795
|
-
function subtreeContainsActive(element) {
|
|
38796
|
-
if (isActiveElement(element)) return true;
|
|
38797
38742
|
for (const child of element.children) {
|
|
38798
|
-
|
|
38743
|
+
walk(child);
|
|
38799
38744
|
}
|
|
38800
|
-
return
|
|
38745
|
+
return options.length > 0 ? options : void 0;
|
|
38801
38746
|
}
|
|
38747
|
+
|
|
38748
|
+
// ../browser-core/src/snapshot-formatter-grid.ts
|
|
38802
38749
|
function findDescendantByRole(el, role) {
|
|
38803
38750
|
if (el.role === role) return el;
|
|
38804
38751
|
for (const child of el.children) {
|
|
@@ -38825,51 +38772,250 @@ function findPrimaryColumnLabel(columnHeader) {
|
|
|
38825
38772
|
};
|
|
38826
38773
|
return search(columnHeader);
|
|
38827
38774
|
}
|
|
38828
|
-
function
|
|
38829
|
-
if (element.role !== "
|
|
38830
|
-
|
|
38831
|
-
|
|
38832
|
-
|
|
38833
|
-
|
|
38834
|
-
const rows
|
|
38835
|
-
|
|
38836
|
-
|
|
38837
|
-
|
|
38838
|
-
|
|
38839
|
-
|
|
38840
|
-
const hasColumnHeaders = rowEl.children.some((c2) => c2.role === "columnheader");
|
|
38841
|
-
if (hasColumnHeaders) {
|
|
38842
|
-
headerRowRef = rowEl.ref;
|
|
38843
|
-
const columnHeaders = rowEl.children.filter((c2) => c2.role === "columnheader");
|
|
38844
|
-
const hasChildful = columnHeaders.some((c2) => c2.children.length > 0);
|
|
38845
|
-
const columnsBefore = columns.length;
|
|
38846
|
-
for (let i = 0; i < rowEl.children.length; i++) {
|
|
38847
|
-
const child = rowEl.children[i];
|
|
38848
|
-
if (child.role === "columnheader") {
|
|
38849
|
-
if (hasChildful && child.children.length === 0) continue;
|
|
38850
|
-
const primaryLabel = findPrimaryColumnLabel(child);
|
|
38851
|
-
const rawName = (primaryLabel ? getElementText(primaryLabel) : void 0) || child.text || "";
|
|
38852
|
-
const cleanName = rawName.replace(/,?\s*sorted in \w+ order/i, "").trim();
|
|
38853
|
-
if (cleanName) {
|
|
38854
|
-
columnByPosition.set(i, cleanName);
|
|
38855
|
-
columns.push({ name: cleanName, ref: primaryLabel?.ref || child.ref });
|
|
38856
|
-
} else {
|
|
38857
|
-
const checkboxChild = findDescendantByRole(child, "checkbox");
|
|
38858
|
-
if (checkboxChild?.ref) {
|
|
38859
|
-
const syntheticName = "\u2610";
|
|
38860
|
-
columnByPosition.set(i, syntheticName);
|
|
38861
|
-
columns.push({ name: syntheticName, ref: checkboxChild.ref });
|
|
38862
|
-
}
|
|
38863
|
-
}
|
|
38775
|
+
function isWidgetTable(element) {
|
|
38776
|
+
if (element.role !== "table") return false;
|
|
38777
|
+
const rows = element.children.filter((c2) => c2.role === "row");
|
|
38778
|
+
if (rows.length === 0 || rows.length > 2) return false;
|
|
38779
|
+
let totalCells = 0;
|
|
38780
|
+
let interactiveCells = 0;
|
|
38781
|
+
for (const row of rows) {
|
|
38782
|
+
for (const cell of row.children) {
|
|
38783
|
+
if (cell.role === "cell" || cell.role === "gridcell" || cell.role === "columnheader") {
|
|
38784
|
+
totalCells++;
|
|
38785
|
+
if (hasInteractiveDescendants(cell)) {
|
|
38786
|
+
interactiveCells++;
|
|
38864
38787
|
}
|
|
38865
38788
|
}
|
|
38866
|
-
|
|
38867
|
-
|
|
38789
|
+
}
|
|
38790
|
+
}
|
|
38791
|
+
return totalCells > 0 && interactiveCells >= totalCells * 0.5;
|
|
38792
|
+
}
|
|
38793
|
+
function gridCellPriority(el) {
|
|
38794
|
+
if (el.isActive) return 5;
|
|
38795
|
+
if (FORM_INPUT_ROLES.has(el.role)) return 4;
|
|
38796
|
+
if (el.isClickableGeneric) return 3;
|
|
38797
|
+
if (el.role !== "button") return 2;
|
|
38798
|
+
return 1;
|
|
38799
|
+
}
|
|
38800
|
+
function extractCellText(el) {
|
|
38801
|
+
const findFirstParagraph = (node) => {
|
|
38802
|
+
if (node.role === "paragraph" && node.text) {
|
|
38803
|
+
return node.text.trim();
|
|
38804
|
+
}
|
|
38805
|
+
for (const child of node.children) {
|
|
38806
|
+
if (child.role === "generic" || child.role === "paragraph") {
|
|
38807
|
+
const found = findFirstParagraph(child);
|
|
38808
|
+
if (found) return found;
|
|
38868
38809
|
}
|
|
38869
|
-
return null;
|
|
38870
38810
|
}
|
|
38871
|
-
|
|
38872
|
-
|
|
38811
|
+
return null;
|
|
38812
|
+
};
|
|
38813
|
+
const paragraphText = findFirstParagraph(el);
|
|
38814
|
+
if (paragraphText) return paragraphText;
|
|
38815
|
+
const text = el.text?.trim();
|
|
38816
|
+
if (text) return text;
|
|
38817
|
+
return null;
|
|
38818
|
+
}
|
|
38819
|
+
function extractGridCell(cellEl) {
|
|
38820
|
+
const interactiveElements = [];
|
|
38821
|
+
const findInteractive = (el) => {
|
|
38822
|
+
if (isClickableByAttribute(el) && el.ref && el.role === "generic") {
|
|
38823
|
+
const childTexts = [];
|
|
38824
|
+
for (const child of el.children) {
|
|
38825
|
+
if (child.role === "generic") {
|
|
38826
|
+
const childText = child.text || child.inputValue;
|
|
38827
|
+
if (childText) childTexts.push(childText);
|
|
38828
|
+
}
|
|
38829
|
+
}
|
|
38830
|
+
interactiveElements.push({
|
|
38831
|
+
ref: el.ref,
|
|
38832
|
+
column: el.attributes["label"] || childTexts[0] || "",
|
|
38833
|
+
value: childTexts.join(" - ") || el.text || el.inputValue || "",
|
|
38834
|
+
role: el.role,
|
|
38835
|
+
isClickableGeneric: true,
|
|
38836
|
+
isActive: isActiveElement(el)
|
|
38837
|
+
});
|
|
38838
|
+
}
|
|
38839
|
+
if (INTERACTIVE_LEAF_ROLES.has(el.role) && el.ref) {
|
|
38840
|
+
const value2 = el.role === "checkbox" ? el.attributes["checked"] === "true" ? "checked" : "unchecked" : el.inputValue || el.text || "";
|
|
38841
|
+
interactiveElements.push({
|
|
38842
|
+
ref: el.ref,
|
|
38843
|
+
column: el.attributes["label"] || el.text || "",
|
|
38844
|
+
value: value2,
|
|
38845
|
+
role: el.role,
|
|
38846
|
+
isClickableGeneric: false,
|
|
38847
|
+
isActive: isActiveElement(el)
|
|
38848
|
+
});
|
|
38849
|
+
}
|
|
38850
|
+
for (const child of el.children) findInteractive(child);
|
|
38851
|
+
};
|
|
38852
|
+
findInteractive(cellEl);
|
|
38853
|
+
if (interactiveElements.length === 0) {
|
|
38854
|
+
const textValue = extractCellText(cellEl);
|
|
38855
|
+
if (textValue) {
|
|
38856
|
+
return {
|
|
38857
|
+
ref: cellEl.ref,
|
|
38858
|
+
column: cellEl.text || "",
|
|
38859
|
+
value: textValue,
|
|
38860
|
+
hasValue: true
|
|
38861
|
+
};
|
|
38862
|
+
}
|
|
38863
|
+
return null;
|
|
38864
|
+
}
|
|
38865
|
+
const found = interactiveElements.reduce(
|
|
38866
|
+
(best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
|
|
38867
|
+
);
|
|
38868
|
+
const column = stripBlankPatterns(found.column);
|
|
38869
|
+
const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
|
|
38870
|
+
const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
|
|
38871
|
+
return {
|
|
38872
|
+
ref: found.ref,
|
|
38873
|
+
column: column.trim(),
|
|
38874
|
+
value,
|
|
38875
|
+
hasValue: value !== "",
|
|
38876
|
+
gridcellLabel: gridcellLabel || void 0,
|
|
38877
|
+
role: found.role
|
|
38878
|
+
};
|
|
38879
|
+
}
|
|
38880
|
+
function reconcileCellColumn(cell, knownColumns) {
|
|
38881
|
+
if (knownColumns.has(cell.column)) return cell;
|
|
38882
|
+
if (cell.gridcellLabel && knownColumns.has(cell.gridcellLabel)) {
|
|
38883
|
+
const newValue = cell.column || cell.value;
|
|
38884
|
+
const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
|
|
38885
|
+
return {
|
|
38886
|
+
...cell,
|
|
38887
|
+
column: cell.gridcellLabel,
|
|
38888
|
+
value: cleanValue,
|
|
38889
|
+
hasValue: cleanValue !== ""
|
|
38890
|
+
};
|
|
38891
|
+
}
|
|
38892
|
+
let bestMatch = "";
|
|
38893
|
+
for (const col of knownColumns) {
|
|
38894
|
+
if (cell.column.startsWith(col) && col.length > bestMatch.length && (cell.column.length === col.length || cell.column[col.length] === " ")) {
|
|
38895
|
+
bestMatch = col;
|
|
38896
|
+
}
|
|
38897
|
+
}
|
|
38898
|
+
if (bestMatch) {
|
|
38899
|
+
const remainder = cell.column.slice(bestMatch.length).trim();
|
|
38900
|
+
const newValue = isBlankValue(remainder) ? "" : remainder;
|
|
38901
|
+
return {
|
|
38902
|
+
...cell,
|
|
38903
|
+
column: bestMatch,
|
|
38904
|
+
value: newValue || cell.value,
|
|
38905
|
+
hasValue: (newValue || cell.value) !== ""
|
|
38906
|
+
};
|
|
38907
|
+
}
|
|
38908
|
+
return cell;
|
|
38909
|
+
}
|
|
38910
|
+
function reconcileGridCells(grid) {
|
|
38911
|
+
if (grid.columns.length === 0) return grid;
|
|
38912
|
+
const knownColumns = new Set(grid.columns.map((c2) => c2.name));
|
|
38913
|
+
return {
|
|
38914
|
+
...grid,
|
|
38915
|
+
rows: grid.rows.map((row) => {
|
|
38916
|
+
const reconciledCells = row.cells.map((cell) => {
|
|
38917
|
+
const reconciled = reconcileCellColumn(cell, knownColumns);
|
|
38918
|
+
if (knownColumns.has(reconciled.column)) return reconciled;
|
|
38919
|
+
if (grid.columnByPosition && cell.positionInRow !== void 0 && grid.headerChildCount !== void 0 && row.totalChildren === grid.headerChildCount) {
|
|
38920
|
+
const positionalColumn = grid.columnByPosition.get(cell.positionInRow);
|
|
38921
|
+
if (positionalColumn) {
|
|
38922
|
+
const newValue = cell.column || cell.value;
|
|
38923
|
+
const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
|
|
38924
|
+
return {
|
|
38925
|
+
...cell,
|
|
38926
|
+
column: positionalColumn,
|
|
38927
|
+
value: cleanValue,
|
|
38928
|
+
hasValue: cleanValue !== ""
|
|
38929
|
+
};
|
|
38930
|
+
}
|
|
38931
|
+
}
|
|
38932
|
+
return reconciled;
|
|
38933
|
+
});
|
|
38934
|
+
if (grid.headerCellPositions && row.cellPositions) {
|
|
38935
|
+
const claimedColumns = new Set(
|
|
38936
|
+
reconciledCells.filter((c2) => knownColumns.has(c2.column)).map((c2) => c2.column)
|
|
38937
|
+
);
|
|
38938
|
+
for (let idx = 0; idx < reconciledCells.length; idx++) {
|
|
38939
|
+
const cell = reconciledCells[idx];
|
|
38940
|
+
if (knownColumns.has(cell.column) || cell.positionInRow === void 0) continue;
|
|
38941
|
+
const cellOrdinal = row.cellPositions.indexOf(cell.positionInRow);
|
|
38942
|
+
if (cellOrdinal >= 0 && cellOrdinal < grid.headerCellPositions.length) {
|
|
38943
|
+
const headerPos = grid.headerCellPositions[cellOrdinal];
|
|
38944
|
+
const positionalColumn = grid.columnByPosition?.get(headerPos);
|
|
38945
|
+
if (positionalColumn && !claimedColumns.has(positionalColumn)) {
|
|
38946
|
+
const newValue = cell.column || cell.value;
|
|
38947
|
+
const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
|
|
38948
|
+
reconciledCells[idx] = {
|
|
38949
|
+
...cell,
|
|
38950
|
+
column: positionalColumn,
|
|
38951
|
+
value: cleanValue,
|
|
38952
|
+
hasValue: cleanValue !== ""
|
|
38953
|
+
};
|
|
38954
|
+
claimedColumns.add(positionalColumn);
|
|
38955
|
+
}
|
|
38956
|
+
}
|
|
38957
|
+
}
|
|
38958
|
+
}
|
|
38959
|
+
return { ...row, cells: reconciledCells };
|
|
38960
|
+
})
|
|
38961
|
+
};
|
|
38962
|
+
}
|
|
38963
|
+
function detectGrid(element) {
|
|
38964
|
+
if (element.role !== "grid" && element.role !== "treegrid" && element.role !== "table") {
|
|
38965
|
+
return null;
|
|
38966
|
+
}
|
|
38967
|
+
const isHTMLTable = element.role === "table";
|
|
38968
|
+
const columns = [];
|
|
38969
|
+
const rows = [];
|
|
38970
|
+
let headerRowRef = null;
|
|
38971
|
+
const columnByPosition = /* @__PURE__ */ new Map();
|
|
38972
|
+
let headerChildCount;
|
|
38973
|
+
let headerCellPositions;
|
|
38974
|
+
const isRowLike = (el) => {
|
|
38975
|
+
if (el.role === "row") return true;
|
|
38976
|
+
const cellCount = el.children.filter(
|
|
38977
|
+
(c2) => c2.role === "cell" || c2.role === "gridcell"
|
|
38978
|
+
).length;
|
|
38979
|
+
return cellCount >= 2;
|
|
38980
|
+
};
|
|
38981
|
+
const processRow = (rowEl, index) => {
|
|
38982
|
+
if (!isRowLike(rowEl)) return null;
|
|
38983
|
+
const hasColumnHeaders = rowEl.children.some((c2) => c2.role === "columnheader");
|
|
38984
|
+
if (hasColumnHeaders) {
|
|
38985
|
+
headerRowRef = rowEl.ref;
|
|
38986
|
+
const columnHeaders = rowEl.children.filter((c2) => c2.role === "columnheader");
|
|
38987
|
+
const childful = columnHeaders.filter((c2) => c2.children.length > 0);
|
|
38988
|
+
const textBearingChildless = columnHeaders.filter((c2) => c2.children.length === 0 && c2.text);
|
|
38989
|
+
const skipChildlessHeaders = childful.length > 0 && textBearingChildless.length <= childful.length;
|
|
38990
|
+
const columnsBefore = columns.length;
|
|
38991
|
+
for (let i = 0; i < rowEl.children.length; i++) {
|
|
38992
|
+
const child = rowEl.children[i];
|
|
38993
|
+
if (child.role === "columnheader") {
|
|
38994
|
+
if (skipChildlessHeaders && child.children.length === 0) continue;
|
|
38995
|
+
const primaryLabel = findPrimaryColumnLabel(child);
|
|
38996
|
+
const rawName = (primaryLabel ? getElementText(primaryLabel) : void 0) || child.text || "";
|
|
38997
|
+
const cleanName = rawName.replace(/,?\s*sorted in \w+ order/i, "").trim();
|
|
38998
|
+
if (cleanName) {
|
|
38999
|
+
columnByPosition.set(i, cleanName);
|
|
39000
|
+
columns.push({ name: cleanName, ref: primaryLabel?.ref || child.ref });
|
|
39001
|
+
} else {
|
|
39002
|
+
const checkboxChild = findDescendantByRole(child, "checkbox");
|
|
39003
|
+
if (checkboxChild?.ref) {
|
|
39004
|
+
const syntheticName = "\u2610";
|
|
39005
|
+
columnByPosition.set(i, syntheticName);
|
|
39006
|
+
columns.push({ name: syntheticName, ref: checkboxChild.ref });
|
|
39007
|
+
}
|
|
39008
|
+
}
|
|
39009
|
+
}
|
|
39010
|
+
}
|
|
39011
|
+
if (columns.length > columnsBefore) {
|
|
39012
|
+
headerChildCount = rowEl.children.length;
|
|
39013
|
+
headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
|
|
39014
|
+
}
|
|
39015
|
+
return null;
|
|
39016
|
+
}
|
|
39017
|
+
if (isHTMLTable && columns.length === 0) {
|
|
39018
|
+
const cellChildren = rowEl.children.filter((c2) => c2.role === "cell");
|
|
38873
39019
|
const textBearingCells = cellChildren.filter((c2) => {
|
|
38874
39020
|
if (c2.text) return true;
|
|
38875
39021
|
return c2.children.some((ch) => ch.text || ch.inputValue);
|
|
@@ -38906,6 +39052,7 @@ function detectGrid(element) {
|
|
|
38906
39052
|
}
|
|
38907
39053
|
if (columns.length > columnsBefore) {
|
|
38908
39054
|
headerChildCount = rowEl.children.length;
|
|
39055
|
+
headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
|
|
38909
39056
|
}
|
|
38910
39057
|
return null;
|
|
38911
39058
|
}
|
|
@@ -38931,7 +39078,20 @@ function detectGrid(element) {
|
|
|
38931
39078
|
findButtons(cellEl);
|
|
38932
39079
|
}
|
|
38933
39080
|
}
|
|
38934
|
-
const
|
|
39081
|
+
const cellPositions = [];
|
|
39082
|
+
for (let i = 0; i < rowEl.children.length; i++) {
|
|
39083
|
+
if (rowEl.children[i].role === "gridcell" || rowEl.children[i].role === "cell") {
|
|
39084
|
+
cellPositions.push(i);
|
|
39085
|
+
}
|
|
39086
|
+
}
|
|
39087
|
+
const row = {
|
|
39088
|
+
ref: rowEl.ref,
|
|
39089
|
+
index,
|
|
39090
|
+
isSelected,
|
|
39091
|
+
cells,
|
|
39092
|
+
totalChildren: rowEl.children.length,
|
|
39093
|
+
cellPositions
|
|
39094
|
+
};
|
|
38935
39095
|
if (cells.length === 0 && rowEl.children.some((c2) => c2.role === "rowheader")) {
|
|
38936
39096
|
const labels = [];
|
|
38937
39097
|
const buttons = [];
|
|
@@ -38959,7 +39119,7 @@ function detectGrid(element) {
|
|
|
38959
39119
|
return row;
|
|
38960
39120
|
};
|
|
38961
39121
|
const findRows = (el) => {
|
|
38962
|
-
if (el
|
|
39122
|
+
if (isRowLike(el)) {
|
|
38963
39123
|
const row = processRow(el, rows.length);
|
|
38964
39124
|
if (row) rows.push(row);
|
|
38965
39125
|
} else {
|
|
@@ -38977,180 +39137,417 @@ function detectGrid(element) {
|
|
|
38977
39137
|
totalRows: rows.length,
|
|
38978
39138
|
containsActive: subtreeContainsActive(element),
|
|
38979
39139
|
columnByPosition: columnByPosition.size > 0 ? columnByPosition : void 0,
|
|
38980
|
-
headerChildCount
|
|
39140
|
+
headerChildCount,
|
|
39141
|
+
headerCellPositions
|
|
38981
39142
|
};
|
|
38982
39143
|
return reconcileGridCells(rawGrid);
|
|
38983
39144
|
}
|
|
38984
|
-
function
|
|
38985
|
-
|
|
38986
|
-
|
|
38987
|
-
|
|
38988
|
-
|
|
38989
|
-
|
|
39145
|
+
function gridContainsRef(grid, targetRef) {
|
|
39146
|
+
for (const column of grid.columns) {
|
|
39147
|
+
if (column.ref === targetRef) return true;
|
|
39148
|
+
}
|
|
39149
|
+
for (const row of grid.rows) {
|
|
39150
|
+
if (row.ref === targetRef) return true;
|
|
39151
|
+
if (row.cells.some((cell) => cell.ref === targetRef)) return true;
|
|
39152
|
+
if (row.rowActions?.some((action) => action.ref === targetRef)) return true;
|
|
39153
|
+
if (row.expandedContent?.buttons.some((button) => button.ref === targetRef)) return true;
|
|
39154
|
+
}
|
|
39155
|
+
return false;
|
|
38990
39156
|
}
|
|
38991
|
-
function
|
|
38992
|
-
const
|
|
38993
|
-
|
|
38994
|
-
|
|
39157
|
+
function countRenderedGridItems(grid) {
|
|
39158
|
+
const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
|
|
39159
|
+
let count = 0;
|
|
39160
|
+
for (const row of grid.rows) {
|
|
39161
|
+
count += row.cells.filter((c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))).length;
|
|
39162
|
+
count += row.rowActions?.length ?? 0;
|
|
39163
|
+
count += row.expandedContent?.buttons.length ?? 0;
|
|
39164
|
+
}
|
|
39165
|
+
return count;
|
|
39166
|
+
}
|
|
39167
|
+
function formatGridOutput(grid, indent, expanded = false) {
|
|
39168
|
+
const indentStr = " ".repeat(indent);
|
|
39169
|
+
const lines = [];
|
|
39170
|
+
const colInfo = grid.columns.length > 0 ? `${grid.columns.length} columns` : "unknown columns";
|
|
39171
|
+
lines.push(`${indentStr}GRID [${grid.ref}]: ${grid.totalRows} rows, ${colInfo}`);
|
|
39172
|
+
const MAX_DISPLAY_COLUMNS = 10;
|
|
39173
|
+
if (grid.columns.length > 0) {
|
|
39174
|
+
const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
|
|
39175
|
+
const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
|
|
39176
|
+
const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
|
|
39177
|
+
lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
|
|
39178
|
+
}
|
|
39179
|
+
lines.push("");
|
|
39180
|
+
const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
|
|
39181
|
+
const MAX_GRID_ROWS = 30;
|
|
39182
|
+
const displayRows = expanded ? grid.rows : grid.rows.slice(0, MAX_GRID_ROWS);
|
|
39183
|
+
for (const row of displayRows) {
|
|
39184
|
+
lines.push(formatGridRow(row, indent + 1, knownColumns));
|
|
39185
|
+
}
|
|
39186
|
+
if (!expanded && grid.rows.length > MAX_GRID_ROWS) {
|
|
39187
|
+
lines.push(`${indentStr} ... and ${grid.rows.length - MAX_GRID_ROWS} more rows (use expand="${grid.ref}" to see all)`);
|
|
39188
|
+
}
|
|
39189
|
+
return lines.join("\n");
|
|
39190
|
+
}
|
|
39191
|
+
function formatGridRow(row, indent, knownColumns) {
|
|
39192
|
+
const indentStr = " ".repeat(indent);
|
|
39193
|
+
if (row.expandedContent) {
|
|
39194
|
+
const parts = [];
|
|
39195
|
+
if (row.expandedContent.labels.length > 0) {
|
|
39196
|
+
parts.push(row.expandedContent.labels.join(", "));
|
|
38995
39197
|
}
|
|
38996
|
-
for (const
|
|
38997
|
-
|
|
38998
|
-
|
|
38999
|
-
|
|
39198
|
+
for (const btn of row.expandedContent.buttons) {
|
|
39199
|
+
parts.push(`${btn.label} [${btn.ref}]`);
|
|
39200
|
+
}
|
|
39201
|
+
return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
|
|
39202
|
+
}
|
|
39203
|
+
const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
|
|
39204
|
+
const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a) => `[\u25B6 ${a.ref}]`).join(" ") + " " : "";
|
|
39205
|
+
const cellsWithValue = row.cells.filter(
|
|
39206
|
+
(c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))
|
|
39207
|
+
);
|
|
39208
|
+
if (cellsWithValue.length > 0) {
|
|
39209
|
+
const cellStrs = cellsWithValue.map((c2) => {
|
|
39210
|
+
if (c2.role === "checkbox") {
|
|
39211
|
+
const icon = c2.value === "checked" ? "\u2611" : "\u2610";
|
|
39212
|
+
return `${icon} [${c2.ref}]`;
|
|
39213
|
+
}
|
|
39214
|
+
if (c2.column === c2.value) {
|
|
39215
|
+
return `${c2.column} [${c2.ref}]`;
|
|
39000
39216
|
}
|
|
39217
|
+
return `${c2.column} [${c2.ref}]: ${c2.value}`;
|
|
39218
|
+
});
|
|
39219
|
+
return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: ${cellStrs.join(" | ")}`;
|
|
39220
|
+
}
|
|
39221
|
+
return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: (empty row)`;
|
|
39222
|
+
}
|
|
39223
|
+
|
|
39224
|
+
// ../browser-core/src/snapshot-formatter-search.ts
|
|
39225
|
+
function findRelevanceScope(elements, activeElementRef) {
|
|
39226
|
+
if (!activeElementRef) return null;
|
|
39227
|
+
const path5 = [];
|
|
39228
|
+
function findPath(el) {
|
|
39229
|
+
if (el.ref) {
|
|
39230
|
+
path5.push({ ref: el.ref, role: el.role });
|
|
39001
39231
|
}
|
|
39002
|
-
|
|
39003
|
-
|
|
39004
|
-
|
|
39005
|
-
|
|
39006
|
-
|
|
39007
|
-
|
|
39232
|
+
if (el.ref === activeElementRef) {
|
|
39233
|
+
return true;
|
|
39234
|
+
}
|
|
39235
|
+
for (const child of el.children) {
|
|
39236
|
+
if (findPath(child)) return true;
|
|
39237
|
+
}
|
|
39238
|
+
if (el.ref) path5.pop();
|
|
39239
|
+
return false;
|
|
39240
|
+
}
|
|
39241
|
+
for (const el of elements) {
|
|
39242
|
+
if (findPath(el)) break;
|
|
39243
|
+
}
|
|
39244
|
+
for (let i = path5.length - 1; i >= 0; i--) {
|
|
39245
|
+
if (SECTION_BOUNDARY_ROLES.has(path5[i].role)) {
|
|
39246
|
+
return path5[i].ref;
|
|
39247
|
+
}
|
|
39248
|
+
}
|
|
39008
39249
|
return null;
|
|
39009
39250
|
}
|
|
39010
|
-
function
|
|
39011
|
-
const
|
|
39012
|
-
const
|
|
39013
|
-
|
|
39014
|
-
|
|
39015
|
-
|
|
39016
|
-
|
|
39017
|
-
|
|
39018
|
-
|
|
39251
|
+
function searchElements(elements, searchTerms, scopeRef = null) {
|
|
39252
|
+
const allMatches = [];
|
|
39253
|
+
const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
|
|
39254
|
+
if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
|
|
39255
|
+
function searchElement(el, inScope) {
|
|
39256
|
+
const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
|
|
39257
|
+
if (el.ref && nowInScope) {
|
|
39258
|
+
const searchLabel = getDisplayText(el);
|
|
39259
|
+
const searchText = [searchLabel, el.inputValue].filter(Boolean).join(" ");
|
|
39260
|
+
const normalizedSearchText = normalizeSearchText(searchText);
|
|
39261
|
+
for (const term of normalizedTerms) {
|
|
39262
|
+
if (normalizedSearchText.includes(term)) {
|
|
39263
|
+
allMatches.push({
|
|
39264
|
+
ref: el.ref,
|
|
39265
|
+
role: el.role,
|
|
39266
|
+
label: searchLabel || el.role,
|
|
39267
|
+
term,
|
|
39268
|
+
isInteractive: isSearchInteractiveElement(el)
|
|
39269
|
+
});
|
|
39270
|
+
break;
|
|
39019
39271
|
}
|
|
39020
39272
|
}
|
|
39021
|
-
interactiveElements.push({
|
|
39022
|
-
ref: el.ref,
|
|
39023
|
-
column: el.attributes["label"] || childTexts[0] || "",
|
|
39024
|
-
value: childTexts.join(" - ") || el.text || el.inputValue || "",
|
|
39025
|
-
role: el.role,
|
|
39026
|
-
isClickableGeneric: true,
|
|
39027
|
-
isActive: isActiveElement(el)
|
|
39028
|
-
});
|
|
39029
39273
|
}
|
|
39030
|
-
|
|
39031
|
-
|
|
39032
|
-
interactiveElements.push({
|
|
39033
|
-
ref: el.ref,
|
|
39034
|
-
column: el.attributes["label"] || el.text || "",
|
|
39035
|
-
value: value2,
|
|
39036
|
-
role: el.role,
|
|
39037
|
-
isClickableGeneric: false,
|
|
39038
|
-
isActive: isActiveElement(el)
|
|
39039
|
-
});
|
|
39274
|
+
for (const child of el.children) {
|
|
39275
|
+
searchElement(child, nowInScope);
|
|
39040
39276
|
}
|
|
39041
|
-
|
|
39277
|
+
}
|
|
39278
|
+
for (const el of elements) {
|
|
39279
|
+
searchElement(el, scopeRef === null);
|
|
39280
|
+
}
|
|
39281
|
+
const totalFound = allMatches.length;
|
|
39282
|
+
allMatches.sort((a, b) => {
|
|
39283
|
+
if (a.isInteractive && !b.isInteractive) return -1;
|
|
39284
|
+
if (!a.isInteractive && b.isInteractive) return 1;
|
|
39285
|
+
return 0;
|
|
39286
|
+
});
|
|
39287
|
+
return {
|
|
39288
|
+
matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
|
|
39289
|
+
totalFound
|
|
39042
39290
|
};
|
|
39043
|
-
|
|
39044
|
-
|
|
39045
|
-
|
|
39046
|
-
|
|
39047
|
-
|
|
39048
|
-
|
|
39049
|
-
|
|
39050
|
-
value: textValue,
|
|
39051
|
-
hasValue: true
|
|
39052
|
-
};
|
|
39291
|
+
}
|
|
39292
|
+
function findElementInSections(sections, targetRef, ancestors = []) {
|
|
39293
|
+
for (const section of sections) {
|
|
39294
|
+
const newAncestors = [...ancestors, section];
|
|
39295
|
+
const path5 = newAncestors.map((s) => s.heading);
|
|
39296
|
+
if (section.elements.some((el) => el.ref === targetRef)) {
|
|
39297
|
+
return { section, ancestors: newAncestors, path: path5 };
|
|
39053
39298
|
}
|
|
39054
|
-
|
|
39299
|
+
if (section.gridInfo && gridContainsRef(section.gridInfo, targetRef)) {
|
|
39300
|
+
return { section, ancestors: newAncestors, path: path5 };
|
|
39301
|
+
}
|
|
39302
|
+
const found = findElementInSections(section.subsections, targetRef, newAncestors);
|
|
39303
|
+
if (found) return found;
|
|
39055
39304
|
}
|
|
39056
|
-
|
|
39057
|
-
|
|
39058
|
-
|
|
39059
|
-
const
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39064
|
-
|
|
39065
|
-
|
|
39066
|
-
|
|
39067
|
-
|
|
39068
|
-
|
|
39305
|
+
return null;
|
|
39306
|
+
}
|
|
39307
|
+
function populateSearchContext(matches, sections) {
|
|
39308
|
+
for (const match of matches) {
|
|
39309
|
+
const result = findElementInSections(sections, match.ref);
|
|
39310
|
+
if (result) {
|
|
39311
|
+
const { section, ancestors, path: path5 } = result;
|
|
39312
|
+
const inFocusedDialog = ancestors.some((s) => s.isFocused);
|
|
39313
|
+
const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
|
|
39314
|
+
if (inFocusedDialog) {
|
|
39315
|
+
match.context = "focused";
|
|
39316
|
+
} else if (inBackground) {
|
|
39317
|
+
match.context = "background";
|
|
39318
|
+
} else {
|
|
39319
|
+
match.context = "foreground";
|
|
39320
|
+
}
|
|
39321
|
+
const SKIP_HEADINGS = ["generic", "main", "iframe"];
|
|
39322
|
+
match.sectionPath = path5.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
|
|
39323
|
+
}
|
|
39324
|
+
}
|
|
39325
|
+
}
|
|
39326
|
+
function sortMatchesByContext(matches) {
|
|
39327
|
+
const contextOrder = {
|
|
39328
|
+
focused: 0,
|
|
39329
|
+
foreground: 1,
|
|
39330
|
+
background: 2
|
|
39069
39331
|
};
|
|
39332
|
+
return [...matches].sort((a, b) => {
|
|
39333
|
+
const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
|
|
39334
|
+
const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
|
|
39335
|
+
if (aContext !== bContext) return aContext - bContext;
|
|
39336
|
+
if (a.isInteractive && !b.isInteractive) return -1;
|
|
39337
|
+
if (!a.isInteractive && b.isInteractive) return 1;
|
|
39338
|
+
return 0;
|
|
39339
|
+
});
|
|
39070
39340
|
}
|
|
39071
|
-
|
|
39072
|
-
|
|
39073
|
-
|
|
39074
|
-
|
|
39075
|
-
|
|
39076
|
-
|
|
39077
|
-
|
|
39078
|
-
|
|
39079
|
-
|
|
39080
|
-
|
|
39081
|
-
|
|
39341
|
+
|
|
39342
|
+
// ../browser-core/src/snapshot-formatter-sections.ts
|
|
39343
|
+
var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
39344
|
+
"generic",
|
|
39345
|
+
"paragraph",
|
|
39346
|
+
"text",
|
|
39347
|
+
"strong",
|
|
39348
|
+
"emphasis",
|
|
39349
|
+
"label",
|
|
39350
|
+
"heading"
|
|
39351
|
+
]);
|
|
39352
|
+
var MIN_ELEMENTS_TO_COLLAPSE = 20;
|
|
39353
|
+
var DIALOG_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
|
|
39354
|
+
function isVisualHeading(element, siblings, siblingIndex) {
|
|
39355
|
+
if (element.role !== "generic" && element.role !== "paragraph") return false;
|
|
39356
|
+
if (isClickableByAttribute(element)) return false;
|
|
39357
|
+
const text = getVisualHeadingText(element);
|
|
39358
|
+
if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
|
|
39359
|
+
if (hasInteractiveDescendants(element)) return false;
|
|
39360
|
+
let interactiveCount = 0;
|
|
39361
|
+
for (let i = siblingIndex + 1; i < siblings.length; i++) {
|
|
39362
|
+
const sib = siblings[i];
|
|
39363
|
+
if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
|
|
39364
|
+
if (sib.role === "generic" || sib.role === "paragraph") {
|
|
39365
|
+
if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
|
|
39366
|
+
const sibText = getVisualHeadingText(sib);
|
|
39367
|
+
if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
|
|
39368
|
+
break;
|
|
39369
|
+
}
|
|
39370
|
+
}
|
|
39371
|
+
}
|
|
39372
|
+
if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
|
|
39373
|
+
interactiveCount++;
|
|
39374
|
+
}
|
|
39082
39375
|
}
|
|
39083
|
-
|
|
39084
|
-
|
|
39085
|
-
|
|
39086
|
-
|
|
39087
|
-
|
|
39376
|
+
return interactiveCount >= 3;
|
|
39377
|
+
}
|
|
39378
|
+
function getVisualHeadingText(element) {
|
|
39379
|
+
const direct = element.text;
|
|
39380
|
+
if (direct) return direct;
|
|
39381
|
+
for (const child of element.children) {
|
|
39382
|
+
if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
|
|
39383
|
+
}
|
|
39384
|
+
return void 0;
|
|
39385
|
+
}
|
|
39386
|
+
function findNextSectionBoundary(siblings, startIndex) {
|
|
39387
|
+
for (let i = startIndex; i < siblings.length; i++) {
|
|
39388
|
+
const sib = siblings[i];
|
|
39389
|
+
if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
|
|
39390
|
+
if (isVisualHeading(sib, siblings, i)) return i;
|
|
39391
|
+
}
|
|
39392
|
+
return siblings.length;
|
|
39393
|
+
}
|
|
39394
|
+
function isFieldLabelCandidate(element) {
|
|
39395
|
+
if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
|
|
39396
|
+
return false;
|
|
39397
|
+
}
|
|
39398
|
+
if (isClickableByAttribute(element)) return false;
|
|
39399
|
+
if (hasInteractiveDescendants(element)) return false;
|
|
39400
|
+
const text = getFieldLabelText(element);
|
|
39401
|
+
if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
|
|
39402
|
+
return true;
|
|
39403
|
+
}
|
|
39404
|
+
function getFieldLabelText(element) {
|
|
39405
|
+
if (element.text) return element.text.trim();
|
|
39406
|
+
if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
|
|
39407
|
+
for (const child of element.children) {
|
|
39408
|
+
if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
|
|
39409
|
+
if (child.text) return child.text.trim();
|
|
39410
|
+
if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
|
|
39088
39411
|
}
|
|
39089
39412
|
}
|
|
39090
|
-
|
|
39091
|
-
|
|
39092
|
-
|
|
39093
|
-
|
|
39094
|
-
|
|
39095
|
-
|
|
39096
|
-
|
|
39097
|
-
|
|
39098
|
-
};
|
|
39413
|
+
return void 0;
|
|
39414
|
+
}
|
|
39415
|
+
function resolveContainerFieldLabel(container) {
|
|
39416
|
+
const kids = container.children;
|
|
39417
|
+
if (kids.length < 2 || kids.length > 4) return void 0;
|
|
39418
|
+
const firstChild = kids[0];
|
|
39419
|
+
if (isFieldLabelCandidate(firstChild)) {
|
|
39420
|
+
return getFieldLabelText(firstChild);
|
|
39099
39421
|
}
|
|
39100
|
-
return
|
|
39422
|
+
return void 0;
|
|
39101
39423
|
}
|
|
39102
|
-
function
|
|
39103
|
-
|
|
39104
|
-
|
|
39105
|
-
|
|
39106
|
-
|
|
39107
|
-
|
|
39108
|
-
|
|
39109
|
-
|
|
39110
|
-
|
|
39111
|
-
|
|
39112
|
-
|
|
39113
|
-
|
|
39114
|
-
|
|
39115
|
-
|
|
39116
|
-
|
|
39117
|
-
return {
|
|
39118
|
-
...cell,
|
|
39119
|
-
column: positionalColumn,
|
|
39120
|
-
value: cleanValue,
|
|
39121
|
-
hasValue: cleanValue !== ""
|
|
39122
|
-
};
|
|
39424
|
+
function findFieldLabelFromSiblings(siblings, interactiveIndex) {
|
|
39425
|
+
for (let i = interactiveIndex - 1; i >= 0; i--) {
|
|
39426
|
+
const sib = siblings[i];
|
|
39427
|
+
if (INTERACTIVE_ROLES.has(sib.role) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
|
|
39428
|
+
break;
|
|
39429
|
+
}
|
|
39430
|
+
if (isClickableByAttribute(sib)) {
|
|
39431
|
+
if (sib.role === "generic" || sib.role === "paragraph" || sib.role === "label") {
|
|
39432
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
39433
|
+
const prev = siblings[j];
|
|
39434
|
+
if (INTERACTIVE_ROLES.has(prev.role) || isClickableByAttribute(prev) || STRUCTURAL_ROLES.has(prev.role) || prev.role === "heading") {
|
|
39435
|
+
break;
|
|
39436
|
+
}
|
|
39437
|
+
if (isFieldLabelCandidate(prev)) {
|
|
39438
|
+
return getFieldLabelText(prev);
|
|
39123
39439
|
}
|
|
39440
|
+
if (prev.children.length >= 1 && prev.children.length <= 4) {
|
|
39441
|
+
const containerLabel = resolveContainerFieldLabel(prev);
|
|
39442
|
+
if (containerLabel) return containerLabel;
|
|
39443
|
+
}
|
|
39444
|
+
if (prev.children.length > 4) break;
|
|
39124
39445
|
}
|
|
39125
|
-
|
|
39126
|
-
|
|
39127
|
-
}
|
|
39128
|
-
|
|
39446
|
+
}
|
|
39447
|
+
break;
|
|
39448
|
+
}
|
|
39449
|
+
if (isFieldLabelCandidate(sib)) {
|
|
39450
|
+
return getFieldLabelText(sib);
|
|
39451
|
+
}
|
|
39452
|
+
if (sib.children.length >= 1 && sib.children.length <= 4) {
|
|
39453
|
+
const containerLabel = resolveContainerFieldLabel(sib);
|
|
39454
|
+
if (containerLabel) return containerLabel;
|
|
39455
|
+
}
|
|
39456
|
+
if (sib.children.length > 4) break;
|
|
39457
|
+
}
|
|
39458
|
+
return void 0;
|
|
39129
39459
|
}
|
|
39130
|
-
function
|
|
39131
|
-
|
|
39132
|
-
|
|
39133
|
-
|
|
39134
|
-
|
|
39460
|
+
function findFieldLabelInContext(_element, siblings, siblingIndex) {
|
|
39461
|
+
return findFieldLabelFromSiblings(siblings, siblingIndex);
|
|
39462
|
+
}
|
|
39463
|
+
function shouldDropInheritedFieldLabel(element, inheritedFieldLabel) {
|
|
39464
|
+
if (!inheritedFieldLabel) return false;
|
|
39465
|
+
if (INTERACTIVE_LEAF_ROLES.has(element.role)) return false;
|
|
39466
|
+
if (!isClickableByAttribute(element)) return false;
|
|
39467
|
+
const directText = getElementText(element);
|
|
39468
|
+
if (directText) {
|
|
39469
|
+
return directText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
|
|
39470
|
+
}
|
|
39471
|
+
const childTexts = [];
|
|
39472
|
+
for (const child of element.children) {
|
|
39473
|
+
if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
|
|
39474
|
+
const text = getElementText(child);
|
|
39475
|
+
if (text && TEXT_CARRYING_ROLES.has(child.role)) {
|
|
39476
|
+
childTexts.push(text);
|
|
39477
|
+
}
|
|
39135
39478
|
}
|
|
39136
|
-
|
|
39137
|
-
|
|
39138
|
-
|
|
39479
|
+
}
|
|
39480
|
+
const composedText = childTexts.join(" ");
|
|
39481
|
+
if (!composedText) return false;
|
|
39482
|
+
return composedText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
|
|
39483
|
+
}
|
|
39484
|
+
function isExpandedElement(element) {
|
|
39485
|
+
return element.attributes["expanded"] === "true" || element.attributes["aria-expanded"] === "true";
|
|
39486
|
+
}
|
|
39487
|
+
function collectExpandedChildren(element, result) {
|
|
39488
|
+
for (const child of element.children) {
|
|
39489
|
+
if (STRUCTURAL_ROLES.has(child.role)) continue;
|
|
39490
|
+
if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
|
|
39491
|
+
result.push(formatElement(child));
|
|
39492
|
+
continue;
|
|
39493
|
+
}
|
|
39494
|
+
if (child.role === "listitem" && child.ref) {
|
|
39495
|
+
const text = child.text || child.children.map((c2) => getElementText(c2)).filter((value) => Boolean(value)).join(" | ") || getDisplayText(child);
|
|
39496
|
+
if (text && text !== "listitem") {
|
|
39497
|
+
result.push(formatElement(child, { overrideLabel: text }));
|
|
39498
|
+
continue;
|
|
39139
39499
|
}
|
|
39140
39500
|
}
|
|
39501
|
+
if (isClickableByAttribute(child) && child.ref) {
|
|
39502
|
+
result.push(formatElement(child));
|
|
39503
|
+
}
|
|
39504
|
+
collectExpandedChildren(child, result);
|
|
39141
39505
|
}
|
|
39142
|
-
|
|
39143
|
-
|
|
39506
|
+
}
|
|
39507
|
+
function hasActionableChildren(element) {
|
|
39508
|
+
const children2 = [];
|
|
39509
|
+
collectExpandedChildren(element, children2);
|
|
39510
|
+
return children2.length > 0;
|
|
39511
|
+
}
|
|
39512
|
+
function isExpandedWithActionableChildren(element) {
|
|
39513
|
+
if (!isExpandedElement(element)) return false;
|
|
39514
|
+
if (element.role === "combobox") return false;
|
|
39515
|
+
return hasActionableChildren(element);
|
|
39516
|
+
}
|
|
39517
|
+
function composeTriggerLabel(element) {
|
|
39518
|
+
const ariaLabel = element.attributes["aria-label"];
|
|
39519
|
+
if (ariaLabel) return ariaLabel;
|
|
39520
|
+
const fullText = element.text || "";
|
|
39521
|
+
if (fullText) {
|
|
39522
|
+
let collectLeafTexts2 = function(el) {
|
|
39523
|
+
for (const child of el.children) {
|
|
39524
|
+
const text = child.text?.trim();
|
|
39525
|
+
if (text) {
|
|
39526
|
+
leafTexts.push(text);
|
|
39527
|
+
}
|
|
39528
|
+
collectLeafTexts2(child);
|
|
39529
|
+
}
|
|
39530
|
+
};
|
|
39531
|
+
var collectLeafTexts = collectLeafTexts2;
|
|
39532
|
+
const leafTexts = [];
|
|
39533
|
+
collectLeafTexts2(element);
|
|
39534
|
+
if (leafTexts.length > 0) {
|
|
39535
|
+
let stripped = fullText;
|
|
39536
|
+
for (const lt of leafTexts) {
|
|
39537
|
+
stripped = stripped.replace(lt, "");
|
|
39538
|
+
}
|
|
39539
|
+
stripped = stripped.replace(/\s+/g, " ").trim();
|
|
39540
|
+
if (stripped) return stripped;
|
|
39541
|
+
}
|
|
39144
39542
|
}
|
|
39145
|
-
return
|
|
39543
|
+
return element.role;
|
|
39146
39544
|
}
|
|
39147
39545
|
function formatElement(element, options) {
|
|
39148
|
-
const isClickable =
|
|
39149
|
-
const
|
|
39150
|
-
const textSource = normalizeIconLabelText(element.role, rawTextSource);
|
|
39546
|
+
const isClickable = isActionableClickableGeneric(element);
|
|
39547
|
+
const textSource = getDisplayText(element, { overrideLabel: options?.overrideLabel });
|
|
39151
39548
|
const extracted = extractDataValue(textSource);
|
|
39152
39549
|
const normalizedAttributes = Object.entries(element.attributes).filter(
|
|
39153
|
-
([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options"
|
|
39550
|
+
([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options" && key !== "field"
|
|
39154
39551
|
).map(([key, val]) => {
|
|
39155
39552
|
if (key === "aria-haspopup") return "haspopup";
|
|
39156
39553
|
if (key === "aria-expanded") return `expanded=${val}`;
|
|
@@ -39169,7 +39566,7 @@ function formatElement(element, options) {
|
|
|
39169
39566
|
ref: element.ref,
|
|
39170
39567
|
role: element.role,
|
|
39171
39568
|
name: textSource,
|
|
39172
|
-
fieldLabel: options?.fieldLabel,
|
|
39569
|
+
fieldLabel: options?.fieldLabel || element.attributes["field"] || void 0,
|
|
39173
39570
|
label: extracted.label || void 0,
|
|
39174
39571
|
value: extracted.value,
|
|
39175
39572
|
status: extracted.status,
|
|
@@ -39181,105 +39578,141 @@ function formatElement(element, options) {
|
|
|
39181
39578
|
attributes: normalizedAttributes
|
|
39182
39579
|
};
|
|
39183
39580
|
}
|
|
39184
|
-
function collectComboboxOptions(element) {
|
|
39185
|
-
const options = [];
|
|
39186
|
-
function walk(el) {
|
|
39187
|
-
if (el.role === "option" && el.text?.trim()) {
|
|
39188
|
-
options.push(el.text.trim());
|
|
39189
|
-
}
|
|
39190
|
-
for (const child of el.children) {
|
|
39191
|
-
walk(child);
|
|
39192
|
-
}
|
|
39193
|
-
}
|
|
39194
|
-
for (const child of element.children) {
|
|
39195
|
-
walk(child);
|
|
39196
|
-
}
|
|
39197
|
-
return options.length > 0 ? options : void 0;
|
|
39198
|
-
}
|
|
39199
|
-
var MIN_ELEMENTS_TO_COLLAPSE = 20;
|
|
39200
39581
|
function shouldCollapse(section) {
|
|
39201
39582
|
if (section.containsActive) return false;
|
|
39202
39583
|
if (section.hasSearchMatch) return false;
|
|
39203
39584
|
if (section.isBackground) return true;
|
|
39204
39585
|
if (section.role === "dialog" || section.role === "alertdialog") return false;
|
|
39205
|
-
if (section.role === "navigation" || section.role === "menubar" || section.role === "menu")
|
|
39206
|
-
return false;
|
|
39207
|
-
}
|
|
39586
|
+
if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") return false;
|
|
39208
39587
|
if (section.role === "banner") return false;
|
|
39209
39588
|
if (section.role === "form") return false;
|
|
39210
39589
|
if (section.elementCount >= MIN_ELEMENTS_TO_COLLAPSE) return true;
|
|
39211
39590
|
return false;
|
|
39212
39591
|
}
|
|
39213
|
-
function
|
|
39214
|
-
const
|
|
39215
|
-
for (const
|
|
39216
|
-
|
|
39217
|
-
|
|
39218
|
-
|
|
39592
|
+
function deduplicateFieldLabels(elements) {
|
|
39593
|
+
const byFieldLabel = /* @__PURE__ */ new Map();
|
|
39594
|
+
for (const el of elements) {
|
|
39595
|
+
if (!el.fieldLabel) continue;
|
|
39596
|
+
const existing = byFieldLabel.get(el.fieldLabel);
|
|
39597
|
+
if (existing) {
|
|
39598
|
+
existing.push(el);
|
|
39599
|
+
} else {
|
|
39600
|
+
byFieldLabel.set(el.fieldLabel, [el]);
|
|
39219
39601
|
}
|
|
39220
39602
|
}
|
|
39221
|
-
|
|
39222
|
-
|
|
39223
|
-
|
|
39224
|
-
|
|
39225
|
-
|
|
39226
|
-
|
|
39227
|
-
|
|
39228
|
-
|
|
39229
|
-
section.isBackground = true;
|
|
39230
|
-
section.collapsed = shouldCollapse(section);
|
|
39603
|
+
for (const [, group] of byFieldLabel) {
|
|
39604
|
+
if (group.length < 2) continue;
|
|
39605
|
+
for (let i = 0; i < group.length; i++) {
|
|
39606
|
+
const el = group[i];
|
|
39607
|
+
if (el.name && el.name !== el.fieldLabel) {
|
|
39608
|
+
el.fieldLabel = `${el.fieldLabel}: ${el.name}`;
|
|
39609
|
+
} else {
|
|
39610
|
+
el.fieldLabel = `${el.fieldLabel} (${i + 1})`;
|
|
39231
39611
|
}
|
|
39232
39612
|
}
|
|
39233
39613
|
}
|
|
39234
|
-
for (const section of sections) {
|
|
39235
|
-
markBackgroundSections(section.subsections);
|
|
39236
|
-
}
|
|
39237
39614
|
}
|
|
39238
|
-
function
|
|
39239
|
-
|
|
39240
|
-
|
|
39241
|
-
|
|
39242
|
-
const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
|
|
39243
|
-
if (hasDirectMatch || hasSubsectionMatch) {
|
|
39244
|
-
section.hasSearchMatch = true;
|
|
39245
|
-
section.collapsed = false;
|
|
39246
|
-
}
|
|
39615
|
+
function buildGridSection(element) {
|
|
39616
|
+
const gridInfo = detectGrid(element);
|
|
39617
|
+
if (!gridInfo) {
|
|
39618
|
+
throw new Error("buildGridSection requires a grid element");
|
|
39247
39619
|
}
|
|
39620
|
+
return {
|
|
39621
|
+
ref: element.ref || "",
|
|
39622
|
+
role: element.role,
|
|
39623
|
+
heading: "Grid",
|
|
39624
|
+
level: 0,
|
|
39625
|
+
elements: [],
|
|
39626
|
+
subsections: [],
|
|
39627
|
+
containsActive: gridInfo.containsActive,
|
|
39628
|
+
collapsed: false,
|
|
39629
|
+
isBackground: false,
|
|
39630
|
+
isFocused: false,
|
|
39631
|
+
elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
|
|
39632
|
+
gridInfo
|
|
39633
|
+
};
|
|
39248
39634
|
}
|
|
39249
|
-
|
|
39250
|
-
|
|
39251
|
-
|
|
39252
|
-
|
|
39253
|
-
|
|
39254
|
-
|
|
39255
|
-
|
|
39635
|
+
function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
|
|
39636
|
+
if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
|
|
39637
|
+
const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
|
|
39638
|
+
if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
|
|
39639
|
+
elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
|
|
39640
|
+
collectExpandedChildren(element, elements);
|
|
39641
|
+
return;
|
|
39256
39642
|
}
|
|
39257
|
-
|
|
39258
|
-
|
|
39259
|
-
const focusedDialog = dialogs.find((d) => d.containsActive);
|
|
39260
|
-
if (focusedDialog) {
|
|
39261
|
-
focusedDialog.isFocused = true;
|
|
39262
|
-
dialogStack.unshift(focusedDialog.heading);
|
|
39643
|
+
if (!isMenuTriggerNoise(element)) {
|
|
39644
|
+
elements.push(formatElement(element, { fieldLabel: effectiveFieldLabel }));
|
|
39263
39645
|
}
|
|
39264
|
-
|
|
39265
|
-
|
|
39266
|
-
|
|
39646
|
+
if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
|
|
39647
|
+
for (const child of element.children) {
|
|
39648
|
+
collectInteractiveElements(child, elements, subsections, depth + 1, effectiveFieldLabel);
|
|
39267
39649
|
}
|
|
39268
39650
|
}
|
|
39269
|
-
|
|
39270
|
-
|
|
39271
|
-
|
|
39272
|
-
|
|
39273
|
-
|
|
39274
|
-
|
|
39651
|
+
return;
|
|
39652
|
+
}
|
|
39653
|
+
if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table" && !isWidgetTable(element)) {
|
|
39654
|
+
const section = buildSection(element, depth);
|
|
39655
|
+
if (section) {
|
|
39656
|
+
subsections.push(section);
|
|
39657
|
+
}
|
|
39658
|
+
return;
|
|
39659
|
+
}
|
|
39660
|
+
const kids = element.children;
|
|
39661
|
+
for (let ki = 0; ki < kids.length; ki++) {
|
|
39662
|
+
const child = kids[ki];
|
|
39663
|
+
if (isVisualHeading(child, kids, ki)) {
|
|
39664
|
+
const headingText = getVisualHeadingText(child);
|
|
39665
|
+
const sectionElements = [];
|
|
39666
|
+
const sectionSubsections = [];
|
|
39667
|
+
const nextBoundary = findNextSectionBoundary(kids, ki + 1);
|
|
39668
|
+
for (let si = ki + 1; si < nextBoundary; si++) {
|
|
39669
|
+
collectInteractiveElements(kids[si], sectionElements, sectionSubsections, depth + 2);
|
|
39670
|
+
}
|
|
39671
|
+
if (sectionElements.length > 0 || sectionSubsections.length > 0) {
|
|
39672
|
+
const totalElements = sectionElements.length + sectionSubsections.reduce((sum, s) => sum + s.elementCount, 0);
|
|
39673
|
+
subsections.push({
|
|
39674
|
+
heading: headingText ?? "",
|
|
39675
|
+
ref: child.ref,
|
|
39676
|
+
role: "heading",
|
|
39677
|
+
level: 4,
|
|
39678
|
+
elements: sectionElements,
|
|
39679
|
+
subsections: sectionSubsections,
|
|
39680
|
+
collapsed: false,
|
|
39681
|
+
containsActive: false,
|
|
39682
|
+
isBackground: false,
|
|
39683
|
+
isFocused: false,
|
|
39684
|
+
elementCount: totalElements
|
|
39685
|
+
});
|
|
39686
|
+
ki = nextBoundary - 1;
|
|
39687
|
+
} else {
|
|
39688
|
+
collectInteractiveElements(child, elements, subsections, depth + 1);
|
|
39689
|
+
}
|
|
39690
|
+
} else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
|
|
39691
|
+
const rawFieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
|
|
39692
|
+
const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
|
|
39693
|
+
if (!isMenuTriggerNoise(child)) {
|
|
39694
|
+
if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
|
|
39695
|
+
elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
|
|
39696
|
+
collectExpandedChildren(child, elements);
|
|
39697
|
+
} else {
|
|
39698
|
+
elements.push(formatElement(child, { fieldLabel }));
|
|
39699
|
+
}
|
|
39700
|
+
}
|
|
39701
|
+
if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
|
|
39702
|
+
for (const grandchild of child.children) {
|
|
39703
|
+
collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
|
|
39704
|
+
}
|
|
39705
|
+
}
|
|
39706
|
+
} else {
|
|
39707
|
+
const containerLabel = resolveContainerFieldLabel(child) ?? (child.role === "table" ? findFieldLabelFromSiblings(kids, ki) : void 0) ?? inheritedFieldLabel;
|
|
39708
|
+
collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
|
|
39709
|
+
}
|
|
39275
39710
|
}
|
|
39276
|
-
const sorted = processLevel(sections);
|
|
39277
|
-
return { sorted, dialogStack };
|
|
39278
39711
|
}
|
|
39279
39712
|
function buildSection(element, depth) {
|
|
39280
39713
|
const gridInfo = detectGrid(element);
|
|
39281
39714
|
if (gridInfo) {
|
|
39282
|
-
return buildGridSection(element
|
|
39715
|
+
return buildGridSection(element);
|
|
39283
39716
|
}
|
|
39284
39717
|
const isHeading = element.role === "heading";
|
|
39285
39718
|
const isStructural = STRUCTURAL_ROLES.has(element.role);
|
|
@@ -39317,7 +39750,6 @@ function buildSection(element, depth) {
|
|
|
39317
39750
|
ref: child.ref,
|
|
39318
39751
|
role: "heading",
|
|
39319
39752
|
level: 4,
|
|
39320
|
-
// Visual headings rendered at h6 level (4 + 2 = 6)
|
|
39321
39753
|
elements: sectionElements,
|
|
39322
39754
|
subsections: sectionSubsections,
|
|
39323
39755
|
collapsed: false,
|
|
@@ -39361,113 +39793,214 @@ function buildSection(element, depth) {
|
|
|
39361
39793
|
const section = {
|
|
39362
39794
|
ref: element.ref || "",
|
|
39363
39795
|
role: element.role,
|
|
39364
|
-
heading: element.text || (isClickableByAttribute(element) ?
|
|
39796
|
+
heading: element.text || (isClickableByAttribute(element) ? getDisplayText(element) : element.role),
|
|
39365
39797
|
level,
|
|
39366
39798
|
elements: interactiveElements,
|
|
39367
39799
|
subsections,
|
|
39368
39800
|
containsActive,
|
|
39369
39801
|
collapsed: false,
|
|
39370
39802
|
isBackground: false,
|
|
39371
|
-
// Will be set by markBackgroundSections if needed
|
|
39372
39803
|
isFocused: false,
|
|
39373
|
-
// Will be set by sortSectionsByFocus if needed
|
|
39374
39804
|
elementCount
|
|
39375
39805
|
};
|
|
39806
|
+
deduplicateFieldLabels(interactiveElements);
|
|
39376
39807
|
section.collapsed = shouldCollapse(section);
|
|
39377
39808
|
return section;
|
|
39378
39809
|
}
|
|
39379
|
-
function
|
|
39380
|
-
|
|
39381
|
-
|
|
39382
|
-
|
|
39383
|
-
|
|
39384
|
-
|
|
39385
|
-
|
|
39386
|
-
|
|
39387
|
-
|
|
39388
|
-
|
|
39389
|
-
|
|
39390
|
-
|
|
39391
|
-
|
|
39392
|
-
isFocused: false,
|
|
39393
|
-
elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
|
|
39394
|
-
gridInfo
|
|
39395
|
-
};
|
|
39810
|
+
function markBackgroundSections(sections) {
|
|
39811
|
+
const hasActiveSibling = sections.some((s) => s.containsActive);
|
|
39812
|
+
if (hasActiveSibling) {
|
|
39813
|
+
for (const section of sections) {
|
|
39814
|
+
if (!section.containsActive) {
|
|
39815
|
+
section.isBackground = true;
|
|
39816
|
+
section.collapsed = shouldCollapse(section);
|
|
39817
|
+
}
|
|
39818
|
+
}
|
|
39819
|
+
}
|
|
39820
|
+
for (const section of sections) {
|
|
39821
|
+
markBackgroundSections(section.subsections);
|
|
39822
|
+
}
|
|
39396
39823
|
}
|
|
39397
|
-
function
|
|
39398
|
-
|
|
39399
|
-
|
|
39400
|
-
|
|
39401
|
-
|
|
39402
|
-
|
|
39824
|
+
function buildSectionTree(elements) {
|
|
39825
|
+
const sections = [];
|
|
39826
|
+
for (const element of elements) {
|
|
39827
|
+
const section = buildSection(element, 0);
|
|
39828
|
+
if (section) {
|
|
39829
|
+
sections.push(section);
|
|
39403
39830
|
}
|
|
39404
|
-
|
|
39405
|
-
|
|
39831
|
+
}
|
|
39832
|
+
markBackgroundSections(sections);
|
|
39833
|
+
return sections;
|
|
39834
|
+
}
|
|
39835
|
+
function markMatchingSections(sections, matchedRefs) {
|
|
39836
|
+
for (const section of sections) {
|
|
39837
|
+
const hasDirectMatch = section.elements.some((el) => matchedRefs.has(el.ref)) || (section.gridInfo ? Array.from(matchedRefs).some((ref) => gridContainsRef(section.gridInfo, ref)) : false);
|
|
39838
|
+
markMatchingSections(section.subsections, matchedRefs);
|
|
39839
|
+
const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
|
|
39840
|
+
if (hasDirectMatch || hasSubsectionMatch) {
|
|
39841
|
+
section.hasSearchMatch = true;
|
|
39842
|
+
section.collapsed = false;
|
|
39406
39843
|
}
|
|
39407
|
-
|
|
39408
|
-
|
|
39409
|
-
|
|
39844
|
+
}
|
|
39845
|
+
}
|
|
39846
|
+
function sortSectionsByFocus(sections) {
|
|
39847
|
+
const dialogStack = [];
|
|
39848
|
+
function processLevel(levelSections) {
|
|
39849
|
+
for (const section of levelSections) {
|
|
39850
|
+
section.subsections = processLevel(section.subsections);
|
|
39851
|
+
}
|
|
39852
|
+
const dialogs = levelSections.filter((s) => DIALOG_ROLES.has(s.role));
|
|
39853
|
+
const nonDialogs = levelSections.filter((s) => !DIALOG_ROLES.has(s.role));
|
|
39854
|
+
const focusedDialog = dialogs.find((d) => d.containsActive);
|
|
39855
|
+
if (focusedDialog) {
|
|
39856
|
+
focusedDialog.isFocused = true;
|
|
39857
|
+
dialogStack.unshift(focusedDialog.heading);
|
|
39858
|
+
}
|
|
39859
|
+
for (const dialog of dialogs) {
|
|
39860
|
+
if (!dialog.containsActive) {
|
|
39861
|
+
dialogStack.push(dialog.heading);
|
|
39410
39862
|
}
|
|
39411
39863
|
}
|
|
39412
|
-
|
|
39864
|
+
const sortedDialogs = dialogs.sort((a, b) => {
|
|
39865
|
+
if (a.containsActive && !b.containsActive) return -1;
|
|
39866
|
+
if (!a.containsActive && b.containsActive) return 1;
|
|
39867
|
+
return 0;
|
|
39868
|
+
});
|
|
39869
|
+
return [...sortedDialogs, ...nonDialogs];
|
|
39413
39870
|
}
|
|
39414
|
-
|
|
39415
|
-
|
|
39416
|
-
|
|
39417
|
-
|
|
39871
|
+
const sorted = processLevel(sections);
|
|
39872
|
+
return { sorted, dialogStack };
|
|
39873
|
+
}
|
|
39874
|
+
function expandSectionByRef(sections, targetRef) {
|
|
39875
|
+
for (const section of sections) {
|
|
39876
|
+
if (section.ref === targetRef) {
|
|
39877
|
+
section.collapsed = false;
|
|
39878
|
+
return true;
|
|
39879
|
+
}
|
|
39880
|
+
if (expandSectionByRef(section.subsections, targetRef)) {
|
|
39881
|
+
return true;
|
|
39418
39882
|
}
|
|
39419
|
-
return;
|
|
39420
39883
|
}
|
|
39421
|
-
|
|
39422
|
-
|
|
39423
|
-
|
|
39424
|
-
|
|
39425
|
-
|
|
39426
|
-
|
|
39427
|
-
|
|
39428
|
-
|
|
39429
|
-
|
|
39430
|
-
|
|
39431
|
-
|
|
39432
|
-
|
|
39433
|
-
|
|
39434
|
-
|
|
39435
|
-
|
|
39436
|
-
|
|
39437
|
-
|
|
39438
|
-
|
|
39439
|
-
|
|
39440
|
-
|
|
39441
|
-
|
|
39442
|
-
|
|
39443
|
-
|
|
39444
|
-
|
|
39445
|
-
|
|
39446
|
-
|
|
39447
|
-
|
|
39448
|
-
|
|
39449
|
-
|
|
39450
|
-
|
|
39451
|
-
|
|
39452
|
-
|
|
39453
|
-
|
|
39454
|
-
|
|
39455
|
-
|
|
39456
|
-
|
|
39457
|
-
|
|
39458
|
-
elements.push(formatElement(child, { fieldLabel }));
|
|
39459
|
-
}
|
|
39460
|
-
}
|
|
39461
|
-
if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
|
|
39462
|
-
for (const grandchild of child.children) {
|
|
39463
|
-
collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
|
|
39464
|
-
}
|
|
39884
|
+
return false;
|
|
39885
|
+
}
|
|
39886
|
+
function findSectionByRef(sections, targetRef) {
|
|
39887
|
+
for (const section of sections) {
|
|
39888
|
+
if (section.ref === targetRef) {
|
|
39889
|
+
return section;
|
|
39890
|
+
}
|
|
39891
|
+
const found = findSectionByRef(section.subsections, targetRef);
|
|
39892
|
+
if (found) {
|
|
39893
|
+
return found;
|
|
39894
|
+
}
|
|
39895
|
+
}
|
|
39896
|
+
return null;
|
|
39897
|
+
}
|
|
39898
|
+
|
|
39899
|
+
// ../browser-core/src/snapshot-formatter-render.ts
|
|
39900
|
+
var COLOR_NAMES = [
|
|
39901
|
+
"red",
|
|
39902
|
+
"green",
|
|
39903
|
+
"blue",
|
|
39904
|
+
"yellow",
|
|
39905
|
+
"magenta",
|
|
39906
|
+
"cyan",
|
|
39907
|
+
"orange",
|
|
39908
|
+
"purple",
|
|
39909
|
+
"teal",
|
|
39910
|
+
"pink"
|
|
39911
|
+
];
|
|
39912
|
+
function findActiveElement(elements) {
|
|
39913
|
+
let deepestActive = null;
|
|
39914
|
+
function findDeepest(element) {
|
|
39915
|
+
for (const child of element.children) {
|
|
39916
|
+
findDeepest(child);
|
|
39917
|
+
}
|
|
39918
|
+
if (element.attributes["active"] !== void 0 || element.rawLine.includes("[active]")) {
|
|
39919
|
+
if (!deepestActive) {
|
|
39920
|
+
deepestActive = element;
|
|
39465
39921
|
}
|
|
39922
|
+
}
|
|
39923
|
+
}
|
|
39924
|
+
for (const element of elements) {
|
|
39925
|
+
findDeepest(element);
|
|
39926
|
+
}
|
|
39927
|
+
return deepestActive;
|
|
39928
|
+
}
|
|
39929
|
+
function formatElementLine(el, indent) {
|
|
39930
|
+
const indentStr = " ".repeat(indent);
|
|
39931
|
+
const parts = [];
|
|
39932
|
+
if (el.isActive) {
|
|
39933
|
+
parts.push("ACTIVE \u2192");
|
|
39934
|
+
}
|
|
39935
|
+
parts.push(`[${el.ref}]`);
|
|
39936
|
+
parts.push(el.role);
|
|
39937
|
+
if (el.isClickable) {
|
|
39938
|
+
parts.push("[clickable]");
|
|
39939
|
+
}
|
|
39940
|
+
if (el.isDisabled) {
|
|
39941
|
+
parts.push("[disabled]");
|
|
39942
|
+
}
|
|
39943
|
+
for (const attr of el.attributes) {
|
|
39944
|
+
if (attr !== "disabled") {
|
|
39945
|
+
parts.push(`[${attr}]`);
|
|
39946
|
+
}
|
|
39947
|
+
}
|
|
39948
|
+
if (el.fieldLabel) {
|
|
39949
|
+
parts.push(`| field: "${el.fieldLabel}"`);
|
|
39950
|
+
}
|
|
39951
|
+
if (el.label && el.value) {
|
|
39952
|
+
parts.push(`| label: "${el.label}" | value: "${el.value}"`);
|
|
39953
|
+
} else if (el.value) {
|
|
39954
|
+
parts.push(`| value: "${el.value}"`);
|
|
39955
|
+
} else if (el.label) {
|
|
39956
|
+
parts.push(`| label: "${el.label}"`);
|
|
39957
|
+
} else if (el.name) {
|
|
39958
|
+
parts.push(`"${el.name}"`);
|
|
39959
|
+
}
|
|
39960
|
+
if (el.status) {
|
|
39961
|
+
parts.push(`| status: "${el.status}"`);
|
|
39962
|
+
}
|
|
39963
|
+
if ((el.role === "textbox" || el.role === "combobox" || el.role === "searchbox") && el.inputValue) {
|
|
39964
|
+
parts.push(`| input: "${el.inputValue}"`);
|
|
39965
|
+
}
|
|
39966
|
+
if (el.options?.length) {
|
|
39967
|
+
const MAX_DISPLAY = 8;
|
|
39968
|
+
const displayed = el.options.slice(0, MAX_DISPLAY);
|
|
39969
|
+
const optionsList = displayed.map((o) => `"${o}"`).join(", ");
|
|
39970
|
+
const suffix = el.options.length > MAX_DISPLAY ? `, ... +${el.options.length - MAX_DISPLAY} more` : "";
|
|
39971
|
+
if (el.role === "combobox") {
|
|
39972
|
+
parts.push(`| options: [${optionsList}${suffix}] \u2192 use browser_select_option`);
|
|
39466
39973
|
} else {
|
|
39467
|
-
|
|
39468
|
-
collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
|
|
39974
|
+
parts.push(`| options: [${optionsList}${suffix}]`);
|
|
39469
39975
|
}
|
|
39470
39976
|
}
|
|
39977
|
+
return indentStr + parts.join(" ");
|
|
39978
|
+
}
|
|
39979
|
+
function collectAllElementsFlat(section) {
|
|
39980
|
+
const elements = [...section.elements];
|
|
39981
|
+
for (const subsection of section.subsections) {
|
|
39982
|
+
elements.push(...collectAllElementsFlat(subsection));
|
|
39983
|
+
}
|
|
39984
|
+
return elements;
|
|
39985
|
+
}
|
|
39986
|
+
function formatSubsectionSummary(subsection) {
|
|
39987
|
+
const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
|
|
39988
|
+
if (subsection.gridInfo) {
|
|
39989
|
+
const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
|
|
39990
|
+
return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
|
|
39991
|
+
}
|
|
39992
|
+
const allElements = collectAllElementsFlat(subsection);
|
|
39993
|
+
const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
|
|
39994
|
+
const heading = subsection.heading || subsection.role;
|
|
39995
|
+
if (elementNames.length === 0) {
|
|
39996
|
+
return `${heading}${refPart}`;
|
|
39997
|
+
}
|
|
39998
|
+
const MAX_PREVIEW_NAMES = 3;
|
|
39999
|
+
const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
|
|
40000
|
+
const remainingCount = elementNames.length - previewNames.length;
|
|
40001
|
+
const namesList = previewNames.join(", ");
|
|
40002
|
+
const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
|
|
40003
|
+
return `${heading}${refPart}: ${namesList}${moreNote}`;
|
|
39471
40004
|
}
|
|
39472
40005
|
function formatSectionOutput(section, indent, showCollapsed) {
|
|
39473
40006
|
if (section.gridInfo) {
|
|
@@ -39480,8 +40013,7 @@ function formatSectionOutput(section, indent, showCollapsed) {
|
|
|
39480
40013
|
const focusedPart = section.isFocused ? " [FOCUSED]" : "";
|
|
39481
40014
|
const backgroundPart = section.isBackground ? " [BACKGROUND]" : "";
|
|
39482
40015
|
const activePart = section.containsActive && !section.isFocused ? " \u2190 ACTIVE" : "";
|
|
39483
|
-
|
|
39484
|
-
lines.push(headerLine);
|
|
40016
|
+
lines.push(`${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`);
|
|
39485
40017
|
if (section.collapsed && !showCollapsed) {
|
|
39486
40018
|
if (section.isBackground) {
|
|
39487
40019
|
if (section.ref) {
|
|
@@ -39520,102 +40052,39 @@ function formatSectionOutput(section, indent, showCollapsed) {
|
|
|
39520
40052
|
}
|
|
39521
40053
|
return lines.join("\n");
|
|
39522
40054
|
}
|
|
39523
|
-
function
|
|
39524
|
-
|
|
39525
|
-
for (const
|
|
39526
|
-
elements.
|
|
39527
|
-
|
|
39528
|
-
|
|
39529
|
-
}
|
|
39530
|
-
function formatSubsectionSummary(subsection) {
|
|
39531
|
-
const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
|
|
39532
|
-
if (subsection.gridInfo) {
|
|
39533
|
-
const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
|
|
39534
|
-
return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
|
|
39535
|
-
}
|
|
39536
|
-
const allElements = collectAllElementsFlat(subsection);
|
|
39537
|
-
const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
|
|
39538
|
-
const heading = subsection.heading || subsection.role;
|
|
39539
|
-
if (elementNames.length === 0) {
|
|
39540
|
-
return `${heading}${refPart}`;
|
|
39541
|
-
}
|
|
39542
|
-
const MAX_PREVIEW_NAMES = 3;
|
|
39543
|
-
const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
|
|
39544
|
-
const remainingCount = elementNames.length - previewNames.length;
|
|
39545
|
-
const namesList = previewNames.join(", ");
|
|
39546
|
-
const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
|
|
39547
|
-
return `${heading}${refPart}: ${namesList}${moreNote}`;
|
|
39548
|
-
}
|
|
39549
|
-
function formatElementLine(el, indent) {
|
|
39550
|
-
const indentStr = " ".repeat(indent);
|
|
39551
|
-
const parts = [];
|
|
39552
|
-
if (el.isActive) {
|
|
39553
|
-
parts.push("ACTIVE \u2192");
|
|
39554
|
-
}
|
|
39555
|
-
parts.push(`[${el.ref}]`);
|
|
39556
|
-
parts.push(el.role);
|
|
39557
|
-
if (el.isClickable) {
|
|
39558
|
-
parts.push("[clickable]");
|
|
39559
|
-
}
|
|
39560
|
-
if (el.isDisabled) {
|
|
39561
|
-
parts.push("[disabled]");
|
|
39562
|
-
}
|
|
39563
|
-
for (const attr of el.attributes) {
|
|
39564
|
-
if (attr !== "disabled") {
|
|
39565
|
-
parts.push(`[${attr}]`);
|
|
40055
|
+
function countRenderedElements(sections) {
|
|
40056
|
+
let count = 0;
|
|
40057
|
+
for (const section of sections) {
|
|
40058
|
+
count += section.elements.length;
|
|
40059
|
+
if (section.gridInfo) {
|
|
40060
|
+
count += countRenderedGridItems(section.gridInfo);
|
|
39566
40061
|
}
|
|
40062
|
+
count += countRenderedElements(section.subsections);
|
|
39567
40063
|
}
|
|
39568
|
-
|
|
39569
|
-
|
|
39570
|
-
|
|
39571
|
-
|
|
39572
|
-
|
|
39573
|
-
|
|
39574
|
-
parts.push(`| value: "${el.value}"`);
|
|
39575
|
-
} else if (el.label) {
|
|
39576
|
-
parts.push(`| label: "${el.label}"`);
|
|
39577
|
-
} else if (el.name) {
|
|
39578
|
-
parts.push(`"${el.name}"`);
|
|
39579
|
-
}
|
|
39580
|
-
if (el.status) {
|
|
39581
|
-
parts.push(`| status: "${el.status}"`);
|
|
39582
|
-
}
|
|
39583
|
-
if ((el.role === "textbox" || el.role === "combobox" || el.role === "searchbox") && el.inputValue) {
|
|
39584
|
-
parts.push(`| input: "${el.inputValue}"`);
|
|
39585
|
-
}
|
|
39586
|
-
if (el.options?.length) {
|
|
39587
|
-
const MAX_DISPLAY = 8;
|
|
39588
|
-
const displayed = el.options.slice(0, MAX_DISPLAY);
|
|
39589
|
-
const optionsList = displayed.map((o) => `"${o}"`).join(", ");
|
|
39590
|
-
const suffix = el.options.length > MAX_DISPLAY ? `, ... +${el.options.length - MAX_DISPLAY} more` : "";
|
|
39591
|
-
if (el.role === "combobox") {
|
|
39592
|
-
parts.push(`| options: [${optionsList}${suffix}] \u2192 use browser_select_option`);
|
|
39593
|
-
} else {
|
|
39594
|
-
parts.push(`| options: [${optionsList}${suffix}]`);
|
|
39595
|
-
}
|
|
40064
|
+
return count;
|
|
40065
|
+
}
|
|
40066
|
+
function countTotalSections(sections) {
|
|
40067
|
+
let count = sections.length;
|
|
40068
|
+
for (const section of sections) {
|
|
40069
|
+
count += countTotalSections(section.subsections);
|
|
39596
40070
|
}
|
|
39597
|
-
return
|
|
40071
|
+
return count;
|
|
39598
40072
|
}
|
|
39599
|
-
|
|
39600
|
-
"
|
|
39601
|
-
"green",
|
|
39602
|
-
"blue",
|
|
39603
|
-
"yellow",
|
|
39604
|
-
"magenta",
|
|
39605
|
-
"cyan",
|
|
39606
|
-
"orange",
|
|
39607
|
-
"purple",
|
|
39608
|
-
"teal",
|
|
39609
|
-
"pink"
|
|
39610
|
-
];
|
|
39611
|
-
function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult) {
|
|
39612
|
-
const activeLabel = activeElement ? normalizeIconLabelText(
|
|
39613
|
-
activeElement.role,
|
|
39614
|
-
INTERACTIVE_LEAF_ROLES.has(activeElement.role) && isExpandedWithActionableChildren(activeElement) ? composeTriggerLabel(activeElement) : activeElement.text || ""
|
|
39615
|
-
) : "";
|
|
40073
|
+
function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult, currentTime, capturedToasts) {
|
|
40074
|
+
const activeLabel = activeElement ? (FORM_INPUT_ROLES.has(activeElement.role) || activeElement.role === "button" || activeElement.role === "link") && (activeElement.attributes["expanded"] === "true" || activeElement.attributes["aria-expanded"] === "true") ? getDisplayText(activeElement, { overrideLabel: composeTriggerLabel(activeElement) }) : getDisplayText(activeElement) : "";
|
|
39616
40075
|
const activeDesc = activeElement ? `${activeElement.ref} ${activeElement.role} "${activeLabel}"` : "None";
|
|
39617
40076
|
const dialogStackLine = dialogStack.length > 0 ? `
|
|
39618
40077
|
DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
|
|
40078
|
+
let toastSection = "";
|
|
40079
|
+
if (capturedToasts && capturedToasts.length > 0) {
|
|
40080
|
+
const toastLines = capturedToasts.map((t) => {
|
|
40081
|
+
const ttl = t.snapshotsRemaining !== void 0 ? ` (expires in ${t.snapshotsRemaining} snapshots)` : "";
|
|
40082
|
+
return ` - [${t.role}] "${t.text}"${ttl}`;
|
|
40083
|
+
});
|
|
40084
|
+
toastSection = `
|
|
40085
|
+
TOAST NOTIFICATIONS:
|
|
40086
|
+
${toastLines.join("\n")}`;
|
|
40087
|
+
}
|
|
39619
40088
|
let searchSection = "";
|
|
39620
40089
|
if (searchMatches && searchMatches.length > 0) {
|
|
39621
40090
|
const hasContextInfo = searchMatches.some((m) => m.context);
|
|
@@ -39641,9 +40110,8 @@ DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
|
|
|
39641
40110
|
SEARCH RESULTS (${matchCountText}${sortNote}):
|
|
39642
40111
|
${matchLines.join("\n")}`;
|
|
39643
40112
|
}
|
|
39644
|
-
const INPUT_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox", "spinbutton"]);
|
|
39645
40113
|
let typeActionHint = "";
|
|
39646
|
-
if (activeElement &&
|
|
40114
|
+
if (activeElement && FORM_INPUT_ROLES.has(activeElement.role)) {
|
|
39647
40115
|
typeActionHint = `
|
|
39648
40116
|
- Type in active field: browser_type ref=${activeElement.ref} text="..."`;
|
|
39649
40117
|
}
|
|
@@ -39665,10 +40133,12 @@ PROBE RESULT:
|
|
|
39665
40133
|
FULL TREE FILE: ${snapshotFilePath}
|
|
39666
40134
|
Use bash or python_execute to grep/parse this file for detailed element analysis.`;
|
|
39667
40135
|
}
|
|
40136
|
+
const browserTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
39668
40137
|
return `---
|
|
39669
40138
|
SEMANTIC SNAPSHOT - ${elementCount} elements in ${sectionCount} sections
|
|
40139
|
+
BROWSER TIME: ${browserTime} (UTC)
|
|
39670
40140
|
FORMAT: Sections grouped by headings. [ref] tags identify clickable elements.
|
|
39671
|
-
ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${searchSection}${probeSection}${fullTreeFileSection}
|
|
40141
|
+
ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${toastSection}${searchSection}${probeSection}${fullTreeFileSection}
|
|
39672
40142
|
ACTIONS:
|
|
39673
40143
|
- Click/type using [ref] values (e.g., browser_click ref=e123)${typeActionHint}
|
|
39674
40144
|
- Click using coordinates from search results (e.g., browser_click x=450 y=320)
|
|
@@ -39676,21 +40146,6 @@ ACTIONS:
|
|
|
39676
40146
|
- Use bash or python_execute on the FULL TREE FILE for detailed element analysis
|
|
39677
40147
|
---`;
|
|
39678
40148
|
}
|
|
39679
|
-
function countTotalElements(sections) {
|
|
39680
|
-
let count = 0;
|
|
39681
|
-
for (const section of sections) {
|
|
39682
|
-
count += section.elements.length;
|
|
39683
|
-
count += countTotalElements(section.subsections);
|
|
39684
|
-
}
|
|
39685
|
-
return count;
|
|
39686
|
-
}
|
|
39687
|
-
function countTotalSections(sections) {
|
|
39688
|
-
let count = sections.length;
|
|
39689
|
-
for (const section of sections) {
|
|
39690
|
-
count += countTotalSections(section.subsections);
|
|
39691
|
-
}
|
|
39692
|
-
return count;
|
|
39693
|
-
}
|
|
39694
40149
|
function formatSemanticSnapshot(yaml, options) {
|
|
39695
40150
|
if (!yaml || yaml.trim() === "") {
|
|
39696
40151
|
return "---\nSEMANTIC SNAPSHOT - 0 elements\nPage appears empty or not loaded.\n---";
|
|
@@ -39722,10 +40177,9 @@ function formatSemanticSnapshot(yaml, options) {
|
|
|
39722
40177
|
}
|
|
39723
40178
|
}
|
|
39724
40179
|
if (options?.probeResult?.ref) {
|
|
39725
|
-
|
|
39726
|
-
markMatchingSections(sortedSections, probeRefs);
|
|
40180
|
+
markMatchingSections(sortedSections, /* @__PURE__ */ new Set([options.probeResult.ref]));
|
|
39727
40181
|
}
|
|
39728
|
-
const elementCount =
|
|
40182
|
+
const elementCount = countRenderedElements(sortedSections);
|
|
39729
40183
|
const sectionCount = countTotalSections(sortedSections);
|
|
39730
40184
|
const lines = [];
|
|
39731
40185
|
lines.push(
|
|
@@ -39735,10 +40189,11 @@ function formatSemanticSnapshot(yaml, options) {
|
|
|
39735
40189
|
activeElement,
|
|
39736
40190
|
dialogStack,
|
|
39737
40191
|
sortedMatches,
|
|
39738
|
-
// Use re-sorted matches with context
|
|
39739
40192
|
options?.totalMatchCount,
|
|
39740
40193
|
options?.snapshotFilePath,
|
|
39741
|
-
options?.probeResult
|
|
40194
|
+
options?.probeResult,
|
|
40195
|
+
options?.currentTime,
|
|
40196
|
+
options?.capturedToasts
|
|
39742
40197
|
)
|
|
39743
40198
|
);
|
|
39744
40199
|
lines.push("");
|
|
@@ -39763,7 +40218,7 @@ function expandSection(yaml, sectionRef) {
|
|
|
39763
40218
|
const sections = buildSectionTree(elements);
|
|
39764
40219
|
const { sorted: sortedSections, dialogStack } = sortSectionsByFocus(sections);
|
|
39765
40220
|
expandSectionByRef(sortedSections, sectionRef);
|
|
39766
|
-
const elementCount =
|
|
40221
|
+
const elementCount = countRenderedElements(sortedSections);
|
|
39767
40222
|
const sectionCount = countTotalSections(sortedSections);
|
|
39768
40223
|
const lines = [];
|
|
39769
40224
|
lines.push(generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack));
|
|
@@ -39777,29 +40232,26 @@ function expandSection(yaml, sectionRef) {
|
|
|
39777
40232
|
}
|
|
39778
40233
|
return lines.join("\n").trim();
|
|
39779
40234
|
}
|
|
39780
|
-
function
|
|
39781
|
-
|
|
39782
|
-
|
|
39783
|
-
|
|
39784
|
-
|
|
39785
|
-
|
|
39786
|
-
|
|
39787
|
-
|
|
39788
|
-
|
|
40235
|
+
function formatExpandedSectionOutput(section) {
|
|
40236
|
+
const lines = [];
|
|
40237
|
+
const subsectionInfo = section.subsections.length > 0 ? `, ${section.subsections.length} subsections` : "";
|
|
40238
|
+
lines.push(`---`);
|
|
40239
|
+
lines.push(`EXPANDED SECTION: ${section.heading} [${section.ref}]`);
|
|
40240
|
+
lines.push(`Contains ${section.elementCount} elements${subsectionInfo}`);
|
|
40241
|
+
lines.push(`---`);
|
|
40242
|
+
lines.push("");
|
|
40243
|
+
if (section.gridInfo) {
|
|
40244
|
+
lines.push(formatGridOutput(section.gridInfo, 0, true));
|
|
40245
|
+
return lines.join("\n").trim();
|
|
39789
40246
|
}
|
|
39790
|
-
|
|
39791
|
-
|
|
39792
|
-
function findSectionByRef(sections, targetRef) {
|
|
39793
|
-
for (const section of sections) {
|
|
39794
|
-
if (section.ref === targetRef) {
|
|
39795
|
-
return section;
|
|
39796
|
-
}
|
|
39797
|
-
const found = findSectionByRef(section.subsections, targetRef);
|
|
39798
|
-
if (found) {
|
|
39799
|
-
return found;
|
|
39800
|
-
}
|
|
40247
|
+
for (const el of section.elements) {
|
|
40248
|
+
lines.push(formatElementLine(el, 0));
|
|
39801
40249
|
}
|
|
39802
|
-
|
|
40250
|
+
for (const subsection of section.subsections) {
|
|
40251
|
+
lines.push("");
|
|
40252
|
+
lines.push(formatSectionOutput(subsection, 0, false));
|
|
40253
|
+
}
|
|
40254
|
+
return lines.join("\n").trim();
|
|
39803
40255
|
}
|
|
39804
40256
|
function expandSectionOnly(yaml, sectionRef) {
|
|
39805
40257
|
if (!yaml || yaml.trim() === "") {
|
|
@@ -39826,79 +40278,133 @@ Section with ref="${sectionRef}" not found in snapshot.
|
|
|
39826
40278
|
targetSection.collapsed = false;
|
|
39827
40279
|
return formatExpandedSectionOutput(targetSection);
|
|
39828
40280
|
}
|
|
39829
|
-
|
|
39830
|
-
|
|
39831
|
-
|
|
39832
|
-
|
|
39833
|
-
|
|
39834
|
-
|
|
39835
|
-
|
|
39836
|
-
|
|
39837
|
-
|
|
39838
|
-
|
|
39839
|
-
|
|
39840
|
-
|
|
39841
|
-
|
|
39842
|
-
|
|
40281
|
+
|
|
40282
|
+
// ../browser-core/src/table-extractor.ts
|
|
40283
|
+
function extractTablesFromSnapshot(yamlOrResponse, options) {
|
|
40284
|
+
const yaml = yamlOrResponse.includes("Page Snapshot:") ? extractSnapshotYaml(yamlOrResponse) : yamlOrResponse;
|
|
40285
|
+
if (!yaml) return null;
|
|
40286
|
+
const elements = parseSnapshot(yaml);
|
|
40287
|
+
if (elements.length === 0) return null;
|
|
40288
|
+
const grids = [];
|
|
40289
|
+
const findGrids = (els) => {
|
|
40290
|
+
for (const el of els) {
|
|
40291
|
+
const grid = detectGrid(el);
|
|
40292
|
+
if (grid && grid.rows.length > 0) {
|
|
40293
|
+
grids.push({ grid, element: el });
|
|
40294
|
+
}
|
|
40295
|
+
if (!grid) {
|
|
40296
|
+
findGrids(el.children);
|
|
40297
|
+
}
|
|
40298
|
+
}
|
|
40299
|
+
};
|
|
40300
|
+
findGrids(elements);
|
|
40301
|
+
if (grids.length === 0) return null;
|
|
40302
|
+
let target;
|
|
40303
|
+
if (options?.ref) {
|
|
40304
|
+
const match = grids.find((g) => g.grid.ref === options.ref);
|
|
40305
|
+
if (!match) return null;
|
|
40306
|
+
target = match;
|
|
40307
|
+
} else {
|
|
40308
|
+
target = grids.reduce(
|
|
40309
|
+
(best, current) => current.grid.rows.length > best.grid.rows.length ? current : best
|
|
40310
|
+
);
|
|
39843
40311
|
}
|
|
39844
|
-
|
|
40312
|
+
const { columns, rows } = gridInfoToRows(target.grid);
|
|
40313
|
+
if (columns.length === 0) return null;
|
|
40314
|
+
const pagination = detectPaginationInfo(elements, target.grid.ref);
|
|
40315
|
+
return {
|
|
40316
|
+
columns,
|
|
40317
|
+
rows,
|
|
40318
|
+
gridRef: target.grid.ref,
|
|
40319
|
+
pagination: pagination ?? void 0
|
|
40320
|
+
};
|
|
39845
40321
|
}
|
|
39846
|
-
function
|
|
39847
|
-
const
|
|
39848
|
-
|
|
39849
|
-
const
|
|
39850
|
-
|
|
39851
|
-
|
|
39852
|
-
if (grid.columns.length > 0) {
|
|
39853
|
-
const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
|
|
39854
|
-
const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
|
|
39855
|
-
const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
|
|
39856
|
-
lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
|
|
40322
|
+
function gridInfoToRows(grid) {
|
|
40323
|
+
const columns = grid.columns.map((c2) => c2.name);
|
|
40324
|
+
if (columns.length === 0) return { columns: [], rows: [] };
|
|
40325
|
+
const columnIndex = /* @__PURE__ */ new Map();
|
|
40326
|
+
for (let i = 0; i < columns.length; i++) {
|
|
40327
|
+
columnIndex.set(columns[i], i);
|
|
39857
40328
|
}
|
|
39858
|
-
|
|
39859
|
-
const
|
|
39860
|
-
|
|
39861
|
-
|
|
39862
|
-
|
|
39863
|
-
|
|
40329
|
+
const rows = [];
|
|
40330
|
+
for (const row of grid.rows) {
|
|
40331
|
+
const values = new Array(columns.length).fill("");
|
|
40332
|
+
for (const cell of row.cells) {
|
|
40333
|
+
const idx = columnIndex.get(cell.column);
|
|
40334
|
+
if (idx !== void 0) {
|
|
40335
|
+
values[idx] = cleanCellValue(cell.value, cell.gridcellLabel);
|
|
40336
|
+
}
|
|
40337
|
+
}
|
|
40338
|
+
rows.push(values);
|
|
39864
40339
|
}
|
|
39865
|
-
|
|
39866
|
-
|
|
40340
|
+
return { columns, rows };
|
|
40341
|
+
}
|
|
40342
|
+
var SVG_ARTIFACT_PATTERN = /^Styled\(svg\)$/i;
|
|
40343
|
+
function cleanCellValue(value, gridcellLabel) {
|
|
40344
|
+
if (!value || SVG_ARTIFACT_PATTERN.test(value)) {
|
|
40345
|
+
if (gridcellLabel) {
|
|
40346
|
+
return gridcellLabel.replace(/\s*Styled\(svg\)\s*/gi, "").trim();
|
|
40347
|
+
}
|
|
40348
|
+
return "";
|
|
39867
40349
|
}
|
|
39868
|
-
return
|
|
40350
|
+
return value;
|
|
39869
40351
|
}
|
|
39870
|
-
function
|
|
39871
|
-
const
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
|
|
40352
|
+
function formatCSVWithFrontmatter(columns, rows, _options) {
|
|
40353
|
+
const lines = [];
|
|
40354
|
+
lines.push(columns.map(escapeCSVField).join(","));
|
|
40355
|
+
for (const row of rows) {
|
|
40356
|
+
lines.push(row.map(escapeCSVField).join(","));
|
|
40357
|
+
}
|
|
40358
|
+
return lines.join("\n") + "\n";
|
|
40359
|
+
}
|
|
40360
|
+
function appendRowsToCSV(existingCSV, newRows, columns, _options) {
|
|
40361
|
+
const existingLines = existingCSV.split("\n").filter((l) => l.trim() !== "");
|
|
40362
|
+
const dataLines = [];
|
|
40363
|
+
let headerLine = "";
|
|
40364
|
+
for (const line of existingLines) {
|
|
40365
|
+
if (line.startsWith("#")) continue;
|
|
40366
|
+
if (!headerLine) {
|
|
40367
|
+
headerLine = line;
|
|
40368
|
+
continue;
|
|
39876
40369
|
}
|
|
39877
|
-
|
|
39878
|
-
|
|
40370
|
+
dataLines.push(line);
|
|
40371
|
+
}
|
|
40372
|
+
const existingRowSet = new Set(dataLines);
|
|
40373
|
+
const newRowStrings = newRows.map((row) => row.map(escapeCSVField).join(","));
|
|
40374
|
+
const addedRows = [];
|
|
40375
|
+
for (const rowStr of newRowStrings) {
|
|
40376
|
+
if (!existingRowSet.has(rowStr)) {
|
|
40377
|
+
addedRows.push(rowStr);
|
|
40378
|
+
existingRowSet.add(rowStr);
|
|
39879
40379
|
}
|
|
39880
|
-
return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
|
|
39881
40380
|
}
|
|
39882
|
-
const
|
|
39883
|
-
const
|
|
39884
|
-
|
|
39885
|
-
|
|
39886
|
-
|
|
39887
|
-
|
|
39888
|
-
|
|
39889
|
-
|
|
39890
|
-
|
|
39891
|
-
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
return `${
|
|
40381
|
+
const allDataLines = [...dataLines, ...addedRows];
|
|
40382
|
+
const result = [headerLine || columns.map(escapeCSVField).join(","), ...allDataLines];
|
|
40383
|
+
return result.join("\n") + "\n";
|
|
40384
|
+
}
|
|
40385
|
+
function detectPaginationInfo(elements, _gridRef) {
|
|
40386
|
+
const paginationPattern = /(\d+)\s*[-–]\s*(\d+)\s+of\s+([\d,]+)/i;
|
|
40387
|
+
const searchText = (els) => {
|
|
40388
|
+
for (const el of els) {
|
|
40389
|
+
const text = el.text || "";
|
|
40390
|
+
const match = text.match(paginationPattern);
|
|
40391
|
+
if (match) {
|
|
40392
|
+
const total = parseInt(match[3].replace(/,/g, ""), 10);
|
|
40393
|
+
return { showing: `${match[1]}-${match[2]}`, total };
|
|
39895
40394
|
}
|
|
39896
|
-
|
|
39897
|
-
|
|
39898
|
-
|
|
39899
|
-
|
|
39900
|
-
|
|
40395
|
+
const childResult = searchText(el.children);
|
|
40396
|
+
if (childResult) return childResult;
|
|
40397
|
+
}
|
|
40398
|
+
return null;
|
|
40399
|
+
};
|
|
40400
|
+
return searchText(elements);
|
|
40401
|
+
}
|
|
40402
|
+
function escapeCSVField(field) {
|
|
40403
|
+
if (!field) return "";
|
|
40404
|
+
if (field.includes(",") || field.includes('"') || field.includes("\n")) {
|
|
40405
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
39901
40406
|
}
|
|
40407
|
+
return field;
|
|
39902
40408
|
}
|
|
39903
40409
|
|
|
39904
40410
|
// ../browser-core/src/snapshot-diff.ts
|
|
@@ -40137,10 +40643,11 @@ function generateHint(diff2, networkInfo) {
|
|
|
40137
40643
|
}
|
|
40138
40644
|
return hints.join(" ");
|
|
40139
40645
|
}
|
|
40140
|
-
function formatSemanticDiff(diff2, actionDescription, networkInfo) {
|
|
40646
|
+
function formatSemanticDiff(diff2, actionDescription, networkInfo, currentTime) {
|
|
40141
40647
|
const lines = [];
|
|
40142
40648
|
lines.push("---");
|
|
40143
40649
|
lines.push("SEMANTIC DIFF: Page state changes after browser action.");
|
|
40650
|
+
lines.push(`BROWSER TIME: ${currentTime ?? (/* @__PURE__ */ new Date()).toISOString()} (UTC)`);
|
|
40144
40651
|
lines.push("Format: [PREVIOUS] value [CURRENT] value");
|
|
40145
40652
|
lines.push("");
|
|
40146
40653
|
lines.push(`ACTION: ${actionDescription}`);
|
|
@@ -40219,6 +40726,38 @@ function formatValue(value) {
|
|
|
40219
40726
|
return `"${truncate(value, 20)}"`;
|
|
40220
40727
|
}
|
|
40221
40728
|
|
|
40729
|
+
// ../browser-core/src/playwright-client/download-utils.ts
|
|
40730
|
+
import * as path from "path";
|
|
40731
|
+
function formatFileSize(bytes) {
|
|
40732
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
40733
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
40734
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
40735
|
+
}
|
|
40736
|
+
function guessMimeType(filename) {
|
|
40737
|
+
const ext = path.extname(filename).toLowerCase();
|
|
40738
|
+
const mimeMap = {
|
|
40739
|
+
".pdf": "application/pdf",
|
|
40740
|
+
".txt": "text/plain",
|
|
40741
|
+
".csv": "text/csv",
|
|
40742
|
+
".json": "application/json",
|
|
40743
|
+
".xml": "application/xml",
|
|
40744
|
+
".html": "text/html",
|
|
40745
|
+
".htm": "text/html",
|
|
40746
|
+
".md": "text/markdown",
|
|
40747
|
+
".png": "image/png",
|
|
40748
|
+
".jpg": "image/jpeg",
|
|
40749
|
+
".jpeg": "image/jpeg",
|
|
40750
|
+
".gif": "image/gif",
|
|
40751
|
+
".svg": "image/svg+xml",
|
|
40752
|
+
".zip": "application/zip",
|
|
40753
|
+
".doc": "application/msword",
|
|
40754
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
40755
|
+
".xls": "application/vnd.ms-excel",
|
|
40756
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
40757
|
+
};
|
|
40758
|
+
return mimeMap[ext] ?? "application/octet-stream";
|
|
40759
|
+
}
|
|
40760
|
+
|
|
40222
40761
|
// ../browser-core/src/playwright-client/element-inspection.ts
|
|
40223
40762
|
function errorMessage(error) {
|
|
40224
40763
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -40618,7 +41157,7 @@ async function extractElementMetadata(page, locator, ref, logger2) {
|
|
|
40618
41157
|
}
|
|
40619
41158
|
|
|
40620
41159
|
// ../browser-core/src/tools/executor.ts
|
|
40621
|
-
import * as
|
|
41160
|
+
import * as path2 from "path";
|
|
40622
41161
|
import * as fs2 from "fs/promises";
|
|
40623
41162
|
import * as crypto2 from "crypto";
|
|
40624
41163
|
|
|
@@ -40750,7 +41289,7 @@ var BrowserToolExecutor = class {
|
|
|
40750
41289
|
if (!options?.workspaceDir && !options?.tmpBaseDir) {
|
|
40751
41290
|
options?.logger?.debug?.("[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)");
|
|
40752
41291
|
}
|
|
40753
|
-
this.workspaceDir = options?.workspaceDir ??
|
|
41292
|
+
this.workspaceDir = options?.workspaceDir ?? path2.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
|
|
40754
41293
|
this.logger = options?.logger;
|
|
40755
41294
|
fs2.mkdir(this.workspaceDir, { recursive: true }).catch(() => {
|
|
40756
41295
|
});
|
|
@@ -40915,15 +41454,18 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
40915
41454
|
try {
|
|
40916
41455
|
await fs2.mkdir(this.workspaceDir, { recursive: true });
|
|
40917
41456
|
const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
|
|
40918
|
-
snapshotFilePath =
|
|
41457
|
+
snapshotFilePath = path2.join(this.workspaceDir, filename);
|
|
40919
41458
|
await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
|
|
40920
41459
|
} catch {
|
|
40921
41460
|
}
|
|
41461
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
40922
41462
|
const semanticText = formatSemanticSnapshot(yaml, {
|
|
40923
41463
|
searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
|
|
40924
41464
|
totalMatchCount: totalMatchCount > 0 ? totalMatchCount : void 0,
|
|
40925
41465
|
snapshotFilePath,
|
|
40926
|
-
probeResult
|
|
41466
|
+
probeResult,
|
|
41467
|
+
currentTime: now,
|
|
41468
|
+
capturedToasts: this.client.getCapturedToasts?.() ?? []
|
|
40927
41469
|
});
|
|
40928
41470
|
if (hasImages) {
|
|
40929
41471
|
const resolutionNote = buildResolutionNote(
|
|
@@ -41166,7 +41708,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
41166
41708
|
if (!afterModal) {
|
|
41167
41709
|
const afterState2 = captureSnapshotState(afterUrl2, afterYaml2);
|
|
41168
41710
|
const diff3 = compareSnapshots(beforeState, afterState2);
|
|
41169
|
-
return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}
|
|
41711
|
+
return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`, void 0, (/* @__PURE__ */ new Date()).toISOString());
|
|
41170
41712
|
}
|
|
41171
41713
|
} catch (err) {
|
|
41172
41714
|
this.logger?.debug?.(`[BrowserTools] Dismiss strategy ${strategy} failed`, {
|
|
@@ -41182,7 +41724,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
41182
41724
|
const diff2 = compareSnapshots(beforeState, afterState);
|
|
41183
41725
|
return `Could not dismiss overlay with strategies: ${strategies.join(", ")}. Modal may require specific interaction.
|
|
41184
41726
|
|
|
41185
|
-
` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)");
|
|
41727
|
+
` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)", void 0, (/* @__PURE__ */ new Date()).toISOString());
|
|
41186
41728
|
}
|
|
41187
41729
|
// ==================== Waiting ====================
|
|
41188
41730
|
async waitFor(opts) {
|
|
@@ -41296,7 +41838,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
41296
41838
|
}
|
|
41297
41839
|
const diff2 = compareSnapshots(beforeState, afterState);
|
|
41298
41840
|
const networkInfo = this.client.getPendingNetworkInfo?.() ?? void 0;
|
|
41299
|
-
const
|
|
41841
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41842
|
+
const diffText = formatSemanticDiff(diff2, actionDescription, networkInfo, now);
|
|
41300
41843
|
const hasChanges = hasDiffChanges(diff2);
|
|
41301
41844
|
const isStable = !networkInfo || networkInfo.pendingCount === 0;
|
|
41302
41845
|
let autoSnapshot;
|
|
@@ -41306,11 +41849,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
41306
41849
|
try {
|
|
41307
41850
|
await fs2.mkdir(this.workspaceDir, { recursive: true });
|
|
41308
41851
|
const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
|
|
41309
|
-
snapshotFilePath =
|
|
41852
|
+
snapshotFilePath = path2.join(this.workspaceDir, filename);
|
|
41310
41853
|
await fs2.writeFile(snapshotFilePath, afterYaml, "utf-8");
|
|
41311
41854
|
} catch {
|
|
41312
41855
|
}
|
|
41313
|
-
const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath });
|
|
41856
|
+
const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath, currentTime: now, capturedToasts: this.client.getCapturedToasts?.() ?? [] });
|
|
41314
41857
|
const afterImages = isMCPContentWithImages(afterResult) ? afterResult.images : void 0;
|
|
41315
41858
|
if (afterImages?.length && this.onScreenshot) {
|
|
41316
41859
|
const img = afterImages[0];
|
|
@@ -41347,11 +41890,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
41347
41890
|
try {
|
|
41348
41891
|
await fs2.mkdir(this.workspaceDir, { recursive: true });
|
|
41349
41892
|
const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
|
|
41350
|
-
snapshotFilePath =
|
|
41893
|
+
snapshotFilePath = path2.join(this.workspaceDir, filename);
|
|
41351
41894
|
await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
|
|
41352
41895
|
} catch {
|
|
41353
41896
|
}
|
|
41354
|
-
const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath });
|
|
41897
|
+
const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath, currentTime: (/* @__PURE__ */ new Date()).toISOString(), capturedToasts: this.client.getCapturedToasts?.() ?? [] });
|
|
41355
41898
|
const yamlBlockPattern = /- Page Snapshot:\n```yaml\n[\s\S]*?```/;
|
|
41356
41899
|
if (yamlBlockPattern.test(result)) {
|
|
41357
41900
|
return result.replace(yamlBlockPattern, `- Page Snapshot (semantic):
|
|
@@ -41806,6 +42349,136 @@ function getBrowserToolDefinitionsWithLifecycle() {
|
|
|
41806
42349
|
];
|
|
41807
42350
|
}
|
|
41808
42351
|
|
|
42352
|
+
// ../browser-core/src/tools/dispatcher.ts
|
|
42353
|
+
async function dispatchBrowserTool(executor, toolName, args) {
|
|
42354
|
+
switch (toolName) {
|
|
42355
|
+
case "browser_navigate":
|
|
42356
|
+
return executor.navigate(args.url);
|
|
42357
|
+
case "browser_navigate_back":
|
|
42358
|
+
return executor.navigateBack();
|
|
42359
|
+
case "browser_snapshot":
|
|
42360
|
+
return executor.snapshot({
|
|
42361
|
+
mode: args.mode,
|
|
42362
|
+
expand: args.expand,
|
|
42363
|
+
search: args.search,
|
|
42364
|
+
showCoordinateGrid: args.showCoordinateGrid,
|
|
42365
|
+
probeAt: args.probeAt
|
|
42366
|
+
});
|
|
42367
|
+
case "browser_screenshot":
|
|
42368
|
+
return executor.screenshot({
|
|
42369
|
+
fullPage: args.fullPage,
|
|
42370
|
+
element: args.element,
|
|
42371
|
+
ref: args.ref,
|
|
42372
|
+
label: args.label,
|
|
42373
|
+
returnImage: args.returnImage
|
|
42374
|
+
});
|
|
42375
|
+
case "browser_evaluate":
|
|
42376
|
+
return executor.evaluate({
|
|
42377
|
+
expression: args.function,
|
|
42378
|
+
element: args.element,
|
|
42379
|
+
ref: args.ref
|
|
42380
|
+
});
|
|
42381
|
+
case "browser_console_messages":
|
|
42382
|
+
return executor.consoleMessages({
|
|
42383
|
+
onlyErrors: args.onlyErrors
|
|
42384
|
+
});
|
|
42385
|
+
case "browser_network_requests":
|
|
42386
|
+
return executor.networkRequests();
|
|
42387
|
+
case "browser_click":
|
|
42388
|
+
return executor.click({
|
|
42389
|
+
ref: args.ref,
|
|
42390
|
+
x: args.x,
|
|
42391
|
+
y: args.y,
|
|
42392
|
+
element: args.element,
|
|
42393
|
+
doubleClick: args.doubleClick,
|
|
42394
|
+
button: args.button,
|
|
42395
|
+
modifiers: args.modifiers
|
|
42396
|
+
});
|
|
42397
|
+
case "browser_hover":
|
|
42398
|
+
return executor.hover({
|
|
42399
|
+
ref: args.ref,
|
|
42400
|
+
element: args.element
|
|
42401
|
+
});
|
|
42402
|
+
case "browser_drag":
|
|
42403
|
+
return executor.drag({
|
|
42404
|
+
startRef: args.startRef,
|
|
42405
|
+
endRef: args.endRef,
|
|
42406
|
+
startElement: args.startElement,
|
|
42407
|
+
endElement: args.endElement,
|
|
42408
|
+
startX: args.startX,
|
|
42409
|
+
startY: args.startY,
|
|
42410
|
+
endX: args.endX,
|
|
42411
|
+
endY: args.endY
|
|
42412
|
+
});
|
|
42413
|
+
case "browser_type":
|
|
42414
|
+
return executor.type({
|
|
42415
|
+
ref: args.ref,
|
|
42416
|
+
text: args.text,
|
|
42417
|
+
element: args.element,
|
|
42418
|
+
submit: args.submit,
|
|
42419
|
+
delay: args.delay
|
|
42420
|
+
});
|
|
42421
|
+
case "browser_press_key":
|
|
42422
|
+
return executor.pressKey(args.key);
|
|
42423
|
+
case "browser_fill_form":
|
|
42424
|
+
return executor.fillForm(
|
|
42425
|
+
args.fields
|
|
42426
|
+
);
|
|
42427
|
+
case "browser_select_option":
|
|
42428
|
+
return executor.selectOption({
|
|
42429
|
+
ref: args.ref,
|
|
42430
|
+
value: args.value,
|
|
42431
|
+
element: args.element
|
|
42432
|
+
});
|
|
42433
|
+
case "browser_file_upload":
|
|
42434
|
+
return executor.fileUpload(args.paths);
|
|
42435
|
+
case "browser_scroll":
|
|
42436
|
+
return executor.scroll({
|
|
42437
|
+
direction: args.direction,
|
|
42438
|
+
amount: args.amount,
|
|
42439
|
+
withinRef: args.withinRef,
|
|
42440
|
+
toRef: args.toRef,
|
|
42441
|
+
x: args.x,
|
|
42442
|
+
y: args.y
|
|
42443
|
+
});
|
|
42444
|
+
case "browser_handle_dialog":
|
|
42445
|
+
return executor.handleDialog({
|
|
42446
|
+
action: args.action,
|
|
42447
|
+
promptText: args.promptText
|
|
42448
|
+
});
|
|
42449
|
+
case "browser_dismiss_overlay":
|
|
42450
|
+
return executor.dismissOverlay({
|
|
42451
|
+
preferredStrategy: args.preferredStrategy
|
|
42452
|
+
});
|
|
42453
|
+
case "browser_wait_for":
|
|
42454
|
+
return executor.waitFor({
|
|
42455
|
+
timeSec: args.time,
|
|
42456
|
+
text: args.text,
|
|
42457
|
+
textGone: args.textGone,
|
|
42458
|
+
selector: args.selector,
|
|
42459
|
+
state: args.state,
|
|
42460
|
+
timeout: args.timeout
|
|
42461
|
+
});
|
|
42462
|
+
case "browser_close":
|
|
42463
|
+
return executor.close();
|
|
42464
|
+
case "browser_resize":
|
|
42465
|
+
return executor.resize(args.width, args.height);
|
|
42466
|
+
case "browser_tabs":
|
|
42467
|
+
return executor.tabs({
|
|
42468
|
+
action: args.action,
|
|
42469
|
+
index: args.index
|
|
42470
|
+
});
|
|
42471
|
+
case "browser_list_downloads":
|
|
42472
|
+
return executor.listDownloads();
|
|
42473
|
+
case "browser_read_download":
|
|
42474
|
+
return executor.readDownload(args.downloadId);
|
|
42475
|
+
case "browser_pdf_read":
|
|
42476
|
+
return executor.fetchPdfText(args.url);
|
|
42477
|
+
default:
|
|
42478
|
+
throw new Error(`Unknown browser tool: ${toolName}`);
|
|
42479
|
+
}
|
|
42480
|
+
}
|
|
42481
|
+
|
|
41809
42482
|
// ../browser-core/src/playwright-client.ts
|
|
41810
42483
|
import {
|
|
41811
42484
|
chromium as playwrightChromium
|
|
@@ -42586,8 +43259,8 @@ var errors = playwrightLoader.lazyloadExportOrDie("errors");
|
|
|
42586
43259
|
|
|
42587
43260
|
// ../browser-core/src/playwright-client.ts
|
|
42588
43261
|
var import_puppeteer_extra_plugin_stealth = __toESM(require_puppeteer_extra_plugin_stealth(), 1);
|
|
42589
|
-
import * as
|
|
42590
|
-
import * as
|
|
43262
|
+
import * as fs4 from "fs/promises";
|
|
43263
|
+
import * as path4 from "path";
|
|
42591
43264
|
|
|
42592
43265
|
// ../browser-core/src/playwright-client/toast-capture-script.ts
|
|
42593
43266
|
var TOAST_CAPTURE_SCRIPT = `
|
|
@@ -42926,6 +43599,29 @@ function appendAttributeIfMissing(line, key, value) {
|
|
|
42926
43599
|
const baseLine = hasTrailingColon ? line.replace(/:\s*$/, "") : line;
|
|
42927
43600
|
return `${baseLine} [${key}=${value}]${hasTrailingColon ? ":" : ""}`;
|
|
42928
43601
|
}
|
|
43602
|
+
function findLabelFromYamlSiblings(elements, targetRef) {
|
|
43603
|
+
const findParent = (els, parent2) => {
|
|
43604
|
+
for (const el of els) {
|
|
43605
|
+
if (el.ref === targetRef) return parent2;
|
|
43606
|
+
const found = findParent(el.children, el);
|
|
43607
|
+
if (found) return found;
|
|
43608
|
+
}
|
|
43609
|
+
return null;
|
|
43610
|
+
};
|
|
43611
|
+
const parent = findParent(elements, null);
|
|
43612
|
+
if (!parent) return void 0;
|
|
43613
|
+
const targetIdx = parent.children.findIndex((c2) => c2.ref === targetRef);
|
|
43614
|
+
if (targetIdx < 0) return void 0;
|
|
43615
|
+
for (let i = targetIdx - 1; i >= 0; i--) {
|
|
43616
|
+
const sib = parent.children[i];
|
|
43617
|
+
const sibText = sib.text?.trim();
|
|
43618
|
+
if (sibText && sibText.length > 0 && sibText.length < 80 && /[a-zA-Z0-9]/.test(sibText)) {
|
|
43619
|
+
if (FORM_FIELD_ROLES.has(sib.role)) continue;
|
|
43620
|
+
return sibText;
|
|
43621
|
+
}
|
|
43622
|
+
}
|
|
43623
|
+
return void 0;
|
|
43624
|
+
}
|
|
42929
43625
|
async function augmentUnlabeledElements(page, yaml, logger2) {
|
|
42930
43626
|
try {
|
|
42931
43627
|
const elements = parseSnapshot(yaml);
|
|
@@ -43231,6 +43927,9 @@ async function augmentGridCellValues(page, yaml, logger2) {
|
|
|
43231
43927
|
}
|
|
43232
43928
|
}
|
|
43233
43929
|
var FORM_FIELD_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox"]);
|
|
43930
|
+
function splitCamelCase(name) {
|
|
43931
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
|
|
43932
|
+
}
|
|
43234
43933
|
async function augmentFormFieldLabels(page, yaml, logger2) {
|
|
43235
43934
|
try {
|
|
43236
43935
|
const elements = parseSnapshot(yaml);
|
|
@@ -43248,16 +43947,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
|
|
|
43248
43947
|
walkForFields(el);
|
|
43249
43948
|
}
|
|
43250
43949
|
if (formFieldRefs.length === 0) return yaml;
|
|
43251
|
-
|
|
43950
|
+
const glyphFields = formFieldRefs.filter((f) => f.text && !/[a-zA-Z0-9]/.test(f.text));
|
|
43951
|
+
const displayNameIdx = yaml.indexOf("Display Name");
|
|
43952
|
+
const yamlAroundDisplayName = displayNameIdx >= 0 ? yaml.slice(Math.max(0, displayNameIdx - 50), displayNameIdx + 200) : "NOT FOUND";
|
|
43953
|
+
logger2.warn("[DirectPlaywright] augmentFormFieldLabels called", {
|
|
43252
43954
|
count: formFieldRefs.length,
|
|
43253
|
-
|
|
43955
|
+
glyphCount: glyphFields.length,
|
|
43956
|
+
allTexts: formFieldRefs.map((f) => `${f.ref}:${JSON.stringify(f.text)}`),
|
|
43957
|
+
hasDisplayName: yaml.includes("Display Name"),
|
|
43958
|
+
yamlHasGlyph: yaml.includes("\u268A"),
|
|
43959
|
+
yamlAroundDisplayName
|
|
43254
43960
|
});
|
|
43255
43961
|
let augmented = yaml;
|
|
43256
43962
|
for (const { ref, role, text } of formFieldRefs) {
|
|
43257
43963
|
try {
|
|
43964
|
+
const isGlyphField = text && !/[a-zA-Z0-9]/.test(text);
|
|
43965
|
+
if (isGlyphField) {
|
|
43966
|
+
logger2.info("[DirectPlaywright] Glyph-labeled field entering augmentation", { ref, role, text });
|
|
43967
|
+
}
|
|
43258
43968
|
const locator = page.locator(`aria-ref=${ref}`);
|
|
43259
43969
|
const count = await locator.count();
|
|
43260
|
-
if (count === 0)
|
|
43970
|
+
if (count === 0) {
|
|
43971
|
+
if (isGlyphField) logger2.info("[DirectPlaywright] Glyph field: locator count=0, skipping", { ref });
|
|
43972
|
+
continue;
|
|
43973
|
+
}
|
|
43261
43974
|
const label = await locator.first().evaluate((el) => {
|
|
43262
43975
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
43263
43976
|
const labels = el.labels;
|
|
@@ -43266,6 +43979,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
|
|
|
43266
43979
|
if (labelText) return labelText;
|
|
43267
43980
|
}
|
|
43268
43981
|
}
|
|
43982
|
+
{
|
|
43983
|
+
const precedingLabels = [];
|
|
43984
|
+
let prevEl = el.previousElementSibling;
|
|
43985
|
+
while (prevEl) {
|
|
43986
|
+
if (prevEl.tagName === "LABEL") {
|
|
43987
|
+
precedingLabels.unshift(prevEl);
|
|
43988
|
+
} else {
|
|
43989
|
+
break;
|
|
43990
|
+
}
|
|
43991
|
+
prevEl = prevEl.previousElementSibling;
|
|
43992
|
+
}
|
|
43993
|
+
if (precedingLabels.length > 1) {
|
|
43994
|
+
const selected = precedingLabels.find((l) => {
|
|
43995
|
+
const cls = l.className.toLowerCase();
|
|
43996
|
+
return cls.includes("selected") || cls.includes("active") || cls.includes("current") || cls.includes("checked");
|
|
43997
|
+
});
|
|
43998
|
+
if (selected?.textContent?.trim()) return selected.textContent.trim();
|
|
43999
|
+
const first = precedingLabels[0];
|
|
44000
|
+
if (first?.textContent?.trim()) return first.textContent.trim();
|
|
44001
|
+
} else if (precedingLabels.length === 1) {
|
|
44002
|
+
const labelText = precedingLabels[0].textContent?.trim();
|
|
44003
|
+
if (labelText) return labelText;
|
|
44004
|
+
}
|
|
44005
|
+
}
|
|
43269
44006
|
const labelledBy = el.getAttribute("aria-labelledby");
|
|
43270
44007
|
if (labelledBy) {
|
|
43271
44008
|
const labelEl = document.getElementById(labelledBy);
|
|
@@ -43273,31 +44010,101 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
|
|
|
43273
44010
|
}
|
|
43274
44011
|
const ariaLabel = el.getAttribute("aria-label");
|
|
43275
44012
|
const placeholder = el.getAttribute("placeholder");
|
|
43276
|
-
if (ariaLabel && ariaLabel !== placeholder) return ariaLabel;
|
|
43277
|
-
let sibling = el.previousElementSibling;
|
|
44013
|
+
if (ariaLabel && ariaLabel !== placeholder && /[a-zA-Z0-9]/.test(ariaLabel)) return ariaLabel;
|
|
43278
44014
|
let target = el;
|
|
43279
|
-
for (let
|
|
43280
|
-
sibling = target.previousElementSibling;
|
|
43281
|
-
|
|
44015
|
+
for (let depth = 0; depth < 3 && target; depth++) {
|
|
44016
|
+
let sibling = target.previousElementSibling;
|
|
44017
|
+
for (let sibCount = 0; sibling && sibCount < 3; sibCount++) {
|
|
43282
44018
|
const sibText = sibling.textContent?.trim();
|
|
43283
44019
|
if (sibText && sibText.length > 0 && sibText.length < 80 && !sibling.querySelector("input, textarea, select, button")) {
|
|
43284
44020
|
return sibText;
|
|
43285
44021
|
}
|
|
44022
|
+
if (sibling.querySelector("input, textarea, select, button")) break;
|
|
44023
|
+
sibling = sibling.previousElementSibling;
|
|
43286
44024
|
}
|
|
43287
44025
|
target = target.parentElement;
|
|
43288
44026
|
}
|
|
43289
44027
|
return void 0;
|
|
43290
44028
|
});
|
|
44029
|
+
if (isGlyphField) {
|
|
44030
|
+
logger2.info("[DirectPlaywright] Glyph field: DOM evaluate result", { ref, label, text });
|
|
44031
|
+
}
|
|
43291
44032
|
if (label && label !== text) {
|
|
43292
44033
|
augmented = updateElementLineByRef(
|
|
43293
44034
|
augmented,
|
|
43294
44035
|
ref,
|
|
43295
44036
|
(line) => injectElementLabel(line, role, label)
|
|
43296
44037
|
);
|
|
44038
|
+
if (isGlyphField) {
|
|
44039
|
+
logger2.info("[DirectPlaywright] Glyph field: injected DOM label", { ref, label });
|
|
44040
|
+
}
|
|
44041
|
+
}
|
|
44042
|
+
const hasGlyphOnlyLabel = text && !/[a-zA-Z0-9]/.test(text);
|
|
44043
|
+
if (hasGlyphOnlyLabel && (!label || label === text || !/[a-zA-Z0-9]/.test(label))) {
|
|
44044
|
+
const yamlLabel = findLabelFromYamlSiblings(elements, ref);
|
|
44045
|
+
logger2.info("[DirectPlaywright] YAML sibling fallback for glyph field", {
|
|
44046
|
+
ref,
|
|
44047
|
+
text,
|
|
44048
|
+
label,
|
|
44049
|
+
yamlLabel
|
|
44050
|
+
});
|
|
44051
|
+
if (yamlLabel) {
|
|
44052
|
+
augmented = updateElementLineByRef(
|
|
44053
|
+
augmented,
|
|
44054
|
+
ref,
|
|
44055
|
+
(line) => injectElementLabel(line, role, yamlLabel)
|
|
44056
|
+
);
|
|
44057
|
+
}
|
|
43297
44058
|
}
|
|
43298
44059
|
} catch {
|
|
43299
44060
|
}
|
|
43300
44061
|
}
|
|
44062
|
+
try {
|
|
44063
|
+
const postElements = parseSnapshot(augmented);
|
|
44064
|
+
const labeledFields = [];
|
|
44065
|
+
const walkForLabeled = (el) => {
|
|
44066
|
+
if (FORM_FIELD_ROLES.has(el.role) && el.ref && el.text) {
|
|
44067
|
+
labeledFields.push({ ref: el.ref, role: el.role, label: el.text });
|
|
44068
|
+
}
|
|
44069
|
+
for (const child of el.children) walkForLabeled(child);
|
|
44070
|
+
};
|
|
44071
|
+
for (const el of postElements) walkForLabeled(el);
|
|
44072
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
44073
|
+
for (const f of labeledFields) {
|
|
44074
|
+
const existing = byLabel.get(f.label);
|
|
44075
|
+
if (existing) {
|
|
44076
|
+
existing.push({ ref: f.ref, role: f.role });
|
|
44077
|
+
} else {
|
|
44078
|
+
byLabel.set(f.label, [{ ref: f.ref, role: f.role }]);
|
|
44079
|
+
}
|
|
44080
|
+
}
|
|
44081
|
+
for (const [label, fields] of byLabel) {
|
|
44082
|
+
if (fields.length < 2) continue;
|
|
44083
|
+
for (const field of fields) {
|
|
44084
|
+
try {
|
|
44085
|
+
const locator = page.locator(`aria-ref=${field.ref}`);
|
|
44086
|
+
const count = await locator.count();
|
|
44087
|
+
if (count === 0) continue;
|
|
44088
|
+
const disambiguation = await locator.first().evaluate((el) => {
|
|
44089
|
+
const placeholder = el.getAttribute("placeholder") || void 0;
|
|
44090
|
+
const ariaLabel = el.getAttribute("aria-label") || void 0;
|
|
44091
|
+
const name = el.getAttribute("name") || void 0;
|
|
44092
|
+
return { placeholder, ariaLabel, name };
|
|
44093
|
+
});
|
|
44094
|
+
const disambig = disambiguation.placeholder || disambiguation.ariaLabel || (disambiguation.name ? splitCamelCase(disambiguation.name) : void 0);
|
|
44095
|
+
if (disambig && disambig !== label) {
|
|
44096
|
+
augmented = updateElementLineByRef(
|
|
44097
|
+
augmented,
|
|
44098
|
+
field.ref,
|
|
44099
|
+
(line) => injectElementLabel(line, field.role, disambig)
|
|
44100
|
+
);
|
|
44101
|
+
}
|
|
44102
|
+
} catch {
|
|
44103
|
+
}
|
|
44104
|
+
}
|
|
44105
|
+
}
|
|
44106
|
+
} catch {
|
|
44107
|
+
}
|
|
43301
44108
|
return augmented;
|
|
43302
44109
|
} catch (err) {
|
|
43303
44110
|
logger2.debug("[DirectPlaywright] Form field label augmentation failed", {
|
|
@@ -43310,11 +44117,44 @@ async function enrichHiddenClickableElements(page, logger2) {
|
|
|
43310
44117
|
try {
|
|
43311
44118
|
const enrichedCount = await page.evaluate(() => {
|
|
43312
44119
|
let count = 0;
|
|
43313
|
-
const
|
|
43314
|
-
|
|
44120
|
+
const chosenCloseButtons = document.querySelectorAll("abbr.search-choice-close");
|
|
44121
|
+
for (const el of chosenCloseButtons) {
|
|
44122
|
+
if (el.getAttribute("data-enriched-clickable") === "true") continue;
|
|
44123
|
+
if (el.getAttribute("role") === "button") continue;
|
|
44124
|
+
let fieldName;
|
|
44125
|
+
const container = el.closest(".chosen-container");
|
|
44126
|
+
if (container) {
|
|
44127
|
+
const selectEl = container.previousElementSibling;
|
|
44128
|
+
if (selectEl && selectEl.tagName === "SELECT") {
|
|
44129
|
+
const selectId = selectEl.id;
|
|
44130
|
+
if (selectId) {
|
|
44131
|
+
const labelEl = document.querySelector(`label[for="${selectId}"]`);
|
|
44132
|
+
if (labelEl?.textContent?.trim()) {
|
|
44133
|
+
fieldName = labelEl.textContent.trim();
|
|
44134
|
+
}
|
|
44135
|
+
}
|
|
44136
|
+
if (!fieldName) {
|
|
44137
|
+
fieldName = selectEl.getAttribute("aria-label") || void 0;
|
|
44138
|
+
}
|
|
44139
|
+
}
|
|
44140
|
+
if (!fieldName && container.parentElement) {
|
|
44141
|
+
const prev = container.parentElement.previousElementSibling;
|
|
44142
|
+
if (prev && (prev.tagName === "LABEL" || prev.querySelector("label"))) {
|
|
44143
|
+
const labelText = prev.textContent?.trim();
|
|
44144
|
+
if (labelText && labelText.length < 80) fieldName = labelText;
|
|
44145
|
+
}
|
|
44146
|
+
}
|
|
44147
|
+
}
|
|
44148
|
+
const label = fieldName ? `Clear ${fieldName}` : "Clear selection";
|
|
44149
|
+
el.setAttribute("role", "button");
|
|
44150
|
+
el.setAttribute("aria-label", label);
|
|
44151
|
+
el.setAttribute("data-enriched-clickable", "true");
|
|
44152
|
+
count++;
|
|
44153
|
+
}
|
|
44154
|
+
const genericPatterns = [
|
|
43315
44155
|
[".select2-selection__clear", "select2-selection__clear"]
|
|
43316
44156
|
];
|
|
43317
|
-
for (const [selector, fallbackLabel] of
|
|
44157
|
+
for (const [selector, fallbackLabel] of genericPatterns) {
|
|
43318
44158
|
const elements = document.querySelectorAll(selector);
|
|
43319
44159
|
for (const el of elements) {
|
|
43320
44160
|
if (el.getAttribute("data-enriched-clickable") === "true") continue;
|
|
@@ -43326,6 +44166,12 @@ async function enrichHiddenClickableElements(page, logger2) {
|
|
|
43326
44166
|
count++;
|
|
43327
44167
|
}
|
|
43328
44168
|
}
|
|
44169
|
+
const chosenSearchInputs = document.querySelectorAll(
|
|
44170
|
+
".chosen-container:not(.chosen-with-drop) .chosen-search-input"
|
|
44171
|
+
);
|
|
44172
|
+
for (const input of chosenSearchInputs) {
|
|
44173
|
+
input.setAttribute("aria-hidden", "true");
|
|
44174
|
+
}
|
|
43329
44175
|
return count;
|
|
43330
44176
|
});
|
|
43331
44177
|
if (enrichedCount > 0) {
|
|
@@ -43364,10 +44210,29 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
|
|
|
43364
44210
|
if (!trigger) continue;
|
|
43365
44211
|
const rect = trigger.getBoundingClientRect();
|
|
43366
44212
|
const triggerText = trigger.textContent?.trim()?.slice(0, 80) || null;
|
|
44213
|
+
let fieldLabel = null;
|
|
44214
|
+
const selectId = sel.id;
|
|
44215
|
+
if (selectId) {
|
|
44216
|
+
const labelEl = document.querySelector(`label[for="${selectId}"]`);
|
|
44217
|
+
if (labelEl?.textContent?.trim()) {
|
|
44218
|
+
fieldLabel = labelEl.textContent.trim();
|
|
44219
|
+
}
|
|
44220
|
+
}
|
|
44221
|
+
if (!fieldLabel) {
|
|
44222
|
+
fieldLabel = sel.getAttribute("aria-label") || null;
|
|
44223
|
+
}
|
|
44224
|
+
if (!fieldLabel && parent.previousElementSibling) {
|
|
44225
|
+
const prev = parent.previousElementSibling;
|
|
44226
|
+
if (prev.tagName === "LABEL" || prev.querySelector("label")) {
|
|
44227
|
+
const labelText = prev.textContent?.trim();
|
|
44228
|
+
if (labelText && labelText.length < 80) fieldLabel = labelText;
|
|
44229
|
+
}
|
|
44230
|
+
}
|
|
43367
44231
|
results.push({
|
|
43368
44232
|
options: opts.slice(0, 15),
|
|
43369
44233
|
triggerText,
|
|
43370
|
-
triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null
|
|
44234
|
+
triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
|
|
44235
|
+
fieldLabel
|
|
43371
44236
|
});
|
|
43372
44237
|
}
|
|
43373
44238
|
return results;
|
|
@@ -43379,7 +44244,7 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
|
|
|
43379
44244
|
const elements = parseSnapshot(yaml);
|
|
43380
44245
|
let augmented = yaml;
|
|
43381
44246
|
for (const selectInfo of hiddenSelects) {
|
|
43382
|
-
const { options: opts, triggerText, triggerRect } = selectInfo;
|
|
44247
|
+
const { options: opts, triggerText, triggerRect, fieldLabel } = selectInfo;
|
|
43383
44248
|
if (!opts.length) continue;
|
|
43384
44249
|
let matchedRef = null;
|
|
43385
44250
|
if (triggerText) {
|
|
@@ -43428,11 +44293,13 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
|
|
|
43428
44293
|
}
|
|
43429
44294
|
if (!matchedRef) continue;
|
|
43430
44295
|
const optionsValue = opts.join("|");
|
|
43431
|
-
augmented = updateElementLineByRef(
|
|
43432
|
-
|
|
43433
|
-
|
|
43434
|
-
|
|
43435
|
-
|
|
44296
|
+
augmented = updateElementLineByRef(augmented, matchedRef, (line) => {
|
|
44297
|
+
let next = appendAttributeIfMissing(line, "options", optionsValue);
|
|
44298
|
+
if (fieldLabel) {
|
|
44299
|
+
next = appendAttributeIfMissing(next, "field", fieldLabel);
|
|
44300
|
+
}
|
|
44301
|
+
return next;
|
|
44302
|
+
});
|
|
43436
44303
|
}
|
|
43437
44304
|
return augmented;
|
|
43438
44305
|
} catch (err) {
|
|
@@ -43442,6 +44309,148 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
|
|
|
43442
44309
|
return yaml;
|
|
43443
44310
|
}
|
|
43444
44311
|
}
|
|
44312
|
+
async function enrichModalDialogElements(page, logger2) {
|
|
44313
|
+
try {
|
|
44314
|
+
const enrichedCount = await page.evaluate(() => {
|
|
44315
|
+
let count = 0;
|
|
44316
|
+
const MAX_ENRICHMENTS = 3;
|
|
44317
|
+
function shouldSkip(el) {
|
|
44318
|
+
if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") return true;
|
|
44319
|
+
if (el.getAttribute("data-enriched-dialog") === "true") return true;
|
|
44320
|
+
return false;
|
|
44321
|
+
}
|
|
44322
|
+
function isVisible(el) {
|
|
44323
|
+
const style = window.getComputedStyle(el);
|
|
44324
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
44325
|
+
if (el instanceof HTMLElement && el.offsetWidth === 0 && el.offsetHeight === 0) return false;
|
|
44326
|
+
return true;
|
|
44327
|
+
}
|
|
44328
|
+
function enrichAsDialog(el) {
|
|
44329
|
+
if (count >= MAX_ENRICHMENTS) return;
|
|
44330
|
+
if (shouldSkip(el)) return;
|
|
44331
|
+
if (!isVisible(el)) return;
|
|
44332
|
+
el.setAttribute("role", "dialog");
|
|
44333
|
+
const heading = el.querySelector("h1, h2, h3, h4, h5, h6");
|
|
44334
|
+
if (heading?.textContent?.trim()) {
|
|
44335
|
+
el.setAttribute("aria-label", heading.textContent.trim());
|
|
44336
|
+
}
|
|
44337
|
+
el.setAttribute("data-enriched-dialog", "true");
|
|
44338
|
+
count++;
|
|
44339
|
+
}
|
|
44340
|
+
function hasInteractiveContent(el) {
|
|
44341
|
+
return el.querySelector('input, textarea, select, button, a[href], [role="button"], [role="link"], [role="textbox"], [role="combobox"]') !== null;
|
|
44342
|
+
}
|
|
44343
|
+
const CLASS_PATTERNS = [
|
|
44344
|
+
/\bmodal(?![-_]?(backdrop|mask|overlay|fade|bg|background))\b/i,
|
|
44345
|
+
/\bdialog\b/i,
|
|
44346
|
+
/\bpopup\b/i,
|
|
44347
|
+
/\blightbox\b/i,
|
|
44348
|
+
/\bdrawer\b/i
|
|
44349
|
+
];
|
|
44350
|
+
const FRAMEWORK_SELECTORS = [
|
|
44351
|
+
".cdk-overlay-pane",
|
|
44352
|
+
".mat-dialog-container",
|
|
44353
|
+
".mat-mdc-dialog-container",
|
|
44354
|
+
".ui-dialog",
|
|
44355
|
+
".modal-dialog",
|
|
44356
|
+
".ant-modal-content",
|
|
44357
|
+
".el-dialog",
|
|
44358
|
+
".v-dialog",
|
|
44359
|
+
".p-dialog"
|
|
44360
|
+
];
|
|
44361
|
+
for (const selector of FRAMEWORK_SELECTORS) {
|
|
44362
|
+
if (count >= MAX_ENRICHMENTS) break;
|
|
44363
|
+
const els = document.querySelectorAll(selector);
|
|
44364
|
+
for (const el of els) {
|
|
44365
|
+
if (count >= MAX_ENRICHMENTS) break;
|
|
44366
|
+
if (hasInteractiveContent(el)) {
|
|
44367
|
+
enrichAsDialog(el);
|
|
44368
|
+
}
|
|
44369
|
+
}
|
|
44370
|
+
}
|
|
44371
|
+
if (count < MAX_ENRICHMENTS) {
|
|
44372
|
+
const allElements = document.querySelectorAll("*");
|
|
44373
|
+
for (const el of allElements) {
|
|
44374
|
+
if (count >= MAX_ENRICHMENTS) break;
|
|
44375
|
+
const className = el.className;
|
|
44376
|
+
if (typeof className !== "string") continue;
|
|
44377
|
+
if (CLASS_PATTERNS.some((pattern) => pattern.test(className))) {
|
|
44378
|
+
if (hasInteractiveContent(el)) {
|
|
44379
|
+
enrichAsDialog(el);
|
|
44380
|
+
}
|
|
44381
|
+
}
|
|
44382
|
+
}
|
|
44383
|
+
}
|
|
44384
|
+
if (count < MAX_ENRICHMENTS) {
|
|
44385
|
+
const allFixed = document.querySelectorAll("*");
|
|
44386
|
+
for (const el of allFixed) {
|
|
44387
|
+
if (count >= MAX_ENRICHMENTS) break;
|
|
44388
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
44389
|
+
const style = window.getComputedStyle(el);
|
|
44390
|
+
if (style.position !== "fixed" && style.position !== "absolute") continue;
|
|
44391
|
+
const rect = el.getBoundingClientRect();
|
|
44392
|
+
const viewportW = window.innerWidth;
|
|
44393
|
+
const viewportH = window.innerHeight;
|
|
44394
|
+
const coversViewport = rect.width >= viewportW * 0.8 && rect.height >= viewportH * 0.8;
|
|
44395
|
+
if (!coversViewport) continue;
|
|
44396
|
+
const bg = style.backgroundColor;
|
|
44397
|
+
const opacity = parseFloat(style.opacity);
|
|
44398
|
+
const isSemiTransparent = bg.includes("rgba") && !bg.includes("rgba(0, 0, 0, 0)") && bg.match(/,\s*([\d.]+)\)/) && parseFloat(bg.match(/,\s*([\d.]+)\)/)?.[1] || "1") < 1 || opacity < 1 && opacity > 0;
|
|
44399
|
+
if (!isSemiTransparent) continue;
|
|
44400
|
+
const nextSib = el.nextElementSibling;
|
|
44401
|
+
if (nextSib && nextSib instanceof HTMLElement && isVisible(nextSib) && hasInteractiveContent(nextSib)) {
|
|
44402
|
+
enrichAsDialog(nextSib);
|
|
44403
|
+
continue;
|
|
44404
|
+
}
|
|
44405
|
+
if (el.parentElement) {
|
|
44406
|
+
const backdropZ = parseInt(style.zIndex) || 0;
|
|
44407
|
+
for (const sibling of el.parentElement.children) {
|
|
44408
|
+
if (sibling === el || !(sibling instanceof HTMLElement)) continue;
|
|
44409
|
+
const sibStyle = window.getComputedStyle(sibling);
|
|
44410
|
+
const sibZ = parseInt(sibStyle.zIndex) || 0;
|
|
44411
|
+
if (sibZ > backdropZ && isVisible(sibling) && hasInteractiveContent(sibling)) {
|
|
44412
|
+
enrichAsDialog(sibling);
|
|
44413
|
+
break;
|
|
44414
|
+
}
|
|
44415
|
+
}
|
|
44416
|
+
}
|
|
44417
|
+
}
|
|
44418
|
+
}
|
|
44419
|
+
if (count < MAX_ENRICHMENTS) {
|
|
44420
|
+
const allElements = document.querySelectorAll("*");
|
|
44421
|
+
for (const el of allElements) {
|
|
44422
|
+
if (count >= MAX_ENRICHMENTS) break;
|
|
44423
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
44424
|
+
if (el.getAttribute("data-enriched-dialog") === "true") continue;
|
|
44425
|
+
if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") continue;
|
|
44426
|
+
const style = window.getComputedStyle(el);
|
|
44427
|
+
if (style.position !== "fixed" && style.position !== "absolute") continue;
|
|
44428
|
+
const zIndex = parseInt(style.zIndex) || 0;
|
|
44429
|
+
if (zIndex <= 100) continue;
|
|
44430
|
+
if (!isVisible(el)) continue;
|
|
44431
|
+
const rect = el.getBoundingClientRect();
|
|
44432
|
+
if (rect.width < 200 || rect.height < 200) continue;
|
|
44433
|
+
const viewportW = window.innerWidth;
|
|
44434
|
+
const viewportH = window.innerHeight;
|
|
44435
|
+
if (rect.width >= viewportW * 0.95 && rect.height >= viewportH * 0.95) continue;
|
|
44436
|
+
if (hasInteractiveContent(el)) {
|
|
44437
|
+
enrichAsDialog(el);
|
|
44438
|
+
}
|
|
44439
|
+
}
|
|
44440
|
+
}
|
|
44441
|
+
return count;
|
|
44442
|
+
});
|
|
44443
|
+
if (enrichedCount > 0) {
|
|
44444
|
+
logger2.debug("[DirectPlaywright] Enriched modal dialog elements", {
|
|
44445
|
+
count: enrichedCount
|
|
44446
|
+
});
|
|
44447
|
+
}
|
|
44448
|
+
} catch (err) {
|
|
44449
|
+
logger2.debug("[DirectPlaywright] Modal dialog enrichment failed", {
|
|
44450
|
+
error: errorMessage2(err)
|
|
44451
|
+
});
|
|
44452
|
+
}
|
|
44453
|
+
}
|
|
43445
44454
|
async function enrichInteractiveSVGElements(page, logger2) {
|
|
43446
44455
|
try {
|
|
43447
44456
|
const enrichedCount = await page.evaluate(() => {
|
|
@@ -43488,38 +44497,6 @@ async function enrichInteractiveSVGElements(page, logger2) {
|
|
|
43488
44497
|
}
|
|
43489
44498
|
}
|
|
43490
44499
|
|
|
43491
|
-
// ../browser-core/src/playwright-client/download-utils.ts
|
|
43492
|
-
import * as path2 from "path";
|
|
43493
|
-
function formatFileSize(bytes) {
|
|
43494
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
43495
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
43496
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
43497
|
-
}
|
|
43498
|
-
function guessMimeType(filename) {
|
|
43499
|
-
const ext = path2.extname(filename).toLowerCase();
|
|
43500
|
-
const mimeMap = {
|
|
43501
|
-
".pdf": "application/pdf",
|
|
43502
|
-
".txt": "text/plain",
|
|
43503
|
-
".csv": "text/csv",
|
|
43504
|
-
".json": "application/json",
|
|
43505
|
-
".xml": "application/xml",
|
|
43506
|
-
".html": "text/html",
|
|
43507
|
-
".htm": "text/html",
|
|
43508
|
-
".md": "text/markdown",
|
|
43509
|
-
".png": "image/png",
|
|
43510
|
-
".jpg": "image/jpeg",
|
|
43511
|
-
".jpeg": "image/jpeg",
|
|
43512
|
-
".gif": "image/gif",
|
|
43513
|
-
".svg": "image/svg+xml",
|
|
43514
|
-
".zip": "application/zip",
|
|
43515
|
-
".doc": "application/msword",
|
|
43516
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
43517
|
-
".xls": "application/vnd.ms-excel",
|
|
43518
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
43519
|
-
};
|
|
43520
|
-
return mimeMap[ext] ?? "application/octet-stream";
|
|
43521
|
-
}
|
|
43522
|
-
|
|
43523
44500
|
// ../browser-core/src/playwright-client/response-format.ts
|
|
43524
44501
|
var TOAST_SNAPSHOT_TTL = 5;
|
|
43525
44502
|
function formatMcpResponse(input) {
|
|
@@ -43578,10 +44555,172 @@ function formatMcpResponse(input) {
|
|
|
43578
44555
|
};
|
|
43579
44556
|
}
|
|
43580
44557
|
|
|
44558
|
+
// ../browser-core/src/playwright-client/launch-retry.ts
|
|
44559
|
+
var TRANSIENT_BROWSER_LAUNCH_PATTERNS = [
|
|
44560
|
+
/failed to connect/i,
|
|
44561
|
+
/syscall["':\s]+connect/i,
|
|
44562
|
+
/\bENOENT\b/i,
|
|
44563
|
+
/browser has been closed/i,
|
|
44564
|
+
/target page, context or browser has been closed/i,
|
|
44565
|
+
/failed to launch/i,
|
|
44566
|
+
/\bSIGKILL\b/i,
|
|
44567
|
+
/process was terminated/i
|
|
44568
|
+
];
|
|
44569
|
+
function formatBrowserLaunchError(error) {
|
|
44570
|
+
if (error instanceof Error) {
|
|
44571
|
+
const extra = error;
|
|
44572
|
+
const parts = [error.message];
|
|
44573
|
+
if (extra.code) parts.push(`code=${extra.code}`);
|
|
44574
|
+
if (typeof extra.errno === "number") parts.push(`errno=${extra.errno}`);
|
|
44575
|
+
if (extra.syscall) parts.push(`syscall=${extra.syscall}`);
|
|
44576
|
+
if (extra.cause) parts.push(`cause=${formatBrowserLaunchError(extra.cause)}`);
|
|
44577
|
+
return parts.join(" ");
|
|
44578
|
+
}
|
|
44579
|
+
return String(error);
|
|
44580
|
+
}
|
|
44581
|
+
function isTransientBrowserLaunchError(error) {
|
|
44582
|
+
const text = formatBrowserLaunchError(error);
|
|
44583
|
+
return TRANSIENT_BROWSER_LAUNCH_PATTERNS.some((pattern) => pattern.test(text));
|
|
44584
|
+
}
|
|
44585
|
+
async function withBrowserLaunchRetry(operation, options) {
|
|
44586
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
44587
|
+
const initialDelayMs = options.initialDelayMs ?? 150;
|
|
44588
|
+
let lastError;
|
|
44589
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
44590
|
+
try {
|
|
44591
|
+
return await operation(attempt);
|
|
44592
|
+
} catch (error) {
|
|
44593
|
+
lastError = error;
|
|
44594
|
+
if (attempt >= maxAttempts || !isTransientBrowserLaunchError(error)) {
|
|
44595
|
+
throw error;
|
|
44596
|
+
}
|
|
44597
|
+
const delayMs = initialDelayMs * 2 ** (attempt - 1);
|
|
44598
|
+
options.logger.warn("[DirectPlaywright] Browser launch failed transiently, retrying", {
|
|
44599
|
+
label: options.label,
|
|
44600
|
+
attempt,
|
|
44601
|
+
nextAttempt: attempt + 1,
|
|
44602
|
+
delayMs,
|
|
44603
|
+
error: formatBrowserLaunchError(error)
|
|
44604
|
+
});
|
|
44605
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
44606
|
+
}
|
|
44607
|
+
}
|
|
44608
|
+
throw lastError;
|
|
44609
|
+
}
|
|
44610
|
+
|
|
44611
|
+
// ../browser-core/src/playwright-client/test-browser-lock.ts
|
|
44612
|
+
import * as fs3 from "fs/promises";
|
|
44613
|
+
import * as path3 from "path";
|
|
44614
|
+
var TEST_BROWSER_LOCK_DIR = path3.join(getCanaryTmpDir(), "playwright-test-browser-lock");
|
|
44615
|
+
var TEST_BROWSER_LOCK_METADATA_PATH = path3.join(TEST_BROWSER_LOCK_DIR, "owner.json");
|
|
44616
|
+
var TEST_BROWSER_LOCK_POLL_MS = 100;
|
|
44617
|
+
var TEST_BROWSER_LOCK_TIMEOUT_MS = 12e4;
|
|
44618
|
+
var TEST_BROWSER_LOCK_STALE_MS = 15 * 6e4;
|
|
44619
|
+
function shouldUseTestBrowserLock(options) {
|
|
44620
|
+
return process.env.NODE_ENV === "test" && !options.browserLease && !options.cdpUrl && options.browserMode !== "headed";
|
|
44621
|
+
}
|
|
44622
|
+
async function acquireTestBrowserLock(logger2) {
|
|
44623
|
+
const startedAt = Date.now();
|
|
44624
|
+
const owner = {
|
|
44625
|
+
pid: process.pid,
|
|
44626
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44627
|
+
};
|
|
44628
|
+
while (true) {
|
|
44629
|
+
try {
|
|
44630
|
+
await fs3.mkdir(TEST_BROWSER_LOCK_DIR);
|
|
44631
|
+
await fs3.writeFile(TEST_BROWSER_LOCK_METADATA_PATH, JSON.stringify(owner), "utf8");
|
|
44632
|
+
logger2.debug("[DirectPlaywright] Acquired test browser lock", {
|
|
44633
|
+
lockDir: TEST_BROWSER_LOCK_DIR,
|
|
44634
|
+
pid: process.pid
|
|
44635
|
+
});
|
|
44636
|
+
let released = false;
|
|
44637
|
+
return async () => {
|
|
44638
|
+
if (released) return;
|
|
44639
|
+
released = true;
|
|
44640
|
+
try {
|
|
44641
|
+
await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
|
|
44642
|
+
logger2.debug("[DirectPlaywright] Released test browser lock", {
|
|
44643
|
+
lockDir: TEST_BROWSER_LOCK_DIR,
|
|
44644
|
+
pid: process.pid
|
|
44645
|
+
});
|
|
44646
|
+
} catch (error) {
|
|
44647
|
+
logger2.warn("[DirectPlaywright] Failed to release test browser lock", {
|
|
44648
|
+
lockDir: TEST_BROWSER_LOCK_DIR,
|
|
44649
|
+
pid: process.pid,
|
|
44650
|
+
error: error instanceof Error ? error.message : String(error)
|
|
44651
|
+
});
|
|
44652
|
+
}
|
|
44653
|
+
};
|
|
44654
|
+
} catch (error) {
|
|
44655
|
+
const code = error.code;
|
|
44656
|
+
if (code !== "EEXIST") {
|
|
44657
|
+
throw error;
|
|
44658
|
+
}
|
|
44659
|
+
await cleanupStaleTestBrowserLock(logger2);
|
|
44660
|
+
if (Date.now() - startedAt >= TEST_BROWSER_LOCK_TIMEOUT_MS) {
|
|
44661
|
+
throw new Error(
|
|
44662
|
+
`Timed out acquiring test browser lock after ${TEST_BROWSER_LOCK_TIMEOUT_MS}ms`
|
|
44663
|
+
);
|
|
44664
|
+
}
|
|
44665
|
+
await delay(TEST_BROWSER_LOCK_POLL_MS);
|
|
44666
|
+
}
|
|
44667
|
+
}
|
|
44668
|
+
}
|
|
44669
|
+
async function cleanupStaleTestBrowserLock(logger2) {
|
|
44670
|
+
const metadata = await readLockMetadata();
|
|
44671
|
+
if (!metadata) {
|
|
44672
|
+
const orphanedLockStats = await fs3.stat(TEST_BROWSER_LOCK_DIR).catch(() => null);
|
|
44673
|
+
if (!orphanedLockStats) {
|
|
44674
|
+
return;
|
|
44675
|
+
}
|
|
44676
|
+
const orphanedAgeMs = Date.now() - orphanedLockStats.mtimeMs;
|
|
44677
|
+
if (orphanedAgeMs < 1e3) {
|
|
44678
|
+
return;
|
|
44679
|
+
}
|
|
44680
|
+
logger2.warn("[DirectPlaywright] Removing orphaned test browser lock", {
|
|
44681
|
+
lockDir: TEST_BROWSER_LOCK_DIR,
|
|
44682
|
+
ageMs: orphanedAgeMs
|
|
44683
|
+
});
|
|
44684
|
+
await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
|
|
44685
|
+
return;
|
|
44686
|
+
}
|
|
44687
|
+
const ageMs = Date.now() - Date.parse(metadata.acquiredAt);
|
|
44688
|
+
if (ageMs < TEST_BROWSER_LOCK_STALE_MS && isProcessAlive(metadata.pid)) {
|
|
44689
|
+
return;
|
|
44690
|
+
}
|
|
44691
|
+
logger2.warn("[DirectPlaywright] Removing stale test browser lock", {
|
|
44692
|
+
lockDir: TEST_BROWSER_LOCK_DIR,
|
|
44693
|
+
ownerPid: metadata.pid,
|
|
44694
|
+
ageMs
|
|
44695
|
+
});
|
|
44696
|
+
await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
|
|
44697
|
+
}
|
|
44698
|
+
async function readLockMetadata() {
|
|
44699
|
+
try {
|
|
44700
|
+
const raw = await fs3.readFile(TEST_BROWSER_LOCK_METADATA_PATH, "utf8");
|
|
44701
|
+
return JSON.parse(raw);
|
|
44702
|
+
} catch {
|
|
44703
|
+
return null;
|
|
44704
|
+
}
|
|
44705
|
+
}
|
|
44706
|
+
function isProcessAlive(pid) {
|
|
44707
|
+
try {
|
|
44708
|
+
process.kill(pid, 0);
|
|
44709
|
+
return true;
|
|
44710
|
+
} catch (error) {
|
|
44711
|
+
const code = error.code;
|
|
44712
|
+
return code !== "ESRCH";
|
|
44713
|
+
}
|
|
44714
|
+
}
|
|
44715
|
+
function delay(ms) {
|
|
44716
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
44717
|
+
}
|
|
44718
|
+
|
|
43581
44719
|
// ../browser-core/src/playwright-client.ts
|
|
43582
44720
|
var PlaywrightClient = class _PlaywrightClient {
|
|
43583
44721
|
disconnectInProgress = false;
|
|
43584
44722
|
browserLease;
|
|
44723
|
+
releaseTestBrowserLock = null;
|
|
43585
44724
|
logger;
|
|
43586
44725
|
screencastEncoderFactory;
|
|
43587
44726
|
tmpBaseDir;
|
|
@@ -43707,7 +44846,8 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43707
44846
|
extensions,
|
|
43708
44847
|
highlightInteractions,
|
|
43709
44848
|
cursorOverlay,
|
|
43710
|
-
extraHTTPHeaders
|
|
44849
|
+
extraHTTPHeaders,
|
|
44850
|
+
httpCredentials
|
|
43711
44851
|
} = options;
|
|
43712
44852
|
const resolvedBrowserMode = browserMode ?? "headless";
|
|
43713
44853
|
const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
|
|
@@ -43720,7 +44860,8 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43720
44860
|
extensions,
|
|
43721
44861
|
highlightInteractions,
|
|
43722
44862
|
cursorOverlay,
|
|
43723
|
-
extraHTTPHeaders
|
|
44863
|
+
extraHTTPHeaders,
|
|
44864
|
+
httpCredentials
|
|
43724
44865
|
};
|
|
43725
44866
|
this.storageStatePath = storageStatePath;
|
|
43726
44867
|
this.pageClosedIntentionally = false;
|
|
@@ -43743,8 +44884,8 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43743
44884
|
const cursorOpts = typeof cursorOverlay === "object" ? cursorOverlay : {};
|
|
43744
44885
|
this.cursorOverlay = new CursorOverlay({ ...cursorOpts, enabled: cursorEnabled });
|
|
43745
44886
|
}
|
|
43746
|
-
this._downloadDir =
|
|
43747
|
-
await
|
|
44887
|
+
this._downloadDir = path4.join(this.tmpBaseDir, `playwright-downloads-${Date.now()}`);
|
|
44888
|
+
await fs4.mkdir(this._downloadDir, { recursive: true });
|
|
43748
44889
|
this.logger.info("[DirectPlaywright] Launching browser", {
|
|
43749
44890
|
browserMode: resolvedBrowserMode,
|
|
43750
44891
|
hasStorageState: !!storageStatePath,
|
|
@@ -43755,82 +44896,107 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43755
44896
|
highlightEnabled: this.highlightEnabled,
|
|
43756
44897
|
downloadDir: this._downloadDir
|
|
43757
44898
|
});
|
|
43758
|
-
|
|
43759
|
-
|
|
43760
|
-
|
|
43761
|
-
|
|
43762
|
-
|
|
43763
|
-
|
|
43764
|
-
|
|
43765
|
-
|
|
43766
|
-
|
|
43767
|
-
|
|
43768
|
-
|
|
43769
|
-
|
|
43770
|
-
|
|
43771
|
-
|
|
43772
|
-
|
|
43773
|
-
|
|
43774
|
-
|
|
43775
|
-
|
|
43776
|
-
|
|
43777
|
-
|
|
44899
|
+
try {
|
|
44900
|
+
if (shouldUseTestBrowserLock({
|
|
44901
|
+
browserLease: !!this.browserLease,
|
|
44902
|
+
cdpUrl,
|
|
44903
|
+
browserMode: resolvedBrowserMode
|
|
44904
|
+
}) && !this.releaseTestBrowserLock) {
|
|
44905
|
+
this.releaseTestBrowserLock = await acquireTestBrowserLock(this.logger);
|
|
44906
|
+
}
|
|
44907
|
+
if (cdpUrl) {
|
|
44908
|
+
this.logger.info("[DirectPlaywright] Connecting via CDP", { cdpUrl });
|
|
44909
|
+
this.browser = await this.withOperationTimeout(
|
|
44910
|
+
playwrightChromium.connectOverCDP(cdpUrl),
|
|
44911
|
+
3e4,
|
|
44912
|
+
"[DirectPlaywright] connectOverCDP"
|
|
44913
|
+
);
|
|
44914
|
+
const existingContexts = this.browser.contexts();
|
|
44915
|
+
this.context = existingContexts[0] ?? await this.browser.newContext({ viewport: resolvedViewport });
|
|
44916
|
+
} else if (this.browserLease && !this.stealthEnabled && !this.extensionsEnabled && resolvedBrowserMode !== "headed") {
|
|
44917
|
+
const videoConfig2 = this.resolveVideoConfig(recordVideo);
|
|
44918
|
+
const contextOptions = {
|
|
44919
|
+
viewport: resolvedViewport,
|
|
44920
|
+
acceptDownloads: true
|
|
44921
|
+
};
|
|
44922
|
+
if (storageStatePath) {
|
|
44923
|
+
try {
|
|
44924
|
+
await fs4.access(storageStatePath);
|
|
44925
|
+
contextOptions.storageState = storageStatePath;
|
|
44926
|
+
} catch {
|
|
44927
|
+
}
|
|
43778
44928
|
}
|
|
43779
|
-
|
|
43780
|
-
|
|
43781
|
-
|
|
43782
|
-
|
|
43783
|
-
|
|
43784
|
-
|
|
43785
|
-
|
|
43786
|
-
|
|
43787
|
-
|
|
43788
|
-
|
|
43789
|
-
|
|
43790
|
-
|
|
43791
|
-
});
|
|
43792
|
-
this.context = await this.withOperationTimeout(
|
|
43793
|
-
this.browserLease.createContext(contextOptions),
|
|
43794
|
-
3e4,
|
|
43795
|
-
"[DirectPlaywright] browserLease.createContext"
|
|
43796
|
-
);
|
|
43797
|
-
this.browser = null;
|
|
43798
|
-
} else {
|
|
43799
|
-
if (this.browserLease) {
|
|
43800
|
-
this.logger.info("[DirectPlaywright] Releasing pool lease for direct launch", {
|
|
44929
|
+
if (videoConfig2) {
|
|
44930
|
+
await fs4.mkdir(videoConfig2.dir, { recursive: true });
|
|
44931
|
+
contextOptions.recordVideo = videoConfig2;
|
|
44932
|
+
this.videoDir = videoConfig2.dir;
|
|
44933
|
+
}
|
|
44934
|
+
if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
|
|
44935
|
+
contextOptions.extraHTTPHeaders = extraHTTPHeaders;
|
|
44936
|
+
}
|
|
44937
|
+
if (httpCredentials) {
|
|
44938
|
+
contextOptions.httpCredentials = httpCredentials;
|
|
44939
|
+
}
|
|
44940
|
+
this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
|
|
43801
44941
|
leaseId: this.browserLease.leaseId,
|
|
43802
|
-
|
|
44942
|
+
browserId: this.browserLease.browserId
|
|
43803
44943
|
});
|
|
43804
|
-
await this.
|
|
43805
|
-
|
|
44944
|
+
this.context = await this.withOperationTimeout(
|
|
44945
|
+
this.browserLease.createContext(contextOptions),
|
|
44946
|
+
3e4,
|
|
44947
|
+
"[DirectPlaywright] browserLease.createContext"
|
|
44948
|
+
);
|
|
44949
|
+
this.browser = null;
|
|
44950
|
+
} else {
|
|
44951
|
+
if (this.browserLease) {
|
|
44952
|
+
this.logger.info("[DirectPlaywright] Releasing pool lease for direct launch", {
|
|
44953
|
+
leaseId: this.browserLease.leaseId,
|
|
44954
|
+
reason: resolvedBrowserMode === "headed" ? "headed-mode" : "requires-dedicated-browser"
|
|
44955
|
+
});
|
|
44956
|
+
await this.browserLease.release();
|
|
44957
|
+
this.browserLease = null;
|
|
44958
|
+
}
|
|
44959
|
+
const { browser, context } = await withBrowserLaunchRetry(
|
|
44960
|
+
() => this.launchBrowser({
|
|
44961
|
+
browserMode: resolvedBrowserMode,
|
|
44962
|
+
storageStatePath,
|
|
44963
|
+
viewport,
|
|
44964
|
+
recordVideo,
|
|
44965
|
+
stealth,
|
|
44966
|
+
extensions,
|
|
44967
|
+
extraHTTPHeaders,
|
|
44968
|
+
httpCredentials
|
|
44969
|
+
}),
|
|
44970
|
+
{
|
|
44971
|
+
logger: this.logger,
|
|
44972
|
+
label: "direct-launch"
|
|
44973
|
+
}
|
|
44974
|
+
);
|
|
44975
|
+
this.browser = browser;
|
|
44976
|
+
this.context = context;
|
|
43806
44977
|
}
|
|
43807
|
-
|
|
44978
|
+
this.attachContextListeners(this.context);
|
|
44979
|
+
if (this.browser) {
|
|
44980
|
+
this.attachBrowserListeners(this.browser);
|
|
44981
|
+
}
|
|
44982
|
+
await this.createPageWithListeners(this.context, "initial_connect");
|
|
44983
|
+
const now = Date.now();
|
|
44984
|
+
const initialPageId = this._assignPageId(this.page);
|
|
44985
|
+
this._pageCreationTimes.set(initialPageId, now);
|
|
44986
|
+
this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
|
|
44987
|
+
if (storageStatePath) {
|
|
44988
|
+
await this.restoreIndexedDBFromStorageState(storageStatePath);
|
|
44989
|
+
}
|
|
44990
|
+
this.logger.info("[DirectPlaywright] Browser connected successfully", {
|
|
43808
44991
|
browserMode: resolvedBrowserMode,
|
|
43809
|
-
|
|
43810
|
-
|
|
43811
|
-
|
|
43812
|
-
stealth,
|
|
43813
|
-
extensions,
|
|
43814
|
-
extraHTTPHeaders
|
|
44992
|
+
stealthEnabled: this.stealthEnabled,
|
|
44993
|
+
extensionsEnabled: this.extensionsEnabled,
|
|
44994
|
+
pooled: !!this.browserLease
|
|
43815
44995
|
});
|
|
43816
|
-
|
|
43817
|
-
this.
|
|
43818
|
-
|
|
43819
|
-
this.attachContextListeners(this.context);
|
|
43820
|
-
if (this.browser) {
|
|
43821
|
-
this.attachBrowserListeners(this.browser);
|
|
44996
|
+
} catch (error) {
|
|
44997
|
+
await this.cleanupFailedConnect(error);
|
|
44998
|
+
throw error;
|
|
43822
44999
|
}
|
|
43823
|
-
await this.createPageWithListeners(this.context, "initial_connect");
|
|
43824
|
-
const now = Date.now();
|
|
43825
|
-
const initialPageId = this._assignPageId(this.page);
|
|
43826
|
-
this._pageCreationTimes.set(initialPageId, now);
|
|
43827
|
-
this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
|
|
43828
|
-
this.logger.info("[DirectPlaywright] Browser connected successfully", {
|
|
43829
|
-
browserMode: resolvedBrowserMode,
|
|
43830
|
-
stealthEnabled: this.stealthEnabled,
|
|
43831
|
-
extensionsEnabled: this.extensionsEnabled,
|
|
43832
|
-
pooled: !!this.browserLease
|
|
43833
|
-
});
|
|
43834
45000
|
}
|
|
43835
45001
|
supportsContextSwap() {
|
|
43836
45002
|
return !this.extensionsEnabled && (!!this.browser || !!this.browserLease);
|
|
@@ -43900,9 +45066,16 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43900
45066
|
headerNames: Object.keys(mergedHeaders)
|
|
43901
45067
|
});
|
|
43902
45068
|
}
|
|
45069
|
+
const resolvedHttpCredentials = options.httpCredentials ?? this.lastConnectOptions?.httpCredentials;
|
|
45070
|
+
if (resolvedHttpCredentials) {
|
|
45071
|
+
contextOptions.httpCredentials = resolvedHttpCredentials;
|
|
45072
|
+
if (this.lastConnectOptions) {
|
|
45073
|
+
this.lastConnectOptions.httpCredentials = resolvedHttpCredentials;
|
|
45074
|
+
}
|
|
45075
|
+
}
|
|
43903
45076
|
if (nextStorageStatePath) {
|
|
43904
45077
|
try {
|
|
43905
|
-
await
|
|
45078
|
+
await fs4.access(nextStorageStatePath);
|
|
43906
45079
|
contextOptions.storageState = nextStorageStatePath;
|
|
43907
45080
|
this.logger.debug("[DirectPlaywright] Loading storage state for swapped context", {
|
|
43908
45081
|
storageStatePath: nextStorageStatePath
|
|
@@ -43923,7 +45096,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43923
45096
|
this._pageCreationTimes.clear();
|
|
43924
45097
|
this._closedPageVideos = [];
|
|
43925
45098
|
if (videoConfig) {
|
|
43926
|
-
await
|
|
45099
|
+
await fs4.mkdir(videoConfig.dir, { recursive: true });
|
|
43927
45100
|
contextOptions.recordVideo = videoConfig;
|
|
43928
45101
|
this.videoDir = videoConfig.dir;
|
|
43929
45102
|
this.highlightEnabled = true;
|
|
@@ -43951,6 +45124,9 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43951
45124
|
const swapPageId = this._assignPageId(this.page);
|
|
43952
45125
|
this._pageCreationTimes.set(swapPageId, swapNow);
|
|
43953
45126
|
this._tabFocusLog.push({ pageIndex: swapPageId, timestamp: swapNow });
|
|
45127
|
+
if (nextStorageStatePath) {
|
|
45128
|
+
await this.restoreIndexedDBFromStorageState(nextStorageStatePath);
|
|
45129
|
+
}
|
|
43954
45130
|
if (wasScreencasting && this.page && this.screencastHandler) {
|
|
43955
45131
|
await this.startScreencast(this.screencastHandler);
|
|
43956
45132
|
}
|
|
@@ -43969,7 +45145,8 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43969
45145
|
recordVideo,
|
|
43970
45146
|
stealth,
|
|
43971
45147
|
extensions,
|
|
43972
|
-
extraHTTPHeaders
|
|
45148
|
+
extraHTTPHeaders,
|
|
45149
|
+
httpCredentials
|
|
43973
45150
|
} = options;
|
|
43974
45151
|
const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
|
|
43975
45152
|
const useStealthMode = stealth === true || typeof stealth === "object" && stealth.enabled;
|
|
@@ -43980,6 +45157,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43980
45157
|
this.logger.debug("[DirectPlaywright] Stealth plugin applied");
|
|
43981
45158
|
}
|
|
43982
45159
|
const chromiumLauncher = useStealthMode ? chromium : playwrightChromium;
|
|
45160
|
+
const executablePath = await _PlaywrightClient.resolveChromiumExecutable(this.logger);
|
|
43983
45161
|
const args = ["--no-sandbox", "--disable-dev-shm-usage"];
|
|
43984
45162
|
if (browserMode === "headed" && process.platform === "darwin") {
|
|
43985
45163
|
args.push(
|
|
@@ -43999,9 +45177,13 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
43999
45177
|
headerNames: Object.keys(extraHTTPHeaders)
|
|
44000
45178
|
});
|
|
44001
45179
|
}
|
|
45180
|
+
if (httpCredentials) {
|
|
45181
|
+
contextOptions.httpCredentials = httpCredentials;
|
|
45182
|
+
this.logger.info("[DirectPlaywright] HTTP Basic Authentication configured");
|
|
45183
|
+
}
|
|
44002
45184
|
if (storageStatePath) {
|
|
44003
45185
|
try {
|
|
44004
|
-
await
|
|
45186
|
+
await fs4.access(storageStatePath);
|
|
44005
45187
|
contextOptions.storageState = storageStatePath;
|
|
44006
45188
|
this.logger.debug("[DirectPlaywright] Loading storage state from file", {
|
|
44007
45189
|
storageStatePath
|
|
@@ -44013,7 +45195,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44013
45195
|
}
|
|
44014
45196
|
}
|
|
44015
45197
|
if (videoConfig) {
|
|
44016
|
-
await
|
|
45198
|
+
await fs4.mkdir(videoConfig.dir, { recursive: true });
|
|
44017
45199
|
contextOptions.recordVideo = videoConfig;
|
|
44018
45200
|
this.videoDir = videoConfig.dir;
|
|
44019
45201
|
this.logger.info("[DirectPlaywright] Video recording enabled", {
|
|
@@ -44022,9 +45204,9 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44022
45204
|
});
|
|
44023
45205
|
}
|
|
44024
45206
|
if (hasExtensions) {
|
|
44025
|
-
this.userDataDir = extensions.userDataDir ||
|
|
45207
|
+
this.userDataDir = extensions.userDataDir || path4.join(this.tmpBaseDir, `playwright-userdata-${Date.now()}`);
|
|
44026
45208
|
this.shouldCleanupUserDataDir = !extensions.persistUserData;
|
|
44027
|
-
await
|
|
45209
|
+
await fs4.mkdir(this.userDataDir, { recursive: true });
|
|
44028
45210
|
if (browserMode === "headless") {
|
|
44029
45211
|
args.push("--headless=new");
|
|
44030
45212
|
}
|
|
@@ -44040,6 +45222,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44040
45222
|
headless: false,
|
|
44041
45223
|
// We use --headless=new in args for extension support
|
|
44042
45224
|
args,
|
|
45225
|
+
...executablePath ? { executablePath } : {},
|
|
44043
45226
|
...contextOptions
|
|
44044
45227
|
}),
|
|
44045
45228
|
3e4,
|
|
@@ -44050,7 +45233,8 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44050
45233
|
const browser = await this.withOperationTimeout(
|
|
44051
45234
|
chromiumLauncher.launch({
|
|
44052
45235
|
headless: browserMode === "headless",
|
|
44053
|
-
args
|
|
45236
|
+
args,
|
|
45237
|
+
...executablePath ? { executablePath } : {}
|
|
44054
45238
|
}),
|
|
44055
45239
|
3e4,
|
|
44056
45240
|
"[DirectPlaywright] chromium.launch"
|
|
@@ -44062,6 +45246,114 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44062
45246
|
);
|
|
44063
45247
|
return { browser, context };
|
|
44064
45248
|
}
|
|
45249
|
+
/**
|
|
45250
|
+
* Read the storage state file, check for IndexedDB data, and restore it.
|
|
45251
|
+
* IndexedDB is origin-scoped, so we navigate to the appropriate origin
|
|
45252
|
+
* before writing data, then navigate back.
|
|
45253
|
+
*/
|
|
45254
|
+
async restoreIndexedDBFromStorageState(storageStatePath) {
|
|
45255
|
+
try {
|
|
45256
|
+
const raw = await fs4.readFile(storageStatePath, "utf-8");
|
|
45257
|
+
const parsed = JSON.parse(raw);
|
|
45258
|
+
if (!parsed.indexedDB || !parsed.indexedDB.databases || parsed.indexedDB.databases.length === 0) {
|
|
45259
|
+
return;
|
|
45260
|
+
}
|
|
45261
|
+
const page = this.page;
|
|
45262
|
+
if (!page || page.isClosed()) return;
|
|
45263
|
+
let targetOrigin = null;
|
|
45264
|
+
if (parsed.origins && parsed.origins.length > 0) {
|
|
45265
|
+
targetOrigin = parsed.origins[0].origin;
|
|
45266
|
+
}
|
|
45267
|
+
if (!targetOrigin) {
|
|
45268
|
+
const cookies = parsed.cookies;
|
|
45269
|
+
if (cookies && cookies.length > 0) {
|
|
45270
|
+
const domain = cookies[0].domain.replace(/^\./, "");
|
|
45271
|
+
targetOrigin = `https://${domain}`;
|
|
45272
|
+
}
|
|
45273
|
+
}
|
|
45274
|
+
if (!targetOrigin) {
|
|
45275
|
+
this.logger.debug("[DirectPlaywright] No origin found for IndexedDB restoration, skipping");
|
|
45276
|
+
return;
|
|
45277
|
+
}
|
|
45278
|
+
this.logger.debug("[DirectPlaywright] Restoring IndexedDB data", {
|
|
45279
|
+
targetOrigin,
|
|
45280
|
+
databaseCount: parsed.indexedDB.databases.length,
|
|
45281
|
+
databaseNames: parsed.indexedDB.databases.map((db) => db.name)
|
|
45282
|
+
});
|
|
45283
|
+
await page.goto(targetOrigin, { waitUntil: "commit", timeout: 15e3 });
|
|
45284
|
+
await restoreIndexedDB(page, parsed.indexedDB);
|
|
45285
|
+
this.logger.info("[DirectPlaywright] IndexedDB data restored successfully", {
|
|
45286
|
+
databaseCount: parsed.indexedDB.databases.length
|
|
45287
|
+
});
|
|
45288
|
+
} catch (err) {
|
|
45289
|
+
this.logger.warn("[DirectPlaywright] Failed to restore IndexedDB from storage state", {
|
|
45290
|
+
error: err instanceof Error ? err.message : String(err),
|
|
45291
|
+
storageStatePath
|
|
45292
|
+
});
|
|
45293
|
+
}
|
|
45294
|
+
}
|
|
45295
|
+
/**
|
|
45296
|
+
* Resolve a working Chromium executable path, handling version mismatches
|
|
45297
|
+
* between the Playwright version bundled in this package and the browser
|
|
45298
|
+
* binaries installed on the system.
|
|
45299
|
+
*
|
|
45300
|
+
* When the exact expected binary doesn't exist (e.g. Playwright expects
|
|
45301
|
+
* chromium-1191 but only chromium-1194 is installed), this finds the
|
|
45302
|
+
* newest available chromium binary as a fallback.
|
|
45303
|
+
*/
|
|
45304
|
+
static async resolveChromiumExecutable(logger2) {
|
|
45305
|
+
const defaultPath = playwrightChromium.executablePath();
|
|
45306
|
+
try {
|
|
45307
|
+
await fs4.access(defaultPath);
|
|
45308
|
+
return void 0;
|
|
45309
|
+
} catch {
|
|
45310
|
+
}
|
|
45311
|
+
const parts = defaultPath.split(path4.sep);
|
|
45312
|
+
const msPlaywrightIdx = parts.findIndex((p) => p === "ms-playwright");
|
|
45313
|
+
if (msPlaywrightIdx === -1) {
|
|
45314
|
+
logger2.warn("[DirectPlaywright] Cannot locate ms-playwright cache directory", {
|
|
45315
|
+
defaultPath
|
|
45316
|
+
});
|
|
45317
|
+
return void 0;
|
|
45318
|
+
}
|
|
45319
|
+
const cacheDir = parts.slice(0, msPlaywrightIdx + 1).join(path4.sep);
|
|
45320
|
+
const relativeBinaryPath = parts.slice(msPlaywrightIdx + 2).join(path4.sep);
|
|
45321
|
+
try {
|
|
45322
|
+
const entries = await fs4.readdir(cacheDir);
|
|
45323
|
+
const chromiumDirs = entries.filter((e) => e.startsWith("chromium-") && !e.includes("headless")).sort((a, b) => {
|
|
45324
|
+
const revA = parseInt(a.split("-")[1] ?? "0", 10);
|
|
45325
|
+
const revB = parseInt(b.split("-")[1] ?? "0", 10);
|
|
45326
|
+
return revB - revA;
|
|
45327
|
+
});
|
|
45328
|
+
for (const dir of chromiumDirs) {
|
|
45329
|
+
const candidate = path4.join(cacheDir, dir, relativeBinaryPath);
|
|
45330
|
+
try {
|
|
45331
|
+
await fs4.access(candidate);
|
|
45332
|
+
logger2.warn(
|
|
45333
|
+
"[DirectPlaywright] Using fallback Chromium binary (version mismatch)",
|
|
45334
|
+
{
|
|
45335
|
+
expected: defaultPath,
|
|
45336
|
+
using: candidate
|
|
45337
|
+
}
|
|
45338
|
+
);
|
|
45339
|
+
return candidate;
|
|
45340
|
+
} catch {
|
|
45341
|
+
continue;
|
|
45342
|
+
}
|
|
45343
|
+
}
|
|
45344
|
+
logger2.warn("[DirectPlaywright] No compatible Chromium binary found", {
|
|
45345
|
+
cacheDir,
|
|
45346
|
+
candidates: chromiumDirs,
|
|
45347
|
+
expectedRelativePath: relativeBinaryPath
|
|
45348
|
+
});
|
|
45349
|
+
} catch (err) {
|
|
45350
|
+
logger2.warn("[DirectPlaywright] Failed to scan browser cache directory", {
|
|
45351
|
+
cacheDir,
|
|
45352
|
+
error: String(err)
|
|
45353
|
+
});
|
|
45354
|
+
}
|
|
45355
|
+
return void 0;
|
|
45356
|
+
}
|
|
44065
45357
|
async disconnect() {
|
|
44066
45358
|
this.logger.info("[DirectPlaywright] Disconnecting");
|
|
44067
45359
|
this.disconnectInProgress = true;
|
|
@@ -44121,7 +45413,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44121
45413
|
this.lastSnapshotYaml = "";
|
|
44122
45414
|
if (this._downloadDir) {
|
|
44123
45415
|
try {
|
|
44124
|
-
await
|
|
45416
|
+
await fs4.rm(this._downloadDir, { recursive: true, force: true });
|
|
44125
45417
|
this.logger.debug("[DirectPlaywright] Cleaned up download directory", {
|
|
44126
45418
|
downloadDir: this._downloadDir
|
|
44127
45419
|
});
|
|
@@ -44133,12 +45425,26 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44133
45425
|
}
|
|
44134
45426
|
this._downloadDir = null;
|
|
44135
45427
|
}
|
|
45428
|
+
if (this.traceDir) {
|
|
45429
|
+
try {
|
|
45430
|
+
await fs4.rm(this.traceDir, { recursive: true, force: true });
|
|
45431
|
+
this.logger.debug("[DirectPlaywright] Cleaned up trace directory", {
|
|
45432
|
+
traceDir: this.traceDir
|
|
45433
|
+
});
|
|
45434
|
+
} catch (err) {
|
|
45435
|
+
this.logger.warn("[DirectPlaywright] Failed to clean up trace directory", {
|
|
45436
|
+
traceDir: this.traceDir,
|
|
45437
|
+
error: err instanceof Error ? err.message : String(err)
|
|
45438
|
+
});
|
|
45439
|
+
}
|
|
45440
|
+
this.traceDir = null;
|
|
45441
|
+
}
|
|
44136
45442
|
this._capturedDownloads = [];
|
|
44137
45443
|
this._downloadCounter = 0;
|
|
44138
45444
|
this._lastReportedDownloadIndex = 0;
|
|
44139
45445
|
if (this.shouldCleanupUserDataDir && this.userDataDir) {
|
|
44140
45446
|
try {
|
|
44141
|
-
await
|
|
45447
|
+
await fs4.rm(this.userDataDir, { recursive: true, force: true });
|
|
44142
45448
|
this.logger.debug("[DirectPlaywright] Cleaned up user data directory", {
|
|
44143
45449
|
userDataDir: this.userDataDir
|
|
44144
45450
|
});
|
|
@@ -44152,7 +45458,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44152
45458
|
if (this.snapshotFilePaths.size > 0) {
|
|
44153
45459
|
for (const filePath of this.snapshotFilePaths) {
|
|
44154
45460
|
try {
|
|
44155
|
-
await
|
|
45461
|
+
await fs4.unlink(filePath);
|
|
44156
45462
|
} catch (err) {
|
|
44157
45463
|
this.logger.warn("[DirectPlaywright] Failed to clean up snapshot file", {
|
|
44158
45464
|
filePath,
|
|
@@ -44171,11 +45477,33 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
44171
45477
|
this.stealthEnabled = false;
|
|
44172
45478
|
this.logger.info("[DirectPlaywright] Disconnected");
|
|
44173
45479
|
} finally {
|
|
45480
|
+
await this.releaseTestBrowserLockIfHeld();
|
|
44174
45481
|
this.contextClosedIntentionally = false;
|
|
44175
45482
|
this.pageClosedIntentionally = false;
|
|
44176
45483
|
this.disconnectInProgress = false;
|
|
44177
45484
|
}
|
|
44178
45485
|
}
|
|
45486
|
+
async cleanupFailedConnect(error) {
|
|
45487
|
+
this.logger.warn("[DirectPlaywright] Cleaning up after failed connect", {
|
|
45488
|
+
error: error instanceof Error ? error.message : String(error)
|
|
45489
|
+
});
|
|
45490
|
+
try {
|
|
45491
|
+
await this.disconnect();
|
|
45492
|
+
} catch (disconnectError) {
|
|
45493
|
+
this.logger.warn("[DirectPlaywright] Failed cleanup after connect error", {
|
|
45494
|
+
error: disconnectError instanceof Error ? disconnectError.message : String(disconnectError)
|
|
45495
|
+
});
|
|
45496
|
+
await this.releaseTestBrowserLockIfHeld();
|
|
45497
|
+
}
|
|
45498
|
+
}
|
|
45499
|
+
async releaseTestBrowserLockIfHeld() {
|
|
45500
|
+
if (!this.releaseTestBrowserLock) {
|
|
45501
|
+
return;
|
|
45502
|
+
}
|
|
45503
|
+
const release = this.releaseTestBrowserLock;
|
|
45504
|
+
this.releaseTestBrowserLock = null;
|
|
45505
|
+
await release();
|
|
45506
|
+
}
|
|
44179
45507
|
// ================ NAVIGATION ================
|
|
44180
45508
|
async navigate(url, opts) {
|
|
44181
45509
|
const dialogMsg = this.getPendingDialogMessage();
|
|
@@ -44448,8 +45776,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
44448
45776
|
}
|
|
44449
45777
|
const sampleUrls = pendingRequests.slice(0, 3).map((url) => {
|
|
44450
45778
|
try {
|
|
44451
|
-
const
|
|
44452
|
-
return
|
|
45779
|
+
const path5 = new URL(url).pathname + new URL(url).search;
|
|
45780
|
+
return path5.length > 60 ? path5.slice(0, 57) + "..." : path5;
|
|
44453
45781
|
} catch {
|
|
44454
45782
|
return url.length > 60 ? url.slice(0, 57) + "..." : url;
|
|
44455
45783
|
}
|
|
@@ -45124,7 +46452,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45124
46452
|
const video = closingPage.video();
|
|
45125
46453
|
if (video) {
|
|
45126
46454
|
const videoFileName = `video-tab${closePageId}-${Date.now()}.webm`;
|
|
45127
|
-
const savePath =
|
|
46455
|
+
const savePath = path4.join(this.videoDir, videoFileName);
|
|
45128
46456
|
await closingPage.close();
|
|
45129
46457
|
await video.saveAs(savePath);
|
|
45130
46458
|
const createdAt = this._pageCreationTimes.get(closePageId) ?? Date.now();
|
|
@@ -45185,7 +46513,25 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45185
46513
|
this.logger.debug("[DirectPlaywright] Getting storage state", {
|
|
45186
46514
|
method: opts?.method ?? "playwright"
|
|
45187
46515
|
});
|
|
45188
|
-
|
|
46516
|
+
const playwrightState = await context.storageState();
|
|
46517
|
+
try {
|
|
46518
|
+
const page = await this.getPage();
|
|
46519
|
+
const indexedDB2 = await extractIndexedDB(page);
|
|
46520
|
+
if (indexedDB2) {
|
|
46521
|
+
this.logger.info("[DirectPlaywright] Extracted IndexedDB data", {
|
|
46522
|
+
databaseCount: indexedDB2.databases.length,
|
|
46523
|
+
databaseNames: indexedDB2.databases.map((db) => db.name)
|
|
46524
|
+
});
|
|
46525
|
+
return { ...playwrightState, indexedDB: indexedDB2 };
|
|
46526
|
+
} else {
|
|
46527
|
+
this.logger.debug("[DirectPlaywright] No IndexedDB databases found on current page");
|
|
46528
|
+
}
|
|
46529
|
+
} catch (err) {
|
|
46530
|
+
this.logger.warn("[DirectPlaywright] Failed to extract IndexedDB, saving without it", {
|
|
46531
|
+
error: err instanceof Error ? err.message : String(err)
|
|
46532
|
+
});
|
|
46533
|
+
}
|
|
46534
|
+
return playwrightState;
|
|
45189
46535
|
}
|
|
45190
46536
|
async getCurrentUrl(_opts) {
|
|
45191
46537
|
return (await this.getPage()).url();
|
|
@@ -45221,8 +46567,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45221
46567
|
const timestamp = Date.now();
|
|
45222
46568
|
const shortId = crypto.randomUUID().slice(0, 8);
|
|
45223
46569
|
const filename = `snapshot-${timestamp}-${shortId}.yaml`;
|
|
45224
|
-
const filePath =
|
|
45225
|
-
await
|
|
46570
|
+
const filePath = path4.join(this.tmpBaseDir, filename);
|
|
46571
|
+
await fs4.writeFile(filePath, yaml, "utf-8");
|
|
45226
46572
|
this.snapshotFilePaths.add(filePath);
|
|
45227
46573
|
this.logger.debug("[DirectPlaywright] Wrote snapshot to disk", {
|
|
45228
46574
|
filePath,
|
|
@@ -45366,8 +46712,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45366
46712
|
async startTracing(_opts) {
|
|
45367
46713
|
const context = await this.getContext();
|
|
45368
46714
|
this.logger.info("[DirectPlaywright] Starting trace recording");
|
|
45369
|
-
this.traceDir =
|
|
45370
|
-
await
|
|
46715
|
+
this.traceDir = path4.join(this.tmpBaseDir, `playwright-trace-${Date.now()}`);
|
|
46716
|
+
await fs4.mkdir(this.traceDir, { recursive: true });
|
|
45371
46717
|
await context.tracing.start({ screenshots: true, snapshots: true });
|
|
45372
46718
|
this.tracingActive = true;
|
|
45373
46719
|
}
|
|
@@ -45383,7 +46729,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45383
46729
|
const context = await this.getContext();
|
|
45384
46730
|
if (!this.traceDir) throw new Error("Tracing not started");
|
|
45385
46731
|
this.logger.info("[DirectPlaywright] Stopping trace recording");
|
|
45386
|
-
const tracePath =
|
|
46732
|
+
const tracePath = path4.join(this.traceDir, "trace.zip");
|
|
45387
46733
|
await context.tracing.stop({ path: tracePath });
|
|
45388
46734
|
this.tracingActive = false;
|
|
45389
46735
|
return {
|
|
@@ -45432,6 +46778,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45432
46778
|
}
|
|
45433
46779
|
await this.enrichInteractiveSVGElements(page);
|
|
45434
46780
|
await this.enrichHiddenClickableElements(page);
|
|
46781
|
+
await this.enrichModalDialogElements(page);
|
|
45435
46782
|
let snapshot;
|
|
45436
46783
|
const maxRetries = 3;
|
|
45437
46784
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -45560,6 +46907,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
45560
46907
|
async enrichHiddenClickableElements(page) {
|
|
45561
46908
|
await enrichHiddenClickableElements(page, this.logger);
|
|
45562
46909
|
}
|
|
46910
|
+
/**
|
|
46911
|
+
* Enrich positioned overlays that look like modals but lack `role="dialog"`.
|
|
46912
|
+
* Injects `role="dialog"` and `aria-label` so the agent recognizes them.
|
|
46913
|
+
*/
|
|
46914
|
+
async enrichModalDialogElements(page) {
|
|
46915
|
+
await enrichModalDialogElements(page, this.logger);
|
|
46916
|
+
}
|
|
45563
46917
|
/**
|
|
45564
46918
|
* Resolve a ref to a Playwright locator using the internal aria-ref selector.
|
|
45565
46919
|
* This is the same mechanism MCP uses to resolve refs.
|
|
@@ -46047,18 +47401,18 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46047
47401
|
this.logger.error("[DirectPlaywright] Download failed: no download directory", { id });
|
|
46048
47402
|
return;
|
|
46049
47403
|
}
|
|
46050
|
-
const savePath =
|
|
47404
|
+
const savePath = path4.join(this._downloadDir, `${id}-${suggestedFilename}`);
|
|
46051
47405
|
download.saveAs(savePath).then(
|
|
46052
47406
|
async () => {
|
|
46053
47407
|
try {
|
|
46054
|
-
const
|
|
47408
|
+
const stat3 = await fs4.stat(savePath);
|
|
46055
47409
|
entry.savedPath = savePath;
|
|
46056
|
-
entry.sizeBytes =
|
|
47410
|
+
entry.sizeBytes = stat3.size;
|
|
46057
47411
|
entry.completed = true;
|
|
46058
47412
|
this.logger.info("[DirectPlaywright] Download completed", {
|
|
46059
47413
|
id,
|
|
46060
47414
|
suggestedFilename,
|
|
46061
|
-
sizeBytes:
|
|
47415
|
+
sizeBytes: stat3.size,
|
|
46062
47416
|
savedPath: savePath
|
|
46063
47417
|
});
|
|
46064
47418
|
} catch {
|
|
@@ -46200,13 +47554,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46200
47554
|
};
|
|
46201
47555
|
if (ssp) {
|
|
46202
47556
|
try {
|
|
46203
|
-
await
|
|
47557
|
+
await fs4.access(ssp);
|
|
46204
47558
|
ctxOpts.storageState = ssp;
|
|
46205
47559
|
} catch {
|
|
46206
47560
|
}
|
|
46207
47561
|
}
|
|
46208
47562
|
if (videoConfig2) {
|
|
46209
|
-
await
|
|
47563
|
+
await fs4.mkdir(videoConfig2.dir, { recursive: true });
|
|
46210
47564
|
ctxOpts.recordVideo = videoConfig2;
|
|
46211
47565
|
}
|
|
46212
47566
|
if (headers && Object.keys(headers).length > 0) {
|
|
@@ -46232,7 +47586,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46232
47586
|
}
|
|
46233
47587
|
if (this.shouldCleanupUserDataDir && this.userDataDir) {
|
|
46234
47588
|
try {
|
|
46235
|
-
await
|
|
47589
|
+
await fs4.rm(this.userDataDir, { recursive: true, force: true });
|
|
46236
47590
|
} catch {
|
|
46237
47591
|
}
|
|
46238
47592
|
this.userDataDir = null;
|
|
@@ -46321,7 +47675,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46321
47675
|
*/
|
|
46322
47676
|
resolveVideoConfig(recordVideo) {
|
|
46323
47677
|
if (!recordVideo) return null;
|
|
46324
|
-
const dir = recordVideo === true || !recordVideo.dir ?
|
|
47678
|
+
const dir = recordVideo === true || !recordVideo.dir ? path4.join(this.tmpBaseDir, `playwright-video-${Date.now()}`) : recordVideo.dir;
|
|
46325
47679
|
const size = recordVideo !== true && recordVideo.size ? recordVideo.size : DEFAULT_VIEWPORT;
|
|
46326
47680
|
return { dir, size };
|
|
46327
47681
|
}
|
|
@@ -46380,7 +47734,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46380
47734
|
for (const handle of videoHandles) {
|
|
46381
47735
|
try {
|
|
46382
47736
|
const videoFileName = `video-tab${handle.pageIndex}-${timestamp}.webm`;
|
|
46383
|
-
const savePath =
|
|
47737
|
+
const savePath = path4.join(this.videoDir, videoFileName);
|
|
46384
47738
|
await handle.video.saveAs(savePath);
|
|
46385
47739
|
const createdAt = this._pageCreationTimes.get(handle.pageIndex) ?? timestamp;
|
|
46386
47740
|
allPageVideos.push({
|
|
@@ -46446,7 +47800,14 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46446
47800
|
);
|
|
46447
47801
|
return null;
|
|
46448
47802
|
}
|
|
47803
|
+
// ================ TOASTS ================
|
|
47804
|
+
getCapturedToasts() {
|
|
47805
|
+
return this._lastCapturedToasts;
|
|
47806
|
+
}
|
|
46449
47807
|
// ================ DOWNLOADS ================
|
|
47808
|
+
getCapturedDownloads() {
|
|
47809
|
+
return this._capturedDownloads;
|
|
47810
|
+
}
|
|
46450
47811
|
async listDownloads() {
|
|
46451
47812
|
if (this._capturedDownloads.length === 0) {
|
|
46452
47813
|
return "No downloads captured during this session.";
|
|
@@ -46487,7 +47848,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46487
47848
|
const filename = target.suggestedFilename;
|
|
46488
47849
|
const sizeBytes = target.sizeBytes ?? 0;
|
|
46489
47850
|
const mimeType = this.guessMimeType(filename);
|
|
46490
|
-
const ext =
|
|
47851
|
+
const ext = path4.extname(filename).toLowerCase();
|
|
46491
47852
|
if (ext === ".pdf") {
|
|
46492
47853
|
const { extractPdfText: extractPdfText2 } = await import("./pdf-extract-XYDS42VL.js");
|
|
46493
47854
|
const { text } = await extractPdfText2(target.savedPath);
|
|
@@ -46508,7 +47869,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
46508
47869
|
".svg"
|
|
46509
47870
|
]);
|
|
46510
47871
|
if (textExtensions.has(ext)) {
|
|
46511
|
-
const content = await
|
|
47872
|
+
const content = await fs4.readFile(target.savedPath, "utf-8");
|
|
46512
47873
|
return { filename, sizeBytes, mimeType, textContent: content };
|
|
46513
47874
|
}
|
|
46514
47875
|
return { filename, sizeBytes, mimeType, textContent: null };
|
|
@@ -46585,6 +47946,8 @@ export {
|
|
|
46585
47946
|
CursorOverlay,
|
|
46586
47947
|
setCdpScreencastLogger,
|
|
46587
47948
|
CdpScreencastManager,
|
|
47949
|
+
extractIndexedDB,
|
|
47950
|
+
restoreIndexedDB,
|
|
46588
47951
|
setSnapshotAnalyzerLogger,
|
|
46589
47952
|
extractSnapshotYaml,
|
|
46590
47953
|
parseSnapshot,
|
|
@@ -46599,26 +47962,32 @@ export {
|
|
|
46599
47962
|
normalizeIconLabelText,
|
|
46600
47963
|
normalizeSearchText,
|
|
46601
47964
|
extractDataValue,
|
|
47965
|
+
extractCellText,
|
|
47966
|
+
extractGridCell,
|
|
47967
|
+
reconcileCellColumn,
|
|
47968
|
+
detectGrid,
|
|
47969
|
+
formatGridOutput,
|
|
47970
|
+
formatGridRow,
|
|
46602
47971
|
findRelevanceScope,
|
|
46603
47972
|
searchElements,
|
|
46604
47973
|
populateSearchContext,
|
|
46605
47974
|
sortMatchesByContext,
|
|
46606
|
-
detectGrid,
|
|
46607
|
-
extractCellText,
|
|
46608
|
-
extractGridCell,
|
|
46609
|
-
reconcileCellColumn,
|
|
46610
|
-
findActiveElement,
|
|
46611
47975
|
buildSectionTree,
|
|
46612
47976
|
markMatchingSections,
|
|
47977
|
+
findActiveElement,
|
|
46613
47978
|
formatSemanticSnapshot,
|
|
46614
47979
|
expandSection,
|
|
46615
47980
|
expandSectionOnly,
|
|
46616
|
-
|
|
46617
|
-
|
|
47981
|
+
extractTablesFromSnapshot,
|
|
47982
|
+
gridInfoToRows,
|
|
47983
|
+
formatCSVWithFrontmatter,
|
|
47984
|
+
appendRowsToCSV,
|
|
47985
|
+
detectPaginationInfo,
|
|
46618
47986
|
captureSnapshotState,
|
|
46619
47987
|
compareSnapshots,
|
|
46620
47988
|
hasDiffChanges,
|
|
46621
47989
|
formatSemanticDiff,
|
|
47990
|
+
guessMimeType,
|
|
46622
47991
|
captureElementAtPoint,
|
|
46623
47992
|
AUTO_SNAPSHOT_MARKER,
|
|
46624
47993
|
isMCPContentWithImages,
|
|
@@ -46633,6 +48002,7 @@ export {
|
|
|
46633
48002
|
getBrowserToolDefinitions,
|
|
46634
48003
|
BROWSER_LIFECYCLE_TOOLS,
|
|
46635
48004
|
getBrowserToolDefinitionsWithLifecycle,
|
|
48005
|
+
dispatchBrowserTool,
|
|
46636
48006
|
extractSemanticHint,
|
|
46637
48007
|
PlaywrightClient
|
|
46638
48008
|
};
|
|
@@ -46915,4 +48285,4 @@ playwright-extra/dist/index.esm.js:
|
|
|
46915
48285
|
* @license MIT
|
|
46916
48286
|
*)
|
|
46917
48287
|
*/
|
|
46918
|
-
//# sourceMappingURL=chunk-
|
|
48288
|
+
//# sourceMappingURL=chunk-XGO62PO2.js.map
|