testaro 4.3.3 → 4.5.1
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/package.json +1 -1
- package/run.js +196 -140
- package/samples/batches/a11yorgs.json +1 -1
- 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/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();
|
|
@@ -290,11 +291,20 @@ const textOf = async (page, element) => {
|
|
|
290
291
|
if (['A', 'BUTTON', 'INPUT', 'SELECT'].includes(tagName)) {
|
|
291
292
|
// Return its visible labels, descriptions, and legend if the first input in a fieldset.
|
|
292
293
|
totalText = await page.evaluate(element => {
|
|
293
|
-
const {tagName} = element;
|
|
294
|
-
|
|
294
|
+
const {tagName, ariaLabel} = element;
|
|
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);
|
|
305
|
+
if (ariaLabel) {
|
|
306
|
+
labelTexts.push(ariaLabel);
|
|
307
|
+
}
|
|
298
308
|
const refIDs = new Set([
|
|
299
309
|
element.getAttribute('aria-labelledby') || '',
|
|
300
310
|
element.getAttribute('aria-describedby') || ''
|
|
@@ -362,69 +372,79 @@ const textOf = async (page, element) => {
|
|
|
362
372
|
return null;
|
|
363
373
|
}
|
|
364
374
|
};
|
|
365
|
-
// Returns an element case-insensitively matching a text.
|
|
375
|
+
// Returns an element of a type case-insensitively matching a text.
|
|
366
376
|
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}`);
|
|
377
|
+
if (matchText) {
|
|
378
|
+
// If the page still exists:
|
|
379
|
+
if (page) {
|
|
380
|
+
const slimText = debloat(matchText);
|
|
381
|
+
// Identify the elements of the specified type.
|
|
382
|
+
const selections = await page.$$(selector);
|
|
390
383
|
// If there are any:
|
|
391
384
|
if (selections.length) {
|
|
392
385
|
// If there are enough to make a match possible:
|
|
393
386
|
if (index < selections.length) {
|
|
394
|
-
// Return the
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
for (const
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
if (
|
|
401
|
-
|
|
387
|
+
// Return the specified one, if any.
|
|
388
|
+
let matchCount = 0;
|
|
389
|
+
const selectionTexts = [];
|
|
390
|
+
for (const selection of selections) {
|
|
391
|
+
const selectionText = await textOf(page, selection);
|
|
392
|
+
selectionTexts.push(selectionText);
|
|
393
|
+
if (selectionText.includes(slimText)) {
|
|
394
|
+
if (matchCount++ === index) {
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
matchingElement: selection
|
|
398
|
+
};
|
|
399
|
+
}
|
|
402
400
|
}
|
|
403
401
|
}
|
|
404
|
-
return
|
|
402
|
+
// None satisfied the specifications, so return a failure.
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
error: 'exhausted',
|
|
406
|
+
message: `Text found in only ${matchCount} (not ${index + 1}) of ${selections.length}`,
|
|
407
|
+
candidateTexts: selectionTexts
|
|
408
|
+
};
|
|
405
409
|
}
|
|
406
|
-
// Otherwise, i.e. if there are too few to make a match possible:
|
|
410
|
+
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
407
411
|
else {
|
|
408
|
-
// Return
|
|
409
|
-
return
|
|
412
|
+
// Return a failure.
|
|
413
|
+
return {
|
|
414
|
+
success: false,
|
|
415
|
+
error: 'fewer',
|
|
416
|
+
message: `Count of '${selector}' elements only ${selections.length}`
|
|
417
|
+
};
|
|
410
418
|
}
|
|
411
419
|
}
|
|
412
|
-
// Otherwise, i.e. if there are no
|
|
420
|
+
// Otherwise, i.e. if there are no elements of the specified type:
|
|
413
421
|
else {
|
|
414
|
-
|
|
422
|
+
// Return a failure.
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: 'none',
|
|
426
|
+
message: `No '${selector}' elements found`
|
|
427
|
+
};
|
|
415
428
|
}
|
|
416
429
|
}
|
|
417
|
-
// Otherwise, i.e. if the
|
|
430
|
+
// Otherwise, i.e. if the page no longer exists:
|
|
418
431
|
else {
|
|
419
|
-
// Return
|
|
420
|
-
return
|
|
432
|
+
// Return a failure.
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
error: 'gone',
|
|
436
|
+
message: 'Page gone'
|
|
437
|
+
};
|
|
421
438
|
}
|
|
422
439
|
}
|
|
423
|
-
// Otherwise, i.e. if
|
|
440
|
+
// Otherwise, i.e. if no text was specified:
|
|
424
441
|
else {
|
|
425
|
-
// Return
|
|
426
|
-
|
|
427
|
-
|
|
442
|
+
// Return a failure.
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: 'text',
|
|
446
|
+
message: 'No text specified'
|
|
447
|
+
};
|
|
428
448
|
}
|
|
429
449
|
};
|
|
430
450
|
// Returns a string with any final slash removed.
|
|
@@ -495,8 +515,9 @@ const visit = async (act, page, isStrict) => {
|
|
|
495
515
|
// If the visit fails:
|
|
496
516
|
if (response === 'error') {
|
|
497
517
|
// Give up.
|
|
498
|
-
|
|
499
|
-
|
|
518
|
+
const errorMsg = `ERROR: Attemts to visit ${requestedURL} failed`;
|
|
519
|
+
console.log(errorMsg);
|
|
520
|
+
act.result = errorMsg;
|
|
500
521
|
await page.goto('about:blank')
|
|
501
522
|
.catch(error => {
|
|
502
523
|
console.log(`ERROR: Navigation to blank page failed (${error.message})`);
|
|
@@ -549,13 +570,21 @@ const isTrue = (object, specs) => {
|
|
|
549
570
|
}
|
|
550
571
|
return [actual, satisfied];
|
|
551
572
|
};
|
|
552
|
-
// Adds
|
|
573
|
+
// Adds a wait error result to an act.
|
|
553
574
|
const waitError = (page, act, error, what) => {
|
|
554
575
|
console.log(`ERROR waiting for ${what} (${error.message})`);
|
|
555
576
|
act.result = {url: page.url()};
|
|
556
577
|
act.result.error = `ERROR waiting for ${what}`;
|
|
557
578
|
return false;
|
|
558
579
|
};
|
|
580
|
+
// Waits.
|
|
581
|
+
const wait = ms => {
|
|
582
|
+
return new Promise(resolve => {
|
|
583
|
+
setTimeout(() => {
|
|
584
|
+
resolve('');
|
|
585
|
+
}, ms);
|
|
586
|
+
});
|
|
587
|
+
};
|
|
559
588
|
// Recursively performs the acts in a report.
|
|
560
589
|
const doActs = async (report, actIndex, page) => {
|
|
561
590
|
const {acts} = report;
|
|
@@ -590,12 +619,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
590
619
|
if (truth[1]) {
|
|
591
620
|
// If the performance of commands is to stop:
|
|
592
621
|
if (act.jump === 0) {
|
|
593
|
-
//
|
|
622
|
+
// Stop.
|
|
594
623
|
actIndex = -2;
|
|
595
624
|
}
|
|
596
625
|
// Otherwise, if there is a numerical jump:
|
|
597
626
|
else if (act.jump) {
|
|
598
|
-
// Set the
|
|
627
|
+
// Set the act index accordingly.
|
|
599
628
|
actIndex += act.jump - 1;
|
|
600
629
|
}
|
|
601
630
|
// Otherwise, if there is a named next command:
|
|
@@ -623,58 +652,65 @@ const doActs = async (report, actIndex, page) => {
|
|
|
623
652
|
else if (act.type === 'wait') {
|
|
624
653
|
const {what, which} = act;
|
|
625
654
|
console.log(`>> for ${what} to include “${which}”`);
|
|
626
|
-
// Wait 5 seconds for the specified text
|
|
627
|
-
let successJSHandle;
|
|
655
|
+
// Wait 5 or 10 seconds for the specified text, and quit if it does not appear.
|
|
628
656
|
if (act.what === 'url') {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
657
|
+
await page.waitForURL(act.which, {timeout: 15000})
|
|
658
|
+
.catch(error => {
|
|
659
|
+
actIndex = -2;
|
|
660
|
+
waitError(page, act, error, 'URL');
|
|
661
|
+
});
|
|
633
662
|
}
|
|
634
663
|
else if (act.what === 'title') {
|
|
635
|
-
|
|
636
|
-
text => document.title.includes(text),
|
|
664
|
+
await page.waitForFunction(
|
|
665
|
+
text => document && document.title && document.title.includes(text),
|
|
666
|
+
act.which,
|
|
667
|
+
{
|
|
668
|
+
polling: 1000,
|
|
669
|
+
timeout: 5000
|
|
670
|
+
}
|
|
637
671
|
)
|
|
638
|
-
.catch(error =>
|
|
672
|
+
.catch(error => {
|
|
673
|
+
actIndex = -2;
|
|
674
|
+
waitError(page, act, error, 'title');
|
|
675
|
+
});
|
|
639
676
|
}
|
|
640
677
|
else if (act.what === 'body') {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
678
|
+
await page.waitForFunction(
|
|
679
|
+
text => document && document.body && document.body.innerText.includes(text),
|
|
680
|
+
act.which,
|
|
681
|
+
{
|
|
682
|
+
polling: 2000,
|
|
683
|
+
timeout: 10000
|
|
684
|
+
}
|
|
646
685
|
)
|
|
647
|
-
.catch(error =>
|
|
686
|
+
.catch(async error => {
|
|
687
|
+
actIndex = -2;
|
|
688
|
+
waitError(page, act, error, 'body');
|
|
689
|
+
});
|
|
648
690
|
}
|
|
649
|
-
|
|
691
|
+
// If the text was found:
|
|
692
|
+
if (actIndex > -2) {
|
|
693
|
+
// Add this to the report.
|
|
650
694
|
act.result = {url: page.url()};
|
|
651
695
|
if (act.what === 'title') {
|
|
652
696
|
act.result.title = await page.title();
|
|
653
697
|
}
|
|
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
698
|
}
|
|
660
699
|
}
|
|
661
700
|
// Otherwise, if the act is a wait for a state:
|
|
662
701
|
else if (act.type === 'state') {
|
|
663
|
-
//
|
|
702
|
+
// Wait for it, and quit if it does not appear.
|
|
664
703
|
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';
|
|
704
|
+
await page.waitForLoadState(
|
|
705
|
+
['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 5000][stateIndex]}
|
|
706
|
+
)
|
|
707
|
+
.catch(error => {
|
|
708
|
+
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
709
|
+
act.result = `ERROR waiting for page to be ${act.which}`;
|
|
710
|
+
actIndex = -2;
|
|
711
|
+
});
|
|
712
|
+
if (actIndex > -2) {
|
|
713
|
+
act.result = `Page became ${act.which}`;
|
|
678
714
|
}
|
|
679
715
|
}
|
|
680
716
|
// Otherwise, if the act is a page switch:
|
|
@@ -864,48 +900,34 @@ const doActs = async (report, actIndex, page) => {
|
|
|
864
900
|
// Otherwise, if the act is a move:
|
|
865
901
|
else if (moves[act.type]) {
|
|
866
902
|
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
|
-
};
|
|
903
|
+
// Wait 10 seconds until the element to perform the move on is identified.
|
|
904
|
+
let matchResult = {success: false};
|
|
905
|
+
let tries = 0;
|
|
906
|
+
while (tries++ < 5 && ! matchResult.success) {
|
|
907
|
+
matchResult = await matchElement(page, selector, act.which || '', act.index);
|
|
908
|
+
if (! matchResult.success) {
|
|
909
|
+
await wait(2000);
|
|
910
|
+
}
|
|
890
911
|
}
|
|
891
|
-
//
|
|
892
|
-
|
|
912
|
+
// If a match was found:
|
|
913
|
+
if (matchResult.success) {
|
|
914
|
+
const {matchingElement} = matchResult;
|
|
893
915
|
// If the move is a button click, perform it.
|
|
894
916
|
if (act.type === 'button') {
|
|
895
|
-
await
|
|
917
|
+
await matchingElement.click({timeout: 3000});
|
|
896
918
|
act.result = 'clicked';
|
|
897
919
|
}
|
|
898
920
|
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
899
921
|
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
900
|
-
await
|
|
922
|
+
await matchingElement.waitForElementState('stable', {timeout: 2000})
|
|
901
923
|
.catch(error => {
|
|
902
924
|
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
903
925
|
act.result = `ERROR waiting for stable ${act.type}`;
|
|
904
926
|
});
|
|
905
927
|
if (! act.result) {
|
|
906
|
-
const isEnabled = await
|
|
928
|
+
const isEnabled = await matchingElement.isEnabled();
|
|
907
929
|
if (isEnabled) {
|
|
908
|
-
await
|
|
930
|
+
await matchingElement.check({
|
|
909
931
|
force: true,
|
|
910
932
|
timeout: 2000
|
|
911
933
|
})
|
|
@@ -926,28 +948,53 @@ const doActs = async (report, actIndex, page) => {
|
|
|
926
948
|
}
|
|
927
949
|
// Otherwise, if it is focusing the element, perform it.
|
|
928
950
|
else if (act.type === 'focus') {
|
|
929
|
-
await
|
|
951
|
+
await matchingElement.focus({timeout: 2000});
|
|
930
952
|
act.result = 'focused';
|
|
931
953
|
}
|
|
932
954
|
// Otherwise, if it is clicking a link, perform it.
|
|
933
955
|
else if (act.type === 'link') {
|
|
934
|
-
const href = await
|
|
935
|
-
const target = await
|
|
936
|
-
await
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
956
|
+
const href = await matchingElement.getAttribute('href');
|
|
957
|
+
const target = await matchingElement.getAttribute('target');
|
|
958
|
+
await matchingElement.click({timeout: 2000})
|
|
959
|
+
.catch(async () => {
|
|
960
|
+
console.log('ERROR: First attempt to click link timed out');
|
|
961
|
+
await matchingElement.click({
|
|
962
|
+
force: true,
|
|
963
|
+
timeout: 10000
|
|
964
|
+
})
|
|
965
|
+
.catch(() => {
|
|
966
|
+
actIndex = -2;
|
|
967
|
+
console.log('ERROR: Second (forced) attempt to click link timed out');
|
|
968
|
+
act.result = 'ERROR: Normal and forced click attempts timed out';
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
if (actIndex > -2) {
|
|
972
|
+
act.result = {
|
|
973
|
+
href: href || 'NONE',
|
|
974
|
+
target: target || 'NONE',
|
|
975
|
+
move: 'clicked'
|
|
976
|
+
};
|
|
977
|
+
}
|
|
942
978
|
}
|
|
943
979
|
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
944
980
|
else if (act.type === 'select') {
|
|
945
|
-
await
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
981
|
+
const options = await matchingElement.$$('option');
|
|
982
|
+
let optionText = '';
|
|
983
|
+
if (options && Array.isArray(options) && options.length) {
|
|
984
|
+
const optionTexts = [];
|
|
985
|
+
for (const option of options) {
|
|
986
|
+
const optionText = await option.textContent();
|
|
987
|
+
optionTexts.push(optionText);
|
|
988
|
+
}
|
|
989
|
+
const matchTexts = optionTexts.map((text, index) => text.includes(act.what) ? index : -1);
|
|
990
|
+
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
991
|
+
if (index !== undefined) {
|
|
992
|
+
await matchingElement.selectOption({index});
|
|
993
|
+
optionText = optionTexts[index];
|
|
994
|
+
}
|
|
995
|
+
}
|
|
949
996
|
act.result = optionText
|
|
950
|
-
?
|
|
997
|
+
? `“${optionText}” selected`
|
|
951
998
|
: 'ERROR: option not found';
|
|
952
999
|
}
|
|
953
1000
|
// Otherwise, if it is entering text on the element:
|
|
@@ -961,19 +1008,24 @@ const doActs = async (report, actIndex, page) => {
|
|
|
961
1008
|
what = what.replace(/__[A-Z]+__/, envValue);
|
|
962
1009
|
}
|
|
963
1010
|
// Enter the text.
|
|
964
|
-
await
|
|
1011
|
+
await matchingElement.type(act.what);
|
|
965
1012
|
report.presses += act.what.length;
|
|
966
1013
|
act.result = 'entered';
|
|
967
1014
|
}
|
|
968
1015
|
// Otherwise, i.e. if the move is unknown, add the failure to the act.
|
|
969
1016
|
else {
|
|
970
1017
|
// Report the error.
|
|
971
|
-
|
|
1018
|
+
const report = 'ERROR: move unknown';
|
|
1019
|
+
act.result = report;
|
|
1020
|
+
console.log(report);
|
|
972
1021
|
}
|
|
973
1022
|
}
|
|
974
|
-
// Otherwise, i.e. if
|
|
1023
|
+
// Otherwise, i.e. if no match was found:
|
|
975
1024
|
else {
|
|
976
|
-
|
|
1025
|
+
// Stop.
|
|
1026
|
+
act.result = matchResult;
|
|
1027
|
+
console.log('ERROR: Specified element not found');
|
|
1028
|
+
actIndex = -2;
|
|
977
1029
|
}
|
|
978
1030
|
}
|
|
979
1031
|
// Otherwise, if the act is a keypress:
|
|
@@ -1161,7 +1213,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1161
1213
|
// Otherwise, i.e. if the command is invalid:
|
|
1162
1214
|
else {
|
|
1163
1215
|
// Add an error result to the act.
|
|
1164
|
-
|
|
1216
|
+
const errorMsg = `ERROR: Invalid command of type ${act.type}`;
|
|
1217
|
+
act.result = errorMsg;
|
|
1218
|
+
console.log(errorMsg);
|
|
1165
1219
|
}
|
|
1166
1220
|
// Perform the remaining acts.
|
|
1167
1221
|
await doActs(report, actIndex + 1, page);
|
|
@@ -1247,8 +1301,10 @@ exports.handleRequest = async report => {
|
|
|
1247
1301
|
report.timeStamp = report.id.replace(/-.+/, '');
|
|
1248
1302
|
// Add the script commands to the report as its initial acts.
|
|
1249
1303
|
report.acts = JSON.parse(JSON.stringify(report.script.commands));
|
|
1250
|
-
// Inject url acts where necessary to undo DOM changes.
|
|
1251
|
-
|
|
1304
|
+
// Inject url acts where necessary to undo DOM changes, if specified.
|
|
1305
|
+
if (urlInject === 'yes') {
|
|
1306
|
+
injectURLActs(report.acts);
|
|
1307
|
+
}
|
|
1252
1308
|
// Perform the acts, asynchronously adding to the log and report.
|
|
1253
1309
|
await doScript(report);
|
|
1254
1310
|
}
|
|
@@ -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
|
};
|