camou 0.2.0 → 0.3.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/CHANGELOG.md +18 -0
- package/README.md +50 -13
- package/dist/api.d.ts +25 -1
- package/dist/api.js +68 -3
- package/dist/api.js.map +1 -1
- package/dist/browser/manager.d.ts +58 -1
- package/dist/browser/manager.js +134 -3
- package/dist/browser/manager.js.map +1 -1
- package/dist/camoufox/launcher.d.ts +10 -1
- package/dist/camoufox/launcher.js +34 -23
- package/dist/camoufox/launcher.js.map +1 -1
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +17 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output.js +80 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/program.js +80 -3
- package/dist/cli/program.js.map +1 -1
- package/dist/daemon/router.js +22 -0
- package/dist/daemon/router.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/protocol.d.ts +638 -16
- package/dist/ipc/protocol.js +59 -1
- package/dist/ipc/protocol.js.map +1 -1
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is loosely based on Keep a Changelog and uses semantic versioning.
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-03-16
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added Linux/macOS GitHub Actions CI for test, build, and package validation.
|
|
12
|
+
- Added compatibility-matrix workflows and local scripts for probing Camoufox vs `playwright-core` compatibility.
|
|
13
|
+
- Added broader browser automation commands including navigation, hover, type, check/uncheck, select, scroll, `get value`, and richer wait modes.
|
|
14
|
+
- Added higher-level Node API wrappers including `Camoufox`, `AsyncCamoufox`, and `resolveCamoufoxLaunchSpec()`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Updated `README.md`, skill docs, and compatibility docs to reflect the first-class Node API and expanded command surface.
|
|
19
|
+
- Improved CI reliability by removing slow spawned `tsx` subprocesses from the CLI JSON tests.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed the macOS installer integration test to use platform-aware asset names and executable paths.
|
|
24
|
+
|
|
7
25
|
## [0.2.0] - 2026-03-15
|
|
8
26
|
|
|
9
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Camou
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Camou is a local-first CLI and background daemon for driving [Camoufox](https://github.com/daijro/camoufox) through Playwright, without depending on the Camoufox Python SDK.
|
|
4
4
|
|
|
5
5
|
- npm package: `camou`
|
|
6
6
|
- installed command: `camou`
|
|
7
|
-
- project/repo name:
|
|
7
|
+
- project/repo name: camoucli
|
|
8
8
|
|
|
9
9
|
Camou is built for agent-style browser workflows:
|
|
10
10
|
|
|
@@ -113,15 +113,14 @@ Camou can also be used as a Node library, not just a CLI.
|
|
|
113
113
|
The programmatic API is Playwright-based: it launches Camoufox for you and gives you a real Playwright `BrowserContext`, similar in spirit to the Camoufox Python wrapper.
|
|
114
114
|
|
|
115
115
|
```ts
|
|
116
|
-
import {
|
|
116
|
+
import { Camoufox } from 'camou';
|
|
117
117
|
|
|
118
|
-
const camou = await
|
|
118
|
+
const camou = await Camoufox.launch({
|
|
119
119
|
session: 'script',
|
|
120
120
|
headless: false,
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
const page = await camou.
|
|
124
|
-
await page.goto('https://example.com');
|
|
123
|
+
const page = await camou.open('https://example.com');
|
|
125
124
|
console.log(await page.title());
|
|
126
125
|
|
|
127
126
|
await camou.close();
|
|
@@ -130,19 +129,23 @@ await camou.close();
|
|
|
130
129
|
If you prefer a scoped helper:
|
|
131
130
|
|
|
132
131
|
```ts
|
|
133
|
-
import {
|
|
132
|
+
import { Camoufox } from 'camou';
|
|
134
133
|
|
|
135
|
-
await
|
|
136
|
-
const page = await
|
|
137
|
-
await page.goto('https://example.com');
|
|
134
|
+
await Camoufox.with({ session: 'script' }, async (camou) => {
|
|
135
|
+
const page = await camou.open('https://example.com');
|
|
138
136
|
console.log(await page.title());
|
|
139
137
|
});
|
|
140
138
|
```
|
|
141
139
|
|
|
142
140
|
Useful exported helpers include:
|
|
143
141
|
|
|
142
|
+
- `Camoufox.launch()`
|
|
143
|
+
- `Camoufox.launchContext()`
|
|
144
|
+
- `Camoufox.with()`
|
|
145
|
+
- `AsyncCamoufox`
|
|
144
146
|
- `launchCamoufox()`
|
|
145
147
|
- `launchCamoufoxContext()`
|
|
148
|
+
- `resolveCamoufoxLaunchSpec()`
|
|
146
149
|
- `withCamoufox()`
|
|
147
150
|
- `installCamoufox()`
|
|
148
151
|
- `listInstalledBrowsers()`
|
|
@@ -290,15 +293,26 @@ camou doctor
|
|
|
290
293
|
|
|
291
294
|
```bash
|
|
292
295
|
camou open <url>
|
|
296
|
+
camou back
|
|
297
|
+
camou forward
|
|
298
|
+
camou reload
|
|
293
299
|
camou snapshot [-i]
|
|
294
300
|
camou click <selectorOrRef>
|
|
301
|
+
camou hover <selectorOrRef>
|
|
295
302
|
camou fill <selectorOrRef> <text>
|
|
303
|
+
camou type <selectorOrRef> <text>
|
|
304
|
+
camou check <selectorOrRef>
|
|
305
|
+
camou uncheck <selectorOrRef>
|
|
306
|
+
camou select <selectorOrRef> <value>
|
|
296
307
|
camou press <key>
|
|
297
|
-
camou
|
|
308
|
+
camou scroll <direction> [amount]
|
|
309
|
+
camou scrollintoview <selectorOrRef>
|
|
310
|
+
camou wait [selectorOrRef] [--text <text>] [--load <state>]
|
|
298
311
|
camou screenshot [path]
|
|
299
312
|
camou get url
|
|
300
313
|
camou get title
|
|
301
314
|
camou get text <selectorOrRef>
|
|
315
|
+
camou get value <selectorOrRef>
|
|
302
316
|
```
|
|
303
317
|
|
|
304
318
|
### Sessions and tabs
|
|
@@ -332,6 +346,11 @@ Most browser commands support:
|
|
|
332
346
|
- `--json`
|
|
333
347
|
- `--verbose`
|
|
334
348
|
|
|
349
|
+
`wait` also supports:
|
|
350
|
+
|
|
351
|
+
- `--text <text>`
|
|
352
|
+
- `--load <domcontentloaded|load|networkidle>`
|
|
353
|
+
|
|
335
354
|
## Presets
|
|
336
355
|
|
|
337
356
|
Built-in presets give you a small layer of tested ergonomics on top of raw config and prefs JSON.
|
|
@@ -396,6 +415,14 @@ Current local verification with `playwright-core` `1.51.1`:
|
|
|
396
415
|
| `135.0.1-beta.24` | launches | smoke-tested successfully |
|
|
397
416
|
| `135.0.1-beta.23` | incompatible | `Browser.setContrast` is not supported |
|
|
398
417
|
|
|
418
|
+
The repo now also includes:
|
|
419
|
+
|
|
420
|
+
- Linux/macOS CI in `.github/workflows/ci.yml`
|
|
421
|
+
- a workflow-driven compatibility probe in `.github/workflows/compatibility-matrix.yml`
|
|
422
|
+
- local scripts to generate compatibility reports and markdown summaries
|
|
423
|
+
|
|
424
|
+
See `docs/compatibility-matrix.md` for the workflow and local tooling.
|
|
425
|
+
|
|
399
426
|
## Storage Layout
|
|
400
427
|
|
|
401
428
|
Camou keeps its own runtime state and profiles, but stores browser binaries in the shared Camoufox cache layout when possible.
|
|
@@ -430,9 +457,19 @@ npm run dev -- --help
|
|
|
430
457
|
npm run dev:daemon
|
|
431
458
|
```
|
|
432
459
|
|
|
460
|
+
Compatibility tooling:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# produce a raw compatibility report JSON
|
|
464
|
+
node scripts/run-compatibility-report.mjs --output compatibility-report.json
|
|
465
|
+
|
|
466
|
+
# turn one or more reports into a markdown table
|
|
467
|
+
node scripts/generate-compatibility-matrix.mjs compatibility-report.json
|
|
468
|
+
```
|
|
469
|
+
|
|
433
470
|
## Acknowledgements
|
|
434
471
|
|
|
435
|
-
|
|
472
|
+
Camou learned a lot from these projects:
|
|
436
473
|
|
|
437
474
|
- [vercel-labs/agent-browser](https://github.com/vercel-labs/agent-browser) for the agent-oriented command workflow and skill ecosystem patterns
|
|
438
475
|
- [BUNotesAI/agent-browser-session](https://github.com/BUNotesAI/agent-browser-session) for persistent-session and named-tab ergonomics
|
package/dist/api.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import type { BrowserContext, Page } from 'playwright-core';
|
|
2
2
|
import type { LaunchInput, ResolvedLaunchConfig } from './camoufox/config.js';
|
|
3
|
+
import { type PreparedPersistentCamoufoxLaunch } from './camoufox/launcher.js';
|
|
3
4
|
import { type CamoucliPaths } from './state/paths.js';
|
|
4
5
|
export interface LaunchCamoufoxOptions extends LaunchInput {
|
|
5
6
|
session?: string | undefined;
|
|
6
7
|
paths?: CamoucliPaths | undefined;
|
|
7
8
|
verbose?: boolean | undefined;
|
|
8
9
|
}
|
|
10
|
+
export interface ResolvedCamoufoxLaunchSpec {
|
|
11
|
+
sessionName: string;
|
|
12
|
+
browserVersion: string;
|
|
13
|
+
executablePath: string;
|
|
14
|
+
profileDir: string;
|
|
15
|
+
downloadsDir: string;
|
|
16
|
+
artifactsDir: string;
|
|
17
|
+
resolvedConfig: ResolvedLaunchConfig;
|
|
18
|
+
userDataDir: string;
|
|
19
|
+
launchOptions: PreparedPersistentCamoufoxLaunch['launchOptions'];
|
|
20
|
+
}
|
|
9
21
|
export declare class CamoufoxSession {
|
|
10
22
|
readonly context: BrowserContext;
|
|
11
23
|
readonly sessionName: string;
|
|
@@ -25,10 +37,22 @@ export declare class CamoufoxSession {
|
|
|
25
37
|
artifactsDir: string;
|
|
26
38
|
resolvedConfig: ResolvedLaunchConfig;
|
|
27
39
|
});
|
|
28
|
-
newPage(): Promise<Page>;
|
|
40
|
+
newPage(url?: string): Promise<Page>;
|
|
29
41
|
pages(): Page[];
|
|
42
|
+
firstPage(): Page | undefined;
|
|
43
|
+
ensurePage(): Promise<Page>;
|
|
44
|
+
open(url: string, page?: Page): Promise<Page>;
|
|
30
45
|
close(): Promise<void>;
|
|
31
46
|
}
|
|
47
|
+
export declare class Camoufox extends CamoufoxSession {
|
|
48
|
+
static launch(options?: LaunchCamoufoxOptions): Promise<Camoufox>;
|
|
49
|
+
static launchContext(options?: LaunchCamoufoxOptions): Promise<BrowserContext>;
|
|
50
|
+
static resolveLaunch(options?: LaunchCamoufoxOptions): Promise<ResolvedCamoufoxLaunchSpec>;
|
|
51
|
+
static with<T>(options: LaunchCamoufoxOptions, callback: (browser: Camoufox) => Promise<T> | T): Promise<T>;
|
|
52
|
+
}
|
|
53
|
+
export declare class AsyncCamoufox extends Camoufox {
|
|
54
|
+
}
|
|
32
55
|
export declare function launchCamoufox(options?: LaunchCamoufoxOptions): Promise<CamoufoxSession>;
|
|
56
|
+
export declare function resolveCamoufoxLaunchSpec(options?: LaunchCamoufoxOptions): Promise<ResolvedCamoufoxLaunchSpec>;
|
|
33
57
|
export declare function launchCamoufoxContext(options?: LaunchCamoufoxOptions): Promise<BrowserContext>;
|
|
34
58
|
export declare function withCamoufox<T>(options: LaunchCamoufoxOptions, callback: (session: CamoufoxSession) => Promise<T> | T): Promise<T>;
|
package/dist/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { launchPersistentCamoufox } from './camoufox/launcher.js';
|
|
1
|
+
import { launchPersistentCamoufox, preparePersistentCamoufoxLaunch } from './camoufox/launcher.js';
|
|
2
2
|
import { ensureBasePaths, getCamoucliPaths } from './state/paths.js';
|
|
3
3
|
import { Logger } from './util/log.js';
|
|
4
4
|
export class CamoufoxSession {
|
|
@@ -20,16 +20,63 @@ export class CamoufoxSession {
|
|
|
20
20
|
this.artifactsDir = input.artifactsDir;
|
|
21
21
|
this.resolvedConfig = input.resolvedConfig;
|
|
22
22
|
}
|
|
23
|
-
async newPage() {
|
|
24
|
-
|
|
23
|
+
async newPage(url) {
|
|
24
|
+
const page = await this.context.newPage();
|
|
25
|
+
if (url) {
|
|
26
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
27
|
+
}
|
|
28
|
+
return page;
|
|
25
29
|
}
|
|
26
30
|
pages() {
|
|
27
31
|
return this.context.pages();
|
|
28
32
|
}
|
|
33
|
+
firstPage() {
|
|
34
|
+
return this.pages()[0];
|
|
35
|
+
}
|
|
36
|
+
async ensurePage() {
|
|
37
|
+
return this.firstPage() ?? this.context.newPage();
|
|
38
|
+
}
|
|
39
|
+
async open(url, page) {
|
|
40
|
+
const targetPage = page ?? await this.ensurePage();
|
|
41
|
+
await targetPage.goto(url, { waitUntil: 'domcontentloaded' });
|
|
42
|
+
return targetPage;
|
|
43
|
+
}
|
|
29
44
|
async close() {
|
|
30
45
|
await this.context.close();
|
|
31
46
|
}
|
|
32
47
|
}
|
|
48
|
+
export class Camoufox extends CamoufoxSession {
|
|
49
|
+
static async launch(options = {}) {
|
|
50
|
+
const session = await launchCamoufox(options);
|
|
51
|
+
return new Camoufox({
|
|
52
|
+
context: session.context,
|
|
53
|
+
sessionName: session.sessionName,
|
|
54
|
+
browserVersion: session.browserVersion,
|
|
55
|
+
executablePath: session.executablePath,
|
|
56
|
+
profileDir: session.profileDir,
|
|
57
|
+
downloadsDir: session.downloadsDir,
|
|
58
|
+
artifactsDir: session.artifactsDir,
|
|
59
|
+
resolvedConfig: session.resolvedConfig,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
static async launchContext(options = {}) {
|
|
63
|
+
return launchCamoufoxContext(options);
|
|
64
|
+
}
|
|
65
|
+
static async resolveLaunch(options = {}) {
|
|
66
|
+
return resolveCamoufoxLaunchSpec(options);
|
|
67
|
+
}
|
|
68
|
+
static async with(options, callback) {
|
|
69
|
+
const browser = await Camoufox.launch(options);
|
|
70
|
+
try {
|
|
71
|
+
return await callback(browser);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
await browser.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class AsyncCamoufox extends Camoufox {
|
|
79
|
+
}
|
|
33
80
|
function createApiLogger(verbose = false) {
|
|
34
81
|
if (!verbose) {
|
|
35
82
|
return undefined;
|
|
@@ -57,6 +104,24 @@ export async function launchCamoufox(options = {}) {
|
|
|
57
104
|
resolvedConfig: launched.resolvedConfig,
|
|
58
105
|
});
|
|
59
106
|
}
|
|
107
|
+
export async function resolveCamoufoxLaunchSpec(options = {}) {
|
|
108
|
+
const sessionName = options.session ?? 'default';
|
|
109
|
+
const paths = options.paths ?? getCamoucliPaths();
|
|
110
|
+
await ensureBasePaths(paths);
|
|
111
|
+
const logger = createApiLogger(options.verbose);
|
|
112
|
+
const prepared = await preparePersistentCamoufoxLaunch(paths, sessionName, options, logger);
|
|
113
|
+
return {
|
|
114
|
+
sessionName,
|
|
115
|
+
browserVersion: prepared.browserVersion,
|
|
116
|
+
executablePath: prepared.installPath,
|
|
117
|
+
profileDir: prepared.sessionPaths.profileDir,
|
|
118
|
+
downloadsDir: prepared.sessionPaths.downloadsDir,
|
|
119
|
+
artifactsDir: prepared.sessionPaths.artifactsDir,
|
|
120
|
+
resolvedConfig: prepared.resolvedConfig,
|
|
121
|
+
userDataDir: prepared.userDataDir,
|
|
122
|
+
launchOptions: prepared.launchOptions,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
60
125
|
export async function launchCamoufoxContext(options = {}) {
|
|
61
126
|
const session = await launchCamoufox(options);
|
|
62
127
|
return session.context;
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,18 @@ export declare class BrowserManager {
|
|
|
22
22
|
tabName: string;
|
|
23
23
|
url: string;
|
|
24
24
|
}): Promise<Record<string, unknown>>;
|
|
25
|
+
back(input: LaunchInput & {
|
|
26
|
+
session: string;
|
|
27
|
+
tabName: string;
|
|
28
|
+
}): Promise<Record<string, unknown>>;
|
|
29
|
+
forward(input: LaunchInput & {
|
|
30
|
+
session: string;
|
|
31
|
+
tabName: string;
|
|
32
|
+
}): Promise<Record<string, unknown>>;
|
|
33
|
+
reload(input: LaunchInput & {
|
|
34
|
+
session: string;
|
|
35
|
+
tabName: string;
|
|
36
|
+
}): Promise<Record<string, unknown>>;
|
|
25
37
|
snapshot(input: LaunchInput & {
|
|
26
38
|
session: string;
|
|
27
39
|
tabName: string;
|
|
@@ -32,17 +44,55 @@ export declare class BrowserManager {
|
|
|
32
44
|
tabName: string;
|
|
33
45
|
target: string;
|
|
34
46
|
}): Promise<Record<string, unknown>>;
|
|
47
|
+
hover(input: LaunchInput & {
|
|
48
|
+
session: string;
|
|
49
|
+
tabName: string;
|
|
50
|
+
target: string;
|
|
51
|
+
}): Promise<Record<string, unknown>>;
|
|
35
52
|
fill(input: LaunchInput & {
|
|
36
53
|
session: string;
|
|
37
54
|
tabName: string;
|
|
38
55
|
target: string;
|
|
39
56
|
text: string;
|
|
40
57
|
}): Promise<Record<string, unknown>>;
|
|
58
|
+
type(input: LaunchInput & {
|
|
59
|
+
session: string;
|
|
60
|
+
tabName: string;
|
|
61
|
+
target: string;
|
|
62
|
+
text: string;
|
|
63
|
+
}): Promise<Record<string, unknown>>;
|
|
64
|
+
check(input: LaunchInput & {
|
|
65
|
+
session: string;
|
|
66
|
+
tabName: string;
|
|
67
|
+
target: string;
|
|
68
|
+
}): Promise<Record<string, unknown>>;
|
|
69
|
+
uncheck(input: LaunchInput & {
|
|
70
|
+
session: string;
|
|
71
|
+
tabName: string;
|
|
72
|
+
target: string;
|
|
73
|
+
}): Promise<Record<string, unknown>>;
|
|
74
|
+
select(input: LaunchInput & {
|
|
75
|
+
session: string;
|
|
76
|
+
tabName: string;
|
|
77
|
+
target: string;
|
|
78
|
+
value: string;
|
|
79
|
+
}): Promise<Record<string, unknown>>;
|
|
41
80
|
press(input: LaunchInput & {
|
|
42
81
|
session: string;
|
|
43
82
|
tabName: string;
|
|
44
83
|
key: string;
|
|
45
84
|
}): Promise<Record<string, unknown>>;
|
|
85
|
+
scroll(input: LaunchInput & {
|
|
86
|
+
session: string;
|
|
87
|
+
tabName: string;
|
|
88
|
+
direction: 'up' | 'down' | 'left' | 'right';
|
|
89
|
+
amount?: number | undefined;
|
|
90
|
+
}): Promise<Record<string, unknown>>;
|
|
91
|
+
scrollIntoView(input: LaunchInput & {
|
|
92
|
+
session: string;
|
|
93
|
+
tabName: string;
|
|
94
|
+
target: string;
|
|
95
|
+
}): Promise<Record<string, unknown>>;
|
|
46
96
|
screenshot(input: LaunchInput & {
|
|
47
97
|
session: string;
|
|
48
98
|
tabName: string;
|
|
@@ -61,10 +111,17 @@ export declare class BrowserManager {
|
|
|
61
111
|
tabName: string;
|
|
62
112
|
target: string;
|
|
63
113
|
}): Promise<Record<string, unknown>>;
|
|
64
|
-
|
|
114
|
+
getValue(input: LaunchInput & {
|
|
65
115
|
session: string;
|
|
66
116
|
tabName: string;
|
|
67
117
|
target: string;
|
|
118
|
+
}): Promise<Record<string, unknown>>;
|
|
119
|
+
wait(input: LaunchInput & {
|
|
120
|
+
session: string;
|
|
121
|
+
tabName: string;
|
|
122
|
+
target?: string | undefined;
|
|
123
|
+
text?: string | undefined;
|
|
124
|
+
loadState?: 'domcontentloaded' | 'load' | 'networkidle' | undefined;
|
|
68
125
|
timeoutMs?: number | undefined;
|
|
69
126
|
}): Promise<Record<string, unknown>>;
|
|
70
127
|
listTabs(sessionName: string): Promise<Array<Record<string, unknown>>>;
|
package/dist/browser/manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { launchPersistentCamoufox } from '../camoufox/launcher.js';
|
|
3
|
-
import { SessionError } from '../util/errors.js';
|
|
3
|
+
import { SessionError, ValidationError } from '../util/errors.js';
|
|
4
4
|
import { locatorForTarget } from './actions.js';
|
|
5
5
|
import { clearSnapshotRefs, takeSnapshot } from './snapshot.js';
|
|
6
6
|
import { createTabRuntime } from './tabs.js';
|
|
@@ -51,6 +51,36 @@ export class BrowserManager {
|
|
|
51
51
|
title: await tab.page.title(),
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
async back(input) {
|
|
55
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
56
|
+
await tab.page.goBack({ waitUntil: 'domcontentloaded' }).catch(() => null);
|
|
57
|
+
return {
|
|
58
|
+
sessionName: input.session,
|
|
59
|
+
tabName: tab.name,
|
|
60
|
+
url: tab.page.url(),
|
|
61
|
+
title: await tab.page.title(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async forward(input) {
|
|
65
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
66
|
+
await tab.page.goForward({ waitUntil: 'domcontentloaded' }).catch(() => null);
|
|
67
|
+
return {
|
|
68
|
+
sessionName: input.session,
|
|
69
|
+
tabName: tab.name,
|
|
70
|
+
url: tab.page.url(),
|
|
71
|
+
title: await tab.page.title(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async reload(input) {
|
|
75
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
76
|
+
await tab.page.reload({ waitUntil: 'domcontentloaded' });
|
|
77
|
+
return {
|
|
78
|
+
sessionName: input.session,
|
|
79
|
+
tabName: tab.name,
|
|
80
|
+
url: tab.page.url(),
|
|
81
|
+
title: await tab.page.title(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
54
84
|
async snapshot(input) {
|
|
55
85
|
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
56
86
|
const result = await takeSnapshot(tab.page, input.interactive);
|
|
@@ -75,6 +105,15 @@ export class BrowserManager {
|
|
|
75
105
|
url: tab.page.url(),
|
|
76
106
|
};
|
|
77
107
|
}
|
|
108
|
+
async hover(input) {
|
|
109
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
110
|
+
await locatorForTarget(tab.page, tab, input.target).hover();
|
|
111
|
+
return {
|
|
112
|
+
sessionName: input.session,
|
|
113
|
+
tabName: tab.name,
|
|
114
|
+
target: input.target,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
78
117
|
async fill(input) {
|
|
79
118
|
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
80
119
|
await locatorForTarget(tab.page, tab, input.target).fill(input.text);
|
|
@@ -85,6 +124,46 @@ export class BrowserManager {
|
|
|
85
124
|
valueLength: input.text.length,
|
|
86
125
|
};
|
|
87
126
|
}
|
|
127
|
+
async type(input) {
|
|
128
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
129
|
+
await locatorForTarget(tab.page, tab, input.target).type(input.text);
|
|
130
|
+
return {
|
|
131
|
+
sessionName: input.session,
|
|
132
|
+
tabName: tab.name,
|
|
133
|
+
target: input.target,
|
|
134
|
+
valueLength: input.text.length,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async check(input) {
|
|
138
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
139
|
+
await locatorForTarget(tab.page, tab, input.target).check();
|
|
140
|
+
return {
|
|
141
|
+
sessionName: input.session,
|
|
142
|
+
tabName: tab.name,
|
|
143
|
+
target: input.target,
|
|
144
|
+
checked: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async uncheck(input) {
|
|
148
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
149
|
+
await locatorForTarget(tab.page, tab, input.target).uncheck();
|
|
150
|
+
return {
|
|
151
|
+
sessionName: input.session,
|
|
152
|
+
tabName: tab.name,
|
|
153
|
+
target: input.target,
|
|
154
|
+
checked: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async select(input) {
|
|
158
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
159
|
+
await locatorForTarget(tab.page, tab, input.target).selectOption(input.value);
|
|
160
|
+
return {
|
|
161
|
+
sessionName: input.session,
|
|
162
|
+
tabName: tab.name,
|
|
163
|
+
target: input.target,
|
|
164
|
+
value: input.value,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
88
167
|
async press(input) {
|
|
89
168
|
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
90
169
|
await tab.page.keyboard.press(input.key);
|
|
@@ -94,6 +173,34 @@ export class BrowserManager {
|
|
|
94
173
|
key: input.key,
|
|
95
174
|
};
|
|
96
175
|
}
|
|
176
|
+
async scroll(input) {
|
|
177
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
178
|
+
const amount = input.amount ?? 500;
|
|
179
|
+
const delta = input.direction === 'up'
|
|
180
|
+
? { x: 0, y: -amount }
|
|
181
|
+
: input.direction === 'down'
|
|
182
|
+
? { x: 0, y: amount }
|
|
183
|
+
: input.direction === 'left'
|
|
184
|
+
? { x: -amount, y: 0 }
|
|
185
|
+
: { x: amount, y: 0 };
|
|
186
|
+
await tab.page.mouse.wheel(delta.x, delta.y);
|
|
187
|
+
return {
|
|
188
|
+
sessionName: input.session,
|
|
189
|
+
tabName: tab.name,
|
|
190
|
+
direction: input.direction,
|
|
191
|
+
amount,
|
|
192
|
+
url: tab.page.url(),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async scrollIntoView(input) {
|
|
196
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
197
|
+
await locatorForTarget(tab.page, tab, input.target).scrollIntoViewIfNeeded();
|
|
198
|
+
return {
|
|
199
|
+
sessionName: input.session,
|
|
200
|
+
tabName: tab.name,
|
|
201
|
+
target: input.target,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
97
204
|
async screenshot(input) {
|
|
98
205
|
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
99
206
|
const session = await this.ensureSession(input.session, input);
|
|
@@ -131,14 +238,37 @@ export class BrowserManager {
|
|
|
131
238
|
text,
|
|
132
239
|
};
|
|
133
240
|
}
|
|
241
|
+
async getValue(input) {
|
|
242
|
+
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
243
|
+
const value = await locatorForTarget(tab.page, tab, input.target).inputValue();
|
|
244
|
+
return {
|
|
245
|
+
sessionName: input.session,
|
|
246
|
+
tabName: tab.name,
|
|
247
|
+
target: input.target,
|
|
248
|
+
value,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
134
251
|
async wait(input) {
|
|
135
252
|
const tab = await this.ensureTab(input.session, input.tabName, input);
|
|
253
|
+
if (!input.target && !input.text && !input.loadState) {
|
|
254
|
+
throw new ValidationError('wait requires a target, --text value, or --load state.');
|
|
255
|
+
}
|
|
136
256
|
const waitOptions = input.timeoutMs ? { timeout: input.timeoutMs } : undefined;
|
|
137
|
-
|
|
257
|
+
if (input.target) {
|
|
258
|
+
await locatorForTarget(tab.page, tab, input.target).waitFor(waitOptions);
|
|
259
|
+
}
|
|
260
|
+
if (input.text) {
|
|
261
|
+
await tab.page.getByText(input.text).first().waitFor(waitOptions);
|
|
262
|
+
}
|
|
263
|
+
if (input.loadState) {
|
|
264
|
+
await tab.page.waitForLoadState(input.loadState, waitOptions);
|
|
265
|
+
}
|
|
138
266
|
return {
|
|
139
267
|
sessionName: input.session,
|
|
140
268
|
tabName: tab.name,
|
|
141
|
-
target: input.target,
|
|
269
|
+
...(input.target ? { target: input.target } : {}),
|
|
270
|
+
...(input.text ? { text: input.text } : {}),
|
|
271
|
+
...(input.loadState ? { loadState: input.loadState } : {}),
|
|
142
272
|
url: tab.page.url(),
|
|
143
273
|
};
|
|
144
274
|
}
|
|
@@ -187,6 +317,7 @@ export class BrowserManager {
|
|
|
187
317
|
async ensureSession(sessionName, input) {
|
|
188
318
|
const existing = this.sessions.get(sessionName);
|
|
189
319
|
if (existing) {
|
|
320
|
+
this.assertSessionCompatible(existing, input);
|
|
190
321
|
return existing;
|
|
191
322
|
}
|
|
192
323
|
const inFlight = this.startingSessions.get(sessionName);
|