@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.cjs CHANGED
@@ -1259,6 +1259,355 @@ function sleep(ms) {
1259
1259
  return new Promise((resolve) => setTimeout(resolve, ms));
1260
1260
  }
1261
1261
 
1262
+ // ../browser-core/src/post-load-tracker.ts
1263
+ var DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS = 400;
1264
+ var DEFAULT_ACTION_BOUNDARY_POLL_INTERVAL_MS = 100;
1265
+ function isRecord(value) {
1266
+ return typeof value === "object" && value !== null;
1267
+ }
1268
+ function readFiniteNumber(value) {
1269
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1270
+ }
1271
+ function readNonNegativeNumber(value) {
1272
+ const parsed = readFiniteNumber(value);
1273
+ return parsed === void 0 || parsed < 0 ? 0 : parsed;
1274
+ }
1275
+ function normalizePostLoadTrackerState(value) {
1276
+ if (!isRecord(value)) {
1277
+ return void 0;
1278
+ }
1279
+ const installedAt = readFiniteNumber(value.installedAt);
1280
+ const lastMutationAt = readFiniteNumber(value.lastMutationAt);
1281
+ const lastNetworkActivityAt = readFiniteNumber(value.lastNetworkActivityAt);
1282
+ const lastTrackedNetworkActivityAt = readFiniteNumber(value.lastTrackedNetworkActivityAt);
1283
+ const now = readFiniteNumber(value.now);
1284
+ const readyState = typeof value.readyState === "string" ? value.readyState : void 0;
1285
+ if (installedAt === void 0 || lastMutationAt === void 0 || lastNetworkActivityAt === void 0 || lastTrackedNetworkActivityAt === void 0 || now === void 0 || readyState === void 0) {
1286
+ return void 0;
1287
+ }
1288
+ return {
1289
+ installedAt,
1290
+ lastMutationAt,
1291
+ lastNetworkActivityAt,
1292
+ lastTrackedNetworkActivityAt,
1293
+ now,
1294
+ pendingFetches: readNonNegativeNumber(value.pendingFetches),
1295
+ pendingTimeouts: readNonNegativeNumber(value.pendingTimeouts),
1296
+ pendingXhrs: readNonNegativeNumber(value.pendingXhrs),
1297
+ trackedPendingFetches: readNonNegativeNumber(value.trackedPendingFetches),
1298
+ trackedPendingXhrs: readNonNegativeNumber(value.trackedPendingXhrs),
1299
+ collecting: value.collecting === true,
1300
+ readyState
1301
+ };
1302
+ }
1303
+ function buildPostLoadTrackerInstallScript() {
1304
+ return `(() => {
1305
+ const globalObject = globalThis;
1306
+ if (globalObject.__opensteerActionBoundaryTrackerInstalled) {
1307
+ return true;
1308
+ }
1309
+
1310
+ const tracker = {
1311
+ installedAt: performance.now(),
1312
+ lastMutationAt: performance.now(),
1313
+ lastNetworkActivityAt: performance.now(),
1314
+ lastTrackedNetworkActivityAt: performance.now(),
1315
+ pendingFetches: 0,
1316
+ pendingTimeouts: 0,
1317
+ pendingXhrs: 0,
1318
+ trackedPendingFetches: 0,
1319
+ trackedPendingXhrs: 0,
1320
+ collecting: true,
1321
+ readyState: document.readyState,
1322
+ };
1323
+ globalObject.__opensteerActionBoundaryTrackerInstalled = true;
1324
+ globalObject.__opensteerActionBoundaryTracker = tracker;
1325
+
1326
+ const markMutation = () => {
1327
+ tracker.lastMutationAt = performance.now();
1328
+ tracker.readyState = document.readyState;
1329
+ };
1330
+ const markNetwork = () => {
1331
+ tracker.lastNetworkActivityAt = performance.now();
1332
+ tracker.readyState = document.readyState;
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
+ };
1346
+
1347
+ const startObserver = () => {
1348
+ const target = document.documentElement ?? document;
1349
+ if (!(target instanceof Node)) {
1350
+ return;
1351
+ }
1352
+ const observer = new MutationObserver(markMutation);
1353
+ observer.observe(target, {
1354
+ subtree: true,
1355
+ childList: true,
1356
+ characterData: true,
1357
+ attributes: true,
1358
+ });
1359
+ markMutation();
1360
+ };
1361
+
1362
+ if (document.documentElement) {
1363
+ startObserver();
1364
+ } else {
1365
+ document.addEventListener("DOMContentLoaded", startObserver, { once: true });
1366
+ }
1367
+
1368
+ document.addEventListener("readystatechange", markMutation);
1369
+ addEventListener("load", markMutation, { once: true });
1370
+
1371
+ if (typeof globalObject.fetch === "function") {
1372
+ const nativeFetch = globalObject.fetch.bind(globalObject);
1373
+ globalObject.fetch = (...args) => {
1374
+ const tracked = tracker.collecting === true;
1375
+ tracker.pendingFetches += 1;
1376
+ if (tracked) {
1377
+ tracker.trackedPendingFetches += 1;
1378
+ markTrackedNetwork();
1379
+ } else {
1380
+ markNetwork();
1381
+ }
1382
+ return nativeFetch(...args)
1383
+ .finally(() => {
1384
+ tracker.pendingFetches = Math.max(0, tracker.pendingFetches - 1);
1385
+ if (tracked) {
1386
+ tracker.trackedPendingFetches = Math.max(0, tracker.trackedPendingFetches - 1);
1387
+ markTrackedNetwork();
1388
+ } else {
1389
+ markNetwork();
1390
+ }
1391
+ });
1392
+ };
1393
+ }
1394
+
1395
+ if (typeof globalObject.XMLHttpRequest === "function") {
1396
+ const NativeXMLHttpRequest = globalObject.XMLHttpRequest;
1397
+ const nativeSend = NativeXMLHttpRequest.prototype.send;
1398
+ NativeXMLHttpRequest.prototype.send = function(...args) {
1399
+ const tracked = tracker.collecting === true;
1400
+ tracker.pendingXhrs += 1;
1401
+ if (tracked) {
1402
+ tracker.trackedPendingXhrs += 1;
1403
+ markTrackedNetwork();
1404
+ } else {
1405
+ markNetwork();
1406
+ }
1407
+ const finalize = () => {
1408
+ this.removeEventListener("loadend", finalize);
1409
+ tracker.pendingXhrs = Math.max(0, tracker.pendingXhrs - 1);
1410
+ if (tracked) {
1411
+ tracker.trackedPendingXhrs = Math.max(0, tracker.trackedPendingXhrs - 1);
1412
+ markTrackedNetwork();
1413
+ } else {
1414
+ markNetwork();
1415
+ }
1416
+ };
1417
+ this.addEventListener("loadend", finalize, { once: true });
1418
+ return nativeSend.apply(this, args);
1419
+ };
1420
+ }
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
+
1432
+ return true;
1433
+ })()`;
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
+ }
1453
+ function buildPostLoadTrackerReadExpression() {
1454
+ return `(() => {
1455
+ const tracker = globalThis.__opensteerActionBoundaryTracker;
1456
+ if (!tracker) {
1457
+ return null;
1458
+ }
1459
+
1460
+ return {
1461
+ installedAt: Number(tracker.installedAt ?? 0),
1462
+ lastMutationAt: Number(tracker.lastMutationAt ?? 0),
1463
+ lastNetworkActivityAt: Number(tracker.lastNetworkActivityAt ?? 0),
1464
+ lastTrackedNetworkActivityAt: Number(tracker.lastTrackedNetworkActivityAt ?? 0),
1465
+ now: Number(performance.now()),
1466
+ pendingFetches: Number(tracker.pendingFetches ?? 0),
1467
+ pendingTimeouts: Number(tracker.pendingTimeouts ?? 0),
1468
+ pendingXhrs: Number(tracker.pendingXhrs ?? 0),
1469
+ trackedPendingFetches: Number(tracker.trackedPendingFetches ?? 0),
1470
+ trackedPendingXhrs: Number(tracker.trackedPendingXhrs ?? 0),
1471
+ collecting: tracker.collecting === true,
1472
+ readyState: String(document.readyState),
1473
+ };
1474
+ })()`;
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
+ }
1489
+ function postLoadTrackerIsSettled(tracker, quietWindowMs = DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS) {
1490
+ if (!tracker) {
1491
+ return false;
1492
+ }
1493
+ if (tracker.readyState !== "complete") {
1494
+ return false;
1495
+ }
1496
+ if (tracker.trackedPendingFetches > 0 || tracker.trackedPendingXhrs > 0) {
1497
+ return false;
1498
+ }
1499
+ const lastActivityAt = Math.max(
1500
+ tracker.installedAt,
1501
+ tracker.lastMutationAt,
1502
+ tracker.lastTrackedNetworkActivityAt
1503
+ );
1504
+ return tracker.now - lastActivityAt >= quietWindowMs;
1505
+ }
1506
+
1507
+ // ../browser-core/src/action-boundary.ts
1508
+ var CROSS_DOCUMENT_INTERACTION_TIMEOUT_MS = 3e4;
1509
+ var CROSS_DOCUMENT_DETECTION_WINDOW_MS = 500;
1510
+ async function waitForActionBoundary(input) {
1511
+ if (input.timeoutMs <= 0) {
1512
+ return {
1513
+ trigger: "dom-action",
1514
+ crossDocument: false,
1515
+ bootstrapSettled: false,
1516
+ timedOutPhase: "bootstrap"
1517
+ };
1518
+ }
1519
+ const deadline = Date.now() + input.timeoutMs;
1520
+ const crossDocumentDetectionDeadline = input.snapshot === void 0 ? void 0 : Math.min(deadline, Date.now() + CROSS_DOCUMENT_DETECTION_WINDOW_MS);
1521
+ const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_ACTION_BOUNDARY_POLL_INTERVAL_MS;
1522
+ let trigger = "dom-action";
1523
+ let crossDocument = false;
1524
+ let sameDocumentAsyncActivity = false;
1525
+ while (Date.now() < deadline) {
1526
+ input.throwBackgroundError();
1527
+ if (input.isPageClosed()) {
1528
+ return {
1529
+ trigger,
1530
+ crossDocument,
1531
+ bootstrapSettled: true
1532
+ };
1533
+ }
1534
+ if (input.signal?.aborted) {
1535
+ if (isTimeoutAbort(input.signal.reason) && Date.now() >= deadline) {
1536
+ return {
1537
+ trigger,
1538
+ crossDocument,
1539
+ bootstrapSettled: false,
1540
+ timedOutPhase: "bootstrap"
1541
+ };
1542
+ }
1543
+ throw abortError(input.signal);
1544
+ }
1545
+ const currentDocumentRef = input.getCurrentMainFrameDocumentRef();
1546
+ const currentPageUrl = input.getCurrentPageUrl?.();
1547
+ if (input.snapshot !== void 0 && currentDocumentRef !== void 0 && currentDocumentRef !== input.snapshot.documentRef) {
1548
+ trigger = "navigation";
1549
+ crossDocument = true;
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;
1561
+ }
1562
+ if (!crossDocument && crossDocumentDetectionDeadline !== void 0 && Date.now() >= crossDocumentDetectionDeadline) {
1563
+ return {
1564
+ trigger,
1565
+ crossDocument,
1566
+ bootstrapSettled: true
1567
+ };
1568
+ }
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) {
1581
+ return {
1582
+ trigger,
1583
+ crossDocument,
1584
+ bootstrapSettled: true
1585
+ };
1586
+ }
1587
+ await delay(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
1588
+ }
1589
+ return {
1590
+ trigger,
1591
+ crossDocument,
1592
+ bootstrapSettled: false,
1593
+ timedOutPhase: "bootstrap"
1594
+ };
1595
+ }
1596
+ function abortError(signal) {
1597
+ return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
1598
+ }
1599
+ async function delay(ms) {
1600
+ if (ms <= 0) {
1601
+ return;
1602
+ }
1603
+ await new Promise((resolve) => {
1604
+ setTimeout(resolve, ms);
1605
+ });
1606
+ }
1607
+ function isTimeoutAbort(reason) {
1608
+ return typeof reason === "object" && reason !== null && "code" in reason && reason.code === "timeout";
1609
+ }
1610
+
1262
1611
  // ../protocol/src/computer-use-bridge.ts
