cdp-skill 1.0.8 → 1.0.15

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 (51) hide show
  1. package/README.md +80 -35
  2. package/SKILL.md +157 -241
  3. package/install.js +1 -0
  4. package/package.json +1 -1
  5. package/src/aria/index.js +8 -0
  6. package/src/aria/output-processor.js +173 -0
  7. package/src/aria/role-query.js +1229 -0
  8. package/src/aria/snapshot.js +459 -0
  9. package/src/aria.js +251 -50
  10. package/src/cdp/browser.js +22 -4
  11. package/src/cdp-skill.js +246 -69
  12. package/src/dom/LazyResolver.js +634 -0
  13. package/src/dom/click-executor.js +366 -94
  14. package/src/dom/element-locator.js +34 -25
  15. package/src/dom/fill-executor.js +83 -50
  16. package/src/dom/index.js +3 -0
  17. package/src/page/dialog-handler.js +119 -0
  18. package/src/page/page-controller.js +236 -3
  19. package/src/runner/context-helpers.js +33 -55
  20. package/src/runner/execute-dynamic.js +8 -7
  21. package/src/runner/execute-form.js +11 -11
  22. package/src/runner/execute-input.js +2 -2
  23. package/src/runner/execute-interaction.js +105 -126
  24. package/src/runner/execute-navigation.js +14 -29
  25. package/src/runner/execute-query.js +17 -11
  26. package/src/runner/step-executors.js +225 -84
  27. package/src/runner/step-registry.js +1064 -0
  28. package/src/runner/step-validator.js +16 -754
  29. package/src/tests/Aria.test.js +1025 -0
  30. package/src/tests/ClickExecutor.test.js +170 -50
  31. package/src/tests/ContextHelpers.test.js +41 -30
  32. package/src/tests/ExecuteBrowser.test.js +572 -0
  33. package/src/tests/ExecuteDynamic.test.js +2 -457
  34. package/src/tests/ExecuteForm.test.js +700 -0
  35. package/src/tests/ExecuteInput.test.js +540 -0
  36. package/src/tests/ExecuteInteraction.test.js +319 -0
  37. package/src/tests/ExecuteQuery.test.js +820 -0
  38. package/src/tests/FillExecutor.test.js +89 -37
  39. package/src/tests/LazyResolver.test.js +383 -0
  40. package/src/tests/StepValidator.test.js +224 -78
  41. package/src/tests/TestRunner.test.js +38 -27
  42. package/src/tests/integration.test.js +2 -1
  43. package/src/types.js +9 -9
  44. package/src/utils/backoff.js +118 -0
  45. package/src/utils/cdp-helpers.js +130 -0
  46. package/src/utils/devices.js +140 -0
  47. package/src/utils/errors.js +242 -0
  48. package/src/utils/index.js +65 -0
  49. package/src/utils/temp.js +75 -0
  50. package/src/utils/validators.js +433 -0
  51. package/src/utils.js +14 -1142
@@ -120,17 +120,48 @@ describe('ClickExecutor', () => {
120
120
  if (params?.expression?.includes('location.href')) {
121
121
  return { result: { value: 'https://example.com' } };
122
122
  }
123
- if (params?.expression?.includes('__ariaRefs')) {
123
+ // LazyResolver: first queries __ariaRefMeta for metadata
124
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get') && !params?.expression?.includes('lazyResolveRef')) {
125
+ return { result: { value: { selector: '#btn', role: 'button', name: 'Submit' } } };
126
+ }
127
+ // LazyResolver: then resolves element by selector
128
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
129
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
130
+ }
131
+ // LazyResolver: gets objectId
132
+ if (params?.expression?.includes('querySelector') && !params?.expression?.includes('lazyResolveRef')) {
133
+ return { result: { objectId: 'obj-123' } };
134
+ }
135
+ // Browser-side lazy resolution for click verification/execution - return success
136
+ if (params?.expression?.includes('lazyResolveRef') && params?.expression?.includes('click')) {
137
+ return { result: { value: { success: true } } };
138
+ }
139
+ // Browser-side lazy resolution for event setup
140
+ if (params?.expression?.includes('lazyResolveRef')) {
141
+ return { result: { value: null } };
142
+ }
143
+ // Verification check
144
+ if (params?.expression?.includes('__clickVerifyEl') || params?.expression?.includes('targetReceived')) {
124
145
  return { result: { value: { targetReceived: true } } };
125
146
  }
126
147
  return { result: { objectId: 'obj-123' } };
127
148
  }
