@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.
Files changed (57) hide show
  1. package/dist/config-schema.d.ts +354 -0
  2. package/dist/config-schema.d.ts.map +1 -0
  3. package/dist/config-schema.js +83 -0
  4. package/dist/config-schema.js.map +1 -0
  5. package/dist/config.d.ts +107 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +5 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/duration.d.ts +39 -0
  10. package/dist/duration.d.ts.map +1 -0
  11. package/dist/duration.js +111 -0
  12. package/dist/duration.js.map +1 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +20 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/locator-object.d.ts +556 -0
  18. package/dist/locator-object.d.ts.map +1 -0
  19. package/dist/locator-object.js +114 -0
  20. package/dist/locator-object.js.map +1 -0
  21. package/dist/lockfile.d.ts +1501 -0
  22. package/dist/lockfile.d.ts.map +1 -0
  23. package/dist/lockfile.js +86 -0
  24. package/dist/lockfile.js.map +1 -0
  25. package/dist/run-store.d.ts +81 -0
  26. package/dist/run-store.d.ts.map +1 -0
  27. package/dist/run-store.js +181 -0
  28. package/dist/run-store.js.map +1 -0
  29. package/dist/spec-loader.d.ts +17 -0
  30. package/dist/spec-loader.d.ts.map +1 -0
  31. package/dist/spec-loader.js +37 -0
  32. package/dist/spec-loader.js.map +1 -0
  33. package/dist/spec-schema.d.ts +1411 -0
  34. package/dist/spec-schema.d.ts.map +1 -0
  35. package/dist/spec-schema.js +239 -0
  36. package/dist/spec-schema.js.map +1 -0
  37. package/package.json +45 -0
  38. package/schemas/browserflow.schema.json +220 -0
  39. package/schemas/lockfile.schema.json +568 -0
  40. package/schemas/spec-v2.schema.json +413 -0
  41. package/src/config-schema.test.ts +190 -0
  42. package/src/config-schema.ts +111 -0
  43. package/src/config.ts +112 -0
  44. package/src/duration.test.ts +175 -0
  45. package/src/duration.ts +128 -0
  46. package/src/index.ts +136 -0
  47. package/src/json-schemas.test.ts +374 -0
  48. package/src/locator-object.test.ts +323 -0
  49. package/src/locator-object.ts +250 -0
  50. package/src/lockfile.test.ts +345 -0
  51. package/src/lockfile.ts +209 -0
  52. package/src/run-store.test.ts +232 -0
  53. package/src/run-store.ts +240 -0
  54. package/src/spec-loader.test.ts +181 -0
  55. package/src/spec-loader.ts +47 -0
  56. package/src/spec-schema.test.ts +360 -0
  57. 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
+ }