@suffolklitlab/alkiln 5.14.0 → 5.15.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.
Files changed (35) hide show
  1. package/.github/workflows/github_server.yml +1 -1
  2. package/.github/workflows/playground.yml +2 -1
  3. package/CHANGELOG.md +29 -0
  4. package/action.yml +26 -10
  5. package/action_for_github_server/action.yml +31 -27
  6. package/docs/4_environments.md +2 -2
  7. package/docs/artifacts_folder_name.md +121 -71
  8. package/lib/docassemble/server_install.js +0 -2
  9. package/lib/docassemble/setup.js +1 -3
  10. package/lib/docassemble/takedown.js +2 -2
  11. package/lib/globals.js +10 -0
  12. package/lib/run_cucumber.js +40 -53
  13. package/lib/scope.js +32 -0
  14. package/lib/setup/TestGenerator.js +1757 -0
  15. package/lib/setup/artifacts.js +20 -0
  16. package/lib/setup/generate.js +42 -0
  17. package/lib/setup/get_Gherkin_AST.js +26 -0
  18. package/lib/setup/validate.js +89 -0
  19. package/lib/steps.js +53 -19
  20. package/lib/utils/Log.js +302 -149
  21. package/lib/utils/get_error_stacks.js +16 -0
  22. package/lib/utils/reports.js +43 -23
  23. package/lib/utils/session_vars.js +77 -57
  24. package/lib/utils/set_sources_paths.js +98 -0
  25. package/package.json +18 -7
  26. package/tests/log_codes/check_codes.sh +17 -13
  27. package/tests/log_codes/log_code_expected_instances.txt +58 -0
  28. package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +178 -0
  29. package/tests/unit_tests/constrained_random_answers/invalid.test.js +251 -0
  30. package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +957 -0
  31. package/tests/unit_tests/constrained_random_answers/valid.test.js +563 -0
  32. package/tests/unit_tests/log.test.js +17 -41
  33. package/_alkiln_meta/gherkin_parsed_example.json +0 -1546
  34. package/_alkiln_meta/shift_logs.js +0 -75
  35. /package/docs/{expected_inernal_results.md → expected_internal_results.md} +0 -0
@@ -56,8 +56,6 @@ const server_install = async () => {
56
56
  session_vars.validateEnvironment();
57
57
 
58
58
  // 2. Prepare logs
59
- // Local devs: This will be separate from your test paths
60
- // unless you pass a `path` argument into the cucumber run command
61
59
  const argv = require(`minimist`)(process.argv.slice(2));
62
60
  const log = new Log({ path: argv.path, context, });
63
61
 
@@ -54,7 +54,7 @@ const setup = async () => {
54
54
  + `dainstall this package on your server. See above for more details.`;
55
55
  log.throw({ code: `ALK0047`, context,
56
56
  before: `☝️ ☝️ ☝️ Error data ☝️ ☝️ ☝️\n`,
57
- error: `Error while installing project` },
57
+ error: new Error(`Error while installing project on your docassemble server during GitHub+You™ tests`) },
58
58
  install_err_msg
59
59
  );
60
60
  }
