testaro 5.10.0 → 5.11.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/run.js +230 -198
package/README.md CHANGED
@@ -316,6 +316,8 @@ Tenon recommends giving it a public URL rather than giving it the content of a p
316
316
 
317
317
  The `continuum` test makes use of the files in the `continuum` directory. The test inserts the contents of all three files into the page as scripts and then uses them to perform the tests of the Continuum package.
318
318
 
319
+ Level Access on 22 August 2022 granted authorization for the copying of the `AccessEngine.community.js` file insofar as necessary for allowing Continuum community edition tests to be included in Testaro.
320
+
319
321
  ###### HTML CodeSniffer
320
322
 
321
323
  The `htmlcs` test makes use of the`htmlcs/HTMLCS.js` file. That file was created, and can be recreated if necessary, as follows:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "5.10.0",
3
+ "version": "5.11.2",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -294,16 +294,11 @@ const launch = async typeName => {
294
294
  });
295
295
  // If the launch succeeded:
296
296
  if (healthy) {
297
- // Create a new context (window) in it, taller if debugging is on.
298
- const viewport = debug ? {
299
- viewPort: {
300
- width: 1280,
301
- height: 1120
302
- }
303
- } : {};
304
- browserContext = await browser.newContext(viewport);
305
- // When a page is added to the browser context:
306
- browserContext.on('page', page => {
297
+ browserContext = await browser.newContext();
298
+ // When a page (i.e. browser tab) is added to the browser context (i.e. browser window):
299
+ browserContext.on('page', async page => {
300
+ // Activate the page.
301
+ await page.bringToFront();
307
302
  // Make abbreviations of its console messages get reported in the Playwright console.
308
303
  page.on('console', msg => {
309
304
  const msgText = msg.text();
@@ -339,12 +334,6 @@ const launch = async typeName => {
339
334
  });
340
335
  // Open the first page of the context.
341
336
  const page = await browserContext.newPage();
342
- if (debug) {
343
- page.setViewportSize({
344
- width: 1280,
345
- height: 1120
346
- });
347
- }
348
337
  // Wait until it is stable.
349
338
  await page.waitForLoadState('domcontentloaded');
350
339
  // Update the name of the current browser type and store it in the page.
@@ -445,85 +434,6 @@ const textOf = async (page, element) => {
445
434
  return null;
446
435
  }
447
436
  };
448
- // Returns an element of a type case-insensitively including a text.
449
- const matchElement = async (page, selector, matchText, index = 0) => {
450
- if (matchText) {
451
- // If the page still exists:
452
- if (page) {
453
- const slimText = debloat(matchText);
454
- // Identify the elements of the specified type.
455
- const selections = await page.$$(selector);
456
- // If there are any:
457
- if (selections.length) {
458
- // If there are enough to make a match possible:
459
- if (index < selections.length) {
460
- // For each element of the specified type:
461
- let matchCount = 0;
462
- const selectionTexts = [];
463
- for (const selection of selections) {
464
- // Add its text to the list of texts of such elements.
465
- const selectionText = await textOf(page, selection);
466
- selectionTexts.push(selectionText);
467
- // If its text includes the specified text:
468
- if (selectionText.includes(slimText)) {
469
- // If the count of such elements with such texts found so far is the specified count:
470
- if (matchCount++ === index) {
471
- // Return it as the matching element.
472
- return {
473
- success: true,
474
- matchingElement: selection
475
- };
476
- }
477
- }
478
- }
479
- // None satisfied the specifications, so return a failure.
480
- return {
481
- success: false,
482
- error: 'exhausted',
483
- message: `Text found in only ${matchCount} (not ${index + 1}) of ${selections.length}`,
484
- candidateTexts: selectionTexts
485
- };
486
- }
487
- // Otherwise, i.e. if there are too few such elements to make a match possible:
488
- else {
489
- // Return a failure.
490
- return {
491
- success: false,
492
- error: 'fewer',
493
- message: `Count of '${selector}' elements only ${selections.length}`
494
- };
495
- }
496
- }
497
- // Otherwise, i.e. if there are no elements of the specified type:
498
- else {
499
- // Return a failure.
500
- return {
501
- success: false,
502
- error: 'none',
503
- message: `No '${selector}' elements found`
504
- };
505
- }
506
- }
507
- // Otherwise, i.e. if the page no longer exists:
508
- else {
509
- // Return a failure.
510
- return {
511
- success: false,
512
- error: 'gone',
513
- message: 'Page gone'
514
- };
515
- }
516
- }
517
- // Otherwise, i.e. if no text was specified:
518
- else {
519
- // Return a failure.
520
- return {
521
- success: false,
522
- error: 'text',
523
- message: 'No text specified'
524
- };
525
- }
526
- };
527
437
  // Returns a string with any final slash removed.
528
438
  const deSlash = string => string.endsWith('/') ? string.slice(0, -1) : string;
529
439
  // Tries to visit a URL.
@@ -582,11 +492,11 @@ const visit = async (act, page, isStrict) => {
582
492
  // Identify the URL.
583
493
  const resolved = act.which.replace('__dirname', __dirname);
584
494
  requestedURL = resolved;
585
- // Visit it and wait 15 seconds or until the network is idle.
495
+ // Visit it and wait until the network is idle.
586
496
  let response = await goto(page, requestedURL, 15000, 'networkidle', isStrict);
587
497
  // If the visit fails:
588
498
  if (response === 'error') {
589
- // Try again, but waiting 10 seconds or until the DOM is loaded.
499
+ // Try again until the DOM is loaded.
590
500
  response = await goto(page, requestedURL, 10000, 'domcontentloaded', isStrict);
591
501
  // If the visit fails:
592
502
  if (response === 'error') {
@@ -597,15 +507,15 @@ const visit = async (act, page, isStrict) => {
597
507
  await launch(newBrowserName);
598
508
  // Identify its only page as current.
599
509
  page = browserContext.pages()[0];
600
- // Try again, waiting 10 seconds or until the network is idle.
510
+ // Try again until the network is idle.
601
511
  response = await goto(page, requestedURL, 10000, 'networkidle', isStrict);
602
512
  // If the visit fails:
603
513
  if (response === 'error') {
604
- // Try again, but waiting 5 seconds or until the DOM is loaded.
514
+ // Try again until the DOM is loaded.
605
515
  response = await goto(page, requestedURL, 5000, 'domcontentloaded', isStrict);
606
516
  // If the visit fails:
607
517
  if (response === 'error') {
608
- // Try again, waiting 5 seconds or until a load.
518
+ // Try again or until a load.
609
519
  response = await goto(page, requestedURL, 5000, 'load', isStrict);
610
520
  // If the visit fails:
611
521
  if (response === 'error') {
@@ -682,10 +592,10 @@ const wait = ms => {
682
592
  });
683
593
  };
684
594
  // Adds an error result to an act.
685
- const addError = (act, error) => {
686
- act.result = {
687
- error
688
- };
595
+ const addError = (act, error, message) => {
596
+ act.result.success = false;
597
+ act.result.error = error;
598
+ act.result.message = message;
689
599
  if (act.type === 'test') {
690
600
  act.result.prevented = true;
691
601
  }
@@ -709,6 +619,7 @@ const doActs = async (report, actIndex, page) => {
709
619
  console.log(`>>>> ${act.type}${whichSuffix}`);
710
620
  // Increment the count of commands performed.
711
621
  actCount++;
622
+ act.startTime = Date.now();
712
623
  // If the command is an index changer:
713
624
  if (act.type === 'next') {
714
625
  const condition = act.if;
@@ -764,8 +675,9 @@ const doActs = async (report, actIndex, page) => {
764
675
  const {what, which} = act;
765
676
  console.log(`>> ${what}`);
766
677
  const result = act.result = {};
767
- // Wait for the specified text, and quit if it does not appear.
678
+ // If the text is to be the URL:
768
679
  if (what === 'url') {
680
+ // Wait for it and quit on failure.
769
681
  try {
770
682
  await page.waitForURL(which, {timeout: 15000});
771
683
  result.found = true;
@@ -776,7 +688,9 @@ const doActs = async (report, actIndex, page) => {
776
688
  waitError(page, act, error, 'URL');
777
689
  }
778
690
  }
691
+ // Otherwise, if the text is to be a substring of the page title:
779
692
  else if (what === 'title') {
693
+ // Wait for it and quit on failure.
780
694
  try {
781
695
  await page.waitForFunction(
782
696
  text => document
@@ -789,14 +703,16 @@ const doActs = async (report, actIndex, page) => {
789
703
  }
790
704
  );
791
705
  result.found = true;
792
- result.title = await page.title();
706
+ result.title = await page.title();
793
707
  }
794
708
  catch(error) {
795
709
  actIndex = -2;
796
710
  waitError(page, act, error, 'title');
797
711
  }
798
712
  }
713
+ // Otherwise, if the text is to be a substring of the text of the page body:
799
714
  else if (what === 'body') {
715
+ // Wait for it and quit on failure.
800
716
  try {
801
717
  await page.waitForFunction(
802
718
  text => document
@@ -818,27 +734,33 @@ const doActs = async (report, actIndex, page) => {
818
734
  }
819
735
  // Otherwise, if the act is a wait for a state:
820
736
  else if (act.type === 'state') {
821
- // Wait for it, and quit if it does not appear.
737
+ // Wait for it and quit on failure.
822
738
  const stateIndex = ['loaded', 'idle'].indexOf(act.which);
823
739
  await page.waitForLoadState(
824
- ['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 5000][stateIndex]}
740
+ ['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 15000][stateIndex]}
825
741
  )
826
742
  .catch(error => {
827
743
  console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
828
- addError(act, `ERROR waiting for page to be ${act.which}`);
744
+ act.result = {
745
+ success: false,
746
+ error: `ERROR waiting for page to be ${act.which}`
747
+ };
829
748
  actIndex = -2;
830
749
  });
831
750
  if (actIndex > -2) {
832
- addError(`Page became ${act.which}`);
751
+ act.result = {
752
+ success: true,
753
+ state: act.which
754
+ };
833
755
  }
834
756
  }
835
757
  // Otherwise, if the act is a page switch:
836
758
  else if (act.type === 'page') {
837
759
  // Wait for a page to be created and identify it as current.
838
760
  page = await browserContext.waitForEvent('page');
839
- // Wait until it is stable and thus ready for the next act.
840
- await page.waitForLoadState('networkidle', {timeout: 20000});
841
- // Add the resulting URL and any description of it to the act.
761
+ // Wait until it is idle.
762
+ await page.waitForLoadState('networkidle', {timeout: 15000});
763
+ // Add the resulting URL to the act.
842
764
  const result = {
843
765
  url: page.url()
844
766
  };
@@ -855,7 +777,9 @@ const doActs = async (report, actIndex, page) => {
855
777
  if (act.type === 'reveal') {
856
778
  // Make all elements in the page visible.
857
779
  await require('./procs/allVis').allVis(page);
858
- act.result = 'All elements visible.';
780
+ act.result = {
781
+ success: true
782
+ };
859
783
  }
860
784
  // Otherwise, if the act is a tenon request:
861
785
  else if (act.type === 'tenonRequest') {
@@ -1010,122 +934,209 @@ const doActs = async (report, actIndex, page) => {
1010
934
  });
1011
935
  testReport.result.failureCount = failureCount;
1012
936
  }
937
+ testReport.result.success = true;
1013
938
  report.testTimes.push([act.which, Math.round((Date.now() - startTime) / 1000)]);
1014
939
  report.testTimes.sort((a, b) => b[1] - a[1]);
1015
940
  // Add the result object (possibly an array) to the act.
1016
941
  const resultCount = Object.keys(testReport.result).length;
1017
- act.result = resultCount ? testReport.result : 'NONE';
942
+ act.result = resultCount ? testReport.result : {success: false};
1018
943
  }
1019
944
  // Otherwise, if the act is a move:
1020
945
  else if (moves[act.type]) {
1021
946
  const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
1022
- // Try up to 5 times, every 2 seconds, to identify the element to perform the move on.
1023
- let matchResult = {success: false};
947
+ // Try to identify the element to perform the move on.
948
+ act.result = {found: false};
949
+ let selection = {};
1024
950
  let tries = 0;
1025
- while (tries++ < 5 && ! matchResult.success) {
1026
- matchResult = await matchElement(page, selector, act.which || '', act.index);
1027
- if (! matchResult.success) {
951
+ const slimText = debloat(act.which);
952
+ while (tries++ < 5 && ! act.result.found) {
953
+ if (act.which) {
954
+ // If the page still exists:
955
+ if (page) {
956
+ // Identify the elements of the specified type.
957
+ const selections = await page.$$(selector);
958
+ // If there are any:
959
+ if (selections.length) {
960
+ // If there are enough to make a match possible:
961
+ if ((act.index || 0) < selections.length) {
962
+ // For each element of the specified type:
963
+ let matchCount = 0;
964
+ const selectionTexts = [];
965
+ for (selection of selections) {
966
+ // Add its text to the list of texts of such elements.
967
+ const selectionText = await textOf(page, selection);
968
+ selectionTexts.push(selectionText);
969
+ // If its text includes the specified text:
970
+ if (selectionText.includes(slimText)) {
971
+ // If the element has the specified index among such elements:
972
+ if (matchCount++ === (act.index || 0)) {
973
+ // Report it as the matching element and stop checking.
974
+ act.result.found = true;
975
+ act.result.text = slimText;
976
+ break;
977
+ }
978
+ }
979
+ }
980
+ // If no element satisfied the specifications:
981
+ if (! act.result.found) {
982
+ act.result.success = false;
983
+ act.result.error = 'exhausted';
984
+ act.result.typeElementCount = selections.length;
985
+ act.result.textElementCount = --matchCount;
986
+ act.result.message = 'Not enough elements have the specified text';
987
+ act.result.candidateTexts = selectionTexts;
988
+ }
989
+ }
990
+ // Otherwise, i.e. if there are too few such elements to make a match possible:
991
+ else {
992
+ // Return a failure.
993
+ act.result.success = false;
994
+ act.result.error = 'fewer';
995
+ act.result.typeElementCount = selections.length;
996
+ act.result.message = 'Elements of specified type too few';
997
+ }
998
+ }
999
+ // Otherwise, i.e. if there are no elements of the specified type:
1000
+ else {
1001
+ // Return a failure.
1002
+ act.result.success = false;
1003
+ act.result.error = 'none';
1004
+ act.result.typeElementCount = 0;
1005
+ act.result.message = 'No elements specified type found';
1006
+ }
1007
+ }
1008
+ // Otherwise, i.e. if the page no longer exists:
1009
+ else {
1010
+ // Return a failure.
1011
+ act.result.success = false;
1012
+ act.result.error = 'gone';
1013
+ act.result.message = 'Page gone';
1014
+ }
1015
+ }
1016
+ // Otherwise, i.e. if no text was specified:
1017
+ else {
1018
+ // Return a failure.
1019
+ act.result.success = false;
1020
+ act.result.error = 'text';
1021
+ act.result.message = 'No text specified';
1022
+ }
1023
+ if (! act.result.found) {
1028
1024
  await wait(2000);
1029
1025
  }
1030
1026
  }
1031
1027
  // If a match was found:
1032
- if (matchResult.success) {
1033
- const {matchingElement} = matchResult;
1028
+ if (act.result.found) {
1034
1029
  // If the move is a button click, perform it.
1035
1030
  if (act.type === 'button') {
1036
- await matchingElement.click({timeout: 3000});
1037
- act.result = 'clicked';
1031
+ await selection.click({timeout: 3000});
1032
+ act.result.success = true;
1033
+ act.result.move = 'clicked';
1038
1034
  }
1039
1035
  // Otherwise, if it is checking a radio button or checkbox, perform it.
1040
1036
  else if (['checkbox', 'radio'].includes(act.type)) {
1041
- await matchingElement.waitForElementState('stable', {timeout: 2000})
1037
+ await selection.waitForElementState('stable', {timeout: 2000})
1042
1038
  .catch(error => {
1043
1039
  console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
1044
- addError(act, `ERROR waiting for stable ${act.type}`);
1040
+ act.result.success = false;
1041
+ act.result.error = `ERROR waiting for stable ${act.type}`;
1045
1042
  });
1046
- if (! act.result) {
1047
- const isEnabled = await matchingElement.isEnabled();
1043
+ if (! act.result.error) {
1044
+ const isEnabled = await selection.isEnabled();
1048
1045
  if (isEnabled) {
1049
- await matchingElement.check({
1046
+ await selection.check({
1050
1047
  force: true,
1051
1048
  timeout: 2000
1052
1049
  })
1053
1050
  .catch(error => {
1054
1051
  console.log(`ERROR checking ${act.type} (${error.message})`);
1055
- addError(act, `ERROR checking ${act.type}`);
1052
+ act.result.success = false;
1053
+ act.result.error = `ERROR checking ${act.type}`;
1056
1054
  });
1057
- if (! act.result) {
1058
- act.result = 'checked';
1055
+ if (! act.result.error) {
1056
+ act.result.success = true;
1057
+ act.result.move = 'checked';
1059
1058
  }
1060
1059
  }
1061
1060
  else {
1062
1061
  const report = `ERROR: could not check ${act.type} because disabled`;
1063
1062
  console.log(report);
1064
- act.result = report;
1063
+ act.result.success = false;
1064
+ act.result.error = report;
1065
1065
  }
1066
1066
  }
1067
1067
  }
1068
1068
  // Otherwise, if it is focusing the element, perform it.
1069
1069
  else if (act.type === 'focus') {
1070
- await matchingElement.focus({timeout: 2000});
1071
- act.result = 'focused';
1070
+ await selection.focus({timeout: 2000});
1071
+ act.result.success = true;
1072
+ act.result.move = 'focused';
1072
1073
  }
1073
1074
  // Otherwise, if it is clicking a link:
1074
1075
  else if (act.type === 'link') {
1075
- // Try to click it.
1076
- const href = await matchingElement.getAttribute('href');
1077
- const target = await matchingElement.getAttribute('target');
1078
- await matchingElement.click({timeout: 3000})
1079
- // If it cannot be clicked within 3 seconds:
1080
- .catch(async error => {
1081
- // Try to force-click it without actionability checks.
1082
- const errorSummary = error.message.replace(/\n.+/, '');
1083
- console.log(`ERROR: Link to ${href} not clickable (${errorSummary})`);
1084
- await matchingElement.click({
1085
- force: true
1086
- })
1087
- // If it cannot be force-clicked:
1088
- .catch(error => {
1076
+ const href = await selection.getAttribute('href');
1077
+ const target = await selection.getAttribute('target');
1078
+ act.result.href = href || 'NONE';
1079
+ act.result.target = target || 'DEFAULT';
1080
+ // If the destination is a new page:
1081
+ if (target && target !== '_self') {
1082
+ // Click the link and wait for the resulting page event.
1083
+ try {
1084
+ const [newPage] = await Promise.all([
1085
+ page.context().waitForEvent('page', {timeout: 6000}),
1086
+ selection.click({timeout: 5000})
1087
+ ]);
1088
+ // Wait for the new page to load.
1089
+ await newPage.waitForLoadState('domcontentloaded', {timeout: 10000});
1090
+ // Make the new page the current page.
1091
+ page = newPage;
1092
+ act.result.success = true;
1093
+ act.result.move = 'clicked';
1094
+ act.result.newURL = page.url();
1095
+ }
1096
+ // If the click, event, or load failed:
1097
+ catch(error) {
1089
1098
  // Quit and report the failure.
1099
+ console.log(
1100
+ `ERROR clicking new-page link (${error.message.replace(/\n.+/s, '')})`
1101
+ );
1102
+ act.result.success = false;
1103
+ act.result.error = 'unclickable';
1104
+ act.result.message = 'ERROR: click, navigation, or load timed out';
1090
1105
  actIndex = -2;
1091
- const errorSummary = error.message.replace(/\n.+/, '');
1092
- console.log(`ERROR: Link to ${href} not force-clickable (${errorSummary})`);
1093
- act.result = {
1094
- href: href || 'NONE',
1095
- target: target || 'NONE',
1096
- error: 'ERROR: Normal and forced attempts to click link timed out'
1097
- };
1098
- });
1099
- });
1100
- let loadState = 'idle';
1101
- // Wait up to 3 seconds for the resulting page to be idle.
1102
- await page.waitForLoadState('networkidle', {timeout: 3000})
1103
- // If the wait times out:
1104
- .catch(async () => {
1105
- loadState = 'loaded';
1106
- // Wait up to 2 seconds for the page to be loaded.
1107
- await page.waitForLoadState('domcontentloaded', {timeout: 2000})
1108
- // If the wait times out:
1109
- .catch(() => {
1110
- loadState = 'incomplete';
1111
- // Proceed but report the timeout.
1112
- console.log('ERROR waiting for page to load after link activation');
1113
- });
1114
- });
1115
- // If it was clicked:
1116
- if (actIndex > -2) {
1117
- // Report the success.
1118
- act.result = {
1119
- href: href || 'NONE',
1120
- target: target || 'NONE',
1121
- move: 'clicked',
1122
- loadState
1123
- };
1106
+ }
1107
+ }
1108
+ // Otherwise, i.e. if the destination is in the current page:
1109
+ else {
1110
+ // Click the link and wait for the resulting navigation.
1111
+ try {
1112
+ await selection.click({timeout: 5000});
1113
+ // Wait for the new content to load.
1114
+ await page.waitForLoadState('domcontentloaded', {timeout: 4000});
1115
+ act.result.success = true;
1116
+ act.result.move = 'clicked';
1117
+ act.result.newURL = page.url();
1118
+ }
1119
+ // If the click or load failed:
1120
+ catch(error) {
1121
+ // Quit and report the failure.
1122
+ console.log(
1123
+ `ERROR clicking link (${error.message.replace(/\n.+/s, '')})`
1124
+ );
1125
+ act.result.success = false;
1126
+ act.result.error = 'unclickable';
1127
+ act.result.message = 'ERROR: click or load timed out';
1128
+ actIndex = -2;
1129
+ }
1130
+ // If the link click succeeded:
1131
+ if (! act.result.error) {
1132
+ act.result.success = true;
1133
+ act.result.move = 'clicked';
1134
+ }
1124
1135
  }
1125
1136
  }
1126
1137
  // Otherwise, if it is selecting an option in a select list, perform it.
1127
1138
  else if (act.type === 'select') {
1128
- const options = await matchingElement.$$('option');
1139
+ const options = await selection.$$('option');
1129
1140
  let optionText = '';
1130
1141
  if (options && Array.isArray(options) && options.length) {
1131
1142
  const optionTexts = [];
@@ -1138,13 +1149,13 @@ const doActs = async (report, actIndex, page) => {
1138
1149
  );
1139
1150
  const index = matchTexts.filter(text => text > -1)[act.index || 0];
1140
1151
  if (index !== undefined) {
1141
- await matchingElement.selectOption({index});
1152
+ await selection.selectOption({index});
1142
1153
  optionText = optionTexts[index];
1143
1154
  }
1144
1155
  }
1145
- act.result = optionText
1146
- ? `“${optionText}” selected`
1147
- : 'ERROR: option not found';
1156
+ act.result.success = true;
1157
+ act.result.move = 'selected';
1158
+ act.result.option = optionText;
1148
1159
  }
1149
1160
  // Otherwise, if it is entering text on the element:
1150
1161
  else if (act.type === 'text') {
@@ -1157,22 +1168,26 @@ const doActs = async (report, actIndex, page) => {
1157
1168
  what = what.replace(/__[A-Z]+__/, envValue);
1158
1169
  }
1159
1170
  // Enter the text.
1160
- await matchingElement.type(act.what);
1171
+ await selection.type(act.what);
1161
1172
  report.presses += act.what.length;
1162
- act.result = 'entered';
1173
+ act.result.success = true;
1174
+ act.result.move = 'entered';
1163
1175
  }
1164
1176
  // Otherwise, i.e. if the move is unknown, add the failure to the act.
1165
1177
  else {
1166
1178
  // Report the error.
1167
1179
  const report = 'ERROR: move unknown';
1168
- act.result = report;
1180
+ act.result.success = false;
1181
+ act.result.error = report;
1169
1182
  console.log(report);
1170
1183
  }
1171
1184
  }
1172
1185
  // Otherwise, i.e. if no match was found:
1173
1186
  else {
1174
1187
  // Stop.
1175
- act.result = matchResult;
1188
+ act.result.success = false;
1189
+ act.result.error = 'absent';
1190
+ act.result.message = 'ERROR: specified element not found';
1176
1191
  console.log('ERROR: Specified element not found');
1177
1192
  actIndex = -2;
1178
1193
  }
@@ -1188,7 +1203,10 @@ const doActs = async (report, actIndex, page) => {
1188
1203
  await page.keyboard.press(key);
1189
1204
  }
1190
1205
  const qualifier = act.again ? `${1 + act.again} times` : 'once';
1191
- act.result = `pressed ${qualifier}`;
1206
+ act.result = {
1207
+ success: true,
1208
+ message: `pressed ${qualifier}`
1209
+ };
1192
1210
  }
1193
1211
  // Otherwise, if it is a repetitive keyboard navigation:
1194
1212
  else if (act.type === 'presses') {
@@ -1319,6 +1337,7 @@ const doActs = async (report, actIndex, page) => {
1319
1337
  }
1320
1338
  // Add the result to the act.
1321
1339
  act.result = {
1340
+ success: true,
1322
1341
  status,
1323
1342
  totals: {
1324
1343
  presses,
@@ -1338,32 +1357,45 @@ const doActs = async (report, actIndex, page) => {
1338
1357
  // Otherwise, i.e. if the act type is unknown:
1339
1358
  else {
1340
1359
  // Add the error result to the act.
1341
- act.result = 'ERROR: invalid command type';
1360
+ act.result = {
1361
+ success: false,
1362
+ error: 'badType',
1363
+ message: 'ERROR: invalid command type'
1364
+ };
1342
1365
  }
1343
1366
  }
1344
1367
  // Otherwise, i.e. if redirection is prohibited but occurred:
1345
1368
  else {
1346
- // Add the error result to the act.
1347
- addError(act, `ERROR: Page URL wrong (${url})`);
1369
+ // Add an error result to the act.
1370
+ act.result = {
1371
+ success: false,
1372
+ error: 'redirection',
1373
+ message: `ERROR: Page redirected to (${url})`
1374
+ };
1348
1375
  }
1349
1376
  }
1350
1377
  // Otherwise, i.e. if the required page URL does not exist:
1351
1378
  else {
1352
1379
  // Add an error result to the act.
1353
- addError(act, 'ERROR: Page has no URL');
1380
+ addError(act, 'noURL', 'ERROR: Page has no URL');
1354
1381
  }
1355
1382
  }
1356
1383
  // Otherwise, i.e. if no page exists:
1357
1384
  else {
1358
1385
  // Add an error result to the act.
1359
- addError(act, 'ERROR: No page identified');
1386
+ addError(act, 'noPage', 'ERROR: No page identified');
1360
1387
  }
1388
+ act.endTime = Date.now();
1361
1389
  }
1362
1390
  // Otherwise, i.e. if the command is invalid:
1363
1391
  else {
1364
1392
  // Add an error result to the act.
1365
1393
  const errorMsg = `ERROR: Invalid command of type ${act.type}`;
1366
- act.result = errorMsg;
1394
+ act.result = {
1395
+ success: false,
1396
+ error: 'badCommand',
1397
+ message: errorMsg
1398
+ };
1367
1399
  console.log(errorMsg);
1368
1400
  // Quit.
1369
1401
  actIndex = -2;