diff --git a/.gitignore b/.gitignore index ff0516a..4b5fbc9 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ test-results # Demo recording output recordings +# @humanjs/generator default exports (throwaway recordings) +humanjs-recording.ts +humanjs-recording.spec.ts + # Environment .env .env.local diff --git a/examples/generate-demo.ts b/examples/generate-demo.ts new file mode 100644 index 0000000..091dbe7 --- /dev/null +++ b/examples/generate-demo.ts @@ -0,0 +1,87 @@ +/** + * HumanJS generator demo — record a clean flow, then print the test the + * generator would produce. + * + * The visual generator (`npx @humanjs/generator `) captures your clicks in + * a real browser and exports a humanized `@playwright/test` spec. This demo + * shows the engine behind that export, headless and offline: it records a short + * session, then runs the public codegen (`generatePlaywrightTest` / + * `generateHumanJS`) on the timeline and prints both a spec and a standalone + * script. + * + * Run with: + * pnpm demo:generate + * PERSONALITY=distracted pnpm demo:generate (changes the generated humanOptions) + */ + +import { + chromium, + createHuman, + generateHumanJS, + generatePlaywrightTest, +} from '@humanjs/playwright'; +import { parsePersonality } from './lib'; + +const DEMO_HTML = /* html */ ` + + + + + Sign in + + +

Sign in

+ + + + + +`; + +function banner(title: string): void { + const rule = '─'.repeat(64); + console.log(`\n${rule}\n ${title}\n${rule}`); +} + +async function main() { + const personality = parsePersonality(process.env.PERSONALITY, 'careful', 'PERSONALITY'); + + // Headless — this demo is about the generated code, not a visible run. + const browser = await chromium.launch({ headless: true }); + try { + const page = await browser.newPage(); + await page.setContent(DEMO_HTML); + const human = await createHuman(page, { personality, seed: 'generate-demo' }); + + // Timeline-only recording (`video: false`) — no frame capture or ffmpeg. + // We only want the action log to feed the codegen, exactly like the + // generator's export does. Selectors are role-first, with a CSS fallback + // for the password field (password inputs expose no `textbox` role). + const rec = await human.record({ name: 'sign in', video: false }, async () => { + await human.type('role=textbox[name="Email"]', 'demo@humanjs.dev'); + await human.type('#password', 'hunter2'); // password input → value is masked + await human.click('role=button[name="Continue"]'); + }); + + // The same public codegen the generator runs on a curated timeline. The + // spec uses the `@humanjs/playwright/test` fixture (instant in CI); the + // standalone script drives a `createHuman` session directly. + banner('generatePlaywrightTest(timeline) → sign-in.spec.ts'); + console.log(generatePlaywrightTest(rec.timeline)); + + banner('generateHumanJS(timeline) → sign-in.ts'); + console.log(generateHumanJS(rec.timeline)); + + console.log( + `\nCaptured ${rec.timeline.events.length} actions — this is what "npx @humanjs/generator " exports, minus the visual editor.`, + ); + console.log('The password value is masked; edit the export to read it from process.env.'); + } finally { + await browser.close(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/examples/humanjs-recording.ts b/examples/humanjs-recording.ts deleted file mode 100644 index 959d2cf..0000000 --- a/examples/humanjs-recording.ts +++ /dev/null @@ -1,34 +0,0 @@ -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(); diff --git a/examples/package.json b/examples/package.json index 758734d..506e71f 100644 --- a/examples/package.json +++ b/examples/package.json @@ -15,7 +15,7 @@ "record-manual": "tsx record-manual-demo.ts", "scroll": "tsx scroll-demo.ts", "type": "tsx type-demo.ts", - "recorder": "tsx humanjs-recording.ts" + "generate": "tsx generate-demo.ts" }, "dependencies": { "@humanjs/playwright": "workspace:*", diff --git a/humanjs-recording.ts b/humanjs-recording.ts deleted file mode 100644 index 0477494..0000000 --- a/humanjs-recording.ts +++ /dev/null @@ -1,26 +0,0 @@ -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(); diff --git a/package.json b/package.json index b87b881..e0de4a5 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "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:recorder": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples recorder" + "demo:generate": "turbo run build --filter=@humanjs/playwright... && pnpm --filter @humanjs/examples generate" }, "commitlint": { "extends": [