@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/CHANGELOG.md +25 -0
- package/lib/scope.js +166 -247
- package/lib/steps.js +67 -49
- package/lib/utils/reports.js +147 -0
- package/package.json +1 -1
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
|
-
|
|
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 ) {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
//
|
|
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
|
-
|
|
627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
575
|
+
reports.addToReport( scope, {
|
|
709
576
|
type: `error`,
|
|
710
577
|
value: msg
|
|
711
578
|
});
|
|
712
579
|
} else {
|
|
713
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1618
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ) {
|
|
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
|
-
|
|
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
|
-
|
|
1917
|
+
reports.addToReport(scope, {
|
|
2015
1918
|
type: `row`,
|
|
2016
|
-
value: `${
|
|
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
|
-
|
|
1942
|
+
reports.addToReport(scope, {
|
|
2040
1943
|
type: `row`,
|
|
2041
|
-
value: `${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ) {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() ) {
|
|
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 ) {
|
|
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 ) {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 )) {
|