@opensteer/engine-playwright 0.8.1 → 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.js CHANGED
@@ -1257,6 +1257,355 @@ function sleep(ms) {
1257
1257
  return new Promise((resolve) => setTimeout(resolve, ms));
1258
1258
  }
1259
1259
 
1260
+ // ../browser-core/src/post-load-tracker.ts
1261
+ var DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS = 400;
1262
+ var DEFAULT_ACTION_BOUNDARY_POLL_INTERVAL_MS = 100;
1263
+ function isRecord(value) {
1264
+ return typeof value === "object" && value !== null;
1265
+ }
1266
+ function readFiniteNumber(value) {
1267
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1268
+ }
1269
+ function readNonNegativeNumber(value) {
1270
+ const parsed = readFiniteNumber(value);
1271
+ return parsed === void 0 || parsed < 0 ? 0 : parsed;
1272
+ }
1273
+ function normalizePostLoadTrackerState(value) {
1274
+ if (!isRecord(value)) {
1275
+ return void 0;
1276
+ }
1277
+ const installedAt = readFiniteNumber(value.installedAt);
1278
+ const lastMutationAt = readFiniteNumber(value.lastMutationAt);
1279
+ const lastNetworkActivityAt = readFiniteNumber(value.lastNetworkActivityAt);
1280
+ const lastTrackedNetworkActivityAt = readFiniteNumber(value.lastTrackedNetworkActivityAt);
1281
+ const now = readFiniteNumber(value.now);
1282
+ const readyState = typeof value.readyState === "string" ? value.readyState : void 0;
1283
+ if (installedAt === void 0 || lastMutationAt === void 0 || lastNetworkActivityAt === void 0 || lastTrackedNetworkActivityAt === void 0 || now === void 0 || readyState === void 0) {
1284
+ return void 0;
1285
+ }
1286
+ return {
1287
+ installedAt,
1288
+ lastMutationAt,
1289
+ lastNetworkActivityAt,
1290
+ lastTrackedNetworkActivityAt,
1291
+ now,
1292
+ pendingFetches: readNonNegativeNumber(value.pendingFetches),
1293
+ pendingTimeouts: readNonNegativeNumber(value.pendingTimeouts),
1294
+ pendingXhrs: readNonNegativeNumber(value.pendingXhrs),
1295
+ trackedPendingFetches: readNonNegativeNumber(value.trackedPendingFetches),
1296
+ trackedPendingXhrs: readNonNegativeNumber(value.trackedPendingXhrs),
1297
+ collecting: value.collecting === true,
1298
+ readyState
1299
+ };
1300
+ }
1301
+ function buildPostLoadTrackerInstallScript() {
1302
+ return `(() => {
1303
+ const globalObject = globalThis;
1304
+ if (globalObject.__opensteerActionBoundaryTrackerInstalled) {
1305
+ return true;
1306
+ }
1307
+
1308
+ const tracker = {
1309
+ installedAt: performance.now(),
1310
+ lastMutationAt: performance.now(),
1311
+ lastNetworkActivityAt: performance.now(),
1312
+ lastTrackedNetworkActivityAt: performance.now(),
1313
+ pendingFetches: 0,
1314
+ pendingTimeouts: 0,
1315
+ pendingXhrs: 0,
1316
+ trackedPendingFetches: 0,
1317
+ trackedPendingXhrs: 0,
1318
+ collecting: true,
1319
+ readyState: document.readyState,
1320
+ };
1321
+ globalObject.__opensteerActionBoundaryTrackerInstalled = true;
1322
+ globalObject.__opensteerActionBoundaryTracker = tracker;
1323
+
1324
+ const markMutation = () => {
1325
+ tracker.lastMutationAt = performance.now();
1326
+ tracker.readyState = document.readyState;
1327
+ };
1328
+ const markNetwork = () => {
1329
+ tracker.lastNetworkActivityAt = performance.now();
1330
+ tracker.readyState = document.readyState;
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
+ };
1344
+
1345
+ const startObserver = () => {
1346
+ const target = document.documentElement ?? document;
1347
+ if (!(target instanceof Node)) {
1348
+ return;
1349
+ }
1350
+ const observer = new MutationObserver(markMutation);
1351
+ observer.observe(target, {
1352
+ subtree: true,
1353
+ childList: true,
1354
+ characterData: true,
1355
+ attributes: true,
1356
+ });
1357
+ markMutation();
1358
+ };
1359
+
1360
+ if (document.documentElement) {
1361
+ startObserver();
1362
+ } else {
1363
+ document.addEventListener("DOMContentLoaded", startObserver, { once: true });
1364
+ }
1365
+
1366
+ document.addEventListener("readystatechange", markMutation);
1367
+ addEventListener("load", markMutation, { once: true });
1368
+
1369
+ if (typeof globalObject.fetch === "function") {
1370
+ const nativeFetch = globalObject.fetch.bind(globalObject);
1371
+ globalObject.fetch = (...args) => {
1372
+ const tracked = tracker.collecting === true;
1373
+ tracker.pendingFetches += 1;
1374
+ if (tracked) {
1375
+ tracker.trackedPendingFetches += 1;
1376
+ markTrackedNetwork();
1377
+ } else {
1378
+ markNetwork();
1379
+ }
1380
+ return nativeFetch(...args)
1381
+ .finally(() => {
1382
+ tracker.pendingFetches = Math.max(0, tracker.pendingFetches - 1);
1383
+ if (tracked) {
1384
+ tracker.trackedPendingFetches = Math.max(0, tracker.trackedPendingFetches - 1);
1385
+ markTrackedNetwork();
1386
+ } else {
1387
+ markNetwork();
1388
+ }
1389
+ });
1390
+ };
1391
+ }
1392
+
1393
+ if (typeof globalObject.XMLHttpRequest === "function") {
1394
+ const NativeXMLHttpRequest = globalObject.XMLHttpRequest;
1395
+ const nativeSend = NativeXMLHttpRequest.prototype.send;
1396
+ NativeXMLHttpRequest.prototype.send = function(...args) {
1397
+ const tracked = tracker.collecting === true;
1398
+ tracker.pendingXhrs += 1;
1399
+ if (tracked) {
1400
+ tracker.trackedPendingXhrs += 1;
1401
+ markTrackedNetwork();
1402
+ } else {
1403
+ markNetwork();
1404
+ }
1405
+ const finalize = () => {
1406
+ this.removeEventListener("loadend", finalize);
1407
+ tracker.pendingXhrs = Math.max(0, tracker.pendingXhrs - 1);
1408
+ if (tracked) {
1409
+ tracker.trackedPendingXhrs = Math.max(0, tracker.trackedPendingXhrs - 1);
1410
+ markTrackedNetwork();
1411
+ } else {
1412
+ markNetwork();
1413
+ }
1414
+ };
1415
+ this.addEventListener("loadend", finalize, { once: true });
1416
+ return nativeSend.apply(this, args);
1417
+ };
1418
+ }
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
+
1430
+ return true;
1431
+ })()`;
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
+ }
1451
+ function buildPostLoadTrackerReadExpression() {
1452
+ return `(() => {
1453
+ const tracker = globalThis.__opensteerActionBoundaryTracker;
1454
+ if (!tracker) {
1455
+ return null;
1456
+ }
1457
+
1458
+ return {
1459
+ installedAt: Number(tracker.installedAt ?? 0),
1460
+ lastMutationAt: Number(tracker.lastMutationAt ?? 0),
1461
+ lastNetworkActivityAt: Number(tracker.lastNetworkActivityAt ?? 0),
1462
+ lastTrackedNetworkActivityAt: Number(tracker.lastTrackedNetworkActivityAt ?? 0),
1463
+ now: Number(performance.now()),
1464
+ pendingFetches: Number(tracker.pendingFetches ?? 0),
1465
+ pendingTimeouts: Number(tracker.pendingTimeouts ?? 0),
1466
+ pendingXhrs: Number(tracker.pendingXhrs ?? 0),
1467
+ trackedPendingFetches: Number(tracker.trackedPendingFetches ?? 0),
1468
+ trackedPendingXhrs: Number(tracker.trackedPendingXhrs ?? 0),
1469
+ collecting: tracker.collecting === true,
1470
+ readyState: String(document.readyState),
1471
+ };
1472
+ })()`;
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
+ }
1487
+ function postLoadTrackerIsSettled(tracker, quietWindowMs = DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS) {
1488
+ if (!tracker) {
1489
+ return false;
1490
+ }
1491
+ if (tracker.readyState !== "complete") {
1492
+ return false;
1493
+ }
1494
+ if (tracker.trackedPendingFetches > 0 || tracker.trackedPendingXhrs > 0) {
1495
+ return false;
1496
+ }
1497
+ const lastActivityAt = Math.max(
1498
+ tracker.installedAt,
1499
+ tracker.lastMutationAt,
1500
+ tracker.lastTrackedNetworkActivityAt
1501
+ );
1502
+ return tracker.now - lastActivityAt >= quietWindowMs;
1503
+ }
1504
+
1505
+ // ../browser-core/src/action-boundary.ts
1506
+ var CROSS_DOCUMENT_INTERACTION_TIMEOUT_MS = 3e4;
1507
+ var CROSS_DOCUMENT_DETECTION_WINDOW_MS = 500;
1508
+ async function waitForActionBoundary(input) {
1509
+ if (input.timeoutMs <= 0) {
1510
+ return {
1511
+ trigger: "dom-action",
1512
+ crossDocument: false,
1513
+ bootstrapSettled: false,
1514
+ timedOutPhase: "bootstrap"
1515
+ };
1516
+ }
1517
+ const deadline = Date.now() + input.timeoutMs;
1518
+ const crossDocumentDetectionDeadline = input.snapshot === void 0 ? void 0 : Math.min(deadline, Date.now() + CROSS_DOCUMENT_DETECTION_WINDOW_MS);
1519
+ const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_ACTION_BOUNDARY_POLL_INTERVAL_MS;
1520
+ let trigger = "dom-action";
1521
+ let crossDocument = false;
1522
+ let sameDocumentAsyncActivity = false;
1523
+ while (Date.now() < deadline) {
1524
+ input.throwBackgroundError();
1525
+ if (input.isPageClosed()) {
1526
+ return {
1527
+ trigger,
1528
+ crossDocument,
1529
+ bootstrapSettled: true
1530
+ };
1531
+ }
1532
+ if (input.signal?.aborted) {
1533
+ if (isTimeoutAbort(input.signal.reason) && Date.now() >= deadline) {
1534
+ return {
1535
+ trigger,
1536
+ crossDocument,
1537
+ bootstrapSettled: false,
1538
+ timedOutPhase: "bootstrap"
1539
+ };
1540
+ }
1541
+ throw abortError(input.signal);
1542
+ }
1543
+ const currentDocumentRef = input.getCurrentMainFrameDocumentRef();
1544
+ const currentPageUrl = input.getCurrentPageUrl?.();
1545
+ if (input.snapshot !== void 0 && currentDocumentRef !== void 0 && currentDocumentRef !== input.snapshot.documentRef) {
1546
+ trigger = "navigation";
1547
+ crossDocument = true;
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;
1559
+ }
1560
+ if (!crossDocument && crossDocumentDetectionDeadline !== void 0 && Date.now() >= crossDocumentDetectionDeadline) {
1561
+ return {
1562
+ trigger,
1563
+ crossDocument,
1564
+ bootstrapSettled: true
1565
+ };
1566
+ }
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) {
1579
+ return {
1580
+ trigger,
1581
+ crossDocument,
1582
+ bootstrapSettled: true
1583
+ };
1584
+ }
1585
+ await delay(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
1586
+ }
1587
+ return {
1588
+ trigger,
1589
+ crossDocument,
1590
+ bootstrapSettled: false,
1591
+ timedOutPhase: "bootstrap"
1592
+ };
1593
+ }
1594
+ function abortError(signal) {
1595
+ return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
1596
+ }
1597
+ async function delay(ms) {
1598
+ if (ms <= 0) {
1599
+ return;
1600
+ }
1601
+ await new Promise((resolve) => {
1602
+ setTimeout(resolve, ms);
1603
+ });
1604
+ }
1605
+ function isTimeoutAbort(reason) {
1606
+ return typeof reason === "object" && reason !== null && "code" in reason && reason.code === "timeout";
1607
+ }
1608
+
1260
1609
  // ../protocol/src/computer-use-bridge.ts
