captchakraken 2.1.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.
Files changed (37) hide show
  1. package/README.md +54 -0
  2. package/dist/index.d.ts +56 -0
  3. package/dist/index.js +43 -0
  4. package/dist/playwright-types.d.ts +119 -0
  5. package/dist/playwright-types.js +25 -0
  6. package/dist/puppeteer-adapter.d.ts +70 -0
  7. package/dist/puppeteer-adapter.js +96 -0
  8. package/dist/solver.d.ts +264 -0
  9. package/dist/solver.js +1922 -0
  10. package/dist/token-usage.d.ts +15 -0
  11. package/dist/token-usage.js +102 -0
  12. package/dist/types.d.ts +248 -0
  13. package/dist/types.js +2 -0
  14. package/package.json +49 -0
  15. package/python/Dockerfile +41 -0
  16. package/python/README.md +71 -0
  17. package/python/examples/README.md +68 -0
  18. package/python/examples/_harness.py +158 -0
  19. package/python/examples/demoHcaptcha.py +20 -0
  20. package/python/examples/demoRecaptcha.py +17 -0
  21. package/python/pyproject.toml +79 -0
  22. package/python/src/captchakraken/__init__.py +61 -0
  23. package/python/src/captchakraken/action_types.py +56 -0
  24. package/python/src/captchakraken/cli.py +656 -0
  25. package/python/src/captchakraken/config.py +78 -0
  26. package/python/src/captchakraken/image_processor.py +244 -0
  27. package/python/src/captchakraken/overlay.py +520 -0
  28. package/python/src/captchakraken/planner.py +408 -0
  29. package/python/src/captchakraken/planner_types.py +74 -0
  30. package/python/src/captchakraken/server_manager.py +290 -0
  31. package/python/src/captchakraken/solver.py +434 -0
  32. package/python/src/captchakraken/timing.py +42 -0
  33. package/python/src/captchakraken/tool_calls/find_checkbox.py +72 -0
  34. package/python/src/captchakraken/tool_calls/find_grid.py +1762 -0
  35. package/python/src/captchakraken/tool_calls/move_indicator.py +431 -0
  36. package/scripts/copy-python.mjs +29 -0
  37. package/scripts/setup-python.js +104 -0
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # captchakraken
2
+
3
+ The TypeScript browser driver for [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken).
4
+ Hand it a Playwright/Puppeteer `Page`; it finds the captcha, reads the grid with
5
+ a fine-tuned **Qwen3.5-9B** vision model on **vLLM**, clicks human-like, and
6
+ verifies through to a token.
7
+
8
+ > Full docs — demo videos, accuracy, self-hosting — live in the main repo
9
+ > **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install captchakraken
15
+ ```
16
+
17
+ The package bundles the Python engine (`captchakraken`) and, on `postinstall`,
18
+ creates a local venv with its lightweight core deps so grid detection + the vLLM
19
+ planner work out of the box. It ships **no browser** — bring your own
20
+ Playwright-compatible launcher.
21
+
22
+ - Skip the Python bootstrap: `CAPTCHA_KRAKEN_SKIP_PYTHON_SETUP=1`
23
+ - To **self-host** the model, run the repo's `setup.sh` (installs vLLM + weights).
24
+ Otherwise point `VLLM_BASE_URL` at a server you already run.
25
+
26
+ ## Usage
27
+
28
+ ```typescript
29
+ import { chromium } from 'playwright'; // or patchright / camoufox-js
30
+ import { CaptchaKrakenSolver } from 'captchakraken';
31
+
32
+ const browser = await chromium.launch({ headless: false });
33
+ const page = await (await browser.newContext()).newPage();
34
+ await page.goto('https://www.google.com/recaptcha/api2/demo');
35
+
36
+ // Reads VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY from the environment.
37
+ const solver = new CaptchaKrakenSolver();
38
+ await solver.solve(page); // detect → solve grid → click → verify
39
+
40
+ await browser.close();
41
+ ```
42
+
43
+ Puppeteer users wrap the page once with `fromPuppeteer(page)`.
44
+
45
+ ## Configuration
46
+
47
+ | Variable | Meaning |
48
+ |---|---|
49
+ | `VLLM_BASE_URL` | Inference endpoint (local vLLM, or a server you run) |
50
+ | `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) |
51
+
52
+ ## License
53
+
54
+ GPL-3.0-or-later.
@@ -0,0 +1,56 @@
1
+ export * from './types';
2
+ export { CaptchaKrakenSolver } from './solver';
3
+ /**
4
+ * Adapter for driving the solver with **Puppeteer** instead of a Playwright
5
+ * launcher. Puppeteer's `Page` is API-compatible with Playwright's except for a
6
+ * few method names/options; `fromPuppeteer(page)` wraps it so `solve()` accepts
7
+ * it. Playwright-family launchers (vanilla `playwright`, `patchright`,
8
+ * `camoufox-js`) need no adapter — pass their `Page` directly.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import puppeteer from 'puppeteer';
13
+ * import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
14
+ *
15
+ * const browser = await puppeteer.launch({ headless: false });
16
+ * const page = await browser.newPage();
17
+ * await page.goto('https://www.google.com/recaptcha/api2/demo');
18
+ *
19
+ * const solver = new CaptchaKrakenSolver();
20
+ * await solver.solve(fromPuppeteer(page));
21
+ * await browser.close();
22
+ * ```
23
+ */
24
+ export { fromPuppeteer } from './puppeteer-adapter';
25
+ /**
26
+ * The Playwright `Page` type the solver operates on.
27
+ *
28
+ * This is an implementation-neutral, structurally-typed Playwright `Page` — a
29
+ * duck-typed surface defined by this package, NOT imported from any browser
30
+ * library. The package depends on no concrete browser implementation, so you
31
+ * can drive the solver with ANY Playwright-compatible launcher: vanilla
32
+ * `playwright`, `patchright`, `camoufox-js`, or anything else that hands back a
33
+ * standard Playwright `Page`. They all structurally satisfy this type — pass
34
+ * yours directly, no cast needed.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Pick whichever Playwright-compatible launcher you want — install it
39
+ * // yourself; this package does not bundle a browser:
40
+ * import { chromium } from 'playwright'; // vanilla
41
+ * // import { chromium } from 'patchright'; // stealth-patched
42
+ * // import { Camoufox } from 'camoufox-js'; // Firefox stealth
43
+ * import { CaptchaKrakenSolver } from 'captchakraken';
44
+ *
45
+ * const browser = await chromium.launch({ headless: false });
46
+ * const page = await (await browser.newContext()).newPage();
47
+ * await page.goto('https://www.google.com/recaptcha/api2/demo');
48
+ *
49
+ * // Reads VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY from the environment.
50
+ * const solver = new CaptchaKrakenSolver();
51
+ * await solver.solve(page); // detect → solve grid → click → verify
52
+ *
53
+ * await browser.close();
54
+ * ```
55
+ */
56
+ export type { Page, PlaywrightPage, PlaywrightFrame, PlaywrightElementHandle } from './playwright-types';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.fromPuppeteer = exports.CaptchaKrakenSolver = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var solver_1 = require("./solver");
20
+ Object.defineProperty(exports, "CaptchaKrakenSolver", { enumerable: true, get: function () { return solver_1.CaptchaKrakenSolver; } });
21
+ /**
22
+ * Adapter for driving the solver with **Puppeteer** instead of a Playwright
23
+ * launcher. Puppeteer's `Page` is API-compatible with Playwright's except for a
24
+ * few method names/options; `fromPuppeteer(page)` wraps it so `solve()` accepts
25
+ * it. Playwright-family launchers (vanilla `playwright`, `patchright`,
26
+ * `camoufox-js`) need no adapter — pass their `Page` directly.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import puppeteer from 'puppeteer';
31
+ * import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
32
+ *
33
+ * const browser = await puppeteer.launch({ headless: false });
34
+ * const page = await browser.newPage();
35
+ * await page.goto('https://www.google.com/recaptcha/api2/demo');
36
+ *
37
+ * const solver = new CaptchaKrakenSolver();
38
+ * await solver.solve(fromPuppeteer(page));
39
+ * await browser.close();
40
+ * ```
41
+ */
42
+ var puppeteer_adapter_1 = require("./puppeteer-adapter");
43
+ Object.defineProperty(exports, "fromPuppeteer", { enumerable: true, get: function () { return puppeteer_adapter_1.fromPuppeteer; } });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Minimal, implementation-neutral structural types for the slice of the
3
+ * Playwright API the solver actually uses.
4
+ *
5
+ * **Why these exist.** CaptchaKraken never launches a browser — you bring your
6
+ * own Playwright-compatible launcher and hand `solve()` a `Page`. To stay truly
7
+ * browser-agnostic the package depends on NO concrete browser implementation
8
+ * (not `playwright`, not `patchright`, not `camoufox-js`). Typing the public API
9
+ * against any one of those packages' `Page` would (a) force that package into
10
+ * consumers' trees and (b) break across version skew — e.g. `patchright` pins
11
+ * its own forked, older core whose `Page` is missing the newest `playwright-core`
12
+ * methods, so a `patchright` page won't structurally match a `playwright-core`
13
+ * page and vice-versa.
14
+ *
15
+ * Instead we declare a duck-typed surface covering only what the solver calls.
16
+ * Every real Playwright `Page` / `Frame` / `ElementHandle` — from vanilla
17
+ * `playwright`, `patchright`, `camoufox-js`, or anything else — structurally
18
+ * satisfies these, regardless of which Playwright version it was built against.
19
+ *
20
+ * Keep these in sync with `solver.ts`: if the solver starts calling a new
21
+ * Playwright method, add it here. The set is intentionally small and stable —
22
+ * these are long-standing Playwright primitives, not bleeding-edge additions.
23
+ */
24
+ /** `{ x, y, width, height }` in CSS pixels — the shape `boundingBox()` returns. */
25
+ export interface BoundingBoxRect {
26
+ x: number;
27
+ y: number;
28
+ width: number;
29
+ height: number;
30
+ }
31
+ /** `{ width, height }` — the shape `viewportSize()` returns. */
32
+ export interface ViewportSize {
33
+ width: number;
34
+ height: number;
35
+ }
36
+ /**
37
+ * Structural subset of Playwright's `ElementHandle`. Returned by `Page.$` /
38
+ * `Frame.$` / `Page.waitForSelector` and accepted by the solver as the captcha
39
+ * element / verify button / iframe handle.
40
+ */
41
+ export interface PlaywrightElementHandle {
42
+ /** Screenshot just this element to a PNG file (the only option the solver passes). */
43
+ screenshot(options?: {
44
+ path?: string;
45
+ }): Promise<Buffer>;
46
+ /** The content document of an `<iframe>` element handle, or null if not a frame. */
47
+ contentFrame(): Promise<PlaywrightFrame | null>;
48
+ /** Element box in page CSS pixels, or null if not rendered. */
49
+ boundingBox(): Promise<BoundingBoxRect | null>;
50
+ /** Scroll the element into view if it isn't already. */
51
+ scrollIntoViewIfNeeded(options?: {
52
+ timeout?: number;
53
+ }): Promise<void>;
54
+ /** Read an attribute, or null if absent. */
55
+ getAttribute(name: string): Promise<string | null>;
56
+ /** Whether the element is visible. */
57
+ isVisible(): Promise<boolean>;
58
+ /** The element's text content, or null. */
59
+ textContent(): Promise<string | null>;
60
+ }
61
+ /**
62
+ * Structural subset of Playwright's `Frame` (an iframe's content document).
63
+ */
64
+ export interface PlaywrightFrame {
65
+ /** First matching element handle, or null. */
66
+ $(selector: string): Promise<PlaywrightElementHandle | null>;
67
+ /** Wait for a selector to reach the given state; resolves to the handle (or null). */
68
+ waitForSelector(selector: string, options?: {
69
+ state?: 'attached' | 'detached' | 'visible' | 'hidden';
70
+ timeout?: number;
71
+ }): Promise<PlaywrightElementHandle | null>;
72
+ /** Poll a predicate evaluated in the page until it returns truthy. */
73
+ waitForFunction(pageFunction: Function | string, arg?: any, options?: {
74
+ timeout?: number;
75
+ polling?: number | 'raf';
76
+ }): Promise<unknown>;
77
+ }
78
+ /**
79
+ * Structural subset of Playwright's `Page` — exactly the members `solve()` and
80
+ * its helpers call. Any real Playwright `Page` satisfies this.
81
+ */
82
+ export interface PlaywrightPage {
83
+ /** Low-level mouse control used to drive human-like trajectories and clicks. */
84
+ mouse: {
85
+ move(x: number, y: number, options?: {
86
+ steps?: number;
87
+ }): Promise<void>;
88
+ down(options?: {
89
+ button?: 'left' | 'right' | 'middle';
90
+ clickCount?: number;
91
+ }): Promise<void>;
92
+ up(options?: {
93
+ button?: 'left' | 'right' | 'middle';
94
+ clickCount?: number;
95
+ }): Promise<void>;
96
+ };
97
+ /** Sleep `timeout` ms (Playwright's own timer). */
98
+ waitForTimeout(timeout: number): Promise<void>;
99
+ /** Wait for a selector to reach the given state; resolves to the handle (or null). */
100
+ waitForSelector(selector: string, options?: {
101
+ state?: 'attached' | 'detached' | 'visible' | 'hidden';
102
+ timeout?: number;
103
+ }): Promise<PlaywrightElementHandle | null>;
104
+ /** Current viewport size, or null if not set. */
105
+ viewportSize(): ViewportSize | null;
106
+ /** First matching element handle, or null. */
107
+ $(selector: string): Promise<PlaywrightElementHandle | null>;
108
+ /** All matching element handles. */
109
+ $$(selector: string): Promise<PlaywrightElementHandle[]>;
110
+ /** Run `pageFunction` against the first matching element, in the page context. */
111
+ $eval<R>(selector: string, pageFunction: (element: Element) => R, arg?: any): Promise<R>;
112
+ }
113
+ /**
114
+ * Public alias. This is the type the solver's `solve(page)` accepts — kept under
115
+ * a friendly name so consumers can annotate against it. It is the
116
+ * implementation-neutral Playwright `Page`; pass a page from whichever
117
+ * Playwright-compatible launcher you prefer.
118
+ */
119
+ export type Page = PlaywrightPage;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal, implementation-neutral structural types for the slice of the
4
+ * Playwright API the solver actually uses.
5
+ *
6
+ * **Why these exist.** CaptchaKraken never launches a browser — you bring your
7
+ * own Playwright-compatible launcher and hand `solve()` a `Page`. To stay truly
8
+ * browser-agnostic the package depends on NO concrete browser implementation
9
+ * (not `playwright`, not `patchright`, not `camoufox-js`). Typing the public API
10
+ * against any one of those packages' `Page` would (a) force that package into
11
+ * consumers' trees and (b) break across version skew — e.g. `patchright` pins
12
+ * its own forked, older core whose `Page` is missing the newest `playwright-core`
13
+ * methods, so a `patchright` page won't structurally match a `playwright-core`
14
+ * page and vice-versa.
15
+ *
16
+ * Instead we declare a duck-typed surface covering only what the solver calls.
17
+ * Every real Playwright `Page` / `Frame` / `ElementHandle` — from vanilla
18
+ * `playwright`, `patchright`, `camoufox-js`, or anything else — structurally
19
+ * satisfies these, regardless of which Playwright version it was built against.
20
+ *
21
+ * Keep these in sync with `solver.ts`: if the solver starts calling a new
22
+ * Playwright method, add it here. The set is intentionally small and stable —
23
+ * these are long-standing Playwright primitives, not bleeding-edge additions.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Puppeteer → CaptchaKraken adapter.
3
+ *
4
+ * The solver speaks the Playwright API surface (see playwright-types.ts). Puppeteer
5
+ * is ~95% the same shape but differs in a handful of method names/options. Rather
6
+ * than couple the solver to either library, this thin adapter wraps a Puppeteer
7
+ * `Page` (and, lazily, the `Frame`/`ElementHandle` objects it hands back) so it
8
+ * satisfies the structural `PlaywrightPage` interface the solver consumes.
9
+ *
10
+ * The deltas it bridges (verified against Puppeteer 24.x):
11
+ * - `page.viewportSize()` → `page.viewport()` (same `{width,height}` shape)
12
+ * - `page.waitForTimeout(ms)` → removed in modern Puppeteer; use a timer
13
+ * - `*.waitForSelector(sel, {state}) → Puppeteer uses `{visible}/{hidden}`
14
+ * - `handle.getAttribute(name)` → `handle.evaluate((el,n)=>el.getAttribute(n), name)`
15
+ * - `handle.textContent()` → `handle.evaluate(el=>el.textContent)`
16
+ * - `handle.scrollIntoViewIfNeeded()`→ `handle.scrollIntoView()`
17
+ * Everything else (`$`, `$$`, `$eval`, `waitForFunction`, `mouse.*`,
18
+ * `screenshot({path})`, `boundingBox`, `contentFrame`, `isVisible`) is
19
+ * call-compatible and passed straight through.
20
+ *
21
+ * Usage:
22
+ * ```typescript
23
+ * import puppeteer from 'puppeteer';
24
+ * import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
25
+ *
26
+ * const browser = await puppeteer.launch({ headless: false });
27
+ * const page = await browser.newPage();
28
+ * await page.goto('https://www.google.com/recaptcha/api2/demo');
29
+ *
30
+ * const solver = new CaptchaKrakenSolver();
31
+ * await solver.solve(fromPuppeteer(page));
32
+ * await browser.close();
33
+ * ```
34
+ */
35
+ import { PlaywrightPage, BoundingBoxRect, ViewportSize } from './playwright-types';
36
+ interface PuppeteerElementHandle {
37
+ screenshot(options?: {
38
+ path?: string;
39
+ }): Promise<unknown>;
40
+ contentFrame(): Promise<PuppeteerFrame | null>;
41
+ boundingBox(): Promise<BoundingBoxRect | null>;
42
+ scrollIntoView(): Promise<void>;
43
+ isVisible(): Promise<boolean>;
44
+ evaluate(pageFunction: (el: any, ...args: any[]) => any, ...args: any[]): Promise<any>;
45
+ }
46
+ interface PuppeteerFrame {
47
+ $(selector: string): Promise<PuppeteerElementHandle | null>;
48
+ waitForSelector(selector: string, options?: any): Promise<PuppeteerElementHandle | null>;
49
+ waitForFunction(pageFunction: Function | string, options?: any, ...args: any[]): Promise<unknown>;
50
+ }
51
+ interface PuppeteerPage {
52
+ mouse: {
53
+ move(x: number, y: number, options?: {
54
+ steps?: number;
55
+ }): Promise<void>;
56
+ down(options?: any): Promise<void>;
57
+ up(options?: any): Promise<void>;
58
+ };
59
+ waitForSelector(selector: string, options?: any): Promise<PuppeteerElementHandle | null>;
60
+ viewport(): ViewportSize | null;
61
+ $(selector: string): Promise<PuppeteerElementHandle | null>;
62
+ $$(selector: string): Promise<PuppeteerElementHandle[]>;
63
+ $eval(selector: string, pageFunction: (element: Element) => any, ...args: any[]): Promise<any>;
64
+ }
65
+ /**
66
+ * Wrap a Puppeteer `Page` so it satisfies the solver's structural Playwright
67
+ * `Page`. Pass the result to `solver.solve(...)`.
68
+ */
69
+ export declare function fromPuppeteer(page: PuppeteerPage): PlaywrightPage;
70
+ export {};
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Puppeteer → CaptchaKraken adapter.
4
+ *
5
+ * The solver speaks the Playwright API surface (see playwright-types.ts). Puppeteer
6
+ * is ~95% the same shape but differs in a handful of method names/options. Rather
7
+ * than couple the solver to either library, this thin adapter wraps a Puppeteer
8
+ * `Page` (and, lazily, the `Frame`/`ElementHandle` objects it hands back) so it
9
+ * satisfies the structural `PlaywrightPage` interface the solver consumes.
10
+ *
11
+ * The deltas it bridges (verified against Puppeteer 24.x):
12
+ * - `page.viewportSize()` → `page.viewport()` (same `{width,height}` shape)
13
+ * - `page.waitForTimeout(ms)` → removed in modern Puppeteer; use a timer
14
+ * - `*.waitForSelector(sel, {state}) → Puppeteer uses `{visible}/{hidden}`
15
+ * - `handle.getAttribute(name)` → `handle.evaluate((el,n)=>el.getAttribute(n), name)`
16
+ * - `handle.textContent()` → `handle.evaluate(el=>el.textContent)`
17
+ * - `handle.scrollIntoViewIfNeeded()`→ `handle.scrollIntoView()`
18
+ * Everything else (`$`, `$$`, `$eval`, `waitForFunction`, `mouse.*`,
19
+ * `screenshot({path})`, `boundingBox`, `contentFrame`, `isVisible`) is
20
+ * call-compatible and passed straight through.
21
+ *
22
+ * Usage:
23
+ * ```typescript
24
+ * import puppeteer from 'puppeteer';
25
+ * import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
26
+ *
27
+ * const browser = await puppeteer.launch({ headless: false });
28
+ * const page = await browser.newPage();
29
+ * await page.goto('https://www.google.com/recaptcha/api2/demo');
30
+ *
31
+ * const solver = new CaptchaKrakenSolver();
32
+ * await solver.solve(fromPuppeteer(page));
33
+ * await browser.close();
34
+ * ```
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.fromPuppeteer = fromPuppeteer;
38
+ /** Translate Playwright `{state}` selector options to Puppeteer `{visible}/{hidden}`. */
39
+ function toPuppeteerSelectorOptions(options) {
40
+ if (!options)
41
+ return undefined;
42
+ const { state, timeout } = options;
43
+ const out = {};
44
+ if (timeout !== undefined)
45
+ out.timeout = timeout;
46
+ if (state === 'visible')
47
+ out.visible = true;
48
+ else if (state === 'hidden')
49
+ out.hidden = true;
50
+ // 'attached'/'detached' have no direct Puppeteer flag — default wait (attached)
51
+ // is the closest match, so we pass no visibility flag.
52
+ return out;
53
+ }
54
+ function wrapHandle(h) {
55
+ if (!h)
56
+ return null;
57
+ return {
58
+ screenshot: (options) => h.screenshot(options),
59
+ contentFrame: async () => wrapFrame(await h.contentFrame()),
60
+ boundingBox: () => h.boundingBox(),
61
+ scrollIntoViewIfNeeded: () => h.scrollIntoView(),
62
+ getAttribute: (name) => h.evaluate((el, n) => el.getAttribute(n), name),
63
+ isVisible: () => h.isVisible(),
64
+ textContent: () => h.evaluate((el) => el.textContent),
65
+ };
66
+ }
67
+ function wrapFrame(f) {
68
+ if (!f)
69
+ return null;
70
+ return {
71
+ $: async (selector) => wrapHandle(await f.$(selector)),
72
+ waitForSelector: async (selector, options) => wrapHandle(await f.waitForSelector(selector, toPuppeteerSelectorOptions(options))),
73
+ waitForFunction: (pageFunction, arg, options) =>
74
+ // Playwright: (fn, arg, {timeout,polling}); Puppeteer: (fn, {timeout,polling}, ...args).
75
+ f.waitForFunction(pageFunction, options, arg),
76
+ };
77
+ }
78
+ /**
79
+ * Wrap a Puppeteer `Page` so it satisfies the solver's structural Playwright
80
+ * `Page`. Pass the result to `solver.solve(...)`.
81
+ */
82
+ function fromPuppeteer(page) {
83
+ return {
84
+ mouse: {
85
+ move: (x, y, options) => page.mouse.move(x, y, options),
86
+ down: (options) => page.mouse.down(options),
87
+ up: (options) => page.mouse.up(options),
88
+ },
89
+ waitForTimeout: (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)),
90
+ waitForSelector: async (selector, options) => wrapHandle(await page.waitForSelector(selector, toPuppeteerSelectorOptions(options))),
91
+ viewportSize: () => page.viewport(),
92
+ $: async (selector) => wrapHandle(await page.$(selector)),
93
+ $$: async (selector) => (await page.$$(selector)).map((h) => wrapHandle(h)).filter(Boolean),
94
+ $eval: (selector, pageFunction, arg) => page.$eval(selector, pageFunction, arg),
95
+ };
96
+ }