cdp-skill 1.0.8 → 1.0.14
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.
- package/README.md +80 -35
- package/SKILL.md +151 -239
- package/install.js +1 -0
- package/package.json +1 -1
- package/src/aria/index.js +8 -0
- package/src/aria/output-processor.js +173 -0
- package/src/aria/role-query.js +1229 -0
- package/src/aria/snapshot.js +459 -0
- package/src/aria.js +237 -43
- package/src/cdp/browser.js +22 -4
- package/src/cdp-skill.js +245 -69
- package/src/dom/click-executor.js +240 -76
- package/src/dom/element-locator.js +34 -25
- package/src/dom/fill-executor.js +55 -27
- package/src/page/dialog-handler.js +119 -0
- package/src/page/page-controller.js +190 -3
- package/src/runner/context-helpers.js +33 -55
- package/src/runner/execute-dynamic.js +8 -7
- package/src/runner/execute-form.js +11 -11
- package/src/runner/execute-input.js +2 -2
- package/src/runner/execute-interaction.js +99 -120
- package/src/runner/execute-navigation.js +11 -26
- package/src/runner/execute-query.js +8 -5
- package/src/runner/step-executors.js +225 -84
- package/src/runner/step-registry.js +1064 -0
- package/src/runner/step-validator.js +16 -754
- package/src/tests/Aria.test.js +1025 -0
- package/src/tests/ContextHelpers.test.js +39 -28
- package/src/tests/ExecuteBrowser.test.js +572 -0
- package/src/tests/ExecuteDynamic.test.js +2 -457
- package/src/tests/ExecuteForm.test.js +700 -0
- package/src/tests/ExecuteInput.test.js +540 -0
- package/src/tests/ExecuteInteraction.test.js +319 -0
- package/src/tests/ExecuteQuery.test.js +820 -0
- package/src/tests/FillExecutor.test.js +2 -2
- package/src/tests/StepValidator.test.js +222 -76
- package/src/tests/TestRunner.test.js +36 -25
- package/src/tests/integration.test.js +2 -1
- package/src/types.js +9 -9
- package/src/utils/backoff.js +118 -0
- package/src/utils/cdp-helpers.js +130 -0
- package/src/utils/devices.js +140 -0
- package/src/utils/errors.js +242 -0
- package/src/utils/index.js +65 -0
- package/src/utils/temp.js +75 -0
- package/src/utils/validators.js +433 -0
- package/src/utils.js +14 -1142
|
@@ -7,8 +7,6 @@ import os from 'os';
|
|
|
7
7
|
import {
|
|
8
8
|
executePageFunction,
|
|
9
9
|
executePoll,
|
|
10
|
-
compilePipeline,
|
|
11
|
-
executePipeline,
|
|
12
10
|
executeWriteSiteProfile,
|
|
13
11
|
loadSiteProfile
|
|
14
12
|
} from '../runner/execute-dynamic.js';
|
|
@@ -285,441 +283,6 @@ describe('executePoll', () => {
|
|
|
285
283
|
});
|
|
286
284
|
});
|
|
287
285
|
|
|
288
|
-
// ---------------------------------------------------------------------------
|
|
289
|
-
// Tests: compilePipeline
|
|
290
|
-
// ---------------------------------------------------------------------------
|
|
291
|
-
|
|
292
|
-
describe('compilePipeline', () => {
|
|
293
|
-
it('should generate valid JS for find+fill', () => {
|
|
294
|
-
const js = compilePipeline([{ find: '#name', fill: 'John' }]);
|
|
295
|
-
assert.ok(js.includes('document.querySelector'));
|
|
296
|
-
assert.ok(js.includes('#name'));
|
|
297
|
-
assert.ok(js.includes('John'));
|
|
298
|
-
assert.ok(js.includes('nativeSetter'));
|
|
299
|
-
assert.ok(js.includes("dispatchEvent(new Event('input'"));
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('should generate valid JS for find+click', () => {
|
|
303
|
-
const js = compilePipeline([{ find: '#btn', click: true }]);
|
|
304
|
-
assert.ok(js.includes('document.querySelector'));
|
|
305
|
-
assert.ok(js.includes('#btn'));
|
|
306
|
-
assert.ok(js.includes('.click()'));
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('should generate valid JS for find+type', () => {
|
|
310
|
-
const js = compilePipeline([{ find: '#search', type: 'hello' }]);
|
|
311
|
-
assert.ok(js.includes('document.querySelector'));
|
|
312
|
-
assert.ok(js.includes('#search'));
|
|
313
|
-
assert.ok(js.includes('focus'));
|
|
314
|
-
assert.ok(js.includes('KeyboardEvent'));
|
|
315
|
-
assert.ok(js.includes('hello'));
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('should generate valid JS for find+check', () => {
|
|
319
|
-
const js = compilePipeline([{ find: '#agree', check: true }]);
|
|
320
|
-
assert.ok(js.includes('document.querySelector'));
|
|
321
|
-
assert.ok(js.includes('#agree'));
|
|
322
|
-
assert.ok(js.includes('checked'));
|
|
323
|
-
assert.ok(js.includes('true'));
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it('should generate valid JS for find+select', () => {
|
|
327
|
-
const js = compilePipeline([{ find: '#color', select: 'red' }]);
|
|
328
|
-
assert.ok(js.includes('document.querySelector'));
|
|
329
|
-
assert.ok(js.includes('#color'));
|
|
330
|
-
assert.ok(js.includes('"red"'));
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('should generate valid JS for waitFor', () => {
|
|
334
|
-
const js = compilePipeline([{ waitFor: '() => document.querySelector("#loaded")' }]);
|
|
335
|
-
assert.ok(js.includes('new Promise'));
|
|
336
|
-
assert.ok(js.includes('setInterval'));
|
|
337
|
-
assert.ok(js.includes('#loaded'));
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('should use custom timeout for waitFor', () => {
|
|
341
|
-
const js = compilePipeline([{ waitFor: '() => true', timeout: 5000 }]);
|
|
342
|
-
assert.ok(js.includes('5000'));
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('should generate valid JS for sleep', () => {
|
|
346
|
-
const js = compilePipeline([{ sleep: 200 }]);
|
|
347
|
-
assert.ok(js.includes('setTimeout'));
|
|
348
|
-
assert.ok(js.includes('200'));
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should generate valid JS for return', () => {
|
|
352
|
-
const js = compilePipeline([{ return: '() => document.title' }]);
|
|
353
|
-
assert.ok(js.includes('document.title'));
|
|
354
|
-
assert.ok(js.includes('value:val'));
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('should throw on unrecognized micro-op', () => {
|
|
358
|
-
assert.throws(
|
|
359
|
-
() => compilePipeline([{ unknown: true }]),
|
|
360
|
-
{ message: /unrecognized micro-op/ }
|
|
361
|
-
);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('should combine multiple ops in sequence', () => {
|
|
365
|
-
const js = compilePipeline([
|
|
366
|
-
{ find: '#user', fill: 'Alice' },
|
|
367
|
-
{ find: '#pass', fill: 'secret' },
|
|
368
|
-
{ find: '#submit', click: true }
|
|
369
|
-
]);
|
|
370
|
-
assert.ok(js.includes('#user'));
|
|
371
|
-
assert.ok(js.includes('Alice'));
|
|
372
|
-
assert.ok(js.includes('#pass'));
|
|
373
|
-
assert.ok(js.includes('secret'));
|
|
374
|
-
assert.ok(js.includes('#submit'));
|
|
375
|
-
assert.ok(js.includes('.click()'));
|
|
376
|
-
assert.ok(js.includes('async function'));
|
|
377
|
-
assert.ok(js.includes('completed:true'));
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it('should include error handling in generated code', () => {
|
|
381
|
-
const js = compilePipeline([{ find: '#el', click: true }]);
|
|
382
|
-
assert.ok(js.includes('catch'));
|
|
383
|
-
assert.ok(js.includes('failedAt'));
|
|
384
|
-
assert.ok(js.includes('completed:false'));
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it('should track step index in error info', () => {
|
|
388
|
-
const js = compilePipeline([
|
|
389
|
-
{ find: '#a', fill: 'x' },
|
|
390
|
-
{ find: '#b', fill: 'y' }
|
|
391
|
-
]);
|
|
392
|
-
assert.ok(js.includes('step:0'));
|
|
393
|
-
assert.ok(js.includes('step:1'));
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
// ---------------------------------------------------------------------------
|
|
398
|
-
// Tests: executePipeline
|
|
399
|
-
// ---------------------------------------------------------------------------
|
|
400
|
-
|
|
401
|
-
describe('executePipeline', () => {
|
|
402
|
-
afterEach(() => { mock.reset(); });
|
|
403
|
-
|
|
404
|
-
it('should execute array form', async () => {
|
|
405
|
-
const pc = createMockPageController({ completed: true, steps: 2, results: [{ok:true},{ok:true}] });
|
|
406
|
-
const result = await executePipeline(pc, [
|
|
407
|
-
{ find: '#a', fill: 'x' },
|
|
408
|
-
{ find: '#b', fill: 'y' }
|
|
409
|
-
]);
|
|
410
|
-
|
|
411
|
-
assert.strictEqual(result.completed, true);
|
|
412
|
-
assert.strictEqual(result.steps, 2);
|
|
413
|
-
assert.strictEqual(pc.evaluateInFrame.mock.calls.length, 1);
|
|
414
|
-
// Should use awaitPromise: true
|
|
415
|
-
assert.strictEqual(pc.evaluateInFrame.mock.calls[0].arguments[1].awaitPromise, true);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should execute object form with steps and timeout', async () => {
|
|
419
|
-
const pc = createMockPageController({ completed: true, steps: 1, results: [{ok:true}] });
|
|
420
|
-
const result = await executePipeline(pc, {
|
|
421
|
-
steps: [{ find: '#x', click: true }],
|
|
422
|
-
timeout: 5000
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
assert.strictEqual(result.completed, true);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('should throw on empty array', async () => {
|
|
429
|
-
const pc = createMockPageController(null);
|
|
430
|
-
await assert.rejects(
|
|
431
|
-
() => executePipeline(pc, []),
|
|
432
|
-
{ message: /non-empty array/ }
|
|
433
|
-
);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('should throw on non-array params without steps', async () => {
|
|
437
|
-
const pc = createMockPageController(null);
|
|
438
|
-
await assert.rejects(
|
|
439
|
-
() => executePipeline(pc, { timeout: 1000 }),
|
|
440
|
-
{ message: /non-empty array/ }
|
|
441
|
-
);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('should throw on browser exception', async () => {
|
|
445
|
-
const pc = createMockPageController(undefined, { exception: 'TypeError: el is null' });
|
|
446
|
-
await assert.rejects(
|
|
447
|
-
() => executePipeline(pc, [{ find: '#missing', click: true }]),
|
|
448
|
-
{ message: /pipeline error/ }
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('should timeout when evaluation hangs', async () => {
|
|
453
|
-
const pc = {
|
|
454
|
-
evaluateInFrame: mock.fn(() => new Promise(() => { /* never resolves */ }))
|
|
455
|
-
};
|
|
456
|
-
await assert.rejects(
|
|
457
|
-
() => executePipeline(pc, { steps: [{ find: '#x', click: true }], timeout: 50 }),
|
|
458
|
-
{ message: /timed out after 50ms/ }
|
|
459
|
-
);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('should return result value from browser', async () => {
|
|
463
|
-
const pipelineResult = { completed: false, failedAt: 0, error: 'not found: #x', results: [] };
|
|
464
|
-
const pc = createMockPageController(pipelineResult);
|
|
465
|
-
const result = await executePipeline(pc, [{ find: '#x', click: true }]);
|
|
466
|
-
|
|
467
|
-
assert.strictEqual(result.completed, false);
|
|
468
|
-
assert.strictEqual(result.failedAt, 0);
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// ---------------------------------------------------------------------------
|
|
473
|
-
// Tests: executeWriteSiteProfile and loadSiteProfile
|
|
474
|
-
// ---------------------------------------------------------------------------
|
|
475
|
-
|
|
476
|
-
describe('executeWriteSiteProfile', () => {
|
|
477
|
-
const testDomain = `test-dyn-${Date.now()}.example.com`;
|
|
478
|
-
|
|
479
|
-
afterEach(async () => {
|
|
480
|
-
await cleanupTestProfiles([testDomain]);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it('should write a profile file', async () => {
|
|
484
|
-
const result = await executeWriteSiteProfile({
|
|
485
|
-
domain: testDomain,
|
|
486
|
-
content: '# Test profile\nContent here.'
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
assert.strictEqual(result.written, true);
|
|
490
|
-
assert.ok(result.path);
|
|
491
|
-
assert.strictEqual(result.domain, testDomain);
|
|
492
|
-
|
|
493
|
-
// Verify file was written
|
|
494
|
-
const content = await fs.readFile(result.path, 'utf8');
|
|
495
|
-
assert.strictEqual(content, '# Test profile\nContent here.');
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('should require domain', async () => {
|
|
499
|
-
await assert.rejects(
|
|
500
|
-
() => executeWriteSiteProfile({ content: 'test' }),
|
|
501
|
-
{ message: /requires domain and content/ }
|
|
502
|
-
);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it('should require content', async () => {
|
|
506
|
-
await assert.rejects(
|
|
507
|
-
() => executeWriteSiteProfile({ domain: 'example.com' }),
|
|
508
|
-
{ message: /requires domain and content/ }
|
|
509
|
-
);
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
it('should throw on missing params', async () => {
|
|
513
|
-
await assert.rejects(
|
|
514
|
-
() => executeWriteSiteProfile(null),
|
|
515
|
-
{ message: /requires domain and content/ }
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
describe('loadSiteProfile', () => {
|
|
521
|
-
const testDomain = `test-load-${Date.now()}.example.com`;
|
|
522
|
-
|
|
523
|
-
afterEach(async () => {
|
|
524
|
-
await cleanupTestProfiles([testDomain]);
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it('should return null when no profile exists', async () => {
|
|
528
|
-
const result = await loadSiteProfile('nonexistent-domain-xyz-12345.com');
|
|
529
|
-
assert.strictEqual(result, null);
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it('should return content when profile exists', async () => {
|
|
533
|
-
// First write a profile
|
|
534
|
-
await executeWriteSiteProfile({
|
|
535
|
-
domain: testDomain,
|
|
536
|
-
content: '# Loaded content'
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const result = await loadSiteProfile(testDomain);
|
|
540
|
-
assert.strictEqual(result, '# Loaded content');
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// ---------------------------------------------------------------------------
|
|
548
|
-
// Tests: StepValidator - Dynamic step types
|
|
549
|
-
// ---------------------------------------------------------------------------
|
|
550
|
-
|
|
551
|
-
describe('StepValidator - pageFunction validation', () => {
|
|
552
|
-
it('should accept valid string form', () => {
|
|
553
|
-
const errors = validateStepInternal({ pageFunction: '() => document.title' });
|
|
554
|
-
assert.strictEqual(errors.length, 0);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it('should accept valid object form', () => {
|
|
558
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 42', refs: true, timeout: 5000 } });
|
|
559
|
-
assert.strictEqual(errors.length, 0);
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it('should reject empty string', () => {
|
|
563
|
-
const errors = validateStepInternal({ pageFunction: '' });
|
|
564
|
-
assert.ok(errors.some(e => e.includes('non-empty function string')));
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
it('should reject missing fn in object form', () => {
|
|
568
|
-
const errors = validateStepInternal({ pageFunction: { timeout: 1000 } });
|
|
569
|
-
assert.ok(errors.some(e => e.includes('non-empty fn string')));
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
it('should reject invalid refs type', () => {
|
|
573
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 1', refs: 'yes' } });
|
|
574
|
-
assert.ok(errors.some(e => e.includes('refs must be a boolean')));
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it('should reject invalid timeout', () => {
|
|
578
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 1', timeout: -100 } });
|
|
579
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
it('should reject non-string non-object form', () => {
|
|
583
|
-
const errors = validateStepInternal({ pageFunction: 42 });
|
|
584
|
-
assert.ok(errors.some(e => e.includes('function string or params object')));
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
describe('StepValidator - poll validation', () => {
|
|
589
|
-
it('should accept valid string form', () => {
|
|
590
|
-
const errors = validateStepInternal({ poll: '() => document.readyState === "complete"' });
|
|
591
|
-
assert.strictEqual(errors.length, 0);
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
it('should accept valid object form', () => {
|
|
595
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', interval: 200, timeout: 10000 } });
|
|
596
|
-
assert.strictEqual(errors.length, 0);
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
it('should reject empty string', () => {
|
|
600
|
-
const errors = validateStepInternal({ poll: '' });
|
|
601
|
-
assert.ok(errors.some(e => e.includes('non-empty function string')));
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it('should reject missing fn', () => {
|
|
605
|
-
const errors = validateStepInternal({ poll: { interval: 100 } });
|
|
606
|
-
assert.ok(errors.some(e => e.includes('non-empty fn string')));
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
it('should reject invalid interval', () => {
|
|
610
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', interval: -50 } });
|
|
611
|
-
assert.ok(errors.some(e => e.includes('interval must be a non-negative')));
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
it('should reject invalid timeout', () => {
|
|
615
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', timeout: 'long' } });
|
|
616
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('should reject non-string non-object form', () => {
|
|
620
|
-
const errors = validateStepInternal({ poll: 123 });
|
|
621
|
-
assert.ok(errors.some(e => e.includes('function string or params object')));
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
describe('StepValidator - pipeline validation', () => {
|
|
626
|
-
it('should accept valid array of micro-ops', () => {
|
|
627
|
-
const errors = validateStepInternal({
|
|
628
|
-
pipeline: [
|
|
629
|
-
{ find: '#name', fill: 'John' },
|
|
630
|
-
{ find: '#submit', click: true }
|
|
631
|
-
]
|
|
632
|
-
});
|
|
633
|
-
assert.strictEqual(errors.length, 0);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it('should accept valid object form with steps', () => {
|
|
637
|
-
const errors = validateStepInternal({
|
|
638
|
-
pipeline: {
|
|
639
|
-
steps: [{ find: '#btn', click: true }],
|
|
640
|
-
timeout: 5000
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
assert.strictEqual(errors.length, 0);
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
it('should reject empty array', () => {
|
|
647
|
-
const errors = validateStepInternal({ pipeline: [] });
|
|
648
|
-
assert.ok(errors.some(e => e.includes('non-empty array')));
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
it('should reject invalid micro-ops without find/waitFor/sleep/return', () => {
|
|
652
|
-
const errors = validateStepInternal({ pipeline: [{ something: true }] });
|
|
653
|
-
assert.ok(errors.some(e => e.includes('unrecognized micro-op')));
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it('should reject find without action', () => {
|
|
657
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el' }] });
|
|
658
|
-
assert.ok(errors.some(e => e.includes('find requires an action')));
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
it('should accept find with fill action', () => {
|
|
662
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', fill: 'val' }] });
|
|
663
|
-
assert.strictEqual(errors.length, 0);
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
it('should accept find with click action', () => {
|
|
667
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', click: true }] });
|
|
668
|
-
assert.strictEqual(errors.length, 0);
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
it('should accept find with type action', () => {
|
|
672
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', type: 'text' }] });
|
|
673
|
-
assert.strictEqual(errors.length, 0);
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
it('should accept find with check action', () => {
|
|
677
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', check: true }] });
|
|
678
|
-
assert.strictEqual(errors.length, 0);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('should accept find with select action', () => {
|
|
682
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', select: 'opt' }] });
|
|
683
|
-
assert.strictEqual(errors.length, 0);
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
it('should accept waitFor micro-op', () => {
|
|
687
|
-
const errors = validateStepInternal({ pipeline: [{ waitFor: '() => true' }] });
|
|
688
|
-
assert.strictEqual(errors.length, 0);
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
it('should accept sleep micro-op', () => {
|
|
692
|
-
const errors = validateStepInternal({ pipeline: [{ sleep: 500 }] });
|
|
693
|
-
assert.strictEqual(errors.length, 0);
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
it('should accept return micro-op', () => {
|
|
697
|
-
const errors = validateStepInternal({ pipeline: [{ return: '() => document.title' }] });
|
|
698
|
-
assert.strictEqual(errors.length, 0);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it('should reject invalid timeout on object form', () => {
|
|
702
|
-
const errors = validateStepInternal({
|
|
703
|
-
pipeline: {
|
|
704
|
-
steps: [{ find: '#el', click: true }],
|
|
705
|
-
timeout: -1
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
it('should report errors for multiple invalid ops', () => {
|
|
712
|
-
const errors = validateStepInternal({
|
|
713
|
-
pipeline: [
|
|
714
|
-
{ find: '#a' }, // missing action
|
|
715
|
-
{ bad: true }, // unrecognized
|
|
716
|
-
{ find: '#b', fill: 'ok' } // valid
|
|
717
|
-
]
|
|
718
|
-
});
|
|
719
|
-
assert.ok(errors.length >= 2);
|
|
720
|
-
});
|
|
721
|
-
});
|
|
722
|
-
|
|
723
286
|
describe('StepValidator - writeSiteProfile validation', () => {
|
|
724
287
|
it('should accept valid params', () => {
|
|
725
288
|
const errors = validateStepInternal({
|
|
@@ -815,9 +378,6 @@ describe('STEP_TYPES includes dynamic steps', () => {
|
|
|
815
378
|
assert.ok(STEP_TYPES.includes('poll'));
|
|
816
379
|
});
|
|
817
380
|
|
|
818
|
-
it('should include pipeline', () => {
|
|
819
|
-
assert.ok(STEP_TYPES.includes('pipeline'));
|
|
820
|
-
});
|
|
821
381
|
|
|
822
382
|
it('should include writeSiteProfile', () => {
|
|
823
383
|
assert.ok(STEP_TYPES.includes('writeSiteProfile'));
|
|
@@ -888,20 +448,6 @@ describe('step-executors dispatch for dynamic steps', () => {
|
|
|
888
448
|
assert.ok(result.output.resolved);
|
|
889
449
|
});
|
|
890
450
|
|
|
891
|
-
it('should dispatch pipeline step', async () => {
|
|
892
|
-
mockDeps.pageController.evaluateInFrame = mock.fn(() => Promise.resolve({
|
|
893
|
-
result: { value: { completed: true, steps: 1, results: [{ok:true}] } },
|
|
894
|
-
exceptionDetails: undefined
|
|
895
|
-
}));
|
|
896
|
-
|
|
897
|
-
const result = await executeStep(mockDeps, {
|
|
898
|
-
pipeline: [{ find: '#btn', click: true }]
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
assert.strictEqual(result.action, 'pipeline');
|
|
902
|
-
assert.strictEqual(result.status, 'ok');
|
|
903
|
-
assert.ok(result.output.completed);
|
|
904
|
-
});
|
|
905
451
|
|
|
906
452
|
it('should dispatch writeSiteProfile step', async () => {
|
|
907
453
|
const uniqueDomain = `dispatch-test-${Date.now()}.example.com`;
|
|
@@ -1105,6 +651,7 @@ describe('step-executors goto profile integration', () => {
|
|
|
1105
651
|
});
|
|
1106
652
|
}),
|
|
1107
653
|
navigate: mock.fn(() => Promise.resolve()),
|
|
654
|
+
waitForNetworkSettle: mock.fn(() => Promise.resolve({ settled: true, pendingCount: 0 })),
|
|
1108
655
|
getUrl: mock.fn(() => Promise.resolve(`https://${testDomain}/page`)),
|
|
1109
656
|
session: { send: mockSessionSend }
|
|
1110
657
|
},
|
|
@@ -1160,7 +707,6 @@ describe('validateSteps for dynamic steps', () => {
|
|
|
1160
707
|
{ goto: 'https://example.com' },
|
|
1161
708
|
{ pageFunction: '() => document.title' },
|
|
1162
709
|
{ poll: { fn: '() => true', timeout: 5000 } },
|
|
1163
|
-
{ pipeline: [{ find: '#btn', click: true }] },
|
|
1164
710
|
{ writeSiteProfile: { domain: 'example.com', content: '# test' } }
|
|
1165
711
|
]);
|
|
1166
712
|
assert.strictEqual(result.valid, true);
|
|
@@ -1171,10 +717,9 @@ describe('validateSteps for dynamic steps', () => {
|
|
|
1171
717
|
const result = validateSteps([
|
|
1172
718
|
{ pageFunction: '' },
|
|
1173
719
|
{ poll: { interval: 100 } },
|
|
1174
|
-
{ pipeline: [] },
|
|
1175
720
|
{ writeSiteProfile: { domain: 'x' } }
|
|
1176
721
|
]);
|
|
1177
722
|
assert.strictEqual(result.valid, false);
|
|
1178
|
-
assert.strictEqual(result.errors.length,
|
|
723
|
+
assert.strictEqual(result.errors.length, 3);
|
|
1179
724
|
});
|
|
1180
725
|
});
|