149
+ if (method === 'Runtime.callFunctionOn') {
150
+ // Visibility check after lazy resolution
151
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
152
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
153
+ }
154
+ if (params?.functionDeclaration?.includes('__clickReceived')) {
155
+ return { result: { value: true } };
156
+ }
157
+ return { result: { value: { success: true, targetReceived: true } } };
158
+ }
128
159
  return {};
129
160
  });
130
161
 
131
- const result = await executor.execute({ ref: 's1e1' });
162
+ const result = await executor.execute({ ref: 'f0s1e1' });
132
163
  assert.strictEqual(result.clicked, true);
133
- assert.strictEqual(result.ref, 's1e1');
164
+ assert.strictEqual(result.ref, 'f0s1e1');
134
165
  });
135
166
 
136
167
  it('should detect ref from string selector pattern', async () => {
@@ -139,17 +170,44 @@ describe('ClickExecutor', () => {
139
170
  if (params?.expression?.includes('location.href')) {
140
171
  return { result: { value: 'https://example.com' } };
141
172
  }
142
- if (params?.expression?.includes('__ariaRefs')) {
173
+ // LazyResolver: queries __ariaRefMeta for metadata
174
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get') && !params?.expression?.includes('lazyResolveRef')) {
175
+ return { result: { value: { selector: '#btn', role: 'button', name: 'Submit' } } };
176
+ }
177
+ // LazyResolver: resolves element
178
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
179
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
180
+ }
181
+ if (params?.expression?.includes('querySelector') && !params?.expression?.includes('lazyResolveRef')) {
182
+ return { result: { objectId: 'obj-123' } };
183
+ }
184
+ // Browser-side lazy resolution for click - return success
185
+ if (params?.expression?.includes('lazyResolveRef') && params?.expression?.includes('click')) {
186
+ return { result: { value: { success: true } } };
187
+ }
188
+ if (params?.expression?.includes('lazyResolveRef')) {
189
+ return { result: { value: null } };
190
+ }
191
+ if (params?.expression?.includes('__clickVerifyEl') || params?.expression?.includes('targetReceived')) {
143
192
  return { result: { value: { targetReceived: true } } };
144
193
  }
145
194
  return { result: { objectId: 'obj-123' } };
146
195
  }
196
+ if (method === 'Runtime.callFunctionOn') {
197
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
198
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
199
+ }
200
+ if (params?.functionDeclaration?.includes('__clickReceived')) {
201
+ return { result: { value: true } };
202
+ }
203
+ return { result: { value: { success: true, targetReceived: true } } };
204
+ }
147
205
  return {};
148
206
  });
149
207
 
150
- const result = await executor.execute('s1e12');
208
+ const result = await executor.execute('f0s1e12');
151
209
  assert.strictEqual(result.clicked, true);
152
- assert.strictEqual(result.ref, 's1e12');
210
+ assert.strictEqual(result.ref, 'f0s1e12');
153
211
  });
154
212
 
