cdp-skill 1.0.14 → 1.0.16

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.
@@ -126,9 +126,24 @@ describe('FillExecutor', () => {
126
126
  it('should fill by ref', async () => {
127
127
  mockSession.send = mock.fn(async (method, params) => {
128
128
  if (method === 'Runtime.evaluate') {
129
+ // LazyResolver: queries __ariaRefMeta for metadata
130
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get')) {
131
+ return { result: { value: { selector: '#input', role: 'textbox', name: 'Username' } } };
132
+ }
133
+ // LazyResolver: element found
134
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
135
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
136
+ }
137
+ if (params?.expression?.includes('querySelector')) {
138
+ return { result: { objectId: 'obj-123' } };
139
+ }
129
140
  return { result: { objectId: 'obj-123' } };
130
141
  }
131
142
  if (method === 'Runtime.callFunctionOn') {
143
+ // Visibility and box check after lazy resolution
144
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
145
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
146
+ }
132
147
  if (params?.functionDeclaration?.includes('scrollIntoView')) {
133
148
  return { result: {} };
134
149
  }
@@ -143,18 +158,32 @@ describe('FillExecutor', () => {
143
158
  return {};
144
159
  });
145
160
 
146
- const result = await executor.execute({ ref: 's1e1', value: 'ref value' });
161
+ const result = await executor.execute({ ref: 'f0s1e1', value: 'ref value' });
147
162
  assert.strictEqual(result.filled, true);
148
- assert.strictEqual(result.ref, 's1e1');
163
+ assert.strictEqual(result.ref, 'f0s1e1');
149
164
  assert.strictEqual(result.method, 'insertText');
150
165
  });
151
166
 
152
167
  it('should detect ref from selector pattern', async () => {
153
168
  mockSession.send = mock.fn(async (method, params) => {
154
169
  if (method === 'Runtime.evaluate') {
170
+ // LazyResolver: queries __ariaRefMeta for metadata
171
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get')) {
172
+ return { result: { value: { selector: '#input', role: 'textbox', name: 'Username' } } };
173
+ }
174
+ // LazyResolver: element found
175
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
176
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
177
+ }
178
+ if (params?.expression?.includes('querySelector')) {
179
+ return { result: { objectId: 'obj-123' } };
180
+ }
155
181
  return { result: { objectId: 'obj-123' } };
156
182
  }
157
183
  if (method === 'Runtime.callFunctionOn') {
184
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
185
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
186
+ }
158
187
  if (params?.functionDeclaration?.includes('scrollIntoView')) {
159
188
  return { result: {} };
160
189
  }
@@ -166,9 +195,9 @@ describe('FillExecutor', () => {
166
195
  return {};
167
196
  });
168
197
 
169
- const result = await executor.execute({ selector: 's1e5', value: 'test' });
198
+ const result = await executor.execute({ selector: 'f0s1e5', value: 'test' });
170
199
  assert.strictEqual(result.filled, true);
171
- assert.strictEqual(result.ref, 's1e5');
200
+ assert.strictEqual(result.ref, 'f0s1e5');
172
201
  });
173
202
 
174
203
  it('should fill by label', async () => {
@@ -441,9 +470,23 @@ describe('FillExecutor', () => {
441
470
  it('should handle refs in batch', async () => {
442
471
  mockSession.send = mock.fn(async (method, params) => {
443
472
  if (method === 'Runtime.evaluate') {
473
+ // LazyResolver: queries __ariaRefMeta for metadata
474
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get')) {
475
+ return { result: { value: { selector: '#input', role: 'textbox', name: 'Input' } } };
476
+ }
477
+ // LazyResolver: element found
478
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
479
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
480
+ }
481
+ if (params?.expression?.includes('querySelector')) {
482
+ return { result: { objectId: 'obj-123' } };
483
+ }
444
484
  return { result: { objectId: 'obj-123' } };
445
485
  }
446
486
  if (method === 'Runtime.callFunctionOn') {
487
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
488
+ return { result: { value: { isVisible: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
489
+ }
447
490
  if (params?.functionDeclaration?.includes('scrollIntoView')) {
448
491
  return { result: {} };
449
492
  }
@@ -456,8 +499,8 @@ describe('FillExecutor', () => {
456
499
  });
457
500
 
458
501
  const result = await executor.executeBatch({
459
- 's1e1': 'value1',
460
- 's1e2': 'value2'
502
+ 'f0s1e1': 'value1',
503
+ 'f0s1e2': 'value2'
461
504
  });
462
505
 
463
506
  assert.strictEqual(result.total, 2);
@@ -466,45 +509,54 @@ describe('FillExecutor', () => {
466
509
  });
467
510
 
468
511
  describe('ref-based fill edge cases', () => {
469
- it('should require ariaSnapshot for ref-based fills', async () => {
470
- const noAriaExecutor = createFillExecutor(mockSession, mockElementLocator, mockInputEmulator);
471
-
472
- // Without ariaSnapshot, the fill by ref won't work because ref requires ariaSnapshot
473
- // The executor treats ref: 's1e1' as needing ariaSnapshot
474
- await assert.rejects(
475
- () => noAriaExecutor.execute({ ref: 's1e1', value: 'test' }),
476
- (err) => {
477
- // May fail with "requires selector, ref, or label" because ref is only valid with ariaSnapshot
478
- return err.message.includes('requires') || err.message.includes('ariaSnapshot');
512
+ it('should throw when ref element cannot be resolved (lazy resolution)', async () => {
513
+ // LazyResolver returns null when metadata not found
514
+ mockSession.send = mock.fn(async (method, params) => {
515
+ if (method === 'Runtime.evaluate') {
516
+ if (params?.expression?.includes('__ariaRefMeta')) {
517
+ return { result: { value: null } };
518
+ }
519
+ return { result: { value: null } };
479
520
  }
480
- );
481
- });
482
-
483
- it('should throw when ref element is stale', async () => {
484
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
485
- box: { x: 50, y: 50, width: 200, height: 30 },
486
- isVisible: true,
487
- stale: true
488
- }));
521
+ return {};
522
+ });
489
523
 
490
524
  await assert.rejects(
491
- () => executor.execute({ ref: 's1e1', value: 'test' }),
525
+ () => executor.execute({ ref: 'f0s1e1', value: 'test' }),
492
526
  (err) => {
493
- assert.ok(err.message.includes('no longer attached'));
494
- return true;
527
+ return err.message.includes('not found');
495
528
  }
496
529
  );
497
530
  });
498
531
 
499
532
  it('should throw when ref element is not visible', async () => {
500
- mockAriaSnapshot.getElementByRef = mock.fn(async () => ({
501
- box: { x: 50, y: 50, width: 200, height: 30 },
502
- isVisible: false,
503
- stale: false
504
- }));
533
+ mockSession.send = mock.fn(async (method, params) => {
534
+ if (method === 'Runtime.evaluate') {
535
+ // LazyResolver: metadata found
536
+ if (params?.expression?.includes('__ariaRefMeta') && params?.expression?.includes('get')) {
537
+ return { result: { value: { selector: '#input', role: 'textbox', name: 'Input' } } };
538
+ }
539
+ // LazyResolver: element found
540
+ if (params?.expression?.includes('found') && params?.expression?.includes('box')) {
541
+ return { result: { value: { found: true, box: { x: 50, y: 50, width: 200, height: 30 } } } };
542
+ }
543
+ if (params?.expression?.includes('querySelector')) {
544
+ return { result: { objectId: 'obj-123' } };
545
+ }
546
+ return { result: { objectId: 'obj-123' } };
547
+ }
548
+ if (method === 'Runtime.callFunctionOn') {
549
+ // Visibility check returns not visible
550
+ if (params?.functionDeclaration?.includes('getComputedStyle') && params?.functionDeclaration?.includes('isVisible')) {
551
+ return { result: { value: { isVisible: false, box: { x: 50, y: 50, width: 200, height: 30 } } } };
552
+ }
553
+ return { result: { value: { editable: true } } };
554
+ }
555
+ return {};
556
+ });
505
557
 
506
558
  await assert.rejects(
507
- () => executor.execute({ ref: 's1e1', value: 'test' }),
559
+ () => executor.execute({ ref: 'f0s1e1', value: 'test' }),
508
560
  (err) => {
509
561
  assert.ok(err.message.includes('not visible'));
510
562
  return true;
@@ -516,7 +568,7 @@ describe('FillExecutor', () => {
516
568
  mockAriaSnapshot.getElementByRef = mock.fn(async () => null);
517
569
 
518
570
  await assert.rejects(
519
- () => executor.execute({ ref: 's1e99', value: 'test' }),
571
+ () => executor.execute({ ref: 'f0s1e99', value: 'test' }),
520
572
  (err) => {
521
573
  assert.ok(err.message.includes('not found'));
522
574
  return true;
@@ -0,0 +1,383 @@
1
+ import { describe, it, beforeEach, mock } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { createLazyResolver } from '../dom/LazyResolver.js';
4
+
5
+ describe('LazyResolver', () => {
6
+ let mockSession;
7
+ let resolver;
8
+
9
+ beforeEach(() => {
10
+ mockSession = {
11
+ send: mock.fn()
12
+ };
13
+ resolver = createLazyResolver(mockSession);
14
+ });
15
+
16
+ describe('createLazyResolver', () => {
17
+ it('should throw if session is not provided', () => {
18
+ assert.throws(() => createLazyResolver(null), /CDP session is required/);
19
+ });
20
+
21
+ it('should create a resolver with all methods', () => {
22
+ assert.ok(typeof resolver.resolveRef === 'function');
23
+ assert.ok(typeof resolver.resolveSelector === 'function');
24
+ assert.ok(typeof resolver.resolveText === 'function');
25
+ assert.ok(typeof resolver.resolveByRoleAndName === 'function');
26
+ assert.ok(typeof resolver.resolveThroughShadowDOM === 'function');
27
+ });
28
+ });
29
+
30
+ describe('resolveSelector', () => {
31
+ it('should return null for empty selector', async () => {
32
+ const result = await resolver.resolveSelector('');
33
+ assert.strictEqual(result, null);
34
+ });
35
+
36
+ it('should return null for non-string selector', async () => {
37
+ const result = await resolver.resolveSelector(123);
38
+ assert.strictEqual(result, null);
39
+ });
40
+
41
+ it('should return null when element not found', async () => {
42
+ mockSession.send.mock.mockImplementation((method) => {
43
+ if (method === 'Runtime.evaluate') {
44
+ return { result: { value: null } };
45
+ }
46
+ });
47
+
48
+ const result = await resolver.resolveSelector('#nonexistent');
49
+ assert.strictEqual(result, null);
50
+ });
51
+
52
+ it('should return objectId and box when element found', async () => {
53
+ let callCount = 0;
54
+ mockSession.send.mock.mockImplementation((method) => {
55
+ callCount++;
56
+ if (method === 'Runtime.evaluate') {
57
+ if (callCount === 1) {
58
+ // First call - check existence and get box
59
+ return {
60
+ result: {
61
+ value: { found: true, box: { x: 10, y: 20, width: 100, height: 50 } }
62
+ }
63
+ };
64
+ } else {
65
+ // Second call - get objectId
66
+ return {
67
+ result: { objectId: 'obj-123' }
68
+ };
69
+ }
70
+ }
71
+ });
72
+
73
+ const result = await resolver.resolveSelector('#myButton');
74
+ assert.ok(result);
75
+ assert.strictEqual(result.objectId, 'obj-123');
76
+ assert.deepStrictEqual(result.box, { x: 10, y: 20, width: 100, height: 50 });
77
+ assert.strictEqual(result.resolvedBy, 'selector');
78
+ });
79
+
80
+ it('should handle CDP errors gracefully', async () => {
81
+ mockSession.send.mock.mockImplementation(() => {
82
+ throw new Error('CDP connection lost');
83
+ });
84
+
85
+ const result = await resolver.resolveSelector('#myButton');
86
+ assert.strictEqual(result, null);
87
+ });
88
+ });
89
+
90
+ describe('resolveRef', () => {
91
+ it('should return null for empty ref', async () => {
92
+ const result = await resolver.resolveRef('');
93
+ assert.strictEqual(result, null);
94
+ });
95
+
96
+ it('should return null for non-string ref', async () => {
97
+ const result = await resolver.resolveRef(123);
98
+ assert.strictEqual(result, null);
99
+ });
100
+
101
+ it('should return null when metadata not found', async () => {
102
+ mockSession.send.mock.mockImplementation(() => {
103
+ return { result: { value: null } };
104
+ });
105
+
106
+ const result = await resolver.resolveRef('f0s1e5');
107
+ assert.strictEqual(result, null);
108
+ });
109
+
110
+ it('should resolve by selector from metadata', async () => {
111
+ let callCount = 0;
112
+ mockSession.send.mock.mockImplementation((method, params) => {
113
+ callCount++;
114
+ if (method === 'Runtime.evaluate') {
115
+ if (callCount === 1) {
116
+ // First call - get metadata
117
+ return {
118
+ result: {
119
+ value: { selector: '#submitBtn', role: 'button', name: 'Submit' }
120
+ }
121
+ };
122
+ } else if (callCount === 2) {
123
+ // Second call - check element exists
124
+ return {
125
+ result: {
126
+ value: { found: true, box: { x: 100, y: 200, width: 80, height: 30 } }
127
+ }
128
+ };
129
+ } else {
130
+ // Third call - get objectId
131
+ return {
132
+ result: { objectId: 'resolved-obj-456' }
133
+ };
134
+ }
135
+ }
136
+ });
137
+
138
+ const result = await resolver.resolveRef('f0s1e5');
139
+ assert.ok(result);
140
+ assert.strictEqual(result.objectId, 'resolved-obj-456');
141
+ assert.strictEqual(result.ref, 'f0s1e5');
142
+ assert.strictEqual(result.resolvedBy, 'selector');
143
+ });
144
+
145
+ it('should fall back to role+name search when selector fails', async () => {
146
+ let callCount = 0;
147
+ mockSession.send.mock.mockImplementation((method) => {
148
+ callCount++;
149
+ if (method === 'Runtime.evaluate') {
150
+ if (callCount === 1) {
151
+ // Get metadata
152
+ return {
153
+ result: {
154
+ value: { selector: '#oldId', role: 'button', name: 'Submit' }
155
+ }
156
+ };
157
+ } else if (callCount === 2) {
158
+ // Selector check - fails (element not found)
159
+ return { result: { value: null } };
160
+ } else if (callCount === 3) {
161
+ // Role+name search - succeeds
162
+ return {
163
+ result: {
164
+ value: { found: true, box: { x: 50, y: 60, width: 100, height: 40 }, index: 0 }
165
+ }
166
+ };
167
+ } else {
168
+ // Get objectId via role+name
169
+ return {
170
+ result: { objectId: 'fallback-obj-789' }
171
+ };
172
+ }
173
+ }
174
+ });
175
+
176
+ const result = await resolver.resolveRef('f0s1e5');
177
+ assert.ok(result);
178
+ assert.strictEqual(result.objectId, 'fallback-obj-789');
179
+ assert.strictEqual(result.resolvedBy, 'role+name');
180
+ });
181
+ });
182
+
183
+ describe('resolveByRoleAndName', () => {
184
+ it('should return null for empty role', async () => {
185
+ const result = await resolver.resolveByRoleAndName('', 'test');
186
+ assert.strictEqual(result, null);
187
+ });
188
+
189
+ it('should find element by role and name', async () => {
190
+ let callCount = 0;
191
+ mockSession.send.mock.mockImplementation((method) => {
192
+ callCount++;
193
+ if (method === 'Runtime.evaluate') {
194
+ if (callCount === 1) {
195
+ return {
196
+ result: {
197
+ value: { found: true, box: { x: 10, y: 20, width: 100, height: 50 }, index: 2 }
198
+ }
199
+ };
200
+ } else {
201
+ return {
202
+ result: { objectId: 'role-obj-123' }
203
+ };
204
+ }
205
+ }
206
+ });
207
+
208
+ const result = await resolver.resolveByRoleAndName('button', 'Save');
209
+ assert.ok(result);
210
+ assert.strictEqual(result.objectId, 'role-obj-123');
211
+ assert.strictEqual(result.resolvedBy, 'role+name');
212
+ assert.strictEqual(result.role, 'button');
213
+ assert.strictEqual(result.name, 'Save');
214
+ });
215
+ });
216
+
217
+ describe('resolveThroughShadowDOM', () => {
218
+ it('should return null for empty shadow host path', async () => {
219
+ const result = await resolver.resolveThroughShadowDOM([], '#button');
220
+ assert.strictEqual(result, null);
221
+ });
222
+
223
+ it('should resolve element through shadow DOM', async () => {
224
+ let callCount = 0;
225
+ mockSession.send.mock.mockImplementation((method) => {
226
+ callCount++;
227
+ if (method === 'Runtime.evaluate') {
228
+ if (callCount === 1) {
229
+ return {
230
+ result: {
231
+ value: { found: true, box: { x: 30, y: 40, width: 80, height: 30 } }
232
+ }
233
+ };
234
+ } else {
235
+ return {
236
+ result: { objectId: 'shadow-obj-999' }
237
+ };
238
+ }
239
+ }
240
+ });
241
+
242
+ const result = await resolver.resolveThroughShadowDOM(['#host1', '#host2'], '.inner-button');
243
+ assert.ok(result);
244
+ assert.strictEqual(result.objectId, 'shadow-obj-999');
245
+ assert.strictEqual(result.resolvedBy, 'shadow-dom');
246
+ assert.deepStrictEqual(result.shadowHostPath, ['#host1', '#host2']);
247
+ });
248
+ });
249
+
250
+ describe('resolveText', () => {
251
+ it('should return null for empty text', async () => {
252
+ const result = await resolver.resolveText('');
253
+ assert.strictEqual(result, null);
254
+ });
255
+
256
+ it('should return null for non-string text', async () => {
257
+ const result = await resolver.resolveText(123);
258
+ assert.strictEqual(result, null);
259
+ });
260
+
261
+ it('should find element by text', async () => {
262
+ let callCount = 0;
263
+ mockSession.send.mock.mockImplementation((method) => {
264
+ callCount++;
265
+ if (method === 'Runtime.evaluate') {
266
+ if (callCount === 1) {
267
+ return {
268
+ result: {
269
+ value: {
270
+ found: true,
271
+ box: { x: 100, y: 150, width: 120, height: 35 },
272
+ selectors: 'button, input[type="button"]'
273
+ }
274
+ }
275
+ };
276
+ } else {
277
+ return {
278
+ result: { objectId: 'text-obj-555' }
279
+ };
280
+ }
281
+ }
282
+ });
283
+
284
+ const result = await resolver.resolveText('Submit Form');
285
+ assert.ok(result);
286
+ assert.strictEqual(result.objectId, 'text-obj-555');
287
+ assert.strictEqual(result.resolvedBy, 'text');
288
+ assert.strictEqual(result.text, 'Submit Form');
289
+ });
290
+ });
291
+
292
+ describe('frame context support', () => {
293
+ it('should include contextId in eval params when getFrameContext returns a value', async () => {
294
+ const getFrameContext = () => 12345;
295
+ const resolverWithFrame = createLazyResolver(mockSession, { getFrameContext });
296
+
297
+ mockSession.send.mock.mockImplementation((method, params) => {
298
+ if (method === 'Runtime.evaluate') {
299
+ // Verify contextId is included
300
+ assert.strictEqual(params.contextId, 12345);
301
+ return { result: { value: null } };
302
+ }
303
+ });
304
+
305
+ await resolverWithFrame.resolveSelector('#test');
306
+ assert.ok(mockSession.send.mock.calls.length > 0);
307
+ });
308
+
309
+ it('should not include contextId when getFrameContext returns null', async () => {
310
+ const getFrameContext = () => null;
311
+ const resolverWithFrame = createLazyResolver(mockSession, { getFrameContext });
312
+
313
+ mockSession.send.mock.mockImplementation((method, params) => {
314
+ if (method === 'Runtime.evaluate') {
315
+ // Verify contextId is NOT included
316
+ assert.strictEqual(params.contextId, undefined);
317
+ return { result: { value: null } };
318
+ }
319
+ });
320
+
321
+ await resolverWithFrame.resolveSelector('#test');
322
+ assert.ok(mockSession.send.mock.calls.length > 0);
323
+ });
324
+ });
325
+
326
+ describe('edge cases', () => {
327
+ it('should handle selector returning subtype null', async () => {
328
+ mockSession.send.mock.mockImplementation((method) => {
329
+ if (method === 'Runtime.evaluate') {
330
+ return {
331
+ result: { value: { found: true, box: { x: 0, y: 0, width: 0, height: 0 } } }
332
+ };
333
+ }
334
+ });
335
+
336
+ // Mock second call to return null subtype
337
+ let firstCall = true;
338
+ mockSession.send.mock.mockImplementation(() => {
339
+ if (firstCall) {
340
+ firstCall = false;
341
+ return {
342
+ result: { value: { found: true, box: { x: 0, y: 0, width: 10, height: 10 } } }
343
+ };
344
+ }
345
+ return { result: { subtype: 'null' } };
346
+ });
347
+
348
+ const result = await resolver.resolveSelector('#test');
349
+ assert.strictEqual(result, null);
350
+ });
351
+
352
+ it('should handle metadata with shadowHostPath', async () => {
353
+ let callCount = 0;
354
+ mockSession.send.mock.mockImplementation(() => {
355
+ callCount++;
356
+ if (callCount === 1) {
357
+ // Metadata with shadow path
358
+ return {
359
+ result: {
360
+ value: {
361
+ selector: '.shadow-button',
362
+ role: 'button',
363
+ name: 'Click',
364
+ shadowHostPath: ['#shadow-host']
365
+ }
366
+ }
367
+ };
368
+ } else if (callCount === 2) {
369
+ // Shadow DOM resolution succeeds
370
+ return {
371
+ result: { value: { found: true, box: { x: 10, y: 20, width: 50, height: 30 } } }
372
+ };
373
+ } else {
374
+ return { result: { objectId: 'shadow-resolved-obj' } };
375
+ }
376
+ });
377
+
378
+ const result = await resolver.resolveRef('f0s2e3');
379
+ assert.ok(result);
380
+ assert.strictEqual(result.objectId, 'shadow-resolved-obj');
381
+ });
382
+ });
383
+ });
@@ -170,7 +170,7 @@ describe('StepValidator', () => {
170
170
  });
171
171
 
172
172
  it('should accept object with ref', () => {
173
- const errors = validateStepInternal({ click: { ref: 's1e1' } });
173
+ const errors = validateStepInternal({ click: { ref: 'f0s1e1' } });
174
174
  assert.strictEqual(errors.length, 0);
175
175
  });
176
176
 
@@ -224,7 +224,7 @@ describe('StepValidator', () => {
224
224
  });
225
225
 
226
226
  it('should accept object with ref and value', () => {
227
- const errors = validateStepInternal({ fill: { ref: 's1e1', value: 'test' } });
227
+ const errors = validateStepInternal({ fill: { ref: 'f0s1e1', value: 'test' } });
228
228
  assert.strictEqual(errors.length, 0);
229
229
  });
230
230
 
@@ -603,7 +603,7 @@ describe('TestRunner', () => {
603
603
  });
604
604
 
605
605
  it('should accept fill with ref instead of selector', () => {
606
- const steps = [{ fill: { ref: 's1e3', value: 'test' } }];
606
+ const steps = [{ fill: { ref: 'f0s1e3', value: 'test' } }];
607
607
 
608
608
  const result = testRunner.validateSteps(steps);
609
609
  assert.strictEqual(result.valid, true);
@@ -716,7 +716,7 @@ describe('TestRunner', () => {
716
716
  });
717
717
 
718
718
  it('should accept valid hover with ref', () => {
719
- const result = validateSteps([{ hover: { ref: 's1e4' } }]);
719
+ const result = validateSteps([{ hover: { ref: 'f0s1e4' } }]);
720
720
  assert.strictEqual(result.valid, true);
721
721
  });
722
722