@canaryai/cli 0.2.7 → 0.2.8

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.
@@ -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 delay = opts.delay === void 0 ? 0 : opts.delay;
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 || delay !== 0) {
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++] = delay & 255;
140
- buf[p++] = delay >> 8 & 255;
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 delay = 0;
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
- delay = buf[p++] | buf[p++] << 8;
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 fs4 = __require("fs");
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(path4, frames, spec, encoder) {
3895
+ exports2.write = function(path5, frames, spec, encoder) {
3896
3896
  encoder = encoder || defaultCodec;
3897
- const matches = path4.match(/\.[a-zA-Z]+$/);
3897
+ const matches = path5.match(/\.[a-zA-Z]+$/);
3898
3898
  if (matches !== null && INVALID_SUFFIXES.includes(matches[0].toLowerCase())) {
3899
- throw new Error(`GIF '${path4}' has an unexpected suffix`);
3899
+ throw new Error(`GIF '${path5}' has an unexpected suffix`);
3900
3900
  }
3901
3901
  return encoder.encodeGif(frames, spec).then((gif2) => {
3902
- return _writeBinary(path4, gif2.buffer).then(() => {
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(path4) {
3974
+ function _readBinary(path5) {
3975
3975
  return new Promise((resolve2, reject2) => {
3976
- fs4.readFile(path4, (err, buffer) => {
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(path4, buffer) {
3984
+ function _writeBinary(path5, buffer) {
3985
3985
  return new Promise((resolve2, reject2) => {
3986
- fs4.writeFile(path4, buffer, (err) => {
3986
+ fs5.writeFile(path5, buffer, (err) => {
3987
3987
  if (err) {
3988
3988
  return reject2(err);
3989
3989
  }
@@ -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(path4) {
5967
+ load: function load(path5) {
5968
5968
  var xhr = new XMLHttpRequest();
5969
- xhr.open("GET", path4, true);
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(path4) {
18866
- path4 = String(path4);
18867
- let last = path4.replace(/^.*[/\\]/, "").toLowerCase();
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 < path4.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(path4, options) {
32627
- const mimeType = import_lite.default.getType(path4);
32628
- await writeFile(path4, await this.getBuffer(mimeType, options));
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.
@@ -38485,6 +38485,27 @@ function resolveContainerFieldLabel(container) {
38485
38485
  }
38486
38486
  return void 0;
38487
38487
  }
38488
+ function shouldDropInheritedFieldLabel(element, inheritedFieldLabel) {
38489
+ if (!inheritedFieldLabel) return false;
38490
+ if (INTERACTIVE_LEAF_ROLES.has(element.role)) return false;
38491
+ if (!isClickableByAttribute(element)) return false;
38492
+ const directText = getElementText(element);
38493
+ if (directText) {
38494
+ return directText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
38495
+ }
38496
+ const childTexts = [];
38497
+ for (const child of element.children) {
38498
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38499
+ const text = getElementText(child);
38500
+ if (text && TEXT_CARRYING_ROLES.has(child.role)) {
38501
+ childTexts.push(text);
38502
+ }
38503
+ }
38504
+ }
38505
+ const composedText = childTexts.join(" ");
38506
+ if (!composedText) return false;
38507
+ return composedText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
38508
+ }
38488
38509
  var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38489
38510
  "generic",
38490
38511
  "paragraph",
@@ -38571,10 +38592,10 @@ function extractDataValue(text) {
38571
38592
  }
38572
38593
  function findRelevanceScope(elements, activeElementRef) {
38573
38594
  if (!activeElementRef) return null;
38574
- const path4 = [];
38595
+ const path5 = [];
38575
38596
  function findPath(el) {
38576
38597
  if (el.ref) {
38577
- path4.push({ ref: el.ref, role: el.role });
38598
+ path5.push({ ref: el.ref, role: el.role });
38578
38599
  }
38579
38600
  if (el.ref === activeElementRef) {
38580
38601
  return true;
@@ -38582,15 +38603,15 @@ function findRelevanceScope(elements, activeElementRef) {
38582
38603
  for (const child of el.children) {
38583
38604
  if (findPath(child)) return true;
38584
38605
  }
38585
- if (el.ref) path4.pop();
38606
+ if (el.ref) path5.pop();
38586
38607
  return false;
38587
38608
  }
38588
38609
  for (const el of elements) {
38589
38610
  if (findPath(el)) break;
38590
38611
  }
38591
- for (let i = path4.length - 1; i >= 0; i--) {
38592
- if (SECTION_BOUNDARY_ROLES.has(path4[i].role)) {
38593
- return path4[i].ref;
38612
+ for (let i = path5.length - 1; i >= 0; i--) {
38613
+ if (SECTION_BOUNDARY_ROLES.has(path5[i].role)) {
38614
+ return path5[i].ref;
38594
38615
  }
38595
38616
  }
38596
38617
  return null;
@@ -38639,9 +38660,9 @@ function searchElements(elements, searchTerms, scopeRef = null) {
38639
38660
  function findElementInSections(sections, targetRef, ancestors = []) {
38640
38661
  for (const section of sections) {
38641
38662
  const newAncestors = [...ancestors, section];
38642
- const path4 = newAncestors.map((s) => s.heading);
38663
+ const path5 = newAncestors.map((s) => s.heading);
38643
38664
  if (section.elements.some((el) => el.ref === targetRef)) {
38644
- return { section, ancestors: newAncestors, path: path4 };
38665
+ return { section, ancestors: newAncestors, path: path5 };
38645
38666
  }
38646
38667
  const found = findElementInSections(section.subsections, targetRef, newAncestors);
38647
38668
  if (found) return found;
@@ -38652,7 +38673,7 @@ function populateSearchContext(matches, sections) {
38652
38673
  for (const match of matches) {
38653
38674
  const result = findElementInSections(sections, match.ref);
38654
38675
  if (result) {
38655
- const { section, ancestors, path: path4 } = result;
38676
+ const { section, ancestors, path: path5 } = result;
38656
38677
  const inFocusedDialog = ancestors.some((s) => s.isFocused);
38657
38678
  const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
38658
38679
  if (inFocusedDialog) {
@@ -38663,7 +38684,7 @@ function populateSearchContext(matches, sections) {
38663
38684
  match.context = "foreground";
38664
38685
  }
38665
38686
  const SKIP_HEADINGS = ["generic", "main", "iframe"];
38666
- match.sectionPath = path4.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
38687
+ match.sectionPath = path5.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
38667
38688
  }
38668
38689
  }
38669
38690
  }
@@ -38825,6 +38846,24 @@ function findPrimaryColumnLabel(columnHeader) {
38825
38846
  };
38826
38847
  return search(columnHeader);
38827
38848
  }
38849
+ function isWidgetTable(element) {
38850
+ if (element.role !== "table") return false;
38851
+ const rows = element.children.filter((c2) => c2.role === "row");
38852
+ if (rows.length === 0 || rows.length > 2) return false;
38853
+ let totalCells = 0;
38854
+ let interactiveCells = 0;
38855
+ for (const row of rows) {
38856
+ for (const cell of row.children) {
38857
+ if (cell.role === "cell" || cell.role === "gridcell" || cell.role === "columnheader") {
38858
+ totalCells++;
38859
+ if (hasInteractiveDescendants(cell)) {
38860
+ interactiveCells++;
38861
+ }
38862
+ }
38863
+ }
38864
+ }
38865
+ return totalCells > 0 && interactiveCells >= totalCells * 0.5;
38866
+ }
38828
38867
  function detectGrid(element) {
38829
38868
  if (element.role !== "grid" && element.role !== "treegrid" && element.role !== "table") {
38830
38869
  return null;
@@ -39150,7 +39189,7 @@ function formatElement(element, options) {
39150
39189
  const textSource = normalizeIconLabelText(element.role, rawTextSource);
39151
39190
  const extracted = extractDataValue(textSource);
39152
39191
  const normalizedAttributes = Object.entries(element.attributes).filter(
39153
- ([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options"
39192
+ ([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options" && key !== "field"
39154
39193
  ).map(([key, val]) => {
39155
39194
  if (key === "aria-haspopup") return "haspopup";
39156
39195
  if (key === "aria-expanded") return `expanded=${val}`;
@@ -39169,7 +39208,7 @@ function formatElement(element, options) {
39169
39208
  ref: element.ref,
39170
39209
  role: element.role,
39171
39210
  name: textSource,
39172
- fieldLabel: options?.fieldLabel,
39211
+ fieldLabel: options?.fieldLabel || element.attributes["field"] || void 0,
39173
39212
  label: extracted.label || void 0,
39174
39213
  value: extracted.value,
39175
39214
  status: extracted.status,
@@ -39396,22 +39435,23 @@ function buildGridSection(element, gridInfo) {
39396
39435
  }
39397
39436
  function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
39398
39437
  if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39438
+ const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
39399
39439
  if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39400
- elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: inheritedFieldLabel }));
39440
+ elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
39401
39441
  collectExpandedChildren(element, elements);
39402
39442
  return;
39403
39443
  }
39404
39444
  if (!isMenuTriggerNoise(element)) {
39405
- elements.push(formatElement(element, { fieldLabel: inheritedFieldLabel }));
39445
+ elements.push(formatElement(element, { fieldLabel: effectiveFieldLabel }));
39406
39446
  }
39407
39447
  if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
39408
39448
  for (const child of element.children) {
39409
- collectInteractiveElements(child, elements, subsections, depth + 1, inheritedFieldLabel);
39449
+ collectInteractiveElements(child, elements, subsections, depth + 1, effectiveFieldLabel);
39410
39450
  }
39411
39451
  }
39412
39452
  return;
39413
39453
  }
39414
- if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table") {
39454
+ if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table" && !isWidgetTable(element)) {
39415
39455
  const section = buildSection(element, depth);
39416
39456
  if (section) {
39417
39457
  subsections.push(section);
@@ -39449,7 +39489,8 @@ function collectInteractiveElements(element, elements, subsections, depth, inher
39449
39489
  collectInteractiveElements(child, elements, subsections, depth + 1);
39450
39490
  }
39451
39491
  } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39452
- const fieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39492
+ const rawFieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39493
+ const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
39453
39494
  if (!isMenuTriggerNoise(child)) {
39454
39495
  if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39455
39496
  elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
@@ -39464,7 +39505,7 @@ function collectInteractiveElements(element, elements, subsections, depth, inher
39464
39505
  }
39465
39506
  }
39466
39507
  } else {
39467
- const containerLabel = resolveContainerFieldLabel(child) ?? inheritedFieldLabel;
39508
+ const containerLabel = resolveContainerFieldLabel(child) ?? (child.role === "table" ? findFieldLabelFromSiblings(kids, ki) : void 0) ?? inheritedFieldLabel;
39468
39509
  collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
39469
39510
  }
39470
39511
  }
@@ -39608,7 +39649,7 @@ var COLOR_NAMES = [
39608
39649
  "teal",
39609
39650
  "pink"
39610
39651
  ];
39611
- function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult) {
39652
+ function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult, currentTime, capturedToasts) {
39612
39653
  const activeLabel = activeElement ? normalizeIconLabelText(
39613
39654
  activeElement.role,
39614
39655
  INTERACTIVE_LEAF_ROLES.has(activeElement.role) && isExpandedWithActionableChildren(activeElement) ? composeTriggerLabel(activeElement) : activeElement.text || ""
@@ -39616,6 +39657,16 @@ function generateFrontmatter(elementCount, sectionCount, activeElement, dialogSt
39616
39657
  const activeDesc = activeElement ? `${activeElement.ref} ${activeElement.role} "${activeLabel}"` : "None";
39617
39658
  const dialogStackLine = dialogStack.length > 0 ? `
39618
39659
  DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
39660
+ let toastSection = "";
39661
+ if (capturedToasts && capturedToasts.length > 0) {
39662
+ const toastLines = capturedToasts.map((t) => {
39663
+ const ttl = t.snapshotsRemaining !== void 0 ? ` (expires in ${t.snapshotsRemaining} snapshots)` : "";
39664
+ return ` - [${t.role}] "${t.text}"${ttl}`;
39665
+ });
39666
+ toastSection = `
39667
+ TOAST NOTIFICATIONS:
39668
+ ${toastLines.join("\n")}`;
39669
+ }
39619
39670
  let searchSection = "";
39620
39671
  if (searchMatches && searchMatches.length > 0) {
39621
39672
  const hasContextInfo = searchMatches.some((m) => m.context);
@@ -39665,10 +39716,12 @@ PROBE RESULT:
39665
39716
  FULL TREE FILE: ${snapshotFilePath}
39666
39717
  Use bash or python_execute to grep/parse this file for detailed element analysis.`;
39667
39718
  }
39719
+ const browserTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
39668
39720
  return `---
39669
39721
  SEMANTIC SNAPSHOT - ${elementCount} elements in ${sectionCount} sections
39722
+ BROWSER TIME: ${browserTime} (UTC)
39670
39723
  FORMAT: Sections grouped by headings. [ref] tags identify clickable elements.
39671
- ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${searchSection}${probeSection}${fullTreeFileSection}
39724
+ ACTIVE ELEMENT: ${activeDesc}${dialogStackLine}${toastSection}${searchSection}${probeSection}${fullTreeFileSection}
39672
39725
  ACTIONS:
39673
39726
  - Click/type using [ref] values (e.g., browser_click ref=e123)${typeActionHint}
39674
39727
  - Click using coordinates from search results (e.g., browser_click x=450 y=320)
@@ -39738,7 +39791,9 @@ function formatSemanticSnapshot(yaml, options) {
39738
39791
  // Use re-sorted matches with context
39739
39792
  options?.totalMatchCount,
39740
39793
  options?.snapshotFilePath,
39741
- options?.probeResult
39794
+ options?.probeResult,
39795
+ options?.currentTime,
39796
+ options?.capturedToasts
39742
39797
  )
39743
39798
  );
39744
39799
  lines.push("");
@@ -40137,10 +40192,11 @@ function generateHint(diff2, networkInfo) {
40137
40192
  }
40138
40193
  return hints.join(" ");
40139
40194
  }
40140
- function formatSemanticDiff(diff2, actionDescription, networkInfo) {
40195
+ function formatSemanticDiff(diff2, actionDescription, networkInfo, currentTime) {
40141
40196
  const lines = [];
40142
40197
  lines.push("---");
40143
40198
  lines.push("SEMANTIC DIFF: Page state changes after browser action.");
40199
+ lines.push(`BROWSER TIME: ${currentTime ?? (/* @__PURE__ */ new Date()).toISOString()} (UTC)`);
40144
40200
  lines.push("Format: [PREVIOUS] value [CURRENT] value");
40145
40201
  lines.push("");
40146
40202
  lines.push(`ACTION: ${actionDescription}`);
@@ -40219,6 +40275,38 @@ function formatValue(value) {
40219
40275
  return `"${truncate(value, 20)}"`;
40220
40276
  }
40221
40277
 
40278
+ // ../browser-core/src/playwright-client/download-utils.ts
40279
+ import * as path from "path";
40280
+ function formatFileSize(bytes) {
40281
+ if (bytes < 1024) return `${bytes}B`;
40282
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
40283
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
40284
+ }
40285
+ function guessMimeType(filename) {
40286
+ const ext = path.extname(filename).toLowerCase();
40287
+ const mimeMap = {
40288
+ ".pdf": "application/pdf",
40289
+ ".txt": "text/plain",
40290
+ ".csv": "text/csv",
40291
+ ".json": "application/json",
40292
+ ".xml": "application/xml",
40293
+ ".html": "text/html",
40294
+ ".htm": "text/html",
40295
+ ".md": "text/markdown",
40296
+ ".png": "image/png",
40297
+ ".jpg": "image/jpeg",
40298
+ ".jpeg": "image/jpeg",
40299
+ ".gif": "image/gif",
40300
+ ".svg": "image/svg+xml",
40301
+ ".zip": "application/zip",
40302
+ ".doc": "application/msword",
40303
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
40304
+ ".xls": "application/vnd.ms-excel",
40305
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
40306
+ };
40307
+ return mimeMap[ext] ?? "application/octet-stream";
40308
+ }
40309
+
40222
40310
  // ../browser-core/src/playwright-client/element-inspection.ts
40223
40311
  function errorMessage(error) {
40224
40312
  return error instanceof Error ? error.message : String(error);
@@ -40618,7 +40706,7 @@ async function extractElementMetadata(page, locator, ref, logger2) {
40618
40706
  }
40619
40707
 
40620
40708
  // ../browser-core/src/tools/executor.ts
40621
- import * as path from "path";
40709
+ import * as path2 from "path";
40622
40710
  import * as fs2 from "fs/promises";
40623
40711
  import * as crypto2 from "crypto";
40624
40712
 
@@ -40750,7 +40838,7 @@ var BrowserToolExecutor = class {
40750
40838
  if (!options?.workspaceDir && !options?.tmpBaseDir) {
40751
40839
  options?.logger?.debug?.("[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)");
40752
40840
  }
40753
- this.workspaceDir = options?.workspaceDir ?? path.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
40841
+ this.workspaceDir = options?.workspaceDir ?? path2.join(options?.tmpBaseDir ?? getCanaryTmpDir(), `browser-workspace-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}`);
40754
40842
  this.logger = options?.logger;
40755
40843
  fs2.mkdir(this.workspaceDir, { recursive: true }).catch(() => {
40756
40844
  });
@@ -40915,15 +41003,18 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
40915
41003
  try {
40916
41004
  await fs2.mkdir(this.workspaceDir, { recursive: true });
40917
41005
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
40918
- snapshotFilePath = path.join(this.workspaceDir, filename);
41006
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
40919
41007
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
40920
41008
  } catch {
40921
41009
  }
41010
+ const now = (/* @__PURE__ */ new Date()).toISOString();
40922
41011
  const semanticText = formatSemanticSnapshot(yaml, {
40923
41012
  searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
40924
41013
  totalMatchCount: totalMatchCount > 0 ? totalMatchCount : void 0,
40925
41014
  snapshotFilePath,
40926
- probeResult
41015
+ probeResult,
41016
+ currentTime: now,
41017
+ capturedToasts: this.client.getCapturedToasts?.() ?? []
40927
41018
  });
40928
41019
  if (hasImages) {
40929
41020
  const resolutionNote = buildResolutionNote(
@@ -41166,7 +41257,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41166
41257
  if (!afterModal) {
41167
41258
  const afterState2 = captureSnapshotState(afterUrl2, afterYaml2);
41168
41259
  const diff3 = compareSnapshots(beforeState, afterState2);
41169
- return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`);
41260
+ return formatSemanticDiff(diff3, `Dismissed overlay using ${strategy}`, void 0, (/* @__PURE__ */ new Date()).toISOString());
41170
41261
  }
41171
41262
  } catch (err) {
41172
41263
  this.logger?.debug?.(`[BrowserTools] Dismiss strategy ${strategy} failed`, {
@@ -41182,7 +41273,7 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41182
41273
  const diff2 = compareSnapshots(beforeState, afterState);
41183
41274
  return `Could not dismiss overlay with strategies: ${strategies.join(", ")}. Modal may require specific interaction.
41184
41275
 
41185
- ` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)");
41276
+ ` + formatSemanticDiff(diff2, "Dismiss overlay attempted (failed)", void 0, (/* @__PURE__ */ new Date()).toISOString());
41186
41277
  }
41187
41278
  // ==================== Waiting ====================
41188
41279
  async waitFor(opts) {
@@ -41296,7 +41387,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41296
41387
  }
41297
41388
  const diff2 = compareSnapshots(beforeState, afterState);
41298
41389
  const networkInfo = this.client.getPendingNetworkInfo?.() ?? void 0;
41299
- const diffText = formatSemanticDiff(diff2, actionDescription, networkInfo);
41390
+ const now = (/* @__PURE__ */ new Date()).toISOString();
41391
+ const diffText = formatSemanticDiff(diff2, actionDescription, networkInfo, now);
41300
41392
  const hasChanges = hasDiffChanges(diff2);
41301
41393
  const isStable = !networkInfo || networkInfo.pendingCount === 0;
41302
41394
  let autoSnapshot;
@@ -41306,11 +41398,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41306
41398
  try {
41307
41399
  await fs2.mkdir(this.workspaceDir, { recursive: true });
41308
41400
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
41309
- snapshotFilePath = path.join(this.workspaceDir, filename);
41401
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
41310
41402
  await fs2.writeFile(snapshotFilePath, afterYaml, "utf-8");
41311
41403
  } catch {
41312
41404
  }
41313
- const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath });
41405
+ const semanticText = formatSemanticSnapshot(afterYaml, { snapshotFilePath, currentTime: now, capturedToasts: this.client.getCapturedToasts?.() ?? [] });
41314
41406
  const afterImages = isMCPContentWithImages(afterResult) ? afterResult.images : void 0;
41315
41407
  if (afterImages?.length && this.onScreenshot) {
41316
41408
  const img = afterImages[0];
@@ -41347,11 +41439,11 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41347
41439
  try {
41348
41440
  await fs2.mkdir(this.workspaceDir, { recursive: true });
41349
41441
  const filename = `snapshot-${Date.now()}-${crypto2.randomUUID().slice(0, 8)}.yaml`;
41350
- snapshotFilePath = path.join(this.workspaceDir, filename);
41442
+ snapshotFilePath = path2.join(this.workspaceDir, filename);
41351
41443
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41352
41444
  } catch {
41353
41445
  }
41354
- const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath });
41446
+ const semanticText = formatSemanticSnapshot(yaml, { snapshotFilePath, currentTime: (/* @__PURE__ */ new Date()).toISOString(), capturedToasts: this.client.getCapturedToasts?.() ?? [] });
41355
41447
  const yamlBlockPattern = /- Page Snapshot:\n```yaml\n[\s\S]*?```/;
41356
41448
  if (yamlBlockPattern.test(result)) {
41357
41449
  return result.replace(yamlBlockPattern, `- Page Snapshot (semantic):
@@ -42586,8 +42678,8 @@ var errors = playwrightLoader.lazyloadExportOrDie("errors");
42586
42678
 
42587
42679
  // ../browser-core/src/playwright-client.ts
42588
42680
  var import_puppeteer_extra_plugin_stealth = __toESM(require_puppeteer_extra_plugin_stealth(), 1);
42589
- import * as fs3 from "fs/promises";
42590
- import * as path3 from "path";
42681
+ import * as fs4 from "fs/promises";
42682
+ import * as path4 from "path";
42591
42683
 
42592
42684
  // ../browser-core/src/playwright-client/toast-capture-script.ts
42593
42685
  var TOAST_CAPTURE_SCRIPT = `
@@ -43310,11 +43402,44 @@ async function enrichHiddenClickableElements(page, logger2) {
43310
43402
  try {
43311
43403
  const enrichedCount = await page.evaluate(() => {
43312
43404
  let count = 0;
43313
- const patterns = [
43314
- ["abbr.search-choice-close", "search-choice-close"],
43405
+ const chosenCloseButtons = document.querySelectorAll("abbr.search-choice-close");
43406
+ for (const el of chosenCloseButtons) {
43407
+ if (el.getAttribute("data-enriched-clickable") === "true") continue;
43408
+ if (el.getAttribute("role") === "button") continue;
43409
+ let fieldName;
43410
+ const container = el.closest(".chosen-container");
43411
+ if (container) {
43412
+ const selectEl = container.previousElementSibling;
43413
+ if (selectEl && selectEl.tagName === "SELECT") {
43414
+ const selectId = selectEl.id;
43415
+ if (selectId) {
43416
+ const labelEl = document.querySelector(`label[for="${selectId}"]`);
43417
+ if (labelEl?.textContent?.trim()) {
43418
+ fieldName = labelEl.textContent.trim();
43419
+ }
43420
+ }
43421
+ if (!fieldName) {
43422
+ fieldName = selectEl.getAttribute("aria-label") || void 0;
43423
+ }
43424
+ }
43425
+ if (!fieldName && container.parentElement) {
43426
+ const prev = container.parentElement.previousElementSibling;
43427
+ if (prev && (prev.tagName === "LABEL" || prev.querySelector("label"))) {
43428
+ const labelText = prev.textContent?.trim();
43429
+ if (labelText && labelText.length < 80) fieldName = labelText;
43430
+ }
43431
+ }
43432
+ }
43433
+ const label = fieldName ? `Clear ${fieldName}` : "Clear selection";
43434
+ el.setAttribute("role", "button");
43435
+ el.setAttribute("aria-label", label);
43436
+ el.setAttribute("data-enriched-clickable", "true");
43437
+ count++;
43438
+ }
43439
+ const genericPatterns = [
43315
43440
  [".select2-selection__clear", "select2-selection__clear"]
43316
43441
  ];
43317
- for (const [selector, fallbackLabel] of patterns) {
43442
+ for (const [selector, fallbackLabel] of genericPatterns) {
43318
43443
  const elements = document.querySelectorAll(selector);
43319
43444
  for (const el of elements) {
43320
43445
  if (el.getAttribute("data-enriched-clickable") === "true") continue;
@@ -43326,6 +43451,12 @@ async function enrichHiddenClickableElements(page, logger2) {
43326
43451
  count++;
43327
43452
  }
43328
43453
  }
43454
+ const chosenSearchInputs = document.querySelectorAll(
43455
+ ".chosen-container:not(.chosen-with-drop) .chosen-search-input"
43456
+ );
43457
+ for (const input of chosenSearchInputs) {
43458
+ input.setAttribute("aria-hidden", "true");
43459
+ }
43329
43460
  return count;
43330
43461
  });
43331
43462
  if (enrichedCount > 0) {
@@ -43364,10 +43495,29 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43364
43495
  if (!trigger) continue;
43365
43496
  const rect = trigger.getBoundingClientRect();
43366
43497
  const triggerText = trigger.textContent?.trim()?.slice(0, 80) || null;
43498
+ let fieldLabel = null;
43499
+ const selectId = sel.id;
43500
+ if (selectId) {
43501
+ const labelEl = document.querySelector(`label[for="${selectId}"]`);
43502
+ if (labelEl?.textContent?.trim()) {
43503
+ fieldLabel = labelEl.textContent.trim();
43504
+ }
43505
+ }
43506
+ if (!fieldLabel) {
43507
+ fieldLabel = sel.getAttribute("aria-label") || null;
43508
+ }
43509
+ if (!fieldLabel && parent.previousElementSibling) {
43510
+ const prev = parent.previousElementSibling;
43511
+ if (prev.tagName === "LABEL" || prev.querySelector("label")) {
43512
+ const labelText = prev.textContent?.trim();
43513
+ if (labelText && labelText.length < 80) fieldLabel = labelText;
43514
+ }
43515
+ }
43367
43516
  results.push({
43368
43517
  options: opts.slice(0, 15),
43369
43518
  triggerText,
43370
- triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null
43519
+ triggerRect: rect.width > 0 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
43520
+ fieldLabel
43371
43521
  });
43372
43522
  }
43373
43523
  return results;
@@ -43379,7 +43529,7 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43379
43529
  const elements = parseSnapshot(yaml);
43380
43530
  let augmented = yaml;
43381
43531
  for (const selectInfo of hiddenSelects) {
43382
- const { options: opts, triggerText, triggerRect } = selectInfo;
43532
+ const { options: opts, triggerText, triggerRect, fieldLabel } = selectInfo;
43383
43533
  if (!opts.length) continue;
43384
43534
  let matchedRef = null;
43385
43535
  if (triggerText) {
@@ -43428,11 +43578,13 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43428
43578
  }
43429
43579
  if (!matchedRef) continue;
43430
43580
  const optionsValue = opts.join("|");
43431
- augmented = updateElementLineByRef(
43432
- augmented,
43433
- matchedRef,
43434
- (line) => appendAttributeIfMissing(line, "options", optionsValue)
43435
- );
43581
+ augmented = updateElementLineByRef(augmented, matchedRef, (line) => {
43582
+ let next = appendAttributeIfMissing(line, "options", optionsValue);
43583
+ if (fieldLabel) {
43584
+ next = appendAttributeIfMissing(next, "field", fieldLabel);
43585
+ }
43586
+ return next;
43587
+ });
43436
43588
  }
43437
43589
  return augmented;
43438
43590
  } catch (err) {
@@ -43488,38 +43640,6 @@ async function enrichInteractiveSVGElements(page, logger2) {
43488
43640
  }
43489
43641
  }
43490
43642
 
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
43643
  // ../browser-core/src/playwright-client/response-format.ts
43524
43644
  var TOAST_SNAPSHOT_TTL = 5;
43525
43645
  function formatMcpResponse(input) {
@@ -43578,10 +43698,172 @@ function formatMcpResponse(input) {
43578
43698
  };
43579
43699
  }
43580
43700
 
43701
+ // ../browser-core/src/playwright-client/launch-retry.ts
43702
+ var TRANSIENT_BROWSER_LAUNCH_PATTERNS = [
43703
+ /failed to connect/i,
43704
+ /syscall["':\s]+connect/i,
43705
+ /\bENOENT\b/i,
43706
+ /browser has been closed/i,
43707
+ /target page, context or browser has been closed/i,
43708
+ /failed to launch/i,
43709
+ /\bSIGKILL\b/i,
43710
+ /process was terminated/i
43711
+ ];
43712
+ function formatBrowserLaunchError(error) {
43713
+ if (error instanceof Error) {
43714
+ const extra = error;
43715
+ const parts = [error.message];
43716
+ if (extra.code) parts.push(`code=${extra.code}`);
43717
+ if (typeof extra.errno === "number") parts.push(`errno=${extra.errno}`);
43718
+ if (extra.syscall) parts.push(`syscall=${extra.syscall}`);
43719
+ if (extra.cause) parts.push(`cause=${formatBrowserLaunchError(extra.cause)}`);
43720
+ return parts.join(" ");
43721
+ }
43722
+ return String(error);
43723
+ }
43724
+ function isTransientBrowserLaunchError(error) {
43725
+ const text = formatBrowserLaunchError(error);
43726
+ return TRANSIENT_BROWSER_LAUNCH_PATTERNS.some((pattern) => pattern.test(text));
43727
+ }
43728
+ async function withBrowserLaunchRetry(operation, options) {
43729
+ const maxAttempts = options.maxAttempts ?? 3;
43730
+ const initialDelayMs = options.initialDelayMs ?? 150;
43731
+ let lastError;
43732
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
43733
+ try {
43734
+ return await operation(attempt);
43735
+ } catch (error) {
43736
+ lastError = error;
43737
+ if (attempt >= maxAttempts || !isTransientBrowserLaunchError(error)) {
43738
+ throw error;
43739
+ }
43740
+ const delayMs = initialDelayMs * 2 ** (attempt - 1);
43741
+ options.logger.warn("[DirectPlaywright] Browser launch failed transiently, retrying", {
43742
+ label: options.label,
43743
+ attempt,
43744
+ nextAttempt: attempt + 1,
43745
+ delayMs,
43746
+ error: formatBrowserLaunchError(error)
43747
+ });
43748
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
43749
+ }
43750
+ }
43751
+ throw lastError;
43752
+ }
43753
+
43754
+ // ../browser-core/src/playwright-client/test-browser-lock.ts
43755
+ import * as fs3 from "fs/promises";
43756
+ import * as path3 from "path";
43757
+ var TEST_BROWSER_LOCK_DIR = path3.join(getCanaryTmpDir(), "playwright-test-browser-lock");
43758
+ var TEST_BROWSER_LOCK_METADATA_PATH = path3.join(TEST_BROWSER_LOCK_DIR, "owner.json");
43759
+ var TEST_BROWSER_LOCK_POLL_MS = 100;
43760
+ var TEST_BROWSER_LOCK_TIMEOUT_MS = 12e4;
43761
+ var TEST_BROWSER_LOCK_STALE_MS = 15 * 6e4;
43762
+ function shouldUseTestBrowserLock(options) {
43763
+ return process.env.NODE_ENV === "test" && !options.browserLease && !options.cdpUrl && options.browserMode !== "headed";
43764
+ }
43765
+ async function acquireTestBrowserLock(logger2) {
43766
+ const startedAt = Date.now();
43767
+ const owner = {
43768
+ pid: process.pid,
43769
+ acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
43770
+ };
43771
+ while (true) {
43772
+ try {
43773
+ await fs3.mkdir(TEST_BROWSER_LOCK_DIR);
43774
+ await fs3.writeFile(TEST_BROWSER_LOCK_METADATA_PATH, JSON.stringify(owner), "utf8");
43775
+ logger2.debug("[DirectPlaywright] Acquired test browser lock", {
43776
+ lockDir: TEST_BROWSER_LOCK_DIR,
43777
+ pid: process.pid
43778
+ });
43779
+ let released = false;
43780
+ return async () => {
43781
+ if (released) return;
43782
+ released = true;
43783
+ try {
43784
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
43785
+ logger2.debug("[DirectPlaywright] Released test browser lock", {
43786
+ lockDir: TEST_BROWSER_LOCK_DIR,
43787
+ pid: process.pid
43788
+ });
43789
+ } catch (error) {
43790
+ logger2.warn("[DirectPlaywright] Failed to release test browser lock", {
43791
+ lockDir: TEST_BROWSER_LOCK_DIR,
43792
+ pid: process.pid,
43793
+ error: error instanceof Error ? error.message : String(error)
43794
+ });
43795
+ }
43796
+ };
43797
+ } catch (error) {
43798
+ const code = error.code;
43799
+ if (code !== "EEXIST") {
43800
+ throw error;
43801
+ }
43802
+ await cleanupStaleTestBrowserLock(logger2);
43803
+ if (Date.now() - startedAt >= TEST_BROWSER_LOCK_TIMEOUT_MS) {
43804
+ throw new Error(
43805
+ `Timed out acquiring test browser lock after ${TEST_BROWSER_LOCK_TIMEOUT_MS}ms`
43806
+ );
43807
+ }
43808
+ await delay(TEST_BROWSER_LOCK_POLL_MS);
43809
+ }
43810
+ }
43811
+ }
43812
+ async function cleanupStaleTestBrowserLock(logger2) {
43813
+ const metadata = await readLockMetadata();
43814
+ if (!metadata) {
43815
+ const orphanedLockStats = await fs3.stat(TEST_BROWSER_LOCK_DIR).catch(() => null);
43816
+ if (!orphanedLockStats) {
43817
+ return;
43818
+ }
43819
+ const orphanedAgeMs = Date.now() - orphanedLockStats.mtimeMs;
43820
+ if (orphanedAgeMs < 1e3) {
43821
+ return;
43822
+ }
43823
+ logger2.warn("[DirectPlaywright] Removing orphaned test browser lock", {
43824
+ lockDir: TEST_BROWSER_LOCK_DIR,
43825
+ ageMs: orphanedAgeMs
43826
+ });
43827
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
43828
+ return;
43829
+ }
43830
+ const ageMs = Date.now() - Date.parse(metadata.acquiredAt);
43831
+ if (ageMs < TEST_BROWSER_LOCK_STALE_MS && isProcessAlive(metadata.pid)) {
43832
+ return;
43833
+ }
43834
+ logger2.warn("[DirectPlaywright] Removing stale test browser lock", {
43835
+ lockDir: TEST_BROWSER_LOCK_DIR,
43836
+ ownerPid: metadata.pid,
43837
+ ageMs
43838
+ });
43839
+ await fs3.rm(TEST_BROWSER_LOCK_DIR, { recursive: true, force: true });
43840
+ }
43841
+ async function readLockMetadata() {
43842
+ try {
43843
+ const raw = await fs3.readFile(TEST_BROWSER_LOCK_METADATA_PATH, "utf8");
43844
+ return JSON.parse(raw);
43845
+ } catch {
43846
+ return null;
43847
+ }
43848
+ }
43849
+ function isProcessAlive(pid) {
43850
+ try {
43851
+ process.kill(pid, 0);
43852
+ return true;
43853
+ } catch (error) {
43854
+ const code = error.code;
43855
+ return code !== "ESRCH";
43856
+ }
43857
+ }
43858
+ function delay(ms) {
43859
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
43860
+ }
43861
+
43581
43862
  // ../browser-core/src/playwright-client.ts
43582
43863
  var PlaywrightClient = class _PlaywrightClient {
43583
43864
  disconnectInProgress = false;
43584
43865
  browserLease;
43866
+ releaseTestBrowserLock = null;
43585
43867
  logger;
43586
43868
  screencastEncoderFactory;
43587
43869
  tmpBaseDir;
@@ -43743,8 +44025,8 @@ var PlaywrightClient = class _PlaywrightClient {
43743
44025
  const cursorOpts = typeof cursorOverlay === "object" ? cursorOverlay : {};
43744
44026
  this.cursorOverlay = new CursorOverlay({ ...cursorOpts, enabled: cursorEnabled });
43745
44027
  }
43746
- this._downloadDir = path3.join(this.tmpBaseDir, `playwright-downloads-${Date.now()}`);
43747
- await fs3.mkdir(this._downloadDir, { recursive: true });
44028
+ this._downloadDir = path4.join(this.tmpBaseDir, `playwright-downloads-${Date.now()}`);
44029
+ await fs4.mkdir(this._downloadDir, { recursive: true });
43748
44030
  this.logger.info("[DirectPlaywright] Launching browser", {
43749
44031
  browserMode: resolvedBrowserMode,
43750
44032
  hasStorageState: !!storageStatePath,
@@ -43755,82 +44037,100 @@ var PlaywrightClient = class _PlaywrightClient {
43755
44037
  highlightEnabled: this.highlightEnabled,
43756
44038
  downloadDir: this._downloadDir
43757
44039
  });
43758
- if (cdpUrl) {
43759
- this.logger.info("[DirectPlaywright] Connecting via CDP", { cdpUrl });
43760
- this.browser = await this.withOperationTimeout(
43761
- playwrightChromium.connectOverCDP(cdpUrl),
43762
- 3e4,
43763
- "[DirectPlaywright] connectOverCDP"
43764
- );
43765
- const existingContexts = this.browser.contexts();
43766
- this.context = existingContexts[0] ?? await this.browser.newContext({ viewport: resolvedViewport });
43767
- } else if (this.browserLease && !this.stealthEnabled && !this.extensionsEnabled && resolvedBrowserMode !== "headed") {
43768
- const videoConfig2 = this.resolveVideoConfig(recordVideo);
43769
- const contextOptions = {
43770
- viewport: resolvedViewport,
43771
- acceptDownloads: true
43772
- };
43773
- if (storageStatePath) {
43774
- try {
43775
- await fs3.access(storageStatePath);
43776
- contextOptions.storageState = storageStatePath;
43777
- } catch {
44040
+ try {
44041
+ if (shouldUseTestBrowserLock({
44042
+ browserLease: !!this.browserLease,
44043
+ cdpUrl,
44044
+ browserMode: resolvedBrowserMode
44045
+ }) && !this.releaseTestBrowserLock) {
44046
+ this.releaseTestBrowserLock = await acquireTestBrowserLock(this.logger);
44047
+ }
44048
+ if (cdpUrl) {
44049
+ this.logger.info("[DirectPlaywright] Connecting via CDP", { cdpUrl });
44050
+ this.browser = await this.withOperationTimeout(
44051
+ playwrightChromium.connectOverCDP(cdpUrl),
44052
+ 3e4,
44053
+ "[DirectPlaywright] connectOverCDP"
44054
+ );
44055
+ const existingContexts = this.browser.contexts();
44056
+ this.context = existingContexts[0] ?? await this.browser.newContext({ viewport: resolvedViewport });
44057
+ } else if (this.browserLease && !this.stealthEnabled && !this.extensionsEnabled && resolvedBrowserMode !== "headed") {
44058
+ const videoConfig2 = this.resolveVideoConfig(recordVideo);
44059
+ const contextOptions = {
44060
+ viewport: resolvedViewport,
44061
+ acceptDownloads: true
44062
+ };
44063
+ if (storageStatePath) {
44064
+ try {
44065
+ await fs4.access(storageStatePath);
44066
+ contextOptions.storageState = storageStatePath;
44067
+ } catch {
44068
+ }
43778
44069
  }
43779
- }
43780
- if (videoConfig2) {
43781
- await fs3.mkdir(videoConfig2.dir, { recursive: true });
43782
- contextOptions.recordVideo = videoConfig2;
43783
- this.videoDir = videoConfig2.dir;
43784
- }
43785
- if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
43786
- contextOptions.extraHTTPHeaders = extraHTTPHeaders;
43787
- }
43788
- this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
43789
- leaseId: this.browserLease.leaseId,
43790
- browserId: this.browserLease.browserId
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", {
44070
+ if (videoConfig2) {
44071
+ await fs4.mkdir(videoConfig2.dir, { recursive: true });
44072
+ contextOptions.recordVideo = videoConfig2;
44073
+ this.videoDir = videoConfig2.dir;
44074
+ }
44075
+ if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
44076
+ contextOptions.extraHTTPHeaders = extraHTTPHeaders;
44077
+ }
44078
+ this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
43801
44079
  leaseId: this.browserLease.leaseId,
43802
- reason: resolvedBrowserMode === "headed" ? "headed-mode" : "requires-dedicated-browser"
44080
+ browserId: this.browserLease.browserId
43803
44081
  });
43804
- await this.browserLease.release();
43805
- this.browserLease = null;
44082
+ this.context = await this.withOperationTimeout(
44083
+ this.browserLease.createContext(contextOptions),
44084
+ 3e4,
44085
+ "[DirectPlaywright] browserLease.createContext"
44086
+ );
44087
+ this.browser = null;
44088
+ } else {
44089
+ if (this.browserLease) {
44090
+ this.logger.info("[DirectPlaywright] Releasing pool lease for direct launch", {
44091
+ leaseId: this.browserLease.leaseId,
44092
+ reason: resolvedBrowserMode === "headed" ? "headed-mode" : "requires-dedicated-browser"
44093
+ });
44094
+ await this.browserLease.release();
44095
+ this.browserLease = null;
44096
+ }
44097
+ const { browser, context } = await withBrowserLaunchRetry(
44098
+ () => this.launchBrowser({
44099
+ browserMode: resolvedBrowserMode,
44100
+ storageStatePath,
44101
+ viewport,
44102
+ recordVideo,
44103
+ stealth,
44104
+ extensions,
44105
+ extraHTTPHeaders
44106
+ }),
44107
+ {
44108
+ logger: this.logger,
44109
+ label: "direct-launch"
44110
+ }
44111
+ );
44112
+ this.browser = browser;
44113
+ this.context = context;
43806
44114
  }
43807
- const { browser, context } = await this.launchBrowser({
44115
+ this.attachContextListeners(this.context);
44116
+ if (this.browser) {
44117
+ this.attachBrowserListeners(this.browser);
44118
+ }
44119
+ await this.createPageWithListeners(this.context, "initial_connect");
44120
+ const now = Date.now();
44121
+ const initialPageId = this._assignPageId(this.page);
44122
+ this._pageCreationTimes.set(initialPageId, now);
44123
+ this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
44124
+ this.logger.info("[DirectPlaywright] Browser connected successfully", {
43808
44125
  browserMode: resolvedBrowserMode,
43809
- storageStatePath,
43810
- viewport,
43811
- recordVideo,
43812
- stealth,
43813
- extensions,
43814
- extraHTTPHeaders
44126
+ stealthEnabled: this.stealthEnabled,
44127
+ extensionsEnabled: this.extensionsEnabled,
44128
+ pooled: !!this.browserLease
43815
44129
  });
43816
- this.browser = browser;
43817
- this.context = context;
43818
- }
43819
- this.attachContextListeners(this.context);
43820
- if (this.browser) {
43821
- this.attachBrowserListeners(this.browser);
44130
+ } catch (error) {
44131
+ await this.cleanupFailedConnect(error);
44132
+ throw error;
43822
44133
  }
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
44134
  }
43835
44135
  supportsContextSwap() {
43836
44136
  return !this.extensionsEnabled && (!!this.browser || !!this.browserLease);
@@ -43902,7 +44202,7 @@ var PlaywrightClient = class _PlaywrightClient {
43902
44202
  }
43903
44203
  if (nextStorageStatePath) {
43904
44204
  try {
43905
- await fs3.access(nextStorageStatePath);
44205
+ await fs4.access(nextStorageStatePath);
43906
44206
  contextOptions.storageState = nextStorageStatePath;
43907
44207
  this.logger.debug("[DirectPlaywright] Loading storage state for swapped context", {
43908
44208
  storageStatePath: nextStorageStatePath
@@ -43923,7 +44223,7 @@ var PlaywrightClient = class _PlaywrightClient {
43923
44223
  this._pageCreationTimes.clear();
43924
44224
  this._closedPageVideos = [];
43925
44225
  if (videoConfig) {
43926
- await fs3.mkdir(videoConfig.dir, { recursive: true });
44226
+ await fs4.mkdir(videoConfig.dir, { recursive: true });
43927
44227
  contextOptions.recordVideo = videoConfig;
43928
44228
  this.videoDir = videoConfig.dir;
43929
44229
  this.highlightEnabled = true;
@@ -43980,6 +44280,7 @@ var PlaywrightClient = class _PlaywrightClient {
43980
44280
  this.logger.debug("[DirectPlaywright] Stealth plugin applied");
43981
44281
  }
43982
44282
  const chromiumLauncher = useStealthMode ? chromium : playwrightChromium;
44283
+ const executablePath = await _PlaywrightClient.resolveChromiumExecutable(this.logger);
43983
44284
  const args = ["--no-sandbox", "--disable-dev-shm-usage"];
43984
44285
  if (browserMode === "headed" && process.platform === "darwin") {
43985
44286
  args.push(
@@ -44001,7 +44302,7 @@ var PlaywrightClient = class _PlaywrightClient {
44001
44302
  }
44002
44303
  if (storageStatePath) {
44003
44304
  try {
44004
- await fs3.access(storageStatePath);
44305
+ await fs4.access(storageStatePath);
44005
44306
  contextOptions.storageState = storageStatePath;
44006
44307
  this.logger.debug("[DirectPlaywright] Loading storage state from file", {
44007
44308
  storageStatePath
@@ -44013,7 +44314,7 @@ var PlaywrightClient = class _PlaywrightClient {
44013
44314
  }
44014
44315
  }
44015
44316
  if (videoConfig) {
44016
- await fs3.mkdir(videoConfig.dir, { recursive: true });
44317
+ await fs4.mkdir(videoConfig.dir, { recursive: true });
44017
44318
  contextOptions.recordVideo = videoConfig;
44018
44319
  this.videoDir = videoConfig.dir;
44019
44320
  this.logger.info("[DirectPlaywright] Video recording enabled", {
@@ -44022,9 +44323,9 @@ var PlaywrightClient = class _PlaywrightClient {
44022
44323
  });
44023
44324
  }
44024
44325
  if (hasExtensions) {
44025
- this.userDataDir = extensions.userDataDir || path3.join(this.tmpBaseDir, `playwright-userdata-${Date.now()}`);
44326
+ this.userDataDir = extensions.userDataDir || path4.join(this.tmpBaseDir, `playwright-userdata-${Date.now()}`);
44026
44327
  this.shouldCleanupUserDataDir = !extensions.persistUserData;
44027
- await fs3.mkdir(this.userDataDir, { recursive: true });
44328
+ await fs4.mkdir(this.userDataDir, { recursive: true });
44028
44329
  if (browserMode === "headless") {
44029
44330
  args.push("--headless=new");
44030
44331
  }
@@ -44040,6 +44341,7 @@ var PlaywrightClient = class _PlaywrightClient {
44040
44341
  headless: false,
44041
44342
  // We use --headless=new in args for extension support
44042
44343
  args,
44344
+ ...executablePath ? { executablePath } : {},
44043
44345
  ...contextOptions
44044
44346
  }),
44045
44347
  3e4,
@@ -44050,7 +44352,8 @@ var PlaywrightClient = class _PlaywrightClient {
44050
44352
  const browser = await this.withOperationTimeout(
44051
44353
  chromiumLauncher.launch({
44052
44354
  headless: browserMode === "headless",
44053
- args
44355
+ args,
44356
+ ...executablePath ? { executablePath } : {}
44054
44357
  }),
44055
44358
  3e4,
44056
44359
  "[DirectPlaywright] chromium.launch"
@@ -44062,6 +44365,68 @@ var PlaywrightClient = class _PlaywrightClient {
44062
44365
  );
44063
44366
  return { browser, context };
44064
44367
  }
44368
+ /**
44369
+ * Resolve a working Chromium executable path, handling version mismatches
44370
+ * between the Playwright version bundled in this package and the browser
44371
+ * binaries installed on the system.
44372
+ *
44373
+ * When the exact expected binary doesn't exist (e.g. Playwright expects
44374
+ * chromium-1191 but only chromium-1194 is installed), this finds the
44375
+ * newest available chromium binary as a fallback.
44376
+ */
44377
+ static async resolveChromiumExecutable(logger2) {
44378
+ const defaultPath = playwrightChromium.executablePath();
44379
+ try {
44380
+ await fs4.access(defaultPath);
44381
+ return void 0;
44382
+ } catch {
44383
+ }
44384
+ const parts = defaultPath.split(path4.sep);
44385
+ const msPlaywrightIdx = parts.findIndex((p) => p === "ms-playwright");
44386
+ if (msPlaywrightIdx === -1) {
44387
+ logger2.warn("[DirectPlaywright] Cannot locate ms-playwright cache directory", {
44388
+ defaultPath
44389
+ });
44390
+ return void 0;
44391
+ }
44392
+ const cacheDir = parts.slice(0, msPlaywrightIdx + 1).join(path4.sep);
44393
+ const relativeBinaryPath = parts.slice(msPlaywrightIdx + 2).join(path4.sep);
44394
+ try {
44395
+ const entries = await fs4.readdir(cacheDir);
44396
+ const chromiumDirs = entries.filter((e) => e.startsWith("chromium-") && !e.includes("headless")).sort((a, b) => {
44397
+ const revA = parseInt(a.split("-")[1] ?? "0", 10);
44398
+ const revB = parseInt(b.split("-")[1] ?? "0", 10);
44399
+ return revB - revA;
44400
+ });
44401
+ for (const dir of chromiumDirs) {
44402
+ const candidate = path4.join(cacheDir, dir, relativeBinaryPath);
44403
+ try {
44404
+ await fs4.access(candidate);
44405
+ logger2.warn(
44406
+ "[DirectPlaywright] Using fallback Chromium binary (version mismatch)",
44407
+ {
44408
+ expected: defaultPath,
44409
+ using: candidate
44410
+ }
44411
+ );
44412
+ return candidate;
44413
+ } catch {
44414
+ continue;
44415
+ }
44416
+ }
44417
+ logger2.warn("[DirectPlaywright] No compatible Chromium binary found", {
44418
+ cacheDir,
44419
+ candidates: chromiumDirs,
44420
+ expectedRelativePath: relativeBinaryPath
44421
+ });
44422
+ } catch (err) {
44423
+ logger2.warn("[DirectPlaywright] Failed to scan browser cache directory", {
44424
+ cacheDir,
44425
+ error: String(err)
44426
+ });
44427
+ }
44428
+ return void 0;
44429
+ }
44065
44430
  async disconnect() {
44066
44431
  this.logger.info("[DirectPlaywright] Disconnecting");
44067
44432
  this.disconnectInProgress = true;
@@ -44121,7 +44486,7 @@ var PlaywrightClient = class _PlaywrightClient {
44121
44486
  this.lastSnapshotYaml = "";
44122
44487
  if (this._downloadDir) {
44123
44488
  try {
44124
- await fs3.rm(this._downloadDir, { recursive: true, force: true });
44489
+ await fs4.rm(this._downloadDir, { recursive: true, force: true });
44125
44490
  this.logger.debug("[DirectPlaywright] Cleaned up download directory", {
44126
44491
  downloadDir: this._downloadDir
44127
44492
  });
@@ -44133,12 +44498,26 @@ var PlaywrightClient = class _PlaywrightClient {
44133
44498
  }
44134
44499
  this._downloadDir = null;
44135
44500
  }
44501
+ if (this.traceDir) {
44502
+ try {
44503
+ await fs4.rm(this.traceDir, { recursive: true, force: true });
44504
+ this.logger.debug("[DirectPlaywright] Cleaned up trace directory", {
44505
+ traceDir: this.traceDir
44506
+ });
44507
+ } catch (err) {
44508
+ this.logger.warn("[DirectPlaywright] Failed to clean up trace directory", {
44509
+ traceDir: this.traceDir,
44510
+ error: err instanceof Error ? err.message : String(err)
44511
+ });
44512
+ }
44513
+ this.traceDir = null;
44514
+ }
44136
44515
  this._capturedDownloads = [];
44137
44516
  this._downloadCounter = 0;
44138
44517
  this._lastReportedDownloadIndex = 0;
44139
44518
  if (this.shouldCleanupUserDataDir && this.userDataDir) {
44140
44519
  try {
44141
- await fs3.rm(this.userDataDir, { recursive: true, force: true });
44520
+ await fs4.rm(this.userDataDir, { recursive: true, force: true });
44142
44521
  this.logger.debug("[DirectPlaywright] Cleaned up user data directory", {
44143
44522
  userDataDir: this.userDataDir
44144
44523
  });
@@ -44152,7 +44531,7 @@ var PlaywrightClient = class _PlaywrightClient {
44152
44531
  if (this.snapshotFilePaths.size > 0) {
44153
44532
  for (const filePath of this.snapshotFilePaths) {
44154
44533
  try {
44155
- await fs3.unlink(filePath);
44534
+ await fs4.unlink(filePath);
44156
44535
  } catch (err) {
44157
44536
  this.logger.warn("[DirectPlaywright] Failed to clean up snapshot file", {
44158
44537
  filePath,
@@ -44171,11 +44550,33 @@ var PlaywrightClient = class _PlaywrightClient {
44171
44550
  this.stealthEnabled = false;
44172
44551
  this.logger.info("[DirectPlaywright] Disconnected");
44173
44552
  } finally {
44553
+ await this.releaseTestBrowserLockIfHeld();
44174
44554
  this.contextClosedIntentionally = false;
44175
44555
  this.pageClosedIntentionally = false;
44176
44556
  this.disconnectInProgress = false;
44177
44557
  }
44178
44558
  }
44559
+ async cleanupFailedConnect(error) {
44560
+ this.logger.warn("[DirectPlaywright] Cleaning up after failed connect", {
44561
+ error: error instanceof Error ? error.message : String(error)
44562
+ });
44563
+ try {
44564
+ await this.disconnect();
44565
+ } catch (disconnectError) {
44566
+ this.logger.warn("[DirectPlaywright] Failed cleanup after connect error", {
44567
+ error: disconnectError instanceof Error ? disconnectError.message : String(disconnectError)
44568
+ });
44569
+ await this.releaseTestBrowserLockIfHeld();
44570
+ }
44571
+ }
44572
+ async releaseTestBrowserLockIfHeld() {
44573
+ if (!this.releaseTestBrowserLock) {
44574
+ return;
44575
+ }
44576
+ const release = this.releaseTestBrowserLock;
44577
+ this.releaseTestBrowserLock = null;
44578
+ await release();
44579
+ }
44179
44580
  // ================ NAVIGATION ================
44180
44581
  async navigate(url, opts) {
44181
44582
  const dialogMsg = this.getPendingDialogMessage();
@@ -44448,8 +44849,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
44448
44849
  }
44449
44850
  const sampleUrls = pendingRequests.slice(0, 3).map((url) => {
44450
44851
  try {
44451
- const path4 = new URL(url).pathname + new URL(url).search;
44452
- return path4.length > 60 ? path4.slice(0, 57) + "..." : path4;
44852
+ const path5 = new URL(url).pathname + new URL(url).search;
44853
+ return path5.length > 60 ? path5.slice(0, 57) + "..." : path5;
44453
44854
  } catch {
44454
44855
  return url.length > 60 ? url.slice(0, 57) + "..." : url;
44455
44856
  }
@@ -45124,7 +45525,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45124
45525
  const video = closingPage.video();
45125
45526
  if (video) {
45126
45527
  const videoFileName = `video-tab${closePageId}-${Date.now()}.webm`;
45127
- const savePath = path3.join(this.videoDir, videoFileName);
45528
+ const savePath = path4.join(this.videoDir, videoFileName);
45128
45529
  await closingPage.close();
45129
45530
  await video.saveAs(savePath);
45130
45531
  const createdAt = this._pageCreationTimes.get(closePageId) ?? Date.now();
@@ -45221,8 +45622,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45221
45622
  const timestamp = Date.now();
45222
45623
  const shortId = crypto.randomUUID().slice(0, 8);
45223
45624
  const filename = `snapshot-${timestamp}-${shortId}.yaml`;
45224
- const filePath = path3.join(this.tmpBaseDir, filename);
45225
- await fs3.writeFile(filePath, yaml, "utf-8");
45625
+ const filePath = path4.join(this.tmpBaseDir, filename);
45626
+ await fs4.writeFile(filePath, yaml, "utf-8");
45226
45627
  this.snapshotFilePaths.add(filePath);
45227
45628
  this.logger.debug("[DirectPlaywright] Wrote snapshot to disk", {
45228
45629
  filePath,
@@ -45366,8 +45767,8 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45366
45767
  async startTracing(_opts) {
45367
45768
  const context = await this.getContext();
45368
45769
  this.logger.info("[DirectPlaywright] Starting trace recording");
45369
- this.traceDir = path3.join(this.tmpBaseDir, `playwright-trace-${Date.now()}`);
45370
- await fs3.mkdir(this.traceDir, { recursive: true });
45770
+ this.traceDir = path4.join(this.tmpBaseDir, `playwright-trace-${Date.now()}`);
45771
+ await fs4.mkdir(this.traceDir, { recursive: true });
45371
45772
  await context.tracing.start({ screenshots: true, snapshots: true });
45372
45773
  this.tracingActive = true;
45373
45774
  }
@@ -45383,7 +45784,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45383
45784
  const context = await this.getContext();
45384
45785
  if (!this.traceDir) throw new Error("Tracing not started");
45385
45786
  this.logger.info("[DirectPlaywright] Stopping trace recording");
45386
- const tracePath = path3.join(this.traceDir, "trace.zip");
45787
+ const tracePath = path4.join(this.traceDir, "trace.zip");
45387
45788
  await context.tracing.stop({ path: tracePath });
45388
45789
  this.tracingActive = false;
45389
45790
  return {
@@ -46047,18 +46448,18 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46047
46448
  this.logger.error("[DirectPlaywright] Download failed: no download directory", { id });
46048
46449
  return;
46049
46450
  }
46050
- const savePath = path3.join(this._downloadDir, `${id}-${suggestedFilename}`);
46451
+ const savePath = path4.join(this._downloadDir, `${id}-${suggestedFilename}`);
46051
46452
  download.saveAs(savePath).then(
46052
46453
  async () => {
46053
46454
  try {
46054
- const stat2 = await fs3.stat(savePath);
46455
+ const stat3 = await fs4.stat(savePath);
46055
46456
  entry.savedPath = savePath;
46056
- entry.sizeBytes = stat2.size;
46457
+ entry.sizeBytes = stat3.size;
46057
46458
  entry.completed = true;
46058
46459
  this.logger.info("[DirectPlaywright] Download completed", {
46059
46460
  id,
46060
46461
  suggestedFilename,
46061
- sizeBytes: stat2.size,
46462
+ sizeBytes: stat3.size,
46062
46463
  savedPath: savePath
46063
46464
  });
46064
46465
  } catch {
@@ -46200,13 +46601,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46200
46601
  };
46201
46602
  if (ssp) {
46202
46603
  try {
46203
- await fs3.access(ssp);
46604
+ await fs4.access(ssp);
46204
46605
  ctxOpts.storageState = ssp;
46205
46606
  } catch {
46206
46607
  }
46207
46608
  }
46208
46609
  if (videoConfig2) {
46209
- await fs3.mkdir(videoConfig2.dir, { recursive: true });
46610
+ await fs4.mkdir(videoConfig2.dir, { recursive: true });
46210
46611
  ctxOpts.recordVideo = videoConfig2;
46211
46612
  }
46212
46613
  if (headers && Object.keys(headers).length > 0) {
@@ -46232,7 +46633,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46232
46633
  }
46233
46634
  if (this.shouldCleanupUserDataDir && this.userDataDir) {
46234
46635
  try {
46235
- await fs3.rm(this.userDataDir, { recursive: true, force: true });
46636
+ await fs4.rm(this.userDataDir, { recursive: true, force: true });
46236
46637
  } catch {
46237
46638
  }
46238
46639
  this.userDataDir = null;
@@ -46321,7 +46722,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46321
46722
  */
46322
46723
  resolveVideoConfig(recordVideo) {
46323
46724
  if (!recordVideo) return null;
46324
- const dir = recordVideo === true || !recordVideo.dir ? path3.join(this.tmpBaseDir, `playwright-video-${Date.now()}`) : recordVideo.dir;
46725
+ const dir = recordVideo === true || !recordVideo.dir ? path4.join(this.tmpBaseDir, `playwright-video-${Date.now()}`) : recordVideo.dir;
46325
46726
  const size = recordVideo !== true && recordVideo.size ? recordVideo.size : DEFAULT_VIEWPORT;
46326
46727
  return { dir, size };
46327
46728
  }
@@ -46380,7 +46781,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46380
46781
  for (const handle of videoHandles) {
46381
46782
  try {
46382
46783
  const videoFileName = `video-tab${handle.pageIndex}-${timestamp}.webm`;
46383
- const savePath = path3.join(this.videoDir, videoFileName);
46784
+ const savePath = path4.join(this.videoDir, videoFileName);
46384
46785
  await handle.video.saveAs(savePath);
46385
46786
  const createdAt = this._pageCreationTimes.get(handle.pageIndex) ?? timestamp;
46386
46787
  allPageVideos.push({
@@ -46446,7 +46847,14 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46446
46847
  );
46447
46848
  return null;
46448
46849
  }
46850
+ // ================ TOASTS ================
46851
+ getCapturedToasts() {
46852
+ return this._lastCapturedToasts;
46853
+ }
46449
46854
  // ================ DOWNLOADS ================
46855
+ getCapturedDownloads() {
46856
+ return this._capturedDownloads;
46857
+ }
46450
46858
  async listDownloads() {
46451
46859
  if (this._capturedDownloads.length === 0) {
46452
46860
  return "No downloads captured during this session.";
@@ -46487,7 +46895,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46487
46895
  const filename = target.suggestedFilename;
46488
46896
  const sizeBytes = target.sizeBytes ?? 0;
46489
46897
  const mimeType = this.guessMimeType(filename);
46490
- const ext = path3.extname(filename).toLowerCase();
46898
+ const ext = path4.extname(filename).toLowerCase();
46491
46899
  if (ext === ".pdf") {
46492
46900
  const { extractPdfText: extractPdfText2 } = await import("./pdf-extract-XYDS42VL.js");
46493
46901
  const { text } = await extractPdfText2(target.savedPath);
@@ -46508,7 +46916,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46508
46916
  ".svg"
46509
46917
  ]);
46510
46918
  if (textExtensions.has(ext)) {
46511
- const content = await fs3.readFile(target.savedPath, "utf-8");
46919
+ const content = await fs4.readFile(target.savedPath, "utf-8");
46512
46920
  return { filename, sizeBytes, mimeType, textContent: content };
46513
46921
  }
46514
46922
  return { filename, sizeBytes, mimeType, textContent: null };
@@ -46619,6 +47027,7 @@ export {
46619
47027
  compareSnapshots,
46620
47028
  hasDiffChanges,
46621
47029
  formatSemanticDiff,
47030
+ guessMimeType,
46622
47031
  captureElementAtPoint,
46623
47032
  AUTO_SNAPSHOT_MARKER,
46624
47033
  isMCPContentWithImages,
@@ -46915,4 +47324,4 @@ playwright-extra/dist/index.esm.js:
46915
47324
  * @license MIT
46916
47325
  *)
46917
47326
  */
46918
- //# sourceMappingURL=chunk-RL5Y6V3C.js.map
47327
+ //# sourceMappingURL=chunk-FK3EZADZ.js.map