1263
1612
  var OPENSTEER_COMPUTER_USE_BRIDGE_SYMBOL = /* @__PURE__ */ Symbol.for("@opensteer/computer-use-bridge");
1264
1613
 
@@ -1743,6 +2092,7 @@ function createPlaywrightComputerUseBridge(context) {
1743
2092
  const startedAt = Date.now();
1744
2093
  const actionController = context.resolveController(input.pageRef);
1745
2094
  const action = input.action;
2095
+ let boundary;
1746
2096
  let actionMs = 0;
1747
2097
  let waitMs = 0;
1748
2098
  const actionStartedAt = Date.now();
@@ -1808,7 +2158,12 @@ function createPlaywrightComputerUseBridge(context) {
1808
2158
  await context.flushPendingPageTasks(actionController.sessionRef);
1809
2159
  if (action.type !== "screenshot" && action.type !== "wait") {
1810
2160
  const waitStartedAt = Date.now();
1811
- await input.policySettle(actionController.pageRef);
2161
+ boundary = await context.settleActionBoundary(actionController, {
2162
+ signal: input.signal,
2163
+ ...input.snapshot === void 0 ? {} : { snapshot: input.snapshot },
2164
+ remainingMs: input.remainingMs,
2165
+ policySettle: input.policySettle
2166
+ });
1812
2167
  waitMs = Date.now() - waitStartedAt;
1813
2168
  } else if (action.type === "wait") {
1814
2169
  waitMs = actionMs;
@@ -1820,7 +2175,11 @@ function createPlaywrightComputerUseBridge(context) {
1820
2175
  let resultController = context.resolveController(resultPageRef);
1821
2176
  if (action.type !== "screenshot" && action.type !== "wait" && resultController.pageRef !== actionController.pageRef) {
1822
2177
  const popupWaitStartedAt = Date.now();
1823
- await input.policySettle(resultController.pageRef);
2178
+ await context.settleActionBoundary(resultController, {
2179
+ signal: input.signal,
2180
+ remainingMs: input.remainingMs,
2181
+ policySettle: input.policySettle
2182
+ });
1824
2183
  waitMs += Date.now() - popupWaitStartedAt;
1825
2184
  await context.flushPendingPageTasks(actionController.sessionRef);
1826
2185
  resultController = context.resolveController(resultController.pageRef);
@@ -1844,7 +2203,8 @@ function createPlaywrightComputerUseBridge(context) {
1844
2203
  actionMs,
1845
2204
  waitMs,
1846
2205
  totalMs: Date.now() - startedAt
1847
- }
2206
+ },
2207
+ ...boundary === void 0 ? {} : { boundary }
1848
2208
  };
1849
2209
  }
1850
2210
  };
@@ -2877,10 +3237,12 @@ function createPlaywrightDomActionBridge(context) {
2877
3237
  },
2878
3238
  async finalizeDomAction(pageRef, options) {
2879
3239
  const controller = context.resolveController(pageRef);
2880
- await context.flushPendingPageTasks(controller.sessionRef);
2881
- await options.policySettle(pageRef);
2882
- await context.flushPendingPageTasks(controller.sessionRef);
2883
- await context.flushDomUpdateTask(controller);
3240
+ return context.settleActionBoundary(controller, {
3241
+ signal: options.signal,
3242
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
3243
+ remainingMs: options.remainingMs,
3244
+ policySettle: options.policySettle
3245
+ });
2884
3246
  }
2885
3247
  };
