@suffolklitlab/alkiln 5.12.0-delete6 → 5.13.0-logs1

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/.env.example +29 -0
  2. package/.github/workflows/github_server.yml +11 -9
  3. package/.github/workflows/log_code_tests.yml +0 -2
  4. package/.github/workflows/playground.yml +7 -5
  5. package/CHANGELOG.md +30 -5
  6. package/CONTRIBUTING.md +98 -83
  7. package/action.yml +66 -28
  8. package/action_for_github_server/action.yml +59 -31
  9. package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +46 -0
  10. package/docs/decisions/adopt_architectural_decision_records.md +58 -0
  11. package/docs/decisions/detect_sign_in.md +124 -0
  12. package/docs/decisions/pdfjs_version_2024_08_08.md +18 -0
  13. package/docs/decisions/reports_and_logs_2024_08_11.md +141 -0
  14. package/docs/decisions/update_puppeteer_2024_07_28.md +62 -0
  15. package/lib/docassemble/docassemble_api_interface.js +63 -34
  16. package/lib/docassemble/server_install.js +23 -9
  17. package/lib/docassemble/setup.js +33 -20
  18. package/lib/docassemble/takedown.js +27 -7
  19. package/lib/run_cucumber.js +81 -33
  20. package/lib/scope.js +152 -79
  21. package/lib/steps.js +84 -51
  22. package/lib/utils/files.js +8 -6
  23. package/lib/utils/log.js +477 -84
  24. package/lib/utils/reports.js +14 -5
  25. package/lib/utils/session_vars.js +25 -18
  26. package/lib/utils/time.js +44 -0
  27. package/npm-shrinkwrap.json +635 -193
  28. package/package.json +6 -6
  29. package/tests/log_codes/check_codes.sh +163 -108
  30. package/tests/log_codes/log_code_expected_instances.txt +213 -0
  31. package/tests/unit_tests/getAllFields.test.js +5 -0
  32. package/tests/unit_tests/getMatchingRows.test.js +5 -0
  33. package/tests/unit_tests/log.test.js +20 -0
  34. package/tests/unit_tests/normalizeTable.test.js +5 -0
  35. package/tests/log_codes/deleted_codes.json +0 -8