155
213
  it('should handle text-based click', async () => {
@@ -392,7 +450,7 @@ describe('ClickExecutor', () => {
392
450
  const noAriaExecutor = createClickExecutor(mockSession, mockElementLocator, mockInputEmulator);
393
451
 
394
452
  // Without ariaSnapshot, the executor falls back to selector-based click
395
- // which will fail to find the element "s1e1" (since it's not a valid CSS selector)
453
+ // which will fail to find the element "f0s1e1" (since it's not a valid CSS selector)
396
454
  mockSession.send = mock.fn(async (method, params) => {
397
455
  if (method === 'Runtime.evaluate') {
398
456
  if (params?.expression?.includes('location.href')) {
@@ -404,100 +462,162 @@ describe('ClickExecutor', () => {
404
462
  });
405
463
 
406
464
  await assert.rejects(
407
- () => noAriaExecutor.execute({ ref: 's1e1' }),
465
+ () => noAriaExecutor.execute({ ref: 'f0s1e1' }),
408
466
  (err) => {
409
- // Without ariaSnapshot, the ref 's1e1' is treated as selector, failing to find
467
+ // Without ariaSnapshot, the ref 'f0s1e1' is treated as selector, failing to find
410
468
  return err.message.includes('not found') || err.message.includes('ariaSnapshot');
411
469
  }
412
470
  );
413
471
  });
414
472
 
415
- it('should return stale warning when ref element is stale', async () => {
416
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
417
- box: { x: 50, y: 50, width: 100, height: 40 },
418
- isVisible: true,
419
- stale: true
420
- }));
421
-
473
+ it('should throw when ref element cannot be resolved (lazy resolution)', async () => {
474
+ // LazyResolver returns null when metadata not found or element can't be resolved
422
475
  mockSession.send = mock.fn(async (method, params) => {
423
- if (method === 'Runtime.evaluate' && params?.expression?.includes('location.href')) {
424
- return { result: { value: 'https://example.com' } };
476
+ if (method === 'Runtime.evaluate') {
477
+ if (params?.expression?.includes('location.href')) {
478
+ return { result: { value: 'https://example.com' } };
479
+ }
480
+ // LazyResolver: no metadata found
481
+ if (params?.expression?.includes('__ariaRefMeta')) {
482
+ return { result: { value: null } };
483
+ }
484
+ return { result: { value: null } };
425
485
  }
426
486
  return {};
427
487
  });
428
488
 
429
- const result = await executor.execute({ ref: 's1e1' });
430
- assert.strictEqual(result.clicked, false);
431
- assert.strictEqual(result.stale, true);
432
- assert.ok(result.warning.includes('no longer attached'));
489
+ await assert.rejects(
490
+ async () => await executor.execute({ ref: 'f0s1e1' }),
491
+ (err) => err.message.includes('not found')
492
+ );
433
493
  });
434
494
 
435
495
  it('should return warning when ref element is not visible', async () => {
436
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
437
- box: { x: 50, y: 50, width: 100, height: 40 },
438
- isVisible: false,
439
- stale: false
440
- }));
441
-
442
496
  mockSession.send = mock.fn(async (method, params) => {
443
- if (method === 'Runtime.evaluate' && params?.expression?.includes('location.href')) {
444
- return { result: { value: 'https://example.com' } };
497
+ if (method === 'Runtime.evaluate') {
498
+ if (params?.expression?.includes('location.href')) {
499
+ return { result: { value: 'https://example.com' } };
500
+ }
501
+ // LazyResolver: metadata found
502
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get')) {
503
+ return { result: { value: { selector: '#btn', role: 'button', name: 'Submit' } } };
504
+ }
505
+ // LazyResolver: element found
506
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
507
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
508
+ }
509
+ if (params?.expression?.includes('querySelector')) {
510
+ return { result: { objectId: 'obj-123' } };
511
+ }
512
+ if (params?.expression?.includes('lazyResolveRef')) {
513
+ return { result: { value: null } };
514
+ }
515
+ return { result: { objectId: 'obj-123' } };
516
+ }
517
+ if (method === 'Runtime.callFunctionOn') {
518
+ // Visibility check returns not visible
519
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
520
+ return { result: { value: { isVisible: false, box: { x: 50, y: 50, width: 100, height: 40 } } } };
521
+ }
522
+ return { result: { value: { found: false } } };
445
523
  }
446
524
  return {};
447
525
  });
448
526
 
449
- const result = await executor.execute({ ref: 's1e1' });
527
+ const result = await executor.execute({ ref: 'f0s1e1' });
450
528
  assert.strictEqual(result.clicked, false);
451
529
  assert.ok(result.warning.includes('not visible'));
452
530
  });
453
531
 
454
- it('should succeed when ref element is re-resolved via metadata', async () => {
455
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
456
- box: { x: 50, y: 50, width: 100, height: 40 },
457
- isVisible: true,
458
- stale: false,
459
- reResolved: true
460
- }));
461
-
532
+ it('should succeed when ref element is resolved via lazy resolution', async () => {
462
533
  mockSession.send = mock.fn(async (method, params) => {
463
534
  if (method === 'Runtime.evaluate') {
464
535
  if (params?.expression?.includes('location.href')) {
465
536
  return { result: { value: 'https://example.com' } };
466
537
  }
467
- if (params?.expression?.includes('__ariaRefs')) {
538
+ // LazyResolver: metadata found
539
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get') && !params?.expression?.includes('lazyResolveRef')) {
540
+ return { result: { value: { selector: '#btn', role: 'button', name: 'Submit' } } };
541
+ }
542
+ // LazyResolver: element found
543
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
544
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
545
+ }
546
+ if (params?.expression?.includes('querySelector') && !params?.expression?.includes('lazyResolveRef')) {
547
+ return { result: { objectId: 'obj-123' } };
548
+ }
549
+ // Browser-side lazy resolution for click - return success
550
+ if (params?.expression?.includes('lazyResolveRef') && params?.expression?.includes('click')) {
551
+ return { result: { value: { success: true } } };
552
+ }
553
+ if (params?.expression?.includes('lazyResolveRef')) {
554
+ return { result: { value: null } };
555
+ }
556
+ if (params?.expression?.includes('__clickVerifyEl') || params?.expression?.includes('targetReceived')) {
468
557
  return { result: { value: { targetReceived: true } } };
469
558
  }
470
559
  return { result: { objectId: 'obj-123' } };
471
560
  }
561
+ if (method === 'Runtime.callFunctionOn') {
562
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
563
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
564
+ }
565
+ if (params?.functionDeclaration?.includes('__clickReceived')) {
566
+ return { result: { value: true } };
567
+ }
568
+ return { result: { value: { success: true, targetReceived: true } } };
569
+ }
472
570
  return {};
473
571
  });
