@suffolklitlab/alkiln 5.15.0-feat-random-9 → 5.15.1-fix-race-condition

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.
@@ -160,7 +160,7 @@ jobs:
160
160
  #### Developer note: You can probably leave the rest out
161
161
  #### To learn more, see https://assemblyline.suffolklitlab.org/docs/alkiln/writing/#optional-inputs
162
162
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
163
- # ALKILN_VERSION:
163
+ ALKILN_VERSION: "@race"
164
164
 
165
165
  #### Developer note: Example of making an issue when tests fail
166
166
  #### that includes the text of the failure output file
@@ -82,4 +82,4 @@ jobs:
82
82
  # want to check up on this.
83
83
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
84
84
  #### Developer note: You can probably leave this out
85
- # ALKILN_VERSION:
85
+ ALKILN_VERSION: "@race"
package/CHANGELOG.md CHANGED
@@ -18,49 +18,36 @@ Security - in case of vulnerabilities.
18
18
  Format:
19
19
 
20
20
  ## [Unreleased]
21
-
22
21
  -
23
22
 
24
23
  ## [1.0.0] - 2021-01-16
25
24
  ### Added
26
-
27
25
  -
28
26
 
29
27
  ### Changed
30
-
31
28
  -
32
29
 
33
30
  ### Deprecated
34
-
35
31
  -
36
32
 
37
33
  ### Removed
38
-
39
34
  -
40
35
 
41
36
  ### Fixed
42
-
43
37
  -
44
38
 
45
39
  ### Security
46
-
47
40
  -
48
41
 
49
42
  ### Internal
50
-
51
43
  -
52
44
  -->
53
45
 
54
46
  ## [Unreleased]
55
47
 
56
- ### Added
57
-
58
- - "Breaking news" feature for ALKilnInThePlayground - a way new ALKiln versions can talk directly with authors even if ALKilnInThePlayground's version stays the same. ALKilnInThePlayground will be able to get text from ALKiln that ALKilnInThePlayground can turn into Mako and then insert on a screen the author can see.
59
-
60
- ### Changed
61
-
62
- - Implement constrained random answers Step for ALKilnInThePlayground.
48
+ ### Fixed
63
49
 
50
+ - Fixed race condition between navigating and extracting button text.
64
51
 
65
52
  ## [5.15.1] - 2025-08-05
66
53
 
@@ -111,8 +111,8 @@ async function main() {
111
111
  */
112
112
 
113
113
  if ( success ) {
114
- log.info({ code: `ALK0054`, context: `post_run`, },
115
- `Test process exited without error.`
114
+ log.success({ code: `ALK0054`, context: `post_run`, },
115
+ `Tests succeeded!`
116
116
  );
117
117
  } else {
118
118
  // Don't want to throw error here, so don't pass error object
package/lib/scope.js CHANGED
@@ -7,6 +7,7 @@ const { AxePuppeteer } = require('@axe-core/puppeteer');
7
7
  const safe_filename = require("sanitize-filename");
8
8
  // "en" is faster to import: https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
9
9
  const faker = require('@faker-js/faker/locale/en').faker;
10
+ const { v4: uuidv4 } = require('uuid');
10
11
 
11
12
  // Ours
12
13
  const pdf_reader = require('./utils/pdf_reader');
@@ -254,23 +255,21 @@ module.exports = {
254
255
  if ( options.waitForTimeout ) { await time.waitForTimeout( options.waitForTimeout ); }
255
256
  }, // Ends afterStep
256
257
 
257
- tapElementAndNavigate: async function(scope, { elem, waitForTimeout=0 }) {
258
- /** Tap a given element, detect navigation, and do appropriate related things.
259
- * TODO: not quite sure if we'll be using `waitForTimeout`. */
258
+ tapElementAndNavigate: async function(scope, { elem, waitForTimeout=null }) {
259
+ /** Tap a given element, detect navigation, and do appropriate related things. */
260
260
 
261
261
  let start_url = await scope.page.url(); // so we can later check for navigation
262
262
 
263
263
  await scope.tapElementAndStayOnPage(scope, {
264
264
  elem,
265
- click_promise: Promise.all([
266
- scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
267
- elem.evaluate( (el) => { return el.click(); })
268
- ])
265
+ promise_returners: [
266
+ () => { return scope.nav_race(scope, {}).result; },
267
+ () => { return elem.evaluate( (el) => { return el.click(); }) },
268
+ ]
269
269
  });
270
270
 
271
- // TODO: Not sure how to detect navigation landing at same page, such as
272
- // with a user input validation error. Does that count as navigating?
273
- // Maybe url change + trigger var change? Maybe just trigger var?
271
+ // For other ideas for detecting navigation, see
272
+ // https://github.com/SuffolkLITLab/ALKiln/issues/229
274
273
  let end_url = await scope.page.url();
275
274
  let navigated = start_url !== end_url;
276
275
 
@@ -282,36 +281,77 @@ module.exports = {
282
281
  let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
283
282
 
284
283
  // If navigation, wait for 200ms to let things finish loading and moving
285
- await scope.afterStep(scope, { waitForTimeout: 200, navigated: true });
284
+ await scope.afterStep(scope, {
285
+ waitForTimeout: ( waitForTimeout || 200 ),
286
+ navigated: true
287
+ });
286
288
  } else {
287
- await scope.afterStep(scope, { waitForTimeout, navigated: false });
289
+ // Discuss: Fail here or later since the test didn't manage to navigate?
290
+ await scope.afterStep(scope, {
291
+ waitForTimeout: ( waitForTimeout || 0 ),
292
+ navigated: false
293
+ });
288
294
  }
289
295
  }, // Ends scope.tapElementAndNavigate()
290
296
 
291
- tapElementAndStayOnPage: async function(scope, { elem, click_promise }) {
297
+ nav_race: function(scope, opts={}) {
298
+ /**
299
+ * Return a Promise for a race that detects which, if any, navigation result
300
+ * a navigation had.
301
+ *
302
+ * Warning: Unable to effectively add event listener for `daPageLoad`. Gets
303
+ * overwritten on every page load.
304
+ *
305
+ * @returns
306
+ * { obj }
307
+ * ok { bool } - True if no unexpected errors
308
+ * messages {[ str ]} - Any error or info messages
309
+ * errors {[ Error ]} - Any Errors that happened
310
+ * result { Promise } with result of { obj } - HTTP response or other data
311
+ * aborters {[ obj ]} - Abort signals for all the Promises involved
312
+ * */
313
+ let ok, messages, errors;
314
+ let aborters = [];
315
+
316
+ let result = Promise.race([
317
+ scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
318
+ ]);
319
+
320
+ return { result, aborters, ok, messages, errors, };
321
+ }, // Ends scope.nav_race()
322
+
323
+ tapElementAndStayOnPage: async function(scope, { elem, promise_returners=null }) {
292
324
  /** Tap a given element, detect navigation, and do appropriate related things.
293
- * `click_promise can be any kind of Promise, including a Promise.all()
325
+ *
326
+ * @param elem { obj } - element to tap
327
+ * @param promise_returners {[ func -> Promise ]} - list of functions that
328
+ * return promises
294
329
  */
295
330
  await scope.guard_against_missing_tap_element(scope, { elem });
296
331
 
297
332
  // Get the text on the button or link for a potential error message later
298
333
  let tapperText = await (await elem.getProperty('textContent')).jsonValue();
299
334
 
300
- click_promise = click_promise || elem.click();
335
+ let click_promises = null;
336
+ if ( promise_returners ) {
337
+ click_promises = Promise.all(promise_returners.map( pr_ret => pr_ret() ));
338
+ } else {
339
+ click_promises = elem.click();
340
+ }
301
341
 
302
342
  // TODO: implement and use scope.handle_possible_timeout()
303
343
  try {
304
- // Serever error loads page, so keeping things shorter by not looking
344
+ // Server error loads page, so keeping things shorter by not looking
305
345
  // for that text too. Also, puppeteer will ensure proper timeout.
306
346
  await Promise.race([
307
- click_promise,
347
+ click_promises,
308
348
  // This is meant to detect user-visible alerts/errors,
309
349
  // not breaking errors in the testing code.
350
+ // No need for aborters since puppeteer has its own timeout
310
351
  scope.page.waitForSelector('.alert-danger', { visible: true }),
311
352
  scope.page.waitForSelector('.da-has-error', { visible: true }),
312
353
  ]);
313
354
  } catch ( error ) {
314
-
315
355
  if ( error.name === `TimeoutError` ) {
316
356
  let non_reload_report_msg = `After ALKiln tapped "${ tapperText }" it took too long to load the next page.`;
317
357
  await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
@@ -2729,7 +2769,7 @@ module.exports = {
2729
2769
  pic_name = `pic_on-${ short_id }-${ timestamp }`;
2730
2770
  }
2731
2771
 
2732
- await scope.take_a_screenshot( scope, { path: `${ scope.paths.scenario }/${ pic_name }.jpg` });
2772
+ await scope.take_a_screenshot( scope, { path: `./${ scope.paths.scenario }/${ pic_name }.jpg` });
2733
2773
 
2734
2774
  await scope.afterStep( scope );
2735
2775
  }, // Ends scope.steps.screenshot()
@@ -15,13 +15,11 @@ const get_error_stacks = require(`../utils/get_error_stacks`);
15
15
  * file incorrectly.
16
16
  * Discuss: Require author to use "Template Scenario:" as they keyword (Also
17
17
  * "Template Scenario Outline:") (or "Scenario Template", maybe harder to
18
- * scan) for 2 reasons. 1. Gets the vocab of "generator" in early (though
18
+ * scan) for 2 reasons. 1. Gets the vocab of "template" in early (though
19
19
  * that doesn't match "constraints" Step, which is a whole other language
20
- * problem we haven't solved yet.) 2. Saying
21
- * "ALKILN_MAX_RANDOM_TESTS_PER_SCENARIO" is confusing when "Scenario" means
22
- * different things - a generator Scenario and a regular Scenario. Also,
23
- * "ALKILN_MAX_RANDOM**IZED**_TESTS_PER_SCENARIO" might be more accurate. Is
24
- * "ALKILN_MAX_TESTS_PER_GENERATOR" better?
20
+ * problem we haven't solved yet.) 2. Saying "ALKILN_MAX_RANDOM_TESTS_PER_SCENARIO"
21
+ * is confusing when "Scenario" means different things - a template
22
+ * Scenario and a regular Scenario.
25
23
  * Discuss: Now that Log is less fragile and duplicated, shall we create a new
26
24
  * Log instance in here?
27
25
  * Discuss: consider debug types that are for authors. Sort of like warnings,
@@ -103,13 +101,13 @@ module.exports = class TestGenerator {
103
101
  }
104
102
  }
105
103
 
106
- log.debug({ code: `ALK0267`, context: `generating files`, }, `Constrained random answers tests:\n`, new_files_strings );
104
+ log.debug({ code: `ALK0267`, context: `generating files`, }, `Constrained random answers tests:`, new_files_strings );
107
105
 
108
106
  let tests_path = `${ sources_path }/${ FOLDER_NAME }`;
109
107
  let artifacts_path = `${ session_vars.get_artifacts_path_name() }/${ FOLDER_NAME }`;
110
108
  save_files({ tests_path, artifacts_path, file_strings: new_files_strings });
111
109
 
112
- log.debug({ code: `ALK0271`, context: `generating files`, }, `Tried to save generated files to ${ artifacts_path }` );
110
+ log.info({ code: `ALK0271`, context: `generating files`, }, `Tried to save generated files to ${ artifacts_path }` );
113
111
 
114
112
  return { new_strings: new_files_strings, errors: all_errors };
115
113
  }
@@ -181,7 +179,7 @@ module.exports = class TestGenerator {
181
179
  let scenario_objs = doc_AST.feature.children;
182
180
  if ( !scenario_objs[0] ) {
183
181
  let syntax_error = log.error({ code: `ALK0235`, context: `generating files`, before: `\n\n`, },
184
- `It looks like the author's generator .feature file at "${ generator_path }" has 0 Scenarios.`
182
+ `It looks like the author's template .feature file at "${ generator_path }" has 0 Scenarios.`
185
183
  );
186
184
  return { new_contents: null, errors: [new Error( syntax_error )]};
187
185
  }
@@ -189,7 +187,7 @@ module.exports = class TestGenerator {
189
187
 
190
188
  if ( scenario_objs.length > 1 ) {
191
189
  let too_many_scenarios = log.error({ code: `ALK0236`, context: `generating files`, },
192
- `A generator file can only have 1 generator Scenario. This one has ${ scenario_objs.length }.`
190
+ `A template file can only have 1 template Scenario. This one has ${ scenario_objs.length }.`
193
191
  );
194
192
  return { new_contents: ``, errors: [new Error( too_many_scenarios )] };
195
193
  }
@@ -200,14 +198,14 @@ module.exports = class TestGenerator {
200
198
 
201
199
  let feature_text = get_Feature_text({ feature: doc_AST.feature, generator_lines });
202
200
 
203
- // One string for each generator Scenario
204
- let { scenarios_strings, total_Scenarios_made, errors: scenarios_errors } = this.get_new_Scenarios_strs({
201
+ let { scenarios_strings, errors: scenarios_errors } = this.get_new_Scenarios_strs({
205
202
  scenario_objs,
206
203
  generator_lines
207
204
  });
208
205
  generation_errors.push( ...scenarios_errors );
209
206
 
210
- let count_msg = `ALKiln generated ${ total_Scenarios_made } Scenario or Scenarios`;
207
+
208
+ let count_msg = `ALKiln generated ${ scenarios_strings.length } Scenario or Scenarios`;
211
209
 
212
210
  if ( scenarios_strings.length === 0 ) {
213
211
  let error = log.error({ code: `ALK0263`, context: `generating files`, }, count_msg );
@@ -233,7 +231,7 @@ module.exports = class TestGenerator {
233
231
 
234
232
  get_new_Scenarios_strs({ scenario_objs, generator_lines }) {
235
233
  /** Return all the strings of the generated Scenarios and any Errors we ran
236
- * into. Each generator Scenario gets one string of all its Scenarios.
234
+ * into.
237
235
  *
238
236
  * @param {object} obj - Named arguments
239
237
  * @param {object} obj.scenario_objs - 0 or more Gherkin Scenario objects
@@ -254,8 +252,7 @@ module.exports = class TestGenerator {
254
252
  let errs_n_warns = new Misbehaviors();
255
253
 
256
254
  let strs_for_all_new_Scenarios = [];
257
- let total_Scenarios_made = 0;
258
- // Soon we want to be able to handle multiple generator Scenarios per file
255
+ // Soon we want to be able to handle multiple template Scenarios per file
259
256
  for ( let scenario_obj_i = 0; scenario_obj_i < scenario_objs.length; scenario_obj_i++ ) {
260
257
 
261
258
  errs_n_warns.reset_warnings();
@@ -275,7 +272,7 @@ module.exports = class TestGenerator {
275
272
  );
276
273
  if ( errs_n_warns.errors.length > 0 ) { continue; }
277
274
 
278
- let { new_Scenarios_text, num_made } = errs_n_warns.add(
275
+ let { new_Scenarios_text } = errs_n_warns.add(
279
276
  this.get_1_text_block_of_all_new_Scenarios_from_1_generator_Scenario({
280
277
  scenario: generator_Scenario,
281
278
  generator_lines: generator_lines,
@@ -284,7 +281,6 @@ module.exports = class TestGenerator {
284
281
  })
285
282
  );
286
283
  if ( errs_n_warns.errors.length > 0 ) { continue; }
287
- total_Scenarios_made += num_made;
288
284
 
289
285
  if ( errs_n_warns.warnings.length > 0 ) {
290
286
  new_Scenarios_text = add_warnings_Scenario({
@@ -296,7 +292,7 @@ module.exports = class TestGenerator {
296
292
  strs_for_all_new_Scenarios.push( new_Scenarios_text );
297
293
  } // ends for every scenario_container
298
294
 
299
- return { scenarios_strings: strs_for_all_new_Scenarios, total_Scenarios_made, ...errs_n_warns };
295
+ return { scenarios_strings: strs_for_all_new_Scenarios, ...errs_n_warns };
300
296
  }
301
297
 
302
298
 
@@ -376,32 +372,22 @@ module.exports = class TestGenerator {
376
372
 
377
373
  let got_fewer_tests_than_desired = new_test_bodies.length < num_to_make;
378
374
  if ( got_fewer_tests_than_desired ) {
379
- let fewer_test_msg = `ALKiln tried a bunch of times, but it could only`
380
- + ` make ${ new_test_bodies.length } *unique* tests out of the`
381
- + ` ${ num_to_make } the author wanted for this Scenario. This probably`
382
- + ` happened because ALKiln makes sure every test is unique^1. Maybe`
383
- + ` there were too few combinations for ALKiln to use. The Scenario:`
384
- + `\n ${ scenario_description_for_logs({ scenario }) }`
385
- + `\n\n[^1]: "Unique tests" are only unique from the other tests ALKiln`
386
- + ` is generating *right now*. ALKiln doesn't check your regular tests.`;
387
-
388
- let warn_too_few = log.warn({ code: `ALK0260`, context: `generating files generator-scenario-level`, },
389
- fewer_test_msg,
375
+ let warn_too_few = log.warn({ code: `ALK0260`, context: `generating files template-scenario-level`, },
376
+ `ALKiln could only make ${ new_test_bodies.length } *unique* tests out of the ${ num_to_make } the author wanted for this Scenario. Note that ALKiln compares all generated Scenarios with each other to make sure all are unique. This Scenario may be a duplicate of a Scenario generated from a previous template. The Scenario:\n ${ scenario_description_for_logs({ scenario }) }`,
390
377
  )
391
378
  // Discuss: Should we add more structure to Misbehaviors to give them
392
- // context levels (Feature, generator Scenario, generated Scenario)
379
+ // context levels (Feature, template Scenario, generated Scenario)
393
380
  misfits.add({ warnings: [ warn_too_few ] });
394
381
  }
395
382
 
396
383
  let new_Scenarios_text = new_test_bodies.join(`\n\n`);
397
- let num_made = new_test_bodies.length;
398
384
  new_Scenarios_text = add_total_tests({
399
- scenario_group_str: new_Scenarios_text, num_tests: num_made
385
+ scenario_group_str: new_Scenarios_text, num_tests: new_test_bodies.length
400
386
  });
401
387
 
402
388
  log.debug({ code: `ALK0261`, context: `generating files`, }, `new_Scenarios_text before adding warnings:\n`, new_Scenarios_text );
403
389
 
404
- return { new_Scenarios_text, num_made, ...misfits };
390
+ return { new_Scenarios_text, ...misfits };
405
391
  }
406
392
 
407
393
  try_to_get_one_unique_middle({
@@ -509,7 +495,7 @@ function get_generator_AST({ file_text, generator_path }) {
509
495
 
510
496
  if ( generator_errors.length > 0 ) {
511
497
  let syntax_error = log.error({ code: `ALK0232`, context: `generating files`, before: `\n\n`, },
512
- `The author's generator .feature file at "${ generator_path }" has Gherkin syntax error(s). You might see more warnings above or below this one.\n`,
498
+ `The author's template .feature file at "${ generator_path }" has Gherkin syntax error(s). You might see more warnings above or below this one.\n`,
513
499
  ...get_error_stacks({ errors: generator_errors })
514
500
  );
515
501
  return { doc_AST: null, errors: [new Error( syntax_error )] };
@@ -519,14 +505,14 @@ function get_generator_AST({ file_text, generator_path }) {
519
505
 
520
506
  if ( !doc_AST.feature ) {
521
507
  let feature_error = log.error({ code: `ALK0233`, context: `generating files`, before: `\n\n`, },
522
- `The author's generator .feature file at "${ generator_path }" needs to include the \`Feature\` keyword.`
508
+ `The author's template .feature file at "${ generator_path }" needs to include the \`Feature\` keyword.`
523
509
  );
524
510
  return { doc_AST: null, errors: [new Error( feature_error )] };
525
511
  }
526
512
 
527
513
  // Should not get here
528
514
  let unknown_error = log.error({ code: `ALK0234`, context: `generating files`, before: `\n\n`, },
529
- `The author's generator .feature file at "${ generator_path }" ran into an unanticipated error when ALKiln tried to parse it. If you have a moment, we would love for you to send us your debug.txt.`
515
+ `The author's template .feature file at "${ generator_path }" ran into an unanticipated error when ALKiln tried to parse it. If you have a moment, we would love for you to send us your debug.txt.`
530
516
  );
531
517
  return { doc_AST, errors: [new Error( unknown_error )] };
532
518
  }
@@ -673,7 +659,7 @@ function get_fore_Steps({ scenario }) {
673
659
  if ( there_were_no_fore_Steps ) {
674
660
  misfits.add({ errors: [
675
661
  new Error( log.error({ code: `ALK0242`, context: `generating files`, },
676
- `A generator Scenario's first Step must say how many constrained random answers tests to make. The generator Scenario on line ${ scenario.location.line } is missing that Step. The Scenario's description is:\n${ description }`
662
+ `A template Scenario's first Step must say how many constrained random answers tests to make. The template Scenario on line ${ scenario.location.line } is missing that Step. The Scenario's description is:\n${ description }`
677
663
  ) )
678
664
  ] });
679
665
  } else {
@@ -740,7 +726,7 @@ function get_number_of_Scenarios_to_generate2({ step, prev_num_to_make }) {
740
726
  // a way that is also maintainable?
741
727
  warnings.push(
742
728
  log.warn({ code: `ALK0238`, context: `generating files`, },
743
- `A generator Scenario has too many Steps that set the number of tests to make. ALKiln will use the final number Step it finds or calculates. The current Step:\n ${ text }`
729
+ `A template Scenario has too many Steps that set the number of tests to make. ALKiln will use the final number Step it finds or calculates. The current Step:\n ${ text }`
744
730
  )
745
731
  );
746
732
  }
@@ -751,7 +737,7 @@ function get_number_of_Scenarios_to_generate2({ step, prev_num_to_make }) {
751
737
  if ( number_matcher === null ) {
752
738
  warnings.push(
753
739
  log.warn({ code: `ALK0239`, context: `generating files`, },
754
- `A generator Scenario is missing the number of tests to make. ALKiln will now make 1 constrained random answers test. The current Step:\n ${ text }`
740
+ `A template Scenario is missing the number of tests to make. ALKiln will now make 1 constrained random answers test. The current Step:\n ${ text }`
755
741
  )
756
742
  );
757
743
  return { num_to_make: 1, warnings, errors };
@@ -1115,7 +1101,7 @@ function get_final_ids({ step_text }) {
1115
1101
  let ids = step_text.match( CAPTURE_QUESTION_IDS_REGEX );
1116
1102
 
1117
1103
  if ( ids === null ) {
1118
- let missing_ids_msg = `The constrained random answers Step is missing its question ids. ALKiln is unable to create random tests for this generator Scenario. The Step:\n${ step_text }`;
1104
+ let missing_ids_msg = `The constrained random answers Step is missing its question ids. ALKiln is unable to create random tests for this template Scenario. The Step:\n${ step_text }`;
1119
1105
  let error = log.error({ code: `ALK0250`, context: `generating files`, }, missing_ids_msg );
1120
1106
  errors.push(new Error( error ));
1121
1107
  }
@@ -1364,7 +1350,7 @@ function remove_warning_tag_placeholder({ new_text }) {
1364
1350
 
1365
1351
 
1366
1352
  function add_total_tests({ scenario_group_str, num_tests }) {
1367
- /** Paste in the total number of tests generated for this generator Scenario.
1353
+ /** Paste in the total number of tests generated for this template Scenario.
1368
1354
  *
1369
1355
  * @param {object} obj - Named arguments
1370
1356
  * @param {str} obj.scenario_group_str - The combined string of all the
@@ -1385,10 +1371,10 @@ function add_warnings_Scenario({ scenarios_str, warnings }) {
1385
1371
  * Scenarios. Basically, a whole-generator-scope warning.
1386
1372
  *
1387
1373
  * Discuss: Add a pre-group Scenario for every group of generated Scenarios
1388
- * identifying the generator Scenario of the subsequent new Scenarios.
1374
+ * identifying the template Scenario of the subsequent new Scenarios.
1389
1375
  * That is, even when there is no warning to give.
1390
1376
  *
1391
- * TODO: If/when we allow multiple generator Scenarios, use the ID here too
1377
+ * TODO: If/when we allow multiple template Scenarios, use the ID here too
1392
1378
  *
1393
1379
  * @param {object} obj - Named arguments
1394
1380
  * @param {str} obj.scenarios_str - The str of the already existing Scenarios
@@ -1517,7 +1503,7 @@ function save_files({ tests_path, artifacts_path, file_strings }) {
1517
1503
  fs.mkdirSync( artifacts_path, { recursive: true });
1518
1504
 
1519
1505
  log.debug({ code: `ALK0269`, context: `generating files`, },
1520
- `Saving ${ file_strings.length } newly generated files in "${ tests_path }" and "${ artifacts_path }"`
1506
+ `Saving ${ file_strings.length } newly generated files in "${ tests_path }"`
1521
1507
  );
1522
1508
 
1523
1509
  let count = 1;
@@ -1529,8 +1515,8 @@ function save_files({ tests_path, artifacts_path, file_strings }) {
1529
1515
  fs.appendFileSync( `${ artifacts_path }/${ filename }`, file_str );
1530
1516
  }
1531
1517
 
1532
- log.info({ code: `ALK0270`, context: `generating files`, },
1533
- `Saved new generated files in "${ artifacts_path }"`
1518
+ log.debug({ code: `ALK0270`, context: `generating files`, },
1519
+ `Saved new generated files:`, fs.readdirSync( tests_path )
1534
1520
  );
1535
1521
  }
1536
1522
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const session_vars = require(`../utils/session_vars`);
4
4
  const files = require(`../utils/files`);
5
- const Log = require(`../utils/Log.js`);
5
+ const Log = require(`../setup/artifacts.js`);
6
6
 
7
7
  /** Reset and refresh artifacts path name */
8
8
 
@@ -12,15 +12,9 @@ session_vars.delete_artifacts_path_name();
12
12
  // If using `npm run cucumber` (as opposed to the bin commands),
13
13
  // use `--` before `--sources=./foo`
14
14
  const argv = require(`minimist`)( process.argv.slice(2) );
15
-
16
- const artifacts_path = files.make_artifacts_folder();
15
+ const artifacts_path = files.make_artifacts_folder( argv.path );
17
16
 
18
17
  session_vars.save_artifacts_path_name( artifacts_path );
19
18
 
20
- const log = new Log({ path: artifacts_path, context: `artifacts` });
21
- log.info({ code: `ALK0284`, context: `artifacts`,},
22
- `Created the artifacts folder at "${ artifacts_path }"`
23
- );
24
-
25
- // This is just for artifacts, not any other config values. Leave the config
26
- // Project name value as it is. Local developers find it useful to re-use it.
19
+ // Leave the config Project name value as it is. Local developers find it
20
+ // useful to re-use that value.
@@ -32,6 +32,7 @@ function validate_Gherkin_files() {
32
32
  * generator files. If there are any, it logs those errors and then finally
33
33
  * throws an error. Uses the values in command line arguments.
34
34
  * */
35
+
35
36
  let sources_paths = set_sources_paths({ log });
36
37
 
37
38
  let all_errors = [];
@@ -43,16 +44,10 @@ function validate_Gherkin_files() {
43
44
  // Discuss: If an author has only some files with syntax errors, should we try
44
45
  // to still run the tests for the files that had valid syntax?
45
46
  if ( all_errors.length > 0 ) {
46
- let parse_error_msg = `ALKiln ran into Gherkin syntax error(s) when parsing `
47
- + `these test feature files. See warnings and errors above.`
48
47
  log.throw({ code: `ALK0230`, context: `validating files`,
49
- error: new Error( parse_error_msg )
48
+ error: new Error( `ALKiln ran into Gherkin syntax error(s) when parsing your test feature files. See warnings and errors above.` )
50
49
  });
51
50
  };
52
-
53
- log.info({ code: `ALK0283`, context: `validating files`,},
54
- `Validated all feature files`
55
- );
56
51
  }
57
52
 
58
53
 
@@ -69,23 +64,15 @@ function parse_all_feature_files_in({ sources_path }) {
69
64
  * */
70
65
  let all_AST_errors = [];
71
66
 
72
- let feature_file_paths = null;
73
- try {
74
- feature_file_paths = fast_glob.sync(
75
- `${ sources_path }/**/*.feature`,
76
- // These files were generated last time and will get deleted before the run
77
- { ignore: [`${ sources_path }/${ globals.generated_files_folder_name }/*`] }
78
- );
79
- } catch ( feature_paths_error ) {
80
- log.error({ code: `ALK0282`, context: `validating files`, },
81
- `Error while trying to find feature files at "${ sources_path }"`,
82
- feature_paths_error
83
- );
84
- return { errors: [ feature_paths_error ]};
85
- }
67
+ let feature_file_paths = fast_glob.sync(
68
+ `${ sources_path }/**/*.feature`,
69
+ // These files were generated last time and will get deleted before the run
70
+ { ignore: [`${ sources_path }/${ globals.generated_files_folder_name }/*`] }
71
+ );
86
72
 
87
73
  for ( let feature_path of feature_file_paths ) {
88
- let file_text = fs.readFileSync( feature_path, { encoding: `utf8` } );
74
+
75
+ let file_text = fs.readFileSync( feature_path, { encoding: `utf8` });
89
76
  let { AST, errors: these_AST_errors } = get_Gherkin_AST({ file_text });
90
77
 
91
78
  if ( these_AST_errors.length > 0 ) {
package/lib/steps.js CHANGED
@@ -25,9 +25,6 @@ const session_vars = require('./utils/session_vars');
25
25
  const files = require('./utils/files' );
26
26
  const reports = require(`./utils/reports`);
27
27
  const da_api = require('./docassemble/docassemble_api_interface');
28
- const globals = require(`./globals`);
29
-
30
- const GENERATED_FILES_FOLDER_NAME = globals.generated_files_folder_name;
31
28
 
32
29
  /* Of Note:
33
30
  - We're using `*=` for selectors because sometimes da text has funny characters in it that are hard to anticipate
@@ -121,9 +118,6 @@ Before(async (scenario) => {
121
118
 
122
119
  // Start the running "progress bar" for the Scenario
123
120
  log.stdout({}, `\nScenario: ${ scenario.pickle.name }: `);
124
- // Save Scenario data in Scenario folder
125
- // '@alkiln @generated'
126
- // console.log( JSON.stringify( scenario ) );
127
121
 
128
122
  // Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
129
123
  if (!scope.browser) {
@@ -161,16 +155,6 @@ Before(async (scenario) => {
161
155
  fs.mkdirSync( scope.paths.scenario );
162
156
  // Store path name for this Scenario's individualized report
163
157
  scope.paths.scenario_report = `${ scope.paths.scenario }/report.txt`;
164
- // If it's a generated Scenario, save the `.feature` file in here
165
- // GENERATED_FILES_FOLDER_NAME
166
- if ( scenario.gherkinDocument.uri.includes("_alkiln_generated") ) {
167
- try {
168
- let file_text = fs.readFileSync( scenario.gherkinDocument.uri, {encoding: `utf8`});
169
- fs.appendFileSync( `${ scope.paths.scenario }/${ filename }`, file_text );
170
- } catch ( saving_feat_file_error ) {
171
- // Ignore. This is not crucial.
172
- }
173
- }
174
158
 
175
159
  // Downloads
176
160
  scope.downloadComplete = false;
package/lib/utils/Log.js CHANGED
@@ -228,7 +228,6 @@ class Log {
228
228
  }
229
229
 
230
230
  if ( !fs.existsSync( dir )) {
231
- process.stdout.write(`Making artifacts folder in Log at ${ dir }`)
232
231
  files.make_artifacts_folder( dir );
233
232
  }
234
233
 
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const safe_filename = require('sanitize-filename');
3
- const path = require(`path`);
4
3
 
5
4
  let files = {};
6
5
 
@@ -22,30 +21,27 @@ files.readable_date = function() {
22
21
  return fancy_str;
23
22
  }; // Ends files.readable_date()
24
23
 
25
- files.make_artifacts_folder = function ( file_path ) {
24
+ files.make_artifacts_folder = function (path) {
26
25
  /** Makes a folder in the root of the project if it doesn't
27
26
  * already exist, either with the given name or with a default name.
28
27
  *
29
28
  * Works for local development as well, without action.yml or workflow files.
30
29
  * */
31
- if (file_path) {
32
- let name_parts = file_path.split(`/`);
30
+ if (path) {
31
+ let name_parts = path.split(`/`);
33
32
  let safe_parts = [];
34
33
  for ( let part of name_parts ) {
35
34
  safe_parts.push(safe_filename( part ));
36
35
  }
37
- file_path = path.join( ...safe_parts );
36
+ path = safe_parts.join(`/`);
38
37
  } else {
39
- file_path = `alkiln-${ files.readable_date() }`
38
+ path = `alkiln-${ files.readable_date() }`
40
39
  }
41
40
 
42
- const root = process.cwd();
43
- root_path = path.join( root, file_path );
44
-
45
41
  // `recursive` avoids errors if folder already exists
46
42
  // https://nodejs.org/docs/latest-v18.x/api/fs.html#fspromisesmkdirpath-options
47
- fs.mkdirSync( root_path, { recursive: true });
48
- return root_path;
43
+ fs.mkdirSync( path, { recursive: true });
44
+ return path;
49
45
  }; // Ends files.make_artifacts_folder()
50
46
 
51
47
  module.exports = files;