2886
3248
  }
@@ -3148,6 +3510,210 @@ async function releaseObject(controller, objectId) {
3148
3510
  await controller.cdp.send("Runtime.releaseObject", { objectId }).catch(() => void 0);
3149
3511
  }
3150
3512
 
3513
+ // src/action-settle.ts
3514
+ var DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS = CROSS_DOCUMENT_INTERACTION_TIMEOUT_MS;
3515
+ var DEFAULT_PLAYWRIGHT_POST_LOAD_CAPTURE_WINDOW_MS = 1e3;
3516
+ function clampPlaywrightActionSettleTimeout(timeoutMs) {
3517
+ if (timeoutMs === void 0) {
3518
+ return DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS;
3519
+ }
3520
+ return Math.max(0, Math.min(DEFAULT_PLAYWRIGHT_ACTION_SETTLE_TIMEOUT_MS, timeoutMs));
3521
+ }
3522
+ function createPlaywrightActionSettler(context) {
3523
+ const installScript = buildPostLoadTrackerInstallScript();
3524
+ const beginExpression = buildPostLoadTrackerBeginExpression();
3525
+ const freezeExpression = buildPostLoadTrackerFreezeExpression();
3526
+ const readExpression = buildPostLoadTrackerReadExpression();
3527
+ async function installTracker(controller) {
3528
+ if (!controller.settleTrackerRegistered) {
3529
+ await controller.page.addInitScript(installScript);
3530
+ controller.settleTrackerRegistered = true;
3531
+ }
3532
+ try {
3533
+ await controller.cdp.send("Runtime.evaluate", {
3534
+ expression: installScript,
3535
+ returnByValue: true,
3536
+ awaitPromise: true
3537
+ });
3538
+ } catch (error) {
3539
+ if (controller.lifecycleState === "closed" || isContextClosedError(error)) {
3540
+ return;
3541
+ }
3542
+ throw normalizePlaywrightError(error, controller.pageRef);
3543
+ }
3544
+ }
3545
+ async function readTrackerState(controller) {
3546
+ try {
3547
+ const evaluated = await controller.cdp.send("Runtime.evaluate", {
3548
+ expression: readExpression,
3549
+ returnByValue: true,
3550
+ awaitPromise: true
3551
+ });
3552
+ return normalizePostLoadTrackerState(evaluated.result?.value);
3553
+ } catch (error) {
3554
+ if (isIgnorableTrackerReadError(error)) {
3555
+ return void 0;
3556
+ }
3557
+ throw normalizePlaywrightError(error, controller.pageRef);
3558
+ }
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
+ }
3633
+ async function settle(options) {
3634
+ const { controller, timeoutMs, signal, snapshot, policySettle } = options;
3635
+ if (timeoutMs <= 0) {
3636
+ return {
3637
+ trigger: "dom-action",
3638
+ crossDocument: false,
3639
+ bootstrapSettled: false,
3640
+ timedOutPhase: "bootstrap"
3641
+ };
3642
+ }
3643
+ await context.flushPendingPageTasks(controller.sessionRef);
3644
+ let boundary;
3645
+ if (snapshot === void 0) {
3646
+ if (policySettle) {
3647
+ if (signal?.aborted) {
3648
+ throw signal.reason ?? abortError2();
3649
+ }
3650
+ await policySettle(controller.pageRef, "dom-action");
3651
+ }
3652
+ boundary = {
3653
+ trigger: "dom-action",
3654
+ crossDocument: false,
3655
+ bootstrapSettled: true
3656
+ };
3657
+ } else {
3658
+ await installTracker(controller);
3659
+ boundary = await waitForActionBoundary({
3660
+ timeoutMs,
3661
+ ...signal === void 0 ? {} : { signal },
3662
+ snapshot,
3663
+ getCurrentMainFrameDocumentRef: () => context.getMainFrameDocumentRef(controller),
3664
+ getCurrentPageUrl: () => controller.page.url(),
3665
+ isCurrentMainFrameBootstrapSettled: () => context.isCurrentMainFrameBootstrapSettled(controller),
3666
+ readTrackerState: () => readTrackerState(controller),
3667
+ throwBackgroundError: () => context.throwBackgroundError(controller),
3668
+ isPageClosed: () => controller.lifecycleState === "closed"
3669
+ });
3670
+ if (policySettle) {
3671
+ await policySettle(controller.pageRef, boundary.trigger);
3672
+ }
3673
+ }
3674
+ await context.flushPendingPageTasks(controller.sessionRef);
3675
+ if (controller.lifecycleState !== "closed") {
3676
+ await context.flushDomUpdateTask(controller);
3677
+ }
3678
+ return boundary;
3679
+ }
3680
+ return {
3681
+ captureSnapshot,
3682
+ installTracker,
3683
+ waitForPostLoadQuiet,
3684
+ settle
3685
+ };
3686
+ }
3687
+ function abortError2() {
3688
+ return new DOMException("The operation was aborted", "AbortError");
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
+ }
3711
+ function isIgnorableTrackerReadError(error) {
3712
+ return isContextClosedError(error) || error instanceof Error && /Execution context was destroyed|Cannot find context|Inspected target navigated or closed/i.test(
3713
+ error.message
3714
+ );
3715
+ }
3716
+
3151
3717
  // src/storage-capture.ts