1261
1610
  var OPENSTEER_COMPUTER_USE_BRIDGE_SYMBOL = /* @__PURE__ */ Symbol.for("@opensteer/computer-use-bridge");
1262
1611
 
@@ -1741,6 +2090,7 @@ function createPlaywrightComputerUseBridge(context) {
1741
2090
  const startedAt = Date.now();
1742
2091
  const actionController = context.resolveController(input.pageRef);
1743
2092
  const action = input.action;
2093
+ let boundary;
1744
2094
  let actionMs = 0;
1745
2095
  let waitMs = 0;
1746
2096
  const actionStartedAt = Date.now();
@@ -1806,7 +2156,12 @@ function createPlaywrightComputerUseBridge(context) {
1806
2156
  await context.flushPendingPageTasks(actionController.sessionRef);
1807
2157
  if (action.type !== "screenshot" && action.type !== "wait") {
1808
2158
  const waitStartedAt = Date.now();
1809
- await input.policySettle(actionController.pageRef);
2159
+ boundary = await context.settleActionBoundary(actionController, {
2160
+ signal: input.signal,
2161
+ ...input.snapshot === void 0 ? {} : { snapshot: input.snapshot },
2162
+ remainingMs: input.remainingMs,
2163
+ policySettle: input.policySettle
2164
+ });
1810
2165
  waitMs = Date.now() - waitStartedAt;
1811
2166
  } else if (action.type === "wait") {
1812
2167
  waitMs = actionMs;
@@ -1818,7 +2173,11 @@ function createPlaywrightComputerUseBridge(context) {
1818
2173
  let resultController = context.resolveController(resultPageRef);
1819
2174
  if (action.type !== "screenshot" && action.type !== "wait" && resultController.pageRef !== actionController.pageRef) {
1820
2175
  const popupWaitStartedAt = Date.now();
1821
- await input.policySettle(resultController.pageRef);
2176
+ await context.settleActionBoundary(resultController, {
2177
+ signal: input.signal,
2178
+ remainingMs: input.remainingMs,
2179
+ policySettle: input.policySettle
2180
+ });
1822
2181
  waitMs += Date.now() - popupWaitStartedAt;
1823
2182
  await context.flushPendingPageTasks(actionController.sessionRef);
1824
2183
  resultController = context.resolveController(resultController.pageRef);
@@ -1842,7 +2201,8 @@ function createPlaywrightComputerUseBridge(context) {
1842
2201
  actionMs,
1843
2202
  waitMs,
1844
2203
  totalMs: Date.now() - startedAt
1845
- }
2204
+ },
2205
+ ...boundary === void 0 ? {} : { boundary }
1846
2206
  };
1847
2207
  }
1848
2208
  };
@@ -2875,10 +3235,12 @@ function createPlaywrightDomActionBridge(context) {
2875
3235
  },
2876
3236
  async finalizeDomAction(pageRef, options) {
2877
3237
  const controller = context.resolveController(pageRef);
2878
- await context.flushPendingPageTasks(controller.sessionRef);
2879
- await options.policySettle(pageRef);
2880
- await context.flushPendingPageTasks(controller.sessionRef);
2881
- await context.flushDomUpdateTask(controller);
3238
+ return context.settleActionBoundary(controller, {
3239
+ signal: options.signal,
3240
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
3241
+ remainingMs: options.remainingMs,
3242
+ policySettle: options.policySettle
3243
+ });
2882
3244
  }
2883
3245
  };
