@browserflow-ai/exploration 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 (58) hide show
  1. package/dist/adapters/claude-cli.d.ts +57 -0
  2. package/dist/adapters/claude-cli.d.ts.map +1 -0
  3. package/dist/adapters/claude-cli.js +195 -0
  4. package/dist/adapters/claude-cli.js.map +1 -0
  5. package/dist/adapters/claude.d.ts +54 -0
  6. package/dist/adapters/claude.d.ts.map +1 -0
  7. package/dist/adapters/claude.js +160 -0
  8. package/dist/adapters/claude.js.map +1 -0
  9. package/dist/adapters/index.d.ts +6 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +4 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/types.d.ts +196 -0
  14. package/dist/adapters/types.d.ts.map +1 -0
  15. package/dist/adapters/types.js +3 -0
  16. package/dist/adapters/types.js.map +1 -0
  17. package/dist/agent-browser-session.d.ts +62 -0
  18. package/dist/agent-browser-session.d.ts.map +1 -0
  19. package/dist/agent-browser-session.js +272 -0
  20. package/dist/agent-browser-session.js.map +1 -0
  21. package/dist/evidence.d.ts +111 -0
  22. package/dist/evidence.d.ts.map +1 -0
  23. package/dist/evidence.js +144 -0
  24. package/dist/evidence.js.map +1 -0
  25. package/dist/explorer.d.ts +180 -0
  26. package/dist/explorer.d.ts.map +1 -0
  27. package/dist/explorer.js +393 -0
  28. package/dist/explorer.js.map +1 -0
  29. package/dist/index.d.ts +15 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +15 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/locator-candidates.d.ts +127 -0
  34. package/dist/locator-candidates.d.ts.map +1 -0
  35. package/dist/locator-candidates.js +358 -0
  36. package/dist/locator-candidates.js.map +1 -0
  37. package/dist/step-executor.d.ts +99 -0
  38. package/dist/step-executor.d.ts.map +1 -0
  39. package/dist/step-executor.js +646 -0
  40. package/dist/step-executor.js.map +1 -0
  41. package/package.json +34 -0
  42. package/src/adapters/claude-cli.test.ts +134 -0
  43. package/src/adapters/claude-cli.ts +240 -0
  44. package/src/adapters/claude.test.ts +195 -0
  45. package/src/adapters/claude.ts +190 -0
  46. package/src/adapters/index.ts +21 -0
  47. package/src/adapters/types.ts +207 -0
  48. package/src/agent-browser-session.test.ts +369 -0
  49. package/src/agent-browser-session.ts +349 -0
  50. package/src/evidence.test.ts +239 -0
  51. package/src/evidence.ts +203 -0
  52. package/src/explorer.test.ts +321 -0
  53. package/src/explorer.ts +565 -0
  54. package/src/index.ts +51 -0
  55. package/src/locator-candidates.test.ts +602 -0
  56. package/src/locator-candidates.ts +441 -0
  57. package/src/step-executor.test.ts +696 -0
  58. package/src/step-executor.ts +783 -0
