cbrowser 18.69.3 โ 18.71.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/browser.d.ts +32 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +46 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +1465 -24
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts +10 -0
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +8 -0
- package/dist/cognitive/index.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +151 -3
- package/dist/daemon.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +67 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.d.ts +189 -0
- package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/capture-tools.js +667 -0
- package/dist/mcp-tools/base/capture-tools.js.map +1 -0
- package/dist/mcp-tools/base/gif-tools.d.ts +19 -0
- package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/gif-tools.js +45 -27
- package/dist/mcp-tools/base/gif-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.d.ts +2 -1
- package/dist/mcp-tools/base/index.d.ts.map +1 -1
- package/dist/mcp-tools/base/index.js +5 -1
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +11 -0
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/recording/auto-capture.d.ts +72 -0
- package/dist/recording/auto-capture.d.ts.map +1 -0
- package/dist/recording/auto-capture.js +155 -0
- package/dist/recording/auto-capture.js.map +1 -0
- package/dist/recording/contact-sheet.d.ts +65 -0
- package/dist/recording/contact-sheet.d.ts.map +1 -0
- package/dist/recording/contact-sheet.js +204 -0
- package/dist/recording/contact-sheet.js.map +1 -0
- package/dist/recording/encoders.d.ts +101 -0
- package/dist/recording/encoders.d.ts.map +1 -0
- package/dist/recording/encoders.js +304 -0
- package/dist/recording/encoders.js.map +1 -0
- package/dist/recording/engine.d.ts +263 -0
- package/dist/recording/engine.d.ts.map +1 -0
- package/dist/recording/engine.js +890 -0
- package/dist/recording/engine.js.map +1 -0
- package/dist/recording/ssim.d.ts +35 -0
- package/dist/recording/ssim.d.ts.map +1 -0
- package/dist/recording/ssim.js +113 -0
- package/dist/recording/ssim.js.map +1 -0
- package/dist/recording/timing.d.ts +124 -0
- package/dist/recording/timing.d.ts.map +1 -0
- package/dist/recording/timing.js +251 -0
- package/dist/recording/timing.js.map +1 -0
- package/dist/recording/types.d.ts +213 -0
- package/dist/recording/types.d.ts.map +1 -0
- package/dist/recording/types.js +171 -0
- package/dist/recording/types.js.map +1 -0
- package/dist/testing/nl-test-suite.d.ts +13 -1
- package/dist/testing/nl-test-suite.d.ts.map +1 -1
- package/dist/testing/nl-test-suite.js +91 -6
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/types.d.ts +25 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBrowser - Cognitive Browser Automation
|
|
3
|
+
* Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
|
|
4
|
+
* Learn more at https://cbrowser.ai - MIT License
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Recording manifest contract.
|
|
8
|
+
*
|
|
9
|
+
* The manifest is the single artifact that describes a recording run: how it
|
|
10
|
+
* was captured, what was captured, and every frame that made it into the
|
|
11
|
+
* output. Encoders, the CLI, and any downstream consumer read this file rather
|
|
12
|
+
* than re-deriving state from the frame directory.
|
|
13
|
+
*
|
|
14
|
+
* Schemas are zod v4; every exported TS type is inferred from its schema so the
|
|
15
|
+
* runtime contract and the compile-time contract cannot drift.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
/** Manifest format version emitted by this build. */
|
|
19
|
+
export declare const MANIFEST_VERSION = 1;
|
|
20
|
+
export declare const EngineSchema: z.ZodEnum<{
|
|
21
|
+
chromium: "chromium";
|
|
22
|
+
firefox: "firefox";
|
|
23
|
+
webkit: "webkit";
|
|
24
|
+
}>;
|
|
25
|
+
export declare const CaptureMethodSchema: z.ZodEnum<{
|
|
26
|
+
"cdp-screencast": "cdp-screencast";
|
|
27
|
+
"screenshot-loop": "screenshot-loop";
|
|
28
|
+
}>;
|
|
29
|
+
export declare const ViewportSchema: z.ZodObject<{
|
|
30
|
+
width: z.ZodNumber;
|
|
31
|
+
height: z.ZodNumber;
|
|
32
|
+
deviceScaleFactor: z.ZodNumber;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export declare const RectSchema: z.ZodObject<{
|
|
35
|
+
x: z.ZodNumber;
|
|
36
|
+
y: z.ZodNumber;
|
|
37
|
+
width: z.ZodNumber;
|
|
38
|
+
height: z.ZodNumber;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export declare const DimsSchema: z.ZodObject<{
|
|
41
|
+
width: z.ZodNumber;
|
|
42
|
+
height: z.ZodNumber;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
/**
|
|
45
|
+
* What the recorder was pointed at. Discriminated on `kind` so encoders can
|
|
46
|
+
* switch exhaustively without guessing from the presence of optional fields.
|
|
47
|
+
*/
|
|
48
|
+
export declare const TargetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
49
|
+
kind: z.ZodLiteral<"viewport">;
|
|
50
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
51
|
+
kind: z.ZodLiteral<"region">;
|
|
52
|
+
rect: z.ZodObject<{
|
|
53
|
+
x: z.ZodNumber;
|
|
54
|
+
y: z.ZodNumber;
|
|
55
|
+
width: z.ZodNumber;
|
|
56
|
+
height: z.ZodNumber;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
59
|
+
kind: z.ZodLiteral<"element">;
|
|
60
|
+
selector: z.ZodString;
|
|
61
|
+
padding: z.ZodNumber;
|
|
62
|
+
}, z.core.$strip>], "kind">;
|
|
63
|
+
/**
|
|
64
|
+
* Console and network records are kept deliberately permissive: the recorder
|
|
65
|
+
* owns their exact shape and may attach engine-specific extras. Only the fields
|
|
66
|
+
* the encoders and reports actually read are required; unknown keys survive
|
|
67
|
+
* parsing untouched.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ConsoleEntrySchema: z.ZodObject<{
|
|
70
|
+
t_ms: z.ZodNumber;
|
|
71
|
+
type: z.ZodString;
|
|
72
|
+
text: z.ZodString;
|
|
73
|
+
}, z.core.$loose>;
|
|
74
|
+
export declare const NetworkEntrySchema: z.ZodObject<{
|
|
75
|
+
t_ms: z.ZodNumber;
|
|
76
|
+
url: z.ZodString;
|
|
77
|
+
method: z.ZodOptional<z.ZodString>;
|
|
78
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
failed: z.ZodOptional<z.ZodBoolean>;
|
|
80
|
+
}, z.core.$loose>;
|
|
81
|
+
export declare const FrameSchema: z.ZodObject<{
|
|
82
|
+
index: z.ZodNumber;
|
|
83
|
+
t_ms: z.ZodNumber;
|
|
84
|
+
path: z.ZodString;
|
|
85
|
+
crop: z.ZodObject<{
|
|
86
|
+
x: z.ZodNumber;
|
|
87
|
+
y: z.ZodNumber;
|
|
88
|
+
width: z.ZodNumber;
|
|
89
|
+
height: z.ZodNumber;
|
|
90
|
+
}, z.core.$strip>;
|
|
91
|
+
ssim_prev: z.ZodNullable<z.ZodNumber>;
|
|
92
|
+
anomaly: z.ZodBoolean;
|
|
93
|
+
tracking: z.ZodNullable<z.ZodEnum<{
|
|
94
|
+
ok: "ok";
|
|
95
|
+
stale: "stale";
|
|
96
|
+
}>>;
|
|
97
|
+
console: z.ZodArray<z.ZodObject<{
|
|
98
|
+
t_ms: z.ZodNumber;
|
|
99
|
+
type: z.ZodString;
|
|
100
|
+
text: z.ZodString;
|
|
101
|
+
}, z.core.$loose>>;
|
|
102
|
+
network: z.ZodArray<z.ZodObject<{
|
|
103
|
+
t_ms: z.ZodNumber;
|
|
104
|
+
url: z.ZodString;
|
|
105
|
+
method: z.ZodOptional<z.ZodString>;
|
|
106
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
107
|
+
failed: z.ZodOptional<z.ZodBoolean>;
|
|
108
|
+
}, z.core.$loose>>;
|
|
109
|
+
}, z.core.$strip>;
|
|
110
|
+
export declare const FrameGapSchema: z.ZodObject<{
|
|
111
|
+
after_index: z.ZodNumber;
|
|
112
|
+
gap_ms: z.ZodNumber;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
export declare const RecordingManifestSchema: z.ZodObject<{
|
|
115
|
+
version: z.ZodNumber;
|
|
116
|
+
slug: z.ZodString;
|
|
117
|
+
engine: z.ZodEnum<{
|
|
118
|
+
chromium: "chromium";
|
|
119
|
+
firefox: "firefox";
|
|
120
|
+
webkit: "webkit";
|
|
121
|
+
}>;
|
|
122
|
+
capture_method: z.ZodEnum<{
|
|
123
|
+
"cdp-screencast": "cdp-screencast";
|
|
124
|
+
"screenshot-loop": "screenshot-loop";
|
|
125
|
+
}>;
|
|
126
|
+
target_fps: z.ZodNumber;
|
|
127
|
+
actual_fps: z.ZodNumber;
|
|
128
|
+
viewport: z.ZodObject<{
|
|
129
|
+
width: z.ZodNumber;
|
|
130
|
+
height: z.ZodNumber;
|
|
131
|
+
deviceScaleFactor: z.ZodNumber;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
134
|
+
kind: z.ZodLiteral<"viewport">;
|
|
135
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
136
|
+
kind: z.ZodLiteral<"region">;
|
|
137
|
+
rect: z.ZodObject<{
|
|
138
|
+
x: z.ZodNumber;
|
|
139
|
+
y: z.ZodNumber;
|
|
140
|
+
width: z.ZodNumber;
|
|
141
|
+
height: z.ZodNumber;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
144
|
+
kind: z.ZodLiteral<"element">;
|
|
145
|
+
selector: z.ZodString;
|
|
146
|
+
padding: z.ZodNumber;
|
|
147
|
+
}, z.core.$strip>], "kind">;
|
|
148
|
+
start_trigger: z.ZodNullable<z.ZodString>;
|
|
149
|
+
stop_trigger: z.ZodNullable<z.ZodString>;
|
|
150
|
+
trigger_timeout: z.ZodBoolean;
|
|
151
|
+
started_at: z.ZodString;
|
|
152
|
+
duration_ms: z.ZodNumber;
|
|
153
|
+
output_dims: z.ZodObject<{
|
|
154
|
+
width: z.ZodNumber;
|
|
155
|
+
height: z.ZodNumber;
|
|
156
|
+
}, z.core.$strip>;
|
|
157
|
+
frames: z.ZodArray<z.ZodObject<{
|
|
158
|
+
index: z.ZodNumber;
|
|
159
|
+
t_ms: z.ZodNumber;
|
|
160
|
+
path: z.ZodString;
|
|
161
|
+
crop: z.ZodObject<{
|
|
162
|
+
x: z.ZodNumber;
|
|
163
|
+
y: z.ZodNumber;
|
|
164
|
+
width: z.ZodNumber;
|
|
165
|
+
height: z.ZodNumber;
|
|
166
|
+
}, z.core.$strip>;
|
|
167
|
+
ssim_prev: z.ZodNullable<z.ZodNumber>;
|
|
168
|
+
anomaly: z.ZodBoolean;
|
|
169
|
+
tracking: z.ZodNullable<z.ZodEnum<{
|
|
170
|
+
ok: "ok";
|
|
171
|
+
stale: "stale";
|
|
172
|
+
}>>;
|
|
173
|
+
console: z.ZodArray<z.ZodObject<{
|
|
174
|
+
t_ms: z.ZodNumber;
|
|
175
|
+
type: z.ZodString;
|
|
176
|
+
text: z.ZodString;
|
|
177
|
+
}, z.core.$loose>>;
|
|
178
|
+
network: z.ZodArray<z.ZodObject<{
|
|
179
|
+
t_ms: z.ZodNumber;
|
|
180
|
+
url: z.ZodString;
|
|
181
|
+
method: z.ZodOptional<z.ZodString>;
|
|
182
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
183
|
+
failed: z.ZodOptional<z.ZodBoolean>;
|
|
184
|
+
}, z.core.$loose>>;
|
|
185
|
+
}, z.core.$strip>>;
|
|
186
|
+
change_points: z.ZodArray<z.ZodNumber>;
|
|
187
|
+
frame_gaps: z.ZodArray<z.ZodObject<{
|
|
188
|
+
after_index: z.ZodNumber;
|
|
189
|
+
gap_ms: z.ZodNumber;
|
|
190
|
+
}, z.core.$strip>>;
|
|
191
|
+
region_clamped: z.ZodOptional<z.ZodBoolean>;
|
|
192
|
+
artifacts: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
193
|
+
}, z.core.$strip>;
|
|
194
|
+
export type Engine = z.infer<typeof EngineSchema>;
|
|
195
|
+
export type CaptureMethod = z.infer<typeof CaptureMethodSchema>;
|
|
196
|
+
export type Viewport = z.infer<typeof ViewportSchema>;
|
|
197
|
+
export type Rect = z.infer<typeof RectSchema>;
|
|
198
|
+
export type Dims = z.infer<typeof DimsSchema>;
|
|
199
|
+
export type RecordingTarget = z.infer<typeof TargetSchema>;
|
|
200
|
+
export type ConsoleEntry = z.infer<typeof ConsoleEntrySchema>;
|
|
201
|
+
export type NetworkEntry = z.infer<typeof NetworkEntrySchema>;
|
|
202
|
+
export type Frame = z.infer<typeof FrameSchema>;
|
|
203
|
+
export type FrameGap = z.infer<typeof FrameGapSchema>;
|
|
204
|
+
export type RecordingManifest = z.infer<typeof RecordingManifestSchema>;
|
|
205
|
+
/**
|
|
206
|
+
* Parse and validate a manifest.
|
|
207
|
+
*
|
|
208
|
+
* Accepts an already-parsed object or a raw JSON string. Throws an `Error`
|
|
209
|
+
* whose message lists every failing path, so a malformed manifest reports all
|
|
210
|
+
* of its problems at once instead of one per run.
|
|
211
|
+
*/
|
|
212
|
+
export declare function parseManifest(input: unknown): RecordingManifest;
|
|
213
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/recording/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAMlC,eAAO,MAAM,YAAY;;;;EAA4C,CAAC;AAEtE,eAAO,MAAM,mBAAmB;;;EAAgD,CAAC;AAEjF,eAAO,MAAM,cAAc;;;;iBAIzB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;iBAKrB,CAAC;AAEH,eAAO,MAAM,UAAU;;;iBAGrB,CAAC;AAMH;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;2BAQvB,CAAC;AAMH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;iBAMrB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;iBAQrB,CAAC;AAMX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBtB,CAAC;AAEH,eAAO,MAAM,cAAc;;;iBAIzB,CAAC;AAMH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BlC,CAAC;AAMH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAChD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAMxE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAsB/D"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBrowser - Cognitive Browser Automation
|
|
3
|
+
* Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
|
|
4
|
+
* Learn more at https://cbrowser.ai - MIT License
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Recording manifest contract.
|
|
8
|
+
*
|
|
9
|
+
* The manifest is the single artifact that describes a recording run: how it
|
|
10
|
+
* was captured, what was captured, and every frame that made it into the
|
|
11
|
+
* output. Encoders, the CLI, and any downstream consumer read this file rather
|
|
12
|
+
* than re-deriving state from the frame directory.
|
|
13
|
+
*
|
|
14
|
+
* Schemas are zod v4; every exported TS type is inferred from its schema so the
|
|
15
|
+
* runtime contract and the compile-time contract cannot drift.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
/** Manifest format version emitted by this build. */
|
|
19
|
+
export const MANIFEST_VERSION = 1;
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Primitives
|
|
22
|
+
// ============================================================================
|
|
23
|
+
export const EngineSchema = z.enum(["chromium", "firefox", "webkit"]);
|
|
24
|
+
export const CaptureMethodSchema = z.enum(["cdp-screencast", "screenshot-loop"]);
|
|
25
|
+
export const ViewportSchema = z.object({
|
|
26
|
+
width: z.number().int().positive(),
|
|
27
|
+
height: z.number().int().positive(),
|
|
28
|
+
deviceScaleFactor: z.number().positive(),
|
|
29
|
+
});
|
|
30
|
+
export const RectSchema = z.object({
|
|
31
|
+
x: z.number(),
|
|
32
|
+
y: z.number(),
|
|
33
|
+
width: z.number().positive(),
|
|
34
|
+
height: z.number().positive(),
|
|
35
|
+
});
|
|
36
|
+
export const DimsSchema = z.object({
|
|
37
|
+
width: z.number().int().positive(),
|
|
38
|
+
height: z.number().int().positive(),
|
|
39
|
+
});
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Capture target
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* What the recorder was pointed at. Discriminated on `kind` so encoders can
|
|
45
|
+
* switch exhaustively without guessing from the presence of optional fields.
|
|
46
|
+
*/
|
|
47
|
+
export const TargetSchema = z.discriminatedUnion("kind", [
|
|
48
|
+
z.object({ kind: z.literal("viewport") }),
|
|
49
|
+
z.object({ kind: z.literal("region"), rect: RectSchema }),
|
|
50
|
+
z.object({
|
|
51
|
+
kind: z.literal("element"),
|
|
52
|
+
selector: z.string().min(1),
|
|
53
|
+
padding: z.number().nonnegative(),
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Per-frame side-channel records
|
|
58
|
+
// ============================================================================
|
|
59
|
+
/**
|
|
60
|
+
* Console and network records are kept deliberately permissive: the recorder
|
|
61
|
+
* owns their exact shape and may attach engine-specific extras. Only the fields
|
|
62
|
+
* the encoders and reports actually read are required; unknown keys survive
|
|
63
|
+
* parsing untouched.
|
|
64
|
+
*/
|
|
65
|
+
export const ConsoleEntrySchema = z
|
|
66
|
+
.object({
|
|
67
|
+
t_ms: z.number().nonnegative(),
|
|
68
|
+
type: z.string(),
|
|
69
|
+
text: z.string(),
|
|
70
|
+
})
|
|
71
|
+
.loose();
|
|
72
|
+
export const NetworkEntrySchema = z
|
|
73
|
+
.object({
|
|
74
|
+
t_ms: z.number().nonnegative(),
|
|
75
|
+
url: z.string(),
|
|
76
|
+
method: z.string().optional(),
|
|
77
|
+
status: z.number().int().optional(),
|
|
78
|
+
failed: z.boolean().optional(),
|
|
79
|
+
})
|
|
80
|
+
.loose();
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Frames
|
|
83
|
+
// ============================================================================
|
|
84
|
+
export const FrameSchema = z.object({
|
|
85
|
+
/** Position in the frame sequence, 0-based and dense. */
|
|
86
|
+
index: z.number().int().nonnegative(),
|
|
87
|
+
/** Milliseconds since `started_at`. Non-decreasing across the sequence. */
|
|
88
|
+
t_ms: z.number().nonnegative(),
|
|
89
|
+
/** Path to the frame image, relative to the manifest. */
|
|
90
|
+
path: z.string().min(1),
|
|
91
|
+
/** Source-image crop this frame was cut from (post-clamp). */
|
|
92
|
+
crop: RectSchema,
|
|
93
|
+
/** SSIM against the previous frame; null for frame 0 or when not computed. */
|
|
94
|
+
ssim_prev: z.number().nullable(),
|
|
95
|
+
/** Frame flagged as a visual anomaly by the change detector. */
|
|
96
|
+
anomaly: z.boolean(),
|
|
97
|
+
/** Element-tracking state for element targets; null when not tracking. */
|
|
98
|
+
tracking: z.enum(["ok", "stale"]).nullable(),
|
|
99
|
+
console: z.array(ConsoleEntrySchema),
|
|
100
|
+
network: z.array(NetworkEntrySchema),
|
|
101
|
+
});
|
|
102
|
+
export const FrameGapSchema = z.object({
|
|
103
|
+
/** Gap occurred between this frame index and the next one. */
|
|
104
|
+
after_index: z.number().int().nonnegative(),
|
|
105
|
+
gap_ms: z.number().positive(),
|
|
106
|
+
});
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Manifest
|
|
109
|
+
// ============================================================================
|
|
110
|
+
export const RecordingManifestSchema = z.object({
|
|
111
|
+
version: z.number().int().positive(),
|
|
112
|
+
slug: z.string().min(1),
|
|
113
|
+
engine: EngineSchema,
|
|
114
|
+
capture_method: CaptureMethodSchema,
|
|
115
|
+
/** Frame rate requested by the caller. */
|
|
116
|
+
target_fps: z.number().positive(),
|
|
117
|
+
/** Frame rate actually achieved: frames / (duration_ms / 1000). */
|
|
118
|
+
actual_fps: z.number().nonnegative(),
|
|
119
|
+
viewport: ViewportSchema,
|
|
120
|
+
target: TargetSchema,
|
|
121
|
+
/** Trigger that started capture; null when capture started immediately. */
|
|
122
|
+
start_trigger: z.string().nullable(),
|
|
123
|
+
/** Trigger that stopped capture; null when capture ran to a fixed duration. */
|
|
124
|
+
stop_trigger: z.string().nullable(),
|
|
125
|
+
/** True when capture ended because a trigger timed out rather than fired. */
|
|
126
|
+
trigger_timeout: z.boolean(),
|
|
127
|
+
/** ISO-8601 timestamp of the first frame. */
|
|
128
|
+
started_at: z.string().min(1),
|
|
129
|
+
duration_ms: z.number().nonnegative(),
|
|
130
|
+
output_dims: DimsSchema,
|
|
131
|
+
frames: z.array(FrameSchema),
|
|
132
|
+
/** Frame indices where SSIM dropped below the change threshold. */
|
|
133
|
+
change_points: z.array(z.number().int().nonnegative()),
|
|
134
|
+
frame_gaps: z.array(FrameGapSchema),
|
|
135
|
+
/** Present when a region/element rect had to be clamped into the viewport. */
|
|
136
|
+
region_clamped: z.boolean().optional(),
|
|
137
|
+
/** Output format ("gif", "webp", "mp4", ...) to artifact path. */
|
|
138
|
+
artifacts: z.record(z.string(), z.string()),
|
|
139
|
+
});
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Parsing
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Parse and validate a manifest.
|
|
145
|
+
*
|
|
146
|
+
* Accepts an already-parsed object or a raw JSON string. Throws an `Error`
|
|
147
|
+
* whose message lists every failing path, so a malformed manifest reports all
|
|
148
|
+
* of its problems at once instead of one per run.
|
|
149
|
+
*/
|
|
150
|
+
export function parseManifest(input) {
|
|
151
|
+
let candidate = input;
|
|
152
|
+
if (typeof candidate === "string") {
|
|
153
|
+
try {
|
|
154
|
+
candidate = JSON.parse(candidate);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
throw new Error(`Invalid recording manifest: not valid JSON (${e.message})`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const result = RecordingManifestSchema.safeParse(candidate);
|
|
161
|
+
if (result.success)
|
|
162
|
+
return result.data;
|
|
163
|
+
const details = result.error.issues
|
|
164
|
+
.map((issue) => {
|
|
165
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
166
|
+
return ` - ${path}: ${issue.message}`;
|
|
167
|
+
})
|
|
168
|
+
.join("\n");
|
|
169
|
+
throw new Error(`Invalid recording manifest:\n${details}`);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/recording/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
|
|
4
4
|
* Learn more at https://cbrowser.ai - MIT License
|
|
5
5
|
*/
|
|
6
|
+
import { type AutoCaptureSetting } from "../recording/auto-capture.js";
|
|
6
7
|
import type { NLTestStep, NLTestCase, NLTestSuiteResult } from "../types.js";
|
|
7
8
|
/**
|
|
8
9
|
* Parse a single natural language instruction into an NLTestStep.
|
|
@@ -12,7 +13,9 @@ import type { NLTestStep, NLTestCase, NLTestSuiteResult } from "../types.js";
|
|
|
12
13
|
* - "click [the] <target>" / "press <target>"
|
|
13
14
|
* - "type '<value>' in[to] <target>" / "fill <target> with '<value>'"
|
|
14
15
|
* - "select '<option>' from <dropdown>"
|
|
15
|
-
* - "scroll down/up"
|
|
16
|
+
* - "scroll down/up [N times | N pixels]" / "scroll to [the] top/bottom [of the page]"
|
|
17
|
+
* - "set cookie <name> to <value>" (applies to the current page's origin โ navigate first)
|
|
18
|
+
* - "tap at <x>,<y>" / "click at <x>,<y>" (real pointer events at viewport coordinates)
|
|
16
19
|
* - "wait [for] <seconds> seconds"
|
|
17
20
|
* - "wait for content" / "wait for page to load" / "wait for page ready" (v16.7.1)
|
|
18
21
|
* - "wait for '<text>' appears" / "wait for <element> to appear" (v16.7.1)
|
|
@@ -55,6 +58,15 @@ export interface NLTestSuiteOptions {
|
|
|
55
58
|
headless?: boolean;
|
|
56
59
|
/** Use fuzzy matching for text assertions */
|
|
57
60
|
fuzzyMatch?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Record the run to GIF/WebP/video (v18.70.0).
|
|
63
|
+
*
|
|
64
|
+
* `true` uses defaults; pass an options object to set fps, formats or output
|
|
65
|
+
* directory. The capture starts once the browser is up and stops before it
|
|
66
|
+
* closes, so no separate capture_start/capture_stop call is needed. Capture
|
|
67
|
+
* failures never fail the suite โ they are reported on `result.capture.error`.
|
|
68
|
+
*/
|
|
69
|
+
capture?: AutoCaptureSetting;
|
|
58
70
|
}
|
|
59
71
|
/**
|
|
60
72
|
* Dry-run a test suite: parse all instructions and return without executing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nl-test-suite.d.ts","sourceRoot":"","sources":["../../src/testing/nl-test-suite.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EAIV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB
|
|
1
|
+
{"version":3,"file":"nl-test-suite.d.ts","sourceRoot":"","sources":["../../src/testing/nl-test-suite.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EAIV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CAmMlE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAwB,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CA0CvC;AAED,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kCAAkC;IAClC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAqED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,CAQvE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,EAC5C,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAoV5B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAyFpE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAU5B"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync } from "fs";
|
|
7
7
|
import { CBrowser } from "../browser.js";
|
|
8
|
+
import { finishAutoCapture, resolveCaptureOptions, startAutoCapture, } from "../recording/auto-capture.js";
|
|
8
9
|
/**
|
|
9
10
|
* Parse a single natural language instruction into an NLTestStep.
|
|
10
11
|
*
|
|
@@ -13,7 +14,9 @@ import { CBrowser } from "../browser.js";
|
|
|
13
14
|
* - "click [the] <target>" / "press <target>"
|
|
14
15
|
* - "type '<value>' in[to] <target>" / "fill <target> with '<value>'"
|
|
15
16
|
* - "select '<option>' from <dropdown>"
|
|
16
|
-
* - "scroll down/up"
|
|
17
|
+
* - "scroll down/up [N times | N pixels]" / "scroll to [the] top/bottom [of the page]"
|
|
18
|
+
* - "set cookie <name> to <value>" (applies to the current page's origin โ navigate first)
|
|
19
|
+
* - "tap at <x>,<y>" / "click at <x>,<y>" (real pointer events at viewport coordinates)
|
|
17
20
|
* - "wait [for] <seconds> seconds"
|
|
18
21
|
* - "wait for content" / "wait for page to load" / "wait for page ready" (v16.7.1)
|
|
19
22
|
* - "wait for '<text>' appears" / "wait for <element> to appear" (v16.7.1)
|
|
@@ -35,6 +38,15 @@ export function parseNLInstruction(instruction) {
|
|
|
35
38
|
target: originalMatch ? originalMatch[1].trim() : navigateMatch[1].trim(),
|
|
36
39
|
};
|
|
37
40
|
}
|
|
41
|
+
// Coordinate tap MUST parse before the generic click pattern (which would swallow it)
|
|
42
|
+
const tapAtMatch = lower.match(/^(?:tap|click)\s+at\s+(\d+)\s*[,x]\s*(\d+)$/i);
|
|
43
|
+
if (tapAtMatch) {
|
|
44
|
+
return {
|
|
45
|
+
instruction,
|
|
46
|
+
action: "click",
|
|
47
|
+
target: `at:${tapAtMatch[1]},${tapAtMatch[2]}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
38
50
|
// Click patterns - preserve original case for target
|
|
39
51
|
const clickMatch = lower.match(/^(?:click|tap|press)\s+(?:on\s+)?(?:the\s+)?(.+)$/i);
|
|
40
52
|
if (clickMatch) {
|
|
@@ -79,13 +91,32 @@ export function parseNLInstruction(instruction) {
|
|
|
79
91
|
};
|
|
80
92
|
}
|
|
81
93
|
// Scroll patterns
|
|
82
|
-
const
|
|
94
|
+
const scrollToMatch = lower.match(/^scroll\s+to\s+(?:the\s+)?(top|bottom)(?:\s+of\s+the\s+page)?$/i);
|
|
95
|
+
if (scrollToMatch) {
|
|
96
|
+
return {
|
|
97
|
+
instruction,
|
|
98
|
+
action: "scroll",
|
|
99
|
+
target: scrollToMatch[1],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const scrollMatch = lower.match(/^scroll\s+(up|down|left|right)(?:\s+(\d+)\s+(times|pixels))?$/i);
|
|
83
103
|
if (scrollMatch) {
|
|
84
104
|
return {
|
|
85
105
|
instruction,
|
|
86
106
|
action: "scroll",
|
|
87
107
|
target: scrollMatch[1],
|
|
88
|
-
value: scrollMatch[2] || "
|
|
108
|
+
value: scrollMatch[2] || "1",
|
|
109
|
+
unit: scrollMatch[3] || "times",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Cookie pattern โ "set cookie NAME to VALUE" (value from the ORIGINAL string to preserve case)
|
|
113
|
+
const cookieMatch = trimmed.match(/^set\s+cookie\s+(\S+)\s+to\s+(.+)$/i);
|
|
114
|
+
if (cookieMatch) {
|
|
115
|
+
return {
|
|
116
|
+
instruction,
|
|
117
|
+
action: "cookie",
|
|
118
|
+
target: cookieMatch[1],
|
|
119
|
+
value: cookieMatch[2].trim(),
|
|
89
120
|
};
|
|
90
121
|
}
|
|
91
122
|
// Wait patterns
|
|
@@ -303,7 +334,7 @@ export function dryRunNLTestSuite(suite) {
|
|
|
303
334
|
* Run a natural language test suite.
|
|
304
335
|
*/
|
|
305
336
|
export async function runNLTestSuite(suite, options = {}) {
|
|
306
|
-
const { stepTimeout = 30000, continueOnFailure = true, screenshotOnFailure = true, headless = true, fuzzyMatch = false, } = options;
|
|
337
|
+
const { stepTimeout = 30000, continueOnFailure = true, screenshotOnFailure = true, headless = true, fuzzyMatch = false, capture, } = options;
|
|
307
338
|
const startTime = Date.now();
|
|
308
339
|
const testResults = [];
|
|
309
340
|
console.log(`\n๐งช Running Test Suite: ${suite.name}`);
|
|
@@ -315,8 +346,15 @@ export async function runNLTestSuite(suite, options = {}) {
|
|
|
315
346
|
const browser = new CBrowser({
|
|
316
347
|
headless,
|
|
317
348
|
});
|
|
349
|
+
let captureResult;
|
|
350
|
+
let captureSession = null;
|
|
318
351
|
try {
|
|
319
352
|
await browser.launch();
|
|
353
|
+
// Auto-capture (v18.70.0): starts here so the recording covers every test
|
|
354
|
+
// in the suite, and stops in the finally below before the browser closes.
|
|
355
|
+
captureSession = await startAutoCapture(await browser.getPage(), capture, `suite-${suite.name.replace(/[^a-zA-Z0-9-_]+/g, "-").toLowerCase()}-${Date.now()}`);
|
|
356
|
+
if (captureSession)
|
|
357
|
+
console.log(` ๐ฅ Capturing to ${captureSession.status().outDir}`);
|
|
320
358
|
for (const test of suite.tests) {
|
|
321
359
|
console.log(`\n๐ Test: ${test.name}`);
|
|
322
360
|
const testStartTime = Date.now();
|
|
@@ -338,6 +376,18 @@ export async function runNLTestSuite(suite, options = {}) {
|
|
|
338
376
|
break;
|
|
339
377
|
}
|
|
340
378
|
case "click": {
|
|
379
|
+
const coordTarget = (step.target || "").match(/^at:(\d+),(\d+)$/);
|
|
380
|
+
if (coordTarget) {
|
|
381
|
+
const page = browser.page;
|
|
382
|
+
if (page) {
|
|
383
|
+
const x = parseInt(coordTarget[1], 10);
|
|
384
|
+
const y = parseInt(coordTarget[2], 10);
|
|
385
|
+
await page.mouse.move(x, y);
|
|
386
|
+
await page.mouse.down();
|
|
387
|
+
await page.mouse.up();
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
341
391
|
const result = await browser.smartClick(step.target || "");
|
|
342
392
|
if (!result.success) {
|
|
343
393
|
throw new Error(`Failed to click: ${step.target}`);
|
|
@@ -353,11 +403,42 @@ export async function runNLTestSuite(suite, options = {}) {
|
|
|
353
403
|
break;
|
|
354
404
|
}
|
|
355
405
|
case "scroll": {
|
|
356
|
-
const direction = step.target?.toLowerCase() === "up" ? -500 : 500;
|
|
357
406
|
const page = browser.page;
|
|
358
407
|
if (page) {
|
|
359
|
-
|
|
408
|
+
const target = step.target?.toLowerCase();
|
|
409
|
+
if (target === "top" || target === "bottom") {
|
|
410
|
+
await page.evaluate((t) => {
|
|
411
|
+
window.scrollTo(0, t === "bottom" ? document.documentElement.scrollHeight : 0);
|
|
412
|
+
}, target);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
const sign = target === "up" ? -1 : 1;
|
|
416
|
+
const n = parseInt(step.value || "1", 10) || 1;
|
|
417
|
+
const delta = sign * (step.unit === "pixels" ? n : n * 500);
|
|
418
|
+
await page.evaluate((d) => window.scrollBy(0, d), delta);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "cookie": {
|
|
424
|
+
const page = browser.page;
|
|
425
|
+
if (!page || !step.target) {
|
|
426
|
+
throw new Error(`Cookie step could not run (page=${!!page}, name=${step.target ?? "<missing>"}) โ navigate before setting cookies`);
|
|
427
|
+
}
|
|
428
|
+
// A site-set cookie of the same name (often dot-domain, e.g. an anon PHP
|
|
429
|
+
// session) would otherwise COEXIST with ours (host-only vs dot-domain are
|
|
430
|
+
// distinct identities) and win first-occurrence parsing server-side.
|
|
431
|
+
// Clear same-name cookies first so this is a true replacement.
|
|
432
|
+
const host = new URL(page.url()).hostname;
|
|
433
|
+
try {
|
|
434
|
+
await page.context().clearCookies({ name: step.target });
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
/* clearCookies filters unsupported on old Playwright โ best effort */
|
|
360
438
|
}
|
|
439
|
+
await page.context().addCookies([
|
|
440
|
+
{ name: step.target, value: step.value || "", domain: host, path: "/" },
|
|
441
|
+
]);
|
|
361
442
|
break;
|
|
362
443
|
}
|
|
363
444
|
case "wait": {
|
|
@@ -519,6 +600,9 @@ export async function runNLTestSuite(suite, options = {}) {
|
|
|
519
600
|
}
|
|
520
601
|
}
|
|
521
602
|
finally {
|
|
603
|
+
// Stop before the browser closes โ the page has to still exist. Bounded, so
|
|
604
|
+
// a wedged recorder cannot hang the suite (see finishAutoCapture).
|
|
605
|
+
captureResult = await finishAutoCapture(captureSession, resolveCaptureOptions(capture)?.stopTimeoutMs);
|
|
522
606
|
await browser.close();
|
|
523
607
|
}
|
|
524
608
|
const passed = testResults.filter(t => t.passed).length;
|
|
@@ -553,6 +637,7 @@ export async function runNLTestSuite(suite, options = {}) {
|
|
|
553
637
|
stepPassRate,
|
|
554
638
|
},
|
|
555
639
|
recommendations: recommendations.length > 0 ? recommendations : undefined,
|
|
640
|
+
...(captureResult ? { capture: captureResult } : {}),
|
|
556
641
|
};
|
|
557
642
|
return result;
|
|
558
643
|
}
|