testaro 4.4.0 → 4.5.2
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 -3
- package/package.json +1 -1
- package/run.js +158 -146
- package/tests/aatt.js +66 -29
package/commands.js
CHANGED
|
@@ -100,8 +100,8 @@ exports.commands = {
|
|
|
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: [
|
|
@@ -146,7 +146,8 @@ exports.commands = {
|
|
|
146
146
|
aatt: [
|
|
147
147
|
'Perform an AATT test with HTML CodeSniffer',
|
|
148
148
|
{
|
|
149
|
-
waitLong: [false, 'boolean', '', 'whether to wait
|
|
149
|
+
waitLong: [false, 'boolean', '', 'whether to wait 12 instead of 6 seconds for a result'],
|
|
150
|
+
tryLimit: [false, 'number', '', 'times to try the test before giving up; default 4']
|
|
150
151
|
}
|
|
151
152
|
],
|
|
152
153
|
axe: [
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -256,7 +256,7 @@ const launch = async typeName => {
|
|
|
256
256
|
// Make its console messages appear in the Playwright console.
|
|
257
257
|
page.on('console', msg => {
|
|
258
258
|
const msgText = msg.text();
|
|
259
|
-
console.log(msgText);
|
|
259
|
+
console.log(`[${msgText}]`);
|
|
260
260
|
logCount++;
|
|
261
261
|
logSize += msgText.length;
|
|
262
262
|
const msgLC = msgText.toLowerCase();
|
|
@@ -291,7 +291,7 @@ const textOf = async (page, element) => {
|
|
|
291
291
|
if (['A', 'BUTTON', 'INPUT', 'SELECT'].includes(tagName)) {
|
|
292
292
|
// Return its visible labels, descriptions, and legend if the first input in a fieldset.
|
|
293
293
|
totalText = await page.evaluate(element => {
|
|
294
|
-
const {tagName} = element;
|
|
294
|
+
const {tagName, ariaLabel} = element;
|
|
295
295
|
let ownText = '';
|
|
296
296
|
if (['A', 'BUTTON'].includes(tagName)) {
|
|
297
297
|
ownText = element.textContent;
|
|
@@ -302,6 +302,9 @@ const textOf = async (page, element) => {
|
|
|
302
302
|
// HTML link elements have no labels property.
|
|
303
303
|
const labels = tagName !== 'A' ? Array.from(element.labels) : [];
|
|
304
304
|
const labelTexts = labels.map(label => label.textContent);
|
|
305
|
+
if (ariaLabel) {
|
|
306
|
+
labelTexts.push(ariaLabel);
|
|
307
|
+
}
|
|
305
308
|
const refIDs = new Set([
|
|
306
309
|
element.getAttribute('aria-labelledby') || '',
|
|
307
310
|
element.getAttribute('aria-describedby') || ''
|
|
@@ -369,69 +372,79 @@ const textOf = async (page, element) => {
|
|
|
369
372
|
return null;
|
|
370
373
|
}
|
|
371
374
|
};
|
|
372
|
-
// Returns an element case-insensitively matching a text.
|
|
375
|
+
// Returns an element of a type case-insensitively matching a text.
|
|
373
376
|
const matchElement = async (page, selector, matchText, index = 0) => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const textInBodyJSHandle = await page.waitForFunction(
|
|
381
|
-
args => {
|
|
382
|
-
const matchText = args[0];
|
|
383
|
-
const bodyText = args[1];
|
|
384
|
-
return ! matchText || bodyText.includes(matchText);
|
|
385
|
-
},
|
|
386
|
-
[slimText, slimBody],
|
|
387
|
-
{timeout: 4000}
|
|
388
|
-
)
|
|
389
|
-
.catch(async error => {
|
|
390
|
-
console.log(`ERROR: text “${matchText}” not in body (${error.message})`);
|
|
391
|
-
});
|
|
392
|
-
// If there is no text to be matched or the body contained it:
|
|
393
|
-
if (textInBodyJSHandle) {
|
|
394
|
-
const lcText = matchText ? matchText.toLowerCase() : '';
|
|
395
|
-
// Identify the selected elements.
|
|
396
|
-
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);
|
|
397
383
|
// If there are any:
|
|
398
384
|
if (selections.length) {
|
|
399
385
|
// If there are enough to make a match possible:
|
|
400
386
|
if (index < selections.length) {
|
|
401
|
-
// Return the
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
for (const
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
if (
|
|
408
|
-
|
|
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
|
+
}
|
|
409
400
|
}
|
|
410
401
|
}
|
|
411
|
-
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
|
+
};
|
|
412
409
|
}
|
|
413
|
-
// 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:
|
|
414
411
|
else {
|
|
415
|
-
// Return
|
|
416
|
-
return
|
|
412
|
+
// Return a failure.
|
|
413
|
+
return {
|
|
414
|
+
success: false,
|
|
415
|
+
error: 'fewer',
|
|
416
|
+
message: `Count of '${selector}' elements only ${selections.length}`
|
|
417
|
+
};
|
|
417
418
|
}
|
|
418
419
|
}
|
|
419
|
-
// Otherwise, i.e. if there are no
|
|
420
|
+
// Otherwise, i.e. if there are no elements of the specified type:
|
|
420
421
|
else {
|
|
421
|
-
|
|
422
|
+
// Return a failure.
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: 'none',
|
|
426
|
+
message: `No '${selector}' elements found`
|
|
427
|
+
};
|
|
422
428
|
}
|
|
423
429
|
}
|
|
424
|
-
// Otherwise, i.e. if the
|
|
430
|
+
// Otherwise, i.e. if the page no longer exists:
|
|
425
431
|
else {
|
|
426
|
-
// Return
|
|
427
|
-
return
|
|
432
|
+
// Return a failure.
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
error: 'gone',
|
|
436
|
+
message: 'Page gone'
|
|
437
|
+
};
|
|
428
438
|
}
|
|
429
439
|
}
|
|
430
|
-
// Otherwise, i.e. if
|
|
440
|
+
// Otherwise, i.e. if no text was specified:
|
|
431
441
|
else {
|
|
432
|
-
// Return
|
|
433
|
-
|
|
434
|
-
|
|
442
|
+
// Return a failure.
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: 'text',
|
|
446
|
+
message: 'No text specified'
|
|
447
|
+
};
|
|
435
448
|
}
|
|
436
449
|
};
|
|
437
450
|
// Returns a string with any final slash removed.
|
|
@@ -502,8 +515,9 @@ const visit = async (act, page, isStrict) => {
|
|
|
502
515
|
// If the visit fails:
|
|
503
516
|
if (response === 'error') {
|
|
504
517
|
// Give up.
|
|
505
|
-
|
|
506
|
-
|
|
518
|
+
const errorMsg = `ERROR: Attemts to visit ${requestedURL} failed`;
|
|
519
|
+
console.log(errorMsg);
|
|
520
|
+
act.result = errorMsg;
|
|
507
521
|
await page.goto('about:blank')
|
|
508
522
|
.catch(error => {
|
|
509
523
|
console.log(`ERROR: Navigation to blank page failed (${error.message})`);
|
|
@@ -563,6 +577,14 @@ const waitError = (page, act, error, what) => {
|
|
|
563
577
|
act.result.error = `ERROR waiting for ${what}`;
|
|
564
578
|
return false;
|
|
565
579
|
};
|
|
580
|
+
// Waits.
|
|
581
|
+
const wait = ms => {
|
|
582
|
+
return new Promise(resolve => {
|
|
583
|
+
setTimeout(() => {
|
|
584
|
+
resolve('');
|
|
585
|
+
}, ms);
|
|
586
|
+
});
|
|
587
|
+
};
|
|
566
588
|
// Recursively performs the acts in a report.
|
|
567
589
|
const doActs = async (report, actIndex, page) => {
|
|
568
590
|
const {acts} = report;
|
|
@@ -597,12 +619,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
597
619
|
if (truth[1]) {
|
|
598
620
|
// If the performance of commands is to stop:
|
|
599
621
|
if (act.jump === 0) {
|
|
600
|
-
//
|
|
622
|
+
// Stop.
|
|
601
623
|
actIndex = -2;
|
|
602
624
|
}
|
|
603
625
|
// Otherwise, if there is a numerical jump:
|
|
604
626
|
else if (act.jump) {
|
|
605
|
-
// Set the
|
|
627
|
+
// Set the act index accordingly.
|
|
606
628
|
actIndex += act.jump - 1;
|
|
607
629
|
}
|
|
608
630
|
// Otherwise, if there is a named next command:
|
|
@@ -630,76 +652,65 @@ const doActs = async (report, actIndex, page) => {
|
|
|
630
652
|
else if (act.type === 'wait') {
|
|
631
653
|
const {what, which} = act;
|
|
632
654
|
console.log(`>> for ${what} to include “${which}”`);
|
|
633
|
-
// Wait 5 or 10 seconds for the specified text, and quit if it does not.
|
|
634
|
-
let successJSHandle;
|
|
655
|
+
// Wait 5 or 10 seconds for the specified text, and quit if it does not appear.
|
|
635
656
|
if (act.what === 'url') {
|
|
636
|
-
|
|
637
|
-
text => document.URL.includes(text), act.which, {timeout: 5000}
|
|
638
|
-
)
|
|
657
|
+
await page.waitForURL(act.which, {timeout: 15000})
|
|
639
658
|
.catch(error => {
|
|
640
|
-
actIndex =
|
|
641
|
-
|
|
659
|
+
actIndex = -2;
|
|
660
|
+
waitError(page, act, error, 'URL');
|
|
642
661
|
});
|
|
643
662
|
}
|
|
644
663
|
else if (act.what === 'title') {
|
|
645
|
-
|
|
646
|
-
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
|
+
}
|
|
647
671
|
)
|
|
648
672
|
.catch(error => {
|
|
649
|
-
actIndex =
|
|
650
|
-
|
|
673
|
+
actIndex = -2;
|
|
674
|
+
waitError(page, act, error, 'title');
|
|
651
675
|
});
|
|
652
676
|
}
|
|
653
677
|
else if (act.what === 'body') {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
actIndex = acts.length;
|
|
662
|
-
console.log('ERROR finding document body');
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
665
|
-
}, which, {timeout: 20000}
|
|
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
|
+
}
|
|
666
685
|
)
|
|
667
|
-
.catch(error => {
|
|
668
|
-
actIndex =
|
|
669
|
-
|
|
686
|
+
.catch(async error => {
|
|
687
|
+
actIndex = -2;
|
|
688
|
+
waitError(page, act, error, 'body');
|
|
670
689
|
});
|
|
671
690
|
}
|
|
672
|
-
|
|
691
|
+
// If the text was found:
|
|
692
|
+
if (actIndex > -2) {
|
|
693
|
+
// Add this to the report.
|
|
673
694
|
act.result = {url: page.url()};
|
|
674
695
|
if (act.what === 'title') {
|
|
675
696
|
act.result.title = await page.title();
|
|
676
697
|
}
|
|
677
|
-
await page.waitForLoadState('networkidle', {timeout: 10000})
|
|
678
|
-
.catch(error => {
|
|
679
|
-
console.log(`ERROR waiting for stability after ${act.what} (${error.message})`);
|
|
680
|
-
act.result.error = `ERROR waiting for stability after ${act.what}`;
|
|
681
|
-
});
|
|
682
698
|
}
|
|
683
699
|
}
|
|
684
700
|
// Otherwise, if the act is a wait for a state:
|
|
685
701
|
else if (act.type === 'state') {
|
|
686
|
-
//
|
|
702
|
+
// Wait for it, and quit if it does not appear.
|
|
687
703
|
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
)
|
|
693
|
-
.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
console.log('ERROR: invalid state');
|
|
701
|
-
act.result = 'ERROR: invalid state';
|
|
702
|
-
actIndex = acts.length;
|
|
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}`;
|
|
703
714
|
}
|
|
704
715
|
}
|
|
705
716
|
// Otherwise, if the act is a page switch:
|
|
@@ -889,48 +900,34 @@ const doActs = async (report, actIndex, page) => {
|
|
|
889
900
|
// Otherwise, if the act is a move:
|
|
890
901
|
else if (moves[act.type]) {
|
|
891
902
|
const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
candidateTexts: whichElement
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
// Otherwise, if the body did not contain the text:
|
|
904
|
-
else if (whichElement === -1) {
|
|
905
|
-
// Add the failure to the act.
|
|
906
|
-
act.result = 'ERROR: body did not contain text to match';
|
|
907
|
-
}
|
|
908
|
-
// Otherwise, if there were not enough candidates:
|
|
909
|
-
else if (typeof whichElement === 'number') {
|
|
910
|
-
// Add the failure to the act.
|
|
911
|
-
act.result = {
|
|
912
|
-
candidateCount: whichElement,
|
|
913
|
-
error: 'ERROR: too few such elements to allow a match'
|
|
914
|
-
};
|
|
903
|
+
// Try up to 5 times, every 2 seconds, to identify the element to perform the move on.
|
|
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
|
+
}
|
|
915
911
|
}
|
|
916
|
-
//
|
|
917
|
-
|
|
912
|
+
// If a match was found:
|
|
913
|
+
if (matchResult.success) {
|
|
914
|
+
const {matchingElement} = matchResult;
|
|
918
915
|
// If the move is a button click, perform it.
|
|
919
916
|
if (act.type === 'button') {
|
|
920
|
-
await
|
|
917
|
+
await matchingElement.click({timeout: 3000});
|
|
921
918
|
act.result = 'clicked';
|
|
922
919
|
}
|
|
923
920
|
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
924
921
|
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
925
|
-
await
|
|
922
|
+
await matchingElement.waitForElementState('stable', {timeout: 2000})
|
|
926
923
|
.catch(error => {
|
|
927
924
|
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
928
925
|
act.result = `ERROR waiting for stable ${act.type}`;
|
|
929
926
|
});
|
|
930
927
|
if (! act.result) {
|
|
931
|
-
const isEnabled = await
|
|
928
|
+
const isEnabled = await matchingElement.isEnabled();
|
|
932
929
|
if (isEnabled) {
|
|
933
|
-
await
|
|
930
|
+
await matchingElement.check({
|
|
934
931
|
force: true,
|
|
935
932
|
timeout: 2000
|
|
936
933
|
})
|
|
@@ -951,29 +948,37 @@ const doActs = async (report, actIndex, page) => {
|
|
|
951
948
|
}
|
|
952
949
|
// Otherwise, if it is focusing the element, perform it.
|
|
953
950
|
else if (act.type === 'focus') {
|
|
954
|
-
await
|
|
951
|
+
await matchingElement.focus({timeout: 2000});
|
|
955
952
|
act.result = 'focused';
|
|
956
953
|
}
|
|
957
954
|
// Otherwise, if it is clicking a link, perform it.
|
|
958
955
|
else if (act.type === 'link') {
|
|
959
|
-
const href = await
|
|
960
|
-
const target = await
|
|
961
|
-
await
|
|
956
|
+
const href = await matchingElement.getAttribute('href');
|
|
957
|
+
const target = await matchingElement.getAttribute('target');
|
|
958
|
+
await matchingElement.click({timeout: 2000})
|
|
962
959
|
.catch(async () => {
|
|
963
|
-
|
|
960
|
+
console.log('ERROR: First attempt to click link timed out');
|
|
961
|
+
await matchingElement.click({
|
|
964
962
|
force: true,
|
|
965
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';
|
|
966
969
|
});
|
|
967
970
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
971
|
+
if (actIndex > -2) {
|
|
972
|
+
act.result = {
|
|
973
|
+
href: href || 'NONE',
|
|
974
|
+
target: target || 'NONE',
|
|
975
|
+
move: 'clicked'
|
|
976
|
+
};
|
|
977
|
+
}
|
|
973
978
|
}
|
|
974
979
|
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
975
980
|
else if (act.type === 'select') {
|
|
976
|
-
const options = await
|
|
981
|
+
const options = await matchingElement.$$('option');
|
|
977
982
|
let optionText = '';
|
|
978
983
|
if (options && Array.isArray(options) && options.length) {
|
|
979
984
|
const optionTexts = [];
|
|
@@ -984,12 +989,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
984
989
|
const matchTexts = optionTexts.map((text, index) => text.includes(act.what) ? index : -1);
|
|
985
990
|
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
986
991
|
if (index !== undefined) {
|
|
987
|
-
await
|
|
992
|
+
await matchingElement.selectOption({index});
|
|
988
993
|
optionText = optionTexts[index];
|
|
989
994
|
}
|
|
990
995
|
}
|
|
991
996
|
act.result = optionText
|
|
992
|
-
?
|
|
997
|
+
? `“${optionText}” selected`
|
|
993
998
|
: 'ERROR: option not found';
|
|
994
999
|
}
|
|
995
1000
|
// Otherwise, if it is entering text on the element:
|
|
@@ -1003,19 +1008,24 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1003
1008
|
what = what.replace(/__[A-Z]+__/, envValue);
|
|
1004
1009
|
}
|
|
1005
1010
|
// Enter the text.
|
|
1006
|
-
await
|
|
1011
|
+
await matchingElement.type(act.what);
|
|
1007
1012
|
report.presses += act.what.length;
|
|
1008
1013
|
act.result = 'entered';
|
|
1009
1014
|
}
|
|
1010
1015
|
// Otherwise, i.e. if the move is unknown, add the failure to the act.
|
|
1011
1016
|
else {
|
|
1012
1017
|
// Report the error.
|
|
1013
|
-
|
|
1018
|
+
const report = 'ERROR: move unknown';
|
|
1019
|
+
act.result = report;
|
|
1020
|
+
console.log(report);
|
|
1014
1021
|
}
|
|
1015
1022
|
}
|
|
1016
|
-
// Otherwise, i.e. if
|
|
1023
|
+
// Otherwise, i.e. if no match was found:
|
|
1017
1024
|
else {
|
|
1018
|
-
|
|
1025
|
+
// Stop.
|
|
1026
|
+
act.result = matchResult;
|
|
1027
|
+
console.log('ERROR: Specified element not found');
|
|
1028
|
+
actIndex = -2;
|
|
1019
1029
|
}
|
|
1020
1030
|
}
|
|
1021
1031
|
// Otherwise, if the act is a keypress:
|
|
@@ -1203,7 +1213,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1203
1213
|
// Otherwise, i.e. if the command is invalid:
|
|
1204
1214
|
else {
|
|
1205
1215
|
// Add an error result to the act.
|
|
1206
|
-
|
|
1216
|
+
const errorMsg = `ERROR: Invalid command of type ${act.type}`;
|
|
1217
|
+
act.result = errorMsg;
|
|
1218
|
+
console.log(errorMsg);
|
|
1207
1219
|
}
|
|
1208
1220
|
// Perform the remaining acts.
|
|
1209
1221
|
await doActs(report, actIndex + 1, page);
|
package/tests/aatt.js
CHANGED
|
@@ -7,31 +7,59 @@
|
|
|
7
7
|
const {evaluate} = require('aatt');
|
|
8
8
|
|
|
9
9
|
// FUNCTIONS
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
source
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
10
|
+
// Recursively test a page with HTML CodeSniffer for WCAC 2.1 AAA.
|
|
11
|
+
const retest = async (page, waitLong, triesLeft) => {
|
|
12
|
+
// If the limit on tries has not been exhausted:
|
|
13
|
+
if (triesLeft) {
|
|
14
|
+
// Set the limit in seconds on the wait for the result.
|
|
15
|
+
const timeLimit = waitLong ? 12 : 6;
|
|
16
|
+
// Get the HTML of the document body.
|
|
17
|
+
const source = await page.content();
|
|
18
|
+
// Return the result of a test with the HTML CodeSniffer WCAG 2.1 AA ruleset as a string.
|
|
19
|
+
const report = evaluate({
|
|
20
|
+
source,
|
|
21
|
+
output: 'json',
|
|
22
|
+
engine: 'htmlcs',
|
|
23
|
+
level: 'WCAG2AAA'
|
|
24
|
+
});
|
|
25
|
+
// Wait for it until the time limit expires.
|
|
26
|
+
let timeoutID;
|
|
27
|
+
const wait = new Promise(resolve => {
|
|
28
|
+
timeoutID = setTimeout(() => {
|
|
29
|
+
resolve('');
|
|
30
|
+
}, 1000 * timeLimit);
|
|
31
|
+
});
|
|
32
|
+
const result = await Promise.race([report, wait]);
|
|
33
|
+
// If it arrived within the time limit:
|
|
34
|
+
if (result) {
|
|
35
|
+
clearTimeout(timeoutID);
|
|
36
|
+
// Return the result as JSON.
|
|
37
|
+
const reportJSON = result.replace(/^.+?Object.?\s+|\s+done\s*$/sg, '');
|
|
38
|
+
return {
|
|
39
|
+
triesLeft,
|
|
40
|
+
reportJSON
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`ERROR: Test aatt timed out at ${timeLimit} seconds; tries left: ${triesLeft - 1}`);
|
|
45
|
+
return retest(page, waitLong, --triesLeft);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Otherwise, i.e. if the limit on tries has been exhausted:
|
|
49
|
+
else {
|
|
50
|
+
// Return this.
|
|
51
|
+
return {
|
|
52
|
+
triesLeft,
|
|
53
|
+
reportJSON: ''
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
exports.reporter = async (page, waitLong, tryLimit = 4) => {
|
|
58
|
+
// Try the test up to the limit on tries.
|
|
59
|
+
const result = await retest(page, waitLong, tryLimit);
|
|
60
|
+
const {triesLeft, reportJSON} = result;
|
|
61
|
+
// If any try succeeded:
|
|
62
|
+
if (reportJSON) {
|
|
35
63
|
try {
|
|
36
64
|
// Convert the JSON string to an array.
|
|
37
65
|
const issueArray = JSON.parse(reportJSON);
|
|
@@ -49,10 +77,18 @@ exports.reporter = async (page, waitLong) => {
|
|
|
49
77
|
.join('+');
|
|
50
78
|
}
|
|
51
79
|
});
|
|
52
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
result: {
|
|
82
|
+
report: nonNotices,
|
|
83
|
+
triesLeft
|
|
84
|
+
}
|
|
85
|
+
};
|
|
53
86
|
}
|
|
54
87
|
catch (error) {
|
|
55
88
|
console.log(`ERROR processing AATT report (${error.message})`);
|
|
89
|
+
console.log(
|
|
90
|
+
`JSON report starts with ${reportJSON.slice(0, 50)} and ends with ${reportJSON.slice(-50)}`
|
|
91
|
+
);
|
|
56
92
|
return {
|
|
57
93
|
result: {
|
|
58
94
|
prevented: true,
|
|
@@ -61,14 +97,15 @@ exports.reporter = async (page, waitLong) => {
|
|
|
61
97
|
};
|
|
62
98
|
}
|
|
63
99
|
}
|
|
64
|
-
// Otherwise, i.e. if the
|
|
100
|
+
// Otherwise, i.e. if the limit on tries was exhausted:
|
|
65
101
|
else {
|
|
66
102
|
// Report the failure.
|
|
67
|
-
|
|
103
|
+
const error = 'ERROR: Getting AATT report took too long';
|
|
104
|
+
console.log(error);
|
|
68
105
|
return {
|
|
69
106
|
result: {
|
|
70
107
|
prevented: true,
|
|
71
|
-
error
|
|
108
|
+
error
|
|
72
109
|
}
|
|
73
110
|
};
|
|
74
111
|
}
|