@humanjs/playwright 0.5.0 → 0.7.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.
- package/README.md +68 -1
- package/dist/index.cjs +322 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -9
- package/dist/index.d.ts +188 -9
- package/dist/index.js +322 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -320,15 +320,22 @@ function clamp(value, min, max) {
|
|
|
320
320
|
// src/mouse/index.ts
|
|
321
321
|
async function executeClick(target, ctx, options = {}) {
|
|
322
322
|
const button = options.button ?? "left";
|
|
323
|
-
const locator = typeof target === "string" ? ctx.page.locator(target) : target;
|
|
324
323
|
if (ctx.speed === "instant") {
|
|
325
|
-
|
|
324
|
+
if (isPoint(target)) {
|
|
325
|
+
await ctx.page.mouse.click(target.x, target.y, { button });
|
|
326
|
+
ctx.setMousePosition(target);
|
|
327
|
+
return { target };
|
|
328
|
+
}
|
|
329
|
+
const locator = typeof target === "string" ? ctx.page.locator(target) : target;
|
|
330
|
+
const box2 = await locator.boundingBox();
|
|
326
331
|
await locator.click({ button });
|
|
327
|
-
const center =
|
|
332
|
+
const center = box2 ? { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 } : ctx.getMousePosition();
|
|
328
333
|
ctx.setMousePosition(center);
|
|
329
334
|
return { target: center };
|
|
330
335
|
}
|
|
331
|
-
const targetPoint = await
|
|
336
|
+
const { point: targetPoint, box } = await resolveTargetPointAndBox(target, ctx, "click");
|
|
337
|
+
await maybeMisclickBeat(ctx, box, targetPoint);
|
|
338
|
+
await walkBezierTo(targetPoint, ctx);
|
|
332
339
|
const preClickMs = computeDwellTime(
|
|
333
340
|
ctx.personality.dwell.preClickMs,
|
|
334
341
|
ctx.personality.dwell.preClickJitter,
|
|
@@ -357,7 +364,7 @@ async function executeHover(target, ctx) {
|
|
|
357
364
|
ctx.setMousePosition(center);
|
|
358
365
|
return { target: center };
|
|
359
366
|
}
|
|
360
|
-
const targetPoint = await moveToTarget(target, ctx
|
|
367
|
+
const targetPoint = await moveToTarget(target, ctx);
|
|
361
368
|
const dwellMs = computeDwellTime(
|
|
362
369
|
ctx.personality.dwell.preClickMs,
|
|
363
370
|
ctx.personality.dwell.preClickJitter,
|
|
@@ -440,10 +447,9 @@ async function executeMove(target, ctx) {
|
|
|
440
447
|
ctx.setMousePosition(point);
|
|
441
448
|
return { target: point };
|
|
442
449
|
}
|
|
443
|
-
async function moveToTarget(target, ctx
|
|
444
|
-
const box = await readBoxWithAutoScroll(target, ctx,
|
|
450
|
+
async function moveToTarget(target, ctx) {
|
|
451
|
+
const box = await readBoxWithAutoScroll(target, ctx, "hover");
|
|
445
452
|
const targetPoint = pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
|
|
446
|
-
if (action === "click") await maybeMisclickBeat(ctx, box, targetPoint);
|
|
447
453
|
await walkBezierTo(targetPoint, ctx);
|
|
448
454
|
return targetPoint;
|
|
449
455
|
}
|
|
@@ -706,6 +712,205 @@ async function detectKindFromTag(locator) {
|
|
|
706
712
|
if (tag === "pre" || tag === "code") return "code";
|
|
707
713
|
return void 0;
|
|
708
714
|
}
|
|
715
|
+
|
|
716
|
+
// src/recording/codegen.ts
|
|
717
|
+
var POINT_RE = /^point\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)$/;
|
|
718
|
+
var POINT_COMMENT = " // raw coordinate \u2014 replace with a locator for a stable selector";
|
|
719
|
+
var UNCAPTURED_COMMENT = " // input not captured (masked or captureInputs disabled) \u2014 fill in (e.g. process.env.X)";
|
|
720
|
+
function q(value) {
|
|
721
|
+
return `'${String(value ?? "").replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r")}'`;
|
|
722
|
+
}
|
|
723
|
+
function targetArg(desc) {
|
|
724
|
+
const s = String(desc ?? "");
|
|
725
|
+
const m = s.match(POINT_RE);
|
|
726
|
+
if (m) return { code: `{ x: ${m[1]}, y: ${m[2]} }`, isPoint: true };
|
|
727
|
+
return { code: q(s), isPoint: false };
|
|
728
|
+
}
|
|
729
|
+
function createHumanOptions(timeline, ciSpeed = false) {
|
|
730
|
+
const parts = [` personality: ${q(timeline.personality)},`];
|
|
731
|
+
if (timeline.seed !== null) parts.push(` seed: ${q(timeline.seed)},`);
|
|
732
|
+
parts.push(
|
|
733
|
+
ciSpeed ? ` speed: process.env.CI ? 'instant' : ${q(timeline.speed)},` : ` speed: ${q(timeline.speed)},`
|
|
734
|
+
);
|
|
735
|
+
return `{
|
|
736
|
+
${parts.join("\n")}
|
|
737
|
+
}`;
|
|
738
|
+
}
|
|
739
|
+
function emitScroll(target) {
|
|
740
|
+
const s = String(target ?? "natural");
|
|
741
|
+
if (s === "natural") return " await human.scroll('natural');";
|
|
742
|
+
const by = s.match(/^by:(-?\d+(?:\.\d+)?)$/);
|
|
743
|
+
if (by) return ` await human.scroll({ by: ${by[1]} });`;
|
|
744
|
+
const to = s.match(/^to:(-?\d+(?:\.\d+)?)$/);
|
|
745
|
+
if (to) return ` await human.scroll({ to: ${to[1]} });`;
|
|
746
|
+
return ` await human.scroll(${q(s)});`;
|
|
747
|
+
}
|
|
748
|
+
function emitAction(e, opts = {}) {
|
|
749
|
+
const p = e.params;
|
|
750
|
+
switch (e.type) {
|
|
751
|
+
case "goto": {
|
|
752
|
+
const url = String(p.url ?? "");
|
|
753
|
+
if (opts.baseOrigin && url.startsWith(opts.baseOrigin)) {
|
|
754
|
+
return ` await human.goto(${q(url.slice(opts.baseOrigin.length) || "/")});`;
|
|
755
|
+
}
|
|
756
|
+
return ` await human.goto(${q(url)});`;
|
|
757
|
+
}
|
|
758
|
+
case "click":
|
|
759
|
+
case "rightClick":
|
|
760
|
+
case "hover":
|
|
761
|
+
case "move": {
|
|
762
|
+
const { code, isPoint: isPoint2 } = targetArg(p.target);
|
|
763
|
+
return ` await human.${e.type}(${code});${isPoint2 ? POINT_COMMENT : ""}`;
|
|
764
|
+
}
|
|
765
|
+
case "drag": {
|
|
766
|
+
const from = targetArg(p.from);
|
|
767
|
+
const to = targetArg(p.to);
|
|
768
|
+
const comment = from.isPoint || to.isPoint ? POINT_COMMENT : "";
|
|
769
|
+
return ` await human.drag(${from.code}, ${to.code});${comment}`;
|
|
770
|
+
}
|
|
771
|
+
case "type":
|
|
772
|
+
case "paste": {
|
|
773
|
+
const { code, isPoint: isPoint2 } = targetArg(p.target);
|
|
774
|
+
if (e.inputValue === void 0) {
|
|
775
|
+
return ` await human.${e.type}(${code}, '');${UNCAPTURED_COMMENT}`;
|
|
776
|
+
}
|
|
777
|
+
const call = ` await human.${e.type}(${code}, ${q(e.inputValue)});`;
|
|
778
|
+
if (opts.asserts && !isPoint2) {
|
|
779
|
+
return `${call}
|
|
780
|
+
await expect(page.locator(${code})).toHaveValue(${q(e.inputValue)});`;
|
|
781
|
+
}
|
|
782
|
+
return call;
|
|
783
|
+
}
|
|
784
|
+
case "press":
|
|
785
|
+
return ` await human.press(${q(p.key)});`;
|
|
786
|
+
case "scroll":
|
|
787
|
+
return emitScroll(p.target);
|
|
788
|
+
case "read": {
|
|
789
|
+
const desc = String(p.target ?? "");
|
|
790
|
+
if (/^\d+ words$/.test(desc) || /^text:\d+ chars$/.test(desc)) {
|
|
791
|
+
return ` // human.read(...) \u2014 ${desc}; original target not captured`;
|
|
792
|
+
}
|
|
793
|
+
const call = ` await human.read(${q(desc)});`;
|
|
794
|
+
if (opts.asserts) return `${call}
|
|
795
|
+
await expect(page.locator(${q(desc)})).toBeVisible();`;
|
|
796
|
+
return call;
|
|
797
|
+
}
|
|
798
|
+
case "sleep":
|
|
799
|
+
return ` await sleep(${Number(p.ms) || 0});`;
|
|
800
|
+
case "reload":
|
|
801
|
+
return " await human.reload();";
|
|
802
|
+
case "goBack":
|
|
803
|
+
return " await human.goBack();";
|
|
804
|
+
case "goForward":
|
|
805
|
+
return " await human.goForward();";
|
|
806
|
+
default:
|
|
807
|
+
return ` // unsupported action: ${e.type}`;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function needsSleepImport(timeline) {
|
|
811
|
+
return timeline.events.some((e) => e.type === "sleep");
|
|
812
|
+
}
|
|
813
|
+
function generateHumanJS(timeline) {
|
|
814
|
+
const imports = needsSleepImport(timeline) ? "import { chromium, createHuman, sleep } from '@humanjs/playwright';" : "import { chromium, createHuman } from '@humanjs/playwright';";
|
|
815
|
+
const body = timeline.events.map((e) => emitAction(e)).join("\n");
|
|
816
|
+
return `${imports}
|
|
817
|
+
|
|
818
|
+
async function main() {
|
|
819
|
+
const browser = await chromium.launch({ headless: false });
|
|
820
|
+
const page = await browser.newPage();
|
|
821
|
+
const human = await createHuman(page, ${createHumanOptions(timeline)});
|
|
822
|
+
|
|
823
|
+
${body}
|
|
824
|
+
|
|
825
|
+
await browser.close();
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
main();
|
|
829
|
+
`;
|
|
830
|
+
}
|
|
831
|
+
var NAV_TYPES = /* @__PURE__ */ new Set(["goto", "reload", "goBack", "goForward"]);
|
|
832
|
+
function sharedGotoOrigin(events) {
|
|
833
|
+
let origin;
|
|
834
|
+
for (const e of events) {
|
|
835
|
+
if (e.type !== "goto") continue;
|
|
836
|
+
try {
|
|
837
|
+
const o = new URL(String(e.params.url ?? "")).origin;
|
|
838
|
+
if (origin === void 0) origin = o;
|
|
839
|
+
else if (origin !== o) return void 0;
|
|
840
|
+
} catch {
|
|
841
|
+
return void 0;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return origin;
|
|
845
|
+
}
|
|
846
|
+
function indentLines(block, pad) {
|
|
847
|
+
return block.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
|
|
848
|
+
}
|
|
849
|
+
function stepLabel(event, index, baseOrigin) {
|
|
850
|
+
switch (event.type) {
|
|
851
|
+
case "goto": {
|
|
852
|
+
const url = String(event.params.url ?? "");
|
|
853
|
+
const path = baseOrigin && url.startsWith(baseOrigin) ? url.slice(baseOrigin.length) || "/" : url;
|
|
854
|
+
return `go to ${path}`;
|
|
855
|
+
}
|
|
856
|
+
case "reload":
|
|
857
|
+
return "reload";
|
|
858
|
+
case "goBack":
|
|
859
|
+
return "go back";
|
|
860
|
+
case "goForward":
|
|
861
|
+
return "go forward";
|
|
862
|
+
default:
|
|
863
|
+
return `step ${index + 1}`;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function emitSteps(events, opts) {
|
|
867
|
+
const groups = [];
|
|
868
|
+
for (const e of events) {
|
|
869
|
+
const last = groups[groups.length - 1];
|
|
870
|
+
if (last === void 0 || NAV_TYPES.has(e.type)) groups.push([e]);
|
|
871
|
+
else last.push(e);
|
|
872
|
+
}
|
|
873
|
+
return groups.map((group, i) => {
|
|
874
|
+
const [first] = group;
|
|
875
|
+
const label = first ? stepLabel(first, i, opts.baseOrigin) : `step ${i + 1}`;
|
|
876
|
+
const inner = group.map((e) => indentLines(emitAction(e, opts), " ")).join("\n");
|
|
877
|
+
return ` await test.step(${q(label)}, async () => {
|
|
878
|
+
${inner}
|
|
879
|
+
});`;
|
|
880
|
+
}).join("\n\n");
|
|
881
|
+
}
|
|
882
|
+
function generatePlaywrightTest(timeline, options = {}) {
|
|
883
|
+
const events = options.keepSleeps ? timeline.events : timeline.events.filter((e) => e.type !== "sleep");
|
|
884
|
+
const baseOrigin = options.baseUrl ? sharedGotoOrigin(events) : void 0;
|
|
885
|
+
const emitOpts = { asserts: true, baseOrigin };
|
|
886
|
+
const body = options.steps ? emitSteps(events, emitOpts) : events.map((e) => emitAction(e, emitOpts)).join("\n");
|
|
887
|
+
const needsSleep = events.some((e) => e.type === "sleep");
|
|
888
|
+
const hasAsserts = body.includes("await expect(");
|
|
889
|
+
const testImport = hasAsserts ? "import { expect, test } from '@playwright/test';" : "import { test } from '@playwright/test';";
|
|
890
|
+
const humanImport = needsSleep ? "import { createHuman, sleep } from '@humanjs/playwright';" : "import { createHuman } from '@humanjs/playwright';";
|
|
891
|
+
const title = options.title ?? timeline.name ?? "recorded session";
|
|
892
|
+
const baseUrlNote = baseOrigin ? ` // Set use.baseURL = ${q(baseOrigin)} in playwright.config.ts for these relative paths.
|
|
893
|
+
|
|
894
|
+
` : "";
|
|
895
|
+
const todo = [
|
|
896
|
+
hasAsserts ? " // TODO: add assertions for the outcome of this flow, e.g.:" : " // TODO: assert the outcome \u2014 import { expect } from '@playwright/test', e.g.:",
|
|
897
|
+
" // await expect(page).toHaveURL(/dashboard/);",
|
|
898
|
+
" // await expect(page.getByText('Welcome back')).toBeVisible();"
|
|
899
|
+
].join("\n");
|
|
900
|
+
return `${testImport}
|
|
901
|
+
${humanImport}
|
|
902
|
+
|
|
903
|
+
test(${q(title)}, async ({ page }) => {
|
|
904
|
+
const human = await createHuman(page, ${createHumanOptions(timeline, true)});
|
|
905
|
+
|
|
906
|
+
${baseUrlNote}${body}
|
|
907
|
+
|
|
908
|
+
${todo}
|
|
909
|
+
});
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/recording/index.ts
|
|
709
914
|
var pendingFrameCleanups = /* @__PURE__ */ new Set();
|
|
710
915
|
var exitHandlerInstalled = false;
|
|
711
916
|
function ensureExitHandler() {
|
|
@@ -801,6 +1006,7 @@ var Recording = class {
|
|
|
801
1006
|
get timeline() {
|
|
802
1007
|
return {
|
|
803
1008
|
version: 1,
|
|
1009
|
+
...this.#timelineSource.name !== void 0 ? { name: this.#timelineSource.name } : {},
|
|
804
1010
|
personality: this.#timelineSource.personality,
|
|
805
1011
|
seed: this.#timelineSource.seed,
|
|
806
1012
|
speed: this.#timelineSource.speed,
|
|
@@ -951,6 +1157,38 @@ var Recording = class {
|
|
|
951
1157
|
`, "utf8");
|
|
952
1158
|
return outputPath;
|
|
953
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Generates a standalone, runnable HumanJS script from the timeline and
|
|
1162
|
+
* writes it to `outputPath`. String selectors round-trip verbatim; typed
|
|
1163
|
+
* values are included when `captureInputs` was on (passwords masked).
|
|
1164
|
+
*
|
|
1165
|
+
* Independent of frame capture — works on timeline-only recordings and is
|
|
1166
|
+
* unaffected by `dispose()`.
|
|
1167
|
+
*
|
|
1168
|
+
* @returns the resolved output path.
|
|
1169
|
+
*/
|
|
1170
|
+
async toHumanJS(outputPath) {
|
|
1171
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
1172
|
+
await writeFile(outputPath, generateHumanJS(this.timeline), "utf8");
|
|
1173
|
+
return outputPath;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Generates a `@playwright/test` spec from the timeline — a humanized test
|
|
1177
|
+
* (uses `createHuman` + `human.*`), not raw Playwright — and writes it to
|
|
1178
|
+
* `outputPath`. Runs instant in CI / recorded speed locally, drops timing
|
|
1179
|
+
* `sleep()`s (pass `{ keepSleeps: true }` to keep them), and derives the
|
|
1180
|
+
* assertions it safely can.
|
|
1181
|
+
*
|
|
1182
|
+
* Independent of frame capture — works on timeline-only recordings and is
|
|
1183
|
+
* unaffected by `dispose()`.
|
|
1184
|
+
*
|
|
1185
|
+
* @returns the resolved output path.
|
|
1186
|
+
*/
|
|
1187
|
+
async toPlaywright(outputPath, options) {
|
|
1188
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
1189
|
+
await writeFile(outputPath, generatePlaywrightTest(this.timeline, options), "utf8");
|
|
1190
|
+
return outputPath;
|
|
1191
|
+
}
|
|
954
1192
|
/**
|
|
955
1193
|
* Releases the captured-frames temp directory. After this call, `toVideo()`
|
|
956
1194
|
* and `toGif()` throw — but `toTimeline()` and the in-memory `timeline`
|
|
@@ -1222,7 +1460,8 @@ async function createHuman(page, options = {}) {
|
|
|
1222
1460
|
let hasRecorded = false;
|
|
1223
1461
|
let activeRecordingEvents = null;
|
|
1224
1462
|
let activeRecordingStartMs = 0;
|
|
1225
|
-
|
|
1463
|
+
let activeRecordingCaptureInputs = false;
|
|
1464
|
+
async function performAction(action, actionFn, recordMeta) {
|
|
1226
1465
|
for (const plugin of plugins) {
|
|
1227
1466
|
await plugin.beforeAction?.(action);
|
|
1228
1467
|
}
|
|
@@ -1236,7 +1475,8 @@ async function createHuman(page, options = {}) {
|
|
|
1236
1475
|
type: action.type,
|
|
1237
1476
|
params: action.params ?? {},
|
|
1238
1477
|
tMs: startedAt - activeRecordingStartMs,
|
|
1239
|
-
durationMs
|
|
1478
|
+
durationMs,
|
|
1479
|
+
...recordMeta?.inputValue !== void 0 ? { inputValue: recordMeta.inputValue } : {}
|
|
1240
1480
|
});
|
|
1241
1481
|
}
|
|
1242
1482
|
for (const plugin of plugins) {
|
|
@@ -1250,7 +1490,8 @@ async function createHuman(page, options = {}) {
|
|
|
1250
1490
|
params: action.params ?? {},
|
|
1251
1491
|
tMs: startedAt - activeRecordingStartMs,
|
|
1252
1492
|
durationMs: Date.now() - startedAt,
|
|
1253
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1493
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1494
|
+
...recordMeta?.inputValue !== void 0 ? { inputValue: recordMeta.inputValue } : {}
|
|
1254
1495
|
});
|
|
1255
1496
|
}
|
|
1256
1497
|
for (const plugin of plugins) {
|
|
@@ -1277,6 +1518,21 @@ async function createHuman(page, options = {}) {
|
|
|
1277
1518
|
}
|
|
1278
1519
|
return target.toString?.() ?? "locator";
|
|
1279
1520
|
};
|
|
1521
|
+
const isPointTarget = (target) => typeof target !== "string" && "x" in target && "y" in target && typeof target.x === "number";
|
|
1522
|
+
const captureInputValue = async (target, value) => {
|
|
1523
|
+
if (activeRecordingEvents === null || !activeRecordingCaptureInputs) return void 0;
|
|
1524
|
+
const locator = typeof target === "string" ? page.locator(target) : isPointTarget(target) ? null : target;
|
|
1525
|
+
if (locator !== null) {
|
|
1526
|
+
try {
|
|
1527
|
+
const fieldType = await locator.first().getAttribute("type", { timeout: 1e3 });
|
|
1528
|
+
if (fieldType?.toLowerCase() === "password") {
|
|
1529
|
+
return void 0;
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return value;
|
|
1535
|
+
};
|
|
1280
1536
|
return {
|
|
1281
1537
|
personality,
|
|
1282
1538
|
speed,
|
|
@@ -1318,6 +1574,7 @@ async function createHuman(page, options = {}) {
|
|
|
1318
1574
|
},
|
|
1319
1575
|
async type(target, value) {
|
|
1320
1576
|
const description = describeMouseTarget(target);
|
|
1577
|
+
const inputValue = await captureInputValue(target, value);
|
|
1321
1578
|
await performAction(
|
|
1322
1579
|
{ type: "type", params: { target: description, length: value.length } },
|
|
1323
1580
|
async () => {
|
|
@@ -1325,11 +1582,13 @@ async function createHuman(page, options = {}) {
|
|
|
1325
1582
|
await executeClick(target, mouseCtx());
|
|
1326
1583
|
}
|
|
1327
1584
|
await executeType(target, value, { page, personality, rng, speed });
|
|
1328
|
-
}
|
|
1585
|
+
},
|
|
1586
|
+
inputValue !== void 0 ? { inputValue } : void 0
|
|
1329
1587
|
);
|
|
1330
1588
|
},
|
|
1331
1589
|
async paste(target, value) {
|
|
1332
1590
|
const description = describeMouseTarget(target);
|
|
1591
|
+
const inputValue = await captureInputValue(target, value);
|
|
1333
1592
|
await performAction(
|
|
1334
1593
|
{ type: "paste", params: { target: description, length: value.length } },
|
|
1335
1594
|
async () => {
|
|
@@ -1337,7 +1596,8 @@ async function createHuman(page, options = {}) {
|
|
|
1337
1596
|
await executeClick(target, mouseCtx());
|
|
1338
1597
|
}
|
|
1339
1598
|
await executePaste(target, value, { page, personality, rng, speed });
|
|
1340
|
-
}
|
|
1599
|
+
},
|
|
1600
|
+
inputValue !== void 0 ? { inputValue } : void 0
|
|
1341
1601
|
);
|
|
1342
1602
|
},
|
|
1343
1603
|
async press(key) {
|
|
@@ -1406,6 +1666,7 @@ async function createHuman(page, options = {}) {
|
|
|
1406
1666
|
const windowStartMs = Date.now();
|
|
1407
1667
|
activeRecordingEvents = events;
|
|
1408
1668
|
activeRecordingStartMs = windowStartMs;
|
|
1669
|
+
activeRecordingCaptureInputs = recordOptions.captureInputs !== false;
|
|
1409
1670
|
let windowEndMs = windowStartMs;
|
|
1410
1671
|
try {
|
|
1411
1672
|
await performAction({ type: "record", params: {} }, async () => {
|
|
@@ -1420,14 +1681,62 @@ async function createHuman(page, options = {}) {
|
|
|
1420
1681
|
throw error;
|
|
1421
1682
|
} finally {
|
|
1422
1683
|
activeRecordingEvents = null;
|
|
1684
|
+
activeRecordingCaptureInputs = false;
|
|
1423
1685
|
}
|
|
1424
1686
|
const captureResult = captureSession ? await captureSession.stop() : null;
|
|
1425
1687
|
return new Recording(captureResult, windowStartMs, windowEndMs, {
|
|
1688
|
+
name: recordOptions.name,
|
|
1426
1689
|
personality: personality.name,
|
|
1427
1690
|
seed: options.seed === void 0 ? null : String(options.seed),
|
|
1428
1691
|
speed,
|
|
1429
1692
|
events
|
|
1430
1693
|
});
|
|
1694
|
+
},
|
|
1695
|
+
// ────────────────────────────────────────────────────────────────────
|
|
1696
|
+
// Thin re-exports of common Playwright `Page` methods. See the `Human`
|
|
1697
|
+
// interface for the rationale; implementations forward unchanged.
|
|
1698
|
+
// ────────────────────────────────────────────────────────────────────
|
|
1699
|
+
screenshot(opts) {
|
|
1700
|
+
return page.screenshot(opts);
|
|
1701
|
+
},
|
|
1702
|
+
pageText() {
|
|
1703
|
+
return page.innerText("body");
|
|
1704
|
+
},
|
|
1705
|
+
content() {
|
|
1706
|
+
return page.content();
|
|
1707
|
+
},
|
|
1708
|
+
url() {
|
|
1709
|
+
return page.url();
|
|
1710
|
+
},
|
|
1711
|
+
title() {
|
|
1712
|
+
return page.title();
|
|
1713
|
+
},
|
|
1714
|
+
async reload(opts) {
|
|
1715
|
+
await performAction({ type: "reload", params: {} }, async () => {
|
|
1716
|
+
await page.reload(opts);
|
|
1717
|
+
});
|
|
1718
|
+
},
|
|
1719
|
+
async goBack(opts) {
|
|
1720
|
+
await performAction({ type: "goBack", params: {} }, async () => {
|
|
1721
|
+
await page.goBack(opts);
|
|
1722
|
+
});
|
|
1723
|
+
},
|
|
1724
|
+
async goForward(opts) {
|
|
1725
|
+
await performAction({ type: "goForward", params: {} }, async () => {
|
|
1726
|
+
await page.goForward(opts);
|
|
1727
|
+
});
|
|
1728
|
+
},
|
|
1729
|
+
waitForLoadState(state, opts) {
|
|
1730
|
+
return page.waitForLoadState(state, opts);
|
|
1731
|
+
},
|
|
1732
|
+
waitForURL(url, opts) {
|
|
1733
|
+
return page.waitForURL(url, opts);
|
|
1734
|
+
},
|
|
1735
|
+
setViewportSize(size) {
|
|
1736
|
+
return page.setViewportSize(size);
|
|
1737
|
+
},
|
|
1738
|
+
pdf(opts) {
|
|
1739
|
+
return page.pdf(opts);
|
|
1431
1740
|
}
|
|
1432
1741
|
};
|
|
1433
1742
|
}
|