Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ae1ed31
feat(generator): scaffold @humanjs/generator + export codegen from pl…
totigm Jun 2, 2026
de001c5
feat(generator): launch Chromium + local dashboard server (HTTP/WS)
totigm Jun 2, 2026
8856e93
feat(generator): capture interactions with ranked selector inference
totigm Jun 2, 2026
f76858a
fix(generator): capture typing via input event, add drag, de-noise pr…
totigm Jun 2, 2026
a46b3d6
docs(generator): log selectText as a post-v0.1 combined unit
totigm Jun 2, 2026
4b27ab2
feat(playwright): render explicit assert events in the test codegen
totigm Jun 2, 2026
fc1a77d
feat(generator): export layer with live preview, assertions, secrets
totigm Jun 2, 2026
e245c75
feat(generator): editable timeline store + editor command protocol
totigm Jun 2, 2026
24632a7
feat(generator): editor ui with drag, selector picker, assertions, se…
totigm Jun 2, 2026
b6fdb90
feat(generator): themed select, syntax-highlighted preview, smooth drag
totigm Jun 2, 2026
bcb749d
fix(generator): select sizing + flip-up, animated code preview
totigm Jun 2, 2026
e9cf593
fix(generator): drop phantom click after drag, autoscroll, themed scr…
totigm Jun 3, 2026
382d54d
fix(generator): keep wide selector lines from blowing out the layout
totigm Jun 3, 2026
11fe1ab
fix(generator): reach true bottom on autoscroll, soft-wrap the preview
totigm Jun 3, 2026
0afaa31
docs(generator): launch-ready README + DESIGN cross-link, 0.1.0 chang…
totigm Jun 3, 2026
ad961f5
style(generator): match the dashboard to humanjs.dev (warm orange brand)
totigm Jun 3, 2026
7a4634f
feat(playwright): install the cursor overlay by default in createHuman
totigm Jun 4, 2026
363fada
fix(generator): don't record a goto for navigations caused by a recor…
totigm Jun 15, 2026
fd80fd7
fix(generator): drop the SPA scroll-to-top fired on navigation
totigm Jun 15, 2026
2717e46
fix(generator): only record user-driven scrolls, not programmatic ones
totigm Jun 15, 2026
2b07b19
fix(generator): capture scrollbar-drag too — suppress only programmat…
totigm Jun 15, 2026
a66bbde
fix(generator): don't mis-record a scrollbar drag as a content drag
totigm Jun 15, 2026
cd1d28e
docs(generator): drop the screenshot placeholder from the README
totigm Jun 15, 2026
b9ce62f
Merge remote-tracking branch 'origin/main' into feat/generator
totigm Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/codegen-assert-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@humanjs/playwright": minor
---

`generatePlaywrightTest` now renders explicit `assert` timeline events into `@playwright/test` assertions: `{ kind: 'visible' }` → `expect(locator).toBeVisible()`, `{ kind: 'text', value }` → `toHaveText(value)`, `{ kind: 'url', value }` → `expect(page).toHaveURL(value)`. They interleave with actions in recorded order and pull `page` + `expect` into the test automatically. The standalone `generateHumanJS` script export ignores them (a replay script has no `expect`). This lets tooling that builds a `Timeline` (notably `@humanjs/generator`) emit intentional assertions alongside the actions, beyond the ones already derived from reads and captured inputs.
5 changes: 5 additions & 0 deletions .changeset/codegen-public-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@humanjs/playwright": minor
---

Export `generateHumanJS` and `generatePlaywrightTest` from the package root. They turn a `Timeline` (the structured action log from `human.record()` / `rec.toTimeline()`) directly into a runnable HumanJS script or a `@humanjs/playwright/test` spec — the same code the `Recording` exporters emit, now callable on any `Timeline` you construct or load. This is the codegen entry point `@humanjs/generator` builds on, and it's useful standalone for tooling that produces timelines without running a live recording.
9 changes: 9 additions & 0 deletions .changeset/cursor-on-by-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@humanjs/playwright": minor
---

`createHuman` now installs the visual cursor overlay (`installMouseHelper`) **by default**, so humanized motion is visible in headed runs and recordings without a manual call — exported scripts from `@humanjs/generator` / `Recording.toHumanJS()` now show the cursor when you run them.

Opt out with `cursor: false` — do this for `speed: 'instant'` / CI, where there's no motion to show and the injected cursor would otherwise land in test DOM and screenshots. Pass an options object (`cursor: { color, size, … }`) to style it. The `@humanjs/playwright/test` fixture opts out automatically in CI (it already runs `instant` there) and shows the cursor on local runs.