package/lib/steps.js CHANGED
@@ -5,6 +5,7 @@ const {
5
5
  Given,
6
6
  After,
7
7
  Before,
8
+ AfterStep,
8
9
  BeforeAll,
9
10
  AfterAll,
10
11
  setDefaultTimeout //,
@@ -18,7 +19,7 @@ const { v4: uuidv4 } = require('uuid');
18
19
 
19
20
  // Ours
20
21
  const scope = require('./scope');
21
- const log = require('./utils/log');
22
+ const Log = require(`./utils/log`);
22
23
  const session_vars = require('./utils/session_vars');
23
24
  const files = require('./utils/files' );
24
25
  const reports = require(`./utils/reports`);
@@ -82,7 +83,9 @@ let click_with = {
82
83
  pc: 'click',
83
84
  };
84
85
 
85
- BeforeAll(async () => {
86
+ let log = null;
87
+
88
+ BeforeAll(async function() {
86
89
  scope.showif_timeout = 600;
87
90
  // TODO: Do these really need to be maps? As sets, they may make the output
88
91
  // more predictable to the user, but they add complexity.
@@ -95,29 +98,29 @@ BeforeAll(async () => {
95
98
  // Store path names for files and docs created for the tests
96
99
  scope.paths = {};
97
100
 
98
- // TODO: figure out how to test file and folder creation stuff,
99
- // especially when setup is excluded from the flow.
101
+ // // Make the folder to store all the files for this run of the tests
102
+ // scope.paths.artifacts = files.make_artifacts_folder();
103
+ // // Save its name in a file. `session_vars` can get it back later.
104
+ // // The value we are saving is used by at least run_cucumber.js.
100
105
 
101
- // Make the folder to store all the files for this run of the tests
102
- scope.paths.artifacts = files.make_artifacts_folder();
103
- // Save its name in a file. `session_vars` can get it back later.
104
- // The value we are saving is used by at least run_cucumber.js.
105
- session_vars.save_artifacts_path_name( scope.paths.artifacts );
106
+ // A little indirect, but the artifacts path should always have already been
107
+ // created by now, either by `run_cucumber.js` or the relevant test
108
+ scope.paths.artifacts = session_vars.get_artifacts_path_name();
109
+ log = new Log({ path: scope.paths.artifacts, context: `steps` });
110
+ scope.set_log({ log_obj: log });
106
111
 
107
- scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
112
+ scope.paths.debug_log = `${ scope.paths.artifacts }/${ log.debug_log_filename }`;
113
+ scope.paths.report_log = `${ scope.paths.artifacts }/${ log.report_log_filename }`;
114
+ scope.paths.report = `${ scope.paths.artifacts }/${ log.report_filename }`;
108
115
  scope.paths.failures = `${ scope.paths.artifacts }/${ log.unexpected_filename }`;
109
- scope.paths.debug_log = `${ scope.paths.artifacts }/${ log.debug_log_file }`;
110
- fs.writeFileSync( scope.paths.debug_log, "" );
111
116
 
112
117
  reports.create( scope );
113
118
  });
114
119
 
115
120
  Before(async (scenario) => {
116
121
 
117
- // Useful for those with a lot of tests
118
- // Is unable to print the first Scenario name for some reason
119
- console.log('');
120
- process.stdout.write(`Scenario: ${ scenario.pickle.name }: `);
122
+ // Start the running "progress bar" for the Scenario
123
+ log.stdout({}, `\nScenario: ${ scenario.pickle.name }: `);
121
124
 
122
125
  // Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
123
126
  if (!scope.browser) {
@@ -396,11 +399,9 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
396
399
  from_story_table: true,
397
400
  });
398
401
 
399
- log.debug({
400
- type: `info`,
401
- code: `ALK0061`,
402
- pre: `Rows, including special rows:, ${JSON.stringify( ensured_var_data )}`,
403
- });
402
+ log.debug({ code: `ALK0061`, context: `table`, },
403
+ `Rows, including special rows:`, ensured_var_data
404
+ );
404
405
 
405
406
  // THIS FUNCTION ACTUALLY MATTERS
406
407
  // Some errors happen in there to be closer to the relevant code. We'd have to try/catch anyway.
@@ -650,7 +651,7 @@ Then('I arrive at the next page', async () => { // √
650
651
  // Link observations have the 'Escape' button link in mind
651
652
  Then('I should see the link {string}', async ( linkText ) => { // √
652
653
  /* Loosely match link visible text. Should probably deprecate this. */
653
- let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
654
+ let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
654
655
 
655
656
  let msg = `Cannot find a link with the text "${ linkText }".`;
656
657
  if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0079`, value: msg }); }
@@ -672,7 +673,7 @@ Then('I should see the link to {string}', async ( url ) => { // √
672
673
 
673
674
  Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) => { // √
674
675
  /* Find link by its visible text and where it leads. */
675
- let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
676
+ let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
676
677
  // TODO: Possibly add a check here for the link text, though note this
677
678
  // Step is not currently supported.
678
679
 
@@ -687,7 +688,7 @@ Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) =>
687
688
  });
688
689
 
689
690
  Then(/the "([^"]+)" link opens in (a new window|the same window)/, async (linkText, which_window) => { // √
690
- let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
691
+ let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
691
692
 
692
693
  let prop_obj = await link.getProperty('target');
693
694
  let target = await prop_obj.jsonValue();
@@ -972,7 +973,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
972
973
  } // ends if !has_content
973
974
 
974
975
  if ( buttons_exist ) {
975
- process.stdout.write(`\x1b[36m${ 'v' }\x1b[0m`); // pressed button
976
+ log.stdout({}, `\x1b[36m${ 'v' }\x1b[0m`); // pressed button
976
977
  }
977
978
 
978
979
  resolve();
@@ -1263,11 +1264,22 @@ Then(/I fail to delete (\d) detected interview(?:s)? and get no error __internal
1263
1264
  //#####################################
1264
1265
  //#####################################
1265
1266
 
1267
+ AfterStep(async function({ result }) {
1268
+ // TODO: Abstract this
1269
+ if ( result.status === `PASSED` ) { log.stdout({ records_only: true }, `.`); }
1270
+ else if ( result.status === `FAILED` ) { log.stdout({ records_only: true }, `F`); }
1271
+ else if ( result.status === `UNDEFINED` ) { log.stdout({ records_only: true }, `U`); }
1272
+ else if ( result.status === `SKIPPED` ) { log.stdout({ records_only: true }, `-`); }
1273
+ else if ( result.status === `PENDING` ) { log.stdout({ records_only: true }, `P`); }
1274
+ });
1275
+
1266
1276
  After(async function(scenario) {
1267
1277
 
1268
1278
  // Log errors
1269
1279
  if ( scenario.result.message ) {
1270
- log.debug({ type: `info`, code: `ALK0091`, pre: `cucumber error: ${ scenario.result.message }` }); //, verbose: true });
1280
+ log.debug({ code: `ALK0091`, context: `scenario`, },
1281
+ scenario.result.message
1282
+ );
1271
1283
  }
1272
1284
 
1273
1285
  let changeable_test_status = scenario.result.status;
@@ -1296,11 +1308,9 @@ After(async function(scenario) {
1296
1308
  reports.saveFinalScenarioData( scope, { status: changeable_test_status });
1297
1309
 
1298
1310
  if (changeable_test_status !== `PASSED`) {
1299
- log.debug({
1300
- type: `error`,
1301
- code: `ALK0094`,
1302
- pre: `Non-passed status occurred while running test: ${changeable_test_status}`,
1303
- });
1311
+ log.debug({ code: `ALK0094`, level: `warn`, },
1312
+ `Non-passed status occurred while running test: ${ changeable_test_status }`
1313
+ );
1304
1314
 
1305
1315
  if ( scope.page ) {
1306
1316
 
@@ -1396,8 +1406,11 @@ After(async function(scenario) {
1396
1406
  + `Expected status: ${scope.expected_status}. `
1397
1407
  + `See the report for details.`;
1398
1408
  let code = `ALK0099`;
1399
- log.debug({ type: `error`, code: code, pre: msg, })
1400
- expected_status_err_msg = `🤕 ${ code } ERROR: ${ msg }`
1409
+ expected_status_err_msg = log.debug({ code: code, level: `error`, },
1410
+ msg
1411
+ );
1412
+ // Temp for debugging new log output
1413
+ expect( expected_status_err_msg ).to.equal(`🤕 ${ code } ERROR: ${ msg }`);
1401
1414
  }
1402
1415
  }
1403
1416
  scope.expected_status = null; // reset for next Scenario
@@ -1417,17 +1430,25 @@ After(async function(scenario) {
1417
1430
  // Make sure that tests which ALKiln has calculated as failed
1418
1431
  // cause a cucumber error later. Just changing the status to `FAILED`
1419
1432
  // causes unhelpful cucumber errors. TODO: Add to internal docs.
1420
- log.debug({
1421
- type: `error`,
1422
- code: force_failure_code,
1423
- pre: force_failure_msg
1424
- });
1433
+ // TODO: Should we move this to where the error is thrown?
1434
+ log.debug({ code: force_failure_code, level: `error`, },
1435
+ force_failure_msg
1436
+ );
1425
1437
  // TODO: Look into setDefinitionFunctionWrapper to catch errors
1426
1438
  // of failing tests that should indeed be failing:
1427
1439
  // https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/api_reference.md#setdefinitionfunctionwrapperwrapper
1428
1440
  need_to_force_failed_status = true;
1429
1441
  }
1430
1442
 
1443
+ // Not sure if this is necessary
1444
+ if ( changeable_test_status === 'PASSED' ) {
1445
+ log.stdout({ records_only: true }, `.` );
1446
+ } else if ( changeable_test_status === `SKIPPED` ) {
1447
+ log.stdout({ records_only: true }, `-` );
1448
+ } else {
1449
+ log.stdout({ records_only: true }, changeable_test_status[0].toUpperCase() );
1450
+ }
1451
+
1431
1452
  let signout_succeeded = true;
1432
1453
  let signout_error_msg = ``;
1433
1454
  // If there is a page open, then sign out and close it
@@ -1481,9 +1502,10 @@ After(async function(scenario) {
1481
1502
  // going to the wrong file). If it becomes a problem we can track it down later.
1482
1503
  if ( visible_keys.length > 0 ) {
1483
1504
  // Can an API key ever see an interview that it can't delete?
1484
- log.info({ type: `internal warning`, code: `ALK0197`,
1485
- pre: `The API key can see ${ visible_keys.length } interviews that were not deleted: ${ visible_keys.join( ', ' ) }`
1486
- });
1505
+ log.debug({ code: `ALK0197`, context: `internal`, level: `warn`, }
1506
+ `The API key can see ${ visible_keys.length } interviews that were not deleted:`,
1507
+ visible_keys
1508
+ );
1487
1509
  }
1488
1510
  } catch ( error ) {
1489
1511
  // Deleting interviews is nice, but not non-critical. Don't bother the
@@ -1493,26 +1515,37 @@ After(async function(scenario) {
1493
1515
  // interview. If it becomes a problem we can track it down later.
1494
1516
  // Consider add "internal" to docs: "If you see this, make an
1495
1517
  // issue in ALKiln with your debug/verbose log"
1496
- log.info({ type: `internal warning`, code: `ALK0198`, pre: `Error while cleaning up interviews.`, data: error });
1518
+ log.debug({ code: `ALK0198`, context: `internal`, level: `warn`, error },
1519
+ `Cleaning up interviews incomplete, but unimportant.`
1520
+ );
1497
1521
  }
1498
1522
  }
1499
1523
 
1500
- // Handle all the accumulated errors
1501
- let errors = []
1502
- if ( !expected_status_was_met ) { errors.push( expected_status_err_msg ); }
1503
- if ( !signout_succeeded ) { errors.push( signout_error_msg ); }
1504
- if ( errors.length > 0 ) {
1505
- throw new Error( `${errors.join( errors, '\n')}` );
1506
- }
1524
+ // Collect all the relevant special errors
1525
+ let internal_errors = []
1526
+ if ( !expected_status_was_met ) { internal_errors.push( expected_status_err_msg ); }
1527
+ if ( !signout_succeeded ) { internal_errors.push( signout_error_msg ); }
1507
1528
 
1508
1529
  // Theoretically, we should never get here:
1509
1530
  // If everything else worked, if we need to force an internal test failure
1510
1531
  if ( need_to_force_failed_status ) {
1511
- throw new Error( `🤕 ${force_failure_code} unreachable ERROR: ${force_failure_msg}` );
1532
+ if ( internal_errors.length > 0 ) {
1533
+ // Handle all the accumulated errors
1534
+ throw new Error( `${internal_errors.join( internal_errors, '\n')}` );
1535
+ }
1536
+ throw new Error( `🤕 ${ force_failure_code } unreachable ERROR: ${ force_failure_msg }` );
1537
+
1538
+ } else if ( internal_errors.length > 0 ) {
1539
+ log.debug({ code: `ALK0203`, context: `internal`, level: `warn`, },
1540
+ `There was no need to force failed status and yet there are error messages.`,
1541
+ internal_errors
1542
+ );
1512
1543
  }
1513
1544
 
1514
1545
  // Find the race condition.
1515
- log.debug({ type: `cucumber`, code: `ALK0100`, verbose: true, pre: `Scenario AfterStep() message`, data: scenario.result.message });
1546
+ log.debug({ code: `ALK0100`, context: `scenario`, },
1547
+ `Scenario After() message:`, scenario.result.message
1548
+ );
1516
1549
 
1517
1550
  });
1518
1551
 
@@ -22,18 +22,20 @@ files.readable_date = function() {
22
22
  }; // Ends files.readable_date()
23
23
 
24
24
  files.make_artifacts_folder = function (folder_name) {
25
- /** Makes a folder in the root of the project, either with the given name
26
- * or with a default name.
27
- *
28
- * Works for local development as well, unlike action.yml or the workflow file.
29
- */
25
+ /** Makes a folder in the root of the project if it doesn't
26
+ * already exist, either with the given name or with a default name.
27
+ *
28
+ * Works for local development as well, without action.yml or workflow files.
29
+ * */
30
30
  if (folder_name) {
31
31
  folder_name = safe_filename( folder_name );
32
32
  } else {
33
33
  folder_name = `alkiln-${ files.readable_date() }`
34
34
  }
35
35
 
36
- fs.mkdirSync( folder_name );
36
+ // `recursive` avoids errors if folder already exists
37
+ // https://nodejs.org/docs/latest-v18.x/api/fs.html#fspromisesmkdirpath-options
38
+ fs.mkdirSync( folder_name, { recursive: true });
37
39
  return folder_name;
38
40
  }; // Ends files.make_artifacts_folder()
39
41