@browserflow-ai/generator 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 (41) hide show
  1. package/dist/config-emit.d.ts +41 -0
  2. package/dist/config-emit.d.ts.map +1 -0
  3. package/dist/config-emit.js +191 -0
  4. package/dist/config-emit.js.map +1 -0
  5. package/dist/index.d.ts +16 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +15 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/locator-emit.d.ts +76 -0
  10. package/dist/locator-emit.d.ts.map +1 -0
  11. package/dist/locator-emit.js +239 -0
  12. package/dist/locator-emit.js.map +1 -0
  13. package/dist/locator-emit.test.d.ts +6 -0
  14. package/dist/locator-emit.test.d.ts.map +1 -0
  15. package/dist/locator-emit.test.js +425 -0
  16. package/dist/locator-emit.test.js.map +1 -0
  17. package/dist/playwright-ts.d.ts +97 -0
  18. package/dist/playwright-ts.d.ts.map +1 -0
  19. package/dist/playwright-ts.js +373 -0
  20. package/dist/playwright-ts.js.map +1 -0
  21. package/dist/playwright-ts.test.d.ts +6 -0
  22. package/dist/playwright-ts.test.d.ts.map +1 -0
  23. package/dist/playwright-ts.test.js +548 -0
  24. package/dist/playwright-ts.test.js.map +1 -0
  25. package/dist/visual-checks.d.ts +76 -0
  26. package/dist/visual-checks.d.ts.map +1 -0
  27. package/dist/visual-checks.js +195 -0
  28. package/dist/visual-checks.js.map +1 -0
  29. package/dist/visual-checks.test.d.ts +6 -0
  30. package/dist/visual-checks.test.d.ts.map +1 -0
  31. package/dist/visual-checks.test.js +188 -0
  32. package/dist/visual-checks.test.js.map +1 -0
  33. package/package.json +34 -0
  34. package/src/config-emit.ts +253 -0
  35. package/src/index.ts +57 -0
  36. package/src/locator-emit.test.ts +533 -0
  37. package/src/locator-emit.ts +310 -0
  38. package/src/playwright-ts.test.ts +704 -0
  39. package/src/playwright-ts.ts +519 -0
  40. package/src/visual-checks.test.ts +232 -0
  41. package/src/visual-checks.ts +294 -0