@@ -68,8 +68,6 @@ const setup = async () => {
68
68
  session_vars.validateEnvironment();
69
69
 
70
70
  // 2. Prepare logs
71
- // Local devs: This will be separate from your test paths
72
- // unless you pass a `path` argument into the cucumber run command
73
71
  const argv = require(`minimist`)(process.argv.slice(2));
74
72
  const log = new Log({ path: argv.path, context, });
75
73
 
@@ -40,8 +40,8 @@ const takedown = async () => {
40
40
  session_vars.validateEnvironment();
41
41
 
42
42
  // 2. Prepare logs
43
- // Local devs: This will be separate from your test artifacts paths
44
- // unless you pass a `path` argument into the `cucumber` run command
43
+ // Local devs: This will be separate from your test artifacts paths unless you
44
+ // pass a `path` argument into the `cucumber` run command
45
45
  const argv = require(`minimist`)( process.argv.slice(2) );
46
46
  const log = new Log({ path: argv.path, context, });
47
47
 
package/lib/globals.js ADDED
@@ -0,0 +1,10 @@
1
+ let globals = {
2
+ // File name prefix ALKiln can use to identify random answer generator files
3
+ generators_prefix: `alkiln_random`,
4
+ generated_files_folder_name: `_alkiln_generated`,
5
+ }
6
+
7
+ module.exports = globals;
8
+
9
+ // Discuss: Would this be clearer as a config file instead? If not, change name
10
+ // to `constants`?
@@ -1,21 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
+ const fast_glob = require(`fast-glob`);
4
5
  const path = require(`path`);
5
6
  const cucumber_api = require('@cucumber/cucumber/api');
6
7
 
8
+ const globals = require(`./globals`);
7
9
  const Log = require('./utils/Log');
8
10
  const session_vars = require('./utils/session_vars');
11
+ const set_sources_paths = require(`./utils/set_sources_paths`);
9
12
 
10
13
  const SUMMARY_FILENAME = `cucumber-report.txt`;
11
14
 
12
15
 
13
- function build_config( command_args ) {
16
+ function build_config() {
17
+ /** Use the command line arguments to build the cucumberjs config object.
18
+ *
19
+ * @param {string?} - Arguments passed to the command line.
20
+ *
21
+ * @returns {object} - cucumberjs config object
22
+ * */
14
23
  // Turn all the paths to the sources folders into paths to the feature files
15
24
  let sources_paths = session_vars.get_sources_paths();
16
25
  let feature_paths = [];
17
- for ( let source of sources_paths ) {
18
- feature_paths.push( `${ source }/*.feature` );
26
+
27
+ // Get all the paths for the right feature files (for cucumber to use)
28
+ for ( let sources_path of sources_paths ) {
29
+ let glob = fast_glob.sync(
30
+ `${ sources_path }/**/*.feature`,
31
+ // Ignore "generator" feature files that we used to generate actual tests
32
+ { ignore: [`${ sources_path }/${ globals.generators_prefix }*.feature`] }
33
+ );
34
+ feature_paths.push( ...glob );
19
35
  }
20
36
 
21
37
  const config = {
@@ -26,9 +42,10 @@ function build_config( command_args ) {
26
42
  retry: 1,
27
43
  }
28
44
 
45
+ const cli_args = require(`minimist`)( process.argv.slice(2) );
29
46
  // Normalize tag expressions, if there are any
30
47
  // Joining and splitting handles both tags surrounded with `""` or those without `""`
31
- let tags = command_args[`_`].join(` `).split(` `);
48
+ let tags = cli_args[`_`].join(` `).split(` `);
32
49
  // If there was no tag expression, `tags` is ['']. Remove that item.
33
50
  tags = tags.filter(function(entry) { return entry !== ""; })
34
51
  if ( tags.length > 0 ) {
@@ -56,46 +73,12 @@ function build_config( command_args ) {
56
73
 
57
74
  async function main() {
58
75
  /** Create the cucumber config, run the tests, create and
59
- * manipluate the output. */
76
+ * manipulate the output. */
60
77
 
61
78
  const environment = { cwd: process.cwd() };
62
79
  log.info({ code: `ALK0049`, context: `pre_run environment` },
63
80
  environment
64
81
  );
65
-
66
- // We'll use the user info to construct the possible paths to /sources
67
- let id = session_vars.get_user_id();
68
- // `project` is the name in the server docassemble "Project" page
69
- let project = session_vars.get_user_project_name();
70
-
71
- // process.argv is a list of strings of commands and args.
72
- // If using `npm run cucumber` (as opposed to the bin commands),
73
- // use `--` before `--sources=./foo`
74
- const argv = require(`minimist`)( process.argv.slice(2), {
75
- default: {
76
- sources: [
77
- `./docassemble/*/data/sources`, // In GitHub folder
78
- `/usr/share/docassemble/files/playgroundsources/${id}/${project}/*.feature`, // From playground without S3
79
- `/tmp/playgroundsources/${id}/${project}/*.feature` // From playground with S3
80
- ],
81
- }
82
- });
83
-
84
- // Make sure `sources` is always a list
85
- let sources_list = argv.sources;
86
- if ( typeof argv.sources === `string` ) {
87
- sources_list = [ argv.sources ];
88
- }
89
-
90
- let one_dir_exists = session_vars.set_sources_paths( sources_list );
91
- if ( !one_dir_exists ) {
92
- // If we don't throw, it'll be more confusing when no tests run
93
- log.throw({ code: `ALK0050`, context: `pre_run`,
94
- error: new ReferenceError(
95
- `ALKiln can't find your "sources" folder(s). See above for details.`
96
- ),
97
- });
98
- }
99
82
 
100
83
  const { runConfiguration } = await cucumber_api.loadConfiguration({
101
84
  provided: build_config( argv ),
@@ -122,9 +105,9 @@ async function main() {
122
105
 
123
106
  /** Write to files.
124
107
  *
125
- * Note: Don't add to the purely report-focused files. Those are files where
126
- * authors can focus on the info we give them, which they often miss in
127
- * the GitHub action.
108
+ * Note: Avoid adding to the purely report-focused files. Those are files
109
+ * where authors can focus on the info we give them, which they often miss
110
+ * in the GitHub action.
128
111
  */
129
112
 
130
113
  if ( success ) {
@@ -134,7 +117,7 @@ async function main() {
134
117
  } else {
135
118
  // Don't want to throw error here, so don't pass error object
136
119
  log.warn({ code: `ALK0055`, context: `post_run`, },
137
- `The tests ran into something unexpected. Look above for more details.`
120
+ `The tests ran into something unexpected. See warnings and errors above.`
138
121
  );
139
122
  }
140
123
  // Custom exit code. May overlap with other systems.
@@ -142,14 +125,12 @@ async function main() {
142
125
  const exitCode = success ? 0 : 50;
143
126
  process.exitCode = exitCode;
144
127
 
145
- // This is hardcoded information from `provide_config` above.
146
- // This code isn't used for anything other than detecting and removing color codes.
147
128
  let summary_file_name = SUMMARY_FILENAME;
148
129
  fs.readFile(summary_file_name, 'utf8', (read_error, text) => {
149
130
  if ( read_error ) {
150
131
  // Something else will probably show an error that's more relevant
151
132
  log.warn({ code: `ALK0056`, context: `post_run error`, },
152
- `Failed to read cucumber report`, read_error
133
+ `Failed to read the report of the 3rd party library, cucumberjs. There may be more information above or below.`, read_error
153
134
  );
154
135
  return;
155
136
  }
@@ -184,7 +165,13 @@ async function main() {
184
165
  try {
185
166
  fs.readFileSync(`${ log.path }/${ log.unexpected_filename }`); // Does it have contents?
186
167
  fs.appendFileSync(`${ log.path }/${ log.unexpected_filename }`, `\n\n\n\n\n` );
187
- } catch ( ignore_err ) { /* Missing unexpected file is ok */ }
168
+ } catch ( ignore_err ) {
169
+ /* Missing "unexpected results" file is ok */
170
+ }
171
+
172
+ // TODO: Is this meant to write to the debug file or is this debug info
173
+ // really supposed to be in the "unexpected results" file? Or are these
174
+ // comments old?
188
175
 
189
176
  // Add the heading for debug content
190
177
  fs.appendFileSync( `${ log.path }/${ log.unexpected_filename }`,
@@ -198,8 +185,8 @@ async function main() {
198
185
 
199
186
  } catch ( writing_cucumber_error ) {
200
187
  let code = `ALK0182`;
201
- let context = `post_run error`
202
- let msg = `Something probably went wrong earlier. Look above for more details. Skipped writing to ${ log.path }/${ log.debug_log_filename }.`;
188
+ let context = `post_run`
189
+ let msg = `Something probably went wrong earlier. See warnings and errors above. Skipped writing to ${ log.path }/${ log.debug_log_filename }.`;
203
190
  try {
204
191
  log.warn({ code, context, }, msg, writing_cucumber_error );
205
192
  } catch ( all_writing_error ) {
@@ -223,10 +210,10 @@ session_vars.validateEnvironment();
223
210
  // If using `npm run cucumber` (as opposed to the bin commands),
224
211
  // use `--` before `--sources=./foo`
225
212
  const argv = require(`minimist`)( process.argv.slice(2) );
226
- // The artifacts folder might be getting created for the first
227
- // time right here. The tests themselves should avoid creating
228
- // their own file.
229
213
  const log = new Log({ path: argv.path, context: `run` });
230
214
 
231
- // 3. run
215
+ // 3. Prep the sources paths to find .feature files
216
+ let sources_paths = set_sources_paths({ log });
217
+
218
+ // 4. run
232
219
  main();
package/lib/scope.js CHANGED
@@ -2129,6 +2129,38 @@ module.exports = {
2129
2129
  // await scope.page.mouse.move(1, 1); // Too big? Wait a while before drawing?
2130
2130
  await scope.page.mouse.up();
2131
2131
  },
2132
+
2133
+ get_page_id: async function ( scope ) {
2134
+ /** Return the current page's docassemble `question` block ID or `null`
2135
+ *
2136
+ * Note: The ID is already sanitized by docassemble.
2137
+ *
2138
+ * @param {object} scope - Tests state object
2139
+ * @param {object} scope.page - puppeteerjs page object
2140
+ *
2141
+ * @returns {null | string}
2142
+ * */
2143
+ let question_id_match = await scope.page.$eval(`body`, function (elem) {
2144
+ return elem.className.match(/question-([^ ]+)/);
2145
+ });
2146
+ if ( question_id_match !== null ) {
2147
+ return question_id_match[ 1 ];
2148
+ }
2149
+ return null;
2150
+ },
2151
+
2152
+ to_docassemble_sanitized_id: function ( risky_id ) {
2153
+ /** Return a version of `risky_id` that's sanitized the same way docassemble
2154
+ * sanitizes its IDs to turn them into HTML class names in python:
2155
+ * re.sub(r'[^A-Za-z0-9]+', '-', interview_status.question.id.lower())
2156
+ * (reference needed)
2157
+ *
2158
+ * @param {string} risky_id - The un-sanitized id
2159
+ *
2160
+ * @returns {string} - Sanitized id
2161
+ * */
2162
+ return risky_id.toLowerCase().replace(/[^A-Za-z0-9]+/g, '-');
2163
+ },
2132
2164
 
2133
2165
  examinePageID: async function ( scope, question_id = '' ) {
2134
2166
  /* Looks for a sanitized version of the question id as it's written