@humanjs/playwright 0.5.0 → 0.6.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @humanjs/playwright
2
2
 
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/@humanjs/playwright"><img alt="npm" src="https://img.shields.io/npm/v/@humanjs/playwright"></a>
5
+ <a href="https://www.npmjs.com/package/@humanjs/playwright"><img alt="downloads" src="https://img.shields.io/npm/dt/@humanjs/playwright"></a>
6
+ <a href="https://github.com/totigm/humanjs"><img alt="GitHub" src="https://img.shields.io/badge/GitHub-totigm%2Fhumanjs-181717?logo=github"></a>
7
+ <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>
8
+ <a href="https://github.com/totigm/humanjs/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@humanjs/playwright"></a>
9
+ <a href="https://humanjs.dev"><img alt="docs" src="https://img.shields.io/badge/docs-humanjs.dev-emerald"></a>
10
+ </p>
11
+
3
12
  Humanize Playwright sessions for AI agents, QA tests, and demos. Drop-in adapter for an existing Playwright `Page`.
4
13
 
5
14
  ## Install
@@ -364,6 +373,39 @@ await rec.toTimeline('session.json'); // works
364
373
 
365
374
  Every recording is a regular plugin action — `beforeAction` and `afterAction` observe `{ type: 'record' }` exactly like `'click'` or `'scroll'`.
366
375
 
376
+ ## Using your own browser or a persistent profile
377
+
378
+ `createHuman(page)` wraps **any** Playwright `Page` — so reusing a saved login, your installed Chrome, or an already-running browser is just a matter of how you create that page. HumanJS adds nothing special here; these are standard Playwright entry points, collected so you don't have to hunt for them.
379
+
380
+ **Persistent profile** — keep cookies, local storage, and logins across runs. The first run signs in; later runs are already authenticated:
381
+
382
+ ```ts
383
+ import { chromium, createHuman } from '@humanjs/playwright';
384
+
385
+ const context = await chromium.launchPersistentContext('./.humanjs-profile', {
386
+ headless: false,
387
+ channel: 'chrome', // optional: use installed Google Chrome instead of bundled Chromium
388
+ });
389
+ const page = context.pages()[0] ?? (await context.newPage());
390
+
391
+ const human = await createHuman(page, { personality: 'careful' });
392
+ // …drive the page; state persists in ./.humanjs-profile for next time
393
+ ```
394
+
395
+ **Attach to an already-running browser** — drive a Chrome you started yourself, with all its existing tabs, extensions, and sessions. Launch Chrome with a debugging port first (`chrome --remote-debugging-port=9222`), then:
396
+
397
+ ```ts
398
+ import { chromium, createHuman } from '@humanjs/playwright';
399
+
400
+ const browser = await chromium.connectOverCDP('http://localhost:9222');
401
+ const context = browser.contexts()[0];
402
+ const page = context.pages()[0] ?? (await context.newPage());
403
+
404
+ const human = await createHuman(page, { personality: 'careful' });
405
+ ```
406
+
407
+ > **Heads up:** a persistent profile or a connected real browser carries whatever you're signed into. Driving it means the automation can act with those sessions' privileges — keep that in mind for anything sensitive, and be wary of pages that try to manipulate an agent into actions while logged in.
408
+
367
409
  ## License
368
410
 
369
411
  MIT