@@ -0,0 +1,533 @@
1
+ /**
2
+ * locator-emit.test.ts
3
+ * Tests for the locator-to-code conversion module.
4
+ */
5
+
6
+ import { describe, it, expect } from 'bun:test';
7
+ import type { LegacyLocatorObject } from '@browserflow-ai/core';
8
+
9
+ // Use the legacy interface for backwards compatibility
10
+ type LocatorObject = LegacyLocatorObject;
11
+ import {
12
+ generateLocatorCode,
13
+ resolveLocatorCode,
14
+ escapeString,
15
+ selectorToLocator,
16
+ roleLocator,
17
+ textLocator,
18
+ testIdLocator,
19
+ } from './locator-emit.js';
20
+
21
+ describe('escapeString', () => {
22
+ it('escapes single quotes', () => {
23
+ expect(escapeString("it's")).toBe("it\\'s");
24
+ });
25
+
26
+ it('escapes backslashes', () => {
27
+ expect(escapeString('path\\to\\file')).toBe('path\\\\to\\\\file');
28
+ });
29
+
30
+ it('escapes newlines', () => {
31
+ expect(escapeString('line1\nline2')).toBe('line1\\nline2');
32
+ });
33
+
34
+ it('escapes carriage returns', () => {
35
+ expect(escapeString('line1\rline2')).toBe('line1\\rline2');
36
+ });
37
+
38
+ it('escapes tabs', () => {
39
+ expect(escapeString('col1\tcol2')).toBe('col1\\tcol2');
40
+ });
41
+
42
+ it('handles complex strings with multiple special characters', () => {
43
+ expect(escapeString("it's a\ntest\\path")).toBe("it\\'s a\\ntest\\\\path");
44
+ });
45
+
46
+ it('returns empty string unchanged', () => {
47
+ expect(escapeString('')).toBe('');
48
+ });
49
+
50
+ it('returns normal string unchanged', () => {
51
+ expect(escapeString('normal text')).toBe('normal text');
52
+ });
53
+ });
54
+
55
+ describe('generateLocatorCode', () => {
56
+ describe('CSS selector locators', () => {
57
+ it('generates code from CSS selector', () => {
58
+ const locator: LocatorObject = { selector: 'button.primary' };
59
+ expect(generateLocatorCode(locator)).toBe("page.locator('button.primary')");
60
+ });
61
+
62
+ it('escapes special characters in CSS selectors', () => {
63
+ const locator: LocatorObject = { selector: "[data-test='value']" };
64
+ expect(generateLocatorCode(locator)).toBe("page.locator('[data-test=\\'value\\']')");
65
+ });
66
+
67
+ it('uses custom page variable', () => {
68
+ const locator: LocatorObject = { selector: 'button' };
69
+ expect(generateLocatorCode(locator, { pageVar: 'frame' })).toBe("frame.locator('button')");
70
+ });
71
+ });
72
+
73
+ describe('getByRole locators', () => {
74
+ it('generates getByRole with just role', () => {
75
+ const locator: LocatorObject = {
76
+ method: 'getByRole',
77
+ args: { role: 'button' },
78
+ };
79
+ expect(generateLocatorCode(locator)).toBe("page.getByRole('button')");
80
+ });
81
+
82
+ it('generates getByRole with name', () => {
83
+ const locator: LocatorObject = {
84
+ method: 'getByRole',
85
+ args: { role: 'button', name: 'Submit' },
86
+ };
87
+ expect(generateLocatorCode(locator)).toBe("page.getByRole('button', { name: 'Submit' })");
88
+ });
89
+
90
+ it('generates getByRole with exact match', () => {
91
+ const locator: LocatorObject = {
92
+ method: 'getByRole',
93
+ args: { role: 'heading', name: 'Welcome', exact: true },
94
+ };
95
+ expect(generateLocatorCode(locator)).toBe(
96
+ "page.getByRole('heading', { name: 'Welcome', exact: true })"
97
+ );
98
+ });
99
+
100
+ it('escapes special characters in name', () => {
101
+ const locator: LocatorObject = {
102
+ method: 'getByRole',
103
+ args: { role: 'button', name: "Don't click" },
104
+ };
105
+ expect(generateLocatorCode(locator)).toBe(
106
+ "page.getByRole('button', { name: 'Don\\'t click' })"
107
+ );
108
+ });
109
+
110
+ it('throws error if role is missing', () => {
111
+ const locator: LocatorObject = {
112
+ method: 'getByRole',
113
+ args: { name: 'Submit' },
114
+ };
115
+ expect(() => generateLocatorCode(locator)).toThrow('getByRole requires a role argument');
116
+ });
117
+ });
118
+
119
+ describe('getByText locators', () => {
120
+ it('generates getByText with text', () => {
121
+ const locator: LocatorObject = {
122
+ method: 'getByText',
123
+ args: { text: 'Hello World' },
124
+ };
125
+ expect(generateLocatorCode(locator)).toBe("page.getByText('Hello World')");
126
+ });
127
+
128
+ it('generates getByText with exact option', () => {
129
+ const locator: LocatorObject = {
130
+ method: 'getByText',
131
+ args: { text: 'Hello', exact: true },
132
+ };
133
+ expect(generateLocatorCode(locator)).toBe("page.getByText('Hello', { exact: true })");
134
+ });
135
+
136
+ it('throws error if text is missing', () => {
137
+ const locator: LocatorObject = {
138
+ method: 'getByText',
139
+ args: {},
140
+ };
141
+ expect(() => generateLocatorCode(locator)).toThrow('getByText requires a text argument');
142
+ });
143
+ });
144
+
145
+ describe('getByLabel locators', () => {
146
+ it('generates getByLabel with text', () => {
147
+ const locator: LocatorObject = {
148
+ method: 'getByLabel',
149
+ args: { text: 'Email' },
150
+ };
151
+ expect(generateLocatorCode(locator)).toBe("page.getByLabel('Email')");
152
+ });
153
+
154
+ it('generates getByLabel with exact option', () => {
155
+ const locator: LocatorObject = {
156
+ method: 'getByLabel',
157
+ args: { text: 'Username', exact: false },
158
+ };
159
+ expect(generateLocatorCode(locator)).toBe("page.getByLabel('Username', { exact: false })");
160
+ });
161
+
162
+ it('throws error if text is missing', () => {
163
+ const locator: LocatorObject = {
164
+ method: 'getByLabel',
165
+ args: {},
166
+ };
167
+ expect(() => generateLocatorCode(locator)).toThrow('getByLabel requires a text argument');
168
+ });
169
+ });
170
+
171
+ describe('getByPlaceholder locators', () => {
172
+ it('generates getByPlaceholder with text', () => {
173
+ const locator: LocatorObject = {
174
+ method: 'getByPlaceholder',
175
+ args: { text: 'Enter email...' },
176
+ };
177
+ expect(generateLocatorCode(locator)).toBe("page.getByPlaceholder('Enter email...')");
178
+ });
179
+
180
+ it('generates getByPlaceholder with exact option', () => {
181
+ const locator: LocatorObject = {
182
+ method: 'getByPlaceholder',
183
+ args: { text: 'Search', exact: true },
184
+ };
185
+ expect(generateLocatorCode(locator)).toBe(
186
+ "page.getByPlaceholder('Search', { exact: true })"
187
+ );
188
+ });
189
+
190
+ it('throws error if text is missing', () => {
191
+ const locator: LocatorObject = {
192
+ method: 'getByPlaceholder',
193
+ args: {},
194
+ };
195
+ expect(() => generateLocatorCode(locator)).toThrow(
196
+ 'getByPlaceholder requires a text argument'
197
+ );
198
+ });
199
+ });
200
+
201
+ describe('getByTestId locators', () => {
202
+ it('generates getByTestId with testId', () => {
203
+ const locator: LocatorObject = {
204
+ method: 'getByTestId',
205
+ args: { testId: 'submit-button' },
206
+ };
207
+ expect(generateLocatorCode(locator)).toBe("page.getByTestId('submit-button')");
208
+ });
209
+
210
+ it('escapes special characters in testId', () => {
211
+ const locator: LocatorObject = {
212
+ method: 'getByTestId',
213
+ args: { testId: "user's-form" },
214
+ };
215
+ expect(generateLocatorCode(locator)).toBe("page.getByTestId('user\\'s-form')");
216
+ });
217
+
218
+ it('throws error if testId is missing', () => {
219
+ const locator: LocatorObject = {
220
+ method: 'getByTestId',
221
+ args: {},
222
+ };
223
+ expect(() => generateLocatorCode(locator)).toThrow('getByTestId requires a testId argument');
224
+ });
225
+ });
226
+
227
+ describe('getByAltText locators', () => {
228
+ it('generates getByAltText with text', () => {
229
+ const locator: LocatorObject = {
230
+ method: 'getByAltText',
231
+ args: { text: 'Company Logo' },
232
+ };
233
+ expect(generateLocatorCode(locator)).toBe("page.getByAltText('Company Logo')");
234
+ });
235
+
236
+ it('generates getByAltText with exact option', () => {
237
+ const locator: LocatorObject = {
238
+ method: 'getByAltText',
239
+ args: { text: 'Logo', exact: true },
240
+ };
241
+ expect(generateLocatorCode(locator)).toBe("page.getByAltText('Logo', { exact: true })");
242
+ });
243
+
244
+ it('throws error if text is missing', () => {
245
+ const locator: LocatorObject = {
246
+ method: 'getByAltText',
247
+ args: {},
248
+ };
249
+ expect(() => generateLocatorCode(locator)).toThrow('getByAltText requires a text argument');
250
+ });
251
+ });
252
+
253
+ describe('getByTitle locators', () => {
254
+ it('generates getByTitle with text', () => {
255
+ const locator: LocatorObject = {
256
+ method: 'getByTitle',
257
+ args: { text: 'Settings' },
258
+ };
259
+ expect(generateLocatorCode(locator)).toBe("page.getByTitle('Settings')");
260
+ });
261
+
262
+ it('generates getByTitle with exact option', () => {
263
+ const locator: LocatorObject = {
264
+ method: 'getByTitle',
265
+ args: { text: 'Close', exact: false },
266
+ };
267
+ expect(generateLocatorCode(locator)).toBe("page.getByTitle('Close', { exact: false })");
268
+ });
269
+
270
+ it('throws error if text is missing', () => {
271
+ const locator: LocatorObject = {
272
+ method: 'getByTitle',
273
+ args: {},
274
+ };
275
+ expect(() => generateLocatorCode(locator)).toThrow('getByTitle requires a text argument');
276
+ });
277
+ });
278
+
279
+ describe('locator method', () => {
280
+ it('generates locator with selector', () => {
281
+ const locator: LocatorObject = {
282
+ method: 'locator',
283
+ args: { selector: 'div.container' },
284
+ };
285
+ expect(generateLocatorCode(locator)).toBe("page.locator('div.container')");
286
+ });
287
+
288
+ it('throws error if selector is missing', () => {
289
+ const locator: LocatorObject = {
290
+ method: 'locator',
291
+ args: {},
292
+ };
293
+ expect(() => generateLocatorCode(locator)).toThrow('locator requires a selector argument');
294
+ });
295
+ });
296
+
297
+ describe('ref-based locators', () => {
298
+ it('generates locator from element ref', () => {
299
+ const locator: LocatorObject = { ref: '@e23' };
300
+ expect(generateLocatorCode(locator)).toBe("page.locator('[data-ref=\"@e23\"]')");
301
+ });
302
+
303
+ it('escapes special characters in ref', () => {
304
+ const locator: LocatorObject = { ref: "@e'23" };
305
+ expect(generateLocatorCode(locator)).toBe("page.locator('[data-ref=\"@e\\'23\"]')");
306
+ });
307
+ });
308
+
309
+ describe('chainFirst option', () => {
310
+ it('adds .first() when chainFirst is true', () => {
311
+ const locator: LocatorObject = { selector: 'button' };
312
+ expect(generateLocatorCode(locator, { chainFirst: true })).toBe(
313
+ "page.locator('button').first()"
314
+ );
315
+ });
316
+
317
+ it('works with method-based locators', () => {
318
+ const locator: LocatorObject = {
319
+ method: 'getByRole',
320
+ args: { role: 'button' },
321
+ };
322
+ expect(generateLocatorCode(locator, { chainFirst: true })).toBe(
323
+ "page.getByRole('button').first()"
324
+ );
325
+ });
326
+
327
+ it('does not add .first() when chainFirst is false', () => {
328
+ const locator: LocatorObject = { selector: 'button' };
329
+ expect(generateLocatorCode(locator, { chainFirst: false })).toBe("page.locator('button')");
330
+ });
331
+ });
332
+
333
+ describe('nth handling', () => {
334
+ it('adds .first() when nth is 0', () => {
335
+ const locator: LocatorObject = { selector: 'button' };
336
+ expect(generateLocatorCode(locator, { nth: 0 })).toBe("page.locator('button').first()");
337
+ });
338
+
339
+ it('adds .last() when nth is -1', () => {
340
+ const locator: LocatorObject = { selector: 'button' };
341
+ expect(generateLocatorCode(locator, { nth: -1 })).toBe("page.locator('button').last()");
342
+ });
343
+
344
+ it('adds .nth(n) for positive indices', () => {
345
+ const locator: LocatorObject = { selector: 'button' };
346
+ expect(generateLocatorCode(locator, { nth: 2 })).toBe("page.locator('button').nth(2)");
347
+ });
348
+
349
+ it('adds .nth(n) for negative indices other than -1', () => {
350
+ const locator: LocatorObject = { selector: 'button' };
351
+ expect(generateLocatorCode(locator, { nth: -2 })).toBe("page.locator('button').nth(-2)");
352
+ });
353
+
354
+ it('works with method-based locators', () => {
355
+ const locator: LocatorObject = {
356
+ method: 'getByRole',
357
+ args: { role: 'listitem' },
358
+ };
359
+ expect(generateLocatorCode(locator, { nth: 3 })).toBe(
360
+ "page.getByRole('listitem').nth(3)"
361
+ );
362
+ });
363
+
364
+ it('nth takes precedence over chainFirst', () => {
365
+ const locator: LocatorObject = { selector: 'button' };
366
+ // When nth is specified, it should use nth handling, not chainFirst
367
+ expect(generateLocatorCode(locator, { nth: 2, chainFirst: true })).toBe(
368
+ "page.locator('button').nth(2)"
369
+ );
370
+ });
371
+ });
372
+
373
+ describe('scoping (within)', () => {
374
+ it('chains parent locator before main locator', () => {
375
+ const parent: LocatorObject = {
376
+ method: 'getByTestId',
377
+ args: { testId: 'form' },
378
+ };
379
+ const locator: LocatorObject = {
380
+ method: 'getByRole',
381
+ args: { role: 'button' },
382
+ };
383
+ expect(generateLocatorCode(locator, { within: parent })).toBe(
384
+ "page.getByTestId('form').getByRole('button')"
385
+ );
386
+ });
387
+
388
+ it('handles nested scoping with CSS selector parent', () => {
389
+ const parent: LocatorObject = { selector: '.dialog' };
390
+ const locator: LocatorObject = {
391
+ method: 'getByText',
392
+ args: { text: 'Close' },
393
+ };
394
+ expect(generateLocatorCode(locator, { within: parent })).toBe(
395
+ "page.locator('.dialog').getByText('Close')"
396
+ );
397
+ });
398
+
399
+ it('combines scoping with nth', () => {
400
+ const parent: LocatorObject = {
401
+ method: 'getByTestId',
402
+ args: { testId: 'list' },
403
+ };
404
+ const locator: LocatorObject = {
405
+ method: 'getByRole',
406
+ args: { role: 'listitem' },
407
+ };
408
+ expect(generateLocatorCode(locator, { within: parent, nth: 0 })).toBe(
409
+ "page.getByTestId('list').getByRole('listitem').first()"
410
+ );
411
+ });
412
+
413
+ it('respects custom pageVar with within', () => {
414
+ const parent: LocatorObject = {
415
+ method: 'getByTestId',
416
+ args: { testId: 'modal' },
417
+ };
418
+ const locator: LocatorObject = {
419
+ method: 'getByRole',
420
+ args: { role: 'button', name: 'Close' },
421
+ };
422
+ expect(generateLocatorCode(locator, { within: parent, pageVar: 'frame' })).toBe(
423
+ "frame.getByTestId('modal').getByRole('button', { name: 'Close' })"
424
+ );
425
+ });
426
+ });
427
+
428
+ describe('error handling', () => {
429
+ it('throws error for locator without method, selector, or ref', () => {
430
+ const locator: LocatorObject = {};
431
+ expect(() => generateLocatorCode(locator)).toThrow(
432
+ 'LocatorObject must have either method+args, selector, or ref'
433
+ );
434
+ });
435
+
436
+ it('throws error for unknown locator method', () => {
437
+ const locator: LocatorObject = {
438
+ method: 'unknownMethod' as any,
439
+ args: {},
440
+ };
441
+ expect(() => generateLocatorCode(locator)).toThrow('Unknown locator method: unknownMethod');
442
+ });
443
+ });
444
+ });
445
+
446
+ describe('resolveLocatorCode', () => {
447
+ it('uses method-based locator when available', () => {
448
+ const locator: LocatorObject = {
449
+ method: 'getByRole',
450
+ args: { role: 'button' },
451
+ };
452
+ expect(resolveLocatorCode(locator, 'button.fallback')).toBe("page.getByRole('button')");
453
+ });
454
+
455
+ it('uses selector from locator when method not available', () => {
456
+ const locator: LocatorObject = { selector: 'button.primary' };
457
+ expect(resolveLocatorCode(locator, 'button.fallback')).toBe("page.locator('button.primary')");
458
+ });
459
+
460
+ it('uses fallback selector when locator has no method or selector', () => {
461
+ expect(resolveLocatorCode(undefined, 'button.fallback')).toBe(
462
+ "page.locator('button.fallback')"
463
+ );
464
+ });
465
+
466
+ it('throws error when no locator or fallback available', () => {
467
+ expect(() => resolveLocatorCode(undefined, undefined)).toThrow(
468
+ 'No locator or selector available'
469
+ );
470
+ });
471
+
472
+ it('passes options through', () => {
473
+ const locator: LocatorObject = { selector: 'button' };
474
+ expect(resolveLocatorCode(locator, undefined, { pageVar: 'frame', chainFirst: true })).toBe(
475
+ "frame.locator('button').first()"
476
+ );
477
+ });
478
+ });
479
+
480
+ describe('helper functions', () => {
481
+ describe('selectorToLocator', () => {
482
+ it('creates locator from selector string', () => {
483
+ const locator = selectorToLocator('button.primary');
484
+ expect(locator).toEqual({ selector: 'button.primary' });
485
+ });
486
+ });
487
+
488
+ describe('roleLocator', () => {
489
+ it('creates locator for getByRole with just role', () => {
490
+ const locator = roleLocator('button');
491
+ expect(locator).toEqual({
492
+ method: 'getByRole',
493
+ args: { role: 'button' },
494
+ });
495
+ });
496
+
497
+ it('creates locator for getByRole with options', () => {
498
+ const locator = roleLocator('button', { name: 'Submit', exact: true });
499
+ expect(locator).toEqual({
500
+ method: 'getByRole',
501
+ args: { role: 'button', name: 'Submit', exact: true },
502
+ });
503
+ });
504
+ });
505
+
506
+ describe('textLocator', () => {
507
+ it('creates locator for getByText with just text', () => {
508
+ const locator = textLocator('Hello');
509
+ expect(locator).toEqual({
510
+ method: 'getByText',
511
+ args: { text: 'Hello' },
512
+ });
513
+ });
514
+
515
+ it('creates locator for getByText with options', () => {
516
+ const locator = textLocator('Hello', { exact: true });
517
+ expect(locator).toEqual({
518
+ method: 'getByText',
519
+ args: { text: 'Hello', exact: true },
520
+ });
521
+ });
522
+ });
523
+
524
+ describe('testIdLocator', () => {
525
+ it('creates locator for getByTestId', () => {
526
+ const locator = testIdLocator('submit-btn');
527
+ expect(locator).toEqual({
528
+ method: 'getByTestId',
529
+ args: { testId: 'submit-btn' },
530
+ });
531
+ });
532
+ });
533
+ });