@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/dist/index.d.cts CHANGED
@@ -458,21 +458,29 @@ interface Human {
458
458
  /**
459
459
  * Move the mouse along a humanized Bezier path to `target` and click.
460
460
  *
461
- * `target` accepts either a Playwright-compatible selector string (e.g.
462
- * `'button:has-text("Buy now")'`) or a built `Locator`. The click point
463
- * inside the element is Gaussian-distributed around the center.
461
+ * `target` accepts a Playwright-compatible selector string (e.g.
462
+ * `'button:has-text("Buy now")'`), a built `Locator`, or a raw `Point`.
463
+ * For element targets the click point is Gaussian-distributed around the
464
+ * center; for a raw `Point` the exact coordinates are clicked. The
465
+ * `Point` form is the fallback for things with no clean selector —
466
+ * icon-only buttons, canvas, SVG — where you can see the pixel position
467
+ * but can't address the element.
464
468
  *
465
- * In `speed: 'instant'`, all humanization is skipped and Playwright's
466
- * native `locator.click()` is used directly.
469
+ * In `speed: 'instant'`, all humanization is skipped: element targets use
470
+ * Playwright's native `locator.click()`; a `Point` dispatches one
471
+ * `mouse.click()` at the coordinates.
467
472
  */
468
- click(target: Locator | string): Promise<void>;
473
+ click(target: MouseTarget): Promise<void>;
469
474
  /**
470
475
  * Right-click `target` — opens a native context menu. Same Bezier-path
471
476
  * motion and hover dwell as `click()`; only the dispatched button differs.
477
+ * Accepts the same selector / `Locator` / `Point` targets as `click()`.
472
478
  *
473
- * In `speed: 'instant'`, falls back to `locator.click({ button: 'right' })`.
479
+ * In `speed: 'instant'`, element targets fall back to
480
+ * `locator.click({ button: 'right' })`; a `Point` dispatches one
481
+ * `mouse.click({ button: 'right' })` at the coordinates.
474
482
  */
475
- rightClick(target: Locator | string): Promise<void>;
483
+ rightClick(target: MouseTarget): Promise<void>;
476
484
  /**
477
485
  * Move the cursor to `target` along a humanized Bezier path and settle
478
486
  * on it — no click is dispatched. Useful for hover-triggered UI
@@ -670,6 +678,102 @@ interface Human {
670
678
  */
671
679
  record(fn: () => Promise<void>): Promise<Recording>;
672
680
  record(options: HumanRecordOptions, fn: () => Promise<void>): Promise<Recording>;
681
+ /**
682
+ * Take a screenshot of the page. Forwards to `page.screenshot(options)`
683
+ * unchanged — see Playwright docs for the full options shape.
684
+ *
685
+ * Not a humanized action: no plugin events fire. Pure ergonomic
686
+ * re-export so `human.*` stays a single surface.
687
+ */
688
+ screenshot(options?: Parameters<Page['screenshot']>[0]): Promise<Buffer>;
689
+ /**
690
+ * Visible text content of the page (`document.body.innerText`). Forwards
691
+ * to `page.innerText('body')`. Useful for AI agents that need to
692
+ * understand what's on screen without parsing HTML.
693
+ *
694
+ * For a region instead of the whole page, use `page.innerText(selector)`.
695
+ *
696
+ * Not a humanized action: no plugin events fire.
697
+ */
698
+ pageText(): Promise<string>;
699
+ /**
700
+ * Full HTML of the page. Forwards to `page.content()`. Often large
701
+ * (50–500 KB on typical sites) — prefer {@link Human.pageText} when
702
+ * passing to an LLM unless structure matters.
703
+ *
704
+ * Not a humanized action: no plugin events fire.
705
+ */
706
+ content(): Promise<string>;
707
+ /**
708
+ * Current URL of the page. Forwards to `page.url()`. Synchronous return
709
+ * because Playwright's underlying API is sync.
710
+ *
711
+ * Not a humanized action: no plugin events fire.
712
+ */
713
+ url(): string;
714
+ /**
715
+ * Current document title. Forwards to `page.title()`.
716
+ *
717
+ * Not a humanized action: no plugin events fire.
718
+ */
719
+ title(): Promise<string>;
720
+ /**
721
+ * Reload the current page. Forwards to `page.reload(options)`. Real-user
722
+ * analog of hitting the refresh button — the click motion is *not*
723
+ * humanized (we treat reload as navigation, not a cursor action).
724
+ *
725
+ * Plugins observe a `'reload'` action.
726
+ */
727
+ reload(options?: Parameters<Page['reload']>[0]): Promise<void>;
728
+ /**
729
+ * Navigate back in the browser history. Forwards to `page.goBack(options)`.
730
+ *
731
+ * Plugins observe a `'goBack'` action.
732
+ */
733
+ goBack(options?: Parameters<Page['goBack']>[0]): Promise<void>;
734
+ /**
735
+ * Navigate forward in the browser history. Forwards to
736
+ * `page.goForward(options)`.
737
+ *
738
+ * Plugins observe a `'goForward'` action.
739
+ */
740
+ goForward(options?: Parameters<Page['goForward']>[0]): Promise<void>;
741
+ /**
742
+ * Wait until the page reaches the specified load state. Forwards to
743
+ * `page.waitForLoadState(state, options)`. Common after a humanized
744
+ * click that triggers navigation — gives the page time to settle before
745
+ * the next action.
746
+ *
747
+ * Not a humanized action: no plugin events fire.
748
+ */
749
+ waitForLoadState(state?: Parameters<Page['waitForLoadState']>[0], options?: Parameters<Page['waitForLoadState']>[1]): Promise<void>;
750
+ /**
751
+ * Wait until the page's URL matches `url` (string, RegExp, or predicate).
752
+ * Forwards to `page.waitForURL(url, options)`. Common post-action wait
753
+ * (e.g. after `human.click('#login')`, wait for `/dashboard`).
754
+ *
755
+ * Not a humanized action: no plugin events fire.
756
+ */
757
+ waitForURL(url: Parameters<Page['waitForURL']>[0], options?: Parameters<Page['waitForURL']>[1]): Promise<void>;
758
+ /**
759
+ * Resize the viewport. Forwards to `page.setViewportSize(size)`. Useful
760
+ * for testing responsive layouts or switching between breakpoints
761
+ * mid-session.
762
+ *
763
+ * Not a humanized action: no plugin events fire.
764
+ */
765
+ setViewportSize(size: {
766
+ width: number;
767
+ height: number;
768
+ }): Promise<void>;
769
+ /**
770
+ * Render the page as a PDF. Forwards to `page.pdf(options)`. Chromium
771
+ * only; works most reliably in headless mode (in headed mode, Playwright
772
+ * silently switches to a print-style render).
773
+ *
774
+ * Not a humanized action: no plugin events fire.
775
+ */
776
+ pdf(options?: Parameters<Page['pdf']>[0]): Promise<Buffer>;
673
777
  }