@@ -0,0 +1,602 @@
1
+ // @browserflow-ai/exploration - Locator candidate generator tests
2
+ import { describe, it, expect, beforeEach } from 'bun:test';
3
+ import {
4
+ LocatorCandidateGenerator,
5
+ type LocatorCandidate,
6
+ type ElementInfo,
7
+ } from './locator-candidates';
8
+
9
+ describe('LocatorCandidateGenerator', () => {
10
+ let generator: LocatorCandidateGenerator;
11
+
12
+ beforeEach(() => {
13
+ generator = new LocatorCandidateGenerator();
14
+ });
15
+
16
+ describe('constructor', () => {
17
+ it('should initialize with default config', () => {
18
+ expect(generator.getMaxCandidates()).toBe(5);
19
+ expect(generator.getPreferredStrategies()).toEqual([
20
+ 'ref',
21
+ 'testid',
22
+ 'role',
23
+ 'text',
24
+ 'css',
25
+ ]);
26
+ });
27
+
28
+ it('should allow custom max candidates', () => {
29
+ const gen = new LocatorCandidateGenerator({ maxCandidates: 10 });
30
+ expect(gen.getMaxCandidates()).toBe(10);
31
+ });
32
+
33
+ it('should allow custom preferred strategies', () => {
34
+ const gen = new LocatorCandidateGenerator({
35
+ preferredStrategies: ['testid', 'css'],
36
+ });
37
+ expect(gen.getPreferredStrategies()).toEqual(['testid', 'css']);
38
+ });
39
+ });
40
+
41
+ describe('generateRefLocator', () => {
42
+ it('should generate ref locator with high confidence', () => {
43
+ const result = generator.generateRefLocator('e1');
44
+ expect(result).toEqual({
45
+ locator: '@e1',
46
+ type: 'ref',
47
+ confidence: 1.0,
48
+ description: 'Element reference from snapshot',
49
+ });
50
+ });
51
+ });
52
+
53
+ describe('generateTestIdLocator', () => {
54
+ it('should generate testid locator', () => {
55
+ const result = generator.generateTestIdLocator('submit-btn');
56
+ expect(result.locator).toBe('[data-testid="submit-btn"]');
57
+ expect(result.type).toBe('testid');
58
+ expect(result.confidence).toBe(0.95);
59
+ });
60
+ });
61
+
62
+ describe('generateRoleLocator', () => {
63
+ it('should generate role locator without name', () => {
64
+ const result = generator.generateRoleLocator('button');
65
+ expect(result.locator).toBe('role=button');
66
+ expect(result.type).toBe('role');
67
+ // Role without name has lower confidence (0.85) than role with name (0.9)
68
+ expect(result.confidence).toBe(0.85);
69
+ });
70
+
71
+ it('should generate role locator with name', () => {
72
+ const result = generator.generateRoleLocator('button', 'Submit');
73
+ expect(result.locator).toBe('role=button[name="Submit"]');
74
+ expect(result.description).toContain('Submit');
75
+ });
76
+ });
77
+
78
+ describe('generateTextLocator', () => {
79
+ it('should generate exact text locator', () => {
80
+ const result = generator.generateTextLocator('Submit', true);
81
+ expect(result.locator).toBe('text="Submit"');
82
+ expect(result.confidence).toBe(0.85);
83
+ });
84
+
85
+ it('should generate partial text locator', () => {
86
+ const result = generator.generateTextLocator('Submit', false);
87
+ expect(result.locator).toBe('text~="Submit"');
88
+ expect(result.confidence).toBe(0.7);
89
+ });
90
+ });
91
+
92
+ describe('generateCssLocator', () => {
93
+ it('should generate CSS selector locator', () => {
94
+ const result = generator.generateCssLocator('button.primary');
95
+ expect(result.locator).toBe('button.primary');
96
+ expect(result.type).toBe('css');
97
+ expect(result.confidence).toBe(0.8);
98
+ });
99
+ });
100
+
101
+ describe('generateCandidates', () => {
102
+ it('should find candidates matching query in snapshot', async () => {
103
+ const snapshot = {
104
+ tree: '<button ref="e1" data-testid="submit-btn">Submit</button>',
105
+ refs: {
106
+ e1: {
107
+ tag: 'button',
108
+ text: 'Submit',
109
+ testId: 'submit-btn',
110
+ role: 'button',
111
+ },
112
+ },
113
+ };
114
+
115
+ const candidates = await generator.generateCandidates('submit button', snapshot);
116
+
117
+ expect(candidates.length).toBeGreaterThan(0);
118
+ expect(candidates.length).toBeLessThanOrEqual(5);
119
+ });
120
+
121
+ it('should return empty array when no matching elements', async () => {
122
+ const snapshot = {
123
+ tree: '<div>Hello</div>',
124
+ refs: {},
125
+ };
126
+
127
+ const candidates = await generator.generateCandidates('submit button', snapshot);
128
+
129
+ expect(candidates).toEqual([]);
130
+ });
131
+
132
+ it('should respect maxCandidates setting', async () => {
133
+ const gen = new LocatorCandidateGenerator({ maxCandidates: 2 });
134
+ const snapshot = {
135
+ tree: '<button ref="e1" data-testid="btn">Click</button>',
136
+ refs: {
137
+ e1: {
138
+ tag: 'button',
139
+ text: 'Click',
140
+ testId: 'btn',
141
+ role: 'button',
142
+ ariaLabel: 'Click me',
143
+ },
144
+ },
145
+ };
146
+
147
+ const candidates = await gen.generateCandidates('button', snapshot);
148
+
149
+ expect(candidates.length).toBeLessThanOrEqual(2);
150
+ });
151
+ });
152
+
153
+ describe('generateDetailedCandidates', () => {
154
+ it('should return candidates with confidence scores', async () => {
155
+ const snapshot = {
156
+ tree: '<button ref="e1" data-testid="submit-btn">Submit</button>',
157
+ refs: {
158
+ e1: {
159
+ tag: 'button',
160
+ text: 'Submit',
161
+ testId: 'submit-btn',
162
+ role: 'button',
163
+ },
164
+ },
165
+ };
166
+
167
+ const candidates = await generator.generateDetailedCandidates('submit button', snapshot);
168
+
169
+ expect(candidates.length).toBeGreaterThan(0);
170
+ for (const candidate of candidates) {
171
+ expect(candidate).toHaveProperty('locator');
172
+ expect(candidate).toHaveProperty('type');
173
+ expect(candidate).toHaveProperty('confidence');
174
+ expect(candidate.confidence).toBeGreaterThanOrEqual(0);
175
+ expect(candidate.confidence).toBeLessThanOrEqual(1);
176
+ }
177
+ });
178
+
179
+ it('should order candidates by confidence (highest first)', async () => {
180
+ const snapshot = {
181
+ tree: '<button ref="e1" data-testid="submit">Submit Form</button>',
182
+ refs: {
183
+ e1: {
184
+ tag: 'button',
185
+ text: 'Submit Form',
186
+ testId: 'submit',
187
+ role: 'button',
188
+ },
189
+ },
190
+ };
191
+
192
+ const candidates = await generator.generateDetailedCandidates('submit button', snapshot);
193
+
194
+ if (candidates.length > 1) {
195
+ for (let i = 0; i < candidates.length - 1; i++) {
196
+ expect(candidates[i].confidence).toBeGreaterThanOrEqual(candidates[i + 1].confidence);
197
+ }
198
+ }
199
+ });
200
+
201
+ it('should always include CSS fallback as last resort', async () => {
202
+ const snapshot = {
203
+ tree: '<span ref="e1">Plain text</span>',
204
+ refs: {
205
+ e1: {
206
+ tag: 'span',
207
+ text: 'Plain text',
208
+ },
209
+ },
210
+ };
211
+
212
+ const candidates = await generator.generateDetailedCandidates('plain text element', snapshot);
213
+
214
+ // Should have at least a CSS fallback
215
+ expect(candidates.length).toBeGreaterThan(0);
216
+ const hasCSS = candidates.some((c) => c.type === 'css');
217
+ expect(hasCSS).toBe(true);
218
+ });
219
+
220
+ it('should generate testid candidate when data-testid present', async () => {
221
+ const snapshot = {
222
+ tree: '<button ref="e1" data-testid="my-button">Click</button>',
223
+ refs: {
224
+ e1: {
225
+ tag: 'button',
226
+ text: 'Click',
227
+ testId: 'my-button',
228
+ attributes: { 'data-testid': 'my-button' },
229
+ },
230
+ },
231
+ };
232
+
233
+ const candidates = await generator.generateDetailedCandidates('button', snapshot);
234
+
235
+ const testidCandidate = candidates.find((c) => c.type === 'testid');
236
+ expect(testidCandidate).toBeDefined();
237
+ expect(testidCandidate!.locator).toContain('my-button');
238
+ expect(testidCandidate!.confidence).toBe(0.95);
239
+ });
240
+
241
+ it('should generate role candidate when role is present', async () => {
242
+ const snapshot = {
243
+ tree: '<button ref="e1" role="button">Submit</button>',
244
+ refs: {
245
+ e1: {
246
+ tag: 'button',
247
+ text: 'Submit',
248
+ role: 'button',
249
+ ariaLabel: 'Submit',
250
+ },
251
+ },
252
+ };
253
+
254
+ const candidates = await generator.generateDetailedCandidates('submit', snapshot);
255
+
256
+ const roleCandidate = candidates.find((c) => c.type === 'role');
257
+ expect(roleCandidate).toBeDefined();
258
+ expect(roleCandidate!.confidence).toBeGreaterThanOrEqual(0.85);
259
+ });
260
+
261
+ it('should generate text candidate for visible text', async () => {
262
+ const snapshot = {
263
+ tree: '<a ref="e1">Learn more</a>',
264
+ refs: {
265
+ e1: {
266
+ tag: 'a',
267
+ text: 'Learn more',
268
+ },
269
+ },
270
+ };
271
+
272
+ const candidates = await generator.generateDetailedCandidates('learn more link', snapshot);
273
+
274
+ const textCandidate = candidates.find((c) => c.type === 'text');
275
+ expect(textCandidate).toBeDefined();
276
+ expect(textCandidate!.locator).toContain('Learn more');
277
+ });
278
+ });
279
+
280
+ describe('generateCandidatesForElement', () => {
281
+ it('should generate all applicable candidates for element info', async () => {
282
+ const element: ElementInfo = {
283
+ ref: 'e1',
284
+ tag: 'button',
285
+ text: 'Submit',
286
+ testId: 'submit-btn',
287
+ role: 'button',
288
+ ariaLabel: 'Submit form',
289
+ };
290
+
291
+ const candidates = await generator.generateCandidatesForElement(element);
292
+
293
+ // Should have: ref, testid, role, text, css (at minimum)
294
+ expect(candidates.length).toBeGreaterThanOrEqual(4);
295
+
296
+ // Check each strategy type is present
297
+ const types = candidates.map((c) => c.type);
298
+ expect(types).toContain('ref');
299
+ expect(types).toContain('testid');
300
+ expect(types).toContain('role');
301
+ expect(types).toContain('css');
302
+ });
303
+
304
+ it('should handle element without testid', async () => {
305
+ const element: ElementInfo = {
306
+ ref: 'e2',
307
+ tag: 'div',
308
+ text: 'Hello World',
309
+ className: 'greeting',
310
+ };
311
+
312
+ const candidates = await generator.generateCandidatesForElement(element);
313
+
314
+ // Should still work without testid
315
+ expect(candidates.length).toBeGreaterThan(0);
316
+ const types = candidates.map((c) => c.type);
317
+ expect(types).not.toContain('testid');
318
+ expect(types).toContain('ref');
319
+ expect(types).toContain('css');
320
+ });
321
+
322
+ it('should generate CSS selector using id when available', async () => {
323
+ const element: ElementInfo = {
324
+ ref: 'e3',
325
+ tag: 'input',
326
+ id: 'email-input',
327
+ };
328
+
329
+ const candidates = await generator.generateCandidatesForElement(element);
330
+
331
+ const cssCandidate = candidates.find((c) => c.type === 'css');
332
+ expect(cssCandidate).toBeDefined();
333
+ expect(cssCandidate!.locator).toContain('#email-input');
334
+ });
335
+
336
+ it('should generate CSS selector using class when no id', async () => {
337
+ const element: ElementInfo = {
338
+ ref: 'e4',
339
+ tag: 'button',
340
+ className: 'btn btn-primary',
341
+ };
342
+
343
+ const candidates = await generator.generateCandidatesForElement(element);
344
+
345
+ const cssCandidate = candidates.find((c) => c.type === 'css');
346
+ expect(cssCandidate).toBeDefined();
347
+ // Should use first class or a combination
348
+ expect(cssCandidate!.locator).toMatch(/button\.btn/);
349
+ });
350
+
351
+ it('should infer role from tag name when explicit role missing', async () => {
352
+ // Standard HTML5 elements have implicit roles
353
+ const element: ElementInfo = {
354
+ ref: 'e5',
355
+ tag: 'button',
356
+ text: 'Click me',
357
+ };
358
+
359
+ const candidates = await generator.generateCandidatesForElement(element);
360
+
361
+ const roleCandidate = candidates.find((c) => c.type === 'role');
362
+ expect(roleCandidate).toBeDefined();
363
+ expect(roleCandidate!.locator).toContain('button');
364
+ });
365
+
366
+ it('should not generate text candidate for very long text', async () => {
367
+ const element: ElementInfo = {
368
+ ref: 'e6',
369
+ tag: 'p',
370
+ text: 'A'.repeat(100), // Very long text
371
+ };
372
+
373
+ const candidates = await generator.generateCandidatesForElement(element);
374
+
375
+ // Should not include text strategy for very long text
376
+ const textCandidate = candidates.find((c) => c.type === 'text');
377
+ expect(textCandidate).toBeUndefined();
378
+ });
379
+
380
+ it('should handle elements with special characters in text', async () => {
381
+ const element: ElementInfo = {
382
+ ref: 'e7',
383
+ tag: 'span',
384
+ text: 'Price: $100.00',
385
+ };
386
+
387
+ const candidates = await generator.generateCandidatesForElement(element);
388
+
389
+ const textCandidate = candidates.find((c) => c.type === 'text');
390
+ expect(textCandidate).toBeDefined();
391
+ // Should properly escape or handle special chars
392
+ });
393
+ });
394
+
395
+ describe('element matching', () => {
396
+ it('should match elements by text content', async () => {
397
+ const snapshot = {
398
+ tree: '<button ref="e1">Login</button><button ref="e2">Register</button>',
399
+ refs: {
400
+ e1: { tag: 'button', text: 'Login' },
401
+ e2: { tag: 'button', text: 'Register' },
402
+ },
403
+ };
404
+
405
+ const candidates = await generator.generateDetailedCandidates('login', snapshot);
406
+
407
+ // Should match the Login button, not Register
408
+ expect(candidates.length).toBeGreaterThan(0);
409
+ expect(candidates.some((c) => c.locator.includes('Login') || c.locator.includes('e1'))).toBe(true);
410
+ });
411
+
412
+ it('should match elements by tag name', async () => {
413
+ const snapshot = {
414
+ tree: '<input ref="e1" type="text"><button ref="e2">Submit</button>',
415
+ refs: {
416
+ e1: { tag: 'input', type: 'text' },
417
+ e2: { tag: 'button', text: 'Submit' },
418
+ },
419
+ };
420
+
421
+ const candidates = await generator.generateDetailedCandidates('input field', snapshot);
422
+
423
+ expect(candidates.length).toBeGreaterThan(0);
424
+ // Should match the input element
425
+ });
426
+
427
+ it('should match elements by role', async () => {
428
+ const snapshot = {
429
+ tree: '<nav ref="e1"><a ref="e2">Link</a></nav>',
430
+ refs: {
431
+ e1: { tag: 'nav', role: 'navigation' },
432
+ e2: { tag: 'a', text: 'Link', role: 'link' },
433
+ },
434
+ };
435
+
436
+ const candidates = await generator.generateDetailedCandidates('navigation', snapshot);
437
+
438
+ expect(candidates.length).toBeGreaterThan(0);
439
+ });
440
+
441
+ it('should handle case-insensitive matching', async () => {
442
+ const snapshot = {
443
+ tree: '<button ref="e1">SUBMIT</button>',
444
+ refs: {
445
+ e1: { tag: 'button', text: 'SUBMIT' },
446
+ },
447
+ };
448
+
449
+ const candidates = await generator.generateDetailedCandidates('submit', snapshot);
450
+
451
+ expect(candidates.length).toBeGreaterThan(0);
452
+ });
453
+
454
+ it('should match by aria-label', async () => {
455
+ const snapshot = {
456
+ tree: '<button ref="e1" aria-label="Close dialog">X</button>',
457
+ refs: {
458
+ e1: { tag: 'button', text: 'X', ariaLabel: 'Close dialog' },
459
+ },
460
+ };
461
+
462
+ const candidates = await generator.generateDetailedCandidates('close dialog', snapshot);
463
+
464
+ expect(candidates.length).toBeGreaterThan(0);
465
+ });
466
+ });
467
+
468
+ describe('edge cases', () => {
469
+ it('should handle empty snapshot', async () => {
470
+ const snapshot = {
471
+ tree: '',
472
+ refs: {},
473
+ };
474
+
475
+ const candidates = await generator.generateDetailedCandidates('anything', snapshot);
476
+
477
+ expect(candidates).toEqual([]);
478
+ });
479
+
480
+ it('should handle empty query', async () => {
481
+ const snapshot = {
482
+ tree: '<button ref="e1">Click</button>',
483
+ refs: {
484
+ e1: { tag: 'button', text: 'Click' },
485
+ },
486
+ };
487
+
488
+ const candidates = await generator.generateDetailedCandidates('', snapshot);
489
+
490
+ expect(candidates).toEqual([]);
491
+ });
492
+
493
+ it('should handle elements with no attributes', async () => {
494
+ const snapshot = {
495
+ tree: '<div ref="e1"></div>',
496
+ refs: {
497
+ e1: { tag: 'div' },
498
+ },
499
+ };
500
+
501
+ const candidates = await generator.generateDetailedCandidates('div', snapshot);
502
+
503
+ // Should still generate at least ref and css candidates
504
+ if (candidates.length > 0) {
505
+ const types = candidates.map((c) => c.type);
506
+ expect(types).toContain('ref');
507
+ expect(types).toContain('css');
508
+ }
509
+ });
510
+
511
+ it('should handle numeric refs', async () => {
512
+ const snapshot = {
513
+ tree: '<span ref="123">Text</span>',
514
+ refs: {
515
+ '123': { tag: 'span', text: 'Text' },
516
+ },
517
+ };
518
+
519
+ const candidates = await generator.generateDetailedCandidates('text', snapshot);
520
+
521
+ const refCandidate = candidates.find((c) => c.type === 'ref');
522
+ if (refCandidate) {
523
+ expect(refCandidate.locator).toBe('@123');
524
+ }
525
+ });
526
+ });
527
+
528
+ describe('confidence scoring', () => {
529
+ it('should score testid higher than role', async () => {
530
+ const element: ElementInfo = {
531
+ ref: 'e1',
532
+ tag: 'button',
533
+ text: 'Submit',
534
+ testId: 'submit-btn',
535
+ role: 'button',
536
+ };
537
+
538
+ const candidates = await generator.generateCandidatesForElement(element);
539
+
540
+ const testidCandidate = candidates.find((c) => c.type === 'testid');
541
+ const roleCandidate = candidates.find((c) => c.type === 'role');
542
+
543
+ expect(testidCandidate).toBeDefined();
544
+ expect(roleCandidate).toBeDefined();
545
+ expect(testidCandidate!.confidence).toBeGreaterThan(roleCandidate!.confidence);
546
+ });
547
+
548
+ it('should score role higher than text', async () => {
549
+ const element: ElementInfo = {
550
+ ref: 'e1',
551
+ tag: 'button',
552
+ text: 'Submit',
553
+ role: 'button',
554
+ };
555
+
556
+ const candidates = await generator.generateCandidatesForElement(element);
557
+
558
+ const roleCandidate = candidates.find((c) => c.type === 'role');
559
+ const textCandidate = candidates.find((c) => c.type === 'text');
560
+
561
+ expect(roleCandidate).toBeDefined();
562
+ expect(textCandidate).toBeDefined();
563
+ expect(roleCandidate!.confidence).toBeGreaterThan(textCandidate!.confidence);
564
+ });
565
+
566
+ it('should score text higher than css', async () => {
567
+ const element: ElementInfo = {
568
+ ref: 'e1',
569
+ tag: 'span',
570
+ text: 'Click',
571
+ className: 'action',
572
+ };
573
+
574
+ const candidates = await generator.generateCandidatesForElement(element);
575
+
576
+ const textCandidate = candidates.find((c) => c.type === 'text');
577
+ const cssCandidate = candidates.find((c) => c.type === 'css');
578
+
579
+ expect(textCandidate).toBeDefined();
580
+ expect(cssCandidate).toBeDefined();
581
+ expect(textCandidate!.confidence).toBeGreaterThan(cssCandidate!.confidence);
582
+ });
583
+
584
+ it('should give ref highest confidence', async () => {
585
+ const element: ElementInfo = {
586
+ ref: 'e1',
587
+ tag: 'button',
588
+ text: 'Submit',
589
+ testId: 'submit-btn',
590
+ };
591
+
592
+ const candidates = await generator.generateCandidatesForElement(element);
593
+
594
+ const refCandidate = candidates.find((c) => c.type === 'ref');
595
+ const testidCandidate = candidates.find((c) => c.type === 'testid');
596
+
597
+ expect(refCandidate).toBeDefined();
598
+ expect(refCandidate!.confidence).toBe(1.0);
599
+ expect(refCandidate!.confidence).toBeGreaterThan(testidCandidate!.confidence);
600
+ });
601
+ });
602
+ });