474
572
 
475
- const result = await executor.execute({ ref: 's1e1' });
573
+ const result = await executor.execute({ ref: 'f0s1e1' });
476
574
  assert.strictEqual(result.clicked, true);
477
- assert.strictEqual(result.ref, 's1e1');
575
+ assert.strictEqual(result.ref, 'f0s1e1');
478
576
  });
479
577
 
480
578
  it('should click non-visible element with force option', async () => {
481
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
482
- box: { x: 50, y: 50, width: 100, height: 40 },
483
- isVisible: false,
484
- stale: false
485
- }));
486
-
487
579
  mockSession.send = mock.fn(async (method, params) => {
488
580
  if (method === 'Runtime.evaluate') {
489
581
  if (params?.expression?.includes('location.href')) {
490
582
  return { result: { value: 'https://example.com' } };
491
583
  }
492
- if (params?.expression?.includes('__ariaRefs')) {
584
+ // LazyResolver: metadata found
585
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get') && !params?.expression?.includes('lazyResolveRef')) {
586
+ return { result: { value: { selector: '#btn', role: 'button', name: 'Submit' } } };
587
+ }
588
+ // LazyResolver: element found
589
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
590
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 100, height: 40 } } } };
591
+ }
592
+ if (params?.expression?.includes('querySelector') && !params?.expression?.includes('lazyResolveRef')) {
593
+ return { result: { objectId: 'obj-123' } };
594
+ }
595
+ // Browser-side lazy resolution for click - return success
596
+ if (params?.expression?.includes('lazyResolveRef') && params?.expression?.includes('click')) {
597
+ return { result: { value: { success: true } } };
598
+ }
599
+ if (params?.expression?.includes('lazyResolveRef')) {
600
+ return { result: { value: null } };
601
+ }
602
+ if (params?.expression?.includes('__clickVerifyEl') || params?.expression?.includes('targetReceived')) {
493
603
  return { result: { value: { targetReceived: true } } };
494
604
  }
495
605
  return { result: { objectId: 'obj-123' } };
496
606
  }
