@opensteer/engine-playwright 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1279,19 +1279,24 @@ function normalizePostLoadTrackerState(value) {
1279
1279
  const installedAt = readFiniteNumber(value.installedAt);
1280
1280
  const lastMutationAt = readFiniteNumber(value.lastMutationAt);
1281
1281
  const lastNetworkActivityAt = readFiniteNumber(value.lastNetworkActivityAt);
1282
+ const lastTrackedNetworkActivityAt = readFiniteNumber(value.lastTrackedNetworkActivityAt);
1282
1283
  const now = readFiniteNumber(value.now);
1283
1284
  const readyState = typeof value.readyState === "string" ? value.readyState : void 0;
1284
- if (installedAt === void 0 || lastMutationAt === void 0 || lastNetworkActivityAt === void 0 || now === void 0 || readyState === void 0) {
1285
+ if (installedAt === void 0 || lastMutationAt === void 0 || lastNetworkActivityAt === void 0 || lastTrackedNetworkActivityAt === void 0 || now === void 0 || readyState === void 0) {
1285
1286
  return void 0;
1286
1287
  }
1287
1288
  return {
1288
1289
  installedAt,
1289
1290
  lastMutationAt,
1290
1291
  lastNetworkActivityAt,
1292
+ lastTrackedNetworkActivityAt,
1291
1293
  now,
1292
1294
  pendingFetches: readNonNegativeNumber(value.pendingFetches),
1293
1295
  pendingTimeouts: readNonNegativeNumber(value.pendingTimeouts),
1294
1296
  pendingXhrs: readNonNegativeNumber(value.pendingXhrs),
1297
+ trackedPendingFetches: readNonNegativeNumber(value.trackedPendingFetches),
1298
+ trackedPendingXhrs: readNonNegativeNumber(value.trackedPendingXhrs),
1299
+ collecting: value.collecting === true,
1295
1300
  readyState
1296
1301
  };
1297
1302
  }
@@ -1306,11 +1311,14 @@ function buildPostLoadTrackerInstallScript() {
1306
1311
  installedAt: performance.now(),
1307
1312
  lastMutationAt: performance.now(),
1308
1313
  lastNetworkActivityAt: performance.now(),
1314
+ lastTrackedNetworkActivityAt: performance.now(),
1309
1315
  pendingFetches: 0,
1310
1316
  pendingTimeouts: 0,
1311
1317
  pendingXhrs: 0,
1318
+ trackedPendingFetches: 0,
1319
+ trackedPendingXhrs: 0,
1320
+ collecting: true,
1312
1321
  readyState: document.readyState,
1313
- timeoutIds: new Set(),
1314
1322
  };
1315
1323
  globalObject.__opensteerActionBoundaryTrackerInstalled = true;
1316
1324
  globalObject.__opensteerActionBoundaryTracker = tracker;
@@ -1323,6 +1331,18 @@ function buildPostLoadTrackerInstallScript() {
1323
1331
  tracker.lastNetworkActivityAt = performance.now();
1324
1332
  tracker.readyState = document.readyState;
1325
1333
  };
1334
+ const markTrackedNetwork = () => {
1335
+ tracker.lastTrackedNetworkActivityAt = performance.now();
1336
+ markNetwork();
1337
+ };
1338
+ const resetTracking = () => {
1339
+ const now = performance.now();
1340
+ tracker.lastTrackedNetworkActivityAt = now;
1341
+ tracker.trackedPendingFetches = 0;
1342
+ tracker.trackedPendingXhrs = 0;
1343
+ tracker.collecting = true;
1344
+ tracker.readyState = document.readyState;
1345
+ };
1326
1346
 
1327
1347
  const startObserver = () => {
1328
1348
  const target = document.documentElement ?? document;
@@ -1348,45 +1368,26 @@ function buildPostLoadTrackerInstallScript() {
1348
1368
  document.addEventListener("readystatechange", markMutation);
1349
1369
  addEventListener("load", markMutation, { once: true });
1350
1370
 
1351
- const nativeSetTimeout = globalObject.setTimeout.bind(globalObject);
1352
- const nativeClearTimeout = globalObject.clearTimeout.bind(globalObject);
1353
- globalObject.setTimeout = function(callback, delay, ...args) {
1354
- tracker.pendingTimeouts += 1;
1355
- markNetwork();
1356
- let handle;
1357
- const wrapped =
1358
- typeof callback === "function"
1359
- ? (...callbackArgs) => {
1360
- if (tracker.timeoutIds.delete(handle)) {
1361
- tracker.pendingTimeouts = Math.max(0, tracker.pendingTimeouts - 1);
1362
- }
1363
- try {
1364
- return callback(...callbackArgs);
1365
- } finally {
1366
- markMutation();
1367
- }
1368
- }
1369
- : callback;
1370
- handle = nativeSetTimeout(wrapped, delay, ...args);
1371
- tracker.timeoutIds.add(handle);
1372
- return handle;
1373
- };
1374
- globalObject.clearTimeout = function(handle) {
1375
- if (tracker.timeoutIds.delete(handle)) {
1376
- tracker.pendingTimeouts = Math.max(0, tracker.pendingTimeouts - 1);
1377
- }
1378
- return nativeClearTimeout(handle);
1379
- };
1380
-
1381
1371
  if (typeof globalObject.fetch === "function") {
1382
1372
  const nativeFetch = globalObject.fetch.bind(globalObject);
1383
1373
  globalObject.fetch = (...args) => {
1374
+ const tracked = tracker.collecting === true;
1384
1375
  tracker.pendingFetches += 1;
1385
- markNetwork();
1376
+ if (tracked) {
1377
+ tracker.trackedPendingFetches += 1;
1378
+ markTrackedNetwork();
1379
+ } else {
1380
+ markNetwork();
1381
+ }
1386
1382
  return nativeFetch(...args)
1387
1383
  .finally(() => {
1388
1384
  tracker.pendingFetches = Math.max(0, tracker.pendingFetches - 1);
1389
- markNetwork();
1385
+ if (tracked) {
1386
+ tracker.trackedPendingFetches = Math.max(0, tracker.trackedPendingFetches - 1);
1387
+ markTrackedNetwork();
1388
+ } else {
1389
+ markNetwork();
1390
+ }
1390
1391
  });
1391
1392
  };
1392
1393
  }
@@ -1395,21 +1396,60 @@ function buildPostLoadTrackerInstallScript() {
1395
1396
  const NativeXMLHttpRequest = globalObject.XMLHttpRequest;
1396
1397
  const nativeSend = NativeXMLHttpRequest.prototype.send;
1397
1398
  NativeXMLHttpRequest.prototype.send = function(...args) {
1399
+ const tracked = tracker.collecting === true;
1398
1400
  tracker.pendingXhrs += 1;
1399
- markNetwork();
1401
+ if (tracked) {
1402
+ tracker.trackedPendingXhrs += 1;
1403
+ markTrackedNetwork();
1404
+ } else {
1405
+ markNetwork();
1406
+ }
1400
1407
  const finalize = () => {
1401
1408
  this.removeEventListener("loadend", finalize);
1402
1409
  tracker.pendingXhrs = Math.max(0, tracker.pendingXhrs - 1);
1403
- markNetwork();
1410
+ if (tracked) {
1411
+ tracker.trackedPendingXhrs = Math.max(0, tracker.trackedPendingXhrs - 1);
1412
+ markTrackedNetwork();
1413
+ } else {
1414
+ markNetwork();
1415
+ }
1404
1416
  };
1405
1417
  this.addEventListener("loadend", finalize, { once: true });
1406
1418
  return nativeSend.apply(this, args);
1407
1419
  };
1408
1420
  }
1409
1421
 
1422
+ tracker.beginObservation = () => {
1423
+ resetTracking();
1424
+ return true;
1425
+ };
1426
+ tracker.freezeObservation = () => {
1427
+ tracker.collecting = false;
1428
+ tracker.readyState = document.readyState;
1429
+ return true;
1430
+ };
1431
+
1410
1432
  return true;
1411
1433
  })()`;
1412
1434
  }
1435
+ function buildPostLoadTrackerBeginExpression() {
1436
+ return `(() => {
1437
+ const tracker = globalThis.__opensteerActionBoundaryTracker;
1438
+ if (!tracker || typeof tracker.beginObservation !== "function") {
1439
+ return false;
1440
+ }
1441
+ return tracker.beginObservation();
1442
+ })()`;
1443
+ }
1444
+ function buildPostLoadTrackerFreezeExpression() {
1445
+ return `(() => {
1446
+ const tracker = globalThis.__opensteerActionBoundaryTracker;
1447
+ if (!tracker || typeof tracker.freezeObservation !== "function") {
1448
+ return false;
1449
+ }
1450
+ return tracker.freezeObservation();
1451
+ })()`;
1452
+ }
1413
1453
  function buildPostLoadTrackerReadExpression() {
1414
1454
  return `(() => {
1415
1455
  const tracker = globalThis.__opensteerActionBoundaryTracker;
@@ -1421,14 +1461,31 @@ function buildPostLoadTrackerReadExpression() {
1421
1461
  installedAt: Number(tracker.installedAt ?? 0),
1422
1462
  lastMutationAt: Number(tracker.lastMutationAt ?? 0),
1423
1463
  lastNetworkActivityAt: Number(tracker.lastNetworkActivityAt ?? 0),
1464
+ lastTrackedNetworkActivityAt: Number(tracker.lastTrackedNetworkActivityAt ?? 0),
1424
1465
  now: Number(performance.now()),
1425
1466
  pendingFetches: Number(tracker.pendingFetches ?? 0),
1426
1467
  pendingTimeouts: Number(tracker.pendingTimeouts ?? 0),
1427
1468
  pendingXhrs: Number(tracker.pendingXhrs ?? 0),
1469
+ trackedPendingFetches: Number(tracker.trackedPendingFetches ?? 0),
1470
+ trackedPendingXhrs: Number(tracker.trackedPendingXhrs ?? 0),
1471
+ collecting: tracker.collecting === true,
1428
1472
  readyState: String(document.readyState),
1429
1473
  };
1430
1474
  })()`;
1431
1475
  }
1476
+ function capturePostLoadTrackerSnapshot(tracker) {
1477
+ return {
1478
+ lastTrackedNetworkActivityAt: tracker.lastTrackedNetworkActivityAt,
1479
+ trackedPendingFetches: tracker.trackedPendingFetches,
1480
+ trackedPendingXhrs: tracker.trackedPendingXhrs
1481
+ };
1482
+ }
1483
+ function postLoadTrackerHasTrackedNetworkActivitySince(snapshot, tracker) {
1484
+ if (!tracker) {
1485
+ return false;
1486
+ }
1487
+ return tracker.trackedPendingFetches > snapshot.trackedPendingFetches || tracker.trackedPendingXhrs > snapshot.trackedPendingXhrs || tracker.lastTrackedNetworkActivityAt > snapshot.lastTrackedNetworkActivityAt;
1488
+ }
1432
1489
  function postLoadTrackerIsSettled(tracker, quietWindowMs = DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS) {
1433
1490
  if (!tracker) {
1434
1491
  return false;
@@ -1436,13 +1493,13 @@ function postLoadTrackerIsSettled(tracker, quietWindowMs = DEFAULT_POST_LOAD_TRA
1436
1493
  if (tracker.readyState !== "complete") {
1437
1494
  return false;
1438
1495
  }
1439
- if (tracker.pendingFetches > 0 || tracker.pendingTimeouts > 0 || tracker.pendingXhrs > 0) {
1496
+ if (tracker.trackedPendingFetches > 0 || tracker.trackedPendingXhrs > 0) {
1440
1497
  return false;
1441
1498
  }
1442
1499
  const lastActivityAt = Math.max(
1443
1500
  tracker.installedAt,
1444
1501
  tracker.lastMutationAt,
1445
- tracker.lastNetworkActivityAt
1502
+ tracker.lastTrackedNetworkActivityAt
1446
1503
  );
1447
1504
  return tracker.now - lastActivityAt >= quietWindowMs;
1448
1505
  }
@@ -1464,7 +1521,7 @@ async function waitForActionBoundary(input) {
1464
1521
  const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_ACTION_BOUNDARY_POLL_INTERVAL_MS;
1465
1522
  let trigger = "dom-action";
1466
1523
  let crossDocument = false;
1467
- let waitedForNavigationContentLoaded = false;
1524
+ let sameDocumentAsyncActivity = false;
1468
1525
  while (Date.now() < deadline) {
1469
1526
  input.throwBackgroundError();
1470
1527
  if (input.isPageClosed()) {
@@ -1486,16 +1543,21 @@ async function waitForActionBoundary(input) {
1486
1543
  throw abortError(input.signal);
1487
1544
  }
1488
1545
  const currentDocumentRef = input.getCurrentMainFrameDocumentRef();
1546
+ const currentPageUrl = input.getCurrentPageUrl?.();
1489
1547
  if (input.snapshot !== void 0 && currentDocumentRef !== void 0 && currentDocumentRef !== input.snapshot.documentRef) {
1490
1548
  trigger = "navigation";
1491
1549
  crossDocument = true;
1492
- if (!waitedForNavigationContentLoaded) {
1493
- waitedForNavigationContentLoaded = true;
1494
- const remaining = Math.max(0, deadline - Date.now());
1495
- if (remaining > 0) {
1496
- await input.waitForNavigationContentLoaded(remaining);
1497
- }
1498
- }
1550
+ }
1551
+ if (!crossDocument && !sameDocumentAsyncActivity && input.snapshot?.tracker !== void 0 && postLoadTrackerHasTrackedNetworkActivitySince(
1552
+ input.snapshot.tracker,
1553
+ await input.readTrackerState()
1554
+ )) {
1555
+ trigger = "navigation";
1556
+ sameDocumentAsyncActivity = true;
1557
+ }
1558
+ if (!crossDocument && input.snapshot?.url !== void 0 && currentPageUrl !== void 0 && currentPageUrl !== input.snapshot.url && input.isCurrentMainFrameBootstrapSettled !== void 0 && !input.isCurrentMainFrameBootstrapSettled()) {
1559
+ trigger = "navigation";
1560
+ crossDocument = true;
1499
1561
  }
1500
1562
  if (!crossDocument && crossDocumentDetectionDeadline !== void 0 && Date.now() >= crossDocumentDetectionDeadline) {
1501
1563
  return {
@@ -1504,7 +1566,18 @@ async function waitForActionBoundary(input) {
1504
1566
  bootstrapSettled: true
1505
1567
  };
1506
1568
  }
1507
- if (crossDocument && postLoadTrackerIsSettled(await input.readTrackerState())) {
1569
+ if (sameDocumentAsyncActivity) {
1570
+ return {
1571
+ trigger,
1572
+ crossDocument,
1573
+ bootstrapSettled: true
1574
+ };
1575
+ }
1576
+ if (crossDocument && input.isCurrentMainFrameBootstrapSettled !== void 0 && !input.isCurrentMainFrameBootstrapSettled()) {
1577
+ await delay(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
1578
+ continue;
1579
+ }
1580
+ if (crossDocument) {
1508
1581
  return {
1509
1582
  trigger,
1510
1583
  crossDocument,
@@ -3439,6 +3512,7 @@ async function releaseObject(controller, objectId) {
3439
3512
 
3440
3513
  // src/action-settle.ts
3441
3514
  var DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS = CROSS_DOCUMENT_INTERACTION_TIMEOUT_MS;
3515
+ var DEFAULT_PLAYWRIGHT_POST_LOAD_CAPTURE_WINDOW_MS = 1e3;
3442
3516
  function clampPlaywrightActionSettleTimeout(timeoutMs) {
3443
3517
  if (timeoutMs === void 0) {
3444
3518
  return DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS;
@@ -3447,6 +3521,8 @@ function clampPlaywrightActionSettleTimeout(timeoutMs) {
3447
3521
  }
3448
3522
  function createPlaywrightActionSettler(context) {
3449
3523
  const installScript = buildPostLoadTrackerInstallScript();
3524
+ const beginExpression = buildPostLoadTrackerBeginExpression();
3525
+ const freezeExpression = buildPostLoadTrackerFreezeExpression();
3450
3526
  const readExpression = buildPostLoadTrackerReadExpression();
3451
3527
  async function installTracker(controller) {
3452
3528
  if (!controller.settleTrackerRegistered) {
@@ -3481,6 +3557,79 @@ function createPlaywrightActionSettler(context) {
3481
3557
  throw normalizePlaywrightError(error, controller.pageRef);
3482
3558
  }
3483
3559
  }
3560
+ async function beginTrackerObservation(controller) {
3561
+ await installTracker(controller);
3562
+ try {
3563
+ await controller.cdp.send("Runtime.evaluate", {
3564
+ expression: beginExpression,
3565
+ returnByValue: true,
3566
+ awaitPromise: true
3567
+ });
3568
+ } catch (error) {
3569
+ if (isIgnorableTrackerReadError(error)) {
3570
+ return;
3571
+ }
3572
+ throw normalizePlaywrightError(error, controller.pageRef);
3573
+ }
3574
+ }
3575
+ async function freezeTrackerObservation(controller) {
3576
+ try {
3577
+ await controller.cdp.send("Runtime.evaluate", {
3578
+ expression: freezeExpression,
3579
+ returnByValue: true,
3580
+ awaitPromise: true
3581
+ });
3582
+ } catch (error) {
3583
+ if (isIgnorableTrackerReadError(error)) {
3584
+ return;
3585
+ }
3586
+ throw normalizePlaywrightError(error, controller.pageRef);
3587
+ }
3588
+ }
3589
+ async function captureSnapshot(controller) {
3590
+ const documentRef = context.getMainFrameDocumentRef(controller);
3591
+ if (documentRef === void 0) {
3592
+ throw new Error(`page ${controller.pageRef} does not expose a main frame`);
3593
+ }
3594
+ await beginTrackerObservation(controller);
3595
+ const tracker = await readTrackerState(controller);
3596
+ return {
3597
+ pageRef: controller.pageRef,
3598
+ documentRef,
3599
+ url: controller.page.url(),
3600
+ ...tracker === void 0 ? {} : { tracker: capturePostLoadTrackerSnapshot(tracker) }
3601
+ };
3602
+ }
3603
+ async function waitForPostLoadQuiet(input) {
3604
+ const { controller, timeoutMs, signal } = input;
3605
+ if (timeoutMs <= 0) {
3606
+ return;
3607
+ }
3608
+ const quietMs = input.quietMs ?? DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS;
3609
+ const captureWindowMs = Math.max(
3610
+ 0,
3611
+ Math.min(input.captureWindowMs ?? DEFAULT_PLAYWRIGHT_POST_LOAD_CAPTURE_WINDOW_MS, timeoutMs)
3612
+ );
3613
+ const deadline = Date.now() + timeoutMs;
3614
+ await installTracker(controller);
3615
+ if (captureWindowMs > 0) {
3616
+ await delayWithSignal2(captureWindowMs, signal, deadline);
3617
+ }
3618
+ await freezeTrackerObservation(controller);
3619
+ while (Date.now() < deadline) {
3620
+ if (signal?.aborted) {
3621
+ throw signal.reason ?? abortError2();
3622
+ }
3623
+ context.throwBackgroundError(controller);
3624
+ if (controller.lifecycleState === "closed") {
3625
+ return;
3626
+ }
3627
+ if (postLoadTrackerIsSettled(await readTrackerState(controller), quietMs)) {
3628
+ return;
3629
+ }
3630
+ await delayWithSignal2(100, signal, deadline);
3631
+ }
3632
+ }
3484
3633
  async function settle(options) {
3485
3634
  const { controller, timeoutMs, signal, snapshot, policySettle } = options;
3486
3635
  if (timeoutMs <= 0) {
@@ -3512,18 +3661,8 @@ function createPlaywrightActionSettler(context) {
3512
3661
  ...signal === void 0 ? {} : { signal },
3513
3662
  snapshot,
3514
3663
  getCurrentMainFrameDocumentRef: () => context.getMainFrameDocumentRef(controller),
3515
- waitForNavigationContentLoaded: async (remainingMs) => {
3516
- try {
3517
- await controller.page.waitForLoadState("domcontentloaded", {
3518
- timeout: remainingMs
3519
- });
3520
- } catch (error) {
3521
- if (controller.lifecycleState === "closed" || isContextClosedError(error)) {
3522
- return;
3523
- }
3524
- throw normalizePlaywrightError(error, controller.pageRef);
3525
- }
3526
- },
3664
+ getCurrentPageUrl: () => controller.page.url(),
3665
+ isCurrentMainFrameBootstrapSettled: () => context.isCurrentMainFrameBootstrapSettled(controller),
3527
3666
  readTrackerState: () => readTrackerState(controller),
3528
3667
  throwBackgroundError: () => context.throwBackgroundError(controller),
3529
3668
  isPageClosed: () => controller.lifecycleState === "closed"
@@ -3539,13 +3678,36 @@ function createPlaywrightActionSettler(context) {
3539
3678
  return boundary;
3540
3679
  }
3541
3680
  return {
3681
+ captureSnapshot,
3542
3682
  installTracker,
3683
+ waitForPostLoadQuiet,
3543
3684
  settle
3544
3685
  };
3545
3686
  }
3546
3687
  function abortError2() {
3547
3688
  return new DOMException("The operation was aborted", "AbortError");
3548
3689
  }
3690
+ async function delayWithSignal2(delayMs, signal, deadline) {
3691
+ const effectiveDelay = Math.max(0, Math.min(delayMs, Math.max(0, deadline - Date.now())));
3692
+ if (effectiveDelay <= 0) {
3693
+ return;
3694
+ }
3695
+ if (signal?.aborted) {
3696
+ throw signal.reason ?? abortError2();
3697
+ }
3698
+ await new Promise((resolve, reject) => {
3699
+ const timer = setTimeout(() => {
3700
+ signal?.removeEventListener("abort", onAbort);
3701
+ resolve();
3702
+ }, effectiveDelay);
3703
+ const onAbort = () => {
3704
+ clearTimeout(timer);
3705
+ signal?.removeEventListener("abort", onAbort);
3706
+ reject(signal?.reason ?? abortError2());
3707
+ };
3708
+ signal?.addEventListener("abort", onAbort, { once: true });
3709
+ });
3710
+ }
3549
3711
  function isIgnorableTrackerReadError(error) {
3550
3712
  return isContextClosedError(error) || error instanceof Error && /Execution context was destroyed|Cannot find context|Inspected target navigated or closed/i.test(
3551
3713
  error.message
@@ -3743,6 +3905,71 @@ function cloneKeyPath(keyPath) {
3743
3905
  }
3744
3906
 
3745
3907
  // src/engine.ts
3908
+ var PLAYWRIGHT_RUNTIME_EVENT_RECORDER_SOURCE = String.raw`(() => {
3909
+ const key = "__opensteerRuntimeEventRecorder";
3910
+ const globalScope = globalThis;
3911
+ if (globalScope[key]?.installed === true) {
3912
+ return;
3913
+ }
3914
+
3915
+ const queue = [];
3916
+ const limit = 500;
3917
+ const asString = (value) => {
3918
+ if (typeof value === "string") {
3919
+ return value;
3920
+ }
3921
+ if (typeof value === "number" || typeof value === "boolean" || value === null) {
3922
+ return String(value);
3923
+ }
3924
+ if (value === undefined) {
3925
+ return "undefined";
3926
+ }
3927
+ try {
3928
+ return JSON.stringify(value);
3929
+ } catch {
3930
+ return String(value);
3931
+ }
3932
+ };
3933
+ const enqueue = (entry) => {
3934
+ queue.push({
3935
+ ...entry,
3936
+ timestamp: Date.now(),
3937
+ });
3938
+ if (queue.length > limit) {
3939
+ queue.splice(0, queue.length - limit);
3940
+ }
3941
+ };
3942
+
3943
+ globalScope.addEventListener("error", (event) => {
3944
+ enqueue({
3945
+ kind: "page-error",
3946
+ message:
3947
+ typeof event.message === "string" && event.message.length > 0
3948
+ ? event.message
3949
+ : event.error?.message || "Uncaught exception",
3950
+ stack: typeof event.error?.stack === "string" ? event.error.stack : undefined,
3951
+ });
3952
+ });
3953
+
3954
+ globalScope.addEventListener("unhandledrejection", (event) => {
3955
+ const reason = event.reason;
3956
+ enqueue({
3957
+ kind: "page-error",
3958
+ message:
3959
+ typeof reason?.message === "string" && reason.message.length > 0
3960
+ ? reason.message
3961
+ : asString(reason),
3962
+ stack: typeof reason?.stack === "string" ? reason.stack : undefined,
3963
+ });
3964
+ });
3965
+
3966
+ globalScope[key] = {
3967
+ installed: true,
3968
+ drain() {
3969
+ return queue.splice(0, queue.length);
3970
+ },
3971
+ };
3972
+ })();`;
3746
3973
  var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3747
3974
  capabilities = PLAYWRIGHT_BROWSER_CORE_CAPABILITIES;
3748
3975
  browser;
@@ -3762,6 +3989,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3762
3989
  flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3763
3990
  flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
3764
3991
  getMainFrameDocumentRef: (controller) => controller.mainFrameRef === void 0 ? void 0 : this.frames.get(controller.mainFrameRef)?.currentDocument.documentRef,
3992
+ isCurrentMainFrameBootstrapSettled: (controller) => controller.mainFrameRef !== void 0 && this.frames.get(controller.mainFrameRef)?.currentDocument.domContentLoadedAt !== void 0,
3765
3993
  throwBackgroundError: (controller) => this.throwBackgroundError(controller)
3766
3994
  });
3767
3995
  pageCounter = 0;
@@ -4174,11 +4402,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4174
4402
  });
4175
4403
  await this.flushPendingPageTasks(controller.sessionRef);
4176
4404
  const mainFrame = this.requireMainFrame(controller);
4405
+ const events = mergeDistinctStepEvents([
4406
+ ...this.drainQueuedEvents(controller.pageRef),
4407
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4408
+ ]);
4177
4409
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
4178
4410
  frameRef: mainFrame.frameRef,
4179
4411
  documentRef: mainFrame.currentDocument.documentRef,
4180
4412
  documentEpoch: mainFrame.currentDocument.documentEpoch,
4181
- events: this.drainQueuedEvents(controller.pageRef),
4413
+ events,
4182
4414
  data: hit
4183
4415
  });
4184
4416
  }
@@ -4198,11 +4430,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4198
4430
  await this.flushPendingPageTasks(controller.sessionRef);
4199
4431
  await this.flushDomUpdateTask(controller);
4200
4432
  const mainFrame = this.requireMainFrame(controller);
4433
+ const events = mergeDistinctStepEvents([
4434
+ ...this.drainQueuedEvents(controller.pageRef),
4435
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4436
+ ]);
4201
4437
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
4202
4438
  frameRef: mainFrame.frameRef,
4203
4439
  documentRef: mainFrame.currentDocument.documentRef,
4204
4440
  documentEpoch: mainFrame.currentDocument.documentEpoch,
4205
- events: this.drainQueuedEvents(controller.pageRef),
4441
+ events,
4206
4442
  data: void 0
4207
4443
  });
4208
4444
  }
@@ -4214,11 +4450,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4214
4450
  });
4215
4451
  await this.flushPendingPageTasks(controller.sessionRef);
4216
4452
  const mainFrame = this.requireMainFrame(controller);
4453
+ const events = mergeDistinctStepEvents([
4454
+ ...this.drainQueuedEvents(controller.pageRef),
4455
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4456
+ ]);
4217
4457
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
4218
4458
  frameRef: mainFrame.frameRef,
4219
4459
  documentRef: mainFrame.currentDocument.documentRef,
4220
4460
  documentEpoch: mainFrame.currentDocument.documentEpoch,
4221
- events: this.drainQueuedEvents(controller.pageRef),
4461
+ events,
4222
4462
  data: void 0
4223
4463
  });
4224
4464
  }
@@ -4228,11 +4468,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4228
4468
  await controller.page.keyboard.type(input.text);
4229
4469
  await this.flushPendingPageTasks(controller.sessionRef);
4230
4470
  const mainFrame = this.requireMainFrame(controller);
4471
+ const events = mergeDistinctStepEvents([
4472
+ ...this.drainQueuedEvents(controller.pageRef),
4473
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4474
+ ]);
4231
4475
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
4232
4476
  frameRef: mainFrame.frameRef,
4233
4477
  documentRef: mainFrame.currentDocument.documentRef,
4234
4478
  documentEpoch: mainFrame.currentDocument.documentEpoch,
4235
- events: this.drainQueuedEvents(controller.pageRef),
4479
+ events,
4236
4480
  data: void 0
4237
4481
  });
4238
4482
  }
@@ -4370,6 +4614,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4370
4614
  }
4371
4615
  return infos;
4372
4616
  }
4617
+ async drainEvents(input) {
4618
+ const controller = this.requirePage(input.pageRef);
4619
+ return mergeDistinctStepEvents([
4620
+ ...this.drainQueuedEvents(input.pageRef),
4621
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4622
+ ]);
4623
+ }
4373
4624
  async listFrames(input) {
4374
4625
  const controller = this.requirePage(input.pageRef);
4375
4626
  await this.flushDomUpdateTask(controller);
@@ -4421,6 +4672,12 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4421
4672
  (contentDocIndex) => resolveCapturedContentDocumentRef(controller.framesByCdpId, captured, contentDocIndex)
4422
4673
  );
4423
4674
  }
4675
+ async getActionBoundarySnapshot(input) {
4676
+ const controller = this.requirePage(input.pageRef);
4677
+ await this.flushPendingPageTasks(controller.sessionRef);
4678
+ await this.flushDomUpdateTask(controller);
4679
+ return this.actionSettler.captureSnapshot(controller);
4680
+ }
4424
4681
  async waitForVisualStability(input) {
4425
4682
  const controller = this.requirePage(input.pageRef);
4426
4683
  await this.flushDomUpdateTask(controller);
@@ -4431,6 +4688,21 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4431
4688
  });
4432
4689
  await this.flushDomUpdateTask(controller);
4433
4690
  }
4691
+ async waitForPostLoadQuiet(input) {
4692
+ const controller = this.requirePage(input.pageRef);
4693
+ await this.flushPendingPageTasks(controller.sessionRef);
4694
+ await this.actionSettler.waitForPostLoadQuiet({
4695
+ controller,
4696
+ timeoutMs: clampPlaywrightActionSettleTimeout(input.timeoutMs),
4697
+ ...input.quietMs === void 0 ? {} : { quietMs: input.quietMs },
4698
+ ...input.captureWindowMs === void 0 ? {} : { captureWindowMs: input.captureWindowMs },
4699
+ ...input.signal === void 0 ? {} : { signal: input.signal }
4700
+ });
4701
+ await this.flushPendingPageTasks(controller.sessionRef);
4702
+ if (controller.lifecycleState !== "closed") {
4703
+ await this.flushDomUpdateTask(controller);
4704
+ }
4705
+ }
4434
4706
  async readText(input) {
4435
4707
  const document = this.requireDocument(input.documentRef);
4436
4708
  const controller = this.requirePage(document.pageRef);
@@ -4952,9 +5224,11 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4952
5224
  session.activePageRef = pageRef;
4953
5225
  await cdp.send("Page.enable", { enableFileChooserOpenedEvent: true });
4954
5226
  await cdp.send("Network.enable");
5227
+ await cdp.send("Runtime.enable");
4955
5228
  await cdp.send("DOM.enable", { includeWhitespace: "none" });
4956
5229
  await cdp.send("DOMStorage.enable");
4957
5230
  await cdp.send("DOM.getDocument", { depth: 0 });
5231
+ await this.installRuntimeEventRecorder(page);
4958
5232
  await this.actionSettler.installTracker(controller);
4959
5233
  cdp.on(
4960
5234
  "Page.frameAttached",
@@ -5030,9 +5304,19 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5030
5304
  "DOM.documentUpdated",
5031
5305
  () => this.runControllerEvent(controller, () => this.handleDocumentUpdated(controller))
5032
5306
  );
5033
- page.on(
5034
- "console",
5035
- (message) => this.runControllerEvent(controller, () => this.handleConsole(controller, message))
5307
+ cdp.on(
5308
+ "Runtime.consoleAPICalled",
5309
+ (payload) => this.runControllerEvent(
5310
+ controller,
5311
+ () => this.handleRuntimeConsole(controller, payload)
5312
+ )
5313
+ );
5314
+ cdp.on(
5315
+ "Runtime.exceptionThrown",
5316
+ (payload) => this.runControllerEvent(
5317
+ controller,
5318
+ () => this.handleRuntimeException(controller, payload)
5319
+ )
5036
5320
  );
5037
5321
  page.on(
5038
5322
  "popup",
@@ -5059,10 +5343,6 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5059
5343
  "download",
5060
5344
  (download) => this.runControllerEvent(controller, () => this.handleDownload(controller, download))
5061
5345
  );
5062
- page.on(
5063
- "pageerror",
5064
- (error) => this.runControllerEvent(controller, () => this.handlePageError(controller, error))
5065
- );
5066
5346
  page.on(
5067
5347
  "request",
5068
5348
  (request) => this.runControllerEvent(controller, () => this.handlePlaywrightRequest(controller, request))
@@ -5074,6 +5354,10 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5074
5354
  () => this.handlePlaywrightResponse(controller, response)
5075
5355
  )
5076
5356
  );
5357
+ page.on(
5358
+ "domcontentloaded",
5359
+ () => this.runControllerEvent(controller, () => this.handlePageDomContentLoaded(controller))
5360
+ );
5077
5361
  page.on("close", () => this.handleUnexpectedPageClose(controller));
5078
5362
  const frameTree = await cdp.send("Page.getFrameTree");
5079
5363
  this.syncFrameTree(controller, frameTree.frameTree);
@@ -5126,6 +5410,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5126
5410
  documentRef,
5127
5411
  documentEpoch: createDocumentEpoch(0),
5128
5412
  url: "about:blank",
5413
+ domContentLoadedAt: void 0,
5129
5414
  parentDocumentRef: parent?.currentDocument.documentRef,
5130
5415
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
5131
5416
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -5184,6 +5469,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5184
5469
  documentRef: nextDocumentRef,
5185
5470
  documentEpoch: createDocumentEpoch(0),
5186
5471
  url: combineFrameUrl(frame.url, frame.urlFragment),
5472
+ domContentLoadedAt: void 0,
5187
5473
  parentDocumentRef: parent?.currentDocument.documentRef,
5188
5474
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
5189
5475
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -5213,6 +5499,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5213
5499
  handleDocumentUpdated(controller) {
5214
5500
  this.queueDocumentReconciliation(controller);
5215
5501
  }
5502
+ handlePageDomContentLoaded(controller) {
5503
+ const mainFrame = controller.mainFrameRef === void 0 ? void 0 : this.frames.get(controller.mainFrameRef);
5504
+ if (mainFrame === void 0) {
5505
+ return;
5506
+ }
5507
+ mainFrame.currentDocument.domContentLoadedAt = Date.now();
5508
+ }
5216
5509
  syncFrameTree(controller, tree) {
5217
5510
  const visit = (node, parentFrameRef) => {
5218
5511
  const existing = controller.framesByCdpId.get(node.frame.id);
@@ -5226,6 +5519,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5226
5519
  documentRef,
5227
5520
  documentEpoch: createDocumentEpoch(0),
5228
5521
  url: combineFrameUrl(node.frame.url, node.frame.urlFragment),
5522
+ domContentLoadedAt: void 0,
5229
5523
  parentDocumentRef: parentFrameRef === void 0 ? void 0 : this.requireFrame(parentFrameRef).currentDocument.documentRef,
5230
5524
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
5231
5525
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -5284,19 +5578,20 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5284
5578
  }
5285
5579
  }
5286
5580
  }
5287
- handleConsole(controller, message) {
5581
+ handleRuntimeConsole(controller, payload) {
5582
+ const callFrame = payload.stackTrace?.callFrames?.[0];
5288
5583
  this.queueEvent(
5289
5584
  controller.pageRef,
5290
5585
  this.createEvent({
5291
5586
  kind: "console",
5292
5587
  sessionRef: controller.sessionRef,
5293
5588
  pageRef: controller.pageRef,
5294
- level: normalizeConsoleLevel(message.type()),
5295
- text: message.text(),
5589
+ level: normalizeRuntimeConsoleLevel(payload.type),
5590
+ text: formatRuntimeConsoleText(payload),
5296
5591
  location: {
5297
- url: message.location().url,
5298
- lineNumber: message.location().lineNumber,
5299
- columnNumber: message.location().columnNumber
5592
+ url: callFrame?.url ?? "",
5593
+ lineNumber: callFrame?.lineNumber ?? 0,
5594
+ columnNumber: callFrame?.columnNumber ?? 0
5300
5595
  }
5301
5596
  })
5302
5597
  );
@@ -5377,15 +5672,18 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5377
5672
  })
5378
5673
  );
5379
5674
  }
5380
- handlePageError(controller, error) {
5675
+ handleRuntimeException(controller, payload) {
5676
+ const details = payload.exceptionDetails;
5677
+ const message = formatRuntimeExceptionMessage(details);
5678
+ const stack = formatRuntimeExceptionStack(details);
5381
5679
  this.queueEvent(
5382
5680
  controller.pageRef,
5383
5681
  this.createEvent({
5384
5682
  kind: "page-error",
5385
5683
  sessionRef: controller.sessionRef,
5386
5684
  pageRef: controller.pageRef,
5387
- message: error.message,
5388
- ...error.stack === void 0 ? {} : { stack: error.stack }
5685
+ message,
5686
+ ...stack === void 0 ? {} : { stack }
5389
5687
  })
5390
5688
  );
5391
5689
  }
@@ -6107,10 +6405,11 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
6107
6405
  }
6108
6406
  }
6109
6407
  createEvent(value) {
6408
+ const { timestamp, ...event } = value;
6110
6409
  return {
6111
- ...value,
6410
+ ...event,
6112
6411
  eventId: `event:${++this.eventCounter}`,
6113
- timestamp: Date.now()
6412
+ timestamp: timestamp ?? Date.now()
6114
6413
  };
6115
6414
  }
6116
6415
  queueEvent(pageRef, event) {
@@ -6120,6 +6419,37 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
6120
6419
  }
6121
6420
  controller.queuedEvents.push(event);
6122
6421
  }
6422
+ async installRuntimeEventRecorder(page) {
6423
+ await page.addInitScript({
6424
+ content: PLAYWRIGHT_RUNTIME_EVENT_RECORDER_SOURCE
6425
+ });
6426
+ await page.evaluate(PLAYWRIGHT_RUNTIME_EVENT_RECORDER_SOURCE).catch(() => void 0);
6427
+ }
6428
+ async drainInstrumentedRuntimeEvents(controller) {
6429
+ const recorded = await Promise.all(
6430
+ controller.page.frames().map(async (frame) => {
6431
+ const events = await frame.evaluate(() => {
6432
+ const recorder = globalThis.__opensteerRuntimeEventRecorder;
6433
+ return recorder?.drain?.() ?? [];
6434
+ }).catch(() => []);
6435
+ return events;
6436
+ })
6437
+ );
6438
+ return mergeDistinctStepEvents(
6439
+ recorded.flatMap(
6440
+ (events) => events.map(
6441
+ (event) => this.createEvent({
6442
+ kind: "page-error",
6443
+ sessionRef: controller.sessionRef,
6444
+ pageRef: controller.pageRef,
6445
+ message: event.message ?? "Uncaught exception",
6446
+ ...event.stack === void 0 ? {} : { stack: event.stack },
6447
+ timestamp: event.timestamp
6448
+ })
6449
+ )
6450
+ )
6451
+ );
6452
+ }
6123
6453
  drainQueuedEvents(pageRef) {
6124
6454
  const controller = this.requirePage(pageRef);
6125
6455
  const events = controller.queuedEvents.splice(0, controller.queuedEvents.length);
@@ -6366,6 +6696,104 @@ function withTimeout(promise, timeoutMs) {
6366
6696
  })
6367
6697
  ]);
6368
6698
  }
6699
+ function formatRuntimeConsoleText(payload) {
6700
+ const parts = (payload.args ?? []).map((arg) => formatRuntimeRemoteObject(arg)).filter((value) => value.length > 0);
6701
+ if (parts.length > 0) {
6702
+ return parts.join(" ");
6703
+ }
6704
+ return payload.type?.trim() || "console";
6705
+ }
6706
+ function normalizeRuntimeConsoleLevel(value) {
6707
+ switch (value) {
6708
+ case "warning":
6709
+ case "debug":
6710
+ case "info":
6711
+ case "error":
6712
+ case "trace":
6713
+ return normalizeConsoleLevel(value);
6714
+ default:
6715
+ return "log";
6716
+ }
6717
+ }
6718
+ function formatRuntimeExceptionMessage(details) {
6719
+ const description = details?.exception?.description?.trim();
6720
+ if (description) {
6721
+ const firstLine = description.split("\n")[0]?.trim() ?? "";
6722
+ if (firstLine.startsWith("Error: ")) {
6723
+ return firstLine.slice("Error: ".length);
6724
+ }
6725
+ return firstLine || description;
6726
+ }
6727
+ const value = details?.exception?.value;
6728
+ if (typeof value === "string" && value.trim().length > 0) {
6729
+ return value.trim();
6730
+ }
6731
+ return details?.text?.trim() || "Uncaught exception";
6732
+ }
6733
+ function formatRuntimeExceptionStack(details) {
6734
+ const description = details?.exception?.description?.trim();
6735
+ if (description && description.includes("\n")) {
6736
+ return description;
6737
+ }
6738
+ const frames = details?.stackTrace?.callFrames;
6739
+ if (!frames || frames.length === 0) {
6740
+ return void 0;
6741
+ }
6742
+ return frames.map((frame) => {
6743
+ const url = frame.url ?? "<anonymous>";
6744
+ const lineNumber = frame.lineNumber ?? 0;
6745
+ const columnNumber = frame.columnNumber ?? 0;
6746
+ return ` at ${url}:${lineNumber}:${columnNumber}`;
6747
+ }).join("\n");
6748
+ }
6749
+ function formatRuntimeRemoteObject(object) {
6750
+ if (object.unserializableValue !== void 0) {
6751
+ return object.unserializableValue;
6752
+ }
6753
+ if (object.value !== void 0) {
6754
+ return typeof object.value === "string" ? object.value : JSON.stringify(object.value);
6755
+ }
6756
+ if (object.description !== void 0 && object.description.length > 0) {
6757
+ return object.description;
6758
+ }
6759
+ const previewParts = object.preview?.properties?.map((property) => {
6760
+ if (!property.name) {
6761
+ return "";
6762
+ }
6763
+ return property.value === void 0 ? property.name : `${property.name}: ${property.value}`;
6764
+ }).filter((value) => value.length > 0) ?? [];
6765
+ return previewParts.join(", ");
6766
+ }
6767
+ function mergeDistinctStepEvents(events) {
6768
+ const seen = /* @__PURE__ */ new Set();
6769
+ const merged = [];
6770
+ for (const event of events) {
6771
+ const fingerprint = stepEventFingerprint(event);
6772
+ if (seen.has(fingerprint)) {
6773
+ continue;
6774
+ }
6775
+ seen.add(fingerprint);
6776
+ merged.push(event);
6777
+ }
6778
+ return merged;
6779
+ }
6780
+ function stepEventFingerprint(event) {
6781
+ switch (event.kind) {
6782
+ case "console":
6783
+ return [
6784
+ event.kind,
6785
+ event.level,
6786
+ event.text,
6787
+ event.location?.url ?? "",
6788
+ event.location?.lineNumber ?? "",
6789
+ event.location?.columnNumber ?? ""
6790
+ ].join("|");
6791
+ case "page-error":
6792
+ return [event.kind, event.message, event.stack ?? ""].join("|");
6793
+ default:
6794
+ return event.eventId;
6795
+ }
6796
+ }
6369
6797
 
6370
6798
  exports.PlaywrightBrowserCoreEngine = PlaywrightBrowserCoreEngine;
6371
6799
  exports.capturePlaywrightStorageOrigins = capturePlaywrightStorageOrigins;