@suffolklitlab/alkiln 4.0.0-pre-api.8 → 4.1.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 +69 -11
- package/README.md +10 -2
- package/action.yml +7 -5
- package/lib/docassemble/docassemble_api_interface.js +3 -2
- package/lib/docassemble/get_base_interview_url.js +19 -0
- package/lib/index.js +0 -2
- package/lib/scope.js +170 -99
- package/lib/steps.js +22 -22
- package/lib/utils/langs.js +3 -3
- package/lib/utils/session_vars.js +56 -82
- package/package.json +4 -5
- package/tests/features/establishing_steps.feature +2 -2
- package/tests/features/observation_steps.feature +1 -1
- package/tests/features/reports.feature +23 -55
- package/tests/features/story_table_formats.feature +1 -1
- package/tests/features/story_tables.feature +1 -1
- package/tests/unit_tests/getMatchingRows.test.js +28 -0
- package/tests/unit_tests/matches.fixtures.js +13 -1
- package/tests/unit_tests/session_vars.test.js +46 -0
- package/tests/unit_tests/tables.fixtures.js +12 -2
- package/lib/utils/env_vars.js +0 -177
- package/tests/unit_tests/env_vars.test.js +0 -53
package/lib/scope.js
CHANGED
|
@@ -5,8 +5,8 @@ const fs = require('fs');
|
|
|
5
5
|
const safe_filename = require("sanitize-filename");
|
|
6
6
|
|
|
7
7
|
// Ours
|
|
8
|
-
const env_vars = require('./utils/env_vars');
|
|
9
8
|
const session_vars = require('./utils/session_vars');
|
|
9
|
+
const get_base_interview_url = require(`./docassemble/get_base_interview_url`);
|
|
10
10
|
const time = require('./utils/time');
|
|
11
11
|
|
|
12
12
|
|
|
@@ -25,13 +25,14 @@ let escape_regex = function( to_escape ) {
|
|
|
25
25
|
let proxies_regex = /(\bx\.)|(\bx\[)|(\[[ijklmn]\])/g;
|
|
26
26
|
|
|
27
27
|
module.exports = {
|
|
28
|
+
trigger_not_needed_flag: `ALKiln: no trigger variable needed`,
|
|
28
29
|
addToReport: async function( scope, { type, value }) {
|
|
29
30
|
/* Add an item to a specific list in a scenario's report.
|
|
30
31
|
* Create report if necessary.
|
|
31
32
|
*
|
|
32
33
|
* The report has a key for each scenario and each of those
|
|
33
34
|
* keys should correspond to a list containing the relevant data. */
|
|
34
|
-
if (
|
|
35
|
+
if ( session_vars.get_debug() ) { console.log(`Report:\ntype: ${ type }, value: ${ value }`) }
|
|
35
36
|
let scenario = await scope.makeSureReportPartsExist( scope );
|
|
36
37
|
scenario.lines.push({ type, value });
|
|
37
38
|
|
|
@@ -62,7 +63,7 @@ module.exports = {
|
|
|
62
63
|
}, // Ends scope.setReportScenarioStatus()
|
|
63
64
|
|
|
64
65
|
convertToOriginalStoryTableRow: async function( scope, { row_data }) {
|
|
65
|
-
/* Returns the original data of row into a string
|
|
66
|
+
/* Returns the original data of row into a string formatted as a story table row.
|
|
66
67
|
* Should this show the actual row data that was used instead? That could
|
|
67
68
|
* be pretty confusing to most devs. */
|
|
68
69
|
let source_row = row_data.source || row_data;
|
|
@@ -70,6 +71,10 @@ module.exports = {
|
|
|
70
71
|
let var_str = await scope.toTrimmedJSON( scope, { str: original.var });
|
|
71
72
|
let value_str = await scope.toTrimmedJSON( scope, { str: original.value });
|
|
72
73
|
let trigger_str = await scope.toTrimmedJSON( scope, { str: original.trigger });
|
|
74
|
+
if ( trigger_str === scope.trigger_not_needed_flag ) {
|
|
75
|
+
// This text was added by ALKiln itself. It's not part of the user's data
|
|
76
|
+
trigger_str = ``;
|
|
77
|
+
}
|
|
73
78
|
return `${ ' '.repeat(6) }| ${ var_str } | ${ value_str } | ${ trigger_str } |`;
|
|
74
79
|
}, // Ends scope.convertToOriginalStoryTableRows()
|
|
75
80
|
|
|
@@ -84,7 +89,7 @@ module.exports = {
|
|
|
84
89
|
/** Return printable heading based on Scenario name and tags.
|
|
85
90
|
* Allow characters from other languages.
|
|
86
91
|
*/
|
|
87
|
-
if (
|
|
92
|
+
if ( session_vars.get_debug() ) { console.log( scenario.pickle.name ); }
|
|
88
93
|
|
|
89
94
|
// Collect all tag names
|
|
90
95
|
let tag_names = [];
|
|
@@ -108,7 +113,7 @@ module.exports = {
|
|
|
108
113
|
}, // Ends scope.addReportHeading()
|
|
109
114
|
|
|
110
115
|
getPrintableReport: async function( scope ) {
|
|
111
|
-
/* Return a string
|
|
116
|
+
/* Return a string generated from test report data. */
|
|
112
117
|
let report = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
|
|
113
118
|
|
|
114
119
|
// First list failed scenarios, then non-passed scenarios, then passed scenarios
|
|
@@ -132,7 +137,7 @@ module.exports = {
|
|
|
132
137
|
}, // Ends scope.getPrintableReport()
|
|
133
138
|
|
|
134
139
|
getPrintableScenario: async function( scope, { scenario }) {
|
|
135
|
-
/* Return a string
|
|
140
|
+
/* Return a string generated from scenario list of report items. */
|
|
136
141
|
let [ scenario_id, data ] = scenario;
|
|
137
142
|
let report = ``;
|
|
138
143
|
for ( let line of data.lines ) {
|
|
@@ -386,7 +391,7 @@ module.exports = {
|
|
|
386
391
|
scope.page.waitForSelector(`fieldset.da-field-buttons button[type="submit"]`), // other pages (this is the most consistent way)
|
|
387
392
|
scope.page.waitForSelector(`fieldset .dasigsave`), // signature page
|
|
388
393
|
]);
|
|
389
|
-
|
|
394
|
+
await elem.evaluate( el => { return el.className });
|
|
390
395
|
// Waits for navigation or user error
|
|
391
396
|
await scope.tapElement( scope, elem );
|
|
392
397
|
},
|
|
@@ -397,18 +402,19 @@ module.exports = {
|
|
|
397
402
|
|
|
398
403
|
// If there is no browser open, start a new one
|
|
399
404
|
if (!scope.browser) {
|
|
400
|
-
scope.browser = await scope.driver.launch({ headless: !
|
|
405
|
+
scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug() });
|
|
401
406
|
}
|
|
402
407
|
if ( !scope.page ) { scope.page = await scope.browser.newPage(); }
|
|
403
408
|
|
|
404
409
|
if ( !file_name.includes('.yml') ) { file_name = `${ file_name }.yml` }
|
|
405
|
-
let
|
|
406
|
-
|
|
410
|
+
let base_url = await get_base_interview_url();
|
|
411
|
+
let interview_url = `${ base_url }${ file_name }`;
|
|
412
|
+
if ( session_vars.get_debug() ) { console.log( interview_url ); }
|
|
407
413
|
|
|
408
414
|
await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout })
|
|
409
415
|
|
|
410
416
|
let load_result = await scope.getLoadData( scope );
|
|
411
|
-
// Errors, or reaching timeout, causes an
|
|
417
|
+
// Errors, or reaching timeout, causes an exception and is shown to the developer at the end
|
|
412
418
|
if ( load_result.error ) {
|
|
413
419
|
await scope.addToReport(scope, { type: `error`, value: `On final attempt to load interview, got "${ load_result.error }"` });
|
|
414
420
|
expect( load_result.error ).to.equal( '' );
|
|
@@ -481,7 +487,7 @@ module.exports = {
|
|
|
481
487
|
supported_table.push( result );
|
|
482
488
|
}
|
|
483
489
|
|
|
484
|
-
if (
|
|
490
|
+
if ( session_vars.get_debug() ) { console.log( 'scope.normalizedTable():\n', JSON.stringify( supported_table )); }
|
|
485
491
|
|
|
486
492
|
return supported_table;
|
|
487
493
|
}, // Ends scope.normalizeTable()
|
|
@@ -506,7 +512,7 @@ module.exports = {
|
|
|
506
512
|
let encoded_trigger_var = $( trigger_elem ).data( `variable` ); // This should come out to 'None' at the very least
|
|
507
513
|
if ( !encoded_trigger_var ) {
|
|
508
514
|
// TODO: Add documentation and then link to it in this message.
|
|
509
|
-
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.
|
|
515
|
+
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.` });
|
|
510
516
|
encoded_trigger_var = await scope.toBase64( scope, { utf8_str: `None` });
|
|
511
517
|
}
|
|
512
518
|
let trigger = await scope.fromBase64( scope, { base64_str: encoded_trigger_var });
|
|
@@ -598,7 +604,7 @@ module.exports = {
|
|
|
598
604
|
fields.push( field_data );
|
|
599
605
|
} // ends for every node
|
|
600
606
|
|
|
601
|
-
if (
|
|
607
|
+
if ( session_vars.get_debug() ) { console.log( `page fields:\n${ JSON.stringify( fields )}`); }
|
|
602
608
|
return fields;
|
|
603
609
|
}, // Ends scope.getAllFields()
|
|
604
610
|
|
|
@@ -633,7 +639,7 @@ module.exports = {
|
|
|
633
639
|
|
|
634
640
|
// Make sure this selector matches only one node
|
|
635
641
|
let matching_nodes = $( selector );
|
|
636
|
-
// This check is here to ensure
|
|
642
|
+
// This check is here to ensure safety of future development. If there is more
|
|
637
643
|
// or less than one match, this code may have unexpected behavior later.
|
|
638
644
|
// Really need everything to be unique here if at all possible. Shall we resort
|
|
639
645
|
// to parents and n-th child?
|
|
@@ -828,6 +834,8 @@ module.exports = {
|
|
|
828
834
|
/* Given var_data, a list of objects with variable data for
|
|
829
835
|
* setting fields, and an object for a DOM field, returns list of the
|
|
830
836
|
* rows that match the DOM field. That list of story table rows may be empty.
|
|
837
|
+
*
|
|
838
|
+
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger
|
|
831
839
|
*
|
|
832
840
|
* @param var_data {arr} - fields and the values they can be set to
|
|
833
841
|
* @param var_data[n].var {str} - name of variable to set
|
|
@@ -840,24 +848,26 @@ module.exports = {
|
|
|
840
848
|
* @param field[n].tag {str} - HTML tag of node
|
|
841
849
|
* @param field[n].type {str} - HTML type of node if it has one
|
|
842
850
|
* @param field[n].guesses {arr} - arr of objects that could
|
|
843
|
-
* possibly match a table row
|
|
851
|
+
* possibly match a table row's `var` column
|
|
844
852
|
* @param field[n].guesses[n].var {str} - name of variable
|
|
845
853
|
* @param field[n].guesses[n].value {str} - value of a choice
|
|
846
854
|
* @param <field[n].guesses[n].trigger> {str} - var name that triggered this page
|
|
847
855
|
*
|
|
848
856
|
* @returns [ var_data[n] ]
|
|
849
857
|
*/
|
|
850
|
-
let matches = [];
|
|
851
|
-
let uses_proxies = false;
|
|
852
858
|
|
|
853
|
-
|
|
859
|
+
let rows_that_match_field_names = [];
|
|
860
|
+
let uses_proxies = false; // See "proxies" notes further down
|
|
861
|
+
let trigger_might_be_needed = true;
|
|
862
|
+
|
|
863
|
+
// There are multiple guesses because field names are sometimes encoded
|
|
854
864
|
for ( let guess of field.guesses ) {
|
|
855
|
-
for ( let
|
|
856
|
-
let
|
|
865
|
+
for ( let table_row of var_data ) {
|
|
866
|
+
let this_row_matches_this_field = false;
|
|
857
867
|
|
|
858
868
|
// If the dev uses single instead of double quotes, etc, allow that
|
|
859
|
-
let names_match = await scope.matchNeutralizedQuotes( scope, { value1: guess.var, value2:
|
|
860
|
-
let values_match = await scope.matchNeutralizedQuotes( scope, { value1: guess.value, value2:
|
|
869
|
+
let names_match = await scope.matchNeutralizedQuotes( scope, { value1: guess.var, value2: table_row.var });
|
|
870
|
+
let values_match = await scope.matchNeutralizedQuotes( scope, { value1: guess.value, value2: table_row.value });
|
|
861
871
|
|
|
862
872
|
// Check different things for different fields. An input box won't
|
|
863
873
|
// be expected to have the same value as the var you want to set while
|
|
@@ -865,91 +875,152 @@ module.exports = {
|
|
|
865
875
|
switch ( field.tag ) {
|
|
866
876
|
case 'button':
|
|
867
877
|
case 'select':
|
|
868
|
-
if ( names_match && values_match ) {
|
|
878
|
+
if ( names_match && values_match ) { this_row_matches_this_field = true; }
|
|
869
879
|
break;
|
|
870
880
|
|
|
871
881
|
case 'canvas':
|
|
872
882
|
case 'textarea':
|
|
873
883
|
case 'a':
|
|
874
|
-
if ( names_match ) {
|
|
884
|
+
if ( names_match ) { this_row_matches_this_field = true; }
|
|
875
885
|
break;
|
|
876
886
|
|
|
877
887
|
case 'input':
|
|
878
888
|
if ( field.type === 'radio' ) {
|
|
879
|
-
if ( names_match && values_match ) {
|
|
889
|
+
if ( names_match && values_match ) { this_row_matches_this_field = true; }
|
|
880
890
|
} else { // Some kind of text input
|
|
881
|
-
if ( names_match ) {
|
|
891
|
+
if ( names_match ) { this_row_matches_this_field = true; }
|
|
882
892
|
}
|
|
883
893
|
break;
|
|
884
894
|
}
|
|
885
895
|
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
896
|
+
if ( this_row_matches_this_field ) {
|
|
897
|
+
rows_that_match_field_names.push( table_row );
|
|
898
|
+
// Check this until the value is set to `true` (if it ever is)
|
|
899
|
+
// If the field uses proxy variables - x, i, j, etc, flag it. See "proxies" note.
|
|
900
|
+
uses_proxies = uses_proxies || guess.var.match( proxies_regex );
|
|
901
|
+
|
|
902
|
+
if ( table_row.trigger === scope.trigger_not_needed_flag ) {
|
|
903
|
+
trigger_might_be_needed = false;
|
|
893
904
|
}
|
|
894
|
-
|
|
895
|
-
}
|
|
905
|
+
} // end if this_row_matches_this_field
|
|
896
906
|
|
|
897
907
|
} // ends for each table row
|
|
898
908
|
} // ends for each field row possibility
|
|
899
909
|
|
|
900
|
-
//
|
|
901
|
-
//
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
let
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
//
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
910
|
+
// Proxies:
|
|
911
|
+
// When a dev has a loop with an index var or generic object, the
|
|
912
|
+
// same variable name will be used over and over, e.g.`users[i].name.first`.
|
|
913
|
+
// In a regular Step, there's no ambiguity about the value to set.
|
|
914
|
+
// In a dev's story table, though, there could be multiple rows that match
|
|
915
|
+
// a field named `users[i]`. For kiln to know which row to use on which
|
|
916
|
+
// page, we need the one unique HTML string that will appear on the page -
|
|
917
|
+
// the trigger var name. When a row with `users[i]` has a "trigger" column
|
|
918
|
+
// value that matches the page HTML's trigger var name, kiln will know to
|
|
919
|
+
// use the value from that row. We need to find the matching row.
|
|
920
|
+
let final_matching_rows = [];
|
|
921
|
+
if ( trigger_might_be_needed && uses_proxies && rows_that_match_field_names.length > 0 ) {
|
|
922
|
+
|
|
923
|
+
let rows_that_also_match_trigger_name = [];
|
|
924
|
+
let rows_without_a_trigger_name = [];
|
|
925
|
+
let rows_with_conflicting_trigger_names = [];
|
|
926
|
+
|
|
927
|
+
let page_trigger_name = field.trigger;
|
|
928
|
+
|
|
929
|
+
// field/page trigger name is needed but is missing.
|
|
930
|
+
if ( !page_trigger_name ) {
|
|
931
|
+
let page_trigger_warning = `A field on this page uses an index `
|
|
932
|
+
+ `variable or a generic object, but the page does not `
|
|
933
|
+
+ `contain the trigger variable's name in its HTML. See `
|
|
934
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#a-missing-trigger-variable.`;
|
|
935
|
+
await scope.addToReport(scope, { type: `warning`, value: page_trigger_warning });
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
for ( let row_that_matches_this_field of rows_that_match_field_names ) {
|
|
939
|
+
|
|
940
|
+
let table_row_trigger_name = row_that_matches_this_field.trigger;
|
|
941
|
+
|
|
942
|
+
if ( !table_row_trigger_name ) {
|
|
943
|
+
// var/row trigger name is (probably) needed but is missing. Caveat: If there's
|
|
944
|
+
// only one page in the proxy var loop and there's only one row, trigger name is
|
|
945
|
+
// not needed, but I'm not sure how we figure out all those things here and log
|
|
946
|
+
// a useful message.
|
|
947
|
+
|
|
948
|
+
// First, make the row data more readable to humans.
|
|
949
|
+
let row_as_story_table = await scope.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
|
|
950
|
+
let row_trigger_warning = `This story table row needs a value in the "trigger" `
|
|
951
|
+
+ `column, but it is empty. The test might get unexpected results. See `
|
|
952
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
|
|
953
|
+
+ `\nThe row's data:\n${ row_as_story_table }`
|
|
954
|
+
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
955
|
+
await scope.addToReport(scope, { type: `warning`, value: row_trigger_warning });
|
|
956
|
+
rows_without_a_trigger_name.push( row_that_matches_this_field );
|
|
957
|
+
} else {
|
|
958
|
+
let triggers_match = await scope.matchNeutralizedQuotes( scope, { value1: page_trigger_name, value2: table_row_trigger_name });
|
|
937
959
|
if ( triggers_match ) {
|
|
938
|
-
|
|
960
|
+
rows_that_also_match_trigger_name.push( row_that_matches_this_field );
|
|
961
|
+
} else {
|
|
962
|
+
rows_with_conflicting_trigger_names.push( row_that_matches_this_field );
|
|
939
963
|
}
|
|
940
964
|
}
|
|
965
|
+
} // ends for each row_that_matches_this_field
|
|
966
|
+
|
|
967
|
+
// Be very permissive, based on feedback. See https://github.com/SuffolkLITLab/ALKiln/issues/464#issuecomment-1005166616
|
|
968
|
+
if ( rows_that_also_match_trigger_name.length > 0 ) {
|
|
969
|
+
final_matching_rows = rows_that_also_match_trigger_name;
|
|
970
|
+
} else {
|
|
971
|
+
final_matching_rows = rows_without_a_trigger_name;
|
|
941
972
|
}
|
|
942
|
-
// Feedback for devs - we are missing the matching triggers we need
|
|
943
|
-
if ( matches.length === 0 ) { await scope.addToReport(scope, { type: `warning`, value: `A field on this page uses an index variable or a generic object. The test gave multiple matches for the story data for the field, but none match the trigger variable. Is the story table missing a row? For now, the test will have to ignore this field. The rows' data is\n${ JSON.stringify( original_matches )}.\nThe field's data is\n${ JSON.stringify( field )}` }); }
|
|
944
|
-
}
|
|
945
973
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
974
|
+
// Warn the user if all matching rows had trigger names that conflicted.
|
|
975
|
+
// Missing trigger names don't count as conflicting.
|
|
976
|
+
if ( final_matching_rows.length === 0 ) {
|
|
977
|
+
|
|
978
|
+
// First, make the row data more readable to humans.
|
|
979
|
+
let conflicts_as_story_table_rows = [];
|
|
980
|
+
for ( let row of rows_with_conflicting_trigger_names ) {
|
|
981
|
+
let table_str = await scope.convertToOriginalStoryTableRow( scope, { row_data: row });
|
|
982
|
+
conflicts_as_story_table_rows.push( table_str );
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
let all_trigger_names_conflict_msg = `Is this story table missing a row? `
|
|
986
|
+
+ `${ rows_that_match_field_names.length } story table row(s) matched a field name, `
|
|
987
|
+
+ `but none of their trigger variable name(s) matched the trigger variable `
|
|
988
|
+
+ `name in the screen's HTML. The test will have to ignore this field. See `
|
|
989
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
|
|
990
|
+
+ `\nThe data of row(s) with conflicting trigger name(s):\n${ conflicts_as_story_table_rows.join( `\n` )}`
|
|
991
|
+
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
992
|
+
await scope.addToReport(scope, {
|
|
993
|
+
type: `warning`,
|
|
994
|
+
value: all_trigger_names_conflict_msg
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
} else {
|
|
999
|
+
// These should be used if NO trigger name was needed
|
|
1000
|
+
final_matching_rows = rows_that_match_field_names;
|
|
1001
|
+
} // ends if need trigger name to match
|
|
1002
|
+
|
|
1003
|
+
// Dev may have accidentally included a row more than once.
|
|
1004
|
+
if ( final_matching_rows.length > 1 ) {
|
|
1005
|
+
|
|
1006
|
+
// First, make the row data more readable to humans.
|
|
1007
|
+
let rows_as_story_table = [];
|
|
1008
|
+
for ( let row of final_matching_rows ) {
|
|
1009
|
+
let table_str = await scope.convertToOriginalStoryTableRow( scope, { row_data: row });
|
|
1010
|
+
rows_as_story_table.push( table_str );
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
let multiple_matches_msg = `Do you have a duplicate story table row? `
|
|
1014
|
+
+ `Multiple story table rows match this field's variable name. `
|
|
1015
|
+
+ `The test will use the last row that matched. See `
|
|
1016
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/#trigger.`
|
|
1017
|
+
+ `\nThe matching rows:\n${ rows_as_story_table.join(`\n`) }`
|
|
1018
|
+
+ `\nThis field's data:\n${ JSON.stringify( field )}`;
|
|
1019
|
+
await scope.addToReport(scope, { type: `warning`, value: multiple_matches_msg });
|
|
949
1020
|
}
|
|
950
1021
|
|
|
951
|
-
if (
|
|
952
|
-
return
|
|
1022
|
+
if ( session_vars.get_debug() ) { console.log( `final_matching_rows:\n`, JSON.stringify( final_matching_rows )); }
|
|
1023
|
+
return final_matching_rows;
|
|
953
1024
|
}, // Ends scope.getMatchingRows()
|
|
954
1025
|
|
|
955
1026
|
|
|
@@ -963,7 +1034,6 @@ module.exports = {
|
|
|
963
1034
|
return val_2_str.match( val_1_regex );
|
|
964
1035
|
}, // Ends scope.matchNeutralizedQuotes()
|
|
965
1036
|
|
|
966
|
-
|
|
967
1037
|
setVariable: async function ( scope, { field, var_data }) {
|
|
968
1038
|
/* Returns true if the field was set.
|
|
969
1039
|
*
|
|
@@ -1004,7 +1074,7 @@ module.exports = {
|
|
|
1004
1074
|
// If field is disabled, don't set it yet
|
|
1005
1075
|
if ( disabled ) { return row_used; }
|
|
1006
1076
|
|
|
1007
|
-
if (
|
|
1077
|
+
if ( session_vars.get_debug() ) {
|
|
1008
1078
|
let name = await handle.evaluate(( elem ) => { // Not using this right now
|
|
1009
1079
|
try { elem.nextSibling.style.background = 'Khaki'; }
|
|
1010
1080
|
catch(err){ elem.style.background = 'Khaki'; }
|
|
@@ -1015,7 +1085,7 @@ module.exports = {
|
|
|
1015
1085
|
|
|
1016
1086
|
let { tag, type } = field;
|
|
1017
1087
|
// We use the last match. We wrote a warning for the user if there were
|
|
1018
|
-
//
|
|
1088
|
+
// multiple matches in the story that the last value will be used. In future,
|
|
1019
1089
|
// Steps will be able to set this data gradually and we don't want the developer
|
|
1020
1090
|
// to get confused thinking that an earlier Step was used instead of a later one.
|
|
1021
1091
|
let row = matching_input_rows[ matching_input_rows.length - 1 ];
|
|
@@ -1066,7 +1136,7 @@ module.exports = {
|
|
|
1066
1136
|
for ( let field_i = 0; field_i < fields.length; field_i++ ) {
|
|
1067
1137
|
let field = fields[ field_i ];
|
|
1068
1138
|
|
|
1069
|
-
if (
|
|
1139
|
+
if ( session_vars.get_debug() ) {
|
|
1070
1140
|
await field.evaluate( elem => { elem.style.background = `Khaki`; });
|
|
1071
1141
|
}
|
|
1072
1142
|
|
|
@@ -1130,7 +1200,7 @@ module.exports = {
|
|
|
1130
1200
|
|
|
1131
1201
|
let [label] = await handle.$x(`following-sibling::label`);
|
|
1132
1202
|
|
|
1133
|
-
if (
|
|
1203
|
+
if ( session_vars.get_debug() ) {
|
|
1134
1204
|
if ( set_to.toLowerCase() === 'false' ) { await label.evaluate( elem => { elem.style.background = 'tomato'; }); }
|
|
1135
1205
|
else { await label.evaluate( elem => { elem.style.background = 'teal'; }); }
|
|
1136
1206
|
let name = await label.evaluate( elem => { return elem.getAttribute('for'); });
|
|
@@ -1179,7 +1249,7 @@ module.exports = {
|
|
|
1179
1249
|
|
|
1180
1250
|
// Tests could be in one of two places. Figure out which directory they're in
|
|
1181
1251
|
// Makes an absolute path from the directory in which `npm run` was used plus our internal folders
|
|
1182
|
-
let dir_source = `${ process.cwd() }/docassemble/${
|
|
1252
|
+
let dir_source = `${ process.cwd() }/docassemble/${ session_vars.get_repo_url().match(/docassemble-(.*)/)[1] }/data/sources`;
|
|
1183
1253
|
let dir_features = `${ process.cwd() }/tests/features`;
|
|
1184
1254
|
let dir = null;
|
|
1185
1255
|
|
|
@@ -1290,7 +1360,7 @@ module.exports = {
|
|
|
1290
1360
|
},
|
|
1291
1361
|
|
|
1292
1362
|
examinePageID: async function ( scope, question_id = '' ) {
|
|
1293
|
-
/* Looks for a
|
|
1363
|
+
/* Looks for a sanitized version of the question id as it's written
|
|
1294
1364
|
* in the .yml. docassemble's way in python:
|
|
1295
1365
|
* re.sub(r'[^A-Za-z0-9]+', '-', interview_status.question.id.lower()) */
|
|
1296
1366
|
let id_as_class = question_id.toLowerCase().replace(/[^A-Za-z0-9]+/g, '-');
|
|
@@ -1334,7 +1404,7 @@ module.exports = {
|
|
|
1334
1404
|
* @param ensure_all_vars_used {bool} [false] = Default false. If true, errors if any
|
|
1335
1405
|
* object in var_data is not used during running of this function.
|
|
1336
1406
|
*/
|
|
1337
|
-
if (
|
|
1407
|
+
if ( session_vars.get_debug() ) { console.log( `~~~~~~~~~~~~~~ scope.setFields() ~~~~~~~~~~~~~~` ); }
|
|
1338
1408
|
|
|
1339
1409
|
// Only record the page id once for each page to which we navigate
|
|
1340
1410
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
@@ -1535,7 +1605,7 @@ module.exports = {
|
|
|
1535
1605
|
* we believe we've set it up so that people will make it a number.
|
|
1536
1606
|
* Sometimes this will be from `.there_is_another`. We think we've ensured
|
|
1537
1607
|
* that it will only come from Steps and should be `True` or `False`.
|
|
1538
|
-
* This feels precarious, but I can't see another
|
|
1608
|
+
* This feels precarious, but I can't see another permutation.
|
|
1539
1609
|
*
|
|
1540
1610
|
* This may in future be used to normalize all values that need `True`/`False`
|
|
1541
1611
|
* normalization.
|
|
@@ -1610,7 +1680,7 @@ module.exports = {
|
|
|
1610
1680
|
// All results are lists to match `$x`
|
|
1611
1681
|
let possible_errors = {
|
|
1612
1682
|
complex_user_error: await scope.page.$$('.alert-danger'),
|
|
1613
|
-
// The below does not work if there's not a space
|
|
1683
|
+
// The below does not work if there's not a space between ':' and 'none'
|
|
1614
1684
|
simple_user_error: await scope.page.$x(`//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
|
|
1615
1685
|
system_error: await scope.page.$x( '//meta[@content="docassemble: Error"]' ),
|
|
1616
1686
|
};
|
|
@@ -1621,7 +1691,7 @@ module.exports = {
|
|
|
1621
1691
|
if ( possible_errors[ error_key ][0] ) {
|
|
1622
1692
|
// If you're debugging and you close the window early, the process will still
|
|
1623
1693
|
// take this long to exit after the tests finish. Not sure how to account for that.
|
|
1624
|
-
if (
|
|
1694
|
+
if ( session_vars.get_debug() ) { await scope.page.waitForTimeout( 60 * 1000 ); }
|
|
1625
1695
|
was_found = true;
|
|
1626
1696
|
error_handlers[ error_key ] = possible_errors[ error_key ][0];
|
|
1627
1697
|
}
|
|
@@ -1633,13 +1703,13 @@ module.exports = {
|
|
|
1633
1703
|
throwPageError: async function ( scope, { error_handlers = {}, id = '' }) {
|
|
1634
1704
|
/* Throw a cucumber error with the error that appeared on the page. */
|
|
1635
1705
|
if ( error_handlers.system_error !== undefined ) {
|
|
1636
|
-
let sys_err_message = `The test tried to continue to the next page, but there was a system error. Check the screenshot.`
|
|
1706
|
+
let sys_err_message = `The test tried to continue to the next page after ${ id }, but there was a system error. Check the screenshot.`
|
|
1637
1707
|
await scope.addToReport(scope, { type: `error`, value: sys_err_message });
|
|
1638
1708
|
expect( was_system_error, sys_err_message ).to.be.false;
|
|
1639
1709
|
|
|
1640
1710
|
} else {
|
|
1641
1711
|
// If it wasn't a system error, it was a user error
|
|
1642
|
-
let user_err_message = `The test tried to continue to the next page, but the user got an error. Check the screenshot.`
|
|
1712
|
+
let user_err_message = `The test tried to continue to the next page after ${ id }, but the user got an error. Check the screenshot.`
|
|
1643
1713
|
await scope.addToReport(scope, { type: `error`, value: user_err_message });
|
|
1644
1714
|
expect( true, user_err_message ).to.be.false;
|
|
1645
1715
|
}
|
|
@@ -1705,7 +1775,7 @@ module.exports = {
|
|
|
1705
1775
|
}, // Ends link_works()
|
|
1706
1776
|
|
|
1707
1777
|
set_regular_var: async ( scope, var_name, set_to ) => {
|
|
1708
|
-
/** Set
|
|
1778
|
+
/** Set a non-story table variable (with or without a choice associated with it) to a value. Set:
|
|
1709
1779
|
* - Buttons associated with variables
|
|
1710
1780
|
* - Dropdowns
|
|
1711
1781
|
* - Checkboxes (multiple choice checkboxes must be set to "true" or "false")
|
|
@@ -1713,11 +1783,12 @@ module.exports = {
|
|
|
1713
1783
|
* - Text inputs
|
|
1714
1784
|
* - Textareas
|
|
1715
1785
|
*/
|
|
1786
|
+
// Let kiln know not to test against trigger variable values, even for proxy vars
|
|
1716
1787
|
let var_data = await scope.normalizeTable( scope, {
|
|
1717
|
-
var_data: [{ var: var_name, value: set_to, trigger:
|
|
1788
|
+
var_data: [{ var: var_name, value: set_to, trigger: scope.trigger_not_needed_flag }],
|
|
1718
1789
|
});
|
|
1719
|
-
// Don't continue if the variable doesn't
|
|
1720
|
-
// cannot be
|
|
1790
|
+
// Don't continue if the variable doesn't cause navigation itself. Also,
|
|
1791
|
+
// if this variable cannot be set on this page, trigger an error
|
|
1721
1792
|
await scope.setFields( scope, { var_data, ensure_navigation: false, ensure_all_vars_used: true });
|
|
1722
1793
|
}, // Ends set_regular_var()
|
|
1723
1794
|
|