@humanjs/recorder 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gonzalo Muñoz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # @humanjs/recorder
2
+
3
+ One-call session recording for [HumanJS](https://humanjs.dev) — turn a humanized Playwright session into an mp4, an animated GIF, a structured JSON timeline, or any combination.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @humanjs/recorder @humanjs/playwright playwright
9
+ ```
10
+
11
+ `@humanjs/playwright` bundles `ffmpeg-static`, so no system ffmpeg install is required.
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { record } from '@humanjs/recorder';
17
+
18
+ // Record a session to mp4
19
+ await record({ output: 'demo.mp4' }, async (human) => {
20
+ await human.click('a');
21
+ await human.type('#search', 'humanjs');
22
+ });
23
+
24
+ // Timeline-only — no video overhead
25
+ const rec = await record(async (human) => {
26
+ await human.click('#login');
27
+ });
28
+ await rec.toTimeline('actions.json');
29
+ ```
30
+
31
+ `record()` launches a browser, opens a page, creates a humanized session, runs the callback, manages the entire lifecycle — and returns a `Recording` you can export to any supported format.
32
+
33
+ ## API shape
34
+
35
+ Two overloads — pass options when you have them, skip them when you don't:
36
+
37
+ ```ts
38
+ // No options needed
39
+ const rec = await record(async (human) => { ... });
40
+
41
+ // With options
42
+ const rec = await record({ output: 'demo.mp4', personality: 'careful' }, async (human) => { ... });
43
+ ```
44
+
45
+ The returned `Recording` has:
46
+
47
+ | | |
48
+ |---|---|
49
+ | `rec.toVideo(path, options?)` | Write an mp4 or webm. Repeatable. |
50
+ | `rec.toGif(path, options?)` | Write an animated GIF (palette-optimized, defaults to 15fps). Repeatable. |
51
+ | `rec.toTimeline(path)` | Write the structured JSON timeline. Repeatable. |
52
+ | `rec.timeline` | Read the in-memory `Timeline` object. |
53
+ | `rec.durationMs` | Wall-clock duration of the recorded window. |
54
+ | `rec.hasVideo` | True if frames were captured (i.e. `output` was set). |
55
+ | `rec.dispose()` | *Optional.* Release the captured-frames temp directory early — otherwise a sweep-on-exit handler cleans it when the process ends. After this, `toVideo` / `toGif` throw; `toTimeline` still works. Idempotent. |
56
+
57
+ The exporters are **repeatable and interleavable** — they read the captured frames, they don't consume them. If `output` was passed to `record()`, the matching exporter has already run for you, but you can still call any other exporter (or call the same one again to a different path) on the returned recording. Want mp4 and GIF from the same recording? Just do both:
58
+
59
+ ```ts
60
+ const rec = await record({ output: 'demo.mp4' }, async (human) => { ... });
61
+ await rec.toGif('demo.gif', { width: 720 });
62
+ await rec.toTimeline('demo.json');
63
+ // Done. No explicit cleanup needed for one-shot scripts.
64
+ ```
65
+
66
+ Captured frames live in a temp dir under `os.tmpdir()`. A `process.on('exit')` handler installed on first use sweeps any un-disposed frame dirs, so casual scripts don't have to think about lifecycle. For long-running services or anywhere you want predictable disk usage, call `await rec.dispose()` (idempotent) or use `await using rec = await record(...)` (TypeScript ≥ 5.2 / Node ≥ 20.4).
67
+
68
+ ## Options
69
+
70
+ ```ts
71
+ await record(
72
+ {
73
+ output: 'demo.mp4', // .mp4 / .webm / .gif — omit to skip video entirely
74
+ quality: 'high', // 'fast' | 'standard' | 'high' (default) | 'lossless'
75
+ url: 'https://example.com', // optional — navigate before the callback
76
+ personality: 'careful', // any PersonalityConfig
77
+ seed: 'session-42', // deterministic when set
78
+ viewport: { width: 1920, height: 1080 },
79
+ headless: false, // defaults false so you can watch the recording
80
+ cursor: true, // auto-install visible cursor overlay (default true)
81
+ launch: { args: ['--no-sandbox'] }, // forwarded to chromium.launch()
82
+ context: { locale: 'en-US' }, // forwarded to browser.newContext()
83
+ },
84
+ async (human, page) => {
85
+ // human is the @humanjs/playwright session — all primitives available
86
+ // page is the underlying Playwright Page, in case you need it
87
+ await human.click('#start');
88
+ await human.scroll('#features');
89
+ await human.read('#hero p');
90
+ },
91
+ );
92
+ ```
93
+
94
+ **No-video mode**: omit `output` entirely (or call `record(fn)` with no options). No screenshot polling, no temp files, no encoding overhead. The structured timeline is still captured.
95
+
96
+ ## Quality presets
97
+
98
+ Pick the preset that matches the recording's purpose:
99
+
100
+ | Preset | Viewport | Capture | CRF | ffmpeg preset | tune | Use case |
101
+ |---|---|---|---|---|---|---|
102
+ | `'fast'` | 1280×720 | JPEG q=85 | 23 | `fast` | — | Iteration, throw-away recordings |
103
+ | `'standard'` | 1920×1080 | JPEG q=90 | 20 | `fast` | — | CI dashboards, casual demos |
104
+ | **`'high'`** (default) | 1920×1080 | JPEG q=95 | 18 | `slow` | `animation` | Marketing, tutorials, portfolio output |
105
+ | `'lossless'` | 1920×1080 | PNG | 12 | `veryslow` | `animation` | Archival, edit source |
106
+
107
+ Individual encoding knobs (`crf`, `preset`, `tune`) are exposed through `@humanjs/playwright`'s `Recording.toVideo()` for advanced use — see [`@humanjs/playwright`](../playwright#recording).
108
+
109
+ ## When to use `record()` vs `human.record()`
110
+
111
+ | Use this | When |
112
+ |---|---|
113
+ | **`record()` from `@humanjs/recorder`** | You want lifecycle managed for you. The returned `Recording` still gives full programmatic access — `toTimeline()`, in-memory `.timeline`, `.durationMs`. |
114
+ | **`human.record(cb)` from `@humanjs/playwright`** | You already have a Playwright setup, need multi-page flows, or want to record a slice of a longer session. |
115
+
116
+ Both paths produce identical Recording objects — `record()` is a thin wrapper around `human.record()` plus lifecycle management.
117
+
118
+ ## License
119
+
120
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ var path = require('path');
4
+ var playwright = require('@humanjs/playwright');
5
+
6
+ // src/record/index.ts
7
+ var QUALITY_BROWSER_PRESETS = {
8
+ // 720p source — small files, fast encoding, good for iteration.
9
+ fast: { viewport: { width: 1280, height: 720 } },
10
+ // 1080p source — balanced default for tests and dashboards.
11
+ standard: { viewport: { width: 1920, height: 1080 } },
12
+ // 1080p source, visually-lossless encoding (slow preset + animation tune).
13
+ // Recommended for marketing / portfolio output.
14
+ high: { viewport: { width: 1920, height: 1080 } },
15
+ // 1080p source, archival-quality encoding (very slow, low CRF).
16
+ lossless: { viewport: { width: 1920, height: 1080 } }
17
+ };
18
+ async function record(optionsOrFn, maybeFn) {
19
+ const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
20
+ const {
21
+ output,
22
+ quality,
23
+ url,
24
+ viewport,
25
+ headless,
26
+ launch,
27
+ context,
28
+ cursor,
29
+ ...createHumanOptions
30
+ } = options;
31
+ const resolvedQuality = quality ?? "high";
32
+ const browserPreset = QUALITY_BROWSER_PRESETS[resolvedQuality];
33
+ const resolvedViewport = viewport ?? context?.viewport ?? browserPreset.viewport;
34
+ const wantsCapture = output !== void 0;
35
+ const browser = await playwright.chromium.launch({
36
+ ...launch,
37
+ headless: headless ?? false
38
+ });
39
+ try {
40
+ const browserContext = await browser.newContext({
41
+ ...context,
42
+ viewport: resolvedViewport
43
+ });
44
+ try {
45
+ if (cursor !== false) {
46
+ const cursorOptions = typeof cursor === "object" ? cursor : void 0;
47
+ await playwright.installMouseHelper(browserContext, cursorOptions);
48
+ }
49
+ const page = await browserContext.newPage();
50
+ if (url) await page.goto(url);
51
+ const human = await playwright.createHuman(page, createHumanOptions);
52
+ const recording = await human.record(
53
+ { video: wantsCapture, quality: resolvedQuality },
54
+ () => fn(human, page)
55
+ );
56
+ if (wantsCapture && output) {
57
+ const ext = path.extname(output).toLowerCase();
58
+ if (ext === ".gif") {
59
+ await recording.toGif(output);
60
+ } else {
61
+ await recording.toVideo(output, { quality: resolvedQuality });
62
+ }
63
+ }
64
+ return recording;
65
+ } finally {
66
+ await browserContext.close().catch(() => void 0);
67
+ }
68
+ } finally {
69
+ await browser.close();
70
+ }
71
+ }
72
+
73
+ exports.record = record;
74
+ //# sourceMappingURL=index.cjs.map
75
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/record/index.ts"],"names":["chromium","installMouseHelper","createHuman","extname"],"mappings":"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","file":"index.cjs","sourcesContent":["import { extname } from 'node:path';\nimport {\n type BrowserContextOptions,\n type CreateHumanOptions,\n chromium,\n createHuman,\n type Human,\n type InstallMouseHelperOptions,\n installMouseHelper,\n type LaunchOptions,\n type Page,\n type Recording,\n type RecordingQuality,\n} from '@humanjs/playwright';\n\ninterface QualityBrowserPreset {\n readonly viewport: { readonly width: number; readonly height: number };\n}\n\nconst QUALITY_BROWSER_PRESETS: Record<RecordingQuality, QualityBrowserPreset> = {\n // 720p source — small files, fast encoding, good for iteration.\n fast: { viewport: { width: 1280, height: 720 } },\n // 1080p source — balanced default for tests and dashboards.\n standard: { viewport: { width: 1920, height: 1080 } },\n // 1080p source, visually-lossless encoding (slow preset + animation tune).\n // Recommended for marketing / portfolio output.\n high: { viewport: { width: 1920, height: 1080 } },\n // 1080p source, archival-quality encoding (very slow, low CRF).\n lossless: { viewport: { width: 1920, height: 1080 } },\n};\n\n/**\n * Options for {@link record}. Most fields are passed straight through to\n * Playwright's `chromium.launch()` and `browser.newContext()` so a one-call\n * recording can configure anything a full Playwright setup could.\n */\nexport interface RecordOptions extends CreateHumanOptions {\n /**\n * Output path. Extension determines format — `.mp4` / `.webm` for video,\n * or `.gif` for an animated GIF (palette-optimized, defaults to 15fps).\n * Omit to skip capture entirely; the returned {@link Recording} still has\n * the structured action timeline via `.toTimeline()` / `.timeline`.\n */\n readonly output?: string;\n /**\n * Quality preset. Picks both source viewport and ffmpeg encoding settings.\n * Defaults to `'high'` (visually-lossless 1080p).\n *\n * - `'fast'`: 720p, CRF 23, preset fast\n * - `'standard'`: 1080p, CRF 20, preset fast\n * - `'high'` (default): 1080p, CRF 18, preset slow, tune animation\n * - `'lossless'`: 1080p, CRF 12, preset veryslow, tune animation\n */\n readonly quality?: RecordingQuality;\n /** Optional URL to navigate to before the callback runs. */\n readonly url?: string;\n /** Viewport dimensions. Overrides the quality preset's viewport. */\n readonly viewport?: { readonly width: number; readonly height: number };\n /** Run headless. Defaults to `false` so users can watch the recording happen. */\n readonly headless?: boolean;\n /** Forwarded to `chromium.launch()` (alongside `headless`). */\n readonly launch?: LaunchOptions;\n /** Forwarded to `browser.newContext()` (alongside `viewport`). */\n readonly context?: BrowserContextOptions;\n /**\n * Install the HumanJS visible cursor overlay so recorded videos show\n * mouse motion — Playwright's synthetic mouse doesn't render a cursor\n * by itself, so without this the recording would look like text and\n * UI changing on their own.\n *\n * - `true` (default): install with default styling (HumanJS amber, 22px)\n * - `false`: don't install — the user will install their own, or the\n * recording intentionally has no visible cursor\n * - {@link InstallMouseHelperOptions}: install with custom color / size /\n * click-ripple / halo settings\n *\n * The helper is installed on the context via `addInitScript` + a\n * DOMContentLoaded listener, so it persists across `page.setContent()`\n * and navigation inside the callback.\n */\n readonly cursor?: boolean | InstallMouseHelperOptions;\n}\n\n/** The callback shape both overloads of {@link record} accept. */\nexport type RecordCallback = (human: Human, page: Page) => Promise<void>;\n\n/**\n * One-call session recording. Launches a browser, opens a page, creates a\n * humanized session, runs `fn`, and returns a {@link Recording} you can\n * export to video, JSON timeline, or read in-memory.\n *\n * If `options.output` is set, the output file is written to that path before\n * `record()` resolves — extension dispatches to `toVideo` (`.mp4` / `.webm`)\n * or `toGif` (`.gif`). The returned Recording is still useful for additional\n * exports via `toVideo` / `toGif` / `toTimeline` (all repeatable). If\n * `output` is omitted, frame capture is skipped entirely (no encoding\n * overhead) and only the timeline is captured.\n *\n * @example\n * ```ts\n * // Video + timeline\n * const rec = await record({ output: 'demo.mp4' }, async (human) => {\n * await human.click('#login');\n * });\n * await rec.toTimeline('demo.json');\n * console.log(rec.durationMs, rec.timeline.events.length);\n * ```\n *\n * @example\n * ```ts\n * // Timeline only, no video overhead\n * const rec = await record(async (human) => {\n * await human.click('#login');\n * });\n * await rec.toTimeline('demo.json');\n * ```\n *\n * For multi-page flows or recording a slice of a larger session, use\n * `human.record()` from `@humanjs/playwright` directly.\n */\nexport function record(fn: RecordCallback): Promise<Recording>;\nexport function record(options: RecordOptions, fn: RecordCallback): Promise<Recording>;\nexport async function record(\n optionsOrFn: RecordCallback | RecordOptions,\n maybeFn?: RecordCallback,\n): Promise<Recording> {\n const [options, fn] =\n typeof optionsOrFn === 'function'\n ? [{} as RecordOptions, optionsOrFn]\n : [optionsOrFn, maybeFn as RecordCallback];\n\n const {\n output,\n quality,\n url,\n viewport,\n headless,\n launch,\n context,\n cursor,\n ...createHumanOptions\n } = options;\n\n const resolvedQuality: RecordingQuality = quality ?? 'high';\n const browserPreset = QUALITY_BROWSER_PRESETS[resolvedQuality];\n const resolvedViewport = viewport ?? context?.viewport ?? browserPreset.viewport;\n const wantsCapture = output !== undefined;\n\n const browser = await chromium.launch({\n ...launch,\n headless: headless ?? false,\n });\n try {\n const browserContext = await browser.newContext({\n ...context,\n viewport: resolvedViewport,\n });\n try {\n // Install the visible-cursor overlay before any page is created so\n // the addInitScript runs on the initial page render — and on any\n // page.setContent() inside the callback too.\n if (cursor !== false) {\n const cursorOptions = typeof cursor === 'object' ? cursor : undefined;\n await installMouseHelper(browserContext, cursorOptions);\n }\n\n const page = await browserContext.newPage();\n if (url) await page.goto(url);\n\n const human = await createHuman(page, createHumanOptions);\n\n // Capture runs only when the caller asked for an output file — saves\n // the screenshot + disk-write overhead for timeline-only recordings.\n const recording = await human.record({ video: wantsCapture, quality: resolvedQuality }, () =>\n fn(human, page),\n );\n\n if (wantsCapture && output) {\n // Dispatch by extension: .gif goes through the GIF exporter (which\n // ignores `quality` — GIF doesn't have a CRF concept), everything\n // else flows into the mp4/webm encoder with the chosen preset.\n const ext = extname(output).toLowerCase();\n if (ext === '.gif') {\n await recording.toGif(output);\n } else {\n await recording.toVideo(output, { quality: resolvedQuality });\n }\n }\n\n return recording;\n } finally {\n await browserContext.close().catch(() => undefined);\n }\n } finally {\n await browser.close();\n }\n}\n"]}
@@ -0,0 +1,97 @@
1
+ import { Human, Page, CreateHumanOptions, RecordingQuality, LaunchOptions, BrowserContextOptions, InstallMouseHelperOptions, Recording } from '@humanjs/playwright';
2
+ export { Recording } from '@humanjs/playwright';
3
+
4
+ /**
5
+ * Options for {@link record}. Most fields are passed straight through to
6
+ * Playwright's `chromium.launch()` and `browser.newContext()` so a one-call
7
+ * recording can configure anything a full Playwright setup could.
8
+ */
9
+ interface RecordOptions extends CreateHumanOptions {
10
+ /**
11
+ * Output path. Extension determines format — `.mp4` / `.webm` for video,
12
+ * or `.gif` for an animated GIF (palette-optimized, defaults to 15fps).
13
+ * Omit to skip capture entirely; the returned {@link Recording} still has
14
+ * the structured action timeline via `.toTimeline()` / `.timeline`.
15
+ */
16
+ readonly output?: string;
17
+ /**
18
+ * Quality preset. Picks both source viewport and ffmpeg encoding settings.
19
+ * Defaults to `'high'` (visually-lossless 1080p).
20
+ *
21
+ * - `'fast'`: 720p, CRF 23, preset fast
22
+ * - `'standard'`: 1080p, CRF 20, preset fast
23
+ * - `'high'` (default): 1080p, CRF 18, preset slow, tune animation
24
+ * - `'lossless'`: 1080p, CRF 12, preset veryslow, tune animation
25
+ */
26
+ readonly quality?: RecordingQuality;
27
+ /** Optional URL to navigate to before the callback runs. */
28
+ readonly url?: string;
29
+ /** Viewport dimensions. Overrides the quality preset's viewport. */
30
+ readonly viewport?: {
31
+ readonly width: number;
32
+ readonly height: number;
33
+ };
34
+ /** Run headless. Defaults to `false` so users can watch the recording happen. */
35
+ readonly headless?: boolean;
36
+ /** Forwarded to `chromium.launch()` (alongside `headless`). */
37
+ readonly launch?: LaunchOptions;
38
+ /** Forwarded to `browser.newContext()` (alongside `viewport`). */
39
+ readonly context?: BrowserContextOptions;
40
+ /**
41
+ * Install the HumanJS visible cursor overlay so recorded videos show
42
+ * mouse motion — Playwright's synthetic mouse doesn't render a cursor
43
+ * by itself, so without this the recording would look like text and
44
+ * UI changing on their own.
45
+ *
46
+ * - `true` (default): install with default styling (HumanJS amber, 22px)
47
+ * - `false`: don't install — the user will install their own, or the
48
+ * recording intentionally has no visible cursor
49
+ * - {@link InstallMouseHelperOptions}: install with custom color / size /
50
+ * click-ripple / halo settings
51
+ *
52
+ * The helper is installed on the context via `addInitScript` + a
53
+ * DOMContentLoaded listener, so it persists across `page.setContent()`
54
+ * and navigation inside the callback.
55
+ */
56
+ readonly cursor?: boolean | InstallMouseHelperOptions;
57
+ }
58
+ /** The callback shape both overloads of {@link record} accept. */
59
+ type RecordCallback = (human: Human, page: Page) => Promise<void>;
60
+ /**
61
+ * One-call session recording. Launches a browser, opens a page, creates a
62
+ * humanized session, runs `fn`, and returns a {@link Recording} you can
63
+ * export to video, JSON timeline, or read in-memory.
64
+ *
65
+ * If `options.output` is set, the output file is written to that path before
66
+ * `record()` resolves — extension dispatches to `toVideo` (`.mp4` / `.webm`)
67
+ * or `toGif` (`.gif`). The returned Recording is still useful for additional
68
+ * exports via `toVideo` / `toGif` / `toTimeline` (all repeatable). If
69
+ * `output` is omitted, frame capture is skipped entirely (no encoding
70
+ * overhead) and only the timeline is captured.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // Video + timeline
75
+ * const rec = await record({ output: 'demo.mp4' }, async (human) => {
76
+ * await human.click('#login');
77
+ * });
78
+ * await rec.toTimeline('demo.json');
79
+ * console.log(rec.durationMs, rec.timeline.events.length);
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // Timeline only, no video overhead
85
+ * const rec = await record(async (human) => {
86
+ * await human.click('#login');
87
+ * });
88
+ * await rec.toTimeline('demo.json');
89
+ * ```
90
+ *
91
+ * For multi-page flows or recording a slice of a larger session, use
92
+ * `human.record()` from `@humanjs/playwright` directly.
93
+ */
94
+ declare function record(fn: RecordCallback): Promise<Recording>;
95
+ declare function record(options: RecordOptions, fn: RecordCallback): Promise<Recording>;
96
+
97
+ export { type RecordCallback, type RecordOptions, record };
@@ -0,0 +1,97 @@
1
+ import { Human, Page, CreateHumanOptions, RecordingQuality, LaunchOptions, BrowserContextOptions, InstallMouseHelperOptions, Recording } from '@humanjs/playwright';
2
+ export { Recording } from '@humanjs/playwright';
3
+
4
+ /**
5
+ * Options for {@link record}. Most fields are passed straight through to
6
+ * Playwright's `chromium.launch()` and `browser.newContext()` so a one-call
7
+ * recording can configure anything a full Playwright setup could.
8
+ */
9
+ interface RecordOptions extends CreateHumanOptions {
10
+ /**
11
+ * Output path. Extension determines format — `.mp4` / `.webm` for video,
12
+ * or `.gif` for an animated GIF (palette-optimized, defaults to 15fps).
13
+ * Omit to skip capture entirely; the returned {@link Recording} still has
14
+ * the structured action timeline via `.toTimeline()` / `.timeline`.
15
+ */
16
+ readonly output?: string;
17
+ /**
18
+ * Quality preset. Picks both source viewport and ffmpeg encoding settings.
19
+ * Defaults to `'high'` (visually-lossless 1080p).
20
+ *
21
+ * - `'fast'`: 720p, CRF 23, preset fast
22
+ * - `'standard'`: 1080p, CRF 20, preset fast
23
+ * - `'high'` (default): 1080p, CRF 18, preset slow, tune animation
24
+ * - `'lossless'`: 1080p, CRF 12, preset veryslow, tune animation
25
+ */
26
+ readonly quality?: RecordingQuality;
27
+ /** Optional URL to navigate to before the callback runs. */
28
+ readonly url?: string;
29
+ /** Viewport dimensions. Overrides the quality preset's viewport. */
30
+ readonly viewport?: {
31
+ readonly width: number;
32
+ readonly height: number;
33
+ };
34
+ /** Run headless. Defaults to `false` so users can watch the recording happen. */
35
+ readonly headless?: boolean;
36
+ /** Forwarded to `chromium.launch()` (alongside `headless`). */
37
+ readonly launch?: LaunchOptions;
38
+ /** Forwarded to `browser.newContext()` (alongside `viewport`). */
39
+ readonly context?: BrowserContextOptions;
40
+ /**
41
+ * Install the HumanJS visible cursor overlay so recorded videos show
42
+ * mouse motion — Playwright's synthetic mouse doesn't render a cursor
43
+ * by itself, so without this the recording would look like text and
44
+ * UI changing on their own.
45
+ *
46
+ * - `true` (default): install with default styling (HumanJS amber, 22px)
47
+ * - `false`: don't install — the user will install their own, or the
48
+ * recording intentionally has no visible cursor
49
+ * - {@link InstallMouseHelperOptions}: install with custom color / size /
50
+ * click-ripple / halo settings
51
+ *
52
+ * The helper is installed on the context via `addInitScript` + a
53
+ * DOMContentLoaded listener, so it persists across `page.setContent()`
54
+ * and navigation inside the callback.
55
+ */
56
+ readonly cursor?: boolean | InstallMouseHelperOptions;
57
+ }
58
+ /** The callback shape both overloads of {@link record} accept. */
59
+ type RecordCallback = (human: Human, page: Page) => Promise<void>;
60
+ /**
61
+ * One-call session recording. Launches a browser, opens a page, creates a
62
+ * humanized session, runs `fn`, and returns a {@link Recording} you can
63
+ * export to video, JSON timeline, or read in-memory.
64
+ *
65
+ * If `options.output` is set, the output file is written to that path before
66
+ * `record()` resolves — extension dispatches to `toVideo` (`.mp4` / `.webm`)
67
+ * or `toGif` (`.gif`). The returned Recording is still useful for additional
68
+ * exports via `toVideo` / `toGif` / `toTimeline` (all repeatable). If
69
+ * `output` is omitted, frame capture is skipped entirely (no encoding
70
+ * overhead) and only the timeline is captured.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // Video + timeline
75
+ * const rec = await record({ output: 'demo.mp4' }, async (human) => {
76
+ * await human.click('#login');
77
+ * });
78
+ * await rec.toTimeline('demo.json');
79
+ * console.log(rec.durationMs, rec.timeline.events.length);
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // Timeline only, no video overhead
85
+ * const rec = await record(async (human) => {
86
+ * await human.click('#login');
87
+ * });
88
+ * await rec.toTimeline('demo.json');
89
+ * ```
90
+ *
91
+ * For multi-page flows or recording a slice of a larger session, use
92
+ * `human.record()` from `@humanjs/playwright` directly.
93
+ */
94
+ declare function record(fn: RecordCallback): Promise<Recording>;
95
+ declare function record(options: RecordOptions, fn: RecordCallback): Promise<Recording>;
96
+
97
+ export { type RecordCallback, type RecordOptions, record };
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ import { extname } from 'path';
2
+ import { chromium, installMouseHelper, createHuman } from '@humanjs/playwright';
3
+
4
+ // src/record/index.ts
5
+ var QUALITY_BROWSER_PRESETS = {
6
+ // 720p source — small files, fast encoding, good for iteration.
7
+ fast: { viewport: { width: 1280, height: 720 } },
8
+ // 1080p source — balanced default for tests and dashboards.
9
+ standard: { viewport: { width: 1920, height: 1080 } },
10
+ // 1080p source, visually-lossless encoding (slow preset + animation tune).
11
+ // Recommended for marketing / portfolio output.
12
+ high: { viewport: { width: 1920, height: 1080 } },
13
+ // 1080p source, archival-quality encoding (very slow, low CRF).
14
+ lossless: { viewport: { width: 1920, height: 1080 } }
15
+ };
16
+ async function record(optionsOrFn, maybeFn) {
17
+ const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
18
+ const {
19
+ output,
20
+ quality,
21
+ url,
22
+ viewport,
23
+ headless,
24
+ launch,
25
+ context,
26
+ cursor,
27
+ ...createHumanOptions
28
+ } = options;
29
+ const resolvedQuality = quality ?? "high";
30
+ const browserPreset = QUALITY_BROWSER_PRESETS[resolvedQuality];
31
+ const resolvedViewport = viewport ?? context?.viewport ?? browserPreset.viewport;
32
+ const wantsCapture = output !== void 0;
33
+ const browser = await chromium.launch({
34
+ ...launch,
35
+ headless: headless ?? false
36
+ });
37
+ try {
38
+ const browserContext = await browser.newContext({
39
+ ...context,
40
+ viewport: resolvedViewport
41
+ });
42
+ try {
43
+ if (cursor !== false) {
44
+ const cursorOptions = typeof cursor === "object" ? cursor : void 0;
45
+ await installMouseHelper(browserContext, cursorOptions);
46
+ }
47
+ const page = await browserContext.newPage();
48
+ if (url) await page.goto(url);
49
+ const human = await createHuman(page, createHumanOptions);
50
+ const recording = await human.record(
51
+ { video: wantsCapture, quality: resolvedQuality },
52
+ () => fn(human, page)
53
+ );
54
+ if (wantsCapture && output) {
55
+ const ext = extname(output).toLowerCase();
56
+ if (ext === ".gif") {
57
+ await recording.toGif(output);
58
+ } else {
59
+ await recording.toVideo(output, { quality: resolvedQuality });
60
+ }
61
+ }
62
+ return recording;
63
+ } finally {
64
+ await browserContext.close().catch(() => void 0);
65
+ }
66
+ } finally {
67
+ await browser.close();
68
+ }
69
+ }
70
+
71
+ export { record };
72
+ //# sourceMappingURL=index.js.map
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/record/index.ts"],"names":[],"mappings":";;;;AAmBA,IAAM,uBAAA,GAA0E;AAAA;AAAA,EAE9E,IAAA,EAAM,EAAE,QAAA,EAAU,EAAE,OAAO,IAAA,EAAM,MAAA,EAAQ,KAAI,EAAE;AAAA;AAAA,EAE/C,QAAA,EAAU,EAAE,QAAA,EAAU,EAAE,OAAO,IAAA,EAAM,MAAA,EAAQ,MAAK,EAAE;AAAA;AAAA;AAAA,EAGpD,IAAA,EAAM,EAAE,QAAA,EAAU,EAAE,OAAO,IAAA,EAAM,MAAA,EAAQ,MAAK,EAAE;AAAA;AAAA,EAEhD,QAAA,EAAU,EAAE,QAAA,EAAU,EAAE,OAAO,IAAA,EAAM,MAAA,EAAQ,MAAK;AACpD,CAAA;AA6FA,eAAsB,MAAA,CACpB,aACA,OAAA,EACoB;AACpB,EAAA,MAAM,CAAC,OAAA,EAAS,EAAE,CAAA,GAChB,OAAO,WAAA,KAAgB,UAAA,GACnB,CAAC,EAAC,EAAoB,WAAW,CAAA,GACjC,CAAC,aAAa,OAAyB,CAAA;AAE7C,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAG;AAAA,GACL,GAAI,OAAA;AAEJ,EAAA,MAAM,kBAAoC,OAAA,IAAW,MAAA;AACrD,EAAA,MAAM,aAAA,GAAgB,wBAAwB,eAAe,CAAA;AAC7D,EAAA,MAAM,gBAAA,GAAmB,QAAA,IAAY,OAAA,EAAS,QAAA,IAAY,aAAA,CAAc,QAAA;AACxE,EAAA,MAAM,eAAe,MAAA,KAAW,MAAA;AAEhC,EAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,MAAA,CAAO;AAAA,IACpC,GAAG,MAAA;AAAA,IACH,UAAU,QAAA,IAAY;AAAA,GACvB,CAAA;AACD,EAAA,IAAI;AACF,IAAA,MAAM,cAAA,GAAiB,MAAM,OAAA,CAAQ,UAAA,CAAW;AAAA,MAC9C,GAAG,OAAA;AAAA,MACH,QAAA,EAAU;AAAA,KACX,CAAA;AACD,IAAA,IAAI;AAIF,MAAA,IAAI,WAAW,KAAA,EAAO;AACpB,QAAA,MAAM,aAAA,GAAgB,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,KAAA,CAAA;AAC5D,QAAA,MAAM,kBAAA,CAAmB,gBAAgB,aAAa,CAAA;AAAA,MACxD;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,cAAA,CAAe,OAAA,EAAQ;AAC1C,MAAA,IAAI,GAAA,EAAK,MAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAE5B,MAAA,MAAM,KAAA,GAAQ,MAAM,WAAA,CAAY,IAAA,EAAM,kBAAkB,CAAA;AAIxD,MAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,MAAA;AAAA,QAAO,EAAE,KAAA,EAAO,YAAA,EAAc,OAAA,EAAS,eAAA,EAAgB;AAAA,QAAG,MACtF,EAAA,CAAG,KAAA,EAAO,IAAI;AAAA,OAChB;AAEA,MAAA,IAAI,gBAAgB,MAAA,EAAQ;AAI1B,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,MAAM,CAAA,CAAE,WAAA,EAAY;AACxC,QAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,UAAA,MAAM,SAAA,CAAU,MAAM,MAAM,CAAA;AAAA,QAC9B,CAAA,MAAO;AACL,UAAA,MAAM,UAAU,OAAA,CAAQ,MAAA,EAAQ,EAAE,OAAA,EAAS,iBAAiB,CAAA;AAAA,QAC9D;AAAA,MACF;AAEA,MAAA,OAAO,SAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,cAAA,CAAe,KAAA,EAAM,CAAE,KAAA,CAAM,MAAM,KAAA,CAAS,CAAA;AAAA,IACpD;AAAA,EACF,CAAA,SAAE;AACA,IAAA,MAAM,QAAQ,KAAA,EAAM;AAAA,EACtB;AACF","file":"index.js","sourcesContent":["import { extname } from 'node:path';\nimport {\n type BrowserContextOptions,\n type CreateHumanOptions,\n chromium,\n createHuman,\n type Human,\n type InstallMouseHelperOptions,\n installMouseHelper,\n type LaunchOptions,\n type Page,\n type Recording,\n type RecordingQuality,\n} from '@humanjs/playwright';\n\ninterface QualityBrowserPreset {\n readonly viewport: { readonly width: number; readonly height: number };\n}\n\nconst QUALITY_BROWSER_PRESETS: Record<RecordingQuality, QualityBrowserPreset> = {\n // 720p source — small files, fast encoding, good for iteration.\n fast: { viewport: { width: 1280, height: 720 } },\n // 1080p source — balanced default for tests and dashboards.\n standard: { viewport: { width: 1920, height: 1080 } },\n // 1080p source, visually-lossless encoding (slow preset + animation tune).\n // Recommended for marketing / portfolio output.\n high: { viewport: { width: 1920, height: 1080 } },\n // 1080p source, archival-quality encoding (very slow, low CRF).\n lossless: { viewport: { width: 1920, height: 1080 } },\n};\n\n/**\n * Options for {@link record}. Most fields are passed straight through to\n * Playwright's `chromium.launch()` and `browser.newContext()` so a one-call\n * recording can configure anything a full Playwright setup could.\n */\nexport interface RecordOptions extends CreateHumanOptions {\n /**\n * Output path. Extension determines format — `.mp4` / `.webm` for video,\n * or `.gif` for an animated GIF (palette-optimized, defaults to 15fps).\n * Omit to skip capture entirely; the returned {@link Recording} still has\n * the structured action timeline via `.toTimeline()` / `.timeline`.\n */\n readonly output?: string;\n /**\n * Quality preset. Picks both source viewport and ffmpeg encoding settings.\n * Defaults to `'high'` (visually-lossless 1080p).\n *\n * - `'fast'`: 720p, CRF 23, preset fast\n * - `'standard'`: 1080p, CRF 20, preset fast\n * - `'high'` (default): 1080p, CRF 18, preset slow, tune animation\n * - `'lossless'`: 1080p, CRF 12, preset veryslow, tune animation\n */\n readonly quality?: RecordingQuality;\n /** Optional URL to navigate to before the callback runs. */\n readonly url?: string;\n /** Viewport dimensions. Overrides the quality preset's viewport. */\n readonly viewport?: { readonly width: number; readonly height: number };\n /** Run headless. Defaults to `false` so users can watch the recording happen. */\n readonly headless?: boolean;\n /** Forwarded to `chromium.launch()` (alongside `headless`). */\n readonly launch?: LaunchOptions;\n /** Forwarded to `browser.newContext()` (alongside `viewport`). */\n readonly context?: BrowserContextOptions;\n /**\n * Install the HumanJS visible cursor overlay so recorded videos show\n * mouse motion — Playwright's synthetic mouse doesn't render a cursor\n * by itself, so without this the recording would look like text and\n * UI changing on their own.\n *\n * - `true` (default): install with default styling (HumanJS amber, 22px)\n * - `false`: don't install — the user will install their own, or the\n * recording intentionally has no visible cursor\n * - {@link InstallMouseHelperOptions}: install with custom color / size /\n * click-ripple / halo settings\n *\n * The helper is installed on the context via `addInitScript` + a\n * DOMContentLoaded listener, so it persists across `page.setContent()`\n * and navigation inside the callback.\n */\n readonly cursor?: boolean | InstallMouseHelperOptions;\n}\n\n/** The callback shape both overloads of {@link record} accept. */\nexport type RecordCallback = (human: Human, page: Page) => Promise<void>;\n\n/**\n * One-call session recording. Launches a browser, opens a page, creates a\n * humanized session, runs `fn`, and returns a {@link Recording} you can\n * export to video, JSON timeline, or read in-memory.\n *\n * If `options.output` is set, the output file is written to that path before\n * `record()` resolves — extension dispatches to `toVideo` (`.mp4` / `.webm`)\n * or `toGif` (`.gif`). The returned Recording is still useful for additional\n * exports via `toVideo` / `toGif` / `toTimeline` (all repeatable). If\n * `output` is omitted, frame capture is skipped entirely (no encoding\n * overhead) and only the timeline is captured.\n *\n * @example\n * ```ts\n * // Video + timeline\n * const rec = await record({ output: 'demo.mp4' }, async (human) => {\n * await human.click('#login');\n * });\n * await rec.toTimeline('demo.json');\n * console.log(rec.durationMs, rec.timeline.events.length);\n * ```\n *\n * @example\n * ```ts\n * // Timeline only, no video overhead\n * const rec = await record(async (human) => {\n * await human.click('#login');\n * });\n * await rec.toTimeline('demo.json');\n * ```\n *\n * For multi-page flows or recording a slice of a larger session, use\n * `human.record()` from `@humanjs/playwright` directly.\n */\nexport function record(fn: RecordCallback): Promise<Recording>;\nexport function record(options: RecordOptions, fn: RecordCallback): Promise<Recording>;\nexport async function record(\n optionsOrFn: RecordCallback | RecordOptions,\n maybeFn?: RecordCallback,\n): Promise<Recording> {\n const [options, fn] =\n typeof optionsOrFn === 'function'\n ? [{} as RecordOptions, optionsOrFn]\n : [optionsOrFn, maybeFn as RecordCallback];\n\n const {\n output,\n quality,\n url,\n viewport,\n headless,\n launch,\n context,\n cursor,\n ...createHumanOptions\n } = options;\n\n const resolvedQuality: RecordingQuality = quality ?? 'high';\n const browserPreset = QUALITY_BROWSER_PRESETS[resolvedQuality];\n const resolvedViewport = viewport ?? context?.viewport ?? browserPreset.viewport;\n const wantsCapture = output !== undefined;\n\n const browser = await chromium.launch({\n ...launch,\n headless: headless ?? false,\n });\n try {\n const browserContext = await browser.newContext({\n ...context,\n viewport: resolvedViewport,\n });\n try {\n // Install the visible-cursor overlay before any page is created so\n // the addInitScript runs on the initial page render — and on any\n // page.setContent() inside the callback too.\n if (cursor !== false) {\n const cursorOptions = typeof cursor === 'object' ? cursor : undefined;\n await installMouseHelper(browserContext, cursorOptions);\n }\n\n const page = await browserContext.newPage();\n if (url) await page.goto(url);\n\n const human = await createHuman(page, createHumanOptions);\n\n // Capture runs only when the caller asked for an output file — saves\n // the screenshot + disk-write overhead for timeline-only recordings.\n const recording = await human.record({ video: wantsCapture, quality: resolvedQuality }, () =>\n fn(human, page),\n );\n\n if (wantsCapture && output) {\n // Dispatch by extension: .gif goes through the GIF exporter (which\n // ignores `quality` — GIF doesn't have a CRF concept), everything\n // else flows into the mp4/webm encoder with the chosen preset.\n const ext = extname(output).toLowerCase();\n if (ext === '.gif') {\n await recording.toGif(output);\n } else {\n await recording.toVideo(output, { quality: resolvedQuality });\n }\n }\n\n return recording;\n } finally {\n await browserContext.close().catch(() => undefined);\n }\n } finally {\n await browser.close();\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@humanjs/recorder",
3
+ "version": "0.1.0",
4
+ "description": "One-call session recording for HumanJS — capture a humanized Playwright session as mp4, webm, gif, or a structured JSON timeline.",
5
+ "keywords": [
6
+ "humanjs",
7
+ "playwright",
8
+ "recorder",
9
+ "video",
10
+ "mp4",
11
+ "webm",
12
+ "gif",
13
+ "humanize",
14
+ "browser-automation"
15
+ ],
16
+ "author": "Gonzalo Muñoz <toti@eventurex.com.ar>",
17
+ "license": "MIT",
18
+ "homepage": "https://humanjs.dev",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/totigm/humanjs.git",
22
+ "directory": "packages/recorder"
23
+ },
24
+ "bugs": "https://github.com/totigm/humanjs/issues",
25
+ "type": "module",
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "import": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/index.d.cts",
37
+ "default": "./dist/index.cjs"
38
+ }
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "sideEffects": false,
48
+ "engines": {
49
+ "node": ">=20"
50
+ },
51
+ "dependencies": {
52
+ "@humanjs/playwright": "0.4.0"
53
+ },
54
+ "peerDependencies": {
55
+ "playwright": ">=1.40.0"
56
+ },
57
+ "devDependencies": {
58
+ "playwright": "^1.60.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup",
65
+ "dev": "tsup --watch",
66
+ "test": "vitest run --passWithNoTests",
67
+ "test:integration": "vitest run -c vitest.integration.config.ts",
68
+ "test:watch": "vitest",
69
+ "typecheck": "tsc --noEmit",
70
+ "lint": "biome check .",
71
+ "clean": "rm -rf dist .turbo"
72
+ }
73
+ }