674
778
  /** Options for {@link Human.record}. */
675
779
  interface HumanRecordOptions {
package/dist/index.d.ts CHANGED
@@ -458,21 +458,29 @@ interface Human {
458
458
  /**
459
459
  * Move the mouse along a humanized Bezier path to `target` and click.
460
460
  *
461
- * `target` accepts either a Playwright-compatible selector string (e.g.
462
- * `'button:has-text("Buy now")'`) or a built `Locator`. The click point
463
- * inside the element is Gaussian-distributed around the center.
461
+ * `target` accepts a Playwright-compatible selector string (e.g.
462
+ * `'button:has-text("Buy now")'`), a built `Locator`, or a raw `Point`.
463
+ * For element targets the click point is Gaussian-distributed around the
464
+ * center; for a raw `Point` the exact coordinates are clicked. The
465
+ * `Point` form is the fallback for things with no clean selector —
466
+ * icon-only buttons, canvas, SVG — where you can see the pixel position
467
+ * but can't address the element.
464
468
  *
465
- * In `speed: 'instant'`, all humanization is skipped and Playwright's
466
- * native `locator.click()` is used directly.
469
+ * In `speed: 'instant'`, all humanization is skipped: element targets use
470
+ * Playwright's native `locator.click()`; a `Point` dispatches one
471
+ * `mouse.click()` at the coordinates.
467
472
  */
468
- click(target: Locator | string): Promise<void>;
473
+ click(target: MouseTarget): Promise<void>;
469
474
  /**
470
475
  * Right-click `target` — opens a native context menu. Same Bezier-path
471
476
  * motion and hover dwell as `click()`; only the dispatched button differs.
477
+ * Accepts the same selector / `Locator` / `Point` targets as `click()`.
472
478
  *
473
- * In `speed: 'instant'`, falls back to `locator.click({ button: 'right' })`.
479
+ * In `speed: 'instant'`, element targets fall back to
480
+ * `locator.click({ button: 'right' })`; a `Point` dispatches one
481
+ * `mouse.click({ button: 'right' })` at the coordinates.
474
482
  */
475
- rightClick(target: Locator | string): Promise<void>;
483
+ rightClick(target: MouseTarget): Promise<void>;
476
484
  /**
477
485
  * Move the cursor to `target` along a humanized Bezier path and settle
478
486
  * on it — no click is dispatched. Useful for hover-triggered UI
@@ -670,6 +678,102 @@ interface Human {
670
678
  */
671
679
  record(fn: () => Promise<void>): Promise<Recording>;
672
680
  record(options: HumanRecordOptions, fn: () => Promise<void>): Promise<Recording>;
681
+ /**
682
+ * Take a screenshot of the page. Forwards to `page.screenshot(options)`
683
+ * unchanged — see Playwright docs for the full options shape.
684
+ *
685
+ * Not a humanized action: no plugin events fire. Pure ergonomic
686
+ * re-export so `human.*` stays a single surface.
687
+ */
688
+ screenshot(options?: Parameters<Page['screenshot']>[0]): Promise<Buffer>;
689
+ /**
690
+ * Visible text content of the page (`document.body.innerText`). Forwards
691
+ * to `page.innerText('body')`. Useful for AI agents that need to
692
+ * understand what's on screen without parsing HTML.
693
+ *
694
+ * For a region instead of the whole page, use `page.innerText(selector)`.
695
+ *
696
+ * Not a humanized action: no plugin events fire.
697
+ */
698
+ pageText(): Promise<string>;
699
+ /**
700
+ * Full HTML of the page. Forwards to `page.content()`. Often large
701
+ * (50–500 KB on typical sites) — prefer {@link Human.pageText} when
702
+ * passing to an LLM unless structure matters.
703
+ *
704
+ * Not a humanized action: no plugin events fire.
705
+ */
706
+ content(): Promise<string>;
707
+ /**
708
+ * Current URL of the page. Forwards to `page.url()`. Synchronous return
709
+ * because Playwright's underlying API is sync.
710
+ *
711
+ * Not a humanized action: no plugin events fire.
712
+ */
713
+ url(): string;
714
+ /**
715
+ * Current document title. Forwards to `page.title()`.
716
+ *
717
+ * Not a humanized action: no plugin events fire.
718
+ */
719
+ title(): Promise<string>;
720
+ /**
721
+ * Reload the current page. Forwards to `page.reload(options)`. Real-user
722
+ * analog of hitting the refresh button — the click motion is *not*
723
+ * humanized (we treat reload as navigation, not a cursor action).
724
+ *
725
+ * Plugins observe a `'reload'` action.
726
+ */
727
+ reload(options?: Parameters<Page['reload']>[0]): Promise<void>;
728
+ /**
729
+ * Navigate back in the browser history. Forwards to `page.goBack(options)`.
730
+ *
731
+ * Plugins observe a `'goBack'` action.
732
+ */
733
+ goBack(options?: Parameters<Page['goBack']>[0]): Promise<void>;
734
+ /**
735
+ * Navigate forward in the browser history. Forwards to
736
+ * `page.goForward(options)`.
737
+ *
738
+ * Plugins observe a `'goForward'` action.
739
+ */
740
+ goForward(options?: Parameters<Page['goForward']>[0]): Promise<void>;
741
+ /**
742
+ * Wait until the page reaches the specified load state. Forwards to
743
+ * `page.waitForLoadState(state, options)`. Common after a humanized
744
+ * click that triggers navigation — gives the page time to settle before
745
+ * the next action.
746
+ *
747
+ * Not a humanized action: no plugin events fire.
748
+ */
749
+ waitForLoadState(state?: Parameters<Page['waitForLoadState']>[0], options?: Parameters<Page['waitForLoadState']>[1]): Promise<void>;
750
+ /**
751
+ * Wait until the page's URL matches `url` (string, RegExp, or predicate).
752
+ * Forwards to `page.waitForURL(url, options)`. Common post-action wait
753
+ * (e.g. after `human.click('#login')`, wait for `/dashboard`).
754
+ *
755
+ * Not a humanized action: no plugin events fire.
756
+ */
757
+ waitForURL(url: Parameters<Page['waitForURL']>[0], options?: Parameters<Page['waitForURL']>[1]): Promise<void>;
758
+ /**
759
+ * Resize the viewport. Forwards to `page.setViewportSize(size)`. Useful
760
+ * for testing responsive layouts or switching between breakpoints
761
+ * mid-session.
762
+ *
763
+ * Not a humanized action: no plugin events fire.
764
+ */
765
+ setViewportSize(size: {
766
+ width: number;
767
+ height: number;
768
+ }): Promise<void>;
769
+ /**
770
+ * Render the page as a PDF. Forwards to `page.pdf(options)`. Chromium
771
+ * only; works most reliably in headless mode (in headed mode, Playwright
772
+ * silently switches to a print-style render).
773
+ *
774
+ * Not a humanized action: no plugin events fire.
775
+ */
776
+ pdf(options?: Parameters<Page['pdf']>[0]): Promise<Buffer>;
673
777
  }
674
778
  /** Options for {@link Human.record}. */
675
779
  interface HumanRecordOptions {
package/dist/index.js CHANGED
@@ -320,15 +320,22 @@ function clamp(value, min, max) {
320
320
  // src/mouse/index.ts
321
321
  async function executeClick(target, ctx, options = {}) {
322
322
  const button = options.button ?? "left";
323
- const locator = typeof target === "string" ? ctx.page.locator(target) : target;
324
323
  if (ctx.speed === "instant") {
325
- const box = await locator.boundingBox();
324
+ if (isPoint(target)) {
325
+ await ctx.page.mouse.click(target.x, target.y, { button });
326
+ ctx.setMousePosition(target);
327
+ return { target };
328
+ }
329
+ const locator = typeof target === "string" ? ctx.page.locator(target) : target;
330
+ const box2 = await locator.boundingBox();
326
331
  await locator.click({ button });
327
- const center = box ? { x: box.x + box.width / 2, y: box.y + box.height / 2 } : ctx.getMousePosition();
332
+ const center = box2 ? { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 } : ctx.getMousePosition();
328
333
  ctx.setMousePosition(center);
329
334
  return { target: center };
330
335
  }
331
- const targetPoint = await moveToTarget(target, ctx, "click");
336
+ const { point: targetPoint, box } = await resolveTargetPointAndBox(target, ctx, "click");
337
+ await maybeMisclickBeat(ctx, box, targetPoint);
338
+ await walkBezierTo(targetPoint, ctx);
332
339
  const preClickMs = computeDwellTime(
333
340
  ctx.personality.dwell.preClickMs,
334
341
  ctx.personality.dwell.preClickJitter,
@@ -357,7 +364,7 @@ async function executeHover(target, ctx) {
357
364
  ctx.setMousePosition(center);
358
365
  return { target: center };
359
366
  }
360
- const targetPoint = await moveToTarget(target, ctx, "hover");
367
+ const targetPoint = await moveToTarget(target, ctx);
361
368
  const dwellMs = computeDwellTime(
362
369
  ctx.personality.dwell.preClickMs,
363
370
  ctx.personality.dwell.preClickJitter,
@@ -440,10 +447,9 @@ async function executeMove(target, ctx) {
440
447
  ctx.setMousePosition(point);
441
448
  return { target: point };
442
449
  }
443
- async function moveToTarget(target, ctx, action) {
444
- const box = await readBoxWithAutoScroll(target, ctx, action);
450
+ async function moveToTarget(target, ctx) {
451
+ const box = await readBoxWithAutoScroll(target, ctx, "hover");
445
452
  const targetPoint = pickClickPoint(box, ctx.rng, ctx.personality.mouse.clickSpread);
446
- if (action === "click") await maybeMisclickBeat(ctx, box, targetPoint);
447
453
  await walkBezierTo(targetPoint, ctx);
448
454
  return targetPoint;
449
455
  }
@@ -1428,6 +1434,52 @@ async function createHuman(page, options = {}) {
1428
1434
  speed,
1429
1435
  events
1430
1436
  });
1437
+ },
1438
+ // ────────────────────────────────────────────────────────────────────
1439
+ // Thin re-exports of common Playwright `Page` methods. See the `Human`
1440
+ // interface for the rationale; implementations forward unchanged.
1441
+ // ────────────────────────────────────────────────────────────────────
1442
+ screenshot(opts) {
1443
+ return page.screenshot(opts);
1444
+ },
1445
+ pageText() {
1446
+ return page.innerText("body");
1447
+ },
1448
+ content() {
1449
+ return page.content();
1450
+ },
1451
+ url() {
1452
+ return page.url();
1453
+ },
1454
+ title() {
1455
+ return page.title();
1456
+ },
1457
+ async reload(opts) {
1458
+ await performAction({ type: "reload", params: {} }, async () => {
1459
+ await page.reload(opts);
1460
+ });
1461
+ },
1462
+ async goBack(opts) {
1463
+ await performAction({ type: "goBack", params: {} }, async () => {
1464
+ await page.goBack(opts);
1465
+ });
1466
+ },
1467
+ async goForward(opts) {
1468
+ await performAction({ type: "goForward", params: {} }, async () => {
1469
+ await page.goForward(opts);
1470
+ });
1471
+ },
1472
+ waitForLoadState(state, opts) {
1473
+ return page.waitForLoadState(state, opts);
1474
+ },
1475
+ waitForURL(url, opts) {
1476
+ return page.waitForURL(url, opts);
1477
+ },
1478
+ setViewportSize(size) {
1479
+ return page.setViewportSize(size);
1480
+ },
1481
+ pdf(opts) {
1482
+ return page.pdf(opts);
1431
1483
  }
1432
1484
  };
1433
1485
  }