607
+ if (method === 'Runtime.callFunctionOn') {
608
+ // Visibility check returns not visible, but force=true will proceed
609
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
610
+ return { result: { value: { isVisible: false, box: { x: 50, y: 50, width: 100, height: 40 } } } };
611
+ }
612
+ if (params?.functionDeclaration?.includes('__clickReceived')) {
613
+ return { result: { value: true } };
614
+ }
615
+ return { result: { value: { success: true, targetReceived: true } } };
616
+ }
497
617
  return {};
498
618
  });
499
619
 
500
- const result = await executor.execute({ ref: 's1e1', force: true });
620
+ const result = await executor.execute({ ref: 'f0s1e1', force: true });
501
621
  assert.strictEqual(result.clicked, true);
502
622
  });
503
623
  });
@@ -30,9 +30,7 @@ describe('ContextHelpers', () => {
30
30
  });
31
31
 
32
32
  it('should contain form step types', () => {
33
- assert.ok(STEP_TYPES.includes('fillForm'));
34
- assert.ok(STEP_TYPES.includes('type'));
35
- assert.ok(STEP_TYPES.includes('select'));
33
+ assert.ok(STEP_TYPES.includes('selectText'));
36
34
  assert.ok(STEP_TYPES.includes('selectOption'));
37
35
  assert.ok(STEP_TYPES.includes('submit'));
38
36
  });
