@suffolklitlab/alkiln 5.8.1 → 5.9.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.
- package/.github/workflows/github_server.yml +11 -7
- package/.github/workflows/playground.yml +2 -2
- package/CHANGELOG.md +17 -0
- package/docs/expected_inernal_results.md +15 -0
- package/lib/scope.js +257 -134
- package/lib/steps.js +171 -34
- package/lib/utils/log.js +16 -5
- package/package.json +1 -1
- package/tests/unit_tests/fields.fixtures.js +477 -103
- package/tests/unit_tests/getAllFields.test.js +22 -8
- package/tests/unit_tests/getMatchingRows.test.js +44 -2
- package/tests/unit_tests/html.fixtures.js +66 -4
- package/tests/unit_tests/matches.fixtures.js +20 -2
- package/tests/unit_tests/tables.fixtures.js +22 -5
package/lib/steps.js
CHANGED
|
@@ -106,6 +106,12 @@ BeforeAll(async () => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
Before(async (scenario) => {
|
|
109
|
+
|
|
110
|
+
// Useful for those with a lot of tests
|
|
111
|
+
// Is unable to print the first Scenario name for some reason
|
|
112
|
+
console.log('');
|
|
113
|
+
process.stdout.write(`Scenario: ${ scenario.pickle.name }: `);
|
|
114
|
+
|
|
109
115
|
// Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
|
|
110
116
|
if (!scope.browser) {
|
|
111
117
|
if (session_vars.get_origin() === 'playground') {
|
|
@@ -297,7 +303,9 @@ Given(/the max(?:imum)? sec(?:ond)?s for each step (?:in this scenario )?is (\d+
|
|
|
297
303
|
// Story
|
|
298
304
|
//#####################################
|
|
299
305
|
//#####################################
|
|
300
|
-
|
|
306
|
+
// TODO: Consider more permissive sentences:
|
|
307
|
+
// with(?: this data| these answers)?:?
|
|
308
|
+
// with(?:.*):?
|
|
301
309
|
When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)" with this data:?/, {timeout: -1}, async (target_id, raw_var_data) => {
|
|
302
310
|
/* NO CUCUMBER TIMEOUT. Must handle timeout ourselves:
|
|
303
311
|
* https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/timeouts.md#disable-timeouts
|
|
@@ -341,6 +349,7 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
341
349
|
try {
|
|
342
350
|
let { question_has_id, id, id_matches } = await scope.examinePageID( scope );
|
|
343
351
|
|
|
352
|
+
// THIS FUNCTION ACTUALLY MATTERS
|
|
344
353
|
// Add special rows. Basically, do weirder things.
|
|
345
354
|
// TODO: Remove from_story_table - no longer needed if only called from in here
|
|
346
355
|
let ensured_var_data = await scope.ensureSpecialRows( scope, {
|
|
@@ -352,8 +361,10 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
352
361
|
console.log( `Rows, including special rows:`, JSON.stringify( ensured_var_data ));
|
|
353
362
|
}
|
|
354
363
|
|
|
364
|
+
// THIS FUNCTION ACTUALLY MATTERS
|
|
355
365
|
// Some errors happen in there to be closer to the relevant code. We'd have to try/catch anyway.
|
|
356
366
|
let error = await scope.setFields(scope, { var_data: ensured_var_data, id, ensure_navigation: true });
|
|
367
|
+
|
|
357
368
|
if ( error.was_found ) { await scope.throwPageError( scope, { id: id }); }
|
|
358
369
|
resolve();
|
|
359
370
|
} catch ( err ) {
|
|
@@ -415,6 +426,123 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
415
426
|
reports.addToReport(scope, { type: `note`, value: `` });
|
|
416
427
|
});
|
|
417
428
|
|
|
429
|
+
// When(/^foo id "([^"]+)"/, {timeout: -1}, async (target_id, raw_var_data) => {
|
|
430
|
+
// /* NO CUCUMBER TIMEOUT. Must handle timeout ourselves:
|
|
431
|
+
// * https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/timeouts.md#disable-timeouts
|
|
432
|
+
// * Iterate in here to keep timeout reasonable */
|
|
433
|
+
|
|
434
|
+
// // Note: Why do we not just stop when we've used all the vars in the table or
|
|
435
|
+
// // reach the last var? Vars are not required to be given in sequence and pages that
|
|
436
|
+
// // have non-button fields don't usually have a var assigned to the 'continue' button.
|
|
437
|
+
// // Alternative: require vars even for 'continue' pages. Then in loop require at least one var
|
|
438
|
+
// // to be found on a page before hitting 'continue'. That requires detecting navigation because
|
|
439
|
+
// // pages with fields will not have a var for the continue button, so there will
|
|
440
|
+
// // still have to be some automatic continuing.
|
|
441
|
+
// // It also seems harsh, but more reliable.
|
|
442
|
+
// // Ask developers what they'd like.
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
// let supported_table = await scope.normalizeTable( scope, { raw_var_data });
|
|
446
|
+
|
|
447
|
+
// let { question_has_id, id, id_matches } = await scope.examinePageID( scope, target_id );
|
|
448
|
+
// // Until we reach the target page, hit an error, or time out (take too long)
|
|
449
|
+
// while ( !id_matches ) {
|
|
450
|
+
// let custom_timeout = new Promise(function( resolve, reject ) {
|
|
451
|
+
|
|
452
|
+
// // Set up the timeout that avoids infinite loops
|
|
453
|
+
// let on_page_timeout = async function() {
|
|
454
|
+
|
|
455
|
+
// let non_reload_report_msg = `The target id was "${ target_id }", but it took `
|
|
456
|
+
// + `too long to try to reach it (over ${ scope.timeout/1000/60 } min). The `
|
|
457
|
+
// + `test got stuck on "${ id }".`
|
|
458
|
+
// let error = non_reload_report_msg;
|
|
459
|
+
// await scope.handle_page_timeout_error( scope, { non_reload_report_msg, error });
|
|
460
|
+
// }; // Ends on_page_timeout()
|
|
461
|
+
// let page_timeout_id = setTimeout(on_page_timeout, scope.timeout);
|
|
462
|
+
|
|
463
|
+
// // self-invoke an anonymous async function so we don't have to
|
|
464
|
+
// // abstract the details of resolving and rejecting.
|
|
465
|
+
// // If we need to call recursively, signature function might look like:
|
|
466
|
+
// // func( scope, { resolve, reject, supported_table, page_timeout_id })
|
|
467
|
+
// // Maybe list of timeout and/or interval ids in state that can all be cleared at once?
|
|
468
|
+
// (async function() {
|
|
469
|
+
// try {
|
|
470
|
+
// let { question_has_id, id, id_matches } = await scope.examinePageID( scope );
|
|
471
|
+
|
|
472
|
+
// // Add special rows. Basically, do weirder things.
|
|
473
|
+
// // TODO: Remove from_story_table - no longer needed if only called from in here
|
|
474
|
+
// let ensured_var_data = await scope.ensureSpecialRows( scope, {
|
|
475
|
+
// var_data: supported_table,
|
|
476
|
+
// from_story_table: true,
|
|
477
|
+
// });
|
|
478
|
+
|
|
479
|
+
// if ( session_vars.get_debug() ) {
|
|
480
|
+
// console.log( `Rows, including special rows:`, JSON.stringify( ensured_var_data ));
|
|
481
|
+
// }
|
|
482
|
+
|
|
483
|
+
// // Some errors happen in there to be closer to the relevant code. We'd have to try/catch anyway.
|
|
484
|
+
// let error = await scope.setFields(scope, { var_data: ensured_var_data, id, ensure_navigation: true });
|
|
485
|
+
// if ( error.was_found ) { await scope.throwPageError( scope, { id: id }); }
|
|
486
|
+
// resolve();
|
|
487
|
+
// } catch ( err ) {
|
|
488
|
+
// let err_msg = `Error occurred when tried to answer fields on question "${ id }".`
|
|
489
|
+
// reports.addToReport( scope, { type: `error`, value: err_msg });
|
|
490
|
+
// // Throw error instead?
|
|
491
|
+
// reject( err );
|
|
492
|
+
// } finally {
|
|
493
|
+
// clearTimeout( page_timeout_id ); // prevent temporary hang at end of tests
|
|
494
|
+
// }
|
|
495
|
+
// })();
|
|
496
|
+
// }); // ends custom_timeout for setFields
|
|
497
|
+
|
|
498
|
+
// await custom_timeout;
|
|
499
|
+
|
|
500
|
+
// // Prep for next loop
|
|
501
|
+
// ({ question_has_id, id, id_matches } = await scope.examinePageID( scope, target_id ));
|
|
502
|
+
|
|
503
|
+
// } // ends while !id_matches
|
|
504
|
+
// // Can anything unsatisfactory get through here?
|
|
505
|
+
|
|
506
|
+
// // Add report strings from supported table, not an altered one
|
|
507
|
+
// for ( let row of supported_table ) {
|
|
508
|
+
// row.report_str = `${ reports.convertToOriginalStoryTableRow(scope, { row_data: row }) }`;
|
|
509
|
+
// }
|
|
510
|
+
// // Sort into alphabetical order, as is recommended for writing story tables
|
|
511
|
+
// let sorted_rows = supported_table.sort( function ( row1, row2 ) {
|
|
512
|
+
// if( row1.report_str < row2.report_str ) { return -1; }
|
|
513
|
+
// if( row1.report_str > row2.report_str ) { return 1; }
|
|
514
|
+
// return 0;
|
|
515
|
+
// });
|
|
516
|
+
|
|
517
|
+
// // Add full used table to report so it can be copied if needed.
|
|
518
|
+
// // TODO: Should we/How do we get this in the final scenario report instead of here?
|
|
519
|
+
// reports.addToReport( scope, { type: `note`, value: `\n${ ' '.repeat(2) }Rows that got set:` });
|
|
520
|
+
// reports.addToReport( scope, { type: `step`, value: `${ ' '.repeat(4) }And I get to the question id "${ target_id }" with this data:` });
|
|
521
|
+
// reports.addToReport(scope, { type: `row`, value: `${ ' '.repeat(6) }| var | value | trigger |`, });
|
|
522
|
+
// let at_least_one_row_was_NOT_used = false;
|
|
523
|
+
// for ( let row of sorted_rows ) {
|
|
524
|
+
// if ( row.times_used > 0 ) {
|
|
525
|
+
// reports.addToReport(scope, { type: `row`, value: row.report_str, });
|
|
526
|
+
// } else {
|
|
527
|
+
// at_least_one_row_was_NOT_used = true;
|
|
528
|
+
// }
|
|
529
|
+
// }
|
|
530
|
+
|
|
531
|
+
// // Add unused rows if needed
|
|
532
|
+
// if ( at_least_one_row_was_NOT_used ) {
|
|
533
|
+
// reports.addToReport( scope, { type: `note`, value: `${ ' '.repeat(2) }Unused rows:` });
|
|
534
|
+
// for ( let row of sorted_rows ) {
|
|
535
|
+
// if ( row.times_used === 0 ) {
|
|
536
|
+
// reports.addToReport(scope, { type: `row`, value: row.report_str, });
|
|
537
|
+
// }
|
|
538
|
+
// }
|
|
539
|
+
// } else {
|
|
540
|
+
// reports.addToReport(scope, { type: `note`, value: `${ ' '.repeat(2) }All rows were used` });
|
|
541
|
+
// }
|
|
542
|
+
|
|
543
|
+
// reports.addToReport(scope, { type: `note`, value: `` });
|
|
544
|
+
// });
|
|
545
|
+
|
|
418
546
|
//#####################################
|
|
419
547
|
//#####################################
|
|
420
548
|
// Passive/Observational
|
|
@@ -1122,8 +1250,8 @@ Given(/the Scenario report should not include/i, async ( report_text ) => {
|
|
|
1122
1250
|
|
|
1123
1251
|
});
|
|
1124
1252
|
|
|
1125
|
-
Given(/the final Scenario status should be "(
|
|
1126
|
-
|
|
1253
|
+
Given(/the final Scenario status should be "([^"]+)"/i, async ( expected_status ) => {
|
|
1254
|
+
/** WARNING: If a Step will fail, this Step must come first.
|
|
1127
1255
|
*
|
|
1128
1256
|
* Prepare to test that the Scenario completes with the correct status. */
|
|
1129
1257
|
scope.expected_status = expected_status.toUpperCase();
|
|
@@ -1138,6 +1266,12 @@ Given(/the final Scenario status should be "(passed|failed)"/i, async ( expected
|
|
|
1138
1266
|
|
|
1139
1267
|
After(async function(scenario) {
|
|
1140
1268
|
|
|
1269
|
+
// Log errors
|
|
1270
|
+
if ( scenario.result.message ) {
|
|
1271
|
+
log.debug({ type: `error`, pre: scenario.result.message });
|
|
1272
|
+
// log.debug({ type: `error`, pre: scenario.result.message, verbose: true });
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1141
1275
|
let changeable_test_status = scenario.result.status;
|
|
1142
1276
|
// The accessibility failures don't happen til the end, so the rest of the test can continue
|
|
1143
1277
|
if ( scope.check_all_for_a11y && !scope.passed_all_for_a11y) {
|
|
@@ -1199,39 +1333,46 @@ After(async function(scenario) {
|
|
|
1199
1333
|
|
|
1200
1334
|
} // ends if not passed
|
|
1201
1335
|
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1336
|
+
// Add report text to Scenario folder after everything has been added to the report
|
|
1337
|
+
let current_report_obj = scope.report.get( scope.scenario_id );
|
|
1338
|
+
let scenario_report_obj = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
1339
|
+
let report = reports.getPrintableScenario( scenario_report_obj );
|
|
1340
|
+
// Save the report as in the Scenario folder
|
|
1341
|
+
fs.writeFileSync( scope.paths.scenario_report, report );
|
|
1342
|
+
|
|
1343
|
+
// ---------------- Check internal test results of this framework ----------------
|
|
1344
|
+
|
|
1211
1345
|
if ( scope.expected_in_report && scope.expected_in_report.length > 0) {
|
|
1212
|
-
let
|
|
1346
|
+
let report_includes_all_expected_strings = await scope.reportIncludesAllExpected(
|
|
1347
|
+
scope,
|
|
1348
|
+
{ expected: scope.expected_in_report }
|
|
1349
|
+
);
|
|
1350
|
+
if ( !report_includes_all_expected_strings ) { changeable_test_status = `FAILED`; }
|
|
1213
1351
|
// Reset report values no matter what so they don't mess up future scenarios
|
|
1214
1352
|
scope.expected_in_report = null;
|
|
1215
|
-
let all_were_included = await scope.reportIncludesAllExpected( scope, { expected });
|
|
1216
|
-
|
|
1217
|
-
if ( all_were_included ) { changeable_test_status = `PASSED`; }
|
|
1218
|
-
else { changeable_test_status = `FAILED`; }
|
|
1219
1353
|
}
|
|
1220
1354
|
|
|
1221
1355
|
if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
|
|
1222
|
-
let
|
|
1356
|
+
let all_prohibited_strings_were_absent = await scope.reportDoesNotInclude(
|
|
1357
|
+
scope,
|
|
1358
|
+
{ not_expected: scope.expected_not_in_report }
|
|
1359
|
+
);
|
|
1360
|
+
if ( !all_prohibited_strings_were_absent ) { changeable_test_status = `FAILED`;}
|
|
1223
1361
|
// Reset report values no matter what so they don't mess up future scenarios
|
|
1224
1362
|
scope.expected_not_in_report = null;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if ( all_were_not_there ) {
|
|
1228
|
-
changeable_test_status = `PASSED`;
|
|
1229
|
-
} else {
|
|
1230
|
-
changeable_test_status = `FAILED`;
|
|
1231
|
-
}
|
|
1363
|
+
}
|
|
1232
1364
|
|
|
1365
|
+
// The default is 'passed', which won't need to get in here
|
|
1366
|
+
if ( scope.expected_status ) {
|
|
1367
|
+
// When internal tests should fail (to test our failure conditions), passing _is_ failing
|
|
1368
|
+
expect( changeable_test_status ).to.equal( scope.expected_status );
|
|
1369
|
+
// If the status was as expected, pass this Scenario
|
|
1370
|
+
changeable_test_status = `PASSED`;
|
|
1371
|
+
scope.expected_status = null; // reset for next Scenario
|
|
1233
1372
|
}
|
|
1234
1373
|
|
|
1374
|
+
// ---------------- Ends check internal test results of this framework ----------------
|
|
1375
|
+
|
|
1235
1376
|
// If there is a page open, then sign out and close it
|
|
1236
1377
|
if ( scope.page ) {
|
|
1237
1378
|
// Catch a possible reload error.
|
|
@@ -1259,23 +1400,19 @@ After(async function(scenario) {
|
|
|
1259
1400
|
|
|
1260
1401
|
} // ends if scope.page
|
|
1261
1402
|
|
|
1262
|
-
// Add report text to Scenario folder after everything has been added to the report
|
|
1263
|
-
let current_report_obj = scope.report.get( scope.scenario_id );
|
|
1264
|
-
let scenario_report_obj = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
1265
|
-
let report = reports.getPrintableScenario( scenario_report_obj );
|
|
1266
|
-
// Save the report as in the Scenario folder
|
|
1267
|
-
fs.writeFileSync( scope.paths.scenario_report, report );
|
|
1268
|
-
|
|
1269
1403
|
if ( changeable_test_status === `FAILED` ) {
|
|
1270
1404
|
// If the result object status wasn't failure and we still want
|
|
1271
1405
|
// the tests to fail, as with a11y tests, we can't mess with the
|
|
1272
1406
|
// cucumber result.status object because it will cause unwanted
|
|
1273
1407
|
// errors. We have to throw an error here instead.
|
|
1274
1408
|
if ( scenario.result.status !== `FAILED` ) {
|
|
1275
|
-
throw new Error(`The test failed. See the report for more information.`);
|
|
1409
|
+
throw new Error(`The test failed. See the report above for more information.`);
|
|
1276
1410
|
}
|
|
1277
1411
|
} else {
|
|
1278
|
-
//
|
|
1412
|
+
// Some internal test failures should be ok
|
|
1413
|
+
// TODO: Look into setDefinitionFunctionWrapper to catch errors
|
|
1414
|
+
// of failing tests that should indeed be failing:
|
|
1415
|
+
// https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/api_reference.md#setdefinitionfunctionwrapperwrapper
|
|
1279
1416
|
scenario.result.status = changeable_test_status;
|
|
1280
1417
|
}
|
|
1281
1418
|
});
|
package/lib/utils/log.js
CHANGED
|
@@ -40,14 +40,25 @@ log.error = function ({ type='', pre='', data='', post='' }) {
|
|
|
40
40
|
if ( post ) { console.error( post, `\n` ); }
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
log.debug = function ({ type='', pre='', data='', post='' }) {
|
|
43
|
+
log.debug = function ({ type='', pre='', data='', post='', verbose=false }) {
|
|
44
44
|
/* If debugging is turned on, log a message that includes `ALKiln` and `debug` */
|
|
45
|
-
if ( session_vars.get_debug() ) {
|
|
45
|
+
if ( session_vars.get_debug() || verbose ) {
|
|
46
46
|
let start = create_msg_start([ type, 'debug' ], pre );
|
|
47
|
+
let full = `\n${ start }`;
|
|
47
48
|
// Log them each on a separate line
|
|
48
|
-
console.log(
|
|
49
|
-
if ( data ) {
|
|
50
|
-
|
|
49
|
+
console.log( `\n${ start }` );
|
|
50
|
+
if ( data ) {
|
|
51
|
+
full += `\n${ data }`;
|
|
52
|
+
console.log( data );
|
|
53
|
+
}
|
|
54
|
+
if ( post ) {
|
|
55
|
+
full += `\n${ post }`;
|
|
56
|
+
console.log( post );
|
|
57
|
+
}
|
|
58
|
+
// TODO: Make a verbose log instead of adding to the debug one and always add to it
|
|
59
|
+
if ( verbose ) {
|
|
60
|
+
log.add_to_debug_log( full );
|
|
61
|
+
}
|
|
51
62
|
}
|
|
52
63
|
};
|
|
53
64
|
|