@suffolklitlab/alkiln 5.17.1 → 5.17.2
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/al_da_image_latest.yml +64 -3
- package/.github/workflows/github_server.yml +1 -1
- package/.github/workflows/playground.yml +1 -1
- package/CHANGELOG.md +38 -3
- package/docs/decisions/017_post_navigation_custom_behavior_2026_10_26.md +136 -0
- package/lib/scope.js +652 -234
- package/lib/steps.js +137 -60
- package/package.json +1 -1
- package/tests/log_codes/log_code_expected_instances.txt +10 -1
- package/tests/unit_tests/navigation.test.js +72 -0
package/lib/steps.js
CHANGED
|
@@ -117,6 +117,7 @@ BeforeAll(async function() {
|
|
|
117
117
|
Before(async (scenario) => {
|
|
118
118
|
|
|
119
119
|
// Start the running "progress bar" for the Scenario
|
|
120
|
+
log.debug({ code: `ALK0289`, context: `before`, }, `%%%%%%%%%%%%%%%%%%`);
|
|
120
121
|
log.stdout({}, `\nScenario: ${ scenario.pickle.name }: `);
|
|
121
122
|
|
|
122
123
|
// Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
|
|
@@ -400,7 +401,7 @@ When(/I? ?gets? to ?.*?(".*").*?with.*?(?:this data:)?/i, {timeout: -1}, async (
|
|
|
400
401
|
// If we need to call recursively, signature function might look like:
|
|
401
402
|
// func( scope, { resolve, reject, supported_table, page_timeout_id })
|
|
402
403
|
// Maybe list of timeout and/or interval ids in state that can all be cleared at once?
|
|
403
|
-
(async function() {
|
|
404
|
+
let error_data = (async function() {
|
|
404
405
|
try {
|
|
405
406
|
let id = await scope.get_page_id( scope );
|
|
406
407
|
|
|
@@ -416,28 +417,34 @@ When(/I? ?gets? to ?.*?(".*").*?with.*?(?:this data:)?/i, {timeout: -1}, async (
|
|
|
416
417
|
`Rows, including special rows:`, ensured_var_data
|
|
417
418
|
);
|
|
418
419
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
let error_data = await scope.setFields(scope, {
|
|
421
|
+
var_data: ensured_var_data, id, ensure_navigation: true
|
|
422
|
+
});
|
|
423
|
+
clearTimeout( page_timeout_id ); // prevent temporary hang at end of tests
|
|
422
424
|
|
|
423
425
|
// Story Table assumes we require navigation while other Steps don't.
|
|
424
426
|
// Throw here in case something sneaks through.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
});
|
|
428
|
-
|
|
427
|
+
await scope.throw_on_system_error( scope, { id, error_data });
|
|
428
|
+
await scope.throw_on_invalid_answers( scope, { id, error_data });
|
|
429
|
+
await scope.throw_on_non_navigation_timeout( scope, { id, timed_out: false });
|
|
430
|
+
await scope.throw_on_non_navigation_catchall( scope, { id });
|
|
431
|
+
resolve( error_data );
|
|
432
|
+
|
|
429
433
|
} catch ( err ) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
+
// Catching here allows us to clear the timeout if needed
|
|
435
|
+
clearTimeout( page_timeout_id ); // prevent temporary hang at end of tests
|
|
436
|
+
log.debug({
|
|
437
|
+
code: `ALK0062`, level: `error`, error: err, context: `story_table`,
|
|
438
|
+
}, `ALKiln ran into an error when it tried to answer fields on the page with the id "${ id }"` );
|
|
439
|
+
throw err;
|
|
440
|
+
|
|
434
441
|
} finally {
|
|
435
442
|
clearTimeout( page_timeout_id ); // prevent temporary hang at end of tests
|
|
436
443
|
}
|
|
437
444
|
})();
|
|
438
|
-
}); // ends custom_timeout for
|
|
445
|
+
}); // ends custom_timeout for filling in fields
|
|
439
446
|
|
|
440
|
-
await custom_timeout;
|
|
447
|
+
let page_result = await custom_timeout;
|
|
441
448
|
|
|
442
449
|
// Prep for next loop
|
|
443
450
|
id = await scope.get_page_id( scope );
|
|
@@ -529,7 +536,7 @@ Then(/the (?:question|page|screen) id should be "([^"]+)"/i, { timeout: -1 }, as
|
|
|
529
536
|
let { question_has_id, id, id_matches } = await scope.examinePageID( scope, question_id );
|
|
530
537
|
|
|
531
538
|
// No question id exists
|
|
532
|
-
let no_id_msg = `Did not find any question id
|
|
539
|
+
let no_id_msg = `Did not find any question id`;
|
|
533
540
|
if ( !question_has_id ) {
|
|
534
541
|
reports.addToReport( scope, { type: `error`, code: `ALK0070`, value: no_id_msg });
|
|
535
542
|
}
|
|
@@ -625,44 +632,20 @@ Then('an element should have the id {string}', async ( id ) => { // √
|
|
|
625
632
|
// await scope.afterStep(scope);
|
|
626
633
|
// });
|
|
627
634
|
|
|
628
|
-
|
|
629
|
-
Then(/I (don't|can't|don’t|can’t|cannot|do not) continue/, async (unused) => { // √
|
|
630
|
-
/** Press to continue. Succeeds when navigation then fails. */
|
|
631
|
-
|
|
632
|
-
// Must catch "failure to navigate"-type error.
|
|
633
|
-
// TODO: Explore using .continue + .checkForError (see "invalid answer" Step)
|
|
634
|
-
try {
|
|
635
|
-
let error_data = await scope.setFields( scope, { var_data: [], ensure_navigation: true });
|
|
636
|
-
// TODO: Add test for excluding AL..075 msg when Step succeeds
|
|
637
|
-
if ( scope.navigated ) {
|
|
638
|
-
let report_msg = reports.addToReport(scope, { type: `error`, code: `ALK0075`,
|
|
639
|
-
value: `The page should have stopped the user from continuing, but the user was able to continue.`
|
|
640
|
-
});
|
|
641
|
-
expect( scope.navigated, report_msg ).to.be.false;
|
|
642
|
-
}
|
|
635
|
+
Then(/I will be told (?:an answer is)?(?:some )?(?:all )?(?:answers are)? invalid/i, async () => { // √
|
|
643
636
|
|
|
644
|
-
}
|
|
645
|
-
// Discuss: Too deeply coupled? Alternatives?
|
|
646
|
-
if ( !continue_attempt_error.message?.includes(`ALK0148`) ) {
|
|
647
|
-
throw continue_attempt_error;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
await scope.afterStep(scope);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
Then('I will be told an answer is invalid', async () => { // √
|
|
655
|
-
|
|
656
|
-
let { ok, type, error_handlers } = await scope.checkForError( scope );
|
|
637
|
+
let { ok, type } = await scope.checkForErrors( scope );
|
|
657
638
|
|
|
658
639
|
let report_msg = null;
|
|
659
|
-
if ( ok ) {
|
|
660
|
-
|
|
661
|
-
|
|
640
|
+
if ( ok ) {
|
|
641
|
+
report_msg = reports.addToReport(scope, { type: `error`, code: `ALK0076`, value: `Sorry, you expected one or more invalid answers, but ALKiln only sees valid answers.` });
|
|
642
|
+
throw Error( report_msg );
|
|
643
|
+
}
|
|
662
644
|
if ( type !== `user` ) {
|
|
663
645
|
report_msg = reports.addToReport(scope, { type: `error`, code: `ALK0077`,
|
|
664
646
|
value: `The error was a system error, not an invalid user answer. Check for the picture of the page's error if the Step did not use any secret variables.`
|
|
665
647
|
});
|
|
648
|
+
throw Error( report_msg );
|
|
666
649
|
}
|
|
667
650
|
expect( type, report_msg ).to.equal( `user` );
|
|
668
651
|
|
|
@@ -826,13 +809,105 @@ Then(/the "([^"]+)" link opens a working page/, { timeout: -1 }, async ( linkTex
|
|
|
826
809
|
);
|
|
827
810
|
});
|
|
828
811
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
812
|
+
Then(/I continue(?: to the next )?(?:page|screen)?/i, i_continue );
|
|
813
|
+
async function i_continue() {
|
|
814
|
+
/**
|
|
815
|
+
* Press to continue. If navigation fails, tests fail. This Step implicitly
|
|
816
|
+
* assumes the user is able to continue. Some reasons navigation can fail:
|
|
817
|
+
*
|
|
818
|
+
* - Invalid answers (client-side or `validation code`)
|
|
819
|
+
* - Missing continue button
|
|
820
|
+
* - System error
|
|
821
|
+
* - Timeout
|
|
822
|
+
* - Infinite loop page (unsatisfied `show if:`) (in future implementation)
|
|
823
|
+
* - Others?
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* And I continue
|
|
827
|
+
* @example
|
|
828
|
+
* And I continue to the next page
|
|
829
|
+
* @example
|
|
830
|
+
* And I continue to the next screen
|
|
831
|
+
*
|
|
832
|
+
* */
|
|
833
|
+
let error_data = await scope.continue( scope );
|
|
834
|
+
await scope.afterStep(scope);
|
|
835
|
+
}; // Ends i_continue()
|
|
832
836
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
837
|
+
Then(/I (don't|don’t|do not|can’t|can't|cannot|(?:am unable to)) continue/, async (_unused) => {
|
|
838
|
+
/**
|
|
839
|
+
* Press to continue. If navigation fails in the following ways, succeed:
|
|
840
|
+
*
|
|
841
|
+
* - Invalid answers (client-side or `validation code`)
|
|
842
|
+
* - Missing continue button
|
|
843
|
+
* - Can we think of any others?
|
|
844
|
+
*
|
|
845
|
+
* The following failures to navigate should still fail:
|
|
846
|
+
*
|
|
847
|
+
* - System error
|
|
848
|
+
* - Timeout
|
|
849
|
+
* - Infinite loop page (unsatisfied `show if:`) (in future implementation)
|
|
850
|
+
* - Others?
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* And I can't continue
|
|
854
|
+
* @example
|
|
855
|
+
* And I can’t continue
|
|
856
|
+
* @example
|
|
857
|
+
* And I am unable to continue
|
|
858
|
+
* */ `I do have to try/catch. Right? No? Why would scope.navigated be not False?`
|
|
859
|
+
let error_data = await scope.continue(
|
|
860
|
+
scope, { post_nav_handlers: [
|
|
861
|
+
scope.throw_on_system_error,
|
|
862
|
+
scope.throw_on_non_navigation_timeout,
|
|
863
|
+
]}
|
|
864
|
+
);
|
|
865
|
+
if ( scope.navigated ) {
|
|
866
|
+
let report_msg = reports.addToReport(scope, { type: `error`, code: `ALK0075`,
|
|
867
|
+
value: `The page should have stopped the user from continuing, but the user was able to continue.`
|
|
868
|
+
});
|
|
869
|
+
throw Error( report_msg );
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
await scope.afterStep(scope);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
When(/I tap to continue/i, async () => {
|
|
876
|
+
/**
|
|
877
|
+
* This functionality supports legacy behavior. Deprecate this behavior and
|
|
878
|
+
* break it in v6. In v6 this Step will stop suppressing errors completely.
|
|
879
|
+
* The current "I can't continue" Step will fully take over this behavior.
|
|
880
|
+
* The current behavior:
|
|
881
|
+
*
|
|
882
|
+
* Press to continue. If navigation fails in the following ways, succeed:
|
|
883
|
+
*
|
|
884
|
+
* - Invalid answers (client-side or `validation code`)
|
|
885
|
+
* - Can we think of any others?
|
|
886
|
+
*
|
|
887
|
+
* The following failures to navigate should still fail:
|
|
888
|
+
*
|
|
889
|
+
* - Missing continue button
|
|
890
|
+
* - System error
|
|
891
|
+
* - Timeout
|
|
892
|
+
* - Infinite loop page (unsatisfied `show if:`) (in future implementation)
|
|
893
|
+
* - Others?
|
|
894
|
+
* */
|
|
895
|
+
|
|
896
|
+
await scope.continue(
|
|
897
|
+
scope, { post_nav_handlers: [
|
|
898
|
+
scope.throw_on_system_error, // Throw system errors if any first
|
|
899
|
+
// Author may not expect success with invalid answers. Warn them.
|
|
900
|
+
scope.warn_on_invalid_answers,
|
|
901
|
+
scope.throw_on_non_navigation_timeout,
|
|
902
|
+
scope.warn_on_non_navigation_catchall,
|
|
903
|
+
]}
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
reports.addToReport( scope, { type: `warning`, code: `ALK0294`,
|
|
907
|
+
value: `You are using the old "I tap to continue" Step. We recommend you switch to a newer Step. Use "I continue" if you need to continue to the next page. Use "I can't continue" if you want to check for invalid answer messages or if there is no continue button.`
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
await scope.afterStep(scope);
|
|
836
911
|
});
|
|
837
912
|
|
|
838
913
|
When(/I tap the "([^"]+)" tab/i, { timeout: -1 }, async ( tabId ) => {
|
|
@@ -855,7 +930,7 @@ When(/I tap the "([^"]+)" element and wait (?:for )?([0-9]+) second(?:s)?/i, { t
|
|
|
855
930
|
// Also, this is always supposed to navigate, and that handles its own waiting.
|
|
856
931
|
|
|
857
932
|
// `page` timeout will deal with custom timeout
|
|
858
|
-
await scope.tapSelectorToNavigate( scope, elemSelector, waitTime * 1000 )
|
|
933
|
+
await scope.tapSelectorToNavigate( scope, elemSelector, waitTime * 1000 );
|
|
859
934
|
});
|
|
860
935
|
|
|
861
936
|
When(/I check the page for (?:accessibility|a11y) issues/i, {timeout: -1}, async () => {
|
|
@@ -1464,18 +1539,20 @@ After(async function(scenario) {
|
|
|
1464
1539
|
// test), it failed. If is was supposed to pass and failed, it also
|
|
1465
1540
|
// failed. E.g. a11y failures let the Scenario pass initially, but
|
|
1466
1541
|
// should cause a failure here.
|
|
1467
|
-
|
|
1468
|
-
//
|
|
1469
|
-
// it as failed too.
|
|
1470
|
-
expected_status_was_met = false;
|
|
1471
|
-
// Keep it general. Users may see this message, e.g. for a11y tests.
|
|
1542
|
+
|
|
1543
|
+
// Keep this msg general. Users may see this message, e.g. for a11y tests.
|
|
1472
1544
|
let msg = `Test ${ changeable_test_status }. `
|
|
1473
|
-
+ `Expected status: ${scope.expected_status}. `
|
|
1545
|
+
+ `Expected status: ${ scope.expected_status }. `
|
|
1474
1546
|
+ `See the report for details.`;
|
|
1475
1547
|
let code = `ALK0099`;
|
|
1476
1548
|
expected_status_err_msg = log.debug({ code: code, level: `error`, },
|
|
1477
1549
|
msg
|
|
1478
1550
|
);
|
|
1551
|
+
|
|
1552
|
+
// Flag this failure to trigger an error later, so cucumber shows
|
|
1553
|
+
// it as failed too.
|
|
1554
|
+
changeable_test_status = `FAILED`;
|
|
1555
|
+
expected_status_was_met = false;
|
|
1479
1556
|
}
|
|
1480
1557
|
}
|
|
1481
1558
|
scope.expected_status = null; // reset for next Scenario
|
|
@@ -1609,7 +1686,7 @@ After(async function(scenario) {
|
|
|
1609
1686
|
|
|
1610
1687
|
// Find the race condition.
|
|
1611
1688
|
log.debug({ code: `ALK0100`, context: `scenario`, },
|
|
1612
|
-
`Scenario After() message:`, scenario.result.message
|
|
1689
|
+
`%%%%%%%%%%%%%%%%%%`, `Scenario After() message:`, scenario.result.message
|
|
1613
1690
|
);
|
|
1614
1691
|
|
|
1615
1692
|
});
|
package/package.json
CHANGED
|
@@ -160,7 +160,7 @@ ALK0144:1
|
|
|
160
160
|
ALK0145:1
|
|
161
161
|
ALK0146:1
|
|
162
162
|
ALK0147:1
|
|
163
|
-
ALK0148:
|
|
163
|
+
ALK0148:1
|
|
164
164
|
ALK0149:1
|
|
165
165
|
ALK0150:1
|
|
166
166
|
ALK0151:1
|
|
@@ -301,3 +301,12 @@ ALK0285:1
|
|
|
301
301
|
ALK0286:1
|
|
302
302
|
ALK0287:1
|
|
303
303
|
ALK0288:1
|
|
304
|
+
ALK0289:1
|
|
305
|
+
ALK0290:1
|
|
306
|
+
ALK0291:1
|
|
307
|
+
ALK0292:1
|
|
308
|
+
ALK0293:1
|
|
309
|
+
ALK0294:1
|
|
310
|
+
ALK0295:1
|
|
311
|
+
ALK0296:1
|
|
312
|
+
ALK0297:1
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const chai = require('chai');
|
|
2
|
+
const expect = chai.expect;
|
|
3
|
+
const fs = require(`fs`);
|
|
4
|
+
|
|
5
|
+
const scope = require(`../../lib/scope.js`);
|
|
6
|
+
|
|
7
|
+
scope.log_and_update_if_new_page_id = async function() {
|
|
8
|
+
/** Mock this `scope` function to avoid extraneous errors. */
|
|
9
|
+
return `mock-page-id`;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe(`The deepest navigation logic`, function () {
|
|
13
|
+
|
|
14
|
+
it(`will handle a missing required system error handler by throwing ALK0295`, async function() {
|
|
15
|
+
let error = null
|
|
16
|
+
try {
|
|
17
|
+
await scope.wait_until_continued( scope, { post_nav_handlers: [
|
|
18
|
+
scope.throw_on_invalid_answers,
|
|
19
|
+
scope.throw_on_non_navigation_timeout,
|
|
20
|
+
] });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
error = err;
|
|
23
|
+
}
|
|
24
|
+
expect( error instanceof ReferenceError, `Not a ReferenceError` ).to.be.true;
|
|
25
|
+
expect( error.message.includes(`ALK0295`), `Missing log code ALK0295` ).to.be.true;
|
|
26
|
+
expect( error.message.includes(`ALK0296`), `Unwanted log code ALK0296 is present`).to.be.false;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it(`will handle a missing required navigation timeout handler by throwing ALK0296`, async function() {
|
|
30
|
+
let error = null
|
|
31
|
+
try {
|
|
32
|
+
await scope.wait_until_continued( scope, { post_nav_handlers: [
|
|
33
|
+
scope.throw_on_system_error,
|
|
34
|
+
scope.throw_on_invalid_answers,
|
|
35
|
+
] });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
error = err;
|
|
38
|
+
}
|
|
39
|
+
expect( error instanceof ReferenceError, `Not a ReferenceError` ).to.be.true;
|
|
40
|
+
expect( error.message.includes(`ALK0295`), `Unwanted log code ALK0295 is present`).to.be.false;
|
|
41
|
+
expect( error.message.includes(`ALK0296`), `Missing log code ALK0296` ).to.be.true;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it(`will handle the absence of both critical error throwers by throwing ALK0295 and ALK0296`, async function() {
|
|
45
|
+
let error = null
|
|
46
|
+
try {
|
|
47
|
+
await scope.wait_until_continued( scope, { post_nav_handlers: [
|
|
48
|
+
scope.throw_on_invalid_answers,
|
|
49
|
+
] });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
error = err;
|
|
52
|
+
}
|
|
53
|
+
expect( error instanceof ReferenceError, `Not a ReferenceError` ).to.be.true;
|
|
54
|
+
expect( error.message.includes(`ALK0295`), `Missing log code ALK0295` ).to.be.true;
|
|
55
|
+
expect( error.message.includes(`ALK0296`), `Missing log code ALK0296` ).to.be.true;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it(`will not throw ALK0295 or ALK0296 if all required handlers are present`, async function() {
|
|
59
|
+
let error = null
|
|
60
|
+
try {
|
|
61
|
+
await scope.wait_until_continued( scope, { post_nav_handlers: [
|
|
62
|
+
scope.throw_on_system_error,
|
|
63
|
+
scope.throw_on_non_navigation_timeout,
|
|
64
|
+
] });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
error = err;
|
|
67
|
+
}
|
|
68
|
+
expect( error.message.includes(`ALK0295`), `Unwanted log code ALK0295 is present` ).to.be.false;
|
|
69
|
+
expect( error.message.includes(`ALK0296`), `Unwanted log code ALK0296 is present` ).to.be.false;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
});
|