@humanjs/playwright 0.8.0 → 0.10.0

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.
@@ -1,4 +1,4 @@
1
- import { resolvePersonality, createRng, sleep as sleep$1, planScroll, countWords, computeReadingDwellMs, planReadingScan, planTypeKeystrokes, bezierPath, humanizePath } from '@humanjs/core';
1
+ import { sleep as sleep$1, resolvePersonality, createRng, planScroll, countWords, computeReadingDwellMs, planReadingScan, planTypeKeystrokes, bezierPath, humanizePath } from '@humanjs/core';
2
2
  export { applyMicroJitter, applyVelocityProfile, bezierPath, blend, careful, computeReadingDwellMs, countWords, createRng, distracted, fast, humanizePath, planScroll, planTypeKeystrokes, precise, resolvePersonality, sleep } from '@humanjs/core';
3
3
  import { spawn } from 'child_process';
4
4
  import { rmSync } from 'fs';
@@ -575,6 +575,51 @@ async function executeUpload(target, files, ctx) {
575
575
  }
576
576
  await locator.setInputFiles(files);
577
577
  }
578
+
579
+ // src/internal/select-substring.ts
580
+ function selectSubstringInElement(el, needleRaw) {
581
+ const needle = needleRaw.replace(/\s+/g, " ").trim();
582
+ if (!needle) return false;
583
+ const map = [];
584
+ let normalized = "";
585
+ let prevSpace = true;
586
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
587
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
588
+ const text = node;
589
+ const data = text.data;
590
+ for (let i = 0; i < data.length; i++) {
591
+ const ch = data.charAt(i);
592
+ if (/\s/.test(ch)) {
593
+ if (!prevSpace) {
594
+ normalized += " ";
595
+ map.push([text, i]);
596
+ }
597
+ prevSpace = true;
598
+ } else {
599
+ normalized += ch;
600
+ map.push([text, i]);
601
+ prevSpace = false;
602
+ }
603
+ }
604
+ }
605
+ if (normalized.endsWith(" ")) {
606
+ normalized = normalized.slice(0, -1);
607
+ map.pop();
608
+ }
609
+ const start = normalized.indexOf(needle);
610
+ if (start === -1) return false;
611
+ const startPos = map[start];
612
+ const endPos = map[start + needle.length - 1];
613
+ if (!startPos || !endPos) return false;
614
+ const range = document.createRange();
615
+ range.setStart(startPos[0], startPos[1]);
616
+ range.setEnd(endPos[0], endPos[1] + 1);
617
+ const selection = window.getSelection();
618
+ if (!selection) return false;
619
+ selection.removeAllRanges();
620
+ selection.addRange(range);
621
+ return true;
622
+ }
578
623
  async function executeType(target, value, ctx) {
579
624
  const locator = typeof target === "string" ? ctx.page.locator(target) : target;
580
625
  if (value.length === 0) {
@@ -691,6 +736,118 @@ function normalizeKey(key) {
691
736
  function isMac() {
692
737
  return process.platform === "darwin";
693
738
  }
739
+
740
+ // src/mouse-helper/index.ts
741
+ var CURSOR_PATH = "M 0 0 L 16 6 L 8 9.5 L 5 19 Z";
742
+ var INSTALLED_FLAG = /* @__PURE__ */ Symbol.for("@humanjs/playwright:mouse-helper:installed");
743
+ async function installMouseHelper(target, options = {}) {
744
+ const tagged = target;
745
+ if (tagged[INSTALLED_FLAG]) return;
746
+ tagged[INSTALLED_FLAG] = true;
747
+ const config = {
748
+ color: options.color ?? "#f5a55c",
749
+ stroke: "#020203",
750
+ size: options.size ?? 22,
751
+ showClicks: options.showClicks ?? true,
752
+ haloOpacity: options.haloOpacity ?? 0.18,
753
+ path: CURSOR_PATH
754
+ };
755
+ await target.addInitScript(installScript, config);
756
+ const attachPageHooks = (page) => {
757
+ page.on("domcontentloaded", () => {
758
+ page.evaluate(installScript, config).catch(() => void 0);
759
+ });
760
+ };
761
+ const pages = "pages" in target ? target.pages() : [target];
762
+ for (const page of pages) attachPageHooks(page);
763
+ if ("on" in target && "newPage" in target) {
764
+ target.on("page", attachPageHooks);
765
+ }
766
+ await Promise.all(
767
+ pages.map((page) => page.evaluate(installScript, config).catch(() => void 0))
768
+ );
769
+ }
770
+ function installScript(config) {
771
+ if (document.querySelector("[data-humanjs-cursor]")) return;
772
+ const attach = () => {
773
+ const cursor = document.createElement("div");
774
+ cursor.setAttribute("aria-hidden", "true");
775
+ cursor.setAttribute("data-humanjs-cursor", "true");
776
+ cursor.style.cssText = [
777
+ "position: fixed",
778
+ "left: 0",
779
+ "top: 0",
780
+ `width: ${config.size}px`,
781
+ `height: ${config.size + 4}px`,
782
+ "pointer-events: none",
783
+ "z-index: 2147483647",
784
+ // Start visible at (0, 0) so the cursor is on screen from the moment
785
+ // the page loads — without this the helper looks like nothing happened
786
+ // until the first mousemove arrives.
787
+ "opacity: 1",
788
+ "transform: translate(0px, 0px)",
789
+ // CSS interpolates between successive `mousemove` updates so the
790
+ // cursor reads as continuous motion instead of discrete hops. Slightly
791
+ // longer than the path-walker's typical step interval (~30–80ms) so
792
+ // each tween is still settling when the next move lands → no pauses.
793
+ "transition: transform 110ms ease-out, opacity 0.18s ease-out",
794
+ "will-change: transform"
795
+ ].join("; ");
796
+ const haloRadius = Math.round(config.size * 0.6);
797
+ cursor.innerHTML = `
798
+ <svg width="${config.size}" height="${config.size + 4}" viewBox="0 0 22 24" style="overflow: visible;">
799
+ <circle cx="0" cy="0" r="${haloRadius}" fill="${config.color}" opacity="${config.haloOpacity}" />
800
+ <path d="${config.path}" fill="${config.color}" stroke="${config.stroke}" stroke-width="0.7" stroke-linejoin="round" />
801
+ </svg>
802
+ `;
803
+ document.body.appendChild(cursor);
804
+ let lastX = 0;
805
+ let lastY = 0;
806
+ const onMove = (e) => {
807
+ lastX = e.clientX;
808
+ lastY = e.clientY;
809
+ cursor.style.transform = `translate(${lastX}px, ${lastY}px)`;
810
+ cursor.style.opacity = "1";
811
+ };
812
+ window.addEventListener("mousemove", onMove, { capture: true, passive: true });
813
+ document.addEventListener("mousemove", onMove, { capture: true, passive: true });
814
+ document.addEventListener(
815
+ "mouseleave",
816
+ () => {
817
+ cursor.style.opacity = "0";
818
+ },
819
+ { capture: true, passive: true }
820
+ );
821
+ if (config.showClicks) {
822
+ const styleEl = document.createElement("style");
823
+ styleEl.textContent = "@keyframes humanjs-ripple { 0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } 100% { transform: translate(-50%, -50%) scale(2); opacity: 0; } }";
824
+ document.head.appendChild(styleEl);
825
+ window.addEventListener(
826
+ "mousedown",
827
+ () => {
828
+ const ripple = document.createElement("div");
829
+ ripple.style.cssText = [
830
+ "position: fixed",
831
+ `left: ${lastX}px`,
832
+ `top: ${lastY}px`,
833
+ "width: 28px",
834
+ "height: 28px",
835
+ "border-radius: 50%",
836
+ `border: 1.5px solid ${config.color}`,
837
+ "pointer-events: none",
838
+ "z-index: 2147483646",
839
+ "animation: humanjs-ripple 0.45s ease-out forwards"
840
+ ].join("; ");
841
+ document.body.appendChild(ripple);
842
+ window.setTimeout(() => ripple.remove(), 500);
843
+ },
844
+ { capture: true, passive: true }
845
+ );
846
+ }
847
+ };
848
+ if (document.body) attach();
849
+ else document.addEventListener("DOMContentLoaded", attach, { once: true });
850
+ }
694
851
  async function executeRead(target, ctx, options = {}) {
695
852
  let words = 0;
696
853
  let locator;
@@ -783,8 +940,17 @@ async function detectKindFromTag(locator) {
783
940
  return void 0;
784
941
  }
785
942
 
786
- // src/recording/codegen.ts
943
+ // src/recording/targets.ts
787
944
  var POINT_RE = /^point\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)$/;
945
+ function parsePointTarget(desc) {
946
+ const match = String(desc ?? "").match(POINT_RE);
947
+ return match ? { x: Number(match[1]), y: Number(match[2]) } : null;
948
+ }
949
+ function resolveMouseTarget(desc) {
950
+ return parsePointTarget(desc) ?? String(desc ?? "");
951
+ }
952
+
953
+ // src/recording/codegen.ts
788
954
  var POINT_COMMENT = " // raw coordinate \u2014 replace with a locator for a stable selector";
789
955
  var UNCAPTURED_COMMENT = " // input not captured (masked or captureInputs disabled) \u2014 fill in (e.g. process.env.X)";
790
956
  function q(value) {
@@ -850,6 +1016,13 @@ function emitAction(e, opts = {}) {
850
1016
  const { code } = targetArg(p.target);
851
1017
  return ` await human.${e.type}(${code});`;
852
1018
  }
1019
+ case "selectText": {
1020
+ const { code } = targetArg(p.target);
1021
+ if (typeof p.text === "string" && p.text.length > 0) {
1022
+ return ` await human.selectText(${code}, { text: ${q(p.text)} });`;
1023
+ }
1024
+ return ` await human.selectText(${code});`;
1025
+ }
853
1026
  case "selectOption": {
854
1027
  const { code } = targetArg(p.target);
855
1028
  return ` await human.selectOption(${code}, ${serializeSelectValues(p.values)});`;
@@ -899,6 +1072,14 @@ function emitAction(e, opts = {}) {
899
1072
  return " await human.goBack();";
900
1073
  case "goForward":
901
1074
  return " await human.goForward();";
1075
+ case "assert": {
1076
+ const kind = String(p.kind ?? "visible");
1077
+ if (kind === "url") return ` await expect(page).toHaveURL(${q(p.value)});`;
1078
+ const { code } = targetArg(p.target);
1079
+ if (kind === "text")
1080
+ return ` await expect(page.locator(${code})).toHaveText(${q(p.value)});`;
1081
+ return ` await expect(page.locator(${code})).toBeVisible();`;
1082
+ }
902
1083
  default:
903
1084
  return ` // unsupported action: ${e.type}`;
904
1085
  }
@@ -908,7 +1089,7 @@ function needsSleepImport(timeline) {
908
1089
  }
909
1090
  function generateHumanJS(timeline) {
910
1091
  const imports = needsSleepImport(timeline) ? "import { chromium, createHuman, sleep } from '@humanjs/playwright';" : "import { chromium, createHuman } from '@humanjs/playwright';";
911
- const body = timeline.events.map((e) => emitAction(e)).join("\n");
1092
+ const body = timeline.events.filter((e) => e.type !== "assert").map((e) => emitAction(e)).join("\n");
912
1093
  return `${imports}
913
1094
 
914
1095
  async function main() {
@@ -1005,6 +1186,150 @@ ${todo}
1005
1186
  });
1006
1187
  `;
1007
1188
  }
1189
+ function abortError() {
1190
+ const error = new Error("Replay aborted");
1191
+ error.name = "AbortError";
1192
+ return error;
1193
+ }
1194
+ async function replayTimeline(page, timeline, options = {}) {
1195
+ const events = Array.isArray(timeline) ? timeline : timeline.events;
1196
+ const { onStep, signal } = options;
1197
+ if (signal?.aborted) throw abortError();
1198
+ const human = await createHuman(page, {
1199
+ personality: options.personality ?? "careful",
1200
+ speed: options.speed ?? "human",
1201
+ ...options.seed !== void 0 ? { seed: options.seed } : {},
1202
+ ...options.cursor !== void 0 ? { cursor: options.cursor } : {}
1203
+ });
1204
+ const startedAt = Date.now();
1205
+ const steps = [];
1206
+ for (const [index, event] of events.entries()) {
1207
+ if (signal?.aborted) throw abortError();
1208
+ onStep?.({ index, type: event.type, status: "running" });
1209
+ try {
1210
+ await runEvent(human, page, event);
1211
+ } catch (cause) {
1212
+ const error = cause instanceof Error ? cause.message : String(cause);
1213
+ steps.push({ index, type: event.type, status: "fail", error });
1214
+ onStep?.({ index, type: event.type, status: "fail", error });
1215
+ return { status: "fail", steps, failedIndex: index, durationMs: Date.now() - startedAt };
1216
+ }
1217
+ steps.push({ index, type: event.type, status: "pass" });
1218
+ onStep?.({ index, type: event.type, status: "pass" });
1219
+ }
1220
+ return { status: "pass", steps, durationMs: Date.now() - startedAt };
1221
+ }
1222
+ function parseScrollTarget(target) {
1223
+ const value = String(target ?? "natural");
1224
+ const by = value.match(/^by:(-?\d+(?:\.\d+)?)$/);
1225
+ if (by) return { by: Number(by[1]) };
1226
+ const to = value.match(/^to:(-?\d+(?:\.\d+)?)$/);
1227
+ if (to) return { to: Number(to[1]) };
1228
+ return value;
1229
+ }
1230
+ var normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
1231
+ async function runAssert(page, params) {
1232
+ const kind = String(params.kind ?? "visible");
1233
+ if (kind === "url") {
1234
+ const actual = page.url();
1235
+ const expected = String(params.value ?? "");
1236
+ if (actual !== expected) {
1237
+ throw new Error(`expected URL ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
1238
+ }
1239
+ return;
1240
+ }
1241
+ const locator = page.locator(String(params.target ?? "")).first();
1242
+ await locator.waitFor({ state: "visible" });
1243
+ if (kind === "text") {
1244
+ const actual = normalizeText(await locator.textContent());
1245
+ const expected = normalizeText(String(params.value ?? ""));
1246
+ if (actual !== expected) {
1247
+ throw new Error(`expected text ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
1248
+ }
1249
+ }
1250
+ }
1251
+ async function runEvent(human, page, event) {
1252
+ const p = event.params;
1253
+ switch (event.type) {
1254
+ case "goto":
1255
+ await human.goto(String(p.url ?? ""));
1256
+ return;
1257
+ case "click":
1258
+ await human.click(resolveMouseTarget(p.target));
1259
+ return;
1260
+ case "rightClick":
1261
+ await human.rightClick(resolveMouseTarget(p.target));
1262
+ return;
1263
+ case "doubleClick":
1264
+ await human.doubleClick(resolveMouseTarget(p.target));
1265
+ return;
1266
+ case "move":
1267
+ await human.move(resolveMouseTarget(p.target));
1268
+ return;
1269
+ case "hover":
1270
+ await human.hover(String(p.target ?? ""));
1271
+ return;
1272
+ case "drag":
1273
+ await human.drag(resolveMouseTarget(p.from), resolveMouseTarget(p.to));
1274
+ return;
1275
+ case "type":
1276
+ await human.type(String(p.target ?? ""), event.inputValue ?? "");
1277
+ return;
1278
+ case "paste":
1279
+ await human.paste(String(p.target ?? ""), event.inputValue ?? "");
1280
+ return;
1281
+ case "clear":
1282
+ await human.clear(String(p.target ?? ""));
1283
+ return;
1284
+ case "check":
1285
+ await human.check(String(p.target ?? ""));
1286
+ return;
1287
+ case "uncheck":
1288
+ await human.uncheck(String(p.target ?? ""));
1289
+ return;
1290
+ case "selectText":
1291
+ await human.selectText(
1292
+ String(p.target ?? ""),
1293
+ typeof p.text === "string" ? { text: p.text } : void 0
1294
+ );
1295
+ return;
1296
+ case "selectOption":
1297
+ await human.selectOption(String(p.target ?? ""), p.values);
1298
+ return;
1299
+ case "upload":
1300
+ await human.upload(String(p.target ?? ""), p.files);
1301
+ return;
1302
+ case "press":
1303
+ await human.press(String(p.key ?? ""));
1304
+ return;
1305
+ case "scroll":
1306
+ await human.scroll(parseScrollTarget(p.target));
1307
+ return;
1308
+ case "read": {
1309
+ const target = String(p.target ?? "");
1310
+ if (/^\d+ words$/.test(target) || /^text:\d+ chars$/.test(target)) return;
1311
+ await human.read(target);
1312
+ return;
1313
+ }
1314
+ case "sleep":
1315
+ await sleep$1(Number(p.ms) || 0);
1316
+ return;
1317
+ case "reload":
1318
+ await human.reload();
1319
+ return;
1320
+ case "goBack":
1321
+ await human.goBack();
1322
+ return;
1323
+ case "goForward":
1324
+ await human.goForward();
1325
+ return;
1326
+ case "assert":
1327
+ await runAssert(page, p);
1328
+ return;
1329
+ default:
1330
+ return;
1331
+ }
1332
+ }
1008
1333
 
1009
1334
  // src/recording/index.ts
1010
1335
  var pendingFrameCleanups = /* @__PURE__ */ new Set();
@@ -1430,120 +1755,6 @@ async function startCapture(page, options = {}) {
1430
1755
  }
1431
1756
  };
1432
1757
  }
1433
-
1434
- // src/mouse-helper/index.ts
1435
- var CURSOR_PATH = "M 0 0 L 16 6 L 8 9.5 L 5 19 Z";
1436
- var INSTALLED_FLAG = /* @__PURE__ */ Symbol.for("@humanjs/playwright:mouse-helper:installed");
1437
- async function installMouseHelper(target, options = {}) {
1438
- const tagged = target;
1439
- if (tagged[INSTALLED_FLAG]) return;
1440
- tagged[INSTALLED_FLAG] = true;
1441
- const config = {
1442
- color: options.color ?? "#f5a55c",
1443
- stroke: "#020203",
1444
- size: options.size ?? 22,
1445
- showClicks: options.showClicks ?? true,
1446
- haloOpacity: options.haloOpacity ?? 0.18,
1447
- path: CURSOR_PATH
1448
- };
1449
- await target.addInitScript(installScript, config);
1450
- const attachPageHooks = (page) => {
1451
- page.on("domcontentloaded", () => {
1452
- page.evaluate(installScript, config).catch(() => void 0);
1453
- });
1454
- };
1455
- const pages = "pages" in target ? target.pages() : [target];
1456
- for (const page of pages) attachPageHooks(page);
1457
- if ("on" in target && "newPage" in target) {
1458
- target.on("page", attachPageHooks);
1459
- }
1460
- await Promise.all(
1461
- pages.map((page) => page.evaluate(installScript, config).catch(() => void 0))
1462
- );
1463
- }
1464
- function installScript(config) {
1465
- if (document.querySelector("[data-humanjs-cursor]")) return;
1466
- const attach = () => {
1467
- const cursor = document.createElement("div");
1468
- cursor.setAttribute("aria-hidden", "true");
1469
- cursor.setAttribute("data-humanjs-cursor", "true");
1470
- cursor.style.cssText = [
1471
- "position: fixed",
1472
- "left: 0",
1473
- "top: 0",
1474
- `width: ${config.size}px`,
1475
- `height: ${config.size + 4}px`,
1476
- "pointer-events: none",
1477
- "z-index: 2147483647",
1478
- // Start visible at (0, 0) so the cursor is on screen from the moment
1479
- // the page loads — without this the helper looks like nothing happened
1480
- // until the first mousemove arrives.
1481
- "opacity: 1",
1482
- "transform: translate(0px, 0px)",
1483
- // CSS interpolates between successive `mousemove` updates so the
1484
- // cursor reads as continuous motion instead of discrete hops. Slightly
1485
- // longer than the path-walker's typical step interval (~30–80ms) so
1486
- // each tween is still settling when the next move lands → no pauses.
1487
- "transition: transform 110ms ease-out, opacity 0.18s ease-out",
1488
- "will-change: transform"
1489
- ].join("; ");
1490
- const haloRadius = Math.round(config.size * 0.6);
1491
- cursor.innerHTML = `
1492
- <svg width="${config.size}" height="${config.size + 4}" viewBox="0 0 22 24" style="overflow: visible;">
1493
- <circle cx="0" cy="0" r="${haloRadius}" fill="${config.color}" opacity="${config.haloOpacity}" />
1494
- <path d="${config.path}" fill="${config.color}" stroke="${config.stroke}" stroke-width="0.7" stroke-linejoin="round" />
1495
- </svg>
1496
- `;
1497
- document.body.appendChild(cursor);
1498
- let lastX = 0;
1499
- let lastY = 0;
1500
- const onMove = (e) => {
1501
- lastX = e.clientX;
1502
- lastY = e.clientY;
1503
- cursor.style.transform = `translate(${lastX}px, ${lastY}px)`;
1504
- cursor.style.opacity = "1";
1505
- };
1506
- window.addEventListener("mousemove", onMove, { capture: true, passive: true });
1507
- document.addEventListener("mousemove", onMove, { capture: true, passive: true });
1508
- document.addEventListener(
1509
- "mouseleave",
1510
- () => {
1511
- cursor.style.opacity = "0";
1512
- },
1513
- { capture: true, passive: true }
1514
- );
1515
- if (config.showClicks) {
1516
- const styleEl = document.createElement("style");
1517
- styleEl.textContent = "@keyframes humanjs-ripple { 0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } 100% { transform: translate(-50%, -50%) scale(2); opacity: 0; } }";
1518
- document.head.appendChild(styleEl);
1519
- window.addEventListener(
1520
- "mousedown",
1521
- () => {
1522
- const ripple = document.createElement("div");
1523
- ripple.style.cssText = [
1524
- "position: fixed",
1525
- `left: ${lastX}px`,
1526
- `top: ${lastY}px`,
1527
- "width: 28px",
1528
- "height: 28px",
1529
- "border-radius: 50%",
1530
- `border: 1.5px solid ${config.color}`,
1531
- "pointer-events: none",
1532
- "z-index: 2147483646",
1533
- "animation: humanjs-ripple 0.45s ease-out forwards"
1534
- ].join("; ");
1535
- document.body.appendChild(ripple);
1536
- window.setTimeout(() => ripple.remove(), 500);
1537
- },
1538
- { capture: true, passive: true }
1539
- );
1540
- }
1541
- };
1542
- if (document.body) attach();
1543
- else document.addEventListener("DOMContentLoaded", attach, { once: true });
1544
- }
1545
-
1546
- // src/index.ts
1547
1758
  async function createHuman(page, options = {}) {
1548
1759
  const personality = resolvePersonality(options.personality ?? "careful");
1549
1760
  const rng = createRng(options.seed);
@@ -1553,6 +1764,9 @@ async function createHuman(page, options = {}) {
1553
1764
  for (const plugin of plugins) {
1554
1765
  await plugin.install?.(context);
1555
1766
  }
1767
+ if (options.cursor !== false && typeof page.addInitScript === "function") {
1768
+ await installMouseHelper(page, typeof options.cursor === "object" ? options.cursor : {});
1769
+ }
1556
1770
  let hasRecorded = false;
1557
1771
  let activeRecordingEvents = null;
1558
1772
  let activeRecordingStartMs = 0;
@@ -1735,6 +1949,27 @@ async function createHuman(page, options = {}) {
1735
1949
  () => executeSelectOption(target, values, mouseCtx())
1736
1950
  );
1737
1951
  },
1952
+ async selectText(target, options2) {
1953
+ const text = options2?.text;
1954
+ await performAction(
1955
+ {
1956
+ type: "selectText",
1957
+ params: { target: describeMouseTarget(target), ...text !== void 0 ? { text } : {} }
1958
+ },
1959
+ async () => {
1960
+ if (speed !== "instant") {
1961
+ await executeMove(target, mouseCtx());
1962
+ }
1963
+ const locator = typeof target === "string" ? page.locator(target) : target;
1964
+ if (text === void 0) {
1965
+ await locator.selectText();
1966
+ return;
1967
+ }
1968
+ const found = await locator.evaluate(selectSubstringInElement, text);
1969
+ if (!found) await locator.selectText();
1970
+ }
1971
+ );
1972
+ },
1738
1973
  async upload(target, files) {
1739
1974
  await performAction(
1740
1975
  {
@@ -1910,6 +2145,6 @@ function describeReadTarget(target) {
1910
2145
  return target.toString?.() ?? "locator";
1911
2146
  }
1912
2147
 
1913
- export { Recording, createHuman, installMouseHelper };
1914
- //# sourceMappingURL=chunk-CCZEDNOF.js.map
1915
- //# sourceMappingURL=chunk-CCZEDNOF.js.map
2148
+ export { Recording, createHuman, generateHumanJS, generatePlaywrightTest, installMouseHelper, replayTimeline };
2149
+ //# sourceMappingURL=chunk-I2PQGZU7.js.map
2150
+ //# sourceMappingURL=chunk-I2PQGZU7.js.map