@opensteer/engine-playwright 0.8.2 → 0.8.4

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