@suffolklitlab/alkiln 4.0.0-pre-api.6 → 4.0.0-pre-api.9

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 CHANGED
@@ -51,6 +51,7 @@ Format:
51
51
  - Add control of alkiln npm version to the action.yml
52
52
 
53
53
  ### Changed
54
+ - NPM package is now suffolklitlab scoped package. Refer to @suffolklitlab/alkiln from now on.
54
55
  - Update to v13 of puppeteer
55
56
  - Update to v7 of cucumber
56
57
  - Update action.yml node to v17
@@ -67,10 +68,12 @@ Format:
67
68
  - Peer dependencies and dev dependencies. Now `cucumber` is just a dependency. See https://github.com/SuffolkLITLab/ALKiln/issues/396 for discussion. Setup interview has not been updated to remove dependencies.
68
69
 
69
70
  ### Fixed
71
+ - Proxy var story table row matches for any page containing the given variable name when there's only one row for the relevant variable. #464.
70
72
  - Fixed interview name not allowing url parameters. #449
71
73
  - Freeze colors npm package to before bug in both package.json and action.yml
72
74
  - Fix invalid project name allowed.
73
75
  - fix typo in the report.
76
+ - session_vars branch path regex not removing start of branch path
74
77
 
75
78
  ## [3.0.4] - 2021-12-11
76
79
  ### Fixed
package/action.yml CHANGED
@@ -27,11 +27,12 @@ inputs:
27
27
  ALKILN_VERSION:
28
28
  description: 'Version of ALKiln to get from npm.'
29
29
  required: false
30
- default: '^4.0.0'
30
+ default: '4.0.0'
31
31
 
32
32
  runs:
33
33
  using: "composite"
34
34
  steps:
35
+
35
36
  # Set environment variables
36
37
  - name: "ALKiln: Set environment variables"
37
38
  # If extra languages were set manually, override the repository
@@ -52,8 +53,11 @@ runs:
52
53
  echo "PLAYGROUND_PASSWORD=${{ inputs.PLAYGROUND_PASSWORD }}" >> $GITHUB_ENV
53
54
  echo "PLAYGROUND_ID=${{ inputs.PLAYGROUND_ID }}" >> $GITHUB_ENV
54
55
  echo "DOCASSEMBLE_DEVELOPER_API_KEY=${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}" >> $GITHUB_ENV
55
- echo "ALKILN_VERSION=${{ inputs.ALKILN_VERSION }}" >> $GITHUB_ENV
56
56
  shell: bash
57
+ - name: "ALKiln: confirm info"
58
+ run: echo -e "\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nManually added languages are $EXTRA_LANGUAGES\nAll other data is secret\n"
59
+ shell: bash
60
+
57
61
  # This doesn't use an already established package-lock.json and that
58
62
  # comes with some types of test breaking risks. Same problem as before.
59
63
  - name: Overwrite or create package.json
@@ -62,7 +66,7 @@ runs:
62
66
  {
63
67
  "scripts": {
64
68
  "test": "npm run langs_setup && npm run cucumber -- $@",
65
- "cucumber_base": "./node_modules/.bin/cucumber-js --require ./node_modules/.bin/alkiln-cucumber",
69
+ "cucumber_base": "./node_modules/.bin/cucumber-js --require ./node_modules/@suffolklitlab/alkiln/lib/index.js",
66
70
  "cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature; }; run_cucumber",
67
71
  "langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./node_modules/.bin/alkiln-cucumber docassemble/*/data/sources/*_lang*.feature",
68
72
  "langs_setup": "node_modules/.bin/alkiln-langs 'docassemble/*/data/sources/*.feature'",
@@ -76,11 +80,8 @@ runs:
76
80
  }
77
81
  }
78
82
  shell: bash
79
- - name: "ALKiln: confirm info"
80
- run: echo -e "\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nManually added languages are $EXTRA_LANGUAGES\nAll other data is secret\n"
81
- shell: bash
82
83
 
83
- # Install node after package.json is created
84
+ # Install node
84
85
  - name: "ALKiln: Install node packages"
85
86
  uses: actions/setup-node@v1
86
87
  with:
@@ -124,7 +124,8 @@ da_i.get_dev_id = async function ( dev_id_options ) {
124
124
 
125
125
  // Default timeouts
126
126
  let { timeout } = dev_id_options || { timeout: 75 * 1000 };
127
- return await _da_REST.get_dev_id( timeout );
127
+ let response = await _da_REST.get_dev_id( timeout );
128
+ return response.data.id;
128
129
  }; // Ends da_i.get_dev_id();
129
130
 
130
131
 
@@ -0,0 +1,19 @@
1
+ let session_vars = require(`../utils/session_vars`);
2
+ let docassemble_api_interface = require('./docassemble_api_interface');
3
+
4
+ let get_base_interview_url = async function () {
5
+ /** Return the part of the interview URL that comes before the filename.
6
+ * Depends on the project name, which may get changed.
7
+ * This also needs to ensure that variable values from previous tests aren't used.
8
+ * That's not completely possible, but our tests try to do this in several ways.
9
+ * This function uses `new_session=1` to try to ensure old session vars aren't used.
10
+ * When finishing a test, we go to the sign out page (which is supposed to help even
11
+ * when the user is not logged in.) We can't use `reset=2` because it caused an
12
+ * 'Unable to locate interview session' error. */
13
+ let server_url = session_vars.get_da_server_url();
14
+ let dev_id = await docassemble_api_interface.get_dev_id();
15
+ let project_name = session_vars.get_project_name();
16
+ return `${ server_url }/interview?new_session=1&i=docassemble.playground${ dev_id }${ project_name }%3A`;
17
+ };
18
+
19
+ module.exports = get_base_interview_url;
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
 
