testaro 4.3.2 → 4.5.0
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/commands.js +4 -4
- package/high.js +5 -2
- package/package.json +1 -1
- package/run.js +191 -139
- package/samples/batches/a11yorgs.json +1 -1
- package/samples/batches/eurail.json +11 -0
- package/samples/scripts/digiCert.json +63 -0
- package/tests/aatt.js +12 -2
- package/tests/alfa.js +8 -1
- package/tests/axe.js +4 -2
- package/tests/bulk.js +1 -0
- package/tests/focOp.js +1 -0
- package/tests/hover.js +1 -0
- package/tests/ibm.js +9 -0
- package/tests/labClash.js +4 -1
- package/tests/motion.js +6 -1
- package/tests/styleDiff.js +9 -5
- package/tests/tabNav.js +63 -54
- package/tests/tenon.js +8 -4
- package/tests/wave.js +1 -0
package/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
exports.commands = {
|
|
2
2
|
etc: {
|
|
3
3
|
button: [
|
|
4
|
-
'Click a button',
|
|
4
|
+
'Click a button or submit input',
|
|
5
5
|
{
|
|
6
6
|
which: [true, 'string', 'hasLength', 'substring of button text'],
|
|
7
7
|
index: [false, 'number', '', 'index among matches if not 0'],
|
|
@@ -94,14 +94,14 @@ exports.commands = {
|
|
|
94
94
|
{
|
|
95
95
|
which: [true, 'string', 'hasLength', 'substring of select-list text'],
|
|
96
96
|
index: [false, 'number', '', 'index among matches if not 0'],
|
|
97
|
-
what: [true, 'string', 'hasLength', 'substring of option text']
|
|
97
|
+
what: [true, 'string', 'hasLength', 'substring of option text content']
|
|
98
98
|
}
|
|
99
99
|
],
|
|
100
100
|
state: [
|
|
101
101
|
'Wait until the page reaches a load state',
|
|
102
102
|
{
|
|
103
|
-
which: [true, 'string', '
|
|
104
|
-
what: [false, 'string', '
|
|
103
|
+
which: [true, 'string', 'isState', '“loaded” or “idle”'],
|
|
104
|
+
what: [false, 'string', 'hasLength', 'comment']
|
|
105
105
|
}
|
|
106
106
|
],
|
|
107
107
|
tenonRequest: [
|
package/high.js
CHANGED
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -12,12 +12,13 @@ const {commands} = require('./commands');
|
|
|
12
12
|
const debug = process.env.DEBUG === 'true';
|
|
13
13
|
// Set WAITS environment variable to a positive number to insert delays (in ms).
|
|
14
14
|
const waits = Number.parseInt(process.env.WAITS) || 0;
|
|
15
|
+
const urlInject = process.env.URL_INJECT || 'yes';
|
|
15
16
|
// CSS selectors for targets of moves.
|
|
16
17
|
const moves = {
|
|
17
|
-
button: 'button',
|
|
18
|
+
button: 'button, [role=button], input[type=submit]',
|
|
18
19
|
checkbox: 'input[type=checkbox]',
|
|
19
20
|
focus: true,
|
|
20
|
-
link: 'a',
|
|
21
|
+
link: 'a, [role=link]',
|
|
21
22
|
radio: 'input[type=radio]',
|
|
22
23
|
select: 'select',
|
|
23
24
|
text: 'input[type=text]'
|
|
@@ -255,7 +256,7 @@ const launch = async typeName => {
|
|
|
255
256
|
// Make its console messages appear in the Playwright console.
|
|
256
257
|
page.on('console', msg => {
|
|
257
258
|
const msgText = msg.text();
|
|
258
|
-
console.log(msgText);
|
|
259
|
+
console.log(`[${msgText}]`);
|
|
259
260
|
logCount++;
|
|
260
261
|
logSize += msgText.length;
|
|
261
262
|
const msgLC = msgText.toLowerCase();
|
|
@@ -291,7 +292,13 @@ const textOf = async (page, element) => {
|
|
|
291
292
|
// Return its visible labels, descriptions, and legend if the first input in a fieldset.
|
|
292
293
|
totalText = await page.evaluate(element => {
|
|
293
294
|
const {tagName} = element;
|
|
294
|
-
|
|
295
|
+
let ownText = '';
|
|
296
|
+
if (['A', 'BUTTON'].includes(tagName)) {
|
|
297
|
+
ownText = element.textContent;
|
|
298
|
+
}
|
|
299
|
+
else if (tagName === 'INPUT' && element.type === 'submit') {
|
|
300
|
+
ownText = element.value;
|
|
301
|
+
}
|
|
295
302
|
// HTML link elements have no labels property.
|
|
296
303
|
const labels = tagName !== 'A' ? Array.from(element.labels) : [];
|
|
297
304
|
const labelTexts = labels.map(label => label.textContent);
|
|
@@ -362,69 +369,79 @@ const textOf = async (page, element) => {
|
|
|
362
369
|
return null;
|
|
363
370
|
}
|
|
364
371
|
};
|
|
365
|
-
// Returns an element case-insensitively matching a text.
|
|
372
|
+
// Returns an element of a type case-insensitively matching a text.
|
|
366
373
|
const matchElement = async (page, selector, matchText, index = 0) => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const textInBodyJSHandle = await page.waitForFunction(
|
|
374
|
-
args => {
|
|
375
|
-
const matchText = args[0];
|
|
376
|
-
const bodyText = args[1];
|
|
377
|
-
return ! matchText || bodyText.includes(matchText);
|
|
378
|
-
},
|
|
379
|
-
[slimText, slimBody],
|
|
380
|
-
{timeout: 2000}
|
|
381
|
-
)
|
|
382
|
-
.catch(async error => {
|
|
383
|
-
console.log(`ERROR: text to match not in body (${error.message})`);
|
|
384
|
-
});
|
|
385
|
-
// If there is no text to be matched or the body contained it:
|
|
386
|
-
if (textInBodyJSHandle) {
|
|
387
|
-
const lcText = matchText ? matchText.toLowerCase() : '';
|
|
388
|
-
// Identify the selected elements.
|
|
389
|
-
const selections = await page.$$(`body ${selector}`);
|
|
374
|
+
if (matchText) {
|
|
375
|
+
// If the page still exists:
|
|
376
|
+
if (page) {
|
|
377
|
+
const slimText = debloat(matchText);
|
|
378
|
+
// Identify the elements of the specified type.
|
|
379
|
+
const selections = await page.$$(selector);
|
|
390
380
|
// If there are any:
|
|
391
381
|
if (selections.length) {
|
|
392
382
|
// If there are enough to make a match possible:
|
|
393
383
|
if (index < selections.length) {
|
|
394
|
-
// Return the
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
for (const
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
if (
|
|
401
|
-
|
|
384
|
+
// Return the specified one, if any.
|
|
385
|
+
let matchCount = 0;
|
|
386
|
+
const selectionTexts = [];
|
|
387
|
+
for (const selection of selections) {
|
|
388
|
+
const selectionText = await textOf(page, selection);
|
|
389
|
+
selectionTexts.push(selectionText);
|
|
390
|
+
if (selectionText.includes(slimText)) {
|
|
391
|
+
if (matchCount++ === index) {
|
|
392
|
+
return {
|
|
393
|
+
success: true,
|
|
394
|
+
matchingElement: selection
|
|
395
|
+
};
|
|
396
|
+
}
|
|
402
397
|
}
|
|
403
398
|
}
|
|
404
|
-
return
|
|
399
|
+
// None satisfied the specifications, so return a failure.
|
|
400
|
+
return {
|
|
401
|
+
success: false,
|
|
402
|
+
error: 'exhausted',
|
|
403
|
+
message: `Text found in only ${matchCount} (not ${index + 1}) of ${selections.length}`,
|
|
404
|
+
candidateTexts: selectionTexts
|
|
405
|
+
};
|
|
405
406
|
}
|
|
406
|
-
// Otherwise, i.e. if there are too few to make a match possible:
|
|
407
|
+
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
407
408
|
else {
|
|
408
|
-
// Return
|
|
409
|
-
return
|
|
409
|
+
// Return a failure.
|
|
410
|
+
return {
|
|
411
|
+
success: false,
|
|
412
|
+
error: 'fewer',
|
|
413
|
+
message: `Count of '${selector}' elements only ${selections.length}`
|
|
414
|
+
};
|
|
410
415
|
}
|
|
411
416
|
}
|
|
412
|
-
// Otherwise, i.e. if there are no
|
|
417
|
+
// Otherwise, i.e. if there are no elements of the specified type:
|
|
413
418
|
else {
|
|
414
|
-
|
|
419
|
+
// Return a failure.
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
error: 'none',
|
|
423
|
+
message: `No '${selector}' elements found`
|
|
424
|
+
};
|
|
415
425
|
}
|
|
416
426
|
}
|
|
417
|
-
// Otherwise, i.e. if the
|
|
427
|
+
// Otherwise, i.e. if the page no longer exists:
|
|
418
428
|
else {
|
|
419
|
-
// Return
|
|
420
|
-
return
|
|
429
|
+
// Return a failure.
|
|
430
|
+
return {
|
|
431
|
+
success: false,
|
|
432
|
+
error: 'gone',
|
|
433
|
+
message: 'Page gone'
|
|
434
|
+
};
|
|
421
435
|
}
|
|
422
436
|
}
|
|
423
|
-
// Otherwise, i.e. if
|
|
437
|
+
// Otherwise, i.e. if no text was specified:
|
|
424
438
|
else {
|
|
425
|
-
// Return
|
|
426
|
-
|
|
427
|
-
|
|
439
|
+
// Return a failure.
|
|
440
|
+
return {
|
|
441
|
+
success: false,
|
|
442
|
+
error: 'text',
|
|
443
|
+
message: 'No text specified'
|
|
444
|
+
};
|
|
428
445
|
}
|
|
429
446
|
};
|
|
430
447
|
// Returns a string with any final slash removed.
|
|
@@ -495,8 +512,9 @@ const visit = async (act, page, isStrict) => {
|
|
|
495
512
|
// If the visit fails:
|
|
496
513
|
if (response === 'error') {
|
|
497
514
|
// Give up.
|
|
498
|
-
|
|
499
|
-
|
|
515
|
+
const errorMsg = `ERROR: Attemts to visit ${requestedURL} failed`;
|
|
516
|
+
console.log(errorMsg);
|
|
517
|
+
act.result = errorMsg;
|
|
500
518
|
await page.goto('about:blank')
|
|
501
519
|
.catch(error => {
|
|
502
520
|
console.log(`ERROR: Navigation to blank page failed (${error.message})`);
|
|
@@ -549,13 +567,21 @@ const isTrue = (object, specs) => {
|
|
|
549
567
|
}
|
|
550
568
|
return [actual, satisfied];
|
|
551
569
|
};
|
|
552
|
-
// Adds
|
|
570
|
+
// Adds a wait error result to an act.
|
|
553
571
|
const waitError = (page, act, error, what) => {
|
|
554
572
|
console.log(`ERROR waiting for ${what} (${error.message})`);
|
|
555
573
|
act.result = {url: page.url()};
|
|
556
574
|
act.result.error = `ERROR waiting for ${what}`;
|
|
557
575
|
return false;
|
|
558
576
|
};
|
|
577
|
+
// Waits.
|
|
578
|
+
const wait = ms => {
|
|
579
|
+
return new Promise(resolve => {
|
|
580
|
+
setTimeout(() => {
|
|
581
|
+
resolve('');
|
|
582
|
+
}, ms);
|
|
583
|
+
});
|
|
584
|
+
};
|
|
559
585
|
// Recursively performs the acts in a report.
|
|
560
586
|
const doActs = async (report, actIndex, page) => {
|
|
561
587
|
const {acts} = report;
|
|
@@ -590,12 +616,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
590
616
|
if (truth[1]) {
|
|
591
617
|
// If the performance of commands is to stop:
|
|
592
618
|
if (act.jump === 0) {
|
|
593
|
-
// Set the
|
|
619
|
+
// Set the act index to cause a stop.
|
|
594
620
|
actIndex = -2;
|
|
595
621
|
}
|
|
596
622
|
// Otherwise, if there is a numerical jump:
|
|
597
623
|
else if (act.jump) {
|
|
598
|
-
// Set the
|
|
624
|
+
// Set the act index accordingly.
|
|
599
625
|
actIndex += act.jump - 1;
|
|
600
626
|
}
|
|
601
627
|
// Otherwise, if there is a named next command:
|
|
@@ -623,58 +649,65 @@ const doActs = async (report, actIndex, page) => {
|
|
|
623
649
|
else if (act.type === 'wait') {
|
|
624
650
|
const {what, which} = act;
|
|
625
651
|
console.log(`>> for ${what} to include “${which}”`);
|
|
626
|
-
// Wait 5 seconds for the specified text
|
|
627
|
-
let successJSHandle;
|
|
652
|
+
// Wait 5 or 10 seconds for the specified text, and quit if it does not appear.
|
|
628
653
|
if (act.what === 'url') {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
654
|
+
await page.waitForURL(act.which, {timeout: 15000})
|
|
655
|
+
.catch(error => {
|
|
656
|
+
actIndex = -2;
|
|
657
|
+
waitError(page, act, error, 'URL');
|
|
658
|
+
});
|
|
633
659
|
}
|
|
634
660
|
else if (act.what === 'title') {
|
|
635
|
-
|
|
636
|
-
text => document.title.includes(text),
|
|
661
|
+
await page.waitForFunction(
|
|
662
|
+
text => document && document.title && document.title.includes(text),
|
|
663
|
+
act.which,
|
|
664
|
+
{
|
|
665
|
+
polling: 1000,
|
|
666
|
+
timeout: 5000
|
|
667
|
+
}
|
|
637
668
|
)
|
|
638
|
-
.catch(error =>
|
|
669
|
+
.catch(error => {
|
|
670
|
+
actIndex = -2;
|
|
671
|
+
waitError(page, act, error, 'title');
|
|
672
|
+
});
|
|
639
673
|
}
|
|
640
674
|
else if (act.what === 'body') {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
675
|
+
await page.waitForFunction(
|
|
676
|
+
text => document && document.body && document.body.innerText.includes(text),
|
|
677
|
+
act.which,
|
|
678
|
+
{
|
|
679
|
+
polling: 2000,
|
|
680
|
+
timeout: 10000
|
|
681
|
+
}
|
|
646
682
|
)
|
|
647
|
-
.catch(error =>
|
|
683
|
+
.catch(async error => {
|
|
684
|
+
actIndex = -2;
|
|
685
|
+
waitError(page, act, error, 'body');
|
|
686
|
+
});
|
|
648
687
|
}
|
|
649
|
-
|
|
688
|
+
// If the text was found:
|
|
689
|
+
if (actIndex > -2) {
|
|
690
|
+
// Add this to the report.
|
|
650
691
|
act.result = {url: page.url()};
|
|
651
692
|
if (act.what === 'title') {
|
|
652
693
|
act.result.title = await page.title();
|
|
653
694
|
}
|
|
654
|
-
await page.waitForLoadState('networkidle', {timeout: 10000})
|
|
655
|
-
.catch(error => {
|
|
656
|
-
console.log(`ERROR waiting for stability after ${act.what} (${error.message})`);
|
|
657
|
-
act.result.error = `ERROR waiting for stability after ${act.what}`;
|
|
658
|
-
});
|
|
659
695
|
}
|
|
660
696
|
}
|
|
661
697
|
// Otherwise, if the act is a wait for a state:
|
|
662
698
|
else if (act.type === 'state') {
|
|
663
|
-
//
|
|
699
|
+
// Wait for it, and quit if it does not appear.
|
|
664
700
|
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
670
|
-
.
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
else {
|
|
676
|
-
console.log('ERROR: invalid state');
|
|
677
|
-
act.result = 'ERROR: invalid state';
|
|
701
|
+
await page.waitForLoadState(
|
|
702
|
+
['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 5000][stateIndex]}
|
|
703
|
+
)
|
|
704
|
+
.catch(error => {
|
|
705
|
+
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
706
|
+
act.result = `ERROR waiting for page to be ${act.which}`;
|
|
707
|
+
actIndex = -2;
|
|
708
|
+
});
|
|
709
|
+
if (actIndex > -2) {
|
|
710
|
+
act.result = `Page became ${act.which}`;
|
|
678
711
|
}
|
|
679
712
|
}
|
|
680
713
|
// Otherwise, if the act is a page switch:
|
|
@@ -864,48 +897,34 @@ const doActs = async (report, actIndex, page) => {
|
|
|
864
897
|
// Otherwise, if the act is a move:
|
|
865
898
|
else if (moves[act.type]) {
|
|
866
899
|
const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
|
|
867
|
-
//
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
candidateTexts: whichElement
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
// Otherwise, if the body did not contain the text:
|
|
879
|
-
else if (whichElement === -1) {
|
|
880
|
-
// Add the failure to the act.
|
|
881
|
-
act.result = 'ERROR: body did not contain text to match';
|
|
882
|
-
}
|
|
883
|
-
// Otherwise, if there were not enough candidates:
|
|
884
|
-
else if (typeof whichElement === 'number') {
|
|
885
|
-
// Add the failure to the act.
|
|
886
|
-
act.result = {
|
|
887
|
-
candidateCount: whichElement,
|
|
888
|
-
error: 'ERROR: too few such elements to allow a match'
|
|
889
|
-
};
|
|
900
|
+
// Wait 10 seconds until the element to perform the move on is identified.
|
|
901
|
+
let matchResult = {success: false};
|
|
902
|
+
let tries = 0;
|
|
903
|
+
while (tries++ < 5 && ! matchResult.success) {
|
|
904
|
+
matchResult = await matchElement(page, selector, act.which || '', act.index);
|
|
905
|
+
if (! matchResult.success) {
|
|
906
|
+
await wait(2000);
|
|
907
|
+
}
|
|
890
908
|
}
|
|
891
|
-
//
|
|
892
|
-
|
|
909
|
+
// If a match was found:
|
|
910
|
+
if (matchResult.success) {
|
|
911
|
+
const {matchingElement} = matchResult;
|
|
893
912
|
// If the move is a button click, perform it.
|
|
894
913
|
if (act.type === 'button') {
|
|
895
|
-
await
|
|
914
|
+
await matchingElement.click({timeout: 3000});
|
|
896
915
|
act.result = 'clicked';
|
|
897
916
|
}
|
|
898
917
|
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
899
918
|
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
900
|
-
await
|
|
919
|
+
await matchingElement.waitForElementState('stable', {timeout: 2000})
|
|
901
920
|
.catch(error => {
|
|
902
921
|
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
903
922
|
act.result = `ERROR waiting for stable ${act.type}`;
|
|
904
923
|
});
|
|
905
924
|
if (! act.result) {
|
|
906
|
-
const isEnabled = await
|
|
925
|
+
const isEnabled = await matchingElement.isEnabled();
|
|
907
926
|
if (isEnabled) {
|
|
908
|
-
await
|
|
927
|
+
await matchingElement.check({
|
|
909
928
|
force: true,
|
|
910
929
|
timeout: 2000
|
|
911
930
|
})
|
|
@@ -926,28 +945,53 @@ const doActs = async (report, actIndex, page) => {
|
|
|
926
945
|
}
|
|
927
946
|
// Otherwise, if it is focusing the element, perform it.
|
|
928
947
|
else if (act.type === 'focus') {
|
|
929
|
-
await
|
|
948
|
+
await matchingElement.focus({timeout: 2000});
|
|
930
949
|
act.result = 'focused';
|
|
931
950
|
}
|
|
932
951
|
// Otherwise, if it is clicking a link, perform it.
|
|
933
952
|
else if (act.type === 'link') {
|
|
934
|
-
const href = await
|
|
935
|
-
const target = await
|
|
936
|
-
await
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
953
|
+
const href = await matchingElement.getAttribute('href');
|
|
954
|
+
const target = await matchingElement.getAttribute('target');
|
|
955
|
+
await matchingElement.click({timeout: 2000})
|
|
956
|
+
.catch(async () => {
|
|
957
|
+
console.log('ERROR: First attempt to click link timed out');
|
|
958
|
+
await matchingElement.click({
|
|
959
|
+
force: true,
|
|
960
|
+
timeout: 10000
|
|
961
|
+
})
|
|
962
|
+
.catch(() => {
|
|
963
|
+
actIndex = -2;
|
|
964
|
+
console.log('ERROR: Second (forced) attempt to click link timed out');
|
|
965
|
+
act.result = 'ERROR: Normal and forced click attempts timed out';
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
if (actIndex > -2) {
|
|
969
|
+
act.result = {
|
|
970
|
+
href: href || 'NONE',
|
|
971
|
+
target: target || 'NONE',
|
|
972
|
+
move: 'clicked'
|
|
973
|
+
};
|
|
974
|
+
}
|
|
942
975
|
}
|
|
943
976
|
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
944
977
|
else if (act.type === 'select') {
|
|
945
|
-
await
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
978
|
+
const options = await matchingElement.$$('option');
|
|
979
|
+
let optionText = '';
|
|
980
|
+
if (options && Array.isArray(options) && options.length) {
|
|
981
|
+
const optionTexts = [];
|
|
982
|
+
for (const option of options) {
|
|
983
|
+
const optionText = await option.textContent();
|
|
984
|
+
optionTexts.push(optionText);
|
|
985
|
+
}
|
|
986
|
+
const matchTexts = optionTexts.map((text, index) => text.includes(act.what) ? index : -1);
|
|
987
|
+
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
988
|
+
if (index !== undefined) {
|
|
989
|
+
await matchingElement.selectOption({index});
|
|
990
|
+
optionText = optionTexts[index];
|
|
991
|
+
}
|
|
992
|
+
}
|
|
949
993
|
act.result = optionText
|
|
950
|
-
?
|
|
994
|
+
? `“${optionText}” selected`
|
|
951
995
|
: 'ERROR: option not found';
|
|
952
996
|
}
|
|
953
997
|
// Otherwise, if it is entering text on the element:
|
|
@@ -961,19 +1005,23 @@ const doActs = async (report, actIndex, page) => {
|
|
|
961
1005
|
what = what.replace(/__[A-Z]+__/, envValue);
|
|
962
1006
|
}
|
|
963
1007
|
// Enter the text.
|
|
964
|
-
await
|
|
1008
|
+
await matchingElement.type(act.what);
|
|
965
1009
|
report.presses += act.what.length;
|
|
966
1010
|
act.result = 'entered';
|
|
967
1011
|
}
|
|
968
1012
|
// Otherwise, i.e. if the move is unknown, add the failure to the act.
|
|
969
1013
|
else {
|
|
970
1014
|
// Report the error.
|
|
971
|
-
|
|
1015
|
+
const report = 'ERROR: move unknown';
|
|
1016
|
+
act.result = report;
|
|
1017
|
+
console.log(report);
|
|
972
1018
|
}
|
|
973
1019
|
}
|
|
974
|
-
// Otherwise, i.e. if
|
|
1020
|
+
// Otherwise, i.e. if no match was found:
|
|
975
1021
|
else {
|
|
976
|
-
|
|
1022
|
+
const report = 'ERROR: Specified element not found';
|
|
1023
|
+
act.result = report;
|
|
1024
|
+
console.log(report);
|
|
977
1025
|
}
|
|
978
1026
|
}
|
|
979
1027
|
// Otherwise, if the act is a keypress:
|
|
@@ -1161,7 +1209,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1161
1209
|
// Otherwise, i.e. if the command is invalid:
|
|
1162
1210
|
else {
|
|
1163
1211
|
// Add an error result to the act.
|
|
1164
|
-
|
|
1212
|
+
const errorMsg = `ERROR: Invalid command of type ${act.type}`;
|
|
1213
|
+
act.result = errorMsg;
|
|
1214
|
+
console.log(errorMsg);
|
|
1165
1215
|
}
|
|
1166
1216
|
// Perform the remaining acts.
|
|
1167
1217
|
await doActs(report, actIndex + 1, page);
|
|
@@ -1247,8 +1297,10 @@ exports.handleRequest = async report => {
|
|
|
1247
1297
|
report.timeStamp = report.id.replace(/-.+/, '');
|
|
1248
1298
|
// Add the script commands to the report as its initial acts.
|
|
1249
1299
|
report.acts = JSON.parse(JSON.stringify(report.script.commands));
|
|
1250
|
-
// Inject url acts where necessary to undo DOM changes.
|
|
1251
|
-
|
|
1300
|
+
// Inject url acts where necessary to undo DOM changes, if specified.
|
|
1301
|
+
if (urlInject === 'yes') {
|
|
1302
|
+
injectURLActs(report.acts);
|
|
1303
|
+
}
|
|
1252
1304
|
// Perform the acts, asynchronously adding to the log and report.
|
|
1253
1305
|
await doScript(report);
|
|
1254
1306
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "digicert",
|
|
3
|
+
"what": "moves on DigiCert website",
|
|
4
|
+
"strict": true,
|
|
5
|
+
"commands": [
|
|
6
|
+
{
|
|
7
|
+
"type": "launch",
|
|
8
|
+
"which": "chromium",
|
|
9
|
+
"what": "Chrome"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "url",
|
|
13
|
+
"which": "https://order.digicert.com/",
|
|
14
|
+
"what": "DigiCert"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "focus",
|
|
18
|
+
"what": "button",
|
|
19
|
+
"which": "Choose payment plan"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "press",
|
|
23
|
+
"which": "ArrowDown",
|
|
24
|
+
"again": 2,
|
|
25
|
+
"what": "Choose 2-year plan"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "press",
|
|
29
|
+
"which": "Enter",
|
|
30
|
+
"what": "Commit to choose 2-year plan"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "button",
|
|
34
|
+
"which": "I don't have",
|
|
35
|
+
"what": "I don’t have my CSR"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "select",
|
|
39
|
+
"which": "Server app details",
|
|
40
|
+
"what": "NGINX"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "text",
|
|
44
|
+
"which": "main website",
|
|
45
|
+
"what": "jpdev.pro"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"type": "button",
|
|
49
|
+
"which": "Continue",
|
|
50
|
+
"what": "Continue"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "wait",
|
|
54
|
+
"what": "url",
|
|
55
|
+
"which": "step2"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "test",
|
|
59
|
+
"which": "bulk",
|
|
60
|
+
"what": "count of visible elements"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
package/tests/aatt.js
CHANGED
|
@@ -53,13 +53,23 @@ exports.reporter = async (page, waitLong) => {
|
|
|
53
53
|
}
|
|
54
54
|
catch (error) {
|
|
55
55
|
console.log(`ERROR processing AATT report (${error.message})`);
|
|
56
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
result: {
|
|
58
|
+
prevented: true,
|
|
59
|
+
error: 'ERROR processing AATT report'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
// Otherwise, i.e. if the result did not arrive within the time limit:
|
|
60
65
|
else {
|
|
61
66
|
// Report the failure.
|
|
62
67
|
console.log('ERROR: getting report took too long');
|
|
63
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
result: {
|
|
70
|
+
prevented: true,
|
|
71
|
+
error: 'ERROR: getting AATT report took too long'
|
|
72
|
+
}
|
|
73
|
+
};
|
|
64
74
|
}
|
|
65
75
|
};
|
package/tests/alfa.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
|
+
|
|
7
8
|
const {Audit} = require('@siteimprove/alfa-act');
|
|
8
9
|
const {Scraper} = require('@siteimprove/alfa-scraper');
|
|
9
10
|
const alfaRules = require('@siteimprove/alfa-rules');
|
|
10
11
|
|
|
11
12
|
// FUNCTIONS
|
|
13
|
+
|
|
12
14
|
// Conducts and reports an alfa test.
|
|
13
15
|
exports.reporter = async page => {
|
|
14
16
|
// Get the document containing the summaries of the alfa rules.
|
|
@@ -104,6 +106,11 @@ exports.reporter = async page => {
|
|
|
104
106
|
}
|
|
105
107
|
catch(error) {
|
|
106
108
|
console.log(`ERROR: navigation to URL timed out (${error})`);
|
|
107
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
result: {
|
|
111
|
+
prevented: true,
|
|
112
|
+
error: 'ERROR: navigation to URL timed out'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
108
115
|
}
|
|
109
116
|
};
|
package/tests/axe.js
CHANGED
|
@@ -16,10 +16,11 @@ exports.reporter = async (page, withItems, rules = []) => {
|
|
|
16
16
|
await injectAxe(page)
|
|
17
17
|
.catch(error => {
|
|
18
18
|
console.log(`ERROR: Axe injection failed (${error.message})`);
|
|
19
|
-
data.
|
|
19
|
+
data.prevented = true;
|
|
20
|
+
data.error = 'ERROR: axe injection failed';
|
|
20
21
|
});
|
|
21
22
|
// If the injection succeeded:
|
|
22
|
-
if (! data.
|
|
23
|
+
if (! data.prevented) {
|
|
23
24
|
// Get the data on the elements violating the specified axe-core rules.
|
|
24
25
|
const axeOptions = {};
|
|
25
26
|
if (rules.length) {
|
|
@@ -100,6 +101,7 @@ exports.reporter = async (page, withItems, rules = []) => {
|
|
|
100
101
|
// Otherwise, i.e. if the test failed:
|
|
101
102
|
else {
|
|
102
103
|
// Report this.
|
|
104
|
+
data.prevented = true;
|
|
103
105
|
data.error = 'ERROR: axe failed';
|
|
104
106
|
console.log('ERROR: axe failed');
|
|
105
107
|
}
|
package/tests/bulk.js
CHANGED
package/tests/focOp.js
CHANGED
package/tests/hover.js
CHANGED
|
@@ -212,6 +212,7 @@ exports.reporter = async (page, withItems) => {
|
|
|
212
212
|
const triggers = await page.$$(selectors.join(', '))
|
|
213
213
|
.catch(error => {
|
|
214
214
|
console.log(`ERROR getting hover triggers (${error.message})`);
|
|
215
|
+
data.prevented = true;
|
|
215
216
|
return [];
|
|
216
217
|
});
|
|
217
218
|
// If they number more than the sample size limit, sample them.
|
package/tests/ibm.js
CHANGED
|
@@ -48,10 +48,12 @@ const report = (ibmReport, withItems) => {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
|
+
data.prevented = true;
|
|
51
52
|
data.error = 'ERROR: ibm test delivered no totals';
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
else {
|
|
56
|
+
data.prevented = true;
|
|
55
57
|
data.error = 'ERROR: ibm test delivered no report summary';
|
|
56
58
|
}
|
|
57
59
|
return data;
|
|
@@ -88,6 +90,7 @@ const doTest = async (content, withItems, timeLimit) => {
|
|
|
88
90
|
else {
|
|
89
91
|
console.log('ERROR: getting ibm test report took too long');
|
|
90
92
|
return {
|
|
93
|
+
prevented: true,
|
|
91
94
|
error: 'ERROR: getting ibm test report took too long'
|
|
92
95
|
};
|
|
93
96
|
}
|
|
@@ -100,12 +103,18 @@ exports.reporter = async (page, withItems, withNewContent) => {
|
|
|
100
103
|
const timeLimit = 15;
|
|
101
104
|
const typeContent = await page.content();
|
|
102
105
|
result.content = await doTest(typeContent, withItems, timeLimit);
|
|
106
|
+
if (result.content.prevented) {
|
|
107
|
+
result.prevented = true;
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
// If a test with new content is to be performed:
|
|
105
111
|
if ([true, undefined].includes(withNewContent)) {
|
|
106
112
|
const timeLimit = 20;
|
|
107
113
|
const typeContent = page.url();
|
|
108
114
|
result.url = await doTest(typeContent, withItems, timeLimit);
|
|
115
|
+
if (result.content.prevented) {
|
|
116
|
+
result.prevented = true;
|
|
117
|
+
}
|
|
109
118
|
}
|
|
110
119
|
return {result};
|
|
111
120
|
};
|
package/tests/labClash.js
CHANGED
|
@@ -151,7 +151,10 @@ exports.reporter = async (page, withItems) => {
|
|
|
151
151
|
}, withItems)
|
|
152
152
|
.catch(error => {
|
|
153
153
|
console.log(`ERROR: labClash failed (${error.message})`);
|
|
154
|
-
const data = {
|
|
154
|
+
const data = {
|
|
155
|
+
prevented: true,
|
|
156
|
+
error: 'ERROR: labClash failed'
|
|
157
|
+
};
|
|
155
158
|
return {result: data};
|
|
156
159
|
});
|
|
157
160
|
};
|
package/tests/motion.js
CHANGED
|
@@ -110,6 +110,11 @@ exports.reporter = async (page, delay, interval, count) => {
|
|
|
110
110
|
// Otherwise, i.e. if the shooting failed:
|
|
111
111
|
else {
|
|
112
112
|
// Return failure.
|
|
113
|
-
return {
|
|
113
|
+
return {
|
|
114
|
+
result: {
|
|
115
|
+
prevented: true,
|
|
116
|
+
error: 'ERROR: screenshots failed'
|
|
117
|
+
}
|
|
118
|
+
};
|
|
114
119
|
}
|
|
115
120
|
};
|
package/tests/styleDiff.js
CHANGED
|
@@ -11,11 +11,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
11
11
|
return await page.$eval('body', (body, args) => {
|
|
12
12
|
const withItems = args[0];
|
|
13
13
|
const linkTypes = args[1];
|
|
14
|
-
// Initialize the data to be returned.
|
|
15
|
-
const data = {totals: {}};
|
|
16
|
-
if (withItems) {
|
|
17
|
-
data.items = {};
|
|
18
|
-
}
|
|
19
14
|
// Identify the settable style properties to be compared for all tag names.
|
|
20
15
|
const mainStyles = [
|
|
21
16
|
'borderStyle',
|
|
@@ -39,6 +34,15 @@ exports.reporter = async (page, withItems) => {
|
|
|
39
34
|
const headingStyles = [
|
|
40
35
|
'fontSize'
|
|
41
36
|
];
|
|
37
|
+
// Initialize the data to be returned.
|
|
38
|
+
const data = {
|
|
39
|
+
mainStyles,
|
|
40
|
+
headingStyles,
|
|
41
|
+
totals: {}
|
|
42
|
+
};
|
|
43
|
+
if (withItems) {
|
|
44
|
+
data.items = {};
|
|
45
|
+
}
|
|
42
46
|
// Identify the heading tag names to be analyzed.
|
|
43
47
|
const headingNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
44
48
|
// Identify the other nonlink tag names to be analyzed.
|
package/tests/tabNav.js
CHANGED
|
@@ -3,66 +3,71 @@
|
|
|
3
3
|
This test reports nonstandard keyboard navigation among tab elements in tab lists.
|
|
4
4
|
Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
|
|
7
|
+
// CONSTANTS
|
|
8
|
+
|
|
9
|
+
const data = {
|
|
10
|
+
totals: {
|
|
11
|
+
navigations: {
|
|
12
|
+
all: {
|
|
13
|
+
total: 0,
|
|
14
|
+
correct: 0,
|
|
15
|
+
incorrect: 0
|
|
16
|
+
},
|
|
17
|
+
specific: {
|
|
18
|
+
tab: {
|
|
12
19
|
total: 0,
|
|
13
20
|
correct: 0,
|
|
14
21
|
incorrect: 0
|
|
15
22
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
incorrect: 0
|
|
46
|
-
},
|
|
47
|
-
end: {
|
|
48
|
-
total: 0,
|
|
49
|
-
correct: 0,
|
|
50
|
-
incorrect: 0
|
|
51
|
-
}
|
|
23
|
+
left: {
|
|
24
|
+
total: 0,
|
|
25
|
+
correct: 0,
|
|
26
|
+
incorrect: 0
|
|
27
|
+
},
|
|
28
|
+
right: {
|
|
29
|
+
total: 0,
|
|
30
|
+
correct: 0,
|
|
31
|
+
incorrect: 0
|
|
32
|
+
},
|
|
33
|
+
up: {
|
|
34
|
+
total: 0,
|
|
35
|
+
correct: 0,
|
|
36
|
+
incorrect: 0
|
|
37
|
+
},
|
|
38
|
+
down: {
|
|
39
|
+
total: 0,
|
|
40
|
+
correct: 0,
|
|
41
|
+
incorrect: 0
|
|
42
|
+
},
|
|
43
|
+
home: {
|
|
44
|
+
total: 0,
|
|
45
|
+
correct: 0,
|
|
46
|
+
incorrect: 0
|
|
47
|
+
},
|
|
48
|
+
end: {
|
|
49
|
+
total: 0,
|
|
50
|
+
correct: 0,
|
|
51
|
+
incorrect: 0
|
|
52
52
|
}
|
|
53
|
-
},
|
|
54
|
-
tabElements: {
|
|
55
|
-
total: 0,
|
|
56
|
-
correct: 0,
|
|
57
|
-
incorrect: 0
|
|
58
|
-
},
|
|
59
|
-
tabLists: {
|
|
60
|
-
total: 0,
|
|
61
|
-
correct: 0,
|
|
62
|
-
incorrect: 0
|
|
63
53
|
}
|
|
54
|
+
},
|
|
55
|
+
tabElements: {
|
|
56
|
+
total: 0,
|
|
57
|
+
correct: 0,
|
|
58
|
+
incorrect: 0
|
|
59
|
+
},
|
|
60
|
+
tabLists: {
|
|
61
|
+
total: 0,
|
|
62
|
+
correct: 0,
|
|
63
|
+
incorrect: 0
|
|
64
64
|
}
|
|
65
|
-
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// FUNCTIONS
|
|
69
|
+
|
|
70
|
+
exports.reporter = async (page, withItems) => {
|
|
66
71
|
if (withItems) {
|
|
67
72
|
data.tabElements = {
|
|
68
73
|
incorrect: [],
|
|
@@ -182,6 +187,7 @@ exports.reporter = async (page, withItems) => {
|
|
|
182
187
|
.catch(error => {
|
|
183
188
|
console.log(`ERROR: could not get tag name (${error.message})`);
|
|
184
189
|
found = false;
|
|
190
|
+
data.prevented = true;
|
|
185
191
|
return 'ERROR: not found';
|
|
186
192
|
});
|
|
187
193
|
if (found) {
|
|
@@ -260,7 +266,10 @@ exports.reporter = async (page, withItems) => {
|
|
|
260
266
|
if (! orientation) {
|
|
261
267
|
orientation = 'horizontal';
|
|
262
268
|
}
|
|
263
|
-
if (orientation
|
|
269
|
+
if (orientation === 'ERROR') {
|
|
270
|
+
data.prevented = true;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
264
273
|
const tabs = await firstTabList.$$('[role=tab]');
|
|
265
274
|
// If the tablist contains at least 2 tab elements:
|
|
266
275
|
if (tabs.length > 1) {
|
package/tests/tenon.js
CHANGED
|
@@ -107,15 +107,19 @@ exports.reporter = async (tenonData, id) => {
|
|
|
107
107
|
// Otherwise, if the test is still running after a wait for its status:
|
|
108
108
|
else {
|
|
109
109
|
// Report the test status.
|
|
110
|
-
return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
return {
|
|
111
|
+
result: {
|
|
112
|
+
prevented: true,
|
|
113
|
+
error: 'ERROR: test status not completed',
|
|
114
|
+
testStatus
|
|
115
|
+
}
|
|
116
|
+
};
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
else {
|
|
117
120
|
return {
|
|
118
121
|
result: {
|
|
122
|
+
prevented: true,
|
|
119
123
|
error: 'ERROR: tenon authorization and test data incomplete'
|
|
120
124
|
}
|
|
121
125
|
};
|