cdp-skill 1.0.7 → 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 +198 -1344
- 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 +268 -68
- 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 +34 -143
- 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 +256 -95
- package/src/runner/step-registry.js +1064 -0
- package/src/runner/step-validator.js +16 -740
- 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 +34 -736
- 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,13 +7,8 @@ import os from 'os';
|
|
|
7
7
|
import {
|
|
8
8
|
executePageFunction,
|
|
9
9
|
executePoll,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
executeWriteSiteManifest,
|
|
13
|
-
loadSiteManifest,
|
|
14
|
-
generateLightManifest,
|
|
15
|
-
runLightAutoFit,
|
|
16
|
-
LIGHT_FIT_SCRIPT
|
|
10
|
+
executeWriteSiteProfile,
|
|
11
|
+
loadSiteProfile
|
|
17
12
|
} from '../runner/execute-dynamic.js';
|
|
18
13
|
|
|
19
14
|
import { validateStepInternal, validateSteps } from '../runner/step-validator.js';
|
|
@@ -50,11 +45,11 @@ function createMockPageController(evalReturnValue, opts = {}) {
|
|
|
50
45
|
};
|
|
51
46
|
}
|
|
52
47
|
|
|
53
|
-
// Patch SITES_DIR for
|
|
48
|
+
// Patch SITES_DIR for profile tests by overriding environment
|
|
54
49
|
// We use a temp directory to avoid polluting ~/.cdp-skill/sites/
|
|
55
50
|
const REAL_SITES_DIR = path.join(os.homedir(), '.cdp-skill', 'sites');
|
|
56
51
|
|
|
57
|
-
async function
|
|
52
|
+
async function cleanupTestProfiles(domains) {
|
|
58
53
|
for (const domain of domains) {
|
|
59
54
|
const clean = domain.replace(/^www\./, '').replace(/[^a-zA-Z0-9.\-]/g, '_');
|
|
60
55
|
try {
|
|
@@ -288,699 +283,30 @@ describe('executePoll', () => {
|
|
|
288
283
|
});
|
|
289
284
|
});
|
|
290
285
|
|
|
291
|
-
|
|
292
|
-
// Tests: compilePipeline
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
describe('compilePipeline', () => {
|
|
296
|
-
it('should generate valid JS for find+fill', () => {
|
|
297
|
-
const js = compilePipeline([{ find: '#name', fill: 'John' }]);
|
|
298
|
-
assert.ok(js.includes('document.querySelector'));
|
|
299
|
-
assert.ok(js.includes('#name'));
|
|
300
|
-
assert.ok(js.includes('John'));
|
|
301
|
-
assert.ok(js.includes('nativeSetter'));
|
|
302
|
-
assert.ok(js.includes("dispatchEvent(new Event('input'"));
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should generate valid JS for find+click', () => {
|
|
306
|
-
const js = compilePipeline([{ find: '#btn', click: true }]);
|
|
307
|
-
assert.ok(js.includes('document.querySelector'));
|
|
308
|
-
assert.ok(js.includes('#btn'));
|
|
309
|
-
assert.ok(js.includes('.click()'));
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should generate valid JS for find+type', () => {
|
|
313
|
-
const js = compilePipeline([{ find: '#search', type: 'hello' }]);
|
|
314
|
-
assert.ok(js.includes('document.querySelector'));
|
|
315
|
-
assert.ok(js.includes('#search'));
|
|
316
|
-
assert.ok(js.includes('focus'));
|
|
317
|
-
assert.ok(js.includes('KeyboardEvent'));
|
|
318
|
-
assert.ok(js.includes('hello'));
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should generate valid JS for find+check', () => {
|
|
322
|
-
const js = compilePipeline([{ find: '#agree', check: true }]);
|
|
323
|
-
assert.ok(js.includes('document.querySelector'));
|
|
324
|
-
assert.ok(js.includes('#agree'));
|
|
325
|
-
assert.ok(js.includes('checked'));
|
|
326
|
-
assert.ok(js.includes('true'));
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should generate valid JS for find+select', () => {
|
|
330
|
-
const js = compilePipeline([{ find: '#color', select: 'red' }]);
|
|
331
|
-
assert.ok(js.includes('document.querySelector'));
|
|
332
|
-
assert.ok(js.includes('#color'));
|
|
333
|
-
assert.ok(js.includes('"red"'));
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it('should generate valid JS for waitFor', () => {
|
|
337
|
-
const js = compilePipeline([{ waitFor: '() => document.querySelector("#loaded")' }]);
|
|
338
|
-
assert.ok(js.includes('new Promise'));
|
|
339
|
-
assert.ok(js.includes('setInterval'));
|
|
340
|
-
assert.ok(js.includes('#loaded'));
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('should use custom timeout for waitFor', () => {
|
|
344
|
-
const js = compilePipeline([{ waitFor: '() => true', timeout: 5000 }]);
|
|
345
|
-
assert.ok(js.includes('5000'));
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('should generate valid JS for sleep', () => {
|
|
349
|
-
const js = compilePipeline([{ sleep: 200 }]);
|
|
350
|
-
assert.ok(js.includes('setTimeout'));
|
|
351
|
-
assert.ok(js.includes('200'));
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('should generate valid JS for return', () => {
|
|
355
|
-
const js = compilePipeline([{ return: '() => document.title' }]);
|
|
356
|
-
assert.ok(js.includes('document.title'));
|
|
357
|
-
assert.ok(js.includes('value:val'));
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('should throw on unrecognized micro-op', () => {
|
|
361
|
-
assert.throws(
|
|
362
|
-
() => compilePipeline([{ unknown: true }]),
|
|
363
|
-
{ message: /unrecognized micro-op/ }
|
|
364
|
-
);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('should combine multiple ops in sequence', () => {
|
|
368
|
-
const js = compilePipeline([
|
|
369
|
-
{ find: '#user', fill: 'Alice' },
|
|
370
|
-
{ find: '#pass', fill: 'secret' },
|
|
371
|
-
{ find: '#submit', click: true }
|
|
372
|
-
]);
|
|
373
|
-
assert.ok(js.includes('#user'));
|
|
374
|
-
assert.ok(js.includes('Alice'));
|
|
375
|
-
assert.ok(js.includes('#pass'));
|
|
376
|
-
assert.ok(js.includes('secret'));
|
|
377
|
-
assert.ok(js.includes('#submit'));
|
|
378
|
-
assert.ok(js.includes('.click()'));
|
|
379
|
-
assert.ok(js.includes('async function'));
|
|
380
|
-
assert.ok(js.includes('completed:true'));
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('should include error handling in generated code', () => {
|
|
384
|
-
const js = compilePipeline([{ find: '#el', click: true }]);
|
|
385
|
-
assert.ok(js.includes('catch'));
|
|
386
|
-
assert.ok(js.includes('failedAt'));
|
|
387
|
-
assert.ok(js.includes('completed:false'));
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it('should track step index in error info', () => {
|
|
391
|
-
const js = compilePipeline([
|
|
392
|
-
{ find: '#a', fill: 'x' },
|
|
393
|
-
{ find: '#b', fill: 'y' }
|
|
394
|
-
]);
|
|
395
|
-
assert.ok(js.includes('step:0'));
|
|
396
|
-
assert.ok(js.includes('step:1'));
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
// ---------------------------------------------------------------------------
|
|
401
|
-
// Tests: executePipeline
|
|
402
|
-
// ---------------------------------------------------------------------------
|
|
403
|
-
|
|
404
|
-
describe('executePipeline', () => {
|
|
405
|
-
afterEach(() => { mock.reset(); });
|
|
406
|
-
|
|
407
|
-
it('should execute array form', async () => {
|
|
408
|
-
const pc = createMockPageController({ completed: true, steps: 2, results: [{ok:true},{ok:true}] });
|
|
409
|
-
const result = await executePipeline(pc, [
|
|
410
|
-
{ find: '#a', fill: 'x' },
|
|
411
|
-
{ find: '#b', fill: 'y' }
|
|
412
|
-
]);
|
|
413
|
-
|
|
414
|
-
assert.strictEqual(result.completed, true);
|
|
415
|
-
assert.strictEqual(result.steps, 2);
|
|
416
|
-
assert.strictEqual(pc.evaluateInFrame.mock.calls.length, 1);
|
|
417
|
-
// Should use awaitPromise: true
|
|
418
|
-
assert.strictEqual(pc.evaluateInFrame.mock.calls[0].arguments[1].awaitPromise, true);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('should execute object form with steps and timeout', async () => {
|
|
422
|
-
const pc = createMockPageController({ completed: true, steps: 1, results: [{ok:true}] });
|
|
423
|
-
const result = await executePipeline(pc, {
|
|
424
|
-
steps: [{ find: '#x', click: true }],
|
|
425
|
-
timeout: 5000
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
assert.strictEqual(result.completed, true);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('should throw on empty array', async () => {
|
|
432
|
-
const pc = createMockPageController(null);
|
|
433
|
-
await assert.rejects(
|
|
434
|
-
() => executePipeline(pc, []),
|
|
435
|
-
{ message: /non-empty array/ }
|
|
436
|
-
);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should throw on non-array params without steps', async () => {
|
|
440
|
-
const pc = createMockPageController(null);
|
|
441
|
-
await assert.rejects(
|
|
442
|
-
() => executePipeline(pc, { timeout: 1000 }),
|
|
443
|
-
{ message: /non-empty array/ }
|
|
444
|
-
);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it('should throw on browser exception', async () => {
|
|
448
|
-
const pc = createMockPageController(undefined, { exception: 'TypeError: el is null' });
|
|
449
|
-
await assert.rejects(
|
|
450
|
-
() => executePipeline(pc, [{ find: '#missing', click: true }]),
|
|
451
|
-
{ message: /pipeline error/ }
|
|
452
|
-
);
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('should timeout when evaluation hangs', async () => {
|
|
456
|
-
const pc = {
|
|
457
|
-
evaluateInFrame: mock.fn(() => new Promise(() => { /* never resolves */ }))
|
|
458
|
-
};
|
|
459
|
-
await assert.rejects(
|
|
460
|
-
() => executePipeline(pc, { steps: [{ find: '#x', click: true }], timeout: 50 }),
|
|
461
|
-
{ message: /timed out after 50ms/ }
|
|
462
|
-
);
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
it('should return result value from browser', async () => {
|
|
466
|
-
const pipelineResult = { completed: false, failedAt: 0, error: 'not found: #x', results: [] };
|
|
467
|
-
const pc = createMockPageController(pipelineResult);
|
|
468
|
-
const result = await executePipeline(pc, [{ find: '#x', click: true }]);
|
|
469
|
-
|
|
470
|
-
assert.strictEqual(result.completed, false);
|
|
471
|
-
assert.strictEqual(result.failedAt, 0);
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// ---------------------------------------------------------------------------
|
|
476
|
-
// Tests: executeWriteSiteManifest and loadSiteManifest
|
|
477
|
-
// ---------------------------------------------------------------------------
|
|
478
|
-
|
|
479
|
-
describe('executeWriteSiteManifest', () => {
|
|
480
|
-
const testDomain = `test-dyn-${Date.now()}.example.com`;
|
|
481
|
-
|
|
482
|
-
afterEach(async () => {
|
|
483
|
-
await cleanupTestManifests([testDomain]);
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('should write a manifest file', async () => {
|
|
487
|
-
const result = await executeWriteSiteManifest({
|
|
488
|
-
domain: testDomain,
|
|
489
|
-
content: '# Test manifest\nContent here.'
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
assert.strictEqual(result.written, true);
|
|
493
|
-
assert.ok(result.path);
|
|
494
|
-
assert.strictEqual(result.domain, testDomain);
|
|
495
|
-
|
|
496
|
-
// Verify file was written
|
|
497
|
-
const content = await fs.readFile(result.path, 'utf8');
|
|
498
|
-
assert.strictEqual(content, '# Test manifest\nContent here.');
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('should require domain', async () => {
|
|
502
|
-
await assert.rejects(
|
|
503
|
-
() => executeWriteSiteManifest({ content: 'test' }),
|
|
504
|
-
{ message: /requires domain and content/ }
|
|
505
|
-
);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it('should require content', async () => {
|
|
509
|
-
await assert.rejects(
|
|
510
|
-
() => executeWriteSiteManifest({ domain: 'example.com' }),
|
|
511
|
-
{ message: /requires domain and content/ }
|
|
512
|
-
);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it('should throw on missing params', async () => {
|
|
516
|
-
await assert.rejects(
|
|
517
|
-
() => executeWriteSiteManifest(null),
|
|
518
|
-
{ message: /requires domain and content/ }
|
|
519
|
-
);
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
describe('loadSiteManifest', () => {
|
|
524
|
-
const testDomain = `test-load-${Date.now()}.example.com`;
|
|
525
|
-
|
|
526
|
-
afterEach(async () => {
|
|
527
|
-
await cleanupTestManifests([testDomain]);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('should return null when no manifest exists', async () => {
|
|
531
|
-
const result = await loadSiteManifest('nonexistent-domain-xyz-12345.com');
|
|
532
|
-
assert.strictEqual(result, null);
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
it('should return content when manifest exists', async () => {
|
|
536
|
-
// First write a manifest
|
|
537
|
-
await executeWriteSiteManifest({
|
|
538
|
-
domain: testDomain,
|
|
539
|
-
content: '# Loaded content'
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
const result = await loadSiteManifest(testDomain);
|
|
543
|
-
assert.strictEqual(result, '# Loaded content');
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// ---------------------------------------------------------------------------
|
|
548
|
-
// Tests: generateLightManifest
|
|
549
|
-
// ---------------------------------------------------------------------------
|
|
550
|
-
|
|
551
|
-
describe('generateLightManifest', () => {
|
|
552
|
-
it('should generate markdown with detected frameworks', () => {
|
|
553
|
-
const detection = {
|
|
554
|
-
react: true,
|
|
555
|
-
nextjs: true,
|
|
556
|
-
bodyChildCount: 10,
|
|
557
|
-
interactiveCount: 25,
|
|
558
|
-
usesPushState: true,
|
|
559
|
-
hasMain: true,
|
|
560
|
-
mainSelector: '#root'
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
const md = generateLightManifest('example.com', detection);
|
|
564
|
-
|
|
565
|
-
assert.ok(md.includes('# example.com'));
|
|
566
|
-
assert.ok(md.includes('Fitted:'));
|
|
567
|
-
assert.ok(md.includes('light'));
|
|
568
|
-
assert.ok(md.includes('Fingerprint: bc10-ic25'));
|
|
569
|
-
assert.ok(md.includes('React'));
|
|
570
|
-
assert.ok(md.includes('Next.js'));
|
|
571
|
-
assert.ok(md.includes('SPA with pushState'));
|
|
572
|
-
assert.ok(md.includes('mainContent: `#root`'));
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it('should generate markdown with no frameworks', () => {
|
|
576
|
-
const detection = {
|
|
577
|
-
bodyChildCount: 5,
|
|
578
|
-
interactiveCount: 3,
|
|
579
|
-
hasMain: false,
|
|
580
|
-
mainSelector: null
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
const md = generateLightManifest('plain.com', detection);
|
|
584
|
-
|
|
585
|
-
assert.ok(md.includes('# plain.com'));
|
|
586
|
-
assert.ok(md.includes('No major framework'));
|
|
587
|
-
assert.ok(md.includes('No <main>'));
|
|
588
|
-
assert.ok(md.includes('Fingerprint: bc5-ic3'));
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
it('should include all recognized frameworks', () => {
|
|
592
|
-
const detection = {
|
|
593
|
-
react: true,
|
|
594
|
-
vue: true,
|
|
595
|
-
angular: true,
|
|
596
|
-
svelte: true,
|
|
597
|
-
jquery: true,
|
|
598
|
-
turbo: true,
|
|
599
|
-
htmx: true,
|
|
600
|
-
nuxt: true,
|
|
601
|
-
remix: true,
|
|
602
|
-
bodyChildCount: 1,
|
|
603
|
-
interactiveCount: 1,
|
|
604
|
-
hasMain: false,
|
|
605
|
-
mainSelector: null
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
const md = generateLightManifest('multi.com', detection);
|
|
609
|
-
|
|
610
|
-
assert.ok(md.includes('React'));
|
|
611
|
-
assert.ok(md.includes('Vue'));
|
|
612
|
-
assert.ok(md.includes('Angular'));
|
|
613
|
-
assert.ok(md.includes('Svelte'));
|
|
614
|
-
assert.ok(md.includes('jQuery'));
|
|
615
|
-
assert.ok(md.includes('Turbo'));
|
|
616
|
-
assert.ok(md.includes('htmx'));
|
|
617
|
-
assert.ok(md.includes('Nuxt'));
|
|
618
|
-
assert.ok(md.includes('Remix'));
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
it('should include service worker info', () => {
|
|
622
|
-
const detection = {
|
|
623
|
-
hasServiceWorker: true,
|
|
624
|
-
bodyChildCount: 1,
|
|
625
|
-
interactiveCount: 1,
|
|
626
|
-
hasMain: false,
|
|
627
|
-
mainSelector: null
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
const md = generateLightManifest('sw.com', detection);
|
|
631
|
-
assert.ok(md.includes('Service Worker'));
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
it('should include generator meta', () => {
|
|
635
|
-
const detection = {
|
|
636
|
-
metaGenerator: 'WordPress 6.0',
|
|
637
|
-
bodyChildCount: 1,
|
|
638
|
-
interactiveCount: 1,
|
|
639
|
-
hasMain: false,
|
|
640
|
-
mainSelector: null
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
const md = generateLightManifest('wp.com', detection);
|
|
644
|
-
assert.ok(md.includes('Generator: WordPress 6.0'));
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it('should include date in format YYYY-MM-DD', () => {
|
|
648
|
-
const detection = {
|
|
649
|
-
bodyChildCount: 0,
|
|
650
|
-
interactiveCount: 0,
|
|
651
|
-
hasMain: false,
|
|
652
|
-
mainSelector: null
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
const md = generateLightManifest('date.com', detection);
|
|
656
|
-
const datePattern = /\d{4}-\d{2}-\d{2}/;
|
|
657
|
-
assert.ok(datePattern.test(md));
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
it('should include notes section', () => {
|
|
661
|
-
const detection = {
|
|
662
|
-
bodyChildCount: 0,
|
|
663
|
-
interactiveCount: 0,
|
|
664
|
-
hasMain: false,
|
|
665
|
-
mainSelector: null
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const md = generateLightManifest('notes.com', detection);
|
|
669
|
-
assert.ok(md.includes('## Notes'));
|
|
670
|
-
assert.ok(md.includes('Light fit only'));
|
|
671
|
-
});
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
// ---------------------------------------------------------------------------
|
|
675
|
-
// Tests: runLightAutoFit
|
|
676
|
-
// ---------------------------------------------------------------------------
|
|
677
|
-
|
|
678
|
-
describe('runLightAutoFit', () => {
|
|
679
|
-
const testDomain = `autofit-${Date.now()}.example.com`;
|
|
680
|
-
|
|
681
|
-
afterEach(async () => {
|
|
682
|
-
await cleanupTestManifests([testDomain]);
|
|
683
|
-
mock.reset();
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
it('should return null when manifest already exists', async () => {
|
|
687
|
-
// Write a manifest first
|
|
688
|
-
await executeWriteSiteManifest({
|
|
689
|
-
domain: testDomain,
|
|
690
|
-
content: '# existing'
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
const pc = createMockPageController({});
|
|
694
|
-
const result = await runLightAutoFit(pc, `https://${testDomain}/page`);
|
|
695
|
-
|
|
696
|
-
assert.strictEqual(result, null);
|
|
697
|
-
// evaluateInFrame should NOT have been called since manifest exists
|
|
698
|
-
assert.strictEqual(pc.evaluateInFrame.mock.calls.length, 0);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it('should create manifest and return detection info on first visit', async () => {
|
|
702
|
-
const uniqueDomain = `autofit-new-${Date.now()}.example.com`;
|
|
703
|
-
const detection = {
|
|
704
|
-
react: true,
|
|
705
|
-
bodyChildCount: 12,
|
|
706
|
-
interactiveCount: 30,
|
|
707
|
-
hasMain: true,
|
|
708
|
-
mainSelector: '#app',
|
|
709
|
-
usesPushState: true,
|
|
710
|
-
title: 'Test App'
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
const pc = {
|
|
714
|
-
evaluateInFrame: mock.fn(() => Promise.resolve({
|
|
715
|
-
result: { value: detection },
|
|
716
|
-
exceptionDetails: undefined
|
|
717
|
-
}))
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
const result = await runLightAutoFit(pc, `https://${uniqueDomain}/`);
|
|
721
|
-
|
|
722
|
-
assert.ok(result);
|
|
723
|
-
assert.ok(result.domain);
|
|
724
|
-
assert.strictEqual(result.level, 'light');
|
|
725
|
-
assert.ok(result.frameworks.includes('react'));
|
|
726
|
-
|
|
727
|
-
// Verify the manifest was written
|
|
728
|
-
const manifest = await loadSiteManifest(result.domain);
|
|
729
|
-
assert.ok(manifest);
|
|
730
|
-
assert.ok(manifest.includes('React'));
|
|
731
|
-
|
|
732
|
-
// Cleanup
|
|
733
|
-
await cleanupTestManifests([uniqueDomain]);
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
it('should handle invalid URLs gracefully', async () => {
|
|
737
|
-
const pc = createMockPageController({});
|
|
738
|
-
const result = await runLightAutoFit(pc, 'not-a-valid-url');
|
|
739
|
-
|
|
740
|
-
assert.strictEqual(result, null);
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
it('should return null when browser detection fails', async () => {
|
|
744
|
-
const uniqueDomain = `autofit-fail-${Date.now()}.example.com`;
|
|
745
|
-
const pc = createMockPageController(undefined, { exception: 'eval error' });
|
|
746
|
-
|
|
747
|
-
const result = await runLightAutoFit(pc, `https://${uniqueDomain}/`);
|
|
748
|
-
|
|
749
|
-
assert.strictEqual(result, null);
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
it('should return null when detection returns no value', async () => {
|
|
753
|
-
const uniqueDomain = `autofit-noval-${Date.now()}.example.com`;
|
|
754
|
-
const pc = {
|
|
755
|
-
evaluateInFrame: mock.fn(() => Promise.resolve({
|
|
756
|
-
result: { value: null },
|
|
757
|
-
exceptionDetails: undefined
|
|
758
|
-
}))
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
const result = await runLightAutoFit(pc, `https://${uniqueDomain}/`);
|
|
762
|
-
assert.strictEqual(result, null);
|
|
763
|
-
});
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// ---------------------------------------------------------------------------
|
|
767
|
-
// Tests: LIGHT_FIT_SCRIPT
|
|
768
|
-
// ---------------------------------------------------------------------------
|
|
769
|
-
|
|
770
|
-
describe('LIGHT_FIT_SCRIPT', () => {
|
|
771
|
-
it('should be a non-empty string', () => {
|
|
772
|
-
assert.ok(typeof LIGHT_FIT_SCRIPT === 'string');
|
|
773
|
-
assert.ok(LIGHT_FIT_SCRIPT.length > 0);
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
it('should detect common frameworks', () => {
|
|
777
|
-
assert.ok(LIGHT_FIT_SCRIPT.includes('React'));
|
|
778
|
-
assert.ok(LIGHT_FIT_SCRIPT.includes('Vue'));
|
|
779
|
-
assert.ok(LIGHT_FIT_SCRIPT.includes('Angular'));
|
|
780
|
-
assert.ok(LIGHT_FIT_SCRIPT.includes('jQuery'));
|
|
781
|
-
});
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
// ---------------------------------------------------------------------------
|
|
785
|
-
// Tests: StepValidator - Dynamic step types
|
|
786
|
-
// ---------------------------------------------------------------------------
|
|
787
|
-
|
|
788
|
-
describe('StepValidator - pageFunction validation', () => {
|
|
789
|
-
it('should accept valid string form', () => {
|
|
790
|
-
const errors = validateStepInternal({ pageFunction: '() => document.title' });
|
|
791
|
-
assert.strictEqual(errors.length, 0);
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it('should accept valid object form', () => {
|
|
795
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 42', refs: true, timeout: 5000 } });
|
|
796
|
-
assert.strictEqual(errors.length, 0);
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
it('should reject empty string', () => {
|
|
800
|
-
const errors = validateStepInternal({ pageFunction: '' });
|
|
801
|
-
assert.ok(errors.some(e => e.includes('non-empty function string')));
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it('should reject missing fn in object form', () => {
|
|
805
|
-
const errors = validateStepInternal({ pageFunction: { timeout: 1000 } });
|
|
806
|
-
assert.ok(errors.some(e => e.includes('non-empty fn string')));
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
it('should reject invalid refs type', () => {
|
|
810
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 1', refs: 'yes' } });
|
|
811
|
-
assert.ok(errors.some(e => e.includes('refs must be a boolean')));
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
it('should reject invalid timeout', () => {
|
|
815
|
-
const errors = validateStepInternal({ pageFunction: { fn: '() => 1', timeout: -100 } });
|
|
816
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
it('should reject non-string non-object form', () => {
|
|
820
|
-
const errors = validateStepInternal({ pageFunction: 42 });
|
|
821
|
-
assert.ok(errors.some(e => e.includes('function string or params object')));
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
describe('StepValidator - poll validation', () => {
|
|
826
|
-
it('should accept valid string form', () => {
|
|
827
|
-
const errors = validateStepInternal({ poll: '() => document.readyState === "complete"' });
|
|
828
|
-
assert.strictEqual(errors.length, 0);
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
it('should accept valid object form', () => {
|
|
832
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', interval: 200, timeout: 10000 } });
|
|
833
|
-
assert.strictEqual(errors.length, 0);
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
it('should reject empty string', () => {
|
|
837
|
-
const errors = validateStepInternal({ poll: '' });
|
|
838
|
-
assert.ok(errors.some(e => e.includes('non-empty function string')));
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
it('should reject missing fn', () => {
|
|
842
|
-
const errors = validateStepInternal({ poll: { interval: 100 } });
|
|
843
|
-
assert.ok(errors.some(e => e.includes('non-empty fn string')));
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
it('should reject invalid interval', () => {
|
|
847
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', interval: -50 } });
|
|
848
|
-
assert.ok(errors.some(e => e.includes('interval must be a non-negative')));
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
it('should reject invalid timeout', () => {
|
|
852
|
-
const errors = validateStepInternal({ poll: { fn: '() => true', timeout: 'long' } });
|
|
853
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
it('should reject non-string non-object form', () => {
|
|
857
|
-
const errors = validateStepInternal({ poll: 123 });
|
|
858
|
-
assert.ok(errors.some(e => e.includes('function string or params object')));
|
|
859
|
-
});
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
describe('StepValidator - pipeline validation', () => {
|
|
863
|
-
it('should accept valid array of micro-ops', () => {
|
|
864
|
-
const errors = validateStepInternal({
|
|
865
|
-
pipeline: [
|
|
866
|
-
{ find: '#name', fill: 'John' },
|
|
867
|
-
{ find: '#submit', click: true }
|
|
868
|
-
]
|
|
869
|
-
});
|
|
870
|
-
assert.strictEqual(errors.length, 0);
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
it('should accept valid object form with steps', () => {
|
|
874
|
-
const errors = validateStepInternal({
|
|
875
|
-
pipeline: {
|
|
876
|
-
steps: [{ find: '#btn', click: true }],
|
|
877
|
-
timeout: 5000
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
assert.strictEqual(errors.length, 0);
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
it('should reject empty array', () => {
|
|
884
|
-
const errors = validateStepInternal({ pipeline: [] });
|
|
885
|
-
assert.ok(errors.some(e => e.includes('non-empty array')));
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
it('should reject invalid micro-ops without find/waitFor/sleep/return', () => {
|
|
889
|
-
const errors = validateStepInternal({ pipeline: [{ something: true }] });
|
|
890
|
-
assert.ok(errors.some(e => e.includes('unrecognized micro-op')));
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
it('should reject find without action', () => {
|
|
894
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el' }] });
|
|
895
|
-
assert.ok(errors.some(e => e.includes('find requires an action')));
|
|
896
|
-
});
|
|
897
|
-
|
|
898
|
-
it('should accept find with fill action', () => {
|
|
899
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', fill: 'val' }] });
|
|
900
|
-
assert.strictEqual(errors.length, 0);
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
it('should accept find with click action', () => {
|
|
904
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', click: true }] });
|
|
905
|
-
assert.strictEqual(errors.length, 0);
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
it('should accept find with type action', () => {
|
|
909
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', type: 'text' }] });
|
|
910
|
-
assert.strictEqual(errors.length, 0);
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it('should accept find with check action', () => {
|
|
914
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', check: true }] });
|
|
915
|
-
assert.strictEqual(errors.length, 0);
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
it('should accept find with select action', () => {
|
|
919
|
-
const errors = validateStepInternal({ pipeline: [{ find: '#el', select: 'opt' }] });
|
|
920
|
-
assert.strictEqual(errors.length, 0);
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
it('should accept waitFor micro-op', () => {
|
|
924
|
-
const errors = validateStepInternal({ pipeline: [{ waitFor: '() => true' }] });
|
|
925
|
-
assert.strictEqual(errors.length, 0);
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
it('should accept sleep micro-op', () => {
|
|
929
|
-
const errors = validateStepInternal({ pipeline: [{ sleep: 500 }] });
|
|
930
|
-
assert.strictEqual(errors.length, 0);
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
it('should accept return micro-op', () => {
|
|
934
|
-
const errors = validateStepInternal({ pipeline: [{ return: '() => document.title' }] });
|
|
935
|
-
assert.strictEqual(errors.length, 0);
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
it('should reject invalid timeout on object form', () => {
|
|
939
|
-
const errors = validateStepInternal({
|
|
940
|
-
pipeline: {
|
|
941
|
-
steps: [{ find: '#el', click: true }],
|
|
942
|
-
timeout: -1
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
assert.ok(errors.some(e => e.includes('timeout must be a non-negative')));
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
it('should report errors for multiple invalid ops', () => {
|
|
949
|
-
const errors = validateStepInternal({
|
|
950
|
-
pipeline: [
|
|
951
|
-
{ find: '#a' }, // missing action
|
|
952
|
-
{ bad: true }, // unrecognized
|
|
953
|
-
{ find: '#b', fill: 'ok' } // valid
|
|
954
|
-
]
|
|
955
|
-
});
|
|
956
|
-
assert.ok(errors.length >= 2);
|
|
957
|
-
});
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
describe('StepValidator - writeSiteManifest validation', () => {
|
|
286
|
+
describe('StepValidator - writeSiteProfile validation', () => {
|
|
961
287
|
it('should accept valid params', () => {
|
|
962
288
|
const errors = validateStepInternal({
|
|
963
|
-
|
|
289
|
+
writeSiteProfile: { domain: 'example.com', content: '# profile' }
|
|
964
290
|
});
|
|
965
291
|
assert.strictEqual(errors.length, 0);
|
|
966
292
|
});
|
|
967
293
|
|
|
968
294
|
it('should reject missing domain', () => {
|
|
969
295
|
const errors = validateStepInternal({
|
|
970
|
-
|
|
296
|
+
writeSiteProfile: { content: '# profile' }
|
|
971
297
|
});
|
|
972
298
|
assert.ok(errors.some(e => e.includes('non-empty domain string')));
|
|
973
299
|
});
|
|
974
300
|
|
|
975
301
|
it('should reject missing content', () => {
|
|
976
302
|
const errors = validateStepInternal({
|
|
977
|
-
|
|
303
|
+
writeSiteProfile: { domain: 'example.com' }
|
|
978
304
|
});
|
|
979
305
|
assert.ok(errors.some(e => e.includes('non-empty content string')));
|
|
980
306
|
});
|
|
981
307
|
|
|
982
308
|
it('should reject non-object params', () => {
|
|
983
|
-
const errors = validateStepInternal({
|
|
309
|
+
const errors = validateStepInternal({ writeSiteProfile: 'test' });
|
|
984
310
|
assert.ok(errors.some(e => e.includes('requires an object')));
|
|
985
311
|
});
|
|
986
312
|
});
|
|
@@ -1052,12 +378,9 @@ describe('STEP_TYPES includes dynamic steps', () => {
|
|
|
1052
378
|
assert.ok(STEP_TYPES.includes('poll'));
|
|
1053
379
|
});
|
|
1054
380
|
|
|
1055
|
-
it('should include pipeline', () => {
|
|
1056
|
-
assert.ok(STEP_TYPES.includes('pipeline'));
|
|
1057
|
-
});
|
|
1058
381
|
|
|
1059
|
-
it('should include
|
|
1060
|
-
assert.ok(STEP_TYPES.includes('
|
|
382
|
+
it('should include writeSiteProfile', () => {
|
|
383
|
+
assert.ok(STEP_TYPES.includes('writeSiteProfile'));
|
|
1061
384
|
});
|
|
1062
385
|
});
|
|
1063
386
|
|
|
@@ -1125,33 +448,19 @@ describe('step-executors dispatch for dynamic steps', () => {
|
|
|
1125
448
|
assert.ok(result.output.resolved);
|
|
1126
449
|
});
|
|
1127
450
|
|
|
1128
|
-
it('should dispatch pipeline step', async () => {
|
|
1129
|
-
mockDeps.pageController.evaluateInFrame = mock.fn(() => Promise.resolve({
|
|
1130
|
-
result: { value: { completed: true, steps: 1, results: [{ok:true}] } },
|
|
1131
|
-
exceptionDetails: undefined
|
|
1132
|
-
}));
|
|
1133
|
-
|
|
1134
|
-
const result = await executeStep(mockDeps, {
|
|
1135
|
-
pipeline: [{ find: '#btn', click: true }]
|
|
1136
|
-
});
|
|
1137
451
|
|
|
1138
|
-
|
|
1139
|
-
assert.strictEqual(result.status, 'ok');
|
|
1140
|
-
assert.ok(result.output.completed);
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
it('should dispatch writeSiteManifest step', async () => {
|
|
452
|
+
it('should dispatch writeSiteProfile step', async () => {
|
|
1144
453
|
const uniqueDomain = `dispatch-test-${Date.now()}.example.com`;
|
|
1145
454
|
const result = await executeStep(mockDeps, {
|
|
1146
|
-
|
|
455
|
+
writeSiteProfile: { domain: uniqueDomain, content: '# test' }
|
|
1147
456
|
});
|
|
1148
457
|
|
|
1149
|
-
assert.strictEqual(result.action, '
|
|
458
|
+
assert.strictEqual(result.action, 'writeSiteProfile');
|
|
1150
459
|
assert.strictEqual(result.status, 'ok');
|
|
1151
460
|
assert.ok(result.output.written);
|
|
1152
461
|
|
|
1153
462
|
// Cleanup
|
|
1154
|
-
await
|
|
463
|
+
await cleanupTestProfiles([uniqueDomain]);
|
|
1155
464
|
});
|
|
1156
465
|
});
|
|
1157
466
|
|
|
@@ -1315,7 +624,7 @@ describe('step-executors hooks', () => {
|
|
|
1315
624
|
});
|
|
1316
625
|
});
|
|
1317
626
|
|
|
1318
|
-
describe('step-executors goto
|
|
627
|
+
describe('step-executors goto profile integration', () => {
|
|
1319
628
|
let mockDeps;
|
|
1320
629
|
const testDomain = `goto-test-${Date.now()}.example.com`;
|
|
1321
630
|
|
|
@@ -1330,29 +639,19 @@ describe('step-executors goto manifest integration', () => {
|
|
|
1330
639
|
mockDeps = {
|
|
1331
640
|
pageController: {
|
|
1332
641
|
evaluateInFrame: mock.fn((expr, opts) => {
|
|
1333
|
-
// For the URL check in goto
|
|
1334
642
|
if (expr === 'window.location.href') {
|
|
1335
643
|
return Promise.resolve({
|
|
1336
644
|
result: { value: `https://${testDomain}/page` },
|
|
1337
645
|
exceptionDetails: undefined
|
|
1338
646
|
});
|
|
1339
647
|
}
|
|
1340
|
-
// For light auto-fit detection
|
|
1341
648
|
return Promise.resolve({
|
|
1342
|
-
result: {
|
|
1343
|
-
value: {
|
|
1344
|
-
react: true,
|
|
1345
|
-
bodyChildCount: 5,
|
|
1346
|
-
interactiveCount: 10,
|
|
1347
|
-
hasMain: true,
|
|
1348
|
-
mainSelector: 'main',
|
|
1349
|
-
title: 'Test'
|
|
1350
|
-
}
|
|
1351
|
-
},
|
|
649
|
+
result: { value: null },
|
|
1352
650
|
exceptionDetails: undefined
|
|
1353
651
|
});
|
|
1354
652
|
}),
|
|
1355
653
|
navigate: mock.fn(() => Promise.resolve()),
|
|
654
|
+
waitForNetworkSettle: mock.fn(() => Promise.resolve({ settled: true, pendingCount: 0 })),
|
|
1356
655
|
getUrl: mock.fn(() => Promise.resolve(`https://${testDomain}/page`)),
|
|
1357
656
|
session: { send: mockSessionSend }
|
|
1358
657
|
},
|
|
@@ -1365,35 +664,36 @@ describe('step-executors goto manifest integration', () => {
|
|
|
1365
664
|
});
|
|
1366
665
|
|
|
1367
666
|
afterEach(async () => {
|
|
1368
|
-
await
|
|
667
|
+
await cleanupTestProfiles([testDomain]);
|
|
1369
668
|
mock.reset();
|
|
1370
669
|
});
|
|
1371
670
|
|
|
1372
|
-
it('should load existing
|
|
1373
|
-
// Write a
|
|
1374
|
-
await
|
|
671
|
+
it('should load existing profile on goto', async () => {
|
|
672
|
+
// Write a profile first
|
|
673
|
+
await executeWriteSiteProfile({
|
|
1375
674
|
domain: testDomain,
|
|
1376
|
-
content: '# Pre-existing
|
|
675
|
+
content: '# Pre-existing profile'
|
|
1377
676
|
});
|
|
1378
677
|
|
|
1379
678
|
const result = await executeStep(mockDeps, { goto: `https://${testDomain}/page` });
|
|
1380
679
|
|
|
1381
680
|
assert.strictEqual(result.action, 'goto');
|
|
1382
681
|
assert.strictEqual(result.status, 'ok');
|
|
1383
|
-
assert.ok(result.
|
|
1384
|
-
assert.ok(result.
|
|
682
|
+
assert.ok(result.siteProfile);
|
|
683
|
+
assert.ok(result.siteProfile.includes('Pre-existing profile'));
|
|
1385
684
|
});
|
|
1386
685
|
|
|
1387
|
-
it('should
|
|
1388
|
-
// Make sure no
|
|
1389
|
-
await
|
|
686
|
+
it('should return profileAvailable false on goto for unknown domain', async () => {
|
|
687
|
+
// Make sure no profile exists
|
|
688
|
+
await cleanupTestProfiles([testDomain]);
|
|
1390
689
|
|
|
1391
690
|
const result = await executeStep(mockDeps, { goto: `https://${testDomain}/page` });
|
|
1392
691
|
|
|
1393
692
|
assert.strictEqual(result.action, 'goto');
|
|
1394
693
|
assert.strictEqual(result.status, 'ok');
|
|
1395
|
-
|
|
1396
|
-
assert.
|
|
694
|
+
assert.strictEqual(result.profileAvailable, false);
|
|
695
|
+
assert.strictEqual(result.profileDomain, testDomain);
|
|
696
|
+
|
|
1397
697
|
});
|
|
1398
698
|
});
|
|
1399
699
|
|
|
@@ -1407,8 +707,7 @@ describe('validateSteps for dynamic steps', () => {
|
|
|
1407
707
|
{ goto: 'https://example.com' },
|
|
1408
708
|
{ pageFunction: '() => document.title' },
|
|
1409
709
|
{ poll: { fn: '() => true', timeout: 5000 } },
|
|
1410
|
-
{
|
|
1411
|
-
{ writeSiteManifest: { domain: 'example.com', content: '# test' } }
|
|
710
|
+
{ writeSiteProfile: { domain: 'example.com', content: '# test' } }
|
|
1412
711
|
]);
|
|
1413
712
|
assert.strictEqual(result.valid, true);
|
|
1414
713
|
assert.strictEqual(result.errors.length, 0);
|
|
@@ -1418,10 +717,9 @@ describe('validateSteps for dynamic steps', () => {
|
|
|
1418
717
|
const result = validateSteps([
|
|
1419
718
|
{ pageFunction: '' },
|
|
1420
719
|
{ poll: { interval: 100 } },
|
|
1421
|
-
{
|
|
1422
|
-
{ writeSiteManifest: { domain: 'x' } }
|
|
720
|
+
{ writeSiteProfile: { domain: 'x' } }
|
|
1423
721
|
]);
|
|
1424
722
|
assert.strictEqual(result.valid, false);
|
|
1425
|
-
assert.strictEqual(result.errors.length,
|
|
723
|
+
assert.strictEqual(result.errors.length, 3);
|
|
1426
724
|
});
|
|
1427
725
|
});
|