@suffolklitlab/alkiln 5.16.1-always-html-6 → 5.17.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.
@@ -31,9 +31,6 @@ jobs:
31
31
  && github.event.pull_request.head.repo.fork
32
32
  )
33
33
  runs-on: ubuntu-latest
34
- strategy:
35
- matrix:
36
- node-version: [24.x]
37
34
 
38
35
  steps:
39
36
  # Place the root directory in this branch to access relative paths to
@@ -36,9 +36,6 @@ jobs:
36
36
  && github.event.pull_request.head.repo.fork
37
37
  )
38
38
  runs-on: ubuntu-latest
39
- strategy:
40
- matrix:
41
- node-version: [24.x]
42
39
 
43
40
  #### Developer note: You can probably leave out these specific
44
41
  #### environment variables. You may not even need the `env`
@@ -160,7 +157,7 @@ jobs:
160
157
  #### Developer note: You can probably leave the rest out
161
158
  #### To learn more, see https://assemblyline.suffolklitlab.org/docs/alkiln/writing/#optional-inputs
162
159
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
163
- ALKILN_VERSION: "screenshot"
160
+ # ALKILN_VERSION:
164
161
 
165
162
  #### Developer note: Example of making an issue when tests fail
166
163
  #### that includes the text of the failure output file
@@ -27,9 +27,6 @@ jobs:
27
27
  && github.event.pull_request.head.repo.fork
28
28
  )
29
29
  runs-on: ubuntu-latest
30
- strategy:
31
- matrix:
32
- node-version: [24.x]
33
30
 
34
31
  env:
35
32
  #### Developer note: We recommend turning these into environment
@@ -83,4 +80,4 @@ jobs:
83
80
  # want to check up on this.
84
81
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
85
82
  #### Developer note: You can probably leave this out
86
- ALKILN_VERSION: "screenshot"
83
+ # ALKILN_VERSION:
package/CHANGELOG.md CHANGED
@@ -45,22 +45,21 @@ Format:
45
45
 
46
46
  ## [Unreleased]
47
47
 
48
+ ## [5.17.0] - 2026-09-10
49
+
48
50
  ### Changed
49
51
 
50
- - On pages with sensitive answers, store the HTML of the page. The HTML excludes field values, so those sensitive answers will not be in the saved file. Still avoid taking a pic of the screen, which would reveal sensitive answers. ‼️ NEVER USE REAL USERS' ANSWERS IN ALKILN TESTS. This HTML can still reveal information about a user's answers. For example, some answers will reveal new questions. That will change the code of the revealed fields and that code will be in the HTML. See #1099
51
52
  - GitHub action release: Restored our GitHub action's default for ALKiln version to the latest version 5 again. Released first on GitHub actions. NPM release will come in time, but npm has no impact on GitHub action releases.
52
53
 
53
54
  ### Fixed
54
55
 
55
- - Updated docassemble cli version.
56
- - Better detect navigation when email and password fail to sign in to a docassemble server account.
56
+ - Fixed GitHub+You action outdated docassemble cli version causing Scenario timeouts.
57
+ - Part 1 of detecting custom AL error screen: Story Table now catches `al_custom_error_screen` error. Previously, Story Tables that ran into this screen would get stuck on the AL error page infinitely. Next steps: Linear Steps may need additional development. Currently linear Steps succeed even when they run into AL's error page. That bears additional discussion. See [issue #656](https://github.com/SuffolkLITLab/ALKiln/issues/656) and [PR #1116](https://github.com/SuffolkLITLab/ALKiln/pull/1116).
57
58
 
58
59
  ### Internal
59
60
 
60
- - Break out the function in `Before()` and `After()` to attempt better error tracing. Goal: try to repeat this for other functions in that file.
61
+ - Simplify workflow files by removing `node` env setup (leaving it to the actions)
61
62
  - Updated both of our actions' dependencies (the checkout, setup-node, setup-python, upload-artifacts, download-artifacts actions). Closes [#1095](https://github.com/suffolkLITLab/aLKiln/issues/1095). Once again, action related.
62
- - When our report phrases are missing, list all missing phrases at one time in our custom message. The cucumber message will stay the same.
63
- - Silence errors from screenshots & HTML downloads when trying to provide more information about failing tests. Those records are nice to have, but not absolutely necessary, and if a server is busy puppeteer will rightly have lost execution context, preventing interactions with the page and we don't need a noisy error about it. Log any problems.
64
63
 
65
64
  ## [5.16.1] - 2026-06-01
66
65
 