package/dist/index.cjs CHANGED
@@ -325,15 +325,22 @@ function clamp(value, min, max) {
325
325
  // src/mouse/index.ts
326
326
  async function executeClick(target, ctx, options = {}) {
327
327
  const button = options.button ?? "left";
328
- const locator = typeof target === "string" ? ctx.page.locator(target) : target;
329
328
  if (ctx.speed === "instant") {
330
- const box = await locator.boundingBox();
329
+ if (isPoint(target)) {
330
+ await ctx.page.mouse.click(target.x, target.y, { button });
331
+ ctx.setMousePosition(target);
332
+ return { target };
333
+ }
334
+ const locator = typeof target === "string" ? ctx.page.locator(target) : target;
335
+ const box2 = await locator.boundingBox();
331
336
  await locator.click({ button });
332
- const center = box ? { x: box.x + box.width / 2, y: box.y + box.height / 2 } : ctx.getMousePosition();
337
+ const center = box2 ? { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 } : ctx.getMousePosition();
333
338
  ctx.setMousePosition(center);
334
339
  return { target: center };
335
340
  }
336
- const targetPoint = await moveToTarget(target, ctx, "click");
341
+ const { point: targetPoint, box } = await resolveTargetPointAndBox(target, ctx, "click");
342
+ await maybeMisclickBeat(ctx, box, targetPoint);
343
+ await walkBezierTo(targetPoint, ctx);
337
344
  const preClickMs = computeDwellTime(
338
345
  ctx.personality.dwell.preClickMs,
339
346
  ctx.personality.dwell.preClickJitter,
@@ -362,7 +369,7 @@ async function executeHover(target, ctx) {
362
369
  ctx.setMousePosition(center);
363
370
  return { target: center };
364
371
  }
365
- const targetPoint = await moveToTarget(target, ctx, "hover");
372
+ const targetPoint = await moveToTarget(target, ctx);
366
373
  const dwellMs = computeDwellTime(
367
374
  ctx.personality.dwell.preClickMs,
368
375
  ctx.personality.dwell.preClickJitter,
@@ -445,10 +452,9 @@ async function executeMove(target, ctx) {
445
452
  ctx.setMousePosition(point);
446
453
  return { target: point };
447
454
  }
448
- async function moveToTarget(target, ctx, action) {
449
- const box = await readBoxWithAutoScroll(target, ctx, action);
455
+ async function moveToTarget(target, ctx) {
456
+ const box = await readBoxWithAutoScroll(target, ctx, "hover");
450
457
  const targetPoint = pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
451
- if (action === "click") await maybeMisclickBeat(ctx, box, targetPoint);
452
458
  await walkBezierTo(targetPoint, ctx);
453
459
  return targetPoint;
454
460
  }
@@ -1433,6 +1439,52 @@ async function createHuman(page, options = {}) {
1433
1439
  speed,
1434
1440
  events
1435
1441
  });
1442
+ },
1443
+ // ────────────────────────────────────────────────────────────────────
1444
+ // Thin re-exports of common Playwright `Page` methods. See the `Human`
1445
+ // interface for the rationale; implementations forward unchanged.
1446
+ // ────────────────────────────────────────────────────────────────────
1447
+ screenshot(opts) {
1448
+ return page.screenshot(opts);
1449
+ },
1450
+ pageText() {
1451
+ return page.innerText("body");
1452
+ },
1453
+ content() {
1454
+ return page.content();
1455
+ },
1456
+ url() {
1457
+ return page.url();
1458
+ },
1459
+ title() {
1460
+ return page.title();
1461
+ },
1462
+ async reload(opts) {
1463
+ await performAction({ type: "reload", params: {} }, async () => {
1464
+ await page.reload(opts);
1465
+ });
1466
+ },
1467
+ async goBack(opts) {
1468
+ await performAction({ type: "goBack", params: {} }, async () => {
1469
+ await page.goBack(opts);
1470
+ });
1471
+ },
1472
+ async goForward(opts) {
1473
+ await performAction({ type: "goForward", params: {} }, async () => {
1474
+ await page.goForward(opts);
1475
+ });
1476
+ },
1477
+ waitForLoadState(state, opts) {
1478
+ return page.waitForLoadState(state, opts);
1479
+ },
1480
+ waitForURL(url, opts) {
1481
+ return page.waitForURL(url, opts);
1482
+ },
1483
+ setViewportSize(size) {
1484
+ return page.setViewportSize(size);
1485
+ },
1486
+ pdf(opts) {
1487
+ return page.pdf(opts);
1436
1488
  }
1437
1489
  };
1438
1490
  }