3152
3718
  var ACTIVATION_PATH = "/__opensteer_storage_capture__";
3153
3719
  var ACTIVATION_TIMEOUT_MS = 15e3;
@@ -3339,6 +3905,71 @@ function cloneKeyPath(keyPath) {
3339
3905
  }
3340
3906
 
3341
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
+ })();`;
3342
3973
  var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3343
3974
  capabilities = PLAYWRIGHT_BROWSER_CORE_CAPABILITIES;
3344
3975
  browser;
@@ -3354,6 +3985,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3354
3985
  pageByPlaywrightPage = /* @__PURE__ */ new WeakMap();
3355
3986
  pendingPopupOpeners = /* @__PURE__ */ new WeakMap();
3356
3987
  preassignedPopupPageRefs = /* @__PURE__ */ new WeakMap();
3988
+ actionSettler = createPlaywrightActionSettler({
3989
+ flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3990
+ flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
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,
3993
+ throwBackgroundError: (controller) => this.throwBackgroundError(controller)
3994
+ });
3357
3995
  pageCounter = 0;
3358
3996
  frameCounter = 0;
3359
3997
  documentCounter = 0;
@@ -3409,6 +4047,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3409
4047
  resolveController: (pageRef) => this.requirePage(pageRef),
3410
4048
  flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3411
4049
  flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
4050
+ settleActionBoundary: (controller, options) => this.actionSettler.settle({
4051
+ controller,
4052
+ timeoutMs: clampPlaywrightActionSettleTimeout(options.remainingMs()),
4053
+ ...options.signal === void 0 ? {} : { signal: options.signal },
4054
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
4055
+ ...options.policySettle === void 0 ? {} : { policySettle: options.policySettle }
4056
+ }),
3412
4057
  requireMainFrame: (controller) => this.requireMainFrame(controller),
3413
4058
  drainQueuedEvents: (pageRef) => this.drainQueuedEvents(pageRef),
3414
4059
  withModifiers: (page, modifiers, action) => this.withModifiers(page, modifiers, action)
@@ -3420,6 +4065,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3420
4065
  resolveController: (pageRef) => this.requirePage(pageRef),
3421
4066
  flushPendingPageTasks: (sessionRef) => this.flushPendingPageTasks(sessionRef),
3422
4067
  flushDomUpdateTask: (controller) => this.flushDomUpdateTask(controller),
4068
+ settleActionBoundary: (controller, options) => this.actionSettler.settle({
4069
+ controller,
4070
+ timeoutMs: clampPlaywrightActionSettleTimeout(options.remainingMs()),
4071
+ ...options.signal === void 0 ? {} : { signal: options.signal },
4072
+ ...options.snapshot === void 0 ? {} : { snapshot: options.snapshot },
4073
+ ...options.policySettle === void 0 ? {} : { policySettle: options.policySettle }
4074
+ }),
3423
4075
  locateBackendNode: (document, backendNodeId) => createNodeLocator(
3424
4076
  document.documentRef,
3425
4077
  document.documentEpoch,
@@ -3750,11 +4402,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3750
4402
  });
3751
4403
  await this.flushPendingPageTasks(controller.sessionRef);
3752
4404
  const mainFrame = this.requireMainFrame(controller);
4405
+ const events = mergeDistinctStepEvents([
4406
+ ...this.drainQueuedEvents(controller.pageRef),
4407
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4408
+ ]);
3753
4409
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3754
4410
  frameRef: mainFrame.frameRef,
3755
4411
  documentRef: mainFrame.currentDocument.documentRef,
3756
4412
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3757
- events: this.drainQueuedEvents(controller.pageRef),
4413
+ events,
3758
4414
  data: hit
3759
4415
  });
3760
4416
  }
@@ -3774,11 +4430,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3774
4430
  await this.flushPendingPageTasks(controller.sessionRef);
3775
4431
  await this.flushDomUpdateTask(controller);
3776
4432
  const mainFrame = this.requireMainFrame(controller);
4433
+ const events = mergeDistinctStepEvents([
4434
+ ...this.drainQueuedEvents(controller.pageRef),
4435
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4436
+ ]);
3777
4437
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3778
4438
  frameRef: mainFrame.frameRef,
3779
4439
  documentRef: mainFrame.currentDocument.documentRef,
3780
4440
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3781
- events: this.drainQueuedEvents(controller.pageRef),
4441
+ events,
3782
4442
  data: void 0
3783
4443
  });
3784
4444
  }
@@ -3790,11 +4450,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3790
4450
  });
3791
4451
  await this.flushPendingPageTasks(controller.sessionRef);
3792
4452
  const mainFrame = this.requireMainFrame(controller);
4453
+ const events = mergeDistinctStepEvents([
4454
+ ...this.drainQueuedEvents(controller.pageRef),
4455
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4456
+ ]);
3793
4457
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3794
4458
  frameRef: mainFrame.frameRef,
3795
4459
  documentRef: mainFrame.currentDocument.documentRef,
3796
4460
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3797
- events: this.drainQueuedEvents(controller.pageRef),
4461
+ events,
3798
4462
  data: void 0
3799
4463
  });
3800
4464
  }
@@ -3804,11 +4468,15 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3804
4468
  await controller.page.keyboard.type(input.text);
3805
4469
  await this.flushPendingPageTasks(controller.sessionRef);
3806
4470
  const mainFrame = this.requireMainFrame(controller);
4471
+ const events = mergeDistinctStepEvents([
4472
+ ...this.drainQueuedEvents(controller.pageRef),
4473
+ ...await this.drainInstrumentedRuntimeEvents(controller)
4474
+ ]);
3807
4475
  return this.createStepResult(controller.sessionRef, controller.pageRef, startedAt, {
3808
4476
  frameRef: mainFrame.frameRef,
3809
4477
  documentRef: mainFrame.currentDocument.documentRef,
3810
4478
  documentEpoch: mainFrame.currentDocument.documentEpoch,
3811
- events: this.drainQueuedEvents(controller.pageRef),
4479
+ events,
3812
4480
  data: void 0
3813
4481
  });
3814
4482
  }
@@ -3946,6 +4614,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3946
4614
  }
3947
4615
  return infos;
3948
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
+ }
3949
4624
  async listFrames(input) {
3950
4625
  const controller = this.requirePage(input.pageRef);
3951
4626
  await this.flushDomUpdateTask(controller);
@@ -3997,6 +4672,12 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
3997
4672
  (contentDocIndex) => resolveCapturedContentDocumentRef(controller.framesByCdpId, captured, contentDocIndex)
3998
4673
  );
3999
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
+ }
4000
4681
  async waitForVisualStability(input) {
4001
4682
  const controller = this.requirePage(input.pageRef);
4002
4683
  await this.flushDomUpdateTask(controller);
@@ -4007,6 +4688,21 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4007
4688
  });
4008
4689
  await this.flushDomUpdateTask(controller);
4009
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
+ }
4010
4706
  async readText(input) {
4011
4707
  const document = this.requireDocument(input.documentRef);
4012
4708
  const controller = this.requirePage(document.pageRef);
@@ -4514,6 +5210,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4514
5210
  backgroundTasks: /* @__PURE__ */ new Set(),
4515
5211
  domUpdateTask: void 0,
4516
5212
  backgroundError: void 0,
5213
+ settleTrackerRegistered: false,
4517
5214
  openerPageRef: void 0,
4518
5215
  mainFrameRef: void 0,
4519
5216
  lifecycleState: "open",
@@ -4527,9 +5224,12 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4527
5224
  session.activePageRef = pageRef;
4528
5225
  await cdp.send("Page.enable", { enableFileChooserOpenedEvent: true });
4529
5226
  await cdp.send("Network.enable");
5227
+ await cdp.send("Runtime.enable");
4530
5228
  await cdp.send("DOM.enable", { includeWhitespace: "none" });
4531
5229
  await cdp.send("DOMStorage.enable");
4532
5230
  await cdp.send("DOM.getDocument", { depth: 0 });
5231
+ await this.installRuntimeEventRecorder(page);
5232
+ await this.actionSettler.installTracker(controller);
4533
5233
  cdp.on(
4534
5234
  "Page.frameAttached",
4535
5235
  (payload) => this.runControllerEvent(
@@ -4604,9 +5304,19 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4604
5304
  "DOM.documentUpdated",
4605
5305
  () => this.runControllerEvent(controller, () => this.handleDocumentUpdated(controller))
4606
5306
  );
4607
- page.on(
4608
- "console",
4609
- (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
+ )
4610
5320
  );
4611
5321
  page.on(
4612
5322
  "popup",
@@ -4633,10 +5343,6 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4633
5343
  "download",
4634
5344
  (download) => this.runControllerEvent(controller, () => this.handleDownload(controller, download))
4635
5345
  );
4636
- page.on(
4637
- "pageerror",
4638
- (error) => this.runControllerEvent(controller, () => this.handlePageError(controller, error))
4639
- );
4640
5346
  page.on(
4641
5347
  "request",
4642
5348
  (request) => this.runControllerEvent(controller, () => this.handlePlaywrightRequest(controller, request))
@@ -4648,6 +5354,10 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4648
5354
  () => this.handlePlaywrightResponse(controller, response)
4649
5355
  )
4650
5356
  );
5357
+ page.on(
5358
+ "domcontentloaded",
5359
+ () => this.runControllerEvent(controller, () => this.handlePageDomContentLoaded(controller))
5360
+ );
4651
5361
  page.on("close", () => this.handleUnexpectedPageClose(controller));
4652
5362
  const frameTree = await cdp.send("Page.getFrameTree");
4653
5363
  this.syncFrameTree(controller, frameTree.frameTree);
@@ -4700,6 +5410,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4700
5410
  documentRef,
4701
5411
  documentEpoch: createDocumentEpoch(0),
4702
5412
  url: "about:blank",
5413
+ domContentLoadedAt: void 0,
4703
5414
  parentDocumentRef: parent?.currentDocument.documentRef,
4704
5415
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4705
5416
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4758,6 +5469,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4758
5469
  documentRef: nextDocumentRef,
4759
5470
  documentEpoch: createDocumentEpoch(0),
4760
5471
  url: combineFrameUrl(frame.url, frame.urlFragment),
5472
+ domContentLoadedAt: void 0,
4761
5473
  parentDocumentRef: parent?.currentDocument.documentRef,
4762
5474
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4763
5475
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4787,6 +5499,13 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4787
5499
  handleDocumentUpdated(controller) {
4788
5500
  this.queueDocumentReconciliation(controller);
4789
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
+ }
4790
5509
  syncFrameTree(controller, tree) {
4791
5510
  const visit = (node, parentFrameRef) => {
4792
5511
  const existing = controller.framesByCdpId.get(node.frame.id);
@@ -4800,6 +5519,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4800
5519
  documentRef,
4801
5520
  documentEpoch: createDocumentEpoch(0),
4802
5521
  url: combineFrameUrl(node.frame.url, node.frame.urlFragment),
5522
+ domContentLoadedAt: void 0,
4803
5523
  parentDocumentRef: parentFrameRef === void 0 ? void 0 : this.requireFrame(parentFrameRef).currentDocument.documentRef,
4804
5524
  nodeRefsByBackendNodeId: /* @__PURE__ */ new Map(),
4805
5525
  backendNodeIdsByNodeRef: /* @__PURE__ */ new Map(),
@@ -4858,19 +5578,20 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4858
5578
  }
4859
5579
  }
4860
5580
  }
4861
- handleConsole(controller, message) {
5581
+ handleRuntimeConsole(controller, payload) {
5582
+ const callFrame = payload.stackTrace?.callFrames?.[0];
4862
5583
  this.queueEvent(
4863
5584
  controller.pageRef,
4864
5585
  this.createEvent({
4865
5586
  kind: "console",
4866
5587
  sessionRef: controller.sessionRef,
4867
5588
  pageRef: controller.pageRef,
4868
- level: normalizeConsoleLevel(message.type()),
4869
- text: message.text(),
5589
+ level: normalizeRuntimeConsoleLevel(payload.type),
5590
+ text: formatRuntimeConsoleText(payload),
4870
5591
  location: {
4871
- url: message.location().url,
4872
- lineNumber: message.location().lineNumber,
4873
- columnNumber: message.location().columnNumber
5592
+ url: callFrame?.url ?? "",
5593
+ lineNumber: callFrame?.lineNumber ?? 0,
5594
+ columnNumber: callFrame?.columnNumber ?? 0
4874
5595
  }
4875
5596
  })
4876
5597
  );
@@ -4951,15 +5672,18 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
4951
5672
  })
4952
5673
  );
4953
5674
  }
4954
- handlePageError(controller, error) {
5675
+ handleRuntimeException(controller, payload) {
5676
+ const details = payload.exceptionDetails;
5677
+ const message = formatRuntimeExceptionMessage(details);
5678
+ const stack = formatRuntimeExceptionStack(details);
4955
5679
  this.queueEvent(
4956
5680
  controller.pageRef,
4957
5681
  this.createEvent({
4958
5682
  kind: "page-error",
4959
5683
  sessionRef: controller.sessionRef,
4960
5684
  pageRef: controller.pageRef,
4961
- message: error.message,
4962
- ...error.stack === void 0 ? {} : { stack: error.stack }
5685
+ message,
5686
+ ...stack === void 0 ? {} : { stack }
4963
5687
  })
4964
5688
  );
4965
5689
  }
@@ -5681,10 +6405,11 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5681
6405
  }
5682
6406
  }
5683
6407
  createEvent(value) {
6408
+ const { timestamp, ...event } = value;
5684
6409
  return {
5685
- ...value,
6410
+ ...event,
5686
6411
  eventId: `event:${++this.eventCounter}`,
5687
- timestamp: Date.now()
6412
+ timestamp: timestamp ?? Date.now()
5688
6413
  };
5689
6414
  }
5690
6415
  queueEvent(pageRef, event) {
@@ -5694,6 +6419,37 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5694
6419
  }
5695
6420
  controller.queuedEvents.push(event);
5696
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
+ }
5697
6453
  drainQueuedEvents(pageRef) {
5698
6454
  const controller = this.requirePage(pageRef);
5699
6455
  const events = controller.queuedEvents.splice(0, controller.queuedEvents.length);
@@ -5940,6 +6696,104 @@ function withTimeout(promise, timeoutMs) {
5940
6696
  })
5941
6697
  ]);
5942
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
+ }
5943
6797
 
5944
6798
  exports.PlaywrightBrowserCoreEngine = PlaywrightBrowserCoreEngine;
5945
6799
  exports.capturePlaywrightStorageOrigins = capturePlaywrightStorageOrigins;