2884
3246
  }
@@ -3146,6 +3508,210 @@ async function releaseObject(controller, objectId) {
3146
3508
  await controller.cdp.send("Runtime.releaseObject", { objectId }).catch(() => void 0);
3147
3509
  }
3148
3510
 
3511
+ // src/action-settle.ts
3512
+ var DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS = CROSS_DOCUMENT_INTERACTION_TIMEOUT_MS;
3513
+ var DEFAULT_PLAYWRIGHT_POST_LOAD_CAPTURE_WINDOW_MS = 1e3;
3514
+ function clampPlaywrightActionSettleTimeout(timeoutMs) {
3515
+ if (timeoutMs === void 0) {
3516
+ return DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS;
3517
+ }
3518
+ return Math.max(0, Math.min(DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS, timeoutMs));
3519
+ }
3520
+ function createPlaywrightActionSettler(context) {
3521
+ const installScript = buildPostLoadTrackerInstallScript();
3522
+ const beginExpression = buildPostLoadTrackerBeginExpression();
3523
+ const freezeExpression = buildPostLoadTrackerFreezeExpression();
3524
+ const readExpression = buildPostLoadTrackerReadExpression();
3525
+ async function installTracker(controller) {
3526
+ if (!controller.settleTrackerRegistered) {
3527
+ await controller.page.addInitScript(installScript);
3528
+ controller.settleTrackerRegistered = true;
3529
+ }
3530
+ try {
3531
+ await controller.cdp.send("Runtime.evaluate", {
3532
+ expression: installScript,
3533
+ returnByValue: true,
3534
+ awaitPromise: true
3535
+ });
3536
+ } catch (error) {
3537
+ if (controller.lifecycleState === "closed" || isContextClosedError(error)) {
3538
+ return;
3539
+ }
3540
+ throw normalizePlaywrightError(error, controller.pageRef);
3541
+ }
3542
+ }
3543
+ async function readTrackerState(controller) {
3544
+ try {
3545
+ const evaluated = await controller.cdp.send("Runtime.evaluate", {
3546
+ expression: readExpression,
3547
+ returnByValue: true,
3548
+ awaitPromise: true
3549
+ });
3550
+ return normalizePostLoadTrackerState(evaluated.result?.value);
3551
+ } catch (error) {
3552
+ if (isIgnorableTrackerReadError(error)) {
3553
+ return void 0;
3554
+ }
3555
+ throw normalizePlaywrightError(error, controller.pageRef);
3556
+ }
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
+ }
3631
+ async function settle(options) {
3632
+ const { controller, timeoutMs, signal, snapshot, policySettle } = options;
3633
+ if (timeoutMs <= 0) {
3634
+ return {
3635
+ trigger: "dom-action",
3636
+ crossDocument: false,
3637
+ bootstrapSettled: false,
3638
+ timedOutPhase: "bootstrap"
3639
+ };
3640
+ }
3641
+ await context.flushPendingPageTasks(controller.sessionRef);
3642
+ let boundary;
3643
+ if (snapshot === void 0) {
3644
+ if (policySettle) {
3645
+ if (signal?.aborted) {
3646
+ throw signal.reason ?? abortError2();
3647
+ }
3648
+ await policySettle(controller.pageRef, "dom-action");
3649
+ }
3650
+ boundary = {
3651
+ trigger: "dom-action",
3652
+ crossDocument: false,
3653
+ bootstrapSettled: true
3654
+ };
3655
+ } else {
3656
+ await installTracker(controller);
3657
+ boundary = await waitForActionBoundary({
3658
+ timeoutMs,
3659
+ ...signal === void 0 ? {} : { signal },
3660
+ snapshot,
3661
+ getCurrentMainFrameDocumentRef: () => context.getMainFrameDocumentRef(controller),
3662
+ getCurrentPageUrl: () => controller.page.url(),
3663
+ isCurrentMainFrameBootstrapSettled: () => context.isCurrentMainFrameBootstrapSettled(controller),
3664
+ readTrackerState: () => readTrackerState(controller),
3665
+ throwBackgroundError: () => context.throwBackgroundError(controller),
3666
+ isPageClosed: () => controller.lifecycleState === "closed"
3667
+ });
3668
+ if (policySettle) {
3669
+ await policySettle(controller.pageRef, boundary.trigger);
3670
+ }
3671
+ }
3672
+ await context.flushPendingPageTasks(controller.sessionRef);
3673
+ if (controller.lifecycleState !== "closed") {
3674
+ await context.flushDomUpdateTask(controller);
3675
+ }
3676
+ return boundary;
3677
+ }
3678
+ return {
3679
+ captureSnapshot,
3680
+ installTracker,
3681
+ waitForPostLoadQuiet,
3682
+ settle
3683
+ };
3684
+ }
3685
+ function abortError2() {
3686
+ return new DOMException("The operation was aborted", "AbortError");
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
+ }
3709
+ function isIgnorableTrackerReadError(error) {
3710
+ return isContextClosedError(error) || error instanceof Error && /Execution context was destroyed|Cannot find context|Inspected target navigated or closed/i.test(
3711
+ error.message
3712
+ );
3713
+ }
3714
+
3149
3715
  // src/storage-capture.ts