The install is scoped to the session's page, idempotent (a manual `installMouseHelper` or the MCP server's install on top is a no-op), and skipped on page objects that don't support it (so unit-test mocks are unaffected).
16 changes: 16 additions & 0 deletions .changeset/generator-initial-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@humanjs/generator": minor
---

Initial release of `@humanjs/generator` — a visual recorder that turns a real browsing session into a clean, humanized Playwright test.

`npx @humanjs/generator <url>` opens a real Chromium window and a local (loopback-only) dashboard. As you click, type, scroll, drag, and navigate, each action streams into the dashboard as a step captured with a role-first selector (ARIA role + accessible name → label → text → test id → `#id` → CSS → XPath). The dashboard is a full editor:

- **drag to reorder**, delete, relabel (label → comment), and edit captured values
- a per-step **selector picker** over the ranked candidates
- **point-and-add assertions** (`toBeVisible` / `toHaveText` / `toHaveURL`)
- a **secret toggle** that exports a value as `process.env.X` instead of a literal (passwords are always masked)
- a **personality switcher** (`careful` / `fast` / `distracted` / `precise`)
- a live, syntax-highlighted code preview that updates on every edit

Export a `@humanjs/playwright/test` spec (`.spec.ts` / `.test.ts`) or a standalone HumanJS script (`.ts`). The curated timeline runs through `@humanjs/playwright`'s codegen, so generated specs stay in lockstep with the library. Requires Node ≥ 20 and the Playwright Chromium browser (`npx playwright install chromium`).
7 changes: 5 additions & 2 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ rec.toTimeline('checkout.json'); // structured analysis data

Three output formats because three audiences want different things: marketers want video, engineers want test code, agent builders want structured timelines for analysis.

### Generator (`@humanjs/generator`, v2)
### Generator (`@humanjs/generator`)

> **Shipping as of `0.1.0`.** See the package [README](../packages/generator/README.md) for usage and the [ROADMAP](../packages/generator/ROADMAP.md) for what's next. The design below is the original plan, now realized.