@@ -40,19 +38,31 @@ describe('ContextHelpers', () => {
40
38
  it('should contain tab step types', () => {
41
39
  assert.ok(STEP_TYPES.includes('listTabs'));
42
40
  assert.ok(STEP_TYPES.includes('closeTab'));
43
- assert.ok(STEP_TYPES.includes('openTab'));
41
+ assert.ok(STEP_TYPES.includes('newTab'));
42
+ assert.ok(STEP_TYPES.includes('switchTab'));
44
43
  });
45
44
 
46
- it('should contain iframe step types', () => {
47
- assert.ok(STEP_TYPES.includes('switchToFrame'));
48
- assert.ok(STEP_TYPES.includes('switchToMainFrame'));
49
- assert.ok(STEP_TYPES.includes('listFrames'));
45
+ it('should contain unified frame step', () => {
46
+ assert.ok(STEP_TYPES.includes('frame'));
47
+ assert.ok(!STEP_TYPES.includes('switchToFrame'));
48
+ assert.ok(!STEP_TYPES.includes('switchToMainFrame'));
49
+ assert.ok(!STEP_TYPES.includes('listFrames'));
50
50
  });
51
51
 
52
- it('should contain coordinate-based step types', () => {
53
- assert.ok(STEP_TYPES.includes('refAt'));
52
+ it('should contain unified elementsAt step', () => {
54
53
  assert.ok(STEP_TYPES.includes('elementsAt'));
55
- assert.ok(STEP_TYPES.includes('elementsNear'));
54
+ assert.ok(!STEP_TYPES.includes('refAt'));
55
+ assert.ok(!STEP_TYPES.includes('elementsNear'));
56
+ });
57
+
58
+ it('should contain sleep step', () => {
59
+ assert.ok(STEP_TYPES.includes('sleep'));
60
+ });
61
+
62
+ it('should not contain removed steps', () => {
63
+ assert.ok(!STEP_TYPES.includes('fillForm'));
64
+ assert.ok(!STEP_TYPES.includes('fillActive'));
65
+ assert.ok(!STEP_TYPES.includes('eval'));
56
66
  });
57
67
  });
58
68
 
@@ -65,7 +75,6 @@ describe('ContextHelpers', () => {
65
75
  assert.ok(VISUAL_ACTIONS.includes('goto'));
66
76
  assert.ok(VISUAL_ACTIONS.includes('click'));
67
77
  assert.ok(VISUAL_ACTIONS.includes('fill'));
68
- assert.ok(VISUAL_ACTIONS.includes('type'));
69
78
  assert.ok(VISUAL_ACTIONS.includes('hover'));
70
79
  assert.ok(VISUAL_ACTIONS.includes('press'));
71
80
  assert.ok(VISUAL_ACTIONS.includes('scroll'));
@@ -76,14 +85,16 @@ describe('ContextHelpers', () => {
76
85
  assert.ok(VISUAL_ACTIONS.includes('query'));
77
86
  assert.ok(VISUAL_ACTIONS.includes('queryAll'));
78
87
  assert.ok(VISUAL_ACTIONS.includes('inspect'));
79
- assert.ok(VISUAL_ACTIONS.includes('eval'));
80
- assert.ok(VISUAL_ACTIONS.includes('extract'));
88
+ assert.ok(VISUAL_ACTIONS.includes('pageFunction'));
89
+ assert.ok(VISUAL_ACTIONS.includes('get'));
81
90
  });
82
91
 
83
92
  it('should not contain non-visual actions', () => {
84
93
  assert.ok(!VISUAL_ACTIONS.includes('cookies'));
85
94
  assert.ok(!VISUAL_ACTIONS.includes('listTabs'));
86
95
  assert.ok(!VISUAL_ACTIONS.includes('closeTab'));
96
+ assert.ok(!VISUAL_ACTIONS.includes('eval'));
97
+ assert.ok(!VISUAL_ACTIONS.includes('fillForm'));
87
98
  });
88
99
  });
89
100
 
@@ -122,8 +133,8 @@ describe('ContextHelpers', () => {
122
133
  });
123
134
 
124
135
  it('should describe click with ref', () => {
125
- const result = buildActionContext('click', { ref: 's1e1' }, {});
126
- assert.strictEqual(result, 'Clicked [ref=s1e1]');
136
+ const result = buildActionContext('click', { ref: 'f0s1e1' }, {});
137
+ assert.strictEqual(result, 'Clicked [ref=f0s1e1]');
127
138
  });
128
139
 
129
140
  it('should describe click with text', () => {
@@ -165,11 +176,6 @@ describe('ContextHelpers', () => {
165
176
  assert.strictEqual(result, 'Typed in "Email"');
166
177
  });
167
178
 
168
- it('should describe type with selector', () => {
169
- const result = buildActionContext('type', { selector: '#search' }, {});
170
- assert.strictEqual(result, 'Typed in #search');
171
- });
172
-
173
179
  it('should return generic for no params', () => {
174
180
  const result = buildActionContext('fill', {}, {});
175
181
  assert.strictEqual(result, 'Typed in input');
@@ -222,10 +228,6 @@ describe('ContextHelpers', () => {
222
228
  assert.strictEqual(result, 'Typed');
223
229
  });
224
230
 
225
- it('should return Typed for type step', () => {
226
- const result = buildCommandContext([{ type: { selector: '#input', text: 'test' } }]);
227
- assert.strictEqual(result, 'Typed');
228
- });
229
231
 
230
232
  it('should return Pressed key for press step', () => {
231
233
  const result = buildCommandContext([{ press: 'Enter' }]);
@@ -237,13 +239,13 @@ describe('ContextHelpers', () => {
237
239
  assert.strictEqual(result, 'Navigated');
238
240
  });
239
241
 
240
- it('should return Navigated for openTab step', () => {
241
- const result = buildCommandContext([{ openTab: 'https://example.com' }]);
242
+ it('should return Navigated for newTab step', () => {
243
+ const result = buildCommandContext([{ newTab: 'https://example.com' }]);
242
244
  assert.strictEqual(result, 'Navigated');
243
245
  });
244
246
 
245
- it('should return Selected for select step', () => {
246
- const result = buildCommandContext([{ select: '#dropdown' }]);
247
+ it('should return Selected for selectText step', () => {
248
+ const result = buildCommandContext([{ selectText: '#dropdown' }]);
247
249
  assert.strictEqual(result, 'Selected');
248
250
  });
249
251
 
@@ -282,7 +284,16 @@ describe('ContextHelpers', () => {
282
284
  mockPageController = {
283
285
  session: {
284
286
  send: mock.fn(async () => ({ result: { value: '' } }))
285
- }
287
+ },
288
+ evaluateInFrame: mock.fn(async (expression, options = {}) => {
289
+ const params = {
290
+ expression,
291
+ returnByValue: options.returnByValue !== false,
292
+ awaitPromise: options.awaitPromise || false
293
+ };
294
+ return mockPageController.session.send('Runtime.evaluate', params);
295
+ }),
296
+ getFrameContext: mock.fn(() => null)
286
297
  };
287
298
  });
288
299