3150
3716
  var ACTIVATION_PATH = "/__opensteer_storage_capture__";
3151
3717
  var ACTIVATION_TIMEOUT_MS = 15e3;
@@ -3337,6 +3903,71 @@ function cloneKeyPath(keyPath) {
3337
3903
  }
3338
3904
 
3339
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
+ })();`;
3340
3971
  var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3341
3972
  capabilities = PLAYWRIGHT_BROWSER_CORE_CAPABILITIES;
3342
3973
  browser;
@@ -3352,6 +3983,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3352
3983
  pageByPlaywrightPage = /* @__PURE__ */ new WeakMap();
3353
3984
  pendingPopupOpeners = /* @__PURE__ */ new WeakMap();
3354
3985
  preassignedPopupPageRefs = /* @__PURE__ */ new WeakMap();
3986
+ actionSettler = createPlaywrightActionSettler({
3987
+ flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3988
+ flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
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,
3991
+ throwBackgroundError: (controller) => this.throwBackgroundError(controller)
3992
+ });
3355
3993
  pageCounter = 0;
3356
3994
  frameCounter = 0;
3357
3995
  documentCounter = 0;
@@ -3407,6 +4045,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3407
4045
  resolveController: (pageRef) => this.requirePage(pageRef),
3408
4046
  flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3409
4047
  flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
4048
+ settleActionBoundary: (controller, options) => this.actionSettler.settle({
4049
+ controller,
4050
+ timeoutMs: clampPlaywrightActionSettleTimeout(options.remainingMs()),
4051
+ ...options.signal === void 0 ? {} : { signal: options.signal },
4052
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
4053
+ ...options.policySettle === void 0 ? {} : { policySettle: options.policySettle }
4054
+ }),
3410
4055
  requireMainFrame: (controller) => this.requireMainFrame(controller),
3411
4056
  drainQueuedEvents: (pageRef) => this.drainQueuedEvents(pageRef),
3412
4057
  withModifiers: (page, modifiers, action) => this.withModifiers(page, modifiers, action)
@@ -3418,6 +4063,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3418
4063
  resolveController: (pageRef) => this.requirePage(pageRef),
3419
4064
  flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3420
4065
  flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
4066
+ settleActionBoundary: (controller, options) => this.actionSettler.settle({
4067
+ controller,
4068
+ timeoutMs: clampPlaywrightActionSettleTimeout(options.remainingMs()),
4069
+ ...options.signal === void 0 ? {} : { signal: options.signal },
4070
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
4071
+ ...options.policySettle === void 0 ? {} : { policySettle: options.policySettle }
4072
+ }),
3421
4073
  locateBackendNode: (document, backendNodeId) => createNodeLocator(
3422
4074
  document.documentRef,
3423
4075
  document.documentEpoch,
@@ -3748,11 +4400,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3748
4400
  });
3749
4401
  await this.flushPendingPageTasks(controller.sessionRef);
3750
4402
  const mainFrame = this.requireMainFrame(controller);
4403
+ const events = mergeDistinctStepEvents([
4404
+ ...this.drainQueuedEvents(controller.pageRef),
4405
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4406
+ ]);
3751
4407
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3752
4408
  frameRef: mainFrame.frameRef,
3753
4409
  documentRef: mainFrame.currentDocument.documentRef,
3754
4410
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3755
- events: this.drainQueuedEvents(controller.pageRef),
4411
+ events,
3756
4412
  data: hit
3757
4413
  });
3758
4414
  }
@@ -3772,11 +4428,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3772
4428
  await this.flushPendingPageTasks(controller.sessionRef);
3773
4429
  await this.flushDomUpdateTask(controller);
3774
4430
  const mainFrame = this.requireMainFrame(controller);
4431
+ const events = mergeDistinctStepEvents([
4432
+ ...this.drainQueuedEvents(controller.pageRef),
4433
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4434
+ ]);
3775
4435
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3776
4436
  frameRef: mainFrame.frameRef,
3777
4437
  documentRef: mainFrame.currentDocument.documentRef,
3778
4438
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3779
- events: this.drainQueuedEvents(controller.pageRef),
4439
+ events,
3780
4440
  data: void 0
3781
4441
  });
3782
4442
  }
@@ -3788,11 +4448,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3788
4448
  });
3789
4449
  await this.flushPendingPageTasks(controller.sessionRef);
3790
4450
  const mainFrame = this.requireMainFrame(controller);
4451
+ const events = mergeDistinctStepEvents([
4452
+ ...this.drainQueuedEvents(controller.pageRef),
4453
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4454
+ ]);
3791
4455
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3792
4456
  frameRef: mainFrame.frameRef,
3793
4457
  documentRef: mainFrame.currentDocument.documentRef,
3794
4458
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3795
- events: this.drainQueuedEvents(controller.pageRef),
4459
+ events,
3796
4460
  data: void 0
3797
4461
  });
3798
4462
  }
@@ -3802,11 +4466,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3802
4466
  await controller.page.keyboard.type(input.text);
3803
4467
  await this.flushPendingPageTasks(controller.sessionRef);
3804
4468
  const mainFrame = this.requireMainFrame(controller);
4469
+ const events = mergeDistinctStepEvents([
4470
+ ...this.drainQueuedEvents(controller.pageRef),
4471
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4472
+ ]);
3805
4473
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3806
4474
  frameRef: mainFrame.frameRef,
3807
4475
  documentRef: mainFrame.currentDocument.documentRef,
3808
4476
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3809
- events: this.drainQueuedEvents(controller.pageRef),
4477
+ events,
3810
4478
  data: void 0
3811
4479
  });
3812
4480
  }
@@ -3944,6 +4612,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3944
4612
  }
3945
4613
  return infos;
3946
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
+ }
3947
4622
  async listFrames(input) {
3948
4623
  const controller = this.requirePage(input.pageRef);
3949
4624
  await this.flushDomUpdateTask(controller);
@@ -3995,6 +4670,12 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3995
4670
  (contentDocIndex) => resolveCapturedContentDocumentRef(controller.framesByCdpId, captured, contentDocIndex)
3996
4671
  );
3997
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
+ }
3998
4679
  async waitForVisualStability(input) {
3999
4680
  const controller = this.requirePage(input.pageRef);
4000
4681
  await this.flushDomUpdateTask(controller);
@@ -4005,6 +4686,21 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4005
4686
  });
4006
4687
  await this.flushDomUpdateTask(controller);
4007
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
+ }
4008
4704
  async readText(input) {
4009
4705
  const document = this.requireDocument(input.documentRef);
4010
4706
  const controller = this.requirePage(document.pageRef);
@@ -4512,6 +5208,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4512
5208
  backgroundTasks: /* @__PURE__ */ new Set(),
4513
5209
  domUpdateTask: void 0,
4514
5210
  backgroundError: void 0,
5211
+ settleTrackerRegistered: false,
4515
5212
  openerPageRef: void 0,
4516
5213
  mainFrameRef: void 0,
4517
5214
  lifecycleState: "open",
@@ -4525,9 +5222,12 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4525
5222
  session.activePageRef = pageRef;
4526
5223
  await cdp.send("Page.enable", { enableFileChooserOpenedEvent: true });
4527
5224
  await cdp.send("Network.enable");
5225
+ await cdp.send("Runtime.enable");
4528
5226
  await cdp.send("DOM.enable", { includeWhitespace: "none" });
4529
5227
  await cdp.send("DOMStorage.enable");
4530
5228
  await cdp.send("DOM.getDocument", { depth: 0 });
5229
+ await this.installRuntimeEventRecorder(page);
5230
+ await this.actionSettler.installTracker(controller);
4531
5231
  cdp.on(
4532
5232
  "Page.frameAttached",
4533
5233
  (payload) => this.runControllerEvent(
@@ -4602,9 +5302,19 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4602
5302
  "DOM.documentUpdated",
4603
5303
  () => this.runControllerEvent(controller, () => this.handleDocumentUpdated(controller))
4604
5304
  );
4605
- page.on(
4606
- "console",
4607
- (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
+ )
4608
5318
  );
4609
5319
  page.on(
4610
5320
  "popup",
@@ -4631,10 +5341,6 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4631
5341
  "download",
4632
5342
  (download) => this.runControllerEvent(controller, () => this.handleDownload(controller, download))
4633
5343
  );
4634
- page.on(
4635
- "pageerror",
4636
- (error) => this.runControllerEvent(controller, () => this.handlePageError(controller, error))
4637
- );
4638
5344
  page.on(
4639
5345
  "request",
4640
5346
  (request) => this.runControllerEvent(controller, () => this.handlePlaywrightRequest(controller, request))
@@ -4646,6 +5352,10 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4646
5352
  () => this.handlePlaywrightResponse(controller, response)
4647
5353
  )
4648
5354
  );
5355
+ page.on(
5356
+ "domcontentloaded",
5357
+ () => this.runControllerEvent(controller, () => this.handlePageDomContentLoaded(controller))
5358
+ );
4649
5359
  page.on("close", () => this.handleUnexpectedPageClose(controller));
4650
5360
  const frameTree = await cdp.send("Page.getFrameTree");
4651
5361
  this.syncFrameTree(controller, frameTree.frameTree);
@@ -4698,6 +5408,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4698
5408
  documentRef,
4699
5409
  documentEpoch: createDocumentEpoch(0),
4700
5410
  url: "about:blank",
5411
+ domContentLoadedAt: void 0,
4701
5412
  parentDocumentRef: parent?.currentDocument.documentRef,
4702
5413
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4703
5414
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4756,6 +5467,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4756
5467
  documentRef: nextDocumentRef,
4757
5468
  documentEpoch: createDocumentEpoch(0),
4758
5469
  url: combineFrameUrl(frame.url, frame.urlFragment),
5470
+ domContentLoadedAt: void 0,
4759
5471
  parentDocumentRef: parent?.currentDocument.documentRef,
4760
5472
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4761
5473
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4785,6 +5497,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4785
5497
  handleDocumentUpdated(controller) {
4786
5498
  this.queueDocumentReconciliation(controller);
4787
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
+ }
4788
5507
  syncFrameTree(controller, tree) {
4789
5508
  const visit = (node, parentFrameRef) => {
4790
5509
  const existing = controller.framesByCdpId.get(node.frame.id);
@@ -4798,6 +5517,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4798
5517
  documentRef,
4799
5518
  documentEpoch: createDocumentEpoch(0),
4800
5519
  url: combineFrameUrl(node.frame.url, node.frame.urlFragment),
5520
+ domContentLoadedAt: void 0,
4801
5521
  parentDocumentRef: parentFrameRef === void 0 ? void 0 : this.requireFrame(parentFrameRef).currentDocument.documentRef,
4802
5522
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4803
5523
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4856,19 +5576,20 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4856
5576
  }
4857
5577
  }
4858
5578
  }
4859
- handleConsole(controller, message) {
5579
+ handleRuntimeConsole(controller, payload) {
5580
+ const callFrame = payload.stackTrace?.callFrames?.[0];
4860
5581
  this.queueEvent(
4861
5582
  controller.pageRef,
4862
5583
  this.createEvent({
4863
5584
  kind: "console",
4864
5585
  sessionRef: controller.sessionRef,
4865
5586
  pageRef: controller.pageRef,
4866
- level: normalizeConsoleLevel(message.type()),
4867
- text: message.text(),
5587
+ level: normalizeRuntimeConsoleLevel(payload.type),
5588
+ text: formatRuntimeConsoleText(payload),
4868
5589
  location: {
4869
- url: message.location().url,
4870
- lineNumber: message.location().lineNumber,
4871
- columnNumber: message.location().columnNumber
5590
+ url: callFrame?.url ?? "",
5591
+ lineNumber: callFrame?.lineNumber ?? 0,
5592
+ columnNumber: callFrame?.columnNumber ?? 0
4872
5593
  }
4873
5594
  })
4874
5595
  );
@@ -4949,15 +5670,18 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4949
5670
  })
4950
5671
  );
4951
5672
  }
4952
- handlePageError(controller, error) {
5673
+ handleRuntimeException(controller, payload) {
5674
+ const details = payload.exceptionDetails;
5675
+ const message = formatRuntimeExceptionMessage(details);
5676
+ const stack = formatRuntimeExceptionStack(details);
4953
5677
  this.queueEvent(
4954
5678
  controller.pageRef,
4955
5679
  this.createEvent({
4956
5680
  kind: "page-error",
4957
5681
  sessionRef: controller.sessionRef,
4958
5682
  pageRef: controller.pageRef,
4959
- message: error.message,
4960
- ...error.stack === void 0 ? {} : { stack: error.stack }
5683
+ message,
5684
+ ...stack === void 0 ? {} : { stack }
4961
5685
  })
4962
5686
  );
4963
5687
  }
@@ -5679,10 +6403,11 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5679
6403
  }
5680
6404
  }
5681
6405
  createEvent(value) {
6406
+ const { timestamp, ...event } = value;
5682
6407
  return {
5683
- ...value,
6408
+ ...event,
5684
6409
  eventId: `event:${++this.eventCounter}`,
5685
- timestamp: Date.now()
6410
+ timestamp: timestamp ?? Date.now()
5686
6411
  };
5687
6412
  }
5688
6413
  queueEvent(pageRef, event) {
@@ -5692,6 +6417,37 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5692
6417
  }
5693
6418
  controller.queuedEvents.push(event);
5694
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
+ }
5695
6451
  drainQueuedEvents(pageRef) {
5696
6452
  const controller = this.requirePage(pageRef);
5697
6453
  const events = controller.queuedEvents.splice(0, controller.queuedEvents.length);
@@ -5938,6 +6694,104 @@ function withTimeout(promise, timeoutMs) {
5938
6694
  })
5939
6695
  ]);
5940
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
+ }
5941
6795
 
5942
6796
  export { PlaywrightBrowserCoreEngine, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine, disconnectPlaywrightChromiumBrowser };
5943
6797
  //# sourceMappingURL=index.js.map