package/lib/scope.js CHANGED
@@ -2673,6 +2673,8 @@ module.exports = {
2673
2673
  // The below does not work if there's not a space between ':' and 'none'
2674
2674
  simple_user_error: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
2675
2675
  system_error: await scope.page.$$(`xpath/.//meta[@content="docassemble: Error"]` ),
2676
+ // Hardcoding the `al_custom_error_action` screen (that's what the base64 here is). It hides actual errors and looks like a continue screen, but isn't.
2677
+ al_custom_error: await scope.page.$$('.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]'),
2676
2678
  };
2677
2679
 
2678
2680
  let error_handlers = {};
@@ -2711,23 +2713,8 @@ module.exports = {
2711
2713
  return scope;
2712
2714
  }, // Ends scope.throwPageError()
2713
2715
 
2714
- take_a_screenshot: async ( scope, {
2715
- path, disable_pic=false, disable_html=false
2716
- }) => {
2717
- /**
2718
- * Saves a jpeg screenshot and page HTML to `path`. Avoids destroying
2719
- * signatures.
2720
- *
2721
- * @param {obj} scope - State and internal functions
2722
- * @param {obj} obj - Named arguments
2723
- * @param {str} obj.path - name of path where to save the files
2724
- * @param {bool} [obj.disable_pic=false] - Optional. Whether to disable a
2725
- * puppeteer screenshot
2726
- * @param {bool} [obj.disable_html=false] - Optional. Whether to disable
2727
- * saving the HTML of the page
2728
- *
2729
- * @returns undefined
2730
- * */
2716
+ take_a_screenshot: async ( scope,{ path }) => {
2717
+ /* Takes a jpeg screenshot. Avoids destroying signatures. */
2731
2718
 
2732
2719
  let fullPage = true;
2733
2720
  let signature_elem = await scope.page.$(scope.signature_selector);
@@ -2736,32 +2723,28 @@ module.exports = {
2736
2723
  fullPage = false;
2737
2724
  }
2738
2725
 
2739
- if ( !disable_pic ){
2740
- await scope.page.screenshot({
2741
- path: path,
2742
- type: 'jpeg',
2743
- fullPage: fullPage,
2744
- });
2745
- }
2746
-
2747
- if ( !disable_html ) {
2748
- let html_path = path;
2749
- if (path.endsWith(".jpg")) {
2750
- html_path = html_path.substring(0, html_path.length - 4) + ".html";
2751
- } else {
2752
- html_path = html_path + ".html";
2753
- }
2726
+ await scope.page.screenshot({
2727
+ path: path,
2728
+ type: 'jpeg',
2729
+ fullPage: fullPage,
2730
+ });
2754
2731
 
2755
- // Also save the HTML of the page
2756
- await scope.page.content().then(content => {
2757
- let server_url = session_vars.get_da_server_url();
2758
- content = content.replaceAll(/"(\/static\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2759
- content = content.replaceAll(/"(\/static\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2760
- content = content.replaceAll(/"(\/packagestatic\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2761
- content = content.replaceAll(/"(\/packagestatic\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2762
- fs.writeFileSync(html_path, content)
2763
- });
2732
+ let html_path = path;
2733
+ if (path.endsWith(".jpg")) {
2734
+ html_path = html_path.substring(0, html_path.length - 4) + ".html";
2735
+ } else {
2736
+ html_path = html_path + ".html";
2764
2737
  }
2738
+
2739
+ // Also save the HTML of the page
2740
+ await scope.page.content().then(content => {
2741
+ let server_url = session_vars.get_da_server_url();
2742
+ content = content.replaceAll(/"(\/static\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2743
+ content = content.replaceAll(/"(\/static\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2744
+ content = content.replaceAll(/"(\/packagestatic\/.*\.css\?v=[^"]+")/g, server_url + "$1");
2745
+ content = content.replaceAll(/"(\/packagestatic\/.*\.js\?v=[^"]+")/g, server_url + "$1");
2746
+ fs.writeFileSync(html_path, content)
2747
+ });
2765
2748
  }, // Ends scope.take_a_screenshot()
2766
2749
 
2767
2750
 
@@ -3170,7 +3153,7 @@ module.exports = {
3170
3153
  await scope.guard_against_missing_tap_element(scope, { elem });
3171
3154
 
3172
3155
  // Submit and see what happens
3173
- let winner = await scope.steps.race_sign_in_navigation( scope, { elem, login_url });
3156
+ let winner = await scope.steps.race_sign_in_navigation( scope, { elem });
3174
3157
  // Add the result to the report and possibly throw errors
3175
3158
  if ( winner[0] === `success` ) {
3176
3159
  reports.addToReport( scope, {
@@ -3193,7 +3176,7 @@ module.exports = {
3193
3176
  }
3194
3177
  }, // Ends scope.steps.sign_in()
3195
3178
 
3196
- race_sign_in_navigation: async function ( scope, { elem, login_url='unknown' }) {
3179
+ race_sign_in_navigation: async function ( scope, { elem }) {
3197
3180
  /** Wait for sign in navigation success or failure, or system error. */
3198
3181
 
3199
3182
  // After everything, clean up incomplete promises
@@ -3230,24 +3213,20 @@ module.exports = {
3230
3213
  });
3231
3214
 
3232
3215
  let click_promise = elem.click(); // MUST complete
3233
- let nav_promise = scope.nav_race(scope, {}).result;
3234
3216
  const winner = await Promise.race([
3235
3217
  Promise.all([ `success`, click_promise, redirect_promise ]),
3236
3218
  Promise.all([ `failure`, click_promise, wrong_sign_in_promise ]),
3237
3219
  Promise.all([ `error`, click_promise, error_promise ]),
3238
- ]).catch(async ( error ) => {
3220
+ ]).catch(function ( error ) {
3239
3221
  let error_msg = reports.addToReport( scope, {
3240
3222
  type: `error`, code: `ALK0207`,
3241
3223
  value: `Unknown error waiting for results during sign in at ${ login_url }.`
3242
3224
  });
3243
- throw error;
3225
+ throw new Error( error );
3244
3226
  });
3245
3227
 
3246
3228
  // Clean up unresolved promises
3247
3229
  controller.abort();
3248
- // Wait to finish navigating
3249
- // TODO: Test the winner 'error' under this circumstance
3250
- await nav_promise;
3251
3230
 
3252
3231
  log.debug({ code: `ALK0208`, context: `nav` },
3253
3232
  `Sign-in winner:`, winner
@@ -3521,15 +3500,14 @@ module.exports = {
3521
3500
  let scenario = scope.report.get( scope.scenario_id );
3522
3501
  let report = reports.getPrintableScenario( scenario );
3523
3502
  let all_are_included = true;
3524
- let missing = [];
3525
3503
  for ( let one_expectation of expected ) {
3526
3504
  if ( !report.includes( one_expectation )) {
3527
3505
  all_are_included = false;
3528
- missing.push( one_expectation );
3506
+ expect( report ).to.contain( one_expectation );
3529
3507
  }
3530
3508
  }
3531
3509
 
3532
- return { all_included: all_are_included, missing };
3510
+ return all_are_included;
3533
3511
  }, // Ends scope.reportIncludesAllExpected()
3534
3512
 
3535
3513
  reportDoesNotInclude: async function ( scope, { not_expected=[] }) {
package/lib/steps.js CHANGED
@@ -114,8 +114,8 @@ BeforeAll(async function() {
114
114
  reports.create( scope );
115
115
  });
116
116
 
117
- Before( beforeScenario );
118
- async function beforeScenario( scenario ) {
117
+ Before(async (scenario) => {
118
+
119
119
  // Start the running "progress bar" for the Scenario
120
120
  log.stdout({}, `\nScenario: ${ scenario.pickle.name }: `);
121
121
 
@@ -171,7 +171,7 @@ async function beforeScenario( scenario ) {
171
171
 
172
172
  // Reset default timeout
173
173
  scope.timeout = default_timeout;
174
- }
174
+ });
175
175
 
176
176
  // Add a check for an error page before each step? After each step?
177
177
 
@@ -291,6 +291,7 @@ Given(/I go to "([^"]+)"/i, {timeout: -1}, async ( url ) => {
291
291
 
292
292
  Given(
293
293
  /I (?:sign|log) ?(?:in)?(?:on)?(?:to the server)? with(?: the email)? "([^"]+)",?(?: and)?(?: the password)? "([^"]+)"(?: SECRETs)?(?:,?(?: and)?(?: the API key)? "([^"]+)")?/i,
294
+ { timeout: -1 },
294
295
  async ( email, password, api_key ) => {
295
296
  /** Uses the names of environment variables (most often GitHub SECRETs) to
296
297
  * log into an account on the user's server. Must be secure.
@@ -1332,8 +1333,8 @@ AfterStep(async function({ result }) {
1332
1333
  reports.outdent();
1333
1334
  });
1334
1335
 
1335
- After(afterScenario);
1336
- async function afterScenario(scenario) {
1336
+ After(async function(scenario) {
1337
+
1337
1338
  // Log errors
1338
1339
  if ( scenario.result.message ) {
1339
1340
  log.debug({ code: `ALK0091`, context: `scenario`, },
@@ -1378,24 +1379,15 @@ async function afterScenario(scenario) {
1378
1379
  reports.addToReport(scope, {
1379
1380
  type: `row info`,
1380
1381
  code: `ALK0095`,
1381
- // Discuss: Might people use secret variables to prevent any
1382
- // information about a screen from getting out? For example, they may
1383
- // have proprietary info in the HTML itself.
1384
- value: `For security, ALKiln will avoid creating a picture of the page for this error. It's possible a secret is being used on this screen. ALKiln will save the HTML as a file. The HTML omits the values in the fields.`
1382
+ value: `For security, ALKiln will avoid creating a picture of the page for this error. It's possible a secret is being used on this screen.`
1385
1383
  });
1386
- }
1384
+ } else {
1387
1385
 
1388
- // Could have an error if page can't load or something similar
1389
- try {
1390
1386
  // Save/download a picture of the screen that's showing during the unexpected status
1391
1387
  // Save one copy in the outer-most artifact folder
1392
1388
  let scenario_filename = await scope.getSafeScenarioFilename( scope, { prefix: `error_on` });
1393
1389
  let path_outer = `${ scope.paths.artifacts }/${ scenario_filename }.jpg`;
1394
- await scope.take_a_screenshot( scope, {
1395
- path: path_outer,
1396
- disable_pic: scope.disable_error_screenshot,
1397
- disable_html: false,
1398
- });
1390
+ await scope.take_a_screenshot( scope, { path: path_outer });
1399
1391
 
1400
1392
  // Save another copy in the artifact's Scenario folder
1401
1393
  let screenshot_name = `error_on`;
@@ -1403,25 +1395,9 @@ async function afterScenario(scenario) {
1403
1395
  let short_id = `${ id }`.substring(0, 20);
1404
1396
  screenshot_name += `-${ short_id }`;
1405
1397
  let path_scenario = `${ scope.paths.scenario }/${ screenshot_name }.jpg`;
1406
- await scope.take_a_screenshot( scope, {
1407
- path: path_scenario,
1408
- disable_pic: scope.disable_error_screenshot,
1409
- disable_html: false,
1410
- });
1411
-
1412
- } catch ( page_error ) {
1413
- // Fail silently. Our inability to take a pic shouldn't cause confusion
1414
- // about why a test failed
1415
- if ( page_error.message.lower().includes(`execution context`) ) {
1416
- reports.addToReport(scope, {
1417
- type: `row warning`,
1418
- code: `ALK0281`,
1419
- value: `ALKiln is unable to get any record of this page whatsoever, even the page HTML. Your server may be busy.`
1420
- });
1421
- }
1422
- log.debug({ code: `ALK0282`, level: `note`, }, page_error );
1423
- }
1398
+ await scope.take_a_screenshot( scope, { path: path_scenario });
1424
1399
 
1400
+ } // ends if scope.disable_error_screenshot
1425
1401
  } // ends if scope.page exists
1426
1402
 
1427
1403
  // This has to come after the security message or security message doesn't print. Not sure why.
@@ -1441,24 +1417,13 @@ async function afterScenario(scenario) {
1441
1417
  // ---------------- Check internal test results ----------------
1442
1418
 
1443
1419
  if ( scope.expected_in_report && scope.expected_in_report.length > 0) {
1444
- let data = await scope.reportIncludesAllExpected(
1420
+ let report_includes_all_expected_strings = await scope.reportIncludesAllExpected(
1445
1421
  scope,
1446
1422
  { expected: scope.expected_in_report }
1447
1423
  );
1448
- let report_includes_all_expected_strings = data.all_included;
1449
- if ( !report_includes_all_expected_strings ) {
1450
- changeable_test_status = `FAILED`;
1451
- let msg = `These strings are missing from the report:
1452
- ${ JSON.stringify(data.missing, null, 2) }
1453
- Instead the report had this text:
1454
- ${ report }`;
1455
- // These failure messages won't match exactly, which is a shame, but at
1456
- // least our devs will have more data _somewhere_ about their failure.
1457
- expect( report, msg ).to.contain( data.missing[0] );
1458
- }
1424
+ if ( !report_includes_all_expected_strings ) { changeable_test_status = `FAILED`; }
1459
1425
  // Reset report values no matter what so they don't mess up future scenarios
1460
1426
  scope.expected_in_report = null;
1461
-
1462
1427
  }
1463
1428
 
1464
1429
  if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
@@ -1640,7 +1605,8 @@ async function afterScenario(scenario) {
1640
1605
  log.debug({ code: `ALK0100`, context: `scenario`, },
1641
1606
  `Scenario After() message:`, scenario.result.message
1642
1607
  );
1643
- } // Ends afterScenario()
1608
+
1609
+ });
1644
1610
 
1645
1611
  AfterAll(async function() {
1646
1612
  // Stop collecting server response statuses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.16.1-always-html-6",
3
+ "version": "5.17.0",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {