@humanjs/playwright 0.6.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 +26 -1
- package/dist/index.cjs +262 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.js +262 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -341,7 +341,32 @@ await rec.toTimeline('session.json'); // → JSON on disk
|
|
|
341
341
|
const timeline = rec.timeline; // → in-memory object
|
|
342
342
|
```
|
|
343
343
|
|
|
344
|
-
The shape (`Timeline` with `personality`, `seed`, `speed`, `durationMs`, and an `events` array of `{ type, params, tMs, durationMs }`) is intended for observability pipelines, replay infrastructure, analytics, and debugger UIs. `toTimeline()` doesn't touch the browser context — call it before or after `toVideo()`, multiple times, in any order.
|
|
344
|
+
The shape (`Timeline` with `personality`, `seed`, `speed`, `durationMs`, and an `events` array of `{ type, params, tMs, durationMs }`, plus `inputValue` on captured `type`/`paste` events) is intended for observability pipelines, replay infrastructure, analytics, and debugger UIs. `toTimeline()` doesn't touch the browser context — call it before or after `toVideo()`, multiple times, in any order.
|
|
345
|
+
|
|
346
|
+
**Code export** — turn the same recording into runnable code:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
await rec.toHumanJS('session.ts'); // standalone HumanJS script
|
|
350
|
+
await rec.toPlaywright('session.spec.ts'); // @playwright/test spec (humanized)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`toHumanJS()` emits a standalone script (`createHuman` + `human.*`); `toPlaywright()` emits a `@playwright/test` spec that drives the page through HumanJS, so the generated test runs humanized too. String selectors round-trip verbatim. Both work on timeline-only recordings and are unaffected by `dispose()`.
|
|
354
|
+
|
|
355
|
+
`toPlaywright()` also derives the assertions it safely can from the recording — a `read` implies its target was visible (`toBeVisible`), a captured input implies its value (`toHaveValue`) — and leaves a `TODO` for outcome assertions (URL changed, text appeared) that can't be inferred from actions alone. It never fabricates assertions that might fail on a correct run.
|
|
356
|
+
|
|
357
|
+
Generated tests are built to *be tests*: they run `speed: process.env.CI ? 'instant' : '<recorded>'` (instant in CI, recorded feel locally) and drop recorded `sleep()` pauses (timing fidelity belongs in a demo, not a test — pass `toPlaywright(path, { keepSleeps: true })` to keep them). The test title comes from the recording's name (`human.record({ name })`) and is overridable with `{ title }`.
|
|
358
|
+
|
|
359
|
+
Two more options: `{ steps: true }` groups the actions into `test.step(...)` blocks (a new step per navigation) for collapsible sections in the HTML report and trace; `{ baseUrl: true }` rewrites same-origin `goto`s to relative paths and adds a note to set `use.baseURL` in your `playwright.config.ts` — so the same test runs against local / staging / prod.
|
|
360
|
+
|
|
361
|
+
By default the actual typed/pasted text is captured into the timeline (and the exported code). Values typed into `input[type="password"]` are always masked; set `captureInputs: false` to record none — exports then emit empty-string placeholders:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
await human.record({ captureInputs: false }, fn);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
> Captured input values land in the timeline JSON and any exported code — treat those artifacts with the same care as the values themselves.
|
|
368
|
+
|
|
369
|
+
Two limits, by design: a target passed as a `Locator` or a raw `point(x, y)` doesn't round-trip to a clean selector (points are emitted verbatim with a flag comment — locator/point → selector synthesis is a planned follow-up), and reads driven by word-count or raw text emit a note instead of code.
|
|
345
370
|
|
|
346
371
|
**Quality presets** trade off file size, encoding time, and visual fidelity. Defaults to `'high'`:
|
|
347
372
|
|
package/dist/index.cjs
CHANGED
|
@@ -717,6 +717,205 @@ async function detectKindFromTag(locator) {
|
|
|
717
717
|
if (tag === "pre" || tag === "code") return "code";
|
|
718
718
|
return void 0;
|
|
719
719
|
}
|
|
720
|
+
|
|
721
|
+
// src/recording/codegen.ts
|
|
722
|
+
var POINT_RE = /^point\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)$/;
|
|
723
|
+
var POINT_COMMENT = " // raw coordinate \u2014 replace with a locator for a stable selector";
|
|
724
|
+
var UNCAPTURED_COMMENT = " // input not captured (masked or captureInputs disabled) \u2014 fill in (e.g. process.env.X)";
|
|
725
|
+
function q(value) {
|
|
726
|
+
return `'${String(value ?? "").replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r")}'`;
|
|
727
|
+
}
|
|
728
|
+
function targetArg(desc) {
|
|
729
|
+
const s = String(desc ?? "");
|
|
730
|
+
const m = s.match(POINT_RE);
|
|
731
|
+
if (m) return { code: `{ x: ${m[1]}, y: ${m[2]} }`, isPoint: true };
|
|
732
|
+
return { code: q(s), isPoint: false };
|
|
733
|
+
}
|
|
734
|
+
function createHumanOptions(timeline, ciSpeed = false) {
|
|
735
|
+
const parts = [` personality: ${q(timeline.personality)},`];
|
|
736
|
+
if (timeline.seed !== null) parts.push(` seed: ${q(timeline.seed)},`);
|
|
737
|
+
parts.push(
|
|
738
|
+
ciSpeed ? ` speed: process.env.CI ? 'instant' : ${q(timeline.speed)},` : ` speed: ${q(timeline.speed)},`
|
|
739
|
+
);
|
|
740
|
+
return `{
|
|
741
|
+
${parts.join("\n")}
|
|
742
|
+
}`;
|
|
743
|
+
}
|
|
744
|
+
function emitScroll(target) {
|
|
745
|
+
const s = String(target ?? "natural");
|
|
746
|
+
if (s === "natural") return " await human.scroll('natural');";
|
|
747
|
+
const by = s.match(/^by:(-?\d+(?:\.\d+)?)$/);
|
|
748
|
+
if (by) return ` await human.scroll({ by: ${by[1]} });`;
|
|
749
|
+
const to = s.match(/^to:(-?\d+(?:\.\d+)?)$/);
|
|
750
|
+
if (to) return ` await human.scroll({ to: ${to[1]} });`;
|
|
751
|
+
return ` await human.scroll(${q(s)});`;
|
|
752
|
+
}
|
|
753
|
+
function emitAction(e, opts = {}) {
|
|
754
|
+
const p = e.params;
|
|
755
|
+
switch (e.type) {
|
|
756
|
+
case "goto": {
|
|
757
|
+
const url = String(p.url ?? "");
|
|
758
|
+
if (opts.baseOrigin && url.startsWith(opts.baseOrigin)) {
|
|
759
|
+
return ` await human.goto(${q(url.slice(opts.baseOrigin.length) || "/")});`;
|
|
760
|
+
}
|
|
761
|
+
return ` await human.goto(${q(url)});`;
|
|
762
|
+
}
|
|
763
|
+
case "click":
|
|
764
|
+
case "rightClick":
|
|
765
|
+
case "hover":
|
|
766
|
+
case "move": {
|
|
767
|
+
const { code, isPoint: isPoint2 } = targetArg(p.target);
|
|
768
|
+
return ` await human.${e.type}(${code});${isPoint2 ? POINT_COMMENT : ""}`;
|
|
769
|
+
}
|
|
770
|
+
case "drag": {
|
|
771
|
+
const from = targetArg(p.from);
|
|
772
|
+
const to = targetArg(p.to);
|
|
773
|
+
const comment = from.isPoint || to.isPoint ? POINT_COMMENT : "";
|
|
774
|
+
return ` await human.drag(${from.code}, ${to.code});${comment}`;
|
|
775
|
+
}
|
|
776
|
+
case "type":
|
|
777
|
+
case "paste": {
|
|
778
|
+
const { code, isPoint: isPoint2 } = targetArg(p.target);
|
|
779
|
+
if (e.inputValue === void 0) {
|
|
780
|
+
return ` await human.${e.type}(${code}, '');${UNCAPTURED_COMMENT}`;
|
|
781
|
+
}
|
|
782
|
+
const call = ` await human.${e.type}(${code}, ${q(e.inputValue)});`;
|
|
783
|
+
if (opts.asserts && !isPoint2) {
|
|
784
|
+
return `${call}
|
|
785
|
+
await expect(page.locator(${code})).toHaveValue(${q(e.inputValue)});`;
|
|
786
|
+
}
|
|
787
|
+
return call;
|
|
788
|
+
}
|
|
789
|
+
case "press":
|
|
790
|
+
return ` await human.press(${q(p.key)});`;
|
|
791
|
+
case "scroll":
|
|
792
|
+
return emitScroll(p.target);
|
|
793
|
+
case "read": {
|
|
794
|
+
const desc = String(p.target ?? "");
|
|
795
|
+
if (/^\d+ words$/.test(desc) || /^text:\d+ chars$/.test(desc)) {
|
|
796
|
+
return ` // human.read(...) \u2014 ${desc}; original target not captured`;
|
|
797
|
+
}
|
|
798
|
+
const call = ` await human.read(${q(desc)});`;
|
|
799
|
+
if (opts.asserts) return `${call}
|
|
800
|
+
await expect(page.locator(${q(desc)})).toBeVisible();`;
|
|
801
|
+
return call;
|
|
802
|
+
}
|
|
803
|
+
case "sleep":
|
|
804
|
+
return ` await sleep(${Number(p.ms) || 0});`;
|
|
805
|
+
case "reload":
|
|
806
|
+
return " await human.reload();";
|
|
807
|
+
case "goBack":
|
|
808
|
+
return " await human.goBack();";
|
|
809
|
+
case "goForward":
|
|
810
|
+
return " await human.goForward();";
|
|
811
|
+
default:
|
|
812
|
+
return ` // unsupported action: ${e.type}`;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function needsSleepImport(timeline) {
|
|
816
|
+
return timeline.events.some((e) => e.type === "sleep");
|
|
817
|
+
}
|
|
818
|
+
function generateHumanJS(timeline) {
|
|
819
|
+
const imports = needsSleepImport(timeline) ? "import { chromium, createHuman, sleep } from '@humanjs/playwright';" : "import { chromium, createHuman } from '@humanjs/playwright';";
|
|
820
|
+
const body = timeline.events.map((e) => emitAction(e)).join("\n");
|
|
821
|
+
return `${imports}
|
|
822
|
+
|
|
823
|
+
async function main() {
|
|
824
|
+
const browser = await chromium.launch({ headless: false });
|
|
825
|
+
const page = await browser.newPage();
|
|
826
|
+
const human = await createHuman(page, ${createHumanOptions(timeline)});
|
|
827
|
+
|
|
828
|
+
${body}
|
|
829
|
+
|
|
830
|
+
await browser.close();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
main();
|
|
834
|
+
`;
|
|
835
|
+
}
|
|
836
|
+
var NAV_TYPES = /* @__PURE__ */ new Set(["goto", "reload", "goBack", "goForward"]);
|
|
837
|
+
function sharedGotoOrigin(events) {
|
|
838
|
+
let origin;
|
|
839
|
+
for (const e of events) {
|
|
840
|
+
if (e.type !== "goto") continue;
|
|
841
|
+
try {
|
|
842
|
+
const o = new URL(String(e.params.url ?? "")).origin;
|
|
843
|
+
if (origin === void 0) origin = o;
|
|
844
|
+
else if (origin !== o) return void 0;
|
|
845
|
+
} catch {
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return origin;
|
|
850
|
+
}
|
|
851
|
+
function indentLines(block, pad) {
|
|
852
|
+
return block.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
|
|
853
|
+
}
|
|
854
|
+
function stepLabel(event, index, baseOrigin) {
|
|
855
|
+
switch (event.type) {
|
|
856
|
+
case "goto": {
|
|
857
|
+
const url = String(event.params.url ?? "");
|
|
858
|
+
const path = baseOrigin && url.startsWith(baseOrigin) ? url.slice(baseOrigin.length) || "/" : url;
|
|
859
|
+
return `go to ${path}`;
|
|
860
|
+
}
|
|
861
|
+
case "reload":
|
|
862
|
+
return "reload";
|
|
863
|
+
case "goBack":
|
|
864
|
+
return "go back";
|
|
865
|
+
case "goForward":
|
|
866
|
+
return "go forward";
|
|
867
|
+
default:
|
|
868
|
+
return `step ${index + 1}`;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function emitSteps(events, opts) {
|
|
872
|
+
const groups = [];
|
|
873
|
+
for (const e of events) {
|
|
874
|
+
const last = groups[groups.length - 1];
|
|
875
|
+
if (last === void 0 || NAV_TYPES.has(e.type)) groups.push([e]);
|
|
876
|
+
else last.push(e);
|
|
877
|
+
}
|
|
878
|
+
return groups.map((group, i) => {
|
|
879
|
+
const [first] = group;
|
|
880
|
+
const label = first ? stepLabel(first, i, opts.baseOrigin) : `step ${i + 1}`;
|
|
881
|
+
const inner = group.map((e) => indentLines(emitAction(e, opts), " ")).join("\n");
|
|
882
|
+
return ` await test.step(${q(label)}, async () => {
|
|
883
|
+
${inner}
|
|
884
|
+
});`;
|
|
885
|
+
}).join("\n\n");
|
|
886
|
+
}
|
|
887
|
+
function generatePlaywrightTest(timeline, options = {}) {
|
|
888
|
+
const events = options.keepSleeps ? timeline.events : timeline.events.filter((e) => e.type !== "sleep");
|
|
889
|
+
const baseOrigin = options.baseUrl ? sharedGotoOrigin(events) : void 0;
|
|
890
|
+
const emitOpts = { asserts: true, baseOrigin };
|
|
891
|
+
const body = options.steps ? emitSteps(events, emitOpts) : events.map((e) => emitAction(e, emitOpts)).join("\n");
|
|
892
|
+
const needsSleep = events.some((e) => e.type === "sleep");
|
|
893
|
+
const hasAsserts = body.includes("await expect(");
|
|
894
|
+
const testImport = hasAsserts ? "import { expect, test } from '@playwright/test';" : "import { test } from '@playwright/test';";
|
|
895
|
+
const humanImport = needsSleep ? "import { createHuman, sleep } from '@humanjs/playwright';" : "import { createHuman } from '@humanjs/playwright';";
|
|
896
|
+
const title = options.title ?? timeline.name ?? "recorded session";
|
|
897
|
+
const baseUrlNote = baseOrigin ? ` // Set use.baseURL = ${q(baseOrigin)} in playwright.config.ts for these relative paths.
|
|
898
|
+
|
|
899
|
+
` : "";
|
|
900
|
+
const todo = [
|
|
901
|
+
hasAsserts ? " // TODO: add assertions for the outcome of this flow, e.g.:" : " // TODO: assert the outcome \u2014 import { expect } from '@playwright/test', e.g.:",
|
|
902
|
+
" // await expect(page).toHaveURL(/dashboard/);",
|
|
903
|
+
" // await expect(page.getByText('Welcome back')).toBeVisible();"
|
|
904
|
+
].join("\n");
|
|
905
|
+
return `${testImport}
|
|
906
|
+
${humanImport}
|
|
907
|
+
|
|
908
|
+
test(${q(title)}, async ({ page }) => {
|
|
909
|
+
const human = await createHuman(page, ${createHumanOptions(timeline, true)});
|
|
910
|
+
|
|
911
|
+
${baseUrlNote}${body}
|
|
912
|
+
|
|
913
|
+
${todo}
|
|
914
|
+
});
|
|
915
|
+
`;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/recording/index.ts
|
|
720
919
|
var pendingFrameCleanups = /* @__PURE__ */ new Set();
|
|
721
920
|
var exitHandlerInstalled = false;
|
|
722
921
|
function ensureExitHandler() {
|
|
@@ -812,6 +1011,7 @@ var Recording = class {
|
|
|
812
1011
|
get timeline() {
|
|
813
1012
|
return {
|
|
814
1013
|
version: 1,
|
|
1014
|
+
...this.#timelineSource.name !== void 0 ? { name: this.#timelineSource.name } : {},
|
|
815
1015
|
personality: this.#timelineSource.personality,
|
|
816
1016
|
seed: this.#timelineSource.seed,
|
|
817
1017
|
speed: this.#timelineSource.speed,
|
|
@@ -962,6 +1162,38 @@ var Recording = class {
|
|
|
962
1162
|
`, "utf8");
|
|
963
1163
|
return outputPath;
|
|
964
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Generates a standalone, runnable HumanJS script from the timeline and
|
|
1167
|
+
* writes it to `outputPath`. String selectors round-trip verbatim; typed
|
|
1168
|
+
* values are included when `captureInputs` was on (passwords masked).
|
|
1169
|
+
*
|
|
1170
|
+
* Independent of frame capture — works on timeline-only recordings and is
|
|
1171
|
+
* unaffected by `dispose()`.
|
|
1172
|
+
*
|
|
1173
|
+
* @returns the resolved output path.
|
|
1174
|
+
*/
|
|
1175
|
+
async toHumanJS(outputPath) {
|
|
1176
|
+
await promises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
1177
|
+
await promises.writeFile(outputPath, generateHumanJS(this.timeline), "utf8");
|
|
1178
|
+
return outputPath;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Generates a `@playwright/test` spec from the timeline — a humanized test
|
|
1182
|
+
* (uses `createHuman` + `human.*`), not raw Playwright — and writes it to
|
|
1183
|
+
* `outputPath`. Runs instant in CI / recorded speed locally, drops timing
|
|
1184
|
+
* `sleep()`s (pass `{ keepSleeps: true }` to keep them), and derives the
|
|
1185
|
+
* assertions it safely can.
|
|
1186
|
+
*
|
|
1187
|
+
* Independent of frame capture — works on timeline-only recordings and is
|
|
1188
|
+
* unaffected by `dispose()`.
|
|
1189
|
+
*
|
|
1190
|
+
* @returns the resolved output path.
|
|
1191
|
+
*/
|
|
1192
|
+
async toPlaywright(outputPath, options) {
|
|
1193
|
+
await promises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
1194
|
+
await promises.writeFile(outputPath, generatePlaywrightTest(this.timeline, options), "utf8");
|
|
1195
|
+
return outputPath;
|
|
1196
|
+
}
|
|
965
1197
|
/**
|
|
966
1198
|
* Releases the captured-frames temp directory. After this call, `toVideo()`
|
|
967
1199
|
* and `toGif()` throw — but `toTimeline()` and the in-memory `timeline`
|
|
@@ -1233,7 +1465,8 @@ async function createHuman(page, options = {}) {
|
|
|
1233
1465
|
let hasRecorded = false;
|
|
1234
1466
|
let activeRecordingEvents = null;
|
|
1235
1467
|
let activeRecordingStartMs = 0;
|
|
1236
|
-
|
|
1468
|
+
let activeRecordingCaptureInputs = false;
|
|
1469
|
+
async function performAction(action, actionFn, recordMeta) {
|
|
1237
1470
|
for (const plugin of plugins) {
|
|
1238
1471
|
await plugin.beforeAction?.(action);
|
|
1239
1472
|
}
|
|
@@ -1247,7 +1480,8 @@ async function createHuman(page, options = {}) {
|
|
|
1247
1480
|
type: action.type,
|
|
1248
1481
|
params: action.params ?? {},
|
|
1249
1482
|
tMs: startedAt - activeRecordingStartMs,
|
|
1250
|
-
durationMs
|
|
1483
|
+
durationMs,
|
|
1484
|
+
...recordMeta?.inputValue !== void 0 ? { inputValue: recordMeta.inputValue } : {}
|
|
1251
1485
|
});
|
|
1252
1486
|
}
|
|
1253
1487
|
for (const plugin of plugins) {
|
|
@@ -1261,7 +1495,8 @@ async function createHuman(page, options = {}) {
|
|
|
1261
1495
|
params: action.params ?? {},
|
|
1262
1496
|
tMs: startedAt - activeRecordingStartMs,
|
|
1263
1497
|
durationMs: Date.now() - startedAt,
|
|
1264
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1498
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1499
|
+
...recordMeta?.inputValue !== void 0 ? { inputValue: recordMeta.inputValue } : {}
|
|
1265
1500
|
});
|
|
1266
1501
|
}
|
|
1267
1502
|
for (const plugin of plugins) {
|
|
@@ -1288,6 +1523,21 @@ async function createHuman(page, options = {}) {
|
|
|
1288
1523
|
}
|
|
1289
1524
|
return target.toString?.() ?? "locator";
|
|
1290
1525
|
};
|
|
1526
|
+
const isPointTarget = (target) => typeof target !== "string" && "x" in target && "y" in target && typeof target.x === "number";
|
|
1527
|
+
const captureInputValue = async (target, value) => {
|
|
1528
|
+
if (activeRecordingEvents === null || !activeRecordingCaptureInputs) return void 0;
|
|
1529
|
+
const locator = typeof target === "string" ? page.locator(target) : isPointTarget(target) ? null : target;
|
|
1530
|
+
if (locator !== null) {
|
|
1531
|
+
try {
|
|
1532
|
+
const fieldType = await locator.first().getAttribute("type", { timeout: 1e3 });
|
|
1533
|
+
if (fieldType?.toLowerCase() === "password") {
|
|
1534
|
+
return void 0;
|
|
1535
|
+
}
|
|
1536
|
+
} catch {
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return value;
|
|
1540
|
+
};
|
|
1291
1541
|
return {
|
|
1292
1542
|
personality,
|
|
1293
1543
|
speed,
|
|
@@ -1329,6 +1579,7 @@ async function createHuman(page, options = {}) {
|
|
|
1329
1579
|
},
|
|
1330
1580
|
async type(target, value) {
|
|
1331
1581
|
const description = describeMouseTarget(target);
|
|
1582
|
+
const inputValue = await captureInputValue(target, value);
|
|
1332
1583
|
await performAction(
|
|
1333
1584
|
{ type: "type", params: { target: description, length: value.length } },
|
|
1334
1585
|
async () => {
|
|
@@ -1336,11 +1587,13 @@ async function createHuman(page, options = {}) {
|
|
|
1336
1587
|
await executeClick(target, mouseCtx());
|
|
1337
1588
|
}
|
|
1338
1589
|
await executeType(target, value, { page, personality, rng, speed });
|
|
1339
|
-
}
|
|
1590
|
+
},
|
|
1591
|
+
inputValue !== void 0 ? { inputValue } : void 0
|
|
1340
1592
|
);
|
|
1341
1593
|
},
|
|
1342
1594
|
async paste(target, value) {
|
|
1343
1595
|
const description = describeMouseTarget(target);
|
|
1596
|
+
const inputValue = await captureInputValue(target, value);
|
|
1344
1597
|
await performAction(
|
|
1345
1598
|
{ type: "paste", params: { target: description, length: value.length } },
|
|
1346
1599
|
async () => {
|
|
@@ -1348,7 +1601,8 @@ async function createHuman(page, options = {}) {
|
|
|
1348
1601
|
await executeClick(target, mouseCtx());
|
|
1349
1602
|
}
|
|
1350
1603
|
await executePaste(target, value, { page, personality, rng, speed });
|
|
1351
|
-
}
|
|
1604
|
+
},
|
|
1605
|
+
inputValue !== void 0 ? { inputValue } : void 0
|
|
1352
1606
|
);
|
|
1353
1607
|
},
|
|
1354
1608
|
async press(key) {
|
|
@@ -1417,6 +1671,7 @@ async function createHuman(page, options = {}) {
|
|
|
1417
1671
|
const windowStartMs = Date.now();
|
|
1418
1672
|
activeRecordingEvents = events;
|
|
1419
1673
|
activeRecordingStartMs = windowStartMs;
|
|
1674
|
+
activeRecordingCaptureInputs = recordOptions.captureInputs !== false;
|
|
1420
1675
|
let windowEndMs = windowStartMs;
|
|
1421
1676
|
try {
|
|
1422
1677
|
await performAction({ type: "record", params: {} }, async () => {
|
|
@@ -1431,9 +1686,11 @@ async function createHuman(page, options = {}) {
|
|
|
1431
1686
|
throw error;
|
|
1432
1687
|
} finally {
|
|
1433
1688
|
activeRecordingEvents = null;
|
|
1689
|
+
activeRecordingCaptureInputs = false;
|
|
1434
1690
|
}
|
|
1435
1691
|
const captureResult = captureSession ? await captureSession.stop() : null;
|
|
1436
1692
|
return new Recording(captureResult, windowStartMs, windowEndMs, {
|
|
1693
|
+
name: recordOptions.name,
|
|
1437
1694
|
personality: personality.name,
|
|
1438
1695
|
seed: options.seed === void 0 ? null : String(options.seed),
|
|
1439
1696
|
speed,
|