@webhands/core 0.1.0 → 0.2.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 +661 -0
- package/README.md +112 -0
- package/dist/cookies-export.d.ts +5 -5
- package/dist/cookies-export.d.ts.map +1 -1
- package/dist/cookies-export.js +4 -4
- package/dist/hand-host.d.ts +217 -0
- package/dist/hand-host.d.ts.map +1 -0
- package/dist/hand-host.js +351 -0
- package/dist/hand-host.js.map +1 -0
- package/dist/hand-loading.d.ts +128 -0
- package/dist/hand-loading.d.ts.map +1 -0
- package/dist/hand-loading.js +143 -0
- package/dist/hand-loading.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/playwright-attach-transport.d.ts +9 -0
- package/dist/playwright-attach-transport.d.ts.map +1 -1
- package/dist/playwright-attach-transport.js +53 -91
- package/dist/playwright-attach-transport.js.map +1 -1
- package/dist/playwright-launch-transport.d.ts +7 -62
- package/dist/playwright-launch-transport.d.ts.map +1 -1
- package/dist/playwright-launch-transport.js +51 -204
- package/dist/playwright-launch-transport.js.map +1 -1
- package/dist/remote-session.d.ts +12 -2
- package/dist/remote-session.d.ts.map +1 -1
- package/dist/remote-session.js +37 -6
- package/dist/remote-session.js.map +1 -1
- package/dist/seam.d.ts +13 -5
- package/dist/seam.d.ts.map +1 -1
- package/dist/session-rpc.d.ts +76 -12
- package/dist/session-rpc.d.ts.map +1 -1
- package/dist/session-rpc.js +76 -8
- package/dist/session-rpc.js.map +1 -1
- package/dist/stub-transport.d.ts +2 -2
- package/dist/stub-transport.d.ts.map +1 -1
- package/dist/stub-transport.js +11 -0
- package/dist/stub-transport.js.map +1 -1
- package/package.json +21 -2
- package/src/cookies-export.ts +5 -5
- package/src/hand-host.ts +511 -0
- package/src/hand-loading.ts +254 -0
- package/src/index.ts +18 -1
- package/src/playwright-attach-transport.ts +65 -119
- package/src/playwright-launch-transport.ts +63 -244
- package/src/remote-session.ts +43 -5
- package/src/seam.ts +13 -5
- package/src/session-rpc.ts +121 -11
- package/src/stub-transport.ts +15 -3
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# webhands
|
|
2
|
+
|
|
3
|
+
A CLI (built with [`incur`](https://github.com/wevm/incur), so it doubles as an
|
|
4
|
+
MCP server) that drives a real, persistent browser via Playwright, letting an
|
|
5
|
+
agent or human control any website from a genuinely logged-in browser session on
|
|
6
|
+
their own machine and IP.
|
|
7
|
+
|
|
8
|
+
It launches (or attaches to) a Chromium browser using a dedicated profile,
|
|
9
|
+
supports a one-time headed login that is later reused headless, keeps the session
|
|
10
|
+
alive across separate CLI invocations behind a long-lived `serve` process, and
|
|
11
|
+
exposes page verbs (`goto`, `snapshot`, `click`, `type`, `eval`, `wait`,
|
|
12
|
+
`cookies`) with structured output.
|
|
13
|
+
|
|
14
|
+
## Use it via your AI agent (start here)
|
|
15
|
+
|
|
16
|
+
The simplest way to use `webhands` is to let your coding agent (Claude Code,
|
|
17
|
+
Cursor, etc.) run it through plain `bash` with `npx`. No MCP wiring, no install
|
|
18
|
+
step — the agent just runs `npx webhands <verb>` commands. The first run of
|
|
19
|
+
`npx webhands` fetches the package automatically.
|
|
20
|
+
|
|
21
|
+
Give your agent something like: *"Use `webhands` to open Kayak and read me the
|
|
22
|
+
live prices for EDI→BOM on 31 Oct."* A capable agent will then:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# 1. start & HOLD the browser. serve blocks, so the agent backgrounds it:
|
|
26
|
+
nohup npx webhands serve --headed > /tmp/webhands.log 2>&1 &
|
|
27
|
+
sleep 12 && cat /tmp/webhands.log # confirm it printed an endpoint + pid
|
|
28
|
+
|
|
29
|
+
# 2. navigate the live page (separate invocation, same browser):
|
|
30
|
+
npx webhands goto 'https://www.kayak.co.uk/flights/EDI-BOM/2026-10-31?sort=price_a'
|
|
31
|
+
|
|
32
|
+
# 3. let JS results render, then read the page token-cheaply:
|
|
33
|
+
npx webhands wait --ms 8000
|
|
34
|
+
npx webhands snapshot --token-limit 6000
|
|
35
|
+
|
|
36
|
+
# 4. always tear down when done:
|
|
37
|
+
npx webhands stop
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Three things a new user should know up front:
|
|
41
|
+
|
|
42
|
+
- **You log in once, in a window you can see.** Run `npx webhands setup-profile`
|
|
43
|
+
(or start with `serve --headed`) and sign in / clear any cookie or anti-bot
|
|
44
|
+
prompt yourself. That state is saved to a dedicated profile and reused on later
|
|
45
|
+
runs. The tool never bypasses logins or solves CAPTCHAs — you do that part.
|
|
46
|
+
- **It acts as the real, logged-in you.** Reading pages is low-risk; let the agent
|
|
47
|
+
do that freely. But anything that spends money, books, posts, or changes account
|
|
48
|
+
state should be YOUR explicit decision — have the agent surface the link and let
|
|
49
|
+
you finish checkout. (See *Scope and honesty* below.)
|
|
50
|
+
- **Anti-bot sites may need the visible window.** Headless runs can hit a
|
|
51
|
+
"you look like a bot" page on sites like Kayak. The fix is to run `--headed` and
|
|
52
|
+
clear the challenge yourself once, not to defeat it.
|
|
53
|
+
|
|
54
|
+
For the full agent playbook (workflow, gotchas, guardrails) install the bundled
|
|
55
|
+
skill: `npx webhands skills add` then look for `use-webhands`. Per-verb flag
|
|
56
|
+
reference: `npx webhands <verb> --help` or `npx webhands --llms-full`.
|
|
57
|
+
|
|
58
|
+
## How it works (the pipe)
|
|
59
|
+
|
|
60
|
+
The browser is owned by ONE long-lived `serve` process; each verb invocation is a
|
|
61
|
+
thin client that drives the SAME live page and exits (see
|
|
62
|
+
[`docs/adr/0005`](docs/adr/0005-incur-serve-hosts-the-long-lived-session.md)). The
|
|
63
|
+
typical end-to-end flow:
|
|
64
|
+
|
|
65
|
+
1. `webhands setup-profile`: opens the dedicated profile in a
|
|
66
|
+
VISIBLE browser so you log in / clear any anti-bot challenge ONCE. State
|
|
67
|
+
(cookies, login, challenge clearance) persists on disk.
|
|
68
|
+
2. `webhands serve --headless`: launches the one browser against
|
|
69
|
+
that saved profile and keeps it alive (runs until `stop` or Ctrl-C).
|
|
70
|
+
3. `webhands goto <url>` then `webhands snapshot` (and
|
|
71
|
+
`click` / `type` / `eval` / `wait`): separate invocations that all drive the
|
|
72
|
+
single live page the server holds.
|
|
73
|
+
4. `webhands stop`: tears the session down.
|
|
74
|
+
|
|
75
|
+
A verb run with no live server prints a clear error telling you to run `serve`
|
|
76
|
+
first; the tool never silently spawns a browser.
|
|
77
|
+
|
|
78
|
+
## Scope and honesty (please read)
|
|
79
|
+
|
|
80
|
+
This is a **personal-use** tool. Its whole premise is that you drive a browser
|
|
81
|
+
**you logged into yourself**, on **your own machine and your own IP**, reusing
|
|
82
|
+
**your own authenticated session** (see
|
|
83
|
+
[`docs/adr/0002`](docs/adr/0002-real-session-over-fingerprint-spoofing.md)). It is
|
|
84
|
+
deliberately local and single-session by design.
|
|
85
|
+
|
|
86
|
+
- **No login-bypass, no CAPTCHA-solving.** The human does the one-time login and
|
|
87
|
+
clears any anti-bot challenge in the headed `setup-profile` step. This tool
|
|
88
|
+
does NOT bypass authentication or solve CAPTCHAs programmatically, and it is not
|
|
89
|
+
intended to.
|
|
90
|
+
- **No fingerprint-spoofing / anti-detect tricks.** It leans on being a *real*
|
|
91
|
+
browser/profile/IP rather than spoofing. There is no proxy rotation or
|
|
92
|
+
anti-detect build here.
|
|
93
|
+
- **Your own session only.** A replayed/stolen cookie does not work anyway
|
|
94
|
+
(clearance is bound to the browser fingerprint and IP, not just the cookie);
|
|
95
|
+
the design assumes the session is genuinely yours.
|
|
96
|
+
|
|
97
|
+
In short: this is for reading and acting on web apps **you already have an account
|
|
98
|
+
on**, from **your own browser**, the way you could by hand.
|
|
99
|
+
|
|
100
|
+
## Security note (the `serve` endpoint runs arbitrary code)
|
|
101
|
+
|
|
102
|
+
The page verbs execute caller-supplied expressions: `eval` runs a JS expression
|
|
103
|
+
in the page, and a `click`/`type` locator is a raw Playwright locator EXPRESSION
|
|
104
|
+
the controller evaluates (see
|
|
105
|
+
[`docs/adr/0004`](docs/adr/0004-verb-surface-exposes-playwright-locator-semantics.md)).
|
|
106
|
+
That is by design for a LOCAL tool driven by its own agent against your own
|
|
107
|
+
session, but it means the running `serve` endpoint is a code-execution surface.
|
|
108
|
+
|
|
109
|
+
- **Do NOT expose the `serve` endpoint to untrusted callers.** Keep it bound to
|
|
110
|
+
localhost (the default); never bind it to a public interface or hand its URL to
|
|
111
|
+
code you do not trust. Anyone who can call it can run arbitrary JavaScript in
|
|
112
|
+
your logged-in session.
|
package/dist/cookies-export.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* The `cookies export` / `cookies import` verb's FILE FORMAT (PRD story 11).
|
|
3
3
|
*
|
|
4
4
|
* The seam already carries the transport-neutral cookie primitives:
|
|
5
|
-
* {@link
|
|
6
|
-
* {@link
|
|
5
|
+
* {@link WebHandsPage.cookies} reads the active context's cookies and
|
|
6
|
+
* {@link WebHandsPage.setCookies} loads cookies into it. The export/import VERB is built
|
|
7
7
|
* ON TOP of those two methods (the forward-note: refine the existing seam,
|
|
8
8
|
* do NOT add a parallel cookie path). What this module adds is only the
|
|
9
9
|
* SERIALIZATION the verb needs to move a session to/from disk: how a
|
|
@@ -31,11 +31,11 @@ export declare const COOKIES_EXPORT_VERSION: 1;
|
|
|
31
31
|
export interface CookiesExport {
|
|
32
32
|
/** Format version (see {@link COOKIES_EXPORT_VERSION}). */
|
|
33
33
|
readonly version: typeof COOKIES_EXPORT_VERSION;
|
|
34
|
-
/** The exported cookies, exactly as the seam's {@link
|
|
34
|
+
/** The exported cookies, exactly as the seam's {@link WebHandsPage.cookies} returns them. */
|
|
35
35
|
readonly cookies: readonly Cookie[];
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Serialize the cookies read from the seam ({@link
|
|
38
|
+
* Serialize the cookies read from the seam ({@link WebHandsPage.cookies}) into the
|
|
39
39
|
* export file's text. Pretty-printed JSON so a human can read/diff a backed-up
|
|
40
40
|
* session. This is pure: it does NO disk I/O, so the caller (the CLI verb, a
|
|
41
41
|
* test) owns WHERE the file lands — which is what lets a test keep its export
|
|
@@ -44,7 +44,7 @@ export interface CookiesExport {
|
|
|
44
44
|
export declare function serializeCookies(cookies: readonly Cookie[]): string;
|
|
45
45
|
/**
|
|
46
46
|
* Parse an export file's text back into the cookies to hand to the seam's
|
|
47
|
-
* {@link
|
|
47
|
+
* {@link WebHandsPage.setCookies} ({@link parse} is pure; the caller does the disk read
|
|
48
48
|
* and the `setCookies` call). Rejects anything that is not a recognised export
|
|
49
49
|
* envelope so a corrupt or wrong-version file surfaces as a clear error rather
|
|
50
50
|
* than silently importing nothing or a half-parsed list.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies-export.d.ts","sourceRoot":"","sources":["../src/cookies-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,sBAAsB,CAAC;IAChD,
|
|
1
|
+
{"version":3,"file":"cookies-export.d.ts","sourceRoot":"","sources":["../src/cookies-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,sBAAsB,CAAC;IAChD,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAyBlE"}
|
package/dist/cookies-export.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* The `cookies export` / `cookies import` verb's FILE FORMAT (PRD story 11).
|
|
3
3
|
*
|
|
4
4
|
* The seam already carries the transport-neutral cookie primitives:
|
|
5
|
-
* {@link
|
|
6
|
-
* {@link
|
|
5
|
+
* {@link WebHandsPage.cookies} reads the active context's cookies and
|
|
6
|
+
* {@link WebHandsPage.setCookies} loads cookies into it. The export/import VERB is built
|
|
7
7
|
* ON TOP of those two methods (the forward-note: refine the existing seam,
|
|
8
8
|
* do NOT add a parallel cookie path). What this module adds is only the
|
|
9
9
|
* SERIALIZATION the verb needs to move a session to/from disk: how a
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
export const COOKIES_EXPORT_VERSION = 1;
|
|
25
25
|
/**
|
|
26
|
-
* Serialize the cookies read from the seam ({@link
|
|
26
|
+
* Serialize the cookies read from the seam ({@link WebHandsPage.cookies}) into the
|
|
27
27
|
* export file's text. Pretty-printed JSON so a human can read/diff a backed-up
|
|
28
28
|
* session. This is pure: it does NO disk I/O, so the caller (the CLI verb, a
|
|
29
29
|
* test) owns WHERE the file lands — which is what lets a test keep its export
|
|
@@ -38,7 +38,7 @@ export function serializeCookies(cookies) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Parse an export file's text back into the cookies to hand to the seam's
|
|
41
|
-
* {@link
|
|
41
|
+
* {@link WebHandsPage.setCookies} ({@link parse} is pure; the caller does the disk read
|
|
42
42
|
* and the `setCookies` call). Rejects anything that is not a recognised export
|
|
43
43
|
* envelope so a corrupt or wrong-version file surfaces as a clear error rather
|
|
44
44
|
* than silently importing nothing or a half-parsed list.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { type BrowserContext, type Page } from 'playwright';
|
|
2
|
+
import type { WebHandsPage, WaitCondition } from './seam.js';
|
|
3
|
+
/**
|
|
4
|
+
* The hand-host primitive (Phase 1 of the "hands" prd,
|
|
5
|
+
* `work/prds/tasked/hands-pluggable-page-capabilities.md`).
|
|
6
|
+
*
|
|
7
|
+
* A **hand** is in-process code that closes over the WebHandsPage and contributes named
|
|
8
|
+
* verbs (+ an optional `dispose`). This module is the host: it builds the
|
|
9
|
+
* scoped-but-LIVE {@link HandContext} from the live Playwright objects, lets
|
|
10
|
+
* each hand contribute its verbs, and composes them into the same {@link WebHandsPage}
|
|
11
|
+
* object the seam already exposes (see {@link composePage}).
|
|
12
|
+
*
|
|
13
|
+
* webhands' OWN eight verbs are themselves built-in hands over this host
|
|
14
|
+
* ({@link BUILT_IN_HANDS}), so the primitive is proven by self-application: if
|
|
15
|
+
* it can express webhands' `click`/`snapshot`/`cookies`, it can host a
|
|
16
|
+
* third-party hand the same way (Phase 2). This is a purely INTERNAL,
|
|
17
|
+
* behavior-preserving refactor — the verb composition that lived as a
|
|
18
|
+
* duplicated `page` object literal in BOTH Playwright transports now lives here
|
|
19
|
+
* once.
|
|
20
|
+
*
|
|
21
|
+
* INTERNAL-ONLY BOUNDARY (the prd's resolved Q2): this whole module is
|
|
22
|
+
* package-internal. {@link Hand}/{@link HandContext}/{@link composePage} are
|
|
23
|
+
* NOT exported from the package entry point (`index.ts`) in Phase 1; they go
|
|
24
|
+
* public in the separate Phase 2 task. The public seam (`seam.ts`) is
|
|
25
|
+
* unchanged.
|
|
26
|
+
*
|
|
27
|
+
* NO-LEAK / CROSS-BROWSER (ADR-0003, refined by the prd): the host is built
|
|
28
|
+
* INSIDE the Playwright transport(s) and uses only the Playwright
|
|
29
|
+
* `Page`/`BrowserContext` API — no CDP/Chromium-only types — so the live
|
|
30
|
+
* `pwPage` stays in-process and never crosses the seam, and the host introduces
|
|
31
|
+
* no Chromium-only dependency that would foreclose a future Firefox launch
|
|
32
|
+
* (only CDP-`attach` stays Chromium-bound, as today).
|
|
33
|
+
*
|
|
34
|
+
* TRUST MODEL (stated, not enforced here): hands are trusted, local, in-process
|
|
35
|
+
* peers with ZERO isolation between them (one live page, one process).
|
|
36
|
+
* Inter-hand reuse is ordinary Node composition (import & call), NOT a
|
|
37
|
+
* sibling-hand registry in the context — so {@link HandContext} carries live
|
|
38
|
+
* page access only.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The scoped-but-LIVE access a hand receives. It carries live page access ONLY
|
|
42
|
+
* (the trust model note above): the real Playwright {@link Page} and
|
|
43
|
+
* {@link BrowserContext} the hand operates against in-process, plus the
|
|
44
|
+
* lifecycle guard.
|
|
45
|
+
*
|
|
46
|
+
* - `pwPage` — the live Node-side Playwright `Page`. NEVER crosses the seam.
|
|
47
|
+
* - `context` — the live `BrowserContext`; the built-in `cookies`/`setCookies`
|
|
48
|
+
* hand proves it is needed (cookies are a context-level, not page-level,
|
|
49
|
+
* concern).
|
|
50
|
+
* - `ensureOpen` — the per-session lifecycle guard. Each verb calls it first so
|
|
51
|
+
* a verb invoked after the session closed rejects with `session is closed`
|
|
52
|
+
* (the seam's lifetime contract). The guard's "closed" state is owned by the
|
|
53
|
+
* per-transport session wiring (launch vs attach differ); the host only reads
|
|
54
|
+
* it through this function.
|
|
55
|
+
*/
|
|
56
|
+
export interface HandContext {
|
|
57
|
+
readonly pwPage: Page;
|
|
58
|
+
readonly context: BrowserContext;
|
|
59
|
+
readonly ensureOpen: () => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What a hand contributes once given its {@link HandContext}: a set of named
|
|
63
|
+
* verbs (a subset of webhands' (eight) seam verbs, i.e. a `Partial` of the
|
|
64
|
+
* seam {@link WebHandsPage}) and an optional `dispose` for any in-process
|
|
65
|
+
* resource it set up.
|
|
66
|
+
*
|
|
67
|
+
* A hand may contribute several verbs (the built-in interaction hand contributes
|
|
68
|
+
* both `click` and `type`) — a hand is NOT a single verb. It is NOT a transport
|
|
69
|
+
* either: it does not `open` sessions. Nothing more than this is allowed (no
|
|
70
|
+
* lifecycle hooks, no event handlers, no MCP-definition objects) — those are
|
|
71
|
+
* either the transport's job (session lifecycle) or a later phase's.
|
|
72
|
+
*/
|
|
73
|
+
export interface HandContribution {
|
|
74
|
+
readonly verbs: Partial<WebHandsPage>;
|
|
75
|
+
readonly dispose?: () => Promise<void> | void;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A hand: a capability MODULE that, given live page access, contributes verbs.
|
|
79
|
+
* It is a plain factory function so a hand is just ordinary in-process Node
|
|
80
|
+
* code closing over the {@link HandContext} — the exact shape webhands' own
|
|
81
|
+
* verbs already had, made explicit.
|
|
82
|
+
*/
|
|
83
|
+
export type Hand = (ctx: HandContext) => HandContribution;
|
|
84
|
+
/**
|
|
85
|
+
* The composed result the host hands back to a transport's session wiring: the
|
|
86
|
+
* {@link WebHandsPage} (the seam object the verbs were merged into) and a single
|
|
87
|
+
* `dispose` that tears down every hand.
|
|
88
|
+
*/
|
|
89
|
+
export interface ComposedHands {
|
|
90
|
+
readonly page: WebHandsPage;
|
|
91
|
+
/**
|
|
92
|
+
* Dispose every hand's resources. Hands are disposed in REVERSE registration
|
|
93
|
+
* order (LIFO, the natural teardown order for layered setup), and every
|
|
94
|
+
* hand's `dispose` is awaited even if an earlier one rejects, so one failing
|
|
95
|
+
* hand cannot strand another's cleanup. This disposes the HANDS only; tearing
|
|
96
|
+
* down the browser/context (and the order relative to this) is the
|
|
97
|
+
* per-transport session lifecycle's job, NOT the host's.
|
|
98
|
+
*/
|
|
99
|
+
dispose(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compose a set of hands over one live {@link HandContext} into a single
|
|
103
|
+
* {@link WebHandsPage}. This is the host primitive both Playwright transports call to
|
|
104
|
+
* build their session's verb surface — the SINGLE shared composition (no
|
|
105
|
+
* duplicated page-object literal).
|
|
106
|
+
*
|
|
107
|
+
* Composition is EAGER (exactly as the page object literal was built before):
|
|
108
|
+
* each hand is invoked once at session-open time and its verbs are merged into
|
|
109
|
+
* one page object. There is no lazy registration and no ordering effect on the
|
|
110
|
+
* verbs themselves (the eight built-in verbs have disjoint names). The returned
|
|
111
|
+
* {@link WebHandsPage} is validated to carry every verb the seam requires, so a missing
|
|
112
|
+
* built-in verb is a build-time/open-time failure here rather than an `undefined
|
|
113
|
+
* is not a function` at the call site.
|
|
114
|
+
*/
|
|
115
|
+
export declare function composePage(ctx: HandContext, hands: readonly Hand[]): ComposedHands;
|
|
116
|
+
/** The `navigate` verb: go to a URL and let it settle on the `load` event. */
|
|
117
|
+
export declare const navigationHand: Hand;
|
|
118
|
+
/** The `snapshot` verb: the token-cheap a11y view, or `--full` raw DOM. */
|
|
119
|
+
export declare const snapshotHand: Hand;
|
|
120
|
+
/** The `click` + `type` verbs: page interaction by raw locator (ADR-0004). */
|
|
121
|
+
export declare const interactionHand: Hand;
|
|
122
|
+
/** The `eval` escape hatch: run a JS EXPRESSION in the page, return by value. */
|
|
123
|
+
export declare const evalHand: Hand;
|
|
124
|
+
/** The `wait` verb: pace actions by a condition (timeout/locator/navigation). */
|
|
125
|
+
export declare const waitHand: Hand;
|
|
126
|
+
/**
|
|
127
|
+
* The `cookies` + `setCookies` verbs. These prove the {@link HandContext} needs
|
|
128
|
+
* the `context`: cookies are a context-level, not page-level, concern, so this
|
|
129
|
+
* hand reaches `ctx.context`, not `ctx.pwPage`.
|
|
130
|
+
*/
|
|
131
|
+
export declare const cookiesHand: Hand;
|
|
132
|
+
/**
|
|
133
|
+
* webhands' eight built-in verbs as built-in hands, in composition order. Both
|
|
134
|
+
* Playwright transports compose THIS exact set, so the verb surface is
|
|
135
|
+
* identical across launch and attach (the only legitimate difference is the
|
|
136
|
+
* per-transport SESSION LIFECYCLE, which is not a hand's concern).
|
|
137
|
+
*/
|
|
138
|
+
export declare const BUILT_IN_HANDS: readonly Hand[];
|
|
139
|
+
/**
|
|
140
|
+
* Compose webhands' built-in hands over a live context into the seam's
|
|
141
|
+
* {@link WebHandsPage}. The convenience both transports call: `composePage(ctx,
|
|
142
|
+
* BUILT_IN_HANDS)`. The built-in hands set up no in-process resources, so the
|
|
143
|
+
* returned `dispose` is a no-op today; it exists so a transport can sequence
|
|
144
|
+
* hand-teardown before its own browser/context teardown once third-party hands
|
|
145
|
+
* (which may hold resources) are added in Phase 2.
|
|
146
|
+
*/
|
|
147
|
+
export declare function composeBuiltInPage(ctx: HandContext): ComposedHands;
|
|
148
|
+
/**
|
|
149
|
+
* Compose webhands' built-in hands together with any explicitly-loaded
|
|
150
|
+
* third-party hands (Phase 2) over a live context. The third-party hands are
|
|
151
|
+
* composed AFTER the built-ins through the EXACT same {@link composePage} the
|
|
152
|
+
* built-ins use, so a loaded hand plugs into the same host: its verbs merge into
|
|
153
|
+
* the same seam {@link WebHandsPage} and its `dispose` is sequenced LIFO with the rest.
|
|
154
|
+
* A third-party hand may add NEW verbs (the common case) and, because later
|
|
155
|
+
* contributions win the merge, may also override a built-in verb — that is the
|
|
156
|
+
* operator's choice, made by the trust act of naming the hand (ADR-0007).
|
|
157
|
+
*/
|
|
158
|
+
export declare function composeWithHands(ctx: HandContext, extraHands: readonly Hand[]): ComposedHands;
|
|
159
|
+
/**
|
|
160
|
+
* Run the `wait` verb's three forms (PRD story 10) against a Playwright page.
|
|
161
|
+
*
|
|
162
|
+
* - `timeout` — pace by a fixed delay (`waitForTimeout`), so an agent can act
|
|
163
|
+
* like a human and let XHR-rendered content land.
|
|
164
|
+
* - `locator` — block until the addressed element appears (`Locator.waitFor()`),
|
|
165
|
+
* the form for content rendered AFTER `goto` settled on `load`.
|
|
166
|
+
* - `navigation` — block until the NEXT navigation settles to `load`. We use
|
|
167
|
+
* `waitForNavigation()` even though Playwright marks it `@deprecated` ("racy,
|
|
168
|
+
* use waitForURL"): that deprecation targets in-process TEST code that can arm
|
|
169
|
+
* the wait BEFORE the action and pass a target URL. Neither holds here. Across
|
|
170
|
+
* this seam verbs are DISCRETE sequential calls (`click` then `wait`), so we
|
|
171
|
+
* CANNOT arm before the trigger; and the realistic trigger is an async,
|
|
172
|
+
* JS-driven transition (a redirect / SPA route change that fires AFTER the
|
|
173
|
+
* agent's action, the "let XHR-rendered content load" case of story 10), so
|
|
174
|
+
* "wait for the NEXT navigation" is exactly right — whereas `waitForLoadState`
|
|
175
|
+
* would see the already-loaded current page and return before the pending
|
|
176
|
+
* transition. `waitForURL` is unusable because the verb has no target URL by
|
|
177
|
+
* design (the agent waits for "a navigation", not a known address). (See the
|
|
178
|
+
* task's ## Decisions note.)
|
|
179
|
+
*
|
|
180
|
+
* Shared by both Playwright transports (via the `wait` built-in hand) so the
|
|
181
|
+
* verb behaviour stays identical (no parallel second implementation).
|
|
182
|
+
*/
|
|
183
|
+
export declare function waitFor(page: Page, condition: WaitCondition): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Resolve a raw Playwright locator EXPRESSION (ADR-0004) against the page. The
|
|
186
|
+
* verb surface passes locator expressions like `getByRole('button', …)`; we
|
|
187
|
+
* evaluate them in a small sandbox where `page`/`p` is the page, so the full
|
|
188
|
+
* Playwright locator grammar is available without leaking the type across the
|
|
189
|
+
* seam.
|
|
190
|
+
*
|
|
191
|
+
* One resolution path for both transports (via the built-in interaction/wait
|
|
192
|
+
* hands), so there is no parallel addressing scheme.
|
|
193
|
+
*/
|
|
194
|
+
export declare function resolveLocator(page: Page, expression: string): import("playwright").Locator;
|
|
195
|
+
/**
|
|
196
|
+
* Run the `click` verb against a Playwright page (PRD story 8), shared by both
|
|
197
|
+
* Playwright transports (via the built-in interaction hand) so the verb behaves
|
|
198
|
+
* identically (mirrors {@link waitFor}; no parallel second implementation).
|
|
199
|
+
*
|
|
200
|
+
* First try a normal `Locator.click()`, which AUTO-WAITS for the element to be
|
|
201
|
+
* visible and actionable — the right behaviour for a real button. A hidden
|
|
202
|
+
* custom input (the case the prd calls out) NEVER becomes actionable, so that
|
|
203
|
+
* click times out; on a Playwright `TimeoutError` we fall back to
|
|
204
|
+
* `dispatchEvent('click')`, which fires a click WITHOUT the actionability
|
|
205
|
+
* checks. The fallback is deliberately the documented Playwright escape (a
|
|
206
|
+
* sibling to the `eval` hatch, ADR-0004), not a reimplemented click: we keep
|
|
207
|
+
* the locator a raw resolved expression and only change HOW the resolved
|
|
208
|
+
* locator is clicked.
|
|
209
|
+
*
|
|
210
|
+
* Only a timeout triggers the fallback. The fallback `dispatchEvent` is itself
|
|
211
|
+
* bounded by the same short timeout, so a locator that resolves NO element (a
|
|
212
|
+
* bad locator) surfaces its timeout quickly instead of hanging the dispatch on
|
|
213
|
+
* Playwright's 30s default — the dispatch escape is for elements that EXIST but
|
|
214
|
+
* are not actionable (hidden custom inputs), not for absent ones.
|
|
215
|
+
*/
|
|
216
|
+
export declare function clickLocator(page: Page, expression: string): Promise<void>;
|
|
217
|
+
//# sourceMappingURL=hand-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hand-host.d.ts","sourceRoot":"","sources":["../src/hand-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAEX,YAAY,EAGZ,aAAa,EACb,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B;;;;;;;OAOG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,IAAI,EAAE,GACpB,aAAa,CAgCf;AAsDD,8EAA8E;AAC9E,eAAO,MAAM,cAAc,EAAE,IAe3B,CAAC;AAEH,2EAA2E;AAC3E,eAAO,MAAM,YAAY,EAAE,IAyBzB,CAAC;AAEH,8EAA8E;AAC9E,eAAO,MAAM,eAAe,EAAE,IAW5B,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,IAoBrB,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,IAOrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,IAYxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,IAAI,EAOzC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,SAAS,IAAI,EAAE,GACzB,aAAa,CAEf;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,OAAO,CAC5B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gCAO5D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CACjC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAYf"}
|