@suffolklitlab/alkiln 5.13.0 → 5.13.1
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 +7 -5
- package/.github/workflows/playground.yml +5 -3
- package/CHANGELOG.md +21 -2
- package/CONTRIBUTING.md +98 -83
- 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/update_puppeteer_2024_07_28.md +62 -0
- package/lib/scope.js +113 -26
- package/lib/steps.js +15 -11
- package/lib/utils/reports.js +1 -1
- package/lib/utils/session_vars.js +1 -1
- package/npm-shrinkwrap.json +647 -201
- package/package.json +6 -6
- package/tests/log_codes/check_codes.sh +162 -108
- package/tests/log_codes/log_code_expected_instances.txt +209 -0
- package/tests/log_codes/deleted_codes.json +0 -8
package/lib/scope.js
CHANGED
|
@@ -171,7 +171,7 @@ module.exports = {
|
|
|
171
171
|
// If something might be hidden or shown, wait extra time to let it hide or show
|
|
172
172
|
// I think `.$()` does not use a timeout
|
|
173
173
|
let showif = await scope.page.$('.dashowif');
|
|
174
|
-
if ( showif ) { await
|
|
174
|
+
if ( showif ) { await time.waitForTimeout( scope.showif_timeout ); }
|
|
175
175
|
},
|
|
176
176
|
|
|
177
177
|
waitForTab: async function waitForTab(scope, { tab_id }) {
|
|
@@ -288,9 +288,7 @@ module.exports = {
|
|
|
288
288
|
/** Tap a given element, detect navigation, and do appropriate related things.
|
|
289
289
|
* `click_promise can be any kind of Promise, including a Promise.all()
|
|
290
290
|
*/
|
|
291
|
-
|
|
292
|
-
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0102`, value: msg }); }
|
|
293
|
-
expect( elem, msg ).to.exist;
|
|
291
|
+
await scope.guard_against_missing_tap_element(scope, { elem });
|
|
294
292
|
|
|
295
293
|
// Get the text on the button or link for a potential error message later
|
|
296
294
|
let tapperText = await (await elem.getProperty('textContent')).jsonValue();
|
|
@@ -326,6 +324,14 @@ module.exports = {
|
|
|
326
324
|
} // ends try/catch
|
|
327
325
|
}, // Ends scope.tapElementAndStayOnPage()
|
|
328
326
|
|
|
327
|
+
guard_against_missing_tap_element: async function(scope, { elem }) {
|
|
328
|
+
/** Handle the error message for a missing tap element, if it's missing. */
|
|
329
|
+
let msg = `Cannot find the element to tap.`;
|
|
330
|
+
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0102`, value: msg }); }
|
|
331
|
+
expect( elem, msg ).to.exist;
|
|
332
|
+
return elem;
|
|
333
|
+
},
|
|
334
|
+
|
|
329
335
|
// Handle server reload errors
|
|
330
336
|
server_statuses: [],
|
|
331
337
|
server_check_interval_ms: 1 * 1000,
|
|
@@ -501,7 +507,7 @@ module.exports = {
|
|
|
501
507
|
if ( diff <= 0 ) { return false; }
|
|
502
508
|
|
|
503
509
|
// If time hasn't expired, wait and then check again
|
|
504
|
-
await
|
|
510
|
+
await time.waitForTimeout( 100 );
|
|
505
511
|
await scope.detectDownloadComplete(scope, endTime);
|
|
506
512
|
|
|
507
513
|
return false;
|
|
@@ -1627,7 +1633,7 @@ module.exports = {
|
|
|
1627
1633
|
catch(err){ elem.style.background = 'Khaki'; }
|
|
1628
1634
|
return elem.getAttribute('for') || elem.getAttribute('name');
|
|
1629
1635
|
});
|
|
1630
|
-
await
|
|
1636
|
+
await time.waitForTimeout( 400 );
|
|
1631
1637
|
}
|
|
1632
1638
|
|
|
1633
1639
|
// We use the last match. We wrote a warning for the user if there were
|
|
@@ -1850,7 +1856,7 @@ module.exports = {
|
|
|
1850
1856
|
});
|
|
1851
1857
|
if ( type === `radio` || type === `checkbox` ) {
|
|
1852
1858
|
|
|
1853
|
-
let [label] = await handle
|
|
1859
|
+
let [label] = await handle.$$(`xpath/.//following-sibling::label`);
|
|
1854
1860
|
|
|
1855
1861
|
if ( session_vars.get_debug() ) {
|
|
1856
1862
|
if ( answer.toLowerCase() === 'false' ) { await label.evaluate( elem => { elem.style.background = 'tomato'; }); }
|
|
@@ -2213,7 +2219,7 @@ module.exports = {
|
|
|
2213
2219
|
} // ends for every non-submit-button field on the page
|
|
2214
2220
|
} // ends while fields are still being set
|
|
2215
2221
|
|
|
2216
|
-
if ( session_vars.get_debug() ) { await
|
|
2222
|
+
if ( session_vars.get_debug() ) { await time.waitForTimeout(2000); }
|
|
2217
2223
|
|
|
2218
2224
|
// Press a continue button with a story table variable name if it exists
|
|
2219
2225
|
let did_navigate = false;
|
|
@@ -2561,7 +2567,7 @@ module.exports = {
|
|
|
2561
2567
|
|
|
2562
2568
|
// Attempt to solve weird race condition issues where page has not properly continued
|
|
2563
2569
|
while ( curr_url === prev_url && !error.was_found && !timedOut ) {
|
|
2564
|
-
await
|
|
2570
|
+
await time.waitForTimeout( 50 );
|
|
2565
2571
|
curr_url = await scope.page.url();
|
|
2566
2572
|
error = await scope.checkForError( scope );
|
|
2567
2573
|
}
|
|
@@ -2582,10 +2588,10 @@ module.exports = {
|
|
|
2582
2588
|
|
|
2583
2589
|
// All results are lists to match `$x`
|
|
2584
2590
|
let possible_errors = {
|
|
2585
|
-
complex_user_error: await scope.page.$$(
|
|
2591
|
+
complex_user_error: await scope.page.$$(`.alert-danger`),
|
|
2586
2592
|
// The below does not work if there's not a space between ':' and 'none'
|
|
2587
|
-
simple_user_error: await scope.page
|
|
2588
|
-
system_error: await scope.page
|
|
2593
|
+
simple_user_error: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
|
|
2594
|
+
system_error: await scope.page.$$(`xpath/.//meta[@content="docassemble: Error"]` ),
|
|
2589
2595
|
};
|
|
2590
2596
|
|
|
2591
2597
|
let error_handlers = {};
|
|
@@ -2597,7 +2603,7 @@ module.exports = {
|
|
|
2597
2603
|
// Also, if you're debugging and you close the window early, the process will still
|
|
2598
2604
|
// take this long to exit after the tests finish. Not sure how to account for that.
|
|
2599
2605
|
if ( session_vars.get_debug() ) {
|
|
2600
|
-
await
|
|
2606
|
+
await time.waitForTimeout( ( scope.timeout - (1 * 1000)) );
|
|
2601
2607
|
}
|
|
2602
2608
|
was_found = true;
|
|
2603
2609
|
error_handlers[ error_key ] = possible_errors[ error_key ][0];
|
|
@@ -2702,7 +2708,7 @@ module.exports = {
|
|
|
2702
2708
|
}, // Ends scope.steps.add_json_vars_to_report()
|
|
2703
2709
|
|
|
2704
2710
|
link_works: async ( scope, linkText ) => {
|
|
2705
|
-
let [link] = await scope.page
|
|
2711
|
+
let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${ linkText }")]`);
|
|
2706
2712
|
let prop_obj = await link.getProperty( 'href' );
|
|
2707
2713
|
let actual_url = await prop_obj.jsonValue();
|
|
2708
2714
|
|
|
@@ -2760,13 +2766,13 @@ module.exports = {
|
|
|
2760
2766
|
/** Tap a link that doesn't navigate. Depends on the language of the text.
|
|
2761
2767
|
* TODO: This may be broken at this point.
|
|
2762
2768
|
* */
|
|
2763
|
-
const [link] = await scope.page
|
|
2769
|
+
const [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${phrase}")]`);
|
|
2764
2770
|
|
|
2765
2771
|
let msg = `The term "${ phrase }" seems to be missing.`;
|
|
2766
2772
|
if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0151`, value: msg }); }
|
|
2767
2773
|
expect( link, msg ).to.exist;
|
|
2768
2774
|
await scope.tapElementAndStayOnPage(scope, { elem: link });
|
|
2769
|
-
await
|
|
2775
|
+
await time.waitForTimeout(10000);
|
|
2770
2776
|
}, // Ends scope.steps.tap_term()
|
|
2771
2777
|
|
|
2772
2778
|
sign: async ( scope, name ) => {
|
|
@@ -2785,7 +2791,7 @@ module.exports = {
|
|
|
2785
2791
|
* TODO: Properly wait for download to complete. See notes in
|
|
2786
2792
|
* scope.js scope.detectDownloadComplete()
|
|
2787
2793
|
*/
|
|
2788
|
-
let [elem] = await scope.page
|
|
2794
|
+
let [elem] = await scope.page.$$(`xpath/.//a[contains(@href, "${ filename }")]`);
|
|
2789
2795
|
|
|
2790
2796
|
let msg = `"${ filename }" seems to be missing. Cannot find a link to that document.`;
|
|
2791
2797
|
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0152`, value: msg }); }
|
|
@@ -3017,20 +3023,101 @@ module.exports = {
|
|
|
3017
3023
|
expect( email, email_msg ).to.not.equal( undefined );
|
|
3018
3024
|
expect( password, password_msg ).to.not.equal( undefined );
|
|
3019
3025
|
|
|
3020
|
-
// Try to
|
|
3026
|
+
// Try to enter the information
|
|
3021
3027
|
await scope.page.type( `#email`, email );
|
|
3022
3028
|
await scope.page.type( `#password`, password );
|
|
3023
|
-
let elem = await scope.page.$( `button[
|
|
3024
|
-
await scope.
|
|
3029
|
+
let elem = await scope.page.$( `button[type="submit"]` );
|
|
3030
|
+
await scope.guard_against_missing_tap_element(scope, { elem });
|
|
3025
3031
|
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3032
|
+
// Submit and see what happens
|
|
3033
|
+
let winner = await scope.steps.race_sign_in_navigation( scope, { elem });
|
|
3034
|
+
// Add the result to the report and possibly throw errors
|
|
3035
|
+
if ( winner[0] === `success` ) {
|
|
3036
|
+
reports.addToReport( scope, {
|
|
3037
|
+
type: `row info`, code: `ALK0163`,
|
|
3038
|
+
value: `Successfully signed into ${ login_url }`
|
|
3039
|
+
});
|
|
3040
|
+
await scope.afterStep(scope, {waitForShowIf: false});
|
|
3041
|
+
} else if ( winner[0] === `failure` ) {
|
|
3042
|
+
let error_msg = reports.addToReport( scope, {
|
|
3043
|
+
type: `error`, code: `ALK0209`,
|
|
3044
|
+
value: `Failed to sign into ${ login_url }. Make sure you followed the instructions at https://assemblyline.suffolklitlab.org/docs/alkiln/writing/#sign-in.`
|
|
3045
|
+
});
|
|
3046
|
+
throw new Error( error_msg );
|
|
3047
|
+
} else if ( winner[0] === `error` ) {
|
|
3048
|
+
let error_msg = reports.addToReport( scope, {
|
|
3049
|
+
type: `error`, code: `ALK0210`,
|
|
3050
|
+
value: `Error on sign in page at ${ login_url }. Try this by hand and then run the test again.`
|
|
3051
|
+
});
|
|
3052
|
+
throw new Error( error_msg );
|
|
3053
|
+
}
|
|
3054
|
+
}, // Ends scope.steps.sign_in()
|
|
3055
|
+
|
|
3056
|
+
race_sign_in_navigation: async function ( scope, { elem }) {
|
|
3057
|
+
/** Wait for sign in navigation success or failure, or system error. */
|
|
3058
|
+
|
|
3059
|
+
// After everything, clean up incomplete promises
|
|
3060
|
+
const controller = new AbortController;
|
|
3061
|
+
|
|
3062
|
+
// Redirect
|
|
3063
|
+
let redirect_promise = scope.page.waitForResponse(function ( response ) {
|
|
3064
|
+
return response.status() === 302;
|
|
3065
|
+
}, { signal: controller.signal })
|
|
3066
|
+
.catch(( error ) => {
|
|
3067
|
+
log.debug({
|
|
3068
|
+
code: `ALK0204`, type: `info`,
|
|
3069
|
+
pre: `302 sign-in wait error: ${ error.name }`,
|
|
3070
|
+
data: error,
|
|
3071
|
+
});
|
|
3030
3072
|
});
|
|
3031
3073
|
|
|
3032
|
-
|
|
3033
|
-
|
|
3074
|
+
// Invalid credentials, no navigation, no sign in
|
|
3075
|
+
let wrong_sign_in_promise = scope.page.waitForResponse(function ( response ) {
|
|
3076
|
+
return response.status() >= 200 && response.status() < 300 && response.url().includes(`/sign-in`);
|
|
3077
|
+
}, { signal: controller.signal })
|
|
3078
|
+
.catch(( error ) => {
|
|
3079
|
+
log.debug({
|
|
3080
|
+
code: `ALK0205`, type: `info`,
|
|
3081
|
+
pre: `200s sign-in wait error: ${ error.name }`,
|
|
3082
|
+
data: error,
|
|
3083
|
+
});
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
// System error
|
|
3087
|
+
let error_promise = scope.page.waitForResponse(function ( response ) {
|
|
3088
|
+
return response.status() >= 500 && response.status() < 600;
|
|
3089
|
+
}, { signal: controller.signal })
|
|
3090
|
+
.catch(( error ) => {
|
|
3091
|
+
log.debug({
|
|
3092
|
+
code: `ALK0206`, type: `info`,
|
|
3093
|
+
pre: `500s sign-in wait error: ${ error.name }`,
|
|
3094
|
+
data: error,
|
|
3095
|
+
});
|
|
3096
|
+
});
|
|
3097
|
+
|
|
3098
|
+
let click_promise = elem.click(); // MUST complete
|
|
3099
|
+
const winner = await Promise.race([
|
|
3100
|
+
Promise.all([ `success`, click_promise, redirect_promise ]),
|
|
3101
|
+
Promise.all([ `failure`, click_promise, wrong_sign_in_promise ]),
|
|
3102
|
+
Promise.all([ `error`, click_promise, error_promise ]),
|
|
3103
|
+
]).catch(function ( error ) {
|
|
3104
|
+
let error_msg = reports.addToReport( scope, {
|
|
3105
|
+
type: `error`, code: `ALK0207`,
|
|
3106
|
+
value: `Unknown error waiting for results during sign in at ${ login_url }.`
|
|
3107
|
+
});
|
|
3108
|
+
throw new Error( error );
|
|
3109
|
+
});
|
|
3110
|
+
|
|
3111
|
+
// Clean up unresolved promises
|
|
3112
|
+
controller.abort();
|
|
3113
|
+
|
|
3114
|
+
log.debug({
|
|
3115
|
+
code: `ALK0208`, type: `info`,
|
|
3116
|
+
pre: `Sign-in winner:`, data: winner,
|
|
3117
|
+
});
|
|
3118
|
+
|
|
3119
|
+
return winner;
|
|
3120
|
+
},
|
|
3034
3121
|
|
|
3035
3122
|
set_random_page_vars: async ( scope ) => {
|
|
3036
3123
|
/** Answer inputs randomly. */
|
package/lib/steps.js
CHANGED
|
@@ -650,7 +650,7 @@ Then('I arrive at the next page', async () => { // √
|
|
|
650
650
|
// Link observations have the 'Escape' button link in mind
|
|
651
651
|
Then('I should see the link {string}', async ( linkText ) => { // √
|
|
652
652
|
/* Loosely match link visible text. Should probably deprecate this. */
|
|
653
|
-
let [link] = await scope.page
|
|
653
|
+
let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
|
|
654
654
|
|
|
655
655
|
let msg = `Cannot find a link with the text "${ linkText }".`;
|
|
656
656
|
if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0079`, value: msg }); }
|
|
@@ -672,7 +672,7 @@ Then('I should see the link to {string}', async ( url ) => { // √
|
|
|
672
672
|
|
|
673
673
|
Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) => { // √
|
|
674
674
|
/* Find link by its visible text and where it leads. */
|
|
675
|
-
let [link] = await scope.page
|
|
675
|
+
let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
|
|
676
676
|
// TODO: Possibly add a check here for the link text, though note this
|
|
677
677
|
// Step is not currently supported.
|
|
678
678
|
|
|
@@ -687,7 +687,7 @@ Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) =>
|
|
|
687
687
|
});
|
|
688
688
|
|
|
689
689
|
Then(/the "([^"]+)" link opens in (a new window|the same window)/, async (linkText, which_window) => { // √
|
|
690
|
-
let [link] = await scope.page
|
|
690
|
+
let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${linkText}")]`);
|
|
691
691
|
|
|
692
692
|
let prop_obj = await link.getProperty('target');
|
|
693
693
|
let target = await prop_obj.jsonValue();
|
|
@@ -1497,18 +1497,22 @@ After(async function(scenario) {
|
|
|
1497
1497
|
}
|
|
1498
1498
|
}
|
|
1499
1499
|
|
|
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
|
-
}
|
|
1500
|
+
// Collect all the relevant special errors
|
|
1501
|
+
let internal_errors = []
|
|
1502
|
+
if ( !expected_status_was_met ) { internal_errors.push( expected_status_err_msg ); }
|
|
1503
|
+
if ( !signout_succeeded ) { internal_errors.push( signout_error_msg ); }
|
|
1507
1504
|
|
|
1508
1505
|
// Theoretically, we should never get here:
|
|
1509
1506
|
// If everything else worked, if we need to force an internal test failure
|
|
1510
1507
|
if ( need_to_force_failed_status ) {
|
|
1511
|
-
|
|
1508
|
+
if ( internal_errors.length > 0 ) {
|
|
1509
|
+
// Handle all the accumulated errors
|
|
1510
|
+
throw new Error( `${internal_errors.join( internal_errors, '\n')}` );
|
|
1511
|
+
}
|
|
1512
|
+
throw new Error( `🤕 ${ force_failure_code } unreachable ERROR: ${ force_failure_msg }` );
|
|
1513
|
+
|
|
1514
|
+
} else if ( internal_errors.length > 0 ) {
|
|
1515
|
+
log.info({ type: `internal error`, code: `ALK0203`, pre: `No need to force failed status and yet there are error messages. ${ errors.join( errors, '\n') }` });
|
|
1512
1516
|
}
|
|
1513
1517
|
|
|
1514
1518
|
// Find the race condition.
|
package/lib/utils/reports.js
CHANGED
|
@@ -81,7 +81,7 @@ reports.addToReport = function( scope, { type, code, value }) {
|
|
|
81
81
|
let scenario = reports.makeSureScenarioExists( scope );
|
|
82
82
|
scenario.lines.push({ type, code, value });
|
|
83
83
|
|
|
84
|
-
return
|
|
84
|
+
return getLineText({ type, code, value });
|
|
85
85
|
}; // Ends reports.addToReport()
|
|
86
86
|
|
|
87
87
|
reports.create = function( scope ) {
|
|
@@ -242,7 +242,7 @@ session_vars.get_runtime_config_var = function ( key ) {
|
|
|
242
242
|
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
|
|
243
243
|
let value = json[ key ];
|
|
244
244
|
if ( session_vars.get_debug() ) {
|
|
245
|
-
console.log( `🐞 ALK0041 DEBUG: Runtime config var "${key}"
|
|
245
|
+
console.log( `🐞 ALK0041 DEBUG: Runtime config var "${key}" is "${value}".` );
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
return value;
|