@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.
|
@@ -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:
|
|
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:
|
|
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
|
-
-
|
|
56
|
-
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
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
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
3506
|
+
expect( report ).to.contain( one_expectation );
|
|
3529
3507
|
}
|
|
3530
3508
|
}
|
|
3531
3509
|
|
|
3532
|
-
return
|
|
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(
|
|
118
|
-
|
|
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(
|
|
1336
|
-
|
|
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
|
-
|
|
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
|
|
1420
|
+
let report_includes_all_expected_strings = await scope.reportIncludesAllExpected(
|
|
1445
1421
|
scope,
|
|
1446
1422
|
{ expected: scope.expected_in_report }
|
|
1447
1423
|
);
|
|
1448
|
-
|
|
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
|
-
|
|
1608
|
+
|
|
1609
|
+
});
|
|
1644
1610
|
|
|
1645
1611
|
AfterAll(async function() {
|
|
1646
1612
|
// Stop collecting server response statuses
|
package/package.json
CHANGED