@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.
- package/.env.example +29 -0
- package/.github/workflows/github_server.yml +11 -9
- package/.github/workflows/log_code_tests.yml +0 -2
- package/.github/workflows/playground.yml +7 -5
- package/CHANGELOG.md +30 -5
- package/CONTRIBUTING.md +98 -83
- package/action.yml +66 -28
- package/action_for_github_server/action.yml +59 -31
- package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +46 -0
- package/docs/decisions/adopt_architectural_decision_records.md +58 -0
- package/docs/decisions/detect_sign_in.md +124 -0
- package/docs/decisions/pdfjs_version_2024_08_08.md +18 -0
- package/docs/decisions/reports_and_logs_2024_08_11.md +141 -0
- package/docs/decisions/update_puppeteer_2024_07_28.md +62 -0
- package/lib/docassemble/docassemble_api_interface.js +63 -34
- package/lib/docassemble/server_install.js +23 -9
- package/lib/docassemble/setup.js +33 -20
- package/lib/docassemble/takedown.js +27 -7
- package/lib/run_cucumber.js +81 -33
- package/lib/scope.js +152 -79
- package/lib/steps.js +84 -51
- package/lib/utils/files.js +8 -6
- package/lib/utils/log.js +477 -84
- package/lib/utils/reports.js +14 -5
- package/lib/utils/session_vars.js +25 -18
- package/lib/utils/time.js +44 -0
- package/npm-shrinkwrap.json +635 -193
- package/package.json +6 -6
- package/tests/log_codes/check_codes.sh +163 -108
- package/tests/log_codes/log_code_expected_instances.txt +213 -0
- package/tests/unit_tests/getAllFields.test.js +5 -0
- package/tests/unit_tests/getMatchingRows.test.js +5 -0
- package/tests/unit_tests/log.test.js +20 -0
- package/tests/unit_tests/normalizeTable.test.js +5 -0
- 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
|
|
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
|
-
|
|
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
|
-
//
|
|
99
|
-
//
|
|
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
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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.
|
|
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
|
-
//
|
|
118
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
1301
|
-
|
|
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({
|
|
1400
|
-
|
|
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
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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.
|
|
1485
|
-
|
|
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.
|
|
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
|
-
//
|
|
1501
|
-
let
|
|
1502
|
-
if ( !expected_status_was_met ) {
|
|
1503
|
-
if ( !signout_succeeded ) {
|
|
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
|
-
|
|
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({
|
|
1546
|
+
log.debug({ code: `ALK0100`, context: `scenario`, },
|
|
1547
|
+
`Scenario After() message:`, scenario.result.message
|
|
1548
|
+
);
|
|
1516
1549
|
|
|
1517
1550
|
});
|
|
1518
1551
|
|
package/lib/utils/files.js
CHANGED
|
@@ -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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|