A standalone CLI — not an iframe-based recorder. (Iframe-based recorders are blocked by `X-Frame-Options` on most real sites; the pattern doesn't scale.)

Expand All @@ -145,8 +147,9 @@ Differentiation from Playwright Codegen:
- Edit-before-export UI (Playwright Codegen has none)
- Better selector inference: roles + accessible names + test IDs before CSS / XPath
- Annotations preserved in generated code
- Point-and-add assertions (`toBeVisible` / `toHaveText` / `toHaveURL`) and secret fields exported as `process.env.*`

The generator is explicitly v2. Shipping it before v1 core is stable would split focus and slow both.
It was deliberately held until v1 core was stable so the two didn't split focus. The editor (Vite + React) is bundled into the package and served by the CLI on a loopback port; the curated timeline runs through `@humanjs/playwright`'s codegen, so generated specs stay in lockstep with the library.

## Personalities — design rationale

Expand Down
34 changes: 34 additions & 0 deletions examples/humanjs-recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { chromium, createHuman } from '@humanjs/playwright';

async function main() {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage({
viewport: { width: 1920, height: 1080 },
});
const human = await createHuman(page, {
personality: 'careful',
speed: 'human',
});

await human.goto('https://www.eventurex.com.ar/');
await human.sleep(1000);
await human.click('role=link[name="Eventos"]');
// await human.goto('https://www.eventurex.com.ar/eventos');
console.log('hola');
await human.click('div:nth-of-type(4) > div > div:nth-of-type(1) > a');

console.log('chau');

// await human.goto('https://www.eventurex.com.ar/eventos/medico-a-palos-1775827780992');
await human.click('role=link[name="Comprar entradas"]');
// await human.goto('https://www.eventurex.com.ar/eventos/medico-a-palos-1775827780992/comprar');
await human.sleep(1000);
await human.click('div:nth-of-type(3) > button');
await human.click('div:nth-of-type(3) > button');
await human.scroll({ to: 239 });
await human.click('role=button[name="Continuar"]');

await browser.close();
}

main();
3 changes: 2 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"record": "tsx record-demo.ts",
"record-manual": "tsx record-manual-demo.ts",
"scroll": "tsx scroll-demo.ts",
"type": "tsx type-demo.ts"
"type": "tsx type-demo.ts",
"recorder": "tsx humanjs-recording.ts"
},
"dependencies": {
"@humanjs/playwright": "workspace:*",
Expand Down
26 changes: 26 additions & 0 deletions humanjs-recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { chromium, createHuman } from '@humanjs/playwright';

async function main() {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const human = await createHuman(page, {
personality: 'careful',
speed: 'human',
});

await human.goto('https://www.eventurex.com.ar/');
await human.click('role=link[name="Eventos"]');
await human.goto('https://www.eventurex.com.ar/eventos');
await human.click('div:nth-of-type(4) > div > div:nth-of-type(1) > a');
await human.goto('https://www.eventurex.com.ar/eventos/medico-a-palos-1775827780992');
await human.click('role=link[name="Comprar entradas"]');
await human.goto('https://www.eventurex.com.ar/eventos/medico-a-palos-1775827780992/comprar');
await human.click('div:nth-of-type(3) > button');
await human.click('div:nth-of-type(3) > button');
await human.scroll({ to: 239 });
await human.click('role=button[name="Continuar"]');

await browser.close();
}

main();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"demo:record": "turbo run build --filter=@humanjs/recorder... && pnpm --filter @humanjs/examples record",
"demo:record-manual": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples record-manual",
"demo:scroll": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples scroll",
"demo:type": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples type"
"demo:type": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples type",
"demo:recorder": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples recorder"
},
"commitlint": {
"extends": [
Expand Down
79 changes: 79 additions & 0 deletions packages/generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# @humanjs/generator

<p>
<a href="https://www.npmjs.com/package/@humanjs/generator"><img alt="npm" src="https://img.shields.io/npm/v/@humanjs/generator"></a>
<a href="https://www.npmjs.com/package/@humanjs/generator"><img alt="downloads" src="https://img.shields.io/npm/dt/@humanjs/generator"></a>
<a href="https://github.com/totigm/humanjs"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-totigm%2Fhumanjs-181717?logo=github"></a>
<a href="https://github.com/totigm/humanjs/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/totigm/humanjs/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/totigm/humanjs/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@humanjs/generator"></a>
<a href="https://humanjs.dev"><img alt="docs" src="https://img.shields.io/badge/docs-humanjs.dev-emerald"></a>
</p>

Visual recorder for [HumanJS](https://humanjs.dev) — record your clicks in a real browser and export a clean, **humanized** Playwright test.

```bash
npx @humanjs/generator https://your-app.com
```

It launches a real Chromium window and a local dashboard. You click through your flow; the dashboard shows a live, editable timeline next to a syntax-highlighted code preview. Curate the steps, pick a personality, and export a ready-to-commit `.spec.ts` that drives the page through HumanJS — so the generated test runs with humanized motion, not robotic jumps.

> **Audience:** AI agent builders, QA engineers, and demo/tutorial creators. HumanJS is **not** a scraping, captcha-bypass, or "undetectable automation" tool — see the [non-goals](https://humanjs.dev).

## How it works

1. `npx @humanjs/generator <url>` opens a real Chromium window at your URL and a local dashboard (loopback only) in your browser.
2. Interact with the site in the Chromium window — clicks, typing, scrolling, drag, navigation. Each action streams into the dashboard as a step, captured with a robust, role-first selector.
3. Curate the timeline in the editor (below), pick a personality, and watch the generated test update live.
4. Hit **Export** to write a `.spec.ts` (a `@humanjs/playwright/test` spec) or a standalone `.ts` script to your working directory.

## The editor

- **Reorder** by dragging a step's handle — the rest slide out of the way.
- **Delete** junk steps, **relabel** any step (the label becomes a comment), and **edit captured values**.
- **Pick a selector** per step from the ranked candidates (role + accessible name → label → text → test id → `#id` → CSS → XPath).
- **Add assertions** by pointing at a step: `toBeVisible`, `toHaveText`, or `toHaveURL`.
- **Mark a field secret** so its value exports as `process.env.X` instead of a literal.
- **Switch personality** (`careful` / `fast` / `distracted` / `precise`) — the same recording, re-shaped, no re-recording.

## Output

A fixture-based spec that runs humanized locally and instant in CI:

```ts
import { test } from '@humanjs/playwright/test';

test.use({ humanOptions: { personality: 'careful', speed: process.env.CI ? 'instant' : 'human' } });

test('recorded session', async ({ human }) => {
await human.goto('https://your-app.com/');
await human.click('role=link[name="Sign in"]');
await human.type('role=textbox[name="Email"]', 'user@example.com');
await human.click('role=button[name="Continue"]');
});
```

Export as `.spec.ts` / `.test.ts` for the spec above, or `.ts` for a standalone HumanJS script.

## How it differs from Playwright Codegen

- **Humanized output** — generated tests use HumanJS primitives (curved clicks, typing rhythm, reading dwell), not raw Playwright calls.
- **Personality switcher** — the same recording exports as `careful`, `fast`, `distracted`, or `precise` without re-recording.
- **Edit before export** — delete junk, reorder, relabel, fix captured text, pick selectors, add assertions (Codegen has no editor).
- **Selector quality** — prefers ARIA role + accessible name, then label / text / test id, before falling back to CSS or XPath.

## Requirements

- Node.js ≥ 20.
- The Playwright Chromium browser. If you don't have it yet: `npx playwright install chromium`.

## Honest limits

- Records in a real browser (no iframe), so it works on sites that block framing — but it does not, and will not, defeat bot detection, fingerprinting, or captchas.
- The local dashboard binds to `127.0.0.1` only.
- Captured passwords are always masked; use the secret-field toggle to wire other sensitive values to environment variables.

See [`ROADMAP.md`](./ROADMAP.md) for what's next (in-app replay, video/GIF export, mid-timeline re-record).

## License

MIT
35 changes: 35 additions & 0 deletions packages/generator/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# @humanjs/generator — roadmap

Tracks what ships in the first release and what's deliberately deferred. Vision lives in [`docs/DESIGN.md`](../../docs/DESIGN.md#generator-humanjsgenerator-v2); this file is the execution plan.

## v0.1 (in progress)

The first published release. Milestones, each independently testable:

1. **Scaffold** — package shell (mirrors `@humanjs/mcp`) + additive `generateHumanJS` / `generatePlaywrightTest` export from `@humanjs/playwright`. _(done)_
2. **CLI + browser launch + local server** — `npx @humanjs/generator <url>` opens a real Chromium window and a localhost-only dashboard (HTTP + WebSocket) that reports "connected". _(done)_
3. **Capture + ranked selector inference** — an injected content script records click / type / scroll / navigation as `TimelineEvent`s and streams them live. `selectors.ts` ranks candidates (ARIA role + accessible name → label → text → test id → CSS → XPath) and is unit-tested against a fixture DOM. _(done)_
4. **Export layer** — curated `Timeline` → `.spec.ts` / `.ts` via the shared codegen, including **assertion rendering** and **secret → `process.env.*` substitution**. e2e: record a fixture flow, export, run the generated spec. _(done)_ The placeholder dashboard now shows a live code preview and Export buttons; the codegen learned to render explicit `assert` events (`@humanjs/playwright`).
5. **Full editor UI** (Vite + React SPA, bundled into the package) — _(done)_
- delete / reorder (drag) / relabel steps, edit captured text
- per-step **selector picker** (choose among ranked candidates)
- **point-and-add assertions** (`toBeVisible` / `toHaveText` / `toHaveURL`)
- **secret-field toggle** (emit `process.env.X`)
- **personality switcher** (`careful` / `fast` / `distracted` / `precise`)
- **live code preview** — regenerated on every timeline mutation
6. **Polish + README + DESIGN cross-link**, then a `minor` changeset to publish `0.1.0`. _(done)_

## v0.2+ (deferred follow-ups)

Each adds a real subsystem, so they stay out of the first cut:

- **In-app replay / verify** — a "run this" button that executes the exported spec (spawns `playwright test`) and shows pass / fail in the dashboard, closing the record → test → green loop.
- **Video + GIF export** — capture frames alongside the DOM events so the same session also exports an `.mp4` / `.gif` via the existing `toVideo` / `toGif` path. Reuses recorder infra; great for the demo / tutorial-creator audience. Needs the frame-capture loop running next to DOM capture.
- **Insert / re-record mid-timeline** — append or splice new actions into an existing recording without starting over.
- **`human.selectText(target)` primitive + capture** — a combined unit done together: add the element-scoped text-selection primitive to `@humanjs/core` + `@humanjs/playwright` (wrapping Playwright's `locator.selectText()`) + the MCP tool + codegen support + a changeset, **and** the small recorder.ts wiring to capture it. Scope: **element-scoped only** (anchor + focus inside one element covering its text). Free-form cross-element range selection stays uncaptured by design — it's offset/coordinate-based and would generate a brittle, reflow-fragile step, against HumanJS's selectors-over-coordinates philosophy. Until then, the recorder's text-selection guard correctly keeps highlighting from producing a bogus `drag`.

## Deliberately out of scope

- **LLM-named selectors / AI step descriptions** — the role + accessible-name inference is sufficient and adds no API-key dependency or per-run cost.
- **MCP tool surface** — the generator is a CLI dev-tool, not a runtime primitive an agent composes; per the repo's "consider the MCP surface" rule, it intentionally ships no MCP tools.
- Anything framed around stealth, fingerprint masking, proxy rotation, captcha bypass, or "undetectable" automation — these are project-wide [non-goals](../../CLAUDE.md).
12 changes: 12 additions & 0 deletions packages/generator/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HumanJS Generator</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading