@browserflow-ai/core 0.0.6
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/config-schema.d.ts +354 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +83 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +107 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/duration.d.ts +39 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +111 -0
- package/dist/duration.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/locator-object.d.ts +556 -0
- package/dist/locator-object.d.ts.map +1 -0
- package/dist/locator-object.js +114 -0
- package/dist/locator-object.js.map +1 -0
- package/dist/lockfile.d.ts +1501 -0
- package/dist/lockfile.d.ts.map +1 -0
- package/dist/lockfile.js +86 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/run-store.d.ts +81 -0
- package/dist/run-store.d.ts.map +1 -0
- package/dist/run-store.js +181 -0
- package/dist/run-store.js.map +1 -0
- package/dist/spec-loader.d.ts +17 -0
- package/dist/spec-loader.d.ts.map +1 -0
- package/dist/spec-loader.js +37 -0
- package/dist/spec-loader.js.map +1 -0
- package/dist/spec-schema.d.ts +1411 -0
- package/dist/spec-schema.d.ts.map +1 -0
- package/dist/spec-schema.js +239 -0
- package/dist/spec-schema.js.map +1 -0
- package/package.json +45 -0
- package/schemas/browserflow.schema.json +220 -0
- package/schemas/lockfile.schema.json +568 -0
- package/schemas/spec-v2.schema.json +413 -0
- package/src/config-schema.test.ts +190 -0
- package/src/config-schema.ts +111 -0
- package/src/config.ts +112 -0
- package/src/duration.test.ts +175 -0
- package/src/duration.ts +128 -0
- package/src/index.ts +136 -0
- package/src/json-schemas.test.ts +374 -0
- package/src/locator-object.test.ts +323 -0
- package/src/locator-object.ts +250 -0
- package/src/lockfile.test.ts +345 -0
- package/src/lockfile.ts +209 -0
- package/src/run-store.test.ts +232 -0
- package/src/run-store.ts +240 -0
- package/src/spec-loader.test.ts +181 -0
- package/src/spec-loader.ts +47 -0
- package/src/spec-schema.test.ts +360 -0
- package/src/spec-schema.ts +347 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$ref": "#/definitions/spec-v2",
|
|
3
|
+
"definitions": {
|
|
4
|
+
"spec-v2": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"version": {
|
|
8
|
+
"type": "number",
|
|
9
|
+
"const": 2
|
|
10
|
+
},
|
|
11
|
+
"name": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[a-z0-9-]+$",
|
|
14
|
+
"errorMessage": {
|
|
15
|
+
"pattern": "Name must be kebab-case (lowercase letters, numbers, and hyphens only)"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"steps": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"id": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1,
|
|
29
|
+
"errorMessage": {
|
|
30
|
+
"minLength": "Step id is required"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"action": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": [
|
|
36
|
+
"click",
|
|
37
|
+
"navigate",
|
|
38
|
+
"back",
|
|
39
|
+
"forward",
|
|
40
|
+
"refresh",
|
|
41
|
+
"reload",
|
|
42
|
+
"fill",
|
|
43
|
+
"type",
|
|
44
|
+
"select",
|
|
45
|
+
"check",
|
|
46
|
+
"press",
|
|
47
|
+
"upload",
|
|
48
|
+
"wait",
|
|
49
|
+
"expect",
|
|
50
|
+
"screenshot",
|
|
51
|
+
"scroll",
|
|
52
|
+
"scroll_into_view",
|
|
53
|
+
"verify_state",
|
|
54
|
+
"identify_element",
|
|
55
|
+
"ai_verify",
|
|
56
|
+
"custom"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"name": {
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
"description": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"why": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
"target": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"query": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
},
|
|
74
|
+
"testid": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
"role": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
80
|
+
"name": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
},
|
|
83
|
+
"label": {
|
|
84
|
+
"type": "string"
|
|
85
|
+
},
|
|
86
|
+
"placeholder": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"text": {
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
92
|
+
"css": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"within": {},
|
|
96
|
+
"nth": {
|
|
97
|
+
"type": "integer"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"additionalProperties": false
|
|
101
|
+
},
|
|
102
|
+
"url": {
|
|
103
|
+
"type": "string"
|
|
104
|
+
},
|
|
105
|
+
"value": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
},
|
|
108
|
+
"text": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
},
|
|
111
|
+
"option": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
},
|
|
114
|
+
"checked": {
|
|
115
|
+
"type": "boolean"
|
|
116
|
+
},
|
|
117
|
+
"duration": {
|
|
118
|
+
"type": "string"
|
|
119
|
+
},
|
|
120
|
+
"state": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"enum": [
|
|
123
|
+
"visible",
|
|
124
|
+
"hidden",
|
|
125
|
+
"enabled",
|
|
126
|
+
"disabled",
|
|
127
|
+
"checked",
|
|
128
|
+
"unchecked",
|
|
129
|
+
"focused",
|
|
130
|
+
"editable",
|
|
131
|
+
"attached",
|
|
132
|
+
"detached"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
"timeout": {
|
|
136
|
+
"type": "string"
|
|
137
|
+
},
|
|
138
|
+
"pressEnter": {
|
|
139
|
+
"type": "boolean"
|
|
140
|
+
},
|
|
141
|
+
"checks": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"items": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"element_visible": {
|
|
147
|
+
"type": "string"
|
|
148
|
+
},
|
|
149
|
+
"element_not_visible": {
|
|
150
|
+
"type": "string"
|
|
151
|
+
},
|
|
152
|
+
"text_contains": {
|
|
153
|
+
"type": "string"
|
|
154
|
+
},
|
|
155
|
+
"text_not_contains": {
|
|
156
|
+
"type": "string"
|
|
157
|
+
},
|
|
158
|
+
"url_contains": {
|
|
159
|
+
"type": "string"
|
|
160
|
+
},
|
|
161
|
+
"element_count": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {
|
|
164
|
+
"selector": {
|
|
165
|
+
"type": "string"
|
|
166
|
+
},
|
|
167
|
+
"expected": {
|
|
168
|
+
"type": "number"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"required": [
|
|
172
|
+
"selector",
|
|
173
|
+
"expected"
|
|
174
|
+
],
|
|
175
|
+
"additionalProperties": false
|
|
176
|
+
},
|
|
177
|
+
"attribute": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"selector": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
},
|
|
183
|
+
"attribute": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
},
|
|
186
|
+
"equals": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"required": [
|
|
191
|
+
"selector",
|
|
192
|
+
"attribute",
|
|
193
|
+
"equals"
|
|
194
|
+
],
|
|
195
|
+
"additionalProperties": false
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"additionalProperties": false
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"highlight": {
|
|
202
|
+
"type": "array",
|
|
203
|
+
"items": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"properties": {
|
|
206
|
+
"selector": {
|
|
207
|
+
"type": "string"
|
|
208
|
+
},
|
|
209
|
+
"label": {
|
|
210
|
+
"type": "string"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"required": [
|
|
214
|
+
"selector"
|
|
215
|
+
],
|
|
216
|
+
"additionalProperties": false
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"mask": {
|
|
220
|
+
"type": "array",
|
|
221
|
+
"items": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {
|
|
224
|
+
"selector": {
|
|
225
|
+
"type": "string"
|
|
226
|
+
},
|
|
227
|
+
"region": {
|
|
228
|
+
"type": "object",
|
|
229
|
+
"properties": {
|
|
230
|
+
"x": {
|
|
231
|
+
"type": "number"
|
|
232
|
+
},
|
|
233
|
+
"y": {
|
|
234
|
+
"type": "number"
|
|
235
|
+
},
|
|
236
|
+
"width": {
|
|
237
|
+
"type": "number"
|
|
238
|
+
},
|
|
239
|
+
"height": {
|
|
240
|
+
"type": "number"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"required": [
|
|
244
|
+
"x",
|
|
245
|
+
"y",
|
|
246
|
+
"width",
|
|
247
|
+
"height"
|
|
248
|
+
],
|
|
249
|
+
"additionalProperties": false
|
|
250
|
+
},
|
|
251
|
+
"reason": {
|
|
252
|
+
"type": "string"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"additionalProperties": false
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"question": {
|
|
259
|
+
"type": "string"
|
|
260
|
+
},
|
|
261
|
+
"expected": {
|
|
262
|
+
"type": [
|
|
263
|
+
"boolean",
|
|
264
|
+
"string",
|
|
265
|
+
"number"
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
"save_as": {
|
|
269
|
+
"type": "string"
|
|
270
|
+
},
|
|
271
|
+
"ref": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"selector": {
|
|
275
|
+
"type": "string"
|
|
276
|
+
},
|
|
277
|
+
"for": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"enum": [
|
|
280
|
+
"element",
|
|
281
|
+
"text",
|
|
282
|
+
"url",
|
|
283
|
+
"time"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
"contains": {
|
|
287
|
+
"type": "string"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"required": [
|
|
291
|
+
"id",
|
|
292
|
+
"action"
|
|
293
|
+
],
|
|
294
|
+
"additionalProperties": false
|
|
295
|
+
},
|
|
296
|
+
"minItems": 1,
|
|
297
|
+
"errorMessage": {
|
|
298
|
+
"minItems": "At least one step required"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"timeout": {
|
|
302
|
+
"type": "string"
|
|
303
|
+
},
|
|
304
|
+
"priority": {
|
|
305
|
+
"type": "string",
|
|
306
|
+
"enum": [
|
|
307
|
+
"critical",
|
|
308
|
+
"high",
|
|
309
|
+
"normal",
|
|
310
|
+
"low"
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
"tags": {
|
|
314
|
+
"type": "array",
|
|
315
|
+
"items": {
|
|
316
|
+
"type": "string"
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"preconditions": {
|
|
320
|
+
"type": "object",
|
|
321
|
+
"properties": {
|
|
322
|
+
"page": {
|
|
323
|
+
"type": "object",
|
|
324
|
+
"properties": {
|
|
325
|
+
"url": {
|
|
326
|
+
"type": "string"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
"additionalProperties": false
|
|
330
|
+
},
|
|
331
|
+
"auth": {
|
|
332
|
+
"type": "object",
|
|
333
|
+
"properties": {
|
|
334
|
+
"user": {
|
|
335
|
+
"type": "string"
|
|
336
|
+
},
|
|
337
|
+
"state": {
|
|
338
|
+
"type": "string"
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
"additionalProperties": false
|
|
342
|
+
},
|
|
343
|
+
"viewport": {
|
|
344
|
+
"type": "object",
|
|
345
|
+
"properties": {
|
|
346
|
+
"width": {
|
|
347
|
+
"type": "number"
|
|
348
|
+
},
|
|
349
|
+
"height": {
|
|
350
|
+
"type": "number"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
"required": [
|
|
354
|
+
"width",
|
|
355
|
+
"height"
|
|
356
|
+
],
|
|
357
|
+
"additionalProperties": false
|
|
358
|
+
},
|
|
359
|
+
"mocks": {
|
|
360
|
+
"type": "array",
|
|
361
|
+
"items": {
|
|
362
|
+
"type": "object",
|
|
363
|
+
"properties": {
|
|
364
|
+
"url": {
|
|
365
|
+
"type": "string"
|
|
366
|
+
},
|
|
367
|
+
"response": {}
|
|
368
|
+
},
|
|
369
|
+
"required": [
|
|
370
|
+
"url"
|
|
371
|
+
],
|
|
372
|
+
"additionalProperties": false
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
"additionalProperties": false
|
|
377
|
+
},
|
|
378
|
+
"expected_outcomes": {
|
|
379
|
+
"type": "array",
|
|
380
|
+
"items": {
|
|
381
|
+
"type": "object",
|
|
382
|
+
"properties": {
|
|
383
|
+
"description": {
|
|
384
|
+
"type": "string"
|
|
385
|
+
},
|
|
386
|
+
"check": {
|
|
387
|
+
"type": "string"
|
|
388
|
+
},
|
|
389
|
+
"expected": {
|
|
390
|
+
"type": [
|
|
391
|
+
"boolean",
|
|
392
|
+
"number",
|
|
393
|
+
"string"
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
"additionalProperties": false
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
"required": [
|
|
402
|
+
"version",
|
|
403
|
+
"name",
|
|
404
|
+
"steps"
|
|
405
|
+
],
|
|
406
|
+
"additionalProperties": false
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
410
|
+
"$id": "https://browserflow.dev/schemas/spec-v2.schema.json",
|
|
411
|
+
"title": "BrowserFlow Spec v2",
|
|
412
|
+
"description": "Schema for BrowserFlow YAML test specifications (version 2)"
|
|
413
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for browserflow.yaml config schema validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect } from 'bun:test';
|
|
6
|
+
import {
|
|
7
|
+
browserflowConfigSchema,
|
|
8
|
+
validateBrowserflowConfig,
|
|
9
|
+
parseBrowserflowConfig,
|
|
10
|
+
} from './config-schema.js';
|
|
11
|
+
|
|
12
|
+
describe('browserflowConfigSchema', () => {
|
|
13
|
+
describe('valid configs', () => {
|
|
14
|
+
test('accepts minimal config with required project.name', () => {
|
|
15
|
+
const config = {
|
|
16
|
+
project: {
|
|
17
|
+
name: 'my-project',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('accepts full config', () => {
|
|
24
|
+
const config = {
|
|
25
|
+
project: {
|
|
26
|
+
name: 'my-project',
|
|
27
|
+
base_url: 'http://localhost:3000',
|
|
28
|
+
},
|
|
29
|
+
runtime: {
|
|
30
|
+
browser: 'chromium',
|
|
31
|
+
headless: true,
|
|
32
|
+
viewport: { width: 1280, height: 720 },
|
|
33
|
+
timeout: '30s',
|
|
34
|
+
},
|
|
35
|
+
locators: {
|
|
36
|
+
prefer_testid: true,
|
|
37
|
+
testid_attributes: ['data-testid', 'data-test'],
|
|
38
|
+
},
|
|
39
|
+
exploration: {
|
|
40
|
+
adapter: 'claude',
|
|
41
|
+
max_retries: 3,
|
|
42
|
+
},
|
|
43
|
+
review: {
|
|
44
|
+
port: 8190,
|
|
45
|
+
auto_open: true,
|
|
46
|
+
},
|
|
47
|
+
output: {
|
|
48
|
+
tests_dir: 'e2e/tests',
|
|
49
|
+
baselines_dir: 'baselines',
|
|
50
|
+
},
|
|
51
|
+
ci: {
|
|
52
|
+
fail_on_baseline_diff: false,
|
|
53
|
+
parallel: 2,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const result = browserflowConfigSchema.safeParse(config);
|
|
57
|
+
expect(result.success).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('accepts browser alias for runtime', () => {
|
|
61
|
+
const config = {
|
|
62
|
+
project: { name: 'test' },
|
|
63
|
+
browser: {
|
|
64
|
+
engine: 'firefox',
|
|
65
|
+
headless: false,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('accepts numeric timeout', () => {
|
|
72
|
+
const config = {
|
|
73
|
+
project: { name: 'test' },
|
|
74
|
+
runtime: {
|
|
75
|
+
timeout: 30000,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('accepts duration string timeout', () => {
|
|
82
|
+
const config = {
|
|
83
|
+
project: { name: 'test' },
|
|
84
|
+
runtime: {
|
|
85
|
+
timeout: '1m30s',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('invalid configs', () => {
|
|
93
|
+
test('rejects missing project', () => {
|
|
94
|
+
const config = {
|
|
95
|
+
runtime: { browser: 'chromium' },
|
|
96
|
+
};
|
|
97
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('rejects missing project.name', () => {
|
|
101
|
+
const config = {
|
|
102
|
+
project: {},
|
|
103
|
+
};
|
|
104
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('rejects empty project.name', () => {
|
|
108
|
+
const config = {
|
|
109
|
+
project: { name: '' },
|
|
110
|
+
};
|
|
111
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('rejects invalid browser type', () => {
|
|
115
|
+
const config = {
|
|
116
|
+
project: { name: 'test' },
|
|
117
|
+
runtime: {
|
|
118
|
+
browser: 'chrome', // Invalid - should be chromium
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('rejects invalid port number', () => {
|
|
125
|
+
const config = {
|
|
126
|
+
project: { name: 'test' },
|
|
127
|
+
review: {
|
|
128
|
+
port: 70000, // Invalid - max is 65535
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('rejects negative viewport dimensions', () => {
|
|
135
|
+
const config = {
|
|
136
|
+
project: { name: 'test' },
|
|
137
|
+
runtime: {
|
|
138
|
+
viewport: { width: -100, height: 720 },
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('rejects negative max_retries', () => {
|
|
145
|
+
const config = {
|
|
146
|
+
project: { name: 'test' },
|
|
147
|
+
exploration: {
|
|
148
|
+
max_retries: -1,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
expect(browserflowConfigSchema.safeParse(config).success).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('validateBrowserflowConfig', () => {
|
|
157
|
+
test('returns true for valid config', () => {
|
|
158
|
+
const config = {
|
|
159
|
+
project: { name: 'test' },
|
|
160
|
+
};
|
|
161
|
+
expect(validateBrowserflowConfig(config)).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('returns false for invalid config', () => {
|
|
165
|
+
const config = {
|
|
166
|
+
project: {},
|
|
167
|
+
};
|
|
168
|
+
expect(validateBrowserflowConfig(config)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('parseBrowserflowConfig', () => {
|
|
173
|
+
test('returns data for valid config', () => {
|
|
174
|
+
const config = {
|
|
175
|
+
project: { name: 'test' },
|
|
176
|
+
};
|
|
177
|
+
const result = parseBrowserflowConfig(config);
|
|
178
|
+
expect(result.success).toBe(true);
|
|
179
|
+
expect(result.data?.project.name).toBe('test');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('returns error message for invalid config', () => {
|
|
183
|
+
const config = {
|
|
184
|
+
project: { name: '' },
|
|
185
|
+
};
|
|
186
|
+
const result = parseBrowserflowConfig(config);
|
|
187
|
+
expect(result.success).toBe(false);
|
|
188
|
+
expect(result.error).toContain('project.name');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for browserflow.yaml config files
|
|
3
|
+
*
|
|
4
|
+
* @see bf-cv6 for implementation task
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { durationSchema } from './spec-schema.js';
|
|
9
|
+
|
|
10
|
+
// Browser types supported by Playwright
|
|
11
|
+
export const browserTypeSchema = z.enum(['chromium', 'firefox', 'webkit']);
|
|
12
|
+
|
|
13
|
+
// Viewport configuration
|
|
14
|
+
export const viewportSchema = z.object({
|
|
15
|
+
width: z.number().int().positive(),
|
|
16
|
+
height: z.number().int().positive(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Project section
|
|
20
|
+
export const projectConfigSchema = z.object({
|
|
21
|
+
name: z.string().min(1),
|
|
22
|
+
base_url: z.string().url().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Runtime/browser section
|
|
26
|
+
export const runtimeConfigSchema = z.object({
|
|
27
|
+
browser: browserTypeSchema.optional().default('chromium'),
|
|
28
|
+
engine: browserTypeSchema.optional(), // Alias for browser
|
|
29
|
+
headless: z.boolean().optional().default(true),
|
|
30
|
+
viewport: viewportSchema.optional(),
|
|
31
|
+
timeout: z.union([durationSchema, z.number()]).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Locator preferences
|
|
35
|
+
export const locatorsConfigSchema = z.object({
|
|
36
|
+
prefer_testid: z.boolean().optional().default(true),
|
|
37
|
+
testid_attributes: z.array(z.string()).optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Exploration settings
|
|
41
|
+
export const explorationConfigSchema = z.object({
|
|
42
|
+
adapter: z.string().optional().default('claude'),
|
|
43
|
+
max_retries: z.number().int().nonnegative().optional().default(3),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Review UI settings
|
|
47
|
+
export const reviewConfigSchema = z.object({
|
|
48
|
+
port: z.number().int().min(1).max(65535).optional().default(8190),
|
|
49
|
+
auto_open: z.boolean().optional().default(true),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Output paths
|
|
53
|
+
export const outputConfigSchema = z.object({
|
|
54
|
+
tests_dir: z.string().optional().default('e2e/tests'),
|
|
55
|
+
baselines_dir: z.string().optional().default('baselines'),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// CI settings
|
|
59
|
+
export const ciConfigSchema = z.object({
|
|
60
|
+
fail_on_baseline_diff: z.boolean().optional().default(false),
|
|
61
|
+
parallel: z.number().int().positive().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Top-level browserflow.yaml config schema
|
|
65
|
+
export const browserflowConfigSchema = z.object({
|
|
66
|
+
project: projectConfigSchema,
|
|
67
|
+
runtime: runtimeConfigSchema.optional(),
|
|
68
|
+
browser: runtimeConfigSchema.optional(), // Alias for runtime
|
|
69
|
+
locators: locatorsConfigSchema.optional(),
|
|
70
|
+
exploration: explorationConfigSchema.optional(),
|
|
71
|
+
review: reviewConfigSchema.optional(),
|
|
72
|
+
output: outputConfigSchema.optional(),
|
|
73
|
+
ci: ciConfigSchema.optional(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Type exports
|
|
77
|
+
export type BrowserTypeConfig = z.infer<typeof browserTypeSchema>;
|
|
78
|
+
export type ViewportConfig = z.infer<typeof viewportSchema>;
|
|
79
|
+
export type ProjectConfig = z.infer<typeof projectConfigSchema>;
|
|
80
|
+
export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
|
|
81
|
+
export type LocatorsConfig = z.infer<typeof locatorsConfigSchema>;
|
|
82
|
+
export type ExplorationConfig = z.infer<typeof explorationConfigSchema>;
|
|
83
|
+
export type ReviewConfig = z.infer<typeof reviewConfigSchema>;
|
|
84
|
+
export type OutputConfig = z.infer<typeof outputConfigSchema>;
|
|
85
|
+
export type CiConfig = z.infer<typeof ciConfigSchema>;
|
|
86
|
+
export type BrowserflowConfig = z.infer<typeof browserflowConfigSchema>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validates a browserflow config object
|
|
90
|
+
*/
|
|
91
|
+
export function validateBrowserflowConfig(config: unknown): config is BrowserflowConfig {
|
|
92
|
+
return browserflowConfigSchema.safeParse(config).success;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parses and validates a browserflow config with error details
|
|
97
|
+
*/
|
|
98
|
+
export function parseBrowserflowConfig(config: unknown): {
|
|
99
|
+
success: boolean;
|
|
100
|
+
data?: BrowserflowConfig;
|
|
101
|
+
error?: string;
|
|
102
|
+
} {
|
|
103
|
+
const result = browserflowConfigSchema.safeParse(config);
|
|
104
|
+
if (result.success) {
|
|
105
|
+
return { success: true, data: result.data };
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
error: result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; '),
|
|
110
|
+
};
|
|
111
|
+
}
|