@suffolklitlab/alkiln 5.2.0 → 5.3.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/lib/scope.js CHANGED
@@ -17,6 +17,7 @@ const { waitForTimeout } = require('./utils/time');
17
17
  const files = require('./utils/files' );
18
18
  const server = require('./docassemble/docassemble_api_interface');
19
19
  const wrapPromiseWithTimeout = require('./cucumber_8_shim');
20
+ const reports = require(`./utils/reports`);
20
21
 
21
22
 
22
23
  let ordinal_to_integer = {
@@ -82,152 +83,10 @@ let toBase64 = function ( utf8_str, exclude_padding=true) {
82
83
 
83
84
  let proxies_regex = /(\bx\.)|(\bx\[)|(\[[ijklmn]\])/g;
84
85
  let da_base64_regex = /^[A-Za-z0-9+/]+$/;
85
- let misc_artifacts_dir = `_alkiln-misc_artifacts`;
86
86
 
87
87
  module.exports = {
88
88
  trigger_not_needed_flag: `ALKiln: no trigger variable needed`,
89
89
 
90
- addToReport: async function( scope, { type, value }) {
91
- /* Add an item to a specific list in a scenario's report.
92
- * Create report if necessary.
93
- *
94
- * The report has a key for each scenario and each of those
95
- * keys should correspond to a list containing the relevant data. */
96
- if ( session_vars.get_debug() ) { console.log(`Report:\ntype: ${ type }, value: ${ value }\n`) }
97
- let scenario = await scope.makeSureReportPartsExist( scope );
98
- scenario.lines.push({ type, value });
99
-
100
- // TODO: Not sure how to use .add_to_debug_log() here considering possibly dynamic filepath
101
- // log.add_to_debug_log(`Report:\ntype: ${ type }, value: ${ value }\n`);
102
-
103
- // Ensure filepath for saving local unit test logs
104
- if ( !scope.paths ) {
105
- // TODO: would it be useful to put in existing artifacts folder if possible.
106
- // Can this be usefully abstracted?
107
- scope.paths = { debug_log: `${ misc_artifacts_dir }/unit_tests_logs-${ files.readable_date() }.txt` };
108
- }
109
-
110
- try {
111
- fs.appendFileSync(scope.paths.debug_log, `\nReport:\ntype: ${ type }, value: ${ value }`);
112
- } catch (err) {
113
- fs.mkdirSync( misc_artifacts_dir );
114
- fs.appendFileSync(scope.paths.debug_log, `\nReport:\ntype: ${ type }, value: ${ value }`);
115
- }
116
- return scenario;
117
-
118
- }, // Ends scope.addToReport()
119
-
120
- makeSureReportPartsExist: async function( scope ) {
121
- /* If a part of the report needs to exist right now, but doesn't exist,
122
- * create it. */
123
- if ( !scope.report ) { scope.report = new Map(); }
124
- if ( !scope.scenario_id ) {
125
- scope.scenario_id = `_report_${ Date.now() }`;
126
- }
127
- if ( !scope.report.get( scope.scenario_id )) {
128
- scope.report.set( scope.scenario_id, {} )
129
- scope.report.get( scope.scenario_id ).status = `in progress`;
130
- }
131
-
132
- let scenario = scope.report.get( scope.scenario_id );
133
- if ( !scenario.lines ) { scenario.lines = []; }
134
-
135
- return scenario;
136
- }, // Ends scope.makeSureReportPartsExist()
137
-
138
- setReportScenarioStatus: async function( scope, { status=`PASSED` }) {
139
- /* Add final status of scenario to scenario report object - passed, failed, etc. */
140
- scope.report.get( scope.scenario_id ).status = status;
141
- }, // Ends scope.setReportScenarioStatus()
142
-
143
- convertToOriginalStoryTableRow: async function( scope, { row_data }) {
144
- /* Returns the original data of row into a string formatted as a story table row.
145
- * Should this show the actual row data that was used instead? That could
146
- * be pretty confusing to most devs. */
147
- let source_row = row_data.source || row_data;
148
- let original = source_row.original;
149
- let var_str = await scope.toTrimmedJSON( scope, { str: original.var });
150
- let value_str = await scope.toTrimmedJSON( scope, { str: original.value });
151
- let trigger_str = await scope.toTrimmedJSON( scope, { str: original.trigger });
152
- if ( trigger_str === scope.trigger_not_needed_flag ) {
153
- // This text was added by ALKiln itself. It's not part of the user's data
154
- trigger_str = ``;
155
- }
156
- return `${ ' '.repeat(6) }| ${ var_str } | ${ value_str } | ${ trigger_str } |`;
157
- }, // Ends scope.convertToOriginalStoryTableRows()
158
-
159
- toTrimmedJSON: async function( scope, { str='' }) {
160
- /* Turn into JSON string then trim quotes from the start and end. */
161
- let json = JSON.stringify( str );
162
- let trimmed = json.replace( /^"/, '' ).replace( /"$/, '' ).replace( /\\\\/g, `\\`);
163
- return trimmed;
164
- }, // Ends scope.toTrimmedJSON()
165
-
166
- addReportHeading: async function (scope, { scenario }) {
167
- /** Return printable heading based on Scenario name and tags.
168
- * Allow characters from other languages.
169
- */
170
- if ( session_vars.get_debug() ) { console.log( scenario.pickle.name ); }
171
-
172
- // Collect all tag names
173
- let tag_names = [];
174
- for ( let tag of scenario.pickle.tags ) {
175
- tag_names.push( tag.name );
176
- }
177
-
178
- let heading = `\n---------------\n`
179
- + `Scenario: ${ scenario.pickle.name }`;
180
-
181
- if ( tag_names.length > 0 ) {
182
- heading += `\nTags: ${ tag_names.join(' ') }`;
183
- }
184
-
185
- heading += `\n---------------`;
186
-
187
- await scope.addToReport( scope, {
188
- type: `heading`,
189
- value: heading,
190
- });
191
- }, // Ends scope.addReportHeading()
192
-
193
- getPrintableReport: async function( scope ) {
194
- /* Return a string generated from test report data. */
195
- let report = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
196
-
197
- // First list failed scenarios, then non-passed scenarios, then passed scenarios
198
- let failed_reports = ``;
199
- let passed_reports = ``;
200
- let other_reports = ``;
201
- for ( let scenario of scope.report ) {
202
-
203
- let report = await scope.getPrintableScenario( scope, { scenario });
204
- let [ scenario_id, data ] = scenario;
205
- if ( data.status === `FAILED` ) { failed_reports += report; }
206
- else if ( data.status === `PASSED` ) { passed_reports += report; }
207
- else { other_reports += report; }
208
- } // ends for every scenario
209
-
210
- if ( failed_reports !== `` ) { report += `\n\n===============================\n===============================\nFailed scenarios:\n${ failed_reports }`; }
211
- if ( other_reports !== `` ) { report += `\n\n===============================\n===============================\nScenarios that did not pass:\n${ other_reports }`; }
212
- if ( passed_reports !== `` ) { report += `\n\n===============================\n===============================\nPassed scenarios:\n${ passed_reports }`; }
213
-
214
- return report;
215
- }, // Ends scope.getPrintableReport()
216
-
217
- getPrintableScenario: async function( scope, { scenario }) {
218
- /* Return a string generated from scenario list of report items. */
219
- let [ scenario_id, data ] = scenario;
220
- let report = ``;
221
- for ( let line of data.lines ) {
222
- if ( line.type === `page_id` ) { report += `screen id: `; }
223
- else if ( line.type === `outcome` ) { report += `\n`; }
224
- else if ( line.type === `warning` ) { report += `WARNING: `; }
225
- else if ( line.type === `error` ) { report += `\nERROR: ` }
226
- report += `${ line.value }\n`;
227
- }
228
- return report;
229
- }, // Ends scope.getPrintableScenario()
230
-
231
90
  getSafeScenarioBaseFilename: async function( scope, { scenario }) {
232
91
  /** Return a string that's safe to be a filename. Include the Scenario
233
92
  * description and tags.
@@ -275,6 +134,15 @@ module.exports = {
275
134
  return safe_name;
276
135
  }, // Ends scope.getSafeScenarioFilename()
277
136
 
137
+ getA11yPath: async function( scope ) {
138
+ // Get a unique path for a json file
139
+ let { id } = await scope.examinePageID( scope, 'none to match' );
140
+ let short_id = `${ id }`.substring(0, 20);
141
+ // Need timestamp in case of a loop where the same page gets assessed for a11y multiple times
142
+ let a11y_path = path.join( scope.paths.scenario, `aXe_failure-${ short_id }-${ Date.now() }.json` );
143
+ return a11y_path;
144
+ },
145
+
278
146
  getJSONFilename: async function( scope ) {
279
147
  // Get a unique path for a json file
280
148
  let { id } = await scope.examinePageID( scope, 'none to match' );
@@ -356,7 +224,7 @@ module.exports = {
356
224
  let error_text_prop = await error_elem.getProperty( 'textContent' );
357
225
  let system_error_text = await error_text_prop.jsonValue();
358
226
 
359
- await scope.addToReport(scope, { type: `error`, value: system_error_text });
227
+ reports.addToReport(scope, { type: `error`, value: system_error_text });
360
228
  throw system_error_text;
361
229
  };
362
230
 
@@ -386,7 +254,7 @@ module.exports = {
386
254
  // first in user-triggered cases - users used to be able to access this almost
387
255
  // directly, but they can't anymore. Our functions should know to only pass good things.
388
256
  let msg = `Cannot find the element to tap.`;
389
- if ( !elem ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
257
+ if ( !elem ) { reports.addToReport(scope, { type: `error`, value: msg }); }
390
258
 
391
259
  expect( elem, msg ).to.exist;
392
260
 
@@ -425,7 +293,7 @@ module.exports = {
425
293
  } else {
426
294
  // Throw any non-timeout error
427
295
  let err_msg = `Error occurred when ALKiln tried to tap "${ tapperText }".`
428
- scope.addToReport( scope, { type: `error`, value: err_msg });
296
+ reports.addToReport( scope, { type: `error`, value: err_msg });
429
297
  throw error;
430
298
  } // ends if error is timeout error
431
299
 
@@ -517,7 +385,7 @@ module.exports = {
517
385
  await scope.get_server_reload_promise( scope );
518
386
  } else {
519
387
  // Otherwise just throw the error
520
- await scope.addToReport( scope, {
388
+ reports.addToReport( scope, {
521
389
  type: `error`,
522
390
  value: non_reload_report_msg });
523
391
  throw error;
@@ -579,7 +447,7 @@ module.exports = {
579
447
  + `${ Math.round( ms_till_server_timeout/1000 ) } seconds to respond. `
580
448
  + `If the server was just reloading and needed more time, try setting the `
581
449
  + `"SERVER_RELOAD_TIMEOUT_SECONDS" GitHub secret to a longer amount of time.`
582
- await scope.addToReport( scope, { type: `error`, value: timeout_message });
450
+ reports.addToReport( scope, { type: `error`, value: timeout_message });
583
451
  }
584
452
 
585
453
  // Once server has responded, throw an error to end the test. After the first
@@ -599,8 +467,8 @@ module.exports = {
599
467
  let reload_failure_msg = `The test was unable to continue because the `
600
468
  + `server was reloading. ALKiln will try this Scenario a total of 2 times. An `
601
469
  + `error will still be printed for this attempt.`;
602
- await scope.addToReport( scope, { type: `error`, value: reload_failure_msg });
603
- // await scope.addToReport( scope, { type: `error`, value: timeout_message });
470
+ reports.addToReport( scope, { type: `error`, value: reload_failure_msg });
471
+ // reports.addToReport( scope, { type: `error`, value: timeout_message });
604
472
  // clearTimeout( server_timeout_id );
605
473
  throw( reload_failure_msg );
606
474
  }, // Ends scope.throw_server_was_reloading_error()
@@ -616,22 +484,22 @@ module.exports = {
616
484
  * https://github.com/puppeteer/puppeteer/issues/299 and
617
485
  */
618
486
 
487
+ // If file has finished downloading
488
+ if ( scope.downloadComplete ) {
489
+ // reset the flag and resolve
490
+ scope.downloadComplete = false;
491
+ return true;
492
+ }
493
+
619
494
  // Stop/resolve if too much time has passed
620
495
  let expirationTime = scope.timeout - 200; // ms
621
496
  if ( !endTime ) { endTime = new Date((new Date()).getTime() + expirationTime); }
622
497
  let diff = endTime.getTime() - Date.now();
623
498
  if ( diff <= 0 ) { return false; }
624
499
 
625
- // If time hasn't expired
626
- if ( scope.downloadComplete ) {
627
- // If download is complete, reset the flag and resolve
628
- scope.downloadComplete = false;
629
- return true;
630
- } else {
631
- // Otherwise wait and then check again
632
- await scope.page.waitForTimeout( 100 );
633
- await scope.detectDownloadComplete(scope, endTime);
634
- }
500
+ // If time hasn't expired, wait and then check again
501
+ await scope.page.waitForTimeout( 100 );
502
+ await scope.detectDownloadComplete(scope, endTime);
635
503
 
636
504
  return false;
637
505
  },
@@ -673,7 +541,7 @@ module.exports = {
673
541
  let elem = await scope.page.$(`#${tab_id}`);
674
542
  let error_msg = `Couldn't find the tab with id "#${tab_id}" on the page, is there a typo?`;
675
543
  if ( !elem ) {
676
- await scope.addToReport(scope, { type: `error`, value: error_msg });
544
+ reports.addToReport(scope, { type: `error`, value: error_msg });
677
545
  }
678
546
  expect( elem, error_msg ).to.exist;
679
547
  await scope.tapElement( scope, elem, tab_id );
@@ -683,7 +551,7 @@ module.exports = {
683
551
  /** Return if there were any violations and the file containing aXe output with more details of the checks */
684
552
  let { id } = await scope.examinePageID( scope, 'none to match' );
685
553
  if ( scope.page_id !== id ) {
686
- await scope.addToReport(scope, { type: `page_id`, value: `${ id }`, });
554
+ reports.addToReport(scope, { type: `page_id`, value: `${ id }`, });
687
555
  scope.page_id = id;
688
556
  }
689
557
  const axe = new AxePuppeteer(scope.page);
@@ -701,16 +569,15 @@ module.exports = {
701
569
  let axe_path = null;
702
570
  const passed = axe_result.violations.length == 0;
703
571
  if (!passed) {
704
- let axe_filename = await scope.getSafeScenarioFilename( scope, {prefix: `aXe_failure`}) + '.json';
705
- axe_path = `${ scope.paths.scenario }/${ axe_filename }`;
572
+ axe_path = await scope.getA11yPath( scope );
706
573
  fs.writeFileSync( axe_path, JSON.stringify( axe_result, null, 2 ));
707
574
  let msg = `Found potential accessibility issues on the ${ scope.page_id } screen. Details in ${ axe_path }.`;
708
- await scope.addToReport( scope, {
575
+ reports.addToReport( scope, {
709
576
  type: `error`,
710
577
  value: msg
711
578
  });
712
579
  } else {
713
- await scope.addToReport(scope, { type: `info`, value: `Accessibility on ${ scope.page_id } passed!`})
580
+ reports.addToReport(scope, { type: `info`, value: `Accessibility on ${ scope.page_id } passed!`})
714
581
  }
715
582
  return {
716
583
  passed: axe_result.violations.length == 0,
@@ -722,7 +589,7 @@ module.exports = {
722
589
  let elem = await scope.page.$(`${selector}`);
723
590
  let error_msg = `Couldn't find "${selector}" on the page, is there a typo?`;
724
591
  if ( !elem ) {
725
- await scope.addToReport(scope, { type: `error`, value: error_msg });
592
+ reports.addToReport(scope, { type: `error`, value: error_msg });
726
593
  }
727
594
  expect( elem, error_msg ).to.exist;
728
595
  await scope.tapElement( scope, elem, '', waitForTimeout );
@@ -732,11 +599,6 @@ module.exports = {
732
599
  /* Try to load the page. Should we also pass a `timeout` arg or just
733
600
  * use scope.timeout? */
734
601
 
735
- await scope.addToReport(scope,{
736
- type: `row`,
737
- value: `Trying to load the interview`
738
- });
739
-
740
602
  let interview_url = null;
741
603
  // If the file is already a full url, just use that arbitrary url
742
604
  if ( /^http/.test( file_name ) ) {
@@ -752,7 +614,11 @@ module.exports = {
752
614
  let base_url = await get_base_interview_url();
753
615
  interview_url = `${ base_url }${ file_name }`;
754
616
  }
755
- if ( session_vars.get_debug() ) { console.log( `interview_url:`, interview_url ); }
617
+
618
+ reports.addToReport(scope,{
619
+ type: `row`,
620
+ value: `Trying to load the interview at "${interview_url}"`
621
+ });
756
622
 
757
623
  if ( !scope.page ) { scope.page = await scope.browser.newPage(); }
758
624
 
@@ -773,7 +639,7 @@ module.exports = {
773
639
  } else {
774
640
 
775
641
  // Throw any non-timeout error
776
- scope.addToReport( scope, {
642
+ reports.addToReport( scope, {
777
643
  type: `error`,
778
644
  value: `Error occurred when ALKiln tried to go to the interview url`
779
645
  });
@@ -785,7 +651,7 @@ module.exports = {
785
651
  let load_result = await scope.getLoadData( scope );
786
652
  // Errors, or reaching timeout, causes an exception and is shown to the developer at the end
787
653
  if ( load_result.error ) {
788
- await scope.addToReport(scope, { type: `error`, value: `On final attempt to load interview, got "${ load_result.error }"` });
654
+ reports.addToReport(scope, { type: `error`, value: `On final attempt to load interview, got "${ load_result.error }"` });
789
655
  expect( load_result.error ).to.equal( '' );
790
656
  }
791
657
 
@@ -855,7 +721,7 @@ module.exports = {
855
721
  if (!var_data && ! raw_var_data) {
856
722
  let no_data_msg = `ALKiln Internal error: normalizeTable called w/o var_data or raw_var_data:` +
857
723
  ` open an issue at https://github.com/SuffolkLITLab/ALKiln/issues/new if you see this!`;
858
- await scope.addToReport( scope, { type: `error`, value: no_data_msg });
724
+ reports.addToReport( scope, { type: `error`, value: no_data_msg });
859
725
  throw Error(no_data_msg);
860
726
  }
861
727
 
@@ -870,7 +736,7 @@ module.exports = {
870
736
  let bad_table_msg = `Your Story Table definition needs to be changed. The best practice`
871
737
  + ` is to make your table 3 columns wide with a first row in this format: "| var | value | trigger |".`
872
738
  + ` The first row of your table is actually "${ raw_array[0] }"`;
873
- await scope.addToReport( scope, { type: `error`, value: bad_table_msg})
739
+ reports.addToReport( scope, { type: `error`, value: bad_table_msg})
874
740
  throw Error(bad_table_msg);
875
741
  } else if (row_raw.length == 2) {
876
742
  var_data.push({var: row_raw[0], value: row_raw[1]});
@@ -890,7 +756,7 @@ module.exports = {
890
756
  actual_var_value = process.env[row.flags.secret_name];
891
757
  if ( actual_var_value === undefined ) {
892
758
  let not_in_env_msg = `The GitHub SECRET "${ row.flags.secret_name }" doesn't exist. Check for a typo and check your workflow file.`;
893
- await scope.addToReport( scope, { type: `error`, value: not_in_env_msg })
759
+ reports.addToReport( scope, { type: `error`, value: not_in_env_msg })
894
760
  throw ReferenceError(`Could not find "${row.flags.secret_name }": ${not_in_env_msg}.`)
895
761
  }
896
762
  }
@@ -942,7 +808,7 @@ module.exports = {
942
808
  let encoded_trigger_var = $( trigger_elem ).data( `variable` ); // This should come out to 'None' at the very least
943
809
  if ( !encoded_trigger_var ) {
944
810
  // TODO: Add documentation and then link to it in this message.
945
- await scope.addToReport(scope, { type: `warning`, value: `You are missing an element containing the data for the page's trigger variable. Some fields, like loops with index variables, may not work correctly in these tests. See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#a-missing-trigger-variable.` });
811
+ reports.addToReport(scope, { type: `warning`, value: `You are missing an element containing the data for the page's trigger variable. Some fields, like loops with index variables, may not work correctly in these tests. See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#a-missing-trigger-variable.` });
946
812
  encoded_trigger_var = toBase64( `None` );
947
813
  }
948
814
  let trigger = fromBase64( encoded_trigger_var );
@@ -1081,7 +947,7 @@ module.exports = {
1081
947
  // Exceptions: Some specific fields can be duplicates without being dangerous
1082
948
  if ( matching_nodes.length > 1 && !selector.includes(`[class="file-caption-name"]`) ) {
1083
949
  let msg = `${ matching_nodes.length } items on this page matched the base64 encoded name '${ selector }'. You may be setting the same variable in multiple places on this page.`;
1084
- await scope.addToReport(scope, { type: `warning`, value: msg });
950
+ reports.addToReport(scope, { type: `warning`, value: msg });
1085
951
  }
1086
952
 
1087
953
  return selector;
@@ -1360,7 +1226,7 @@ module.exports = {
1360
1226
  + `variable or a generic object, but the page does not `
1361
1227
  + `contain the trigger variable's name in its HTML. See `
1362
1228
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#a-missing-trigger-variable.`;
1363
- await scope.addToReport(scope, { type: `warning`, value: page_trigger_warning });
1229
+ reports.addToReport(scope, { type: `warning`, value: page_trigger_warning });
1364
1230
  }
1365
1231
 
1366
1232
  for ( let row_that_matches_this_field of rows_that_match_field_names ) {
@@ -1374,13 +1240,13 @@ module.exports = {
1374
1240
  // a useful message.
1375
1241
 
1376
1242
  // First, make the row data more readable to humans.
1377
- let row_as_story_table = await scope.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
1243
+ let row_as_story_table = reports.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
1378
1244
  let row_trigger_warning = `This story table row needs a value in the "trigger" `
1379
1245
  + `column, but it is empty. The test might get unexpected results. See `
1380
1246
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
1381
1247
  + `\nThe row's data:\n${ row_as_story_table }`
1382
1248
  + `\nThis field's data:\n${ JSON.stringify( field )}`;
1383
- await scope.addToReport(scope, { type: `warning`, value: row_trigger_warning });
1249
+ reports.addToReport(scope, { type: `warning`, value: row_trigger_warning });
1384
1250
  rows_without_a_trigger_name.push( row_that_matches_this_field );
1385
1251
  } else {
1386
1252
  let triggers_match = await scope.matchNeutralizedQuotes( scope, { value1: page_trigger_name, value2: table_row_trigger_name });
@@ -1406,7 +1272,7 @@ module.exports = {
1406
1272
  // First, make the row data more readable to humans.
1407
1273
  let conflicts_as_story_table_rows = [];
1408
1274
  for ( let row of rows_with_conflicting_trigger_names ) {
1409
- let table_str = await scope.convertToOriginalStoryTableRow( scope, { row_data: row });
1275
+ let table_str = reports.convertToOriginalStoryTableRow( scope, { row_data: row });
1410
1276
  conflicts_as_story_table_rows.push( table_str );
1411
1277
  }
1412
1278
 
@@ -1417,7 +1283,7 @@ module.exports = {
1417
1283
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
1418
1284
  + `\nThe data of row(s) with conflicting trigger name(s):\n${ conflicts_as_story_table_rows.join( `\n` )}`
1419
1285
  + `\nThis field's data:\n${ JSON.stringify( field )}`;
1420
- await scope.addToReport(scope, {
1286
+ reports.addToReport(scope, {
1421
1287
  type: `warning`,
1422
1288
  value: all_trigger_names_conflict_msg
1423
1289
  });
@@ -1434,7 +1300,7 @@ module.exports = {
1434
1300
  // First, make the row data more readable to humans.
1435
1301
  let rows_as_story_table = [];
1436
1302
  for ( let row of final_matching_rows ) {
1437
- let table_str = await scope.convertToOriginalStoryTableRow( scope, { row_data: row });
1303
+ let table_str = reports.convertToOriginalStoryTableRow( scope, { row_data: row });
1438
1304
  rows_as_story_table.push( table_str );
1439
1305
  }
1440
1306
 
@@ -1444,7 +1310,7 @@ module.exports = {
1444
1310
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
1445
1311
  + `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
1446
1312
  + `\nThis field's data:\n${ JSON.stringify( field )}`;
1447
- await scope.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
1313
+ reports.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
1448
1314
  }
1449
1315
 
1450
1316
  if ( session_vars.get_debug() ) {
@@ -1477,11 +1343,6 @@ module.exports = {
1477
1343
  */
1478
1344
  let row_used = null;
1479
1345
 
1480
- // Skip hidden fields right away. When a date field was above a combobox,
1481
- // interacting with the hidden field caused the date field to open its
1482
- // calendar which blocked input to the combobox
1483
- if ( field.type === `hidden` ) { return row_used; }
1484
-
1485
1346
  let matching_input_rows = await scope.getMatchingRows( scope, { var_data, field });
1486
1347
 
1487
1348
  // Nothing to set if there are no matching table rows
@@ -1597,8 +1458,16 @@ module.exports = {
1597
1458
  row_was_used = await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1598
1459
  } else {
1599
1460
  // If no custom datatype, set a regular field
1600
- row_was_used = await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1601
- }
1461
+
1462
+ // Skip hidden fields. When a `show if` date field is above a combobox,
1463
+ // interacting with the hidden field causes the date field to open its
1464
+ // calendar which blocks input to the combobox
1465
+ if ( field.type === `hidden` ) {
1466
+ row_was_used = false;
1467
+ } else {
1468
+ row_was_used = await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1469
+ }
1470
+ } // ends if custom datatype
1602
1471
 
1603
1472
  return row_was_used;
1604
1473
  }, // Ends scope.funnel_the_answer()
@@ -1614,8 +1483,12 @@ module.exports = {
1614
1483
 
1615
1484
  // If it doesn't have the right format
1616
1485
  if ( answer === null || !answer.includes(`/`) ) {
1617
- // Do nothing
1618
- return;
1486
+ // Return field unused.
1487
+ reports.addToReport( scope, {
1488
+ type: `warning`,
1489
+ value: `Invalid date. Value given: "${ answer }".`
1490
+ });
1491
+ return false;
1619
1492
  }
1620
1493
 
1621
1494
  let date_parts = answer.split(`/`);
@@ -1636,18 +1509,20 @@ module.exports = {
1636
1509
  handle: field,
1637
1510
  answer: date_parts[ field_i ]
1638
1511
  });
1512
+ await handle.press(`Tab`);
1639
1513
  }
1640
1514
 
1641
1515
  } // For each field
1642
1516
 
1643
1517
  await scope.afterStep( scope );
1518
+ return true;
1644
1519
  }, // Ends scope.setCustomDatatype.al_date()
1645
1520
 
1646
1521
  BirthDate: async function ( scope, { handle, answer }) {
1647
1522
  /** Given a date of the format 'mm/dd/yyyy' and the handle of the
1648
1523
  * custom field container, sets the ALToolbox custom birthdate
1649
1524
  * field values. */
1650
-
1525
+ answer = await scope.today_to_date(scope, { date: answer });
1651
1526
  await scope.setCustomDatatype.al_date( scope, { handle, answer })
1652
1527
  }, // Ends scope.setCustomDatatype.BirthDate()
1653
1528
 
@@ -1655,6 +1530,7 @@ module.exports = {
1655
1530
  /** Given a date of the format 'mm/dd/yyyy' and the handle of the
1656
1531
  * custom field container, sets the ALToolbox custom ThreePartsDate
1657
1532
  * field values. */
1533
+ answer = await scope.today_to_date(scope, { date: answer });
1658
1534
  await scope.setCustomDatatype.al_date( scope, { handle, answer })
1659
1535
  }, // Ends scope.setCustomDatatype.ThreePartsDate()
1660
1536
 
@@ -1729,36 +1605,20 @@ module.exports = {
1729
1605
  await scope.afterStep(scope, { waitForShowIf: true });
1730
1606
 
1731
1607
  } else if (type == `date`) {
1732
- let answer_date = answer;
1733
- // If the date is specified in the format "today" or "today + 2" or "today-1"
1734
- // it will need some pre-processing.
1735
- if (answer.indexOf(`today`) !== -1) {
1736
- // Find out how many days to add/subtract
1737
- let date_delta = answer.replace(/\s/g, '');
1738
- date_delta = date_delta.match(/today([+-]\d+)?/)[1];
1739
- date_delta = date_delta == undefined ? 0 : date_delta;
1740
- date_delta = parseInt(date_delta);
1741
- // Get today's date
1742
- answer_date = new Date();
1743
- // Add/subtract required number of days
1744
- answer_date.setDate(answer_date.getDate() + date_delta);
1745
- // Convert to the docassemble date field format: mm/dd/yyyy
1746
- // Reference: https://gomakethings.com/setting-a-date-input-to-todays-date-with-vanilla-js/
1747
- answer_date = ((answer_date.getMonth() + 1).toString().padStart(2, 0) + '/' +
1748
- answer_date.getDate().toString().padStart(2, 0) + '/' +
1749
- answer_date.getFullYear().toString());
1750
- }
1608
+ let answer_date = await scope.today_to_date(scope, { date: answer });
1751
1609
  await scope.setText( scope, { handle, answer: answer_date });
1610
+ await handle.press(`Tab`);
1752
1611
  await scope.afterStep(scope, { waitForShowIf: true });
1753
1612
  } else if (type == `time`) {
1754
1613
  // Authors should enter the time like 12:34 PM, but we shouldn't type ":", space, or M.
1755
1614
  let answer_time = answer.replace(":", "").replace(" ", "").replace("M", "");
1756
1615
  await handle.focus();
1757
1616
  await handle.type( answer );
1758
- await handle.press("Enter");
1617
+ await handle.press(`Enter`);
1759
1618
  await scope.afterStep(scope, { waitForShowIf: true });
1760
1619
  } else {
1761
1620
  await scope.setText( scope, { handle, answer });
1621
+ await handle.press(`Tab`);
1762
1622
  await scope.afterStep(scope, { waitForShowIf: true });
1763
1623
  }
1764
1624
 
@@ -1774,7 +1634,50 @@ module.exports = {
1774
1634
  return;
1775
1635
  },
1776
1636
 
1777
- }, // ends scope.enter_answer
1637
+ }, // ends scope.enter_answer()
1638
+
1639
+ today_to_date: async function ( scope, { date }) {
1640
+ /** Return `date` or, if the date is specified in the format
1641
+ * "today" or "today + 2" or "today-1", return a date of the format
1642
+ * "mm/dd/yyyy".
1643
+ *
1644
+ * @param scope { obj } REQUIRED. Object with test state.
1645
+ * @param date { str } REQUIRED. Input from the developer.
1646
+ *
1647
+ * @returns { str } `date` or "today" math as date str
1648
+ *
1649
+ * WARNING: If the developer is counting on date boundaries to trigger
1650
+ * other logic then timezone differences may interfere with that. They
1651
+ * should:
1652
+ *
1653
+ * 1. Test with a day well within the date range they're testing instead
1654
+ * of near a boundary. For example, test 8 days from now instead of 10.
1655
+ * This is because their test may run around 12am and thus be vulnerable
1656
+ * to failure.
1657
+ * 2. Warn the user explicitly and let them know when they need to file by
1658
+ * in case the user doesn't know and files a few days from the day they filled
1659
+ * out the form.
1660
+ */
1661
+ let valid_date = date;
1662
+ if (date.indexOf(`today`) !== -1) {
1663
+ // Find out how many days to add/subtract
1664
+ let date_delta = date.replace(/\s/g, '');
1665
+ date_delta = date_delta.match(/today([+-]\d+)?/)[1];
1666
+ date_delta = date_delta == undefined ? 0 : date_delta;
1667
+ date_delta = parseInt(date_delta);
1668
+ // Get today's date
1669
+ valid_date = new Date();
1670
+ // Add/subtract required number of days
1671
+ valid_date.setDate(valid_date.getDate() + date_delta);
1672
+ // Convert to the docassemble date field format: mm/dd/yyyy
1673
+ // Reference: https://gomakethings.com/setting-a-date-input-to-todays-date-with-vanilla-js/
1674
+ valid_date = ((valid_date.getMonth() + 1).toString().padStart(2, 0) + '/' +
1675
+ valid_date.getDate().toString().padStart(2, 0) + '/' +
1676
+ valid_date.getFullYear().toString());
1677
+ }
1678
+
1679
+ return valid_date;
1680
+ }, // Ends scope.today_to_date()
1778
1681
 
1779
1682
  uploadFiles: async function ( scope, { handle, answer }) {
1780
1683
  /* Try to upload one or more files to a file-type input field.
@@ -1787,7 +1690,7 @@ module.exports = {
1787
1690
  // allow a typo where someone forgot to include a space after a comma.
1788
1691
  let uploads_names = answer.split(`,`);
1789
1692
  if ( uploads_names.length === 0 ) {
1790
- await scope.addToReport( scope, {
1693
+ reports.addToReport( scope, {
1791
1694
  type: `error`,
1792
1695
  value: `Your list of files to upload is empty.`
1793
1696
  });
@@ -1819,7 +1722,7 @@ module.exports = {
1819
1722
 
1820
1723
  // Make sure sources directory exists and has files in it
1821
1724
  if ( dirs.length === 0 ) {
1822
- await scope.addToReport( scope, {
1725
+ reports.addToReport( scope, {
1823
1726
  type: `warning`,
1824
1727
  value: `The test files need to be in the "sources" directory, `
1825
1728
  + `but ALKiln couldn't find the directory or it was empty.`
@@ -1851,7 +1754,7 @@ module.exports = {
1851
1754
  } // ends for each file
1852
1755
 
1853
1756
  if ( missing_files.length > 0 ) {
1854
- await scope.addToReport( scope, {
1757
+ reports.addToReport( scope, {
1855
1758
  type: `warning`,
1856
1759
  value: `ALKiln could not find "${ missing_files.join('", "') }" in any of these folders: "${ dirs.join('", "') }".`
1857
1760
  });
@@ -1908,7 +1811,7 @@ module.exports = {
1908
1811
  * takes a while to load. */
1909
1812
  let canvas = await scope.page.waitForSelector( `#dasigcanvas` );
1910
1813
  let msg = `Could not find a signature field.`;
1911
- if ( !canvas ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
1814
+ if ( !canvas ) { reports.addToReport(scope, { type: `error`, value: msg }); }
1912
1815
  expect( canvas, msg ).to.exist;
1913
1816
 
1914
1817
  // Provides functionality to draw name arg on canvas
@@ -1981,7 +1884,7 @@ module.exports = {
1981
1884
  // Only record the page id once for each page to which we navigate
1982
1885
  let { id } = await scope.examinePageID( scope, 'none to match' );
1983
1886
  if ( scope.page_id !== id ) {
1984
- await scope.addToReport(scope, { type: `page_id`, value: `${ id }`, });
1887
+ reports.addToReport(scope, { type: `page_id`, value: `${ id }`, });
1985
1888
  scope.page_id = id;
1986
1889
  }
1987
1890
 
@@ -2011,9 +1914,9 @@ module.exports = {
2011
1914
 
2012
1915
  something_new_was_set = true;
2013
1916
  field.was_set = true;
2014
- await scope.addToReport(scope, {
1917
+ reports.addToReport(scope, {
2015
1918
  type: `row`,
2016
- value: `${ await scope.convertToOriginalStoryTableRow(scope, { row_data: row_used }) }`
1919
+ value: `${ reports.convertToOriginalStoryTableRow(scope, { row_data: row_used }) }`
2017
1920
  });
2018
1921
 
2019
1922
  // Accumulate number of times this row was used. Esp important for `.target_number`
@@ -2036,9 +1939,9 @@ module.exports = {
2036
1939
  let row_used = await scope.setVariable( scope, { field, var_data, });
2037
1940
  if ( row_used ) {
2038
1941
 
2039
- await scope.addToReport(scope, {
1942
+ reports.addToReport(scope, {
2040
1943
  type: `row`,
2041
- value: `${ await scope.convertToOriginalStoryTableRow(scope, { row_data: row_used }) }`
1944
+ value: `${ reports.convertToOriginalStoryTableRow(scope, { row_data: row_used }) }`
2042
1945
  });
2043
1946
 
2044
1947
  // Esp important for `.target_number`
@@ -2080,13 +1983,13 @@ module.exports = {
2080
1983
  if ( row.source ) { source_row = row.source; }
2081
1984
  let original_row = source_row.original;
2082
1985
  let msg = `Did not find a field on this page for the variable "${ original_row.var }" that could be set to "${ original_row.value }"`;
2083
- await scope.addToReport(scope, { type: `warning`, value: msg });
1986
+ reports.addToReport(scope, { type: `warning`, value: msg });
2084
1987
  }
2085
1988
  }
2086
1989
  if ( num_unused_rows > 0 ) {
2087
1990
  let { id } = await scope.examinePageID( scope, 'none to match' );
2088
1991
  let msg = `Missing variable or variables on page "${id}"`;
2089
- await scope.addToReport(scope, { type: `error`, value: msg });
1992
+ reports.addToReport(scope, { type: `error`, value: msg });
2090
1993
  expect( num_unused_rows, msg ).to.equal( 0 );
2091
1994
  }
2092
1995
  }
@@ -2276,7 +2179,7 @@ module.exports = {
2276
2179
  // probably add a confusing message to the report, so mutating seems, sadly, more useful.
2277
2180
  var_datum.value = `False`; // The original value is still preserved
2278
2181
  // Warn the developer not to use `.there_is_another`.
2279
- await scope.addToReport( scope, { type: `warning`,
2182
+ reports.addToReport( scope, { type: `warning`,
2280
2183
  value: 'The attribute `.there_is_another` is invalid in story table tests. Replace it with '
2281
2184
  + '`.target_number` in your `var` and `trigger` columns. Set the `value` to the number of items in '
2282
2185
  + `that list. This test will now set this row's \`value\` to \`False\`. See `
@@ -2310,10 +2213,10 @@ module.exports = {
2310
2213
  } else if ( value.match( /(true|yes|checked|check|selected|select)/i )) {
2311
2214
  if ( from_story_table && !row.artificial ) {
2312
2215
  if ( row.var.match( /\.there_is_another$/ ) ) {
2313
- await scope.addToReport( scope, { type: `warning`, value: `The attribute \`.there_is_another\` is invalid in story table tests. Replace it with \`.target_number\` in your \`var\` column. Set the \`value\` to the number of items in that list. This test will now set this row's \`value\` to \`False\`. See https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing#there_is_another-loop. The row data is\n${ JSON.stringify( row.original )}` });
2216
+ reports.addToReport( scope, { type: `warning`, value: `The attribute \`.there_is_another\` is invalid in story table tests. Replace it with \`.target_number\` in your \`var\` column. Set the \`value\` to the number of items in that list. This test will now set this row's \`value\` to \`False\`. See https://suffolklitlab.github.io/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing#there_is_another-loop. The row data is\n${ JSON.stringify( row.original )}` });
2314
2217
  } else {
2315
2218
  // If `target_number` is set to True instead of a number
2316
- await scope.addToReport( scope, { type: `warning`, value: `${ val_to_print } value is not a valid value for ${ row.var } here. This test will default to \`False\` to avoid problems.` });
2219
+ reports.addToReport( scope, { type: `warning`, value: `${ val_to_print } value is not a valid value for ${ row.var } here. This test will default to \`False\` to avoid problems.` });
2317
2220
  }
2318
2221
  return `False`;
2319
2222
  } else { return `True`; }
@@ -2321,7 +2224,7 @@ module.exports = {
2321
2224
  } else if ( isNaN( desired_num )) { // Story table loop will end
2322
2225
  // I can't figure out how to trigger this warning, so maybe we're
2323
2226
  // already handling everything we need to
2324
- await scope.addToReport( scope, { type: `warning`, value: `${ val_to_print } value is not a valid value for ${ row.var } here. This test will default to \`False\` to avoid problems.` });
2227
+ reports.addToReport( scope, { type: `warning`, value: `${ val_to_print } value is not a valid value for ${ row.var } here. This test will default to \`False\` to avoid problems.` });
2325
2228
  return `False`;
2326
2229
 
2327
2230
  // Check that the number of elements in the list hits our desired number.
@@ -2372,7 +2275,7 @@ module.exports = {
2372
2275
  }
2373
2276
 
2374
2277
  let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. Check the picture of this page. If a secret variable was used, there will be no picture.`
2375
- if ( timedOut ) { await scope.addToReport(scope, { type: `error`, value: timeout_err_message }); }
2278
+ if ( timedOut ) { reports.addToReport(scope, { type: `error`, value: timeout_err_message }); }
2376
2279
  expect( timedOut, timeout_err_message ).to.be.false;
2377
2280
 
2378
2281
  clearTimeout( cont_timeout );
@@ -2416,13 +2319,13 @@ module.exports = {
2416
2319
  /* Throw a cucumber error with the error that appeared on the page. */
2417
2320
  if ( error_handlers.system_error !== undefined ) {
2418
2321
  let sys_err_message = `The test tried to continue to the next page after ${ id }, but there was a system error. Check the picture of this page.`
2419
- await scope.addToReport(scope, { type: `error`, value: sys_err_message });
2322
+ reports.addToReport(scope, { type: `error`, value: sys_err_message });
2420
2323
  expect( was_system_error, sys_err_message ).to.be.false;
2421
2324
 
2422
2325
  } else {
2423
2326
  // If it wasn't a system error, it was a user error
2424
2327
  let user_err_message = `The test tried to continue to the next page after ${ id }, but the user got an error. Check the picture of this page.`
2425
- await scope.addToReport(scope, { type: `error`, value: user_err_message });
2328
+ reports.addToReport(scope, { type: `error`, value: user_err_message });
2426
2329
  expect( true, user_err_message ).to.be.false;
2427
2330
  }
2428
2331
 
@@ -2444,6 +2347,22 @@ module.exports = {
2444
2347
  fullPage: fullPage,
2445
2348
  });
2446
2349
 
2350
+ let html_path = path;
2351
+ if (path.endsWith(".jpg")) {
2352
+ html_path = html_path.substring(0, html_path.length - 4) + ".html";
2353
+ } else {
2354
+ html_path = html_path + ".html";
2355
+ }
2356
+
2357
+ // Also save the HTML of the page
2358
+ await scope.page.content().then(content => {
2359
+ let server_url = session_vars.get_da_server_url();
2360
+ content = content.replaceAll(/"(\/static\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2361
+ content = content.replaceAll(/"(\/static\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2362
+ content = content.replaceAll(/"(\/packagestatic\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2363
+ content = content.replaceAll(/"(\/packagestatic\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2364
+ fs.writeFileSync(html_path, content)
2365
+ });
2447
2366
  }, // Ends scope.take_a_screenshot()
2448
2367
 
2449
2368
 
@@ -2483,7 +2402,7 @@ module.exports = {
2483
2402
  });
2484
2403
 
2485
2404
  // In future, may be saved to artifact file
2486
- await scope.addToReport( scope, {
2405
+ reports.addToReport( scope, {
2487
2406
  type: `json`,
2488
2407
  value: JSON.stringify( data, null, 2 )
2489
2408
  });
@@ -2499,7 +2418,7 @@ module.exports = {
2499
2418
  let response = await linkPage.goto( actual_url, { waitUntil: 'domcontentloaded' });
2500
2419
 
2501
2420
  let msg = `The "${ linkText }" link is broken.`;
2502
- if ( !response.ok() ) { await scope.addToReport( scope, { type: `error`, value: msg }); }
2421
+ if ( !response.ok() ) { reports.addToReport( scope, { type: `error`, value: msg }); }
2503
2422
  expect( response.ok(), msg ).to.be.true;
2504
2423
 
2505
2424
  // This may cause problems if the linked page is the interview page
@@ -2548,7 +2467,7 @@ module.exports = {
2548
2467
  const [link] = await scope.page.$x(`//a[contains(text(), "${phrase}")]`);
2549
2468
 
2550
2469
  let msg = `The term "${ phrase }" seems to be missing.`;
2551
- if ( !link ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
2470
+ if ( !link ) { reports.addToReport(scope, { type: `error`, value: msg }); }
2552
2471
  expect( link, msg ).to.exist;
2553
2472
 
2554
2473
  await scope.tapElement( scope, link );
@@ -2575,7 +2494,7 @@ module.exports = {
2575
2494
  let [elem] = await scope.page.$x(`//a[contains(@href, "${ filename }")]`);
2576
2495
 
2577
2496
  let msg = `"${ filename }" seems to be missing. Cannot find a link to that document.`;
2578
- if ( !elem ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
2497
+ if ( !elem ) { reports.addToReport(scope, { type: `error`, value: msg }); }
2579
2498
  expect( elem, msg ).to.exist;
2580
2499
 
2581
2500
  scope.toDownload = filename;
@@ -2611,7 +2530,7 @@ module.exports = {
2611
2530
  name_parts_slice.push(name_parts_last);
2612
2531
  name_parts = name_parts_slice;
2613
2532
 
2614
- await scope.addToReport(scope, {
2533
+ reports.addToReport(scope, {
2615
2534
  type: `warning`,
2616
2535
  value: `The name "${name_str}" has more than 4 parts, but 4 is the maximum allowed. The test will set the name to "${name_parts.join(" ")}".`
2617
2536
  });
@@ -2697,7 +2616,7 @@ module.exports = {
2697
2616
  await scope.handle_page_timeout_error( scope, { non_reload_report_msg, error });
2698
2617
  } else {
2699
2618
  // Throw any non-timeout error
2700
- scope.addToReport( scope, { type: `error`, value: err_msg });
2619
+ reports.addToReport( scope, { type: `error`, value: err_msg });
2701
2620
  throw error;
2702
2621
  } // ends if error is timeout error
2703
2622
 
@@ -2711,11 +2630,11 @@ module.exports = {
2711
2630
  // this is complex
2712
2631
  let email_msg = `The email address GitHub SECRET "${ email_secret_name }" doesn't exist. Check for a typo and check your workflow file.`
2713
2632
  if ( email === undefined ) {
2714
- await scope.addToReport( scope, { type: `error`, value: email_msg })
2633
+ reports.addToReport( scope, { type: `error`, value: email_msg })
2715
2634
  }
2716
2635
  let password_msg = `The password GitHub SECRET "${ password_secret_name }" doesn't exist. Check for a typo and check your workflow file.`
2717
2636
  if ( password === undefined ) {
2718
- await scope.addToReport( scope, { type: `error`, value: password_msg })
2637
+ reports.addToReport( scope, { type: `error`, value: password_msg })
2719
2638
  }
2720
2639
 
2721
2640
  expect( email, email_msg ).to.not.equal( undefined );
@@ -2732,7 +2651,7 @@ module.exports = {
2732
2651
  // TODO: use a Promise.race to fail faster.
2733
2652
  await scope.page.waitForSelector( `.datopcenter .alert-success` );
2734
2653
 
2735
- await scope.addToReport( scope, {
2654
+ reports.addToReport( scope, {
2736
2655
  type: `row`,
2737
2656
  value: `Signed into ${ session_vars.get_da_server_url() }/user/sign-in successfully.`
2738
2657
  });
@@ -2874,7 +2793,7 @@ module.exports = {
2874
2793
  if ( type !== `` && type !== `hidden` ) {
2875
2794
  // Field types that we can't yet handle, but plan to in future
2876
2795
  if ( type === `file` ) {
2877
- await scope.addToReport( scope, {
2796
+ reports.addToReport( scope, {
2878
2797
  type: `warning`,
2879
2798
  value: `Sorry, ALKiln's random input tests cannot yet handle "${ type }" fields.`
2880
2799
  });
@@ -2980,7 +2899,7 @@ module.exports = {
2980
2899
  */
2981
2900
  let current_report_obj = scope.report.get( scope.scenario_id );
2982
2901
  let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
2983
- let report = await scope.getPrintableScenario( scope, { scenario });
2902
+ let report = reports.getPrintableScenario( scenario );
2984
2903
  let all_are_included = true;
2985
2904
  for ( let one_expectation of expected ) {
2986
2905
  if ( !report.includes( one_expectation )) {
@@ -3000,7 +2919,7 @@ module.exports = {
3000
2919
  */
3001
2920
  let current_report_obj = scope.report.get( scope.scenario_id );
3002
2921
  let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
3003
- let report = await scope.getPrintableScenario( scope, { scenario });
2922
+ let report = reports.getPrintableScenario( scenario );
3004
2923
  let none_included = true;
3005
2924
  for ( let one_expectation of not_expected ) {
3006
2925
  if ( report.includes( one_expectation )) {