@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/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 ( env_vars.DEBUG ) { console.log(`Report:\ntype: ${ type }, value: ${ value }`) }
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 formated as a story table row.
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 ( env_vars.DEBUG ) { console.log( scenario.pickle.name ); }
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 genereated from test report data. */
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 genereated from scenario list of report items. */
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
- let classname = await elem.evaluate( el => { return el.className });
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: !env_vars.DEBUG });
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 interview_url = `${ env_vars.getBaseInterviewURL() }${ file_name }`;
406
- if ( env_vars.DEBUG ) { console.log( interview_url ); }
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 excpetion and is shown to the developer at the end
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 ( env_vars.DEBUG ) { console.log( 'scope.normalizedTable():\n', JSON.stringify( supported_table )); }
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. Talk to the folks at the AssemblyLine project to learn more or see an example here: https://github.com/plocket/docassemble-ALAutomatedTestingTests/blob/ad1232049d7ca7a9b97b22f8ca3915dd3dae8114/docassemble/ALAutomatedTestingTests/data/questions/all_tests.yml#L6-L10.` });
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 ( env_vars.DEBUG ) { console.log( `page fields:\n${ JSON.stringify( fields )}`); }
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 saftey of future development. If there is more
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
- // There are mutliple guesses because field names are encoded
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 var_datum of var_data ) {
856
- let row_matches = false;
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: var_datum.var });
860
- let values_match = await scope.matchNeutralizedQuotes( scope, { value1: guess.value, value2: var_datum.value });
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 ) { row_matches = true; }
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 ) { row_matches = true; }
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 ) { row_matches = true; }
889
+ if ( names_match && values_match ) { this_row_matches_this_field = true; }
880
890
  } else { // Some kind of text input
881
- if ( names_match ) { row_matches = true; }
891
+ if ( names_match ) { this_row_matches_this_field = true; }
882
892
  }
883
893
  break;
884
894
  }
885
895
 
886
- if ( row_matches ) {
887
- matches.push( var_datum ); // Collect all matches to start with.
888
- if ( !uses_proxies ) { // If this gets set to `true` once, leave it alone
889
- // x, i, j, etc. See notes below about the reasoning
890
- // AVOID `.test()` - it advances through a string each time you call it on the
891
- // string, so you can have unexpected results.
892
- uses_proxies = guess.var.match( proxies_regex ); //
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
- continue;
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
- // Some fields will have multiple matches from the story table.
901
- // The only reason we would care about multiple story table row
902
- // matches is if there is a possible proxy var conflict.
903
- // When a dev has a loop with an index var or generic object,
904
- // the same variable name will be used over and over, e.g. `users[i]`.
905
- // That's what will be in the story table, so there will be multiple
906
- // matches for a field setting `users[i]`. For this program to
907
- // know which table row to use on which page, we need the one unique var that
908
- // will appear on the page - the trigger var. When a row with `users[i]`
909
- // has a trigger var that matches the page trigger var, the
910
- // program knows to use that row to set the value for that page.
911
-
912
- // If needed, only get the matches that also match the trigger var
913
- // For now, we're being flexible about it - if we don't think we
914
- // need that trigger var, we don't prevent data getting through.
915
- // e.g. If an interview has a list of `users`, but they only set
916
- // data for `users[0]`, there's no need to check the trigger var.
917
- // That way devs with simple cases can ignore the trigger var column.
918
-
919
- // If field uses proxies and there has been more than one match
920
- // we now use the trigger var to narrow down the matches.
921
- if ( uses_proxies && matches.length > 1 ) {
922
- // Multiple matches means this came from a story
923
-
924
- // Feedback for devs - we need the trigger var but we don't have it (field data)
925
- // TODO: Add docs and link to docs on how to add the trigger var
926
- if ( field.trigger === undefined ) { await scope.addToReport(scope, { type: `warning`, value: `A field on this page uses an index variable or a generic object, but the page does not contain an element with the trigger variable's name. Tell the developer to add an element to the page with the id "trigger"` }); }
927
-
928
- let original_matches = matches;
929
- matches = [];
930
- for ( let match of original_matches ) {
931
- // Feedback for devs - we need the trigger var but we don't have it (var data)
932
- if ( match.trigger === undefined ) { await scope.addToReport(scope, { type: `warning`, value: `A field on this page uses an index variable or a generic object. The test gave story data for setting the field, but that data is missing the trigger. Add a trigger variable to the story table \`trigger\` column. For now, the test may end up ignoring this field. The row's data is\n${ JSON.stringify( match.original )}.\nThe field's data is\n${ JSON.stringify( field )}` }); }
933
- // TODO: Add a warning if they're both `undefined`?
934
- else {
935
-
936
- let triggers_match = await scope.matchNeutralizedQuotes( scope, { value1: field.trigger, value2: match.trigger });
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
- matches.push( match );
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
- // Dev may have accidentally included a row more than once. Give them feedback
947
- if ( matches.length > 1 ) {
948
- await scope.addToReport(scope, { type: `warning`, value: `there are multiple story data matches for this field. The story table data that appears last will be used. The field's data is\n${JSON.stringify( field )}` });
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 ( env_vars.DEBUG ) { console.log( `matches:\n`, JSON.stringify( matches )); }
952
- return matches;
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 ( env_vars.DEBUG ) {
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
- // mutliple matches in the story that the last value will be used. In future,
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 ( env_vars.DEBUG ) {
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 ( env_vars.DEBUG ) {
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/${ env_vars.REPO_URL.match(/docassemble-(.*)/)[1] }/data/sources`;
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 santized version of the question id as it's written
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 ( env_vars.DEBUG ) { console.log( `~~~~~~~~~~~~~~ scope.setFields() ~~~~~~~~~~~~~~` ); }
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 permuation.
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 betweein ':' and 'none'
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 ( env_vars.DEBUG ) { await scope.page.waitForTimeout( 60 * 1000 ); }
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 any variable (with or without a choice associated with it) to a value. 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 navigate and if this variable
1720
- // cannot be seton this page, trigger an error
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