@@ -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 = [];
@@ -397,13 +402,14 @@ 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
 
@@ -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
 
@@ -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 mutliple 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'; }
@@ -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
 
@@ -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' );
@@ -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
  }
@@ -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
 
package/lib/steps.js CHANGED
@@ -19,7 +19,7 @@ const { v4: uuidv4 } = require('uuid');
19
19
 
20
20
  // Ours
21
21
  const scope = require('./scope');
22
- const env_vars = require('./utils/env_vars');
22
+ const session_vars = require('./utils/session_vars');
23
23
 
24
24
  /* Of Note:
25
25
  - We're using `*=` because sometimes da text has funny characters in it that are hard to anticipate
@@ -717,7 +717,7 @@ When('I tap to continue', { timeout: -1 }, async () => {
717
717
  // --- VARIABLES-BASED STEPS ---
718
718
 
719
719
  When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async ( var_name, set_to ) => {
720
- /* Set any variable (with or without a choice associated with it) to a value. Set:
720
+ /* Set a non-story table variable (with or without a choice associated with it) to a value. Set:
721
721
  * - Buttons associated with variables
722
722
  * - Dropdowns
723
723
  * - Checkboxes (multiple choice checkboxes must be set to "true" or "false")
@@ -725,7 +725,7 @@ When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async (
725
725
  * - Text inputs
726
726
  * - Textareas
727
727
  *
728
- * Examples:
728
+ * Variations:
729
729
  * 1. I set the var "benefits['SSI']" to "false"
730
730
  * 1. I set the var "rent_interval" to "weekly"
731
731
  */
@@ -736,7 +736,7 @@ When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async (
736
736
  });
737
737
 
738
738
  Then(/I upload "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async ( filenames, var_name ) => {
739
- /* Uploads sibling file (docx, jpg, etc) to input var. Can actually use
739
+ /* Uploads file (docx, jpg, etc) in same folder to input var. Can actually use
740
740
  * 'I set the var "" to ""', but this is more explicit if it's desired. */
741
741
  return wrapPromiseWithTimeout(
742
742
  scope.steps.set_regular_var( scope, var_name, filenames ),
@@ -867,9 +867,9 @@ After(async function(scenario) {
867
867
  await scope.setReportScenarioStatus( scope, { status: scenario.result.status });
868
868
 
869
869
  if (scenario.result.status !== `PASSED`) {
870
- if ( env_vars.DEBUG ) {
870
+ if ( session_vars.get_debug() ) {
871
871
  console.log( `Non-passed status occurred while running test:`, scenario.result.status );
872
- // If there's a page visible, give us time to examine it
872
+ // If there's a page visible (locally), give us time to examine it
873
873
  if ( scope.page ) { await scope.page.waitForTimeout( 60 * 1000 ); }
874
874
  }
875
875
 
@@ -909,15 +909,15 @@ After(async function(scenario) {
909
909
  // Consider, instead, catching the reload error for a bad restart and just
910
910
  // re-trying at that point.
911
911
  try {
912
- await scope.page.goto(`${ env_vars.BASE_URL }/user/sign-out`, {waitUntil: `domcontentloaded`});
912
+ await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
913
913
  } catch ( err ) {
914
- if ( env_vars.DEBUG ) {
914
+ if ( session_vars.DEBUG ) {
915
915
  console.log( `Trying to reset for the next test a second time because the first time failed.` );
916
916
  }
917
917
 
918
918
  try {
919
- await scope.page.goto(`${ env_vars.BASE_URL }/user/sign-out`, {waitUntil: `domcontentloaded`});
920
-
919
+ await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
920
+
921
921
  } catch ( err ) {
922
922
  await scope.addToReport(scope, {
923
923
  type: `warning`,
@@ -4,7 +4,7 @@ let _path = require('path');
4
4
  let fs = require('fs');
5
5
  let glob = require('glob');
6
6
 
7
- let env_vars = require('./env_vars');
7
+ let session_vars = require('./session_vars');
8
8
 
9
9
  // Get the glob filepaths to find all the test files
10
10
  let dir_root = process.env.INIT_CWD; // Path to root of project
@@ -12,7 +12,7 @@ let test_glob = process.argv[2]; // String of glob (regex for filepaths)
12
12
  let filepaths = glob.sync( _path.resolve( dir_root, test_glob ) );
13
13
  let flag = '_lang_';
14
14
 
15
- let langs = env_vars.EXTRA_LANGUAGES;
15
+ let langs = session_vars.get_languages();
16
16
 
17
17
  // Generate new language files
18
18
  for ( let full_path of filepaths ) { // Handle old language files before creating new ones.