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 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', 'hasLength', '“loaded” or “idle”'],
104
- what: [false, 'string', 'isState', 'comment']
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
@@ -1,5 +1,8 @@
1
- // high.js
2
- // Invokes Testaro with the high-level method.
1
+ /*
2
+ high.js
3
+ Invokes Testaro with the high-level method.
4
+ Usage example: node high tp10 weborgs
5
+ */
3
6
 
4
7
  const {runJob} = require('./create');
5
8
  const scriptID = process.argv[2];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "4.3.2",
3
+ "version": "4.5.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
- const ownText = ['A', 'BUTTON'].includes(tagName) ? element.textContent : '';
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
- // If the page still exists:
368
- if (page) {
369
- // Wait 2 seconds until the body contains any text to be matched.
370
- const slimText = debloat(matchText);
371
- const bodyText = await page.textContent('body');
372
- const slimBody = debloat(bodyText);
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 nth one including any specified text, or the count of candidates if none.
395
- const elementTexts = [];
396
- let nth = 0;
397
- for (const element of selections) {
398
- const elementText = await textOf(page, element);
399
- elementTexts.push(elementText);
400
- if ((! lcText || elementText.includes(lcText)) && nth++ === index) {
401
- return element;
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 elementTexts;
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 the count of candidates.
409
- return selections.length;
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 selected elements, return 0.
417
+ // Otherwise, i.e. if there are no elements of the specified type:
413
418
  else {
414
- return 0;
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 body did not contain it:
427
+ // Otherwise, i.e. if the page no longer exists:
418
428
  else {
419
- // Return the failure.
420
- return -1;
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 the page no longer exists:
437
+ // Otherwise, i.e. if no text was specified:
424
438
  else {
425
- // Return null.
426
- console.log('ERROR: Page gone');
427
- return null;
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
- console.log(`ERROR: Visits to ${requestedURL} failed`);
499
- act.result = `ERROR: Visit to ${requestedURL} failed`;
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 an error result to an act.
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 command index to cause a stop.
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 command index accordingly.
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 to appear in the specified place.
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
- successJSHandle = await page.waitForFunction(
630
- text => document.URL.includes(text), act.which, {timeout: 5000}
631
- )
632
- .catch(error => waitError(page, act, error, 'URL'));
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
- successJSHandle = await page.waitForFunction(
636
- text => document.title.includes(text), act.which, {timeout: 5000}
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 => waitError(page, act, error, 'title'));
669
+ .catch(error => {
670
+ actIndex = -2;
671
+ waitError(page, act, error, 'title');
672
+ });
639
673
  }
640
674
  else if (act.what === 'body') {
641
- successJSHandle = await page.waitForFunction(
642
- matchText => {
643
- const innerText = document.body.innerText;
644
- return innerText.includes(matchText);
645
- }, which, {timeout: 5000}
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 => waitError(page, act, error, 'body'));
683
+ .catch(async error => {
684
+ actIndex = -2;
685
+ waitError(page, act, error, 'body');
686
+ });
648
687
  }
649
- if (successJSHandle) {
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
- // If the state is valid:
699
+ // Wait for it, and quit if it does not appear.
664
700
  const stateIndex = ['loaded', 'idle'].indexOf(act.which);
665
- if (stateIndex !== -1) {
666
- // Wait for it.
667
- await page.waitForLoadState(
668
- ['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 5000][stateIndex]}
669
- )
670
- .catch(error => {
671
- console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
672
- act.result = `ERROR waiting for page to be ${act.which}`;
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
- // Identify the element to perform the move on.
868
- const whichElement = await matchElement(page, selector, act.which || '', act.index);
869
- // If there were enough candidates but no text match:
870
- if (Array.isArray(whichElement)) {
871
- // Add the result to the act.
872
- act.result = {
873
- candidateCount: whichElement.length,
874
- error: 'ERROR: no element with matching text found',
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
- // Otherwise, if a match was found:
892
- else if (whichElement !== null) {
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 whichElement.click({timeout: 3000});
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 whichElement.waitForElementState('stable', {timeout: 2000})
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 whichElement.isEnabled();
925
+ const isEnabled = await matchingElement.isEnabled();
907
926
  if (isEnabled) {
908
- await whichElement.check({
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 whichElement.focus({timeout: 2000});
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 whichElement.getAttribute('href');
935
- const target = await whichElement.getAttribute('target');
936
- await whichElement.click({timeout: 2000});
937
- act.result = {
938
- href: href || 'NONE',
939
- target: target || 'NONE',
940
- move: 'clicked'
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 whichElement.selectOption({what: act.what});
946
- const optionText = await whichElement.$eval(
947
- 'option:selected', el => el.textContent
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
- ? `&ldquo;${optionText}}&rdquo; selected`
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 whichElement.type(act.what);
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
- act.result = 'ERROR: move unknown';
1015
+ const report = 'ERROR: move unknown';
1016
+ act.result = report;
1017
+ console.log(report);
972
1018
  }
973
1019
  }
974
- // Otherwise, i.e. if the page was gone:
1020
+ // Otherwise, i.e. if no match was found:
975
1021
  else {
976
- act.result = 'ERROR: page gone, so matching element not found';
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
- act.result = `ERROR: Invalid command of type ${act.type}`;
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
- injectURLActs(report.acts);
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "a11yorgs",
3
- "what": "Pages expected to pass all accessibility tests",
3
+ "what": "Home pages of accessibility organizations",
4
4
  "hosts": [
5
5
  {
6
6
  "id": "iaap",
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "eurail",
3
+ "what": "Eurail",
4
+ "hosts": [
5
+ {
6
+ "id": "eurail",
7
+ "which": "https://www.eurail.com/en",
8
+ "what": "Eurail"
9
+ }
10
+ ]
11
+ }
@@ -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 {result: 'ERROR processing AATT report'};
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 {result: 'ERROR: getting AATT report took too long'};
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 {result: {error: 'ERROR: navigation to URL timed out'}};
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.result = 'ERROR: axe injection failed';
19
+ data.prevented = true;
20
+ data.error = 'ERROR: axe injection failed';
20
21
  });
21
22
  // If the injection succeeded:
22
- if (! data.result) {
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
@@ -12,6 +12,7 @@ exports.reporter = async page => {
12
12
  await page.waitForSelector('body', {timeout: 10000})
13
13
  .catch(error => {
14
14
  console.log(`ERROR (${error.message})`);
15
+ data.prevented = true;
15
16
  data.error = 'ERROR: bulk timed out';
16
17
  return {result: data};
17
18
  });
package/tests/focOp.js CHANGED
@@ -127,6 +127,7 @@ exports.reporter = async (page, withItems) => {
127
127
  }, withItems)
128
128
  .catch(error => {
129
129
  console.log(`ERROR getting focOp data (${error.message})`);
130
+ data.prevented = true;
130
131
  });
131
132
  return {result: data};
132
133
  };
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 = {error: 'ERROR: labClash failed'};
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 {result: {error: 'ERROR: screenshots failed'}};
113
+ return {
114
+ result: {
115
+ prevented: true,
116
+ error: 'ERROR: screenshots failed'
117
+ }
118
+ };
114
119
  }
115
120
  };
@@ -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
- exports.reporter = async (page, withItems) => {
7
- // Initialize a report.
8
- const data = {
9
- totals: {
10
- navigations: {
11
- all: {
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
- specific: {
17
- tab: {
18
- total: 0,
19
- correct: 0,
20
- incorrect: 0
21
- },
22
- left: {
23
- total: 0,
24
- correct: 0,
25
- incorrect: 0
26
- },
27
- right: {
28
- total: 0,
29
- correct: 0,
30
- incorrect: 0
31
- },
32
- up: {
33
- total: 0,
34
- correct: 0,
35
- incorrect: 0
36
- },
37
- down: {
38
- total: 0,
39
- correct: 0,
40
- incorrect: 0
41
- },
42
- home: {
43
- total: 0,
44
- correct: 0,
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 !== 'ERROR') {
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 {result: {
111
- error: 'ERROR: test status not completed',
112
- testStatus
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
  };
package/tests/wave.js CHANGED
@@ -32,6 +32,7 @@ exports.reporter = async (page, reportType) => {
32
32
  }
33
33
  catch (error) {
34
34
  return resolve({
35
+ prevented: true,
35
36
  error: 'WAVE did not return JSON.',
36
37
  report
37
38
  });