@suffolklitlab/alkiln 5.16.1 → 5.17.0-fix-stop-on-error-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/.github/workflows/da_image_v_sha_7d13853.yml +1 -4
- package/.github/workflows/github_server.yml +1 -4
- package/.github/workflows/playground.yml +1 -4
- package/CHANGELOG.md +29 -1
- package/action.yml +7 -7
- package/action_for_github_server/action.yml +3 -3
- package/lib/scope.js +582 -83
- package/lib/steps.js +35 -22
- package/package.json +1 -1
|
@@ -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
|
|
@@ -65,4 +62,4 @@ jobs:
|
|
|
65
62
|
DOCASSEMBLE_DEVELOPER_API_KEY: "${{ steps.github_server.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}"
|
|
66
63
|
INSTALL_METHOD: "server"
|
|
67
64
|
ALKILN_TAG_EXPRESSION: "@e3" # One very simple test
|
|
68
|
-
|
|
65
|
+
ALKILN_VERSION: "stop_on_error"
|
|
@@ -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
|
-
|
|
160
|
+
ALKILN_VERSION: "stop_on_error"
|
|
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
|
-
|
|
83
|
+
ALKILN_VERSION: "stop_on_error"
|
package/CHANGELOG.md
CHANGED
|
@@ -43,7 +43,35 @@ Format:
|
|
|
43
43
|
-
|
|
44
44
|
-->
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
## [Unreleased]
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Extended error message for reaching a docassemble system error page (like syntax errors, etc.) It now should contain the relevant contents of the page.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- Fixed linear Steps not failing on error screens. See #656.
|
|
55
|
+
|
|
56
|
+
### Internal
|
|
57
|
+
|
|
58
|
+
- Fixed confusing error message when tests expected to fail actually pass.
|
|
59
|
+
|
|
60
|
+
## [5.17.0] - 2026-09-10
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- 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.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Fixed GitHub+You action outdated docassemble cli version causing Scenario timeouts.
|
|
69
|
+
- Part 1 of detecting custom AL error screen: Story Table now catches `al_custom_error_action` 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).
|
|
70
|
+
|
|
71
|
+
### Internal
|
|
72
|
+
|
|
73
|
+
- Simplify workflow files by removing `node` env setup (leaving it to the actions)
|
|
74
|
+
- 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.
|
|
47
75
|
|
|
48
76
|
## [5.16.1] - 2026-06-01
|
|
49
77
|
|
package/action.yml
CHANGED
|
@@ -14,7 +14,7 @@ inputs:
|
|
|
14
14
|
ALKILN_VERSION:
|
|
15
15
|
description: 'Version of ALKiln to get from npm.'
|
|
16
16
|
required: false
|
|
17
|
-
default: '5.
|
|
17
|
+
default: '^5.0.0'
|
|
18
18
|
MAX_SECONDS_FOR_SETUP:
|
|
19
19
|
description: "Amount of time to give the Project to upload your package's GitHub code."
|
|
20
20
|
required: false
|
|
@@ -32,7 +32,7 @@ inputs:
|
|
|
32
32
|
DOCASSEMBLECLI_VERSION:
|
|
33
33
|
description: "The version of docassemblecli to install."
|
|
34
34
|
required: false
|
|
35
|
-
default: "0.0.
|
|
35
|
+
default: "0.0.25"
|
|
36
36
|
ALKILN_MAX_RANDOM_TESTS_PER_SCENARIO:
|
|
37
37
|
description: "The maximum number of tests ALKiln will generate for every Scenario that can generate tests."
|
|
38
38
|
required: false
|
|
@@ -59,7 +59,7 @@ runs:
|
|
|
59
59
|
using: "composite"
|
|
60
60
|
steps:
|
|
61
61
|
# Place the root directory in this branch
|
|
62
|
-
- uses: actions/checkout@
|
|
62
|
+
- uses: actions/checkout@v7
|
|
63
63
|
|
|
64
64
|
# Set environment variables
|
|
65
65
|
- name: "💡 ALK0023 INFO: Set environment variables"
|
|
@@ -111,7 +111,7 @@ runs:
|
|
|
111
111
|
|
|
112
112
|
# Install ALKiln during GitHub workflow
|
|
113
113
|
- name: "💡 ALK0025 INFO: Set up node"
|
|
114
|
-
uses: actions/setup-node@
|
|
114
|
+
uses: actions/setup-node@v6
|
|
115
115
|
with:
|
|
116
116
|
node-version: "24.x"
|
|
117
117
|
package-manager-cache: false
|
|
@@ -155,7 +155,7 @@ runs:
|
|
|
155
155
|
|
|
156
156
|
# Install package on playground
|
|
157
157
|
# Don't rely on the environment's version of python
|
|
158
|
-
- uses: actions/setup-python@
|
|
158
|
+
- uses: actions/setup-python@v6
|
|
159
159
|
if: ${{ inputs.INSTALL_METHOD == 'playground' }}
|
|
160
160
|
with:
|
|
161
161
|
python-version: '3.10'
|
|
@@ -213,7 +213,7 @@ runs:
|
|
|
213
213
|
# Upload artifacts that subscribers can download on the Actions summary page
|
|
214
214
|
- name: "💡 ALK0033 INFO: Upload artifacts folder"
|
|
215
215
|
if: ${{ always() }}
|
|
216
|
-
uses: actions/upload-artifact@
|
|
216
|
+
uses: actions/upload-artifact@v7
|
|
217
217
|
with:
|
|
218
218
|
name: ${{ env.ARTIFACT_FOLDER }}
|
|
219
219
|
path: ./alkiln-*
|
|
@@ -221,7 +221,7 @@ runs:
|
|
|
221
221
|
# Download artifacts folder internally to create action output
|
|
222
222
|
- name: "💡 ALK0183 INFO: Get uploaded artifacts folder"
|
|
223
223
|
if: ${{ always() }}
|
|
224
|
-
uses: actions/download-artifact@
|
|
224
|
+
uses: actions/download-artifact@v8
|
|
225
225
|
with:
|
|
226
226
|
name: ${{ env.ARTIFACT_FOLDER }}
|
|
227
227
|
|
|
@@ -49,7 +49,7 @@ runs:
|
|
|
49
49
|
using: "composite"
|
|
50
50
|
steps:
|
|
51
51
|
# Place the root directory in this branch
|
|
52
|
-
- uses: actions/checkout@
|
|
52
|
+
- uses: actions/checkout@v7
|
|
53
53
|
|
|
54
54
|
- shell: bash
|
|
55
55
|
run: |
|
|
@@ -253,7 +253,7 @@ runs:
|
|
|
253
253
|
done
|
|
254
254
|
|
|
255
255
|
# Don't rely on the environment's version of python
|
|
256
|
-
- uses: actions/setup-python@
|
|
256
|
+
- uses: actions/setup-python@v6
|
|
257
257
|
with:
|
|
258
258
|
python-version: '3.10'
|
|
259
259
|
# No output
|
|
@@ -285,7 +285,7 @@ runs:
|
|
|
285
285
|
echo "$(docker output docassemble_container)" >> "${{ env.DEBUG_LOGS_PATH }}"
|
|
286
286
|
- name: "💡 ALK0020 INFO: Upload artifacts folder"
|
|
287
287
|
if: ${{ always() && env.OUTPUT_DOCKER == 'true' }}
|
|
288
|
-
uses: actions/upload-artifact@
|
|
288
|
+
uses: actions/upload-artifact@v7
|
|
289
289
|
with:
|
|
290
290
|
name: ${{ env.GITHUB_SERVER_ARTIFACT_NAME }}
|
|
291
291
|
path: ${{ env.DEBUG_LOGS_PATH }}
|
package/lib/scope.js
CHANGED
|
@@ -255,9 +255,81 @@ module.exports = {
|
|
|
255
255
|
}, // Ends afterStep
|
|
256
256
|
|
|
257
257
|
tapElementAndNavigate: async function(scope, { elem, waitForTimeout=null }) {
|
|
258
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* Tap a given element, detect navigation, and do appropriate related things.
|
|
260
|
+
*
|
|
261
|
+
* For other ideas for detecting navigation, see
|
|
262
|
+
* https://github.com/SuffolkLITLab/ALKiln/issues/229.
|
|
263
|
+
*
|
|
264
|
+
* TODO: Duplicates much of `waitUntilContinued`. Need to de-duplicate.
|
|
265
|
+
* Alternatively, continue other work on navigation detection with server
|
|
266
|
+
* responses.
|
|
267
|
+
*
|
|
268
|
+
* @param {int} obj.waitForTimeout - How long to wait for a timeout
|
|
269
|
+
* */
|
|
270
|
+
// await scope.throwAnySystemError( scope ); // error on current pg
|
|
271
|
+
|
|
272
|
+
// let prev_url = url;
|
|
273
|
+
// let curr_url = await scope.page.url();
|
|
274
|
+
|
|
275
|
+
// let timed_out = false; // Lazy timeout?
|
|
276
|
+
// let cont_timeout = setTimeout(function(){ timed_out = true }, scope.timeout);
|
|
277
|
+
|
|
278
|
+
// // Attempt to solve weird race condition issues where page has not properly continued
|
|
279
|
+
// while ( curr_url === prev_url && !page_result.element && !timed_out ) {
|
|
280
|
+
// await time.waitForTimeout( 50 );
|
|
281
|
+
// curr_url = await scope.page.url();
|
|
282
|
+
// page_result = await scope.throwAnySystemError( scope ); // error on new pg
|
|
283
|
+
// }
|
|
259
284
|
|
|
260
|
-
|
|
285
|
+
// // TODO: We sometimes log multiple error messages for an error because we're
|
|
286
|
+
// // logging at multiple levels. We should try to refactor to log just once
|
|
287
|
+
// // per error
|
|
288
|
+
// let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. You can check the picture of this page unless a secret variable was used.`
|
|
289
|
+
// if ( timed_out ) { reports.addToReport(scope, { type: `error`, code: `ALK0146`, value: timeout_err_message }); }
|
|
290
|
+
// expect( timed_out, timeout_err_message ).to.be.false;
|
|
291
|
+
|
|
292
|
+
// clearTimeout( cont_timeout );
|
|
293
|
+
|
|
294
|
+
// return page_result;
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
// Invalid user answer will prevent navigation
|
|
298
|
+
// Shoot. How does this work for .setFields()...?
|
|
299
|
+
// Think. Checked for correct id. Possibly not. If did continue:
|
|
300
|
+
// Fill fields/press var of continue button or
|
|
301
|
+
// Press continue anyway (tap to continue or no fields to set)
|
|
302
|
+
// If on page with user error... id should have recognized it as valid
|
|
303
|
+
// destination or we shouldn't get stuck on the page. Which would make
|
|
304
|
+
// this work either way.
|
|
305
|
+
// That's fine. But. I wanted to change behavior so that a Story Table
|
|
306
|
+
// could set fields on the final page. If that were the case and _if_
|
|
307
|
+
// the field were a continue button, that would break our rule that
|
|
308
|
+
// Story Tables can contain extra fields without worry.
|
|
309
|
+
// Maybe that can be a flag - on final id page, so don't try to press
|
|
310
|
+
// continue button fields, even when it sets a variable in the table.
|
|
311
|
+
// Or any buttons? Hmm. I'll have to make an issue.
|
|
312
|
+
// TODO: Make an issue for the alternative Story Table behavior.
|
|
313
|
+
// But also. What if... well, under some circumstances the author will
|
|
314
|
+
// want to navigate to a page and get invalid values. But brain. Brain
|
|
315
|
+
// brain brain. But if the author navigates after expecting the page
|
|
316
|
+
// to have invalid values, then this should fail too.
|
|
317
|
+
// TODO: Write all this up in here, but better.
|
|
318
|
+
// But what if the author is navigating a different way, like with an
|
|
319
|
+
// action button. So, invalid answers, but then wants to navigate with
|
|
320
|
+
// an action button. That might indeed be a real problem. Right? Brain?
|
|
321
|
+
// You should be allowed to navigate away from any page.
|
|
322
|
+
// Ok, we really do need to split up system errors from user errors.
|
|
323
|
+
// Dangit.
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
// If on system error page, can't navigate.
|
|
327
|
+
// TODO: Discuss how to allow AL to test its custom error page buttons.
|
|
328
|
+
// Maybe avoiding a system error here would be sufficient.
|
|
329
|
+
await scope.throwAnySystemError( scope );
|
|
330
|
+
|
|
331
|
+
let start_url = await scope.page.url(); // for navigation check later
|
|
332
|
+
let curr_url = start_url;
|
|
261
333
|
|
|
262
334
|
await scope.tapElementAndStayOnPage(scope, {
|
|
263
335
|
elem,
|
|
@@ -267,29 +339,49 @@ module.exports = {
|
|
|
267
339
|
]
|
|
268
340
|
});
|
|
269
341
|
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
let
|
|
273
|
-
let
|
|
342
|
+
// Attempt to solve weird race condition issues where page has not properly
|
|
343
|
+
// continued. May exceed actual timeout
|
|
344
|
+
let timed_out = false;
|
|
345
|
+
let cont_timeout = setTimeout(
|
|
346
|
+
function(){ timed_out = true; }, ( scope.timeout - 10 )
|
|
347
|
+
);
|
|
348
|
+
while ( curr_url === start_url && !timed_out ) {
|
|
349
|
+
try {
|
|
350
|
+
await scope.throwAnySystemError( scope ); // quick exit on system error
|
|
351
|
+
} catch ( sys_err_msg ) {
|
|
352
|
+
clearTimeout( cont_timeout );
|
|
353
|
+
throw sys_err_msg;
|
|
354
|
+
}
|
|
355
|
+
await time.waitForTimeout( 50 );
|
|
356
|
+
curr_url = await scope.page.url();
|
|
357
|
+
}
|
|
358
|
+
clearTimeout( cont_timeout );
|
|
359
|
+
|
|
360
|
+
await scope.throwAnySystemError( scope ); // system error on new page
|
|
274
361
|
|
|
275
|
-
//
|
|
276
|
-
|
|
362
|
+
// TODO: We sometimes log multiple error messages for an error because we're
|
|
363
|
+
// logging at multiple levels. We should try to refactor to log just once
|
|
364
|
+
// per error
|
|
365
|
+
let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. You can check the picture of this page unless a secret variable was used.`
|
|
366
|
+
if ( timed_out ) { reports.addToReport(scope, { type: `error`, code: `ALK0283`, value: timeout_err_message }); }
|
|
367
|
+
expect( timed_out, timeout_err_message ).to.be.false;
|
|
277
368
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
|
|
369
|
+
// Might be able to handle this in `scope.afterStep`
|
|
370
|
+
if ( start_url !== curr_url ) {
|
|
281
371
|
|
|
372
|
+
scope.navigated = true;
|
|
282
373
|
// If navigation, wait for 200ms to let things finish loading and moving
|
|
283
374
|
await scope.afterStep(scope, {
|
|
284
375
|
waitForTimeout: ( waitForTimeout || 200 ),
|
|
285
376
|
navigated: true
|
|
286
377
|
});
|
|
378
|
+
|
|
287
379
|
} else {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
380
|
+
|
|
381
|
+
// TODO: #656, this with one other change may be all we need
|
|
382
|
+
let non_nav_msg = `Was unable to navigate away from page with the id "${ start_url }".`
|
|
383
|
+
reports.addToReport(scope, { type: `error`, code: `ALK0284`, value: non_nav_msg });
|
|
384
|
+
throw new Error( non_nav_msg );
|
|
293
385
|
}
|
|
294
386
|
}, // Ends scope.tapElementAndNavigate()
|
|
295
387
|
|
|
@@ -347,11 +439,14 @@ module.exports = {
|
|
|
347
439
|
// This is meant to detect user-visible alerts/errors,
|
|
348
440
|
// not breaking errors in the testing code.
|
|
349
441
|
// No need for aborters since puppeteer has its own timeout
|
|
442
|
+
// TODO: Discuss why we need to detect invalid answers on a page where
|
|
443
|
+
// we don't plan to navigate.
|
|
350
444
|
scope.page.waitForSelector('.alert-danger', { visible: true }),
|
|
351
445
|
scope.page.waitForSelector('.da-has-error', { visible: true }),
|
|
352
446
|
]);
|
|
353
447
|
} catch ( error ) {
|
|
354
448
|
if ( error.name === `TimeoutError` ) {
|
|
449
|
+
// TODO: Discuss - this is a navigation timeout even though the tap shouldn't navigate
|
|
355
450
|
let non_reload_report_msg = `After ALKiln tapped "${ tapperText }" it took too long to load the next page.`;
|
|
356
451
|
await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
|
|
357
452
|
code: `ALK0103`,
|
|
@@ -608,6 +703,7 @@ module.exports = {
|
|
|
608
703
|
|
|
609
704
|
checkForA11y: async function (scope ) {
|
|
610
705
|
/** Return if there were any violations and the file containing aXe output with more details of the checks */
|
|
706
|
+
// Discuss: What problem was the page id solving? When was there a mismatch?
|
|
611
707
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
612
708
|
if ( scope.page_id !== id ) {
|
|
613
709
|
reports.addToReport(scope, { type: `page_id info`, code: `ALK0108`, value: `${ id }`, });
|
|
@@ -643,7 +739,7 @@ module.exports = {
|
|
|
643
739
|
|
|
644
740
|
tapSelectorToNavigate: async function ( scope, selector, waitForTimeout ) {
|
|
645
741
|
let elem = await scope.page.$(`${ selector }`);
|
|
646
|
-
let error_msg = `Couldn't find "${ selector }" on the page
|
|
742
|
+
let error_msg = `Couldn't find "${ selector }" on the page. Is there a typo?`;
|
|
647
743
|
if ( !elem ) {
|
|
648
744
|
reports.addToReport(scope, { type: `error`, code: `ALK0111`, value: error_msg });
|
|
649
745
|
}
|
|
@@ -2304,12 +2400,16 @@ module.exports = {
|
|
|
2304
2400
|
|
|
2305
2401
|
// Press a continue button with a story table variable name if it exists
|
|
2306
2402
|
let did_navigate = false;
|
|
2307
|
-
let error = {};
|
|
2308
2403
|
for ( let field of fields ) {
|
|
2309
2404
|
// All other fields should have been set
|
|
2310
2405
|
if ( field.tag === 'button' && field.type === 'submit' ) {
|
|
2311
2406
|
let page_url = await scope.page.url();
|
|
2312
2407
|
|
|
2408
|
+
// TODO: Attempt avoiding `awaiting` and instead allow
|
|
2409
|
+
// waitUntilContinued to detect navigation.
|
|
2410
|
+
// Except `row_used` should be set before an error page. Maybe
|
|
2411
|
+
// `row_used` should always be set before all this, since we know the
|
|
2412
|
+
// field exists on the page (we know it's a submit button).
|
|
2313
2413
|
let row_used = await scope.setVariable( scope, { field, var_data, });
|
|
2314
2414
|
if ( row_used ) {
|
|
2315
2415
|
|
|
@@ -2334,10 +2434,7 @@ module.exports = {
|
|
|
2334
2434
|
// Make sure no system or user error messages appear
|
|
2335
2435
|
// Discuss moving this out of here. Having to pass in `id` just for
|
|
2336
2436
|
// these messages smells bad. Con: Already a lot of code out there.
|
|
2337
|
-
|
|
2338
|
-
// if ( error.was_found ) {
|
|
2339
|
-
// await scope.throwPageError( scope, { id: id });
|
|
2340
|
-
// }
|
|
2437
|
+
await scope.waitUntilContinued( scope, { url: page_url });
|
|
2341
2438
|
|
|
2342
2439
|
did_navigate = true;
|
|
2343
2440
|
log.stdout({}, `\x1b[36m${ 'v' }\x1b[0m`); // pressed button successfully
|
|
@@ -2370,6 +2467,8 @@ module.exports = {
|
|
|
2370
2467
|
if ( !did_navigate && ensure_navigation ) {
|
|
2371
2468
|
// All fields on this page should be taken care of
|
|
2372
2469
|
let page_url = await scope.page.url();
|
|
2470
|
+
// TODO: Attempt avoiding `awaiting` and instead allow
|
|
2471
|
+
// waitUntilContinued to detect navigation.
|
|
2373
2472
|
await scope.continue( scope );
|
|
2374
2473
|
// Have to catch errors right now or may get stuck in an infinite loop?
|
|
2375
2474
|
// TODO: Don't catch errors - maybe the developer expected/wanted an error
|
|
@@ -2381,10 +2480,7 @@ module.exports = {
|
|
|
2381
2480
|
// these messages smells bad. Con: Already a lot of code out there.
|
|
2382
2481
|
|
|
2383
2482
|
// TODO: How do we let 'I set the var __ to __' also afterwards check for an invalidation message?
|
|
2384
|
-
|
|
2385
|
-
// if ( error.was_found ) {
|
|
2386
|
-
// await scope.throwPageError( scope, { id: id });
|
|
2387
|
-
// }
|
|
2483
|
+
await scope.waitUntilContinued( scope, { url: page_url });
|
|
2388
2484
|
|
|
2389
2485
|
did_navigate = true;
|
|
2390
2486
|
log.stdout({}, `\x1b[36m${ '>' }\x1b[0m`); // continued successfully
|
|
@@ -2400,7 +2496,7 @@ module.exports = {
|
|
|
2400
2496
|
// TODO: Since we now continue only once no matter what, we can put
|
|
2401
2497
|
// the error in the caller of the function, though we may want
|
|
2402
2498
|
// to pass the symbol to print as well.
|
|
2403
|
-
return
|
|
2499
|
+
return { ok: true };
|
|
2404
2500
|
// TODO: Maybe we should return something else or something in addition about used vars
|
|
2405
2501
|
}, // Ends scope.setFields()
|
|
2406
2502
|
|
|
@@ -2636,80 +2732,483 @@ module.exports = {
|
|
|
2636
2732
|
}
|
|
2637
2733
|
}, // Ends scope.has_interview_content()
|
|
2638
2734
|
|
|
2639
|
-
waitUntilContinued: async function ( scope, { url = ''
|
|
2640
|
-
/** Given a url, attempt to
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2735
|
+
waitUntilContinued: async function ( scope, { url = '' }) {
|
|
2736
|
+
/** Given a url, attempt to detect navigation until:
|
|
2737
|
+
*
|
|
2738
|
+
* - a page shows a system error or invalid user answers
|
|
2739
|
+
* - the page url changes
|
|
2740
|
+
* - the navigation times out
|
|
2741
|
+
*
|
|
2742
|
+
* Note: intended to run after an attempt to navigate.
|
|
2743
|
+
*
|
|
2744
|
+
* TODO: Duplicates much of `tapElementAndNavigate`. Need to de-duplicate.
|
|
2745
|
+
* Alternatively, continue other work on navigation detection with server
|
|
2746
|
+
* responses.
|
|
2747
|
+
*
|
|
2748
|
+
* If the navigation fails, throw an error.
|
|
2749
|
+
* */
|
|
2750
|
+
|
|
2751
|
+
// Discuss: Delay system err check? May allow AL to test custom error page.
|
|
2752
|
+
await scope.throwAnySystemError( scope ); // system error on current page
|
|
2753
|
+
await scope.throw_on_invalid_answers( scope ); // invalid user answers
|
|
2645
2754
|
|
|
2646
|
-
let
|
|
2647
|
-
let
|
|
2755
|
+
let start_url = url; // for navigation check later
|
|
2756
|
+
let curr_url = start_url;
|
|
2648
2757
|
|
|
2649
2758
|
// Attempt to solve weird race condition issues where page has not properly continued
|
|
2650
|
-
|
|
2759
|
+
// May exceed actual timeout
|
|
2760
|
+
let timed_out = false;
|
|
2761
|
+
let cont_timeout = setTimeout(
|
|
2762
|
+
function(){ timed_out = true; }, ( scope.timeout - 10 )
|
|
2763
|
+
);
|
|
2764
|
+
while ( curr_url === start_url && !timed_out ) {
|
|
2765
|
+
try {
|
|
2766
|
+
await scope.throwAnySystemError( scope ); // system error on new page
|
|
2767
|
+
} catch ( sys_err_msg ) {
|
|
2768
|
+
clearTimeout( cont_timeout );
|
|
2769
|
+
throw sys_err_msg;
|
|
2770
|
+
}
|
|
2651
2771
|
await time.waitForTimeout( 50 );
|
|
2652
2772
|
curr_url = await scope.page.url();
|
|
2653
|
-
error = await scope.checkForError( scope );
|
|
2654
2773
|
}
|
|
2774
|
+
clearTimeout( cont_timeout );
|
|
2655
2775
|
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2776
|
+
await scope.throwAnySystemError( scope ); // system error on new page
|
|
2777
|
+
|
|
2778
|
+
// TODO: We sometimes log multiple error messages for an error because we're
|
|
2779
|
+
// logging at multiple levels. We should try to refactor to log just once
|
|
2780
|
+
// per error
|
|
2781
|
+
let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. You can check the picture of this page unless a secret variable was used.`
|
|
2782
|
+
if ( timed_out ) { reports.addToReport(scope, { type: `error`, code: `ALK0146`, value: timeout_err_message }); }
|
|
2783
|
+
expect( timed_out, timeout_err_message ).to.be.false;
|
|
2784
|
+
|
|
2785
|
+
// Note: Invalid answers on the new page would be ok, though they probably
|
|
2786
|
+
// won't exist yet anyway.
|
|
2787
|
+
|
|
2788
|
+
if ( start_url !== curr_url ) {
|
|
2789
|
+
scope.navigated = true;
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
return true;
|
|
2659
2793
|
|
|
2660
|
-
clearTimeout( cont_timeout );
|
|
2661
2794
|
|
|
2662
|
-
|
|
2795
|
+
// let prev_url = url;
|
|
2796
|
+
// let curr_url = await scope.page.url();
|
|
2797
|
+
// let error_data = await scope.getPageError( scope ); // error on current pg
|
|
2798
|
+
|
|
2799
|
+
// let timed_out = false;
|
|
2800
|
+
// let cont_timeout = setTimeout(
|
|
2801
|
+
// function(){ timed_out = true }, ( scope.timeout - 10 )
|
|
2802
|
+
// );
|
|
2803
|
+
|
|
2804
|
+
// // Attempt to solve weird race condition issues where page has not properly continued
|
|
2805
|
+
// while ( curr_url === prev_url && !error_data.element && !timed_out ) {
|
|
2806
|
+
// await time.waitForTimeout( 50 );
|
|
2807
|
+
// curr_url = await scope.page.url();
|
|
2808
|
+
// error_data = await scope.getPageError( scope ); // error on new pg
|
|
2809
|
+
// }
|
|
2810
|
+
|
|
2811
|
+
// clearTimeout( cont_timeout );
|
|
2812
|
+
|
|
2813
|
+
// // TODO: Log only one error log per... error.
|
|
2814
|
+
// let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. You can check the picture of this page unless a secret variable was used.`
|
|
2815
|
+
// if ( timed_out ) { reports.addToReport(scope, { type: `error`, code: `ALK0146`, value: timeout_err_message }); }
|
|
2816
|
+
// expect( timed_out, timeout_err_message ).to.be.false;
|
|
2817
|
+
|
|
2818
|
+
// return error_data;
|
|
2663
2819
|
}, // Ends scope.waitUntilContinued()
|
|
2664
2820
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
};
|
|
2821
|
+
throwAnySystemError: async function ( scope ) {
|
|
2822
|
+
/**
|
|
2823
|
+
* TODO: Stack trace won't show user answers. Could they reveal other
|
|
2824
|
+
* sensitive information?
|
|
2825
|
+
* TODO: Add a state variable if any secret vars have been used thus far
|
|
2826
|
+
* and, if so, disallow pics, JSON, HTML, and page contents collecting.
|
|
2827
|
+
* In addition, allow author to disable all the above with a Step.
|
|
2828
|
+
*
|
|
2829
|
+
* @param {Object} scope - Tests state object
|
|
2830
|
+
* */
|
|
2831
|
+
let sys_err_msg = `Was unable to get a specific system error message, sorry.`;
|
|
2677
2832
|
|
|
2678
|
-
let
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
await time.waitForTimeout( ( scope.timeout - (1 * 1000)) );
|
|
2688
|
-
}
|
|
2689
|
-
was_found = true;
|
|
2690
|
-
error_handlers[ error_key ] = possible_errors[ error_key ][0];
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2833
|
+
let da_sys_err = await scope.page.$(`xpath/.//meta[@content=": Error"]|.//*[contains(@id, "damain")]//h1[@content="Error"]`);
|
|
2834
|
+
// `al_custom_error_action` in base64. This acts like a continue screen, but actually replaces system error pages.
|
|
2835
|
+
let al_custom_err = await scope.page.$(`.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]`);
|
|
2836
|
+
|
|
2837
|
+
// if ( day_sys_err ) {
|
|
2838
|
+
// sys_err_msg = await scope.get_da_system_err( scope );
|
|
2839
|
+
// } else if ( al_custom_err ) {
|
|
2840
|
+
// sys_err_msg = await scope.get_al_system_err( scope );
|
|
2841
|
+
// }
|
|
2693
2842
|
|
|
2694
|
-
|
|
2695
|
-
}, // Ends scope.checkForError()
|
|
2843
|
+
error_indicator = da_sys_err || al_custom_err;
|
|
2696
2844
|
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
reports.addToReport(scope, { type: `error`, code: `ALK0147`, value: sys_err_message });
|
|
2702
|
-
expect( was_system_error, sys_err_message ).to.be.false;
|
|
2845
|
+
if ( error_indicator ) {
|
|
2846
|
+
let sys_err_msg = await scope.get_system_error_page_sample(
|
|
2847
|
+
scope, { error_indicator }
|
|
2848
|
+
);
|
|
2703
2849
|
|
|
2704
|
-
|
|
2705
|
-
//
|
|
2850
|
+
// TODO: Discuss adding some logs to just a Scenario's report as opposed
|
|
2851
|
+
// to full report.
|
|
2852
|
+
reports.addToReport( scope, { type: `error`, code: `ALK0147`, value: sys_err_msg });
|
|
2853
|
+
throw new Error( sys_err_msg );
|
|
2854
|
+
}
|
|
2855
|
+
},
|
|
2856
|
+
|
|
2857
|
+
get_invalid_answer_indicators: async function ( scope ) {
|
|
2858
|
+
/**
|
|
2859
|
+
* Return any elements associated with invalid user answers. Avoid throwing.
|
|
2860
|
+
* Invalid answers may be the author's goal.
|
|
2861
|
+
*
|
|
2862
|
+
* Note: May later want the actual text of the error messages.
|
|
2863
|
+
*
|
|
2864
|
+
* @param {Object} scope - Tests state object
|
|
2865
|
+
* */
|
|
2866
|
+
// TODO: While alerts come at the top of the page for validation errors,
|
|
2867
|
+
// an author can also use them as flavor text on the page. Ensure that
|
|
2868
|
+
// the latter doesn't trigger failure.
|
|
2869
|
+
let invalid_indicator1 = await scope.page.$$(`.alert-danger`);
|
|
2870
|
+
// ⚠️ 'display:' and 'none' MUST be separated by a space
|
|
2871
|
+
let invalid_indicator2 = await scope.page.$$(`xpath//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`);
|
|
2872
|
+
invalidity_indicators = [ ...invalid_indicator1, ...invalid_indicator2 ];
|
|
2873
|
+
|
|
2874
|
+
return invalidity_indicators;
|
|
2875
|
+
},
|
|
2876
|
+
|
|
2877
|
+
throw_on_invalid_answers: async function ( scope ) {
|
|
2878
|
+
/**
|
|
2879
|
+
* Throw an error if the user has given invalid answers.
|
|
2880
|
+
*
|
|
2881
|
+
* @param {Object} scope - Tests state object
|
|
2882
|
+
* */
|
|
2883
|
+
let invalidity_indicators = await scope.get_invalid_answer_indicators( scope );
|
|
2884
|
+
if ( invalidity_indicators.length > 0 ) {
|
|
2885
|
+
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
2706
2886
|
let user_err_message = `The test tried to continue to the next page after ${ id }, but the user got an error. Check the picture of this page.`
|
|
2707
2887
|
reports.addToReport(scope, { type: `error`, code: `ALK0148`, value: user_err_message });
|
|
2708
|
-
expect(
|
|
2888
|
+
expect( invalidity_indicators, user_err_message ).isEmpty([]);
|
|
2889
|
+
}
|
|
2890
|
+
},
|
|
2891
|
+
|
|
2892
|
+
// getPageError: async function ( scope ) {
|
|
2893
|
+
// /**
|
|
2894
|
+
// * Returns an object with a system/user error elem if they exist, As well
|
|
2895
|
+
// * as other data.
|
|
2896
|
+
// *
|
|
2897
|
+
// * TODO: Discuss how we can split system vs. user errors more effectively.
|
|
2898
|
+
// *
|
|
2899
|
+
// * @param {Object} scope - Tests state object
|
|
2900
|
+
// * */
|
|
2901
|
+
|
|
2902
|
+
// // let possible_errors = {
|
|
2903
|
+
// // // TODO: Ensure that intentional callouts don't trigger failure since they
|
|
2904
|
+
// // // too use .alert-danger I believe (bootstrap component)
|
|
2905
|
+
// // user_alert_error: await scope.page.$$(`.alert-danger`),
|
|
2906
|
+
// // // ⚠️ The below does not work if there's not a space between ':' and 'none'
|
|
2907
|
+
// // user_field_error: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
|
|
2908
|
+
// // // ⚠️ This xpath assumes this content stays in the metadata of the page
|
|
2909
|
+
// // system_error: await scope.page.$$(`xpath/.//meta[@content=": Error"]|.//*[contains(@id, "damain")]//h1[@content="Error"]` ),
|
|
2910
|
+
// // // 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.
|
|
2911
|
+
// // al_custom_system_error: await scope.page.$$(`.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]`),
|
|
2912
|
+
// // };
|
|
2913
|
+
|
|
2914
|
+
// let result = { ok: true, type: ``, element: null, msg: `` };
|
|
2915
|
+
|
|
2916
|
+
// let error_patterns = [
|
|
2917
|
+
// // TODO: While alerts come at the top of the page for validation errors,
|
|
2918
|
+
// // an author can also use them as flavor text on the page. Ensure that
|
|
2919
|
+
// // the latter doesn't trigger failure.
|
|
2920
|
+
// { type: `user_alert`, selector: `.alert-danger` },
|
|
2921
|
+
// // ⚠️ 'display:' and 'none' MUST have a space between them
|
|
2922
|
+
// { type: `user_field`, selector: `xpath//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]` },
|
|
2923
|
+
// { type: `system_da`, selector: `xpath/.//meta[@content=": Error"]|.//*[contains(@id, "damain")]//h1[@content="Error"]` },
|
|
2924
|
+
// // `al_custom_error_action` in base64. This acts like a continue screen, but actually replaces system error pages.
|
|
2925
|
+
// { type: `system_al`, selector: `.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]` },
|
|
2926
|
+
// ];
|
|
2927
|
+
// for ( let pattern of error_patterns ) {
|
|
2928
|
+
// let element = await scope.page.$( pattern.selector );
|
|
2929
|
+
// if ( element ) {
|
|
2930
|
+
// result.type = type: pattern.type;
|
|
2931
|
+
// result.element = element;
|
|
2932
|
+
// }
|
|
2933
|
+
// }
|
|
2934
|
+
|
|
2935
|
+
// if ( result.type.includes(`system`)) {
|
|
2936
|
+
// result.ok = false;
|
|
2937
|
+
// result.msg = await scope.get_system_error_page_sample(
|
|
2938
|
+
// scope, { error_element: result.element }
|
|
2939
|
+
// );
|
|
2940
|
+
// } else if ( result.type.includes(`user`) ) {
|
|
2941
|
+
// // Invalid user answers may be intentional and ok.
|
|
2942
|
+
// result.msg = `User gave invalid answer.`
|
|
2943
|
+
// }
|
|
2944
|
+
|
|
2945
|
+
// return result;
|
|
2946
|
+
|
|
2947
|
+
|
|
2948
|
+
|
|
2949
|
+
|
|
2950
|
+
|
|
2951
|
+
|
|
2952
|
+
// // let error = { type: `user_alert`, element: await scope.page.$$(`.alert-danger`) }
|
|
2953
|
+
// // if ( !error.element ) {
|
|
2954
|
+
// // error = { type: `user_field`, element: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`) }
|
|
2955
|
+
// // }
|
|
2956
|
+
// // if ( !error.element ) {
|
|
2957
|
+
// // error = { type: `system_da`, element: await scope.page.$$(`xpath/.//meta[@content=": Error"]|.//*[contains(@id, "damain")]//h1[@content="Error"]` ) }
|
|
2958
|
+
// // }
|
|
2959
|
+
// // if ( !error.element ) {
|
|
2960
|
+
// // error = { type: `system_al`, await scope.page.$$(`.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]`) }
|
|
2961
|
+
// // }
|
|
2962
|
+
|
|
2963
|
+
// // let error_elems_by_type = {};
|
|
2964
|
+
// // let was_found = false;
|
|
2965
|
+
// // let type = null;
|
|
2966
|
+
// // for ( let error_key in possible_errors ) {
|
|
2967
|
+
// // error_elems_by_type[ error_key ] = possible_errors[ error_key ][0];
|
|
2968
|
+
// // if ( possible_errors[ error_key ][0] ) {
|
|
2969
|
+
// // // If there's a page visible (locally), give us as much time to examine it as timeout allows.
|
|
2970
|
+
// // // Because of previous actions, this may still be a bit too long.
|
|
2971
|
+
// // // Also, if you're debugging and you close the window early, the process will still
|
|
2972
|
+
// // // take this long to exit after the tests finish. Not sure how to account for that.
|
|
2973
|
+
// // if ( session_vars.get_debug() ) {
|
|
2974
|
+
// // await time.waitForTimeout( ( scope.timeout - (1 * 1000)) );
|
|
2975
|
+
// // }
|
|
2976
|
+
// // if ( error_key.includes(`user`) ) { type = `user`; }
|
|
2977
|
+
// // if ( error_key.includes(`system`) ) { type = `system`; }
|
|
2978
|
+
// // was_found = true;
|
|
2979
|
+
// // }
|
|
2980
|
+
// // }
|
|
2981
|
+
|
|
2982
|
+
// // return { was_found, type, error_elems_by_type };
|
|
2983
|
+
// }, // Ends scope.getPageError()
|
|
2984
|
+
|
|
2985
|
+
get_system_error_page_sample: async function ( scope ) {
|
|
2986
|
+
// get_da_system_err: async function ( scope ) {
|
|
2987
|
+
/**
|
|
2988
|
+
* Returns message for a docassemble system error page that includes a
|
|
2989
|
+
* sample of the page contents
|
|
2990
|
+
*
|
|
2991
|
+
* @param {Object} scope - Tests state object
|
|
2992
|
+
*
|
|
2993
|
+
* @returns {string}
|
|
2994
|
+
* */
|
|
2995
|
+
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
2996
|
+
let page_err_text = `Unable to get the text on this page. Check the picture of this page if possible.`
|
|
2997
|
+
try {
|
|
2998
|
+
|
|
2999
|
+
page_err_text = await scope.page.evaluate(() => {
|
|
3000
|
+
return (
|
|
3001
|
+
( document.querySelector(`#YWxfZm9ybWF0dGVkX2Vycm9yX21lc3NhZ2U`)
|
|
3002
|
+
|| document.querySelector(`.dabody`)
|
|
3003
|
+
).textContent
|
|
3004
|
+
.split(`\n`)
|
|
3005
|
+
.slice( 0, 8 )
|
|
3006
|
+
.join(`\n`)
|
|
3007
|
+
.trim()
|
|
3008
|
+
+ `\n...`
|
|
3009
|
+
);
|
|
3010
|
+
});
|
|
3011
|
+
|
|
3012
|
+
} catch ( pupp_err ) {
|
|
3013
|
+
log.debug({ code: `ALK0281`, context: `internal`, },
|
|
3014
|
+
`Skipped getting system error screen text with puppeteerjs:`, pupp_err,
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
return `Ran into an error page after page id "${ id }".
|
|
3019
|
+
|
|
3020
|
+
${ page_err_text }`;
|
|
3021
|
+
|
|
3022
|
+
},
|
|
3023
|
+
|
|
3024
|
+
get_al_system_err: async function ( scope ) {
|
|
3025
|
+
/**
|
|
3026
|
+
* Returns message for a docassemble system error page that includes a
|
|
3027
|
+
* sample of the page contents
|
|
3028
|
+
*
|
|
3029
|
+
* @param {Object} scope - Tests state object
|
|
3030
|
+
*
|
|
3031
|
+
* @returns {string}
|
|
3032
|
+
* */
|
|
3033
|
+
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
3034
|
+
let page_err_text = `Unable to get the text on this page. Check the picture of this page if possible.`
|
|
3035
|
+
try {
|
|
3036
|
+
|
|
3037
|
+
page_err_text = await scope.page.evaluate(() => {
|
|
3038
|
+
return (
|
|
3039
|
+
// b64 for `al_formatted_error_message`, which seems most relevant
|
|
3040
|
+
document.querySelector(`#YWxfZm9ybWF0dGVkX2Vycm9yX21lc3NhZ2U`)
|
|
3041
|
+
.textContent
|
|
3042
|
+
.split(`\n`)
|
|
3043
|
+
.slice( 0, 10 )
|
|
3044
|
+
.join(`\n`)
|
|
3045
|
+
);
|
|
3046
|
+
});
|
|
3047
|
+
|
|
3048
|
+
} catch ( pupp_err ) {
|
|
3049
|
+
log.debug({ code: `ALK0`, context: `internal`, },
|
|
3050
|
+
`Skipped getting system error text on Assembly Line custom error screen with puppeteerjs:`, pupp_err,
|
|
3051
|
+
);
|
|
2709
3052
|
}
|
|
2710
3053
|
|
|
2711
|
-
return
|
|
2712
|
-
|
|
3054
|
+
return `Ran into an error page after page id "${ id }".
|
|
3055
|
+
|
|
3056
|
+
${ page_err_text }`;
|
|
3057
|
+
|
|
3058
|
+
},
|
|
3059
|
+
|
|
3060
|
+
// warnOrThrowOnInvalidPage: async function ( scope, { page_result={} }) {
|
|
3061
|
+
// /**
|
|
3062
|
+
// * Given a page with invalid user input or a system error page, warn about
|
|
3063
|
+
// * it or throw an error.
|
|
3064
|
+
// *
|
|
3065
|
+
// * @param {Object} scope - Tests state object
|
|
3066
|
+
// * @param {Object} obj - Named arguments
|
|
3067
|
+
// * @param (Object) obj.page_result - Data from examining a docassemble page
|
|
3068
|
+
// * */
|
|
3069
|
+
// let error = page_result?.error;
|
|
3070
|
+
// if ( page_result && !page_result.ok ) {
|
|
3071
|
+
// if ( error.type.includes(`system`)) {
|
|
3072
|
+
// // Throw on a system error or an unhandled error of our own
|
|
3073
|
+
// reports.addToReport( scope, { type: `error`, code: `ALK0147`, value: error.msg });
|
|
3074
|
+
// throw new Error( error.msg );
|
|
3075
|
+
// } else {
|
|
3076
|
+
// reports.addToReport( scope, { type: `warn`, code: `ALK0282`, value: error.msg });
|
|
3077
|
+
// }
|
|
3078
|
+
// }
|
|
3079
|
+
// },
|
|
3080
|
+
|
|
3081
|
+
// checkForError: async function ( scope ) {
|
|
3082
|
+
// /* Returns whether any system or user error elements were found and, if so,
|
|
3083
|
+
// * returns those elements. The error elements are probably exclusive, but
|
|
3084
|
+
// * we're not sure. */
|
|
3085
|
+
|
|
3086
|
+
// // All results are lists to match `$x`
|
|
3087
|
+
// let possible_errors = {
|
|
3088
|
+
// // TODO: Ensure that intentional callouts don't trigger failure since they
|
|
3089
|
+
// // too use .alert-danger I believe (bootstrap component)
|
|
3090
|
+
// user_alert_error: await scope.page.$$(`.alert-danger`),
|
|
3091
|
+
// // ⚠️ The below does not work if there's not a space between ':' and 'none'
|
|
3092
|
+
// user_field_error: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
|
|
3093
|
+
// // ⚠️ This xpath assumes this content stays in the metadata of the page
|
|
3094
|
+
// system_error: await scope.page.$$(`xpath/.//meta[@content=": Error"]|.//*[contains(@id, "damain")]//h1[@content="Error"]` ),
|
|
3095
|
+
// // 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.
|
|
3096
|
+
// al_custom_system_error: await scope.page.$$(`.question-custom-error-action .daattributions #trigger[data-variable="YWxfY3VzdG9tX2Vycm9yX2FjdGlvbg"]`),
|
|
3097
|
+
// };
|
|
3098
|
+
|
|
3099
|
+
// let error_elems_by_type = {};
|
|
3100
|
+
// let was_found = false;
|
|
3101
|
+
// let type = null;
|
|
3102
|
+
// for ( let error_key in possible_errors ) {
|
|
3103
|
+
// error_elems_by_type[ error_key ] = possible_errors[ error_key ][0];
|
|
3104
|
+
// if ( possible_errors[ error_key ][0] ) {
|
|
3105
|
+
// // If there's a page visible (locally), give us as much time to examine it as timeout allows.
|
|
3106
|
+
// // Because of previous actions, this may still be a bit too long.
|
|
3107
|
+
// // Also, if you're debugging and you close the window early, the process will still
|
|
3108
|
+
// // take this long to exit after the tests finish. Not sure how to account for that.
|
|
3109
|
+
// if ( session_vars.get_debug() ) {
|
|
3110
|
+
// await time.waitForTimeout( ( scope.timeout - (1 * 1000)) );
|
|
3111
|
+
// }
|
|
3112
|
+
// if ( error_key.includes(`user`) ) { type = `user`; }
|
|
3113
|
+
// if ( error_key.includes(`system`) ) { type = `system`; }
|
|
3114
|
+
// was_found = true;
|
|
3115
|
+
// }
|
|
3116
|
+
// }
|
|
3117
|
+
|
|
3118
|
+
// return { was_found, type, error_elems_by_type };
|
|
3119
|
+
// }, // Ends scope.checkForError()
|
|
3120
|
+
|
|
3121
|
+
// processPageError: async function ( scope, { error_elems_by_type = {}, id = `None` }) {
|
|
3122
|
+
// /**
|
|
3123
|
+
// * Throw a cucumber error with the error that appeared on the page.
|
|
3124
|
+
// *
|
|
3125
|
+
// * Discuss: Should we show the text on the page?
|
|
3126
|
+
// *
|
|
3127
|
+
// * @param {Object} scope - Tests state object
|
|
3128
|
+
// * @param {Object} obj - Named arguments
|
|
3129
|
+
// * @param {Object} obj.error_elems_by_type - A map of DOM Node | undefined
|
|
3130
|
+
// * by type of error
|
|
3131
|
+
// * @param {string} obj.id - The current page id
|
|
3132
|
+
// * */
|
|
3133
|
+
// let error_elems_by_type = error_data.error_elems_by_type
|
|
3134
|
+
// // TODO: #656, wasn't checking for this error specifically. May be almost all we need for that.
|
|
3135
|
+
// if ( error_elems_by_type.system_error !== undefined || error_elems_by_type.al_custom_system_error !== undefined ) {
|
|
3136
|
+
// return await scope.throwAnySystemError( scope, { error_elems_by_type, id });
|
|
3137
|
+
// }
|
|
3138
|
+
|
|
3139
|
+
// // If it wasn't a system error, it was a user error
|
|
3140
|
+
// // TODO: Explore whether this still allows an author to check for correctly
|
|
3141
|
+
// // triggering user input invalidation
|
|
3142
|
+
// let user_err_message = `The test tried to continue to the next page after ${ id }, but the user got an error. Check the picture of this page.`
|
|
3143
|
+
// reports.addToReport(scope, { type: `error`, code: `ALK0148`, value: user_err_message });
|
|
3144
|
+
// expect(
|
|
3145
|
+
// ( error_elems_by_type.user_alert_error == undefined && error_elems_by_type.user_field_error == undefined ),
|
|
3146
|
+
// user_err_message
|
|
3147
|
+
// ).to.be.true;
|
|
3148
|
+
|
|
3149
|
+
// return scope;
|
|
3150
|
+
// }, // Ends scope.processPageError()
|
|
3151
|
+
|
|
3152
|
+
// getPageError: async function( scope, { error_elems_by_type = {}, id = `None` }) {
|
|
3153
|
+
// /**
|
|
3154
|
+
// * Throw an error if the author's code caused a system error page to appear.
|
|
3155
|
+
// *
|
|
3156
|
+
// * @param {Object} scope - Tests state object
|
|
3157
|
+
// * @param {Object} obj - Named arguments
|
|
3158
|
+
// * @param {Object} obj.error_elems_by_type - A map of DOMNode | undefined
|
|
3159
|
+
// * by type of error
|
|
3160
|
+
// * @param {string} obj.id - The current page id
|
|
3161
|
+
// * */
|
|
3162
|
+
// // Get the relevant text on the page
|
|
3163
|
+
// let err_elem = error_elems_by_type.system_error || error_elems_by_type.al_custom_system_error;
|
|
3164
|
+
// let page_err_text = '';
|
|
3165
|
+
// if ( err_elem ) {
|
|
3166
|
+
// page_err_text = await get_page_error_text( scope, { error_elem = err_elem });
|
|
3167
|
+
// }
|
|
3168
|
+
|
|
3169
|
+
// // Add to the report
|
|
3170
|
+
// let sys_err = new Error(`The test tried to continue to the next page after ${ id }, but there was a system error.
|
|
3171
|
+
|
|
3172
|
+
// ${ page_err_text }`);
|
|
3173
|
+
// reports.addToReport(scope, { type: `error`, code: `ALK0147`, value: sys_err });
|
|
3174
|
+
|
|
3175
|
+
// // expect(
|
|
3176
|
+
// // ( error_elems_by_type.system_error == undefined && error_elems_by_type.al_custom_system_error == undefined ),
|
|
3177
|
+
// // sys_err
|
|
3178
|
+
// // ).to.be.true;
|
|
3179
|
+
|
|
3180
|
+
// return sys_err;
|
|
3181
|
+
// },
|
|
3182
|
+
|
|
3183
|
+
// get_page_error_text: async function( scope, { error_elem }) {
|
|
3184
|
+
// /**
|
|
3185
|
+
// * Given an element on an error page, return the relevant content.
|
|
3186
|
+
// *
|
|
3187
|
+
// * @param {object} scope - Tests state object
|
|
3188
|
+
// * @param {object} obj - Named arguments
|
|
3189
|
+
// * @param {Object} obj.error_elem - Element that indicated a system err
|
|
3190
|
+
// * */
|
|
3191
|
+
// let page_err_text = `Unable to get the text on this page. Check the picture of this page if possible.`
|
|
3192
|
+
// try {
|
|
3193
|
+
|
|
3194
|
+
// page_err_text = await page.evaluate(( err_elem ) => {
|
|
3195
|
+
|
|
3196
|
+
// let text_container = (
|
|
3197
|
+
// err_elem.closest(`.dabody`)
|
|
3198
|
+
// || err_elem.closest(`#dabody`)
|
|
3199
|
+
// ).querySelector(`* > .danavbarcontainer ~ *`);
|
|
3200
|
+
// return text_container.textContent;
|
|
3201
|
+
|
|
3202
|
+
// }, err_elems[0]);
|
|
3203
|
+
|
|
3204
|
+
// } catch ( pupp_err ) {
|
|
3205
|
+
// log.debug({ code: `ALK0281`, context: `internal`, },
|
|
3206
|
+
// `Skipped getting system error screen text with puppeteerjs:`, pupp_err,
|
|
3207
|
+
// );
|
|
3208
|
+
// }
|
|
3209
|
+
|
|
3210
|
+
// return page_err_text;
|
|
3211
|
+
// },
|
|
2713
3212
|
|
|
2714
3213
|
take_a_screenshot: async ( scope,{ path }) => {
|
|
2715
3214
|
/* Takes a jpeg screenshot. Avoids destroying signatures. */
|
|
@@ -2930,7 +3429,7 @@ module.exports = {
|
|
|
2930
3429
|
/** Given a fetch response headers' Content-Disposition str, return the
|
|
2931
3430
|
* filename of the response's file.
|
|
2932
3431
|
*
|
|
2933
|
-
* @
|
|
3432
|
+
* @returns { string | null } - Name of fetched file
|
|
2934
3433
|
* */
|
|
2935
3434
|
let filename = default_name;
|
|
2936
3435
|
if ( disposition ) {
|
package/lib/steps.js
CHANGED
|
@@ -400,7 +400,7 @@ When(/I? ?gets? to ?.*?(".*").*?with.*?(?:this data:)?/i, {timeout: -1}, async (
|
|
|
400
400
|
// If we need to call recursively, signature function might look like:
|
|
401
401
|
// func( scope, { resolve, reject, supported_table, page_timeout_id })
|
|
402
402
|
// Maybe list of timeout and/or interval ids in state that can all be cleared at once?
|
|
403
|
-
(async function() {
|
|
403
|
+
(async function setFieldsFromStoryTable() {
|
|
404
404
|
try {
|
|
405
405
|
let id = await scope.get_page_id( scope );
|
|
406
406
|
|
|
@@ -416,25 +416,28 @@ When(/I? ?gets? to ?.*?(".*").*?with.*?(?:this data:)?/i, {timeout: -1}, async (
|
|
|
416
416
|
`Rows, including special rows:`, ensured_var_data
|
|
417
417
|
);
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
let result = await scope.setFields( scope, {
|
|
420
|
+
var_data: ensured_var_data, id, ensure_navigation: true
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
resolve( result );
|
|
424
|
+
|
|
425
|
+
// } catch ( err ) {
|
|
426
|
+
// // // Discuss: This error message seems unnecessary cruft
|
|
427
|
+
// // let err_msg = `ALKiln ran into an error when it tried to answer fields on the page with the question id "${ id }". The text above might have more information.`
|
|
428
|
+
// // reports.addToReport( scope, { type: `error`, code: `ALK0062`, value: err_msg });
|
|
429
|
+
|
|
430
|
+
// // Catch a system error or an unhandled error
|
|
431
|
+
// // A system page error trace here may be more useful than in a deeper function
|
|
432
|
+
// reject( err );
|
|
422
433
|
|
|
423
|
-
// Discuss: use Log class to throw this error?
|
|
424
|
-
if ( error.was_found ) { await scope.throwPageError( scope, { id: id }); }
|
|
425
|
-
resolve();
|
|
426
|
-
} catch ( err ) {
|
|
427
|
-
let err_msg = `ALKiln ran into an error when it tried to answer fields on the page with the question id "${ id }". The text above might have more information.`
|
|
428
|
-
reports.addToReport( scope, { type: `error`, code: `ALK0062`, value: err_msg });
|
|
429
|
-
// Throw error instead?
|
|
430
|
-
reject( err );
|
|
431
434
|
} finally {
|
|
432
435
|
clearTimeout( page_timeout_id ); // prevent temporary hang at end of tests
|
|
433
436
|
}
|
|
434
437
|
})();
|
|
435
|
-
}); // ends custom_timeout for
|
|
438
|
+
}); // ends custom_timeout for filling in fields
|
|
436
439
|
|
|
437
|
-
await custom_timeout;
|
|
440
|
+
let page_result = await custom_timeout;
|
|
438
441
|
|
|
439
442
|
// Prep for next loop
|
|
440
443
|
id = await scope.get_page_id( scope );
|
|
@@ -641,7 +644,7 @@ Then('I will be told an answer is invalid', async () => { // √
|
|
|
641
644
|
|
|
642
645
|
let { was_found, error_handlers } = await scope.checkForError( scope );
|
|
643
646
|
|
|
644
|
-
let no_error_msg = `
|
|
647
|
+
let no_error_msg = `The user should have been told their answer was invalid, but that text is missing on the page`;
|
|
645
648
|
if ( !was_found ) { reports.addToReport(scope, { type: `error`, code: `ALK0076`, value: no_error_msg }); }
|
|
646
649
|
expect( was_found, no_error_msg ).to.be.true;
|
|
647
650
|
|
|
@@ -653,6 +656,11 @@ Then('I will be told an answer is invalid', async () => { // √
|
|
|
653
656
|
await scope.afterStep(scope);
|
|
654
657
|
});
|
|
655
658
|
|
|
659
|
+
// ==============
|
|
660
|
+
// Welp, we have a diversion (da DOM changes)
|
|
661
|
+
// Resume here when ready
|
|
662
|
+
// ==============
|
|
663
|
+
|
|
656
664
|
Then('I arrive at the next page', async () => { // √
|
|
657
665
|
/* Tests for detection of url change from button or link tap.
|
|
658
666
|
* Can other things trigger navigation? Re: other inputs things
|
|
@@ -820,6 +828,8 @@ Then(/the "([^"]+)" link opens a working page/, { timeout: -1 }, async ( linkTex
|
|
|
820
828
|
);
|
|
821
829
|
});
|
|
822
830
|
|
|
831
|
+
// #656, problem: tap to continue doesn't go through the full Story Table cycle
|
|
832
|
+
// and thus doesn't get any page errors detected.
|
|
823
833
|
When(/I tap to continue/i, { timeout: -1 }, async () => {
|
|
824
834
|
// Any selectors for this seem somewhat precarious
|
|
825
835
|
// No data will cause an automatic continue when `ensure_navigation` is true
|
|
@@ -849,7 +859,7 @@ When(/I tap the "([^"]+)" element and wait (?:for )?([0-9]+) second(?:s)?/i, { t
|
|
|
849
859
|
// Also, this is always supposed to navigate, and that handles its own waiting.
|
|
850
860
|
|
|
851
861
|
// `page` timeout will deal with custom timeout
|
|
852
|
-
await scope.tapSelectorToNavigate( scope, elemSelector, waitTime * 1000 )
|
|
862
|
+
await scope.tapSelectorToNavigate( scope, elemSelector, waitTime * 1000 );
|
|
853
863
|
});
|
|
854
864
|
|
|
855
865
|
When(/I check the page for (?:accessibility|a11y) issues/i, {timeout: -1}, async () => {
|
|
@@ -972,6 +982,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
972
982
|
buttons_exist = await scope.continue_exists( scope );
|
|
973
983
|
if ( buttons_exist ) {
|
|
974
984
|
await scope.continue( scope );
|
|
985
|
+
// TODO: Use warnOrThrowInvalidPage to error on error page
|
|
975
986
|
} // ends if continue exists
|
|
976
987
|
} // ends if buttons_exist
|
|
977
988
|
if (!( await scope.is_on_server_still(scope))) {
|
|
@@ -1458,18 +1469,20 @@ After(async function(scenario) {
|
|
|
1458
1469
|
// test), it failed. If is was supposed to pass and failed, it also
|
|
1459
1470
|
// failed. E.g. a11y failures let the Scenario pass initially, but
|
|
1460
1471
|
// should cause a failure here.
|
|
1461
|
-
|
|
1462
|
-
//
|
|
1463
|
-
// it as failed too.
|
|
1464
|
-
expected_status_was_met = false;
|
|
1465
|
-
// Keep it general. Users may see this message, e.g. for a11y tests.
|
|
1472
|
+
|
|
1473
|
+
// Keep this msg general. Users may see this message, e.g. for a11y tests.
|
|
1466
1474
|
let msg = `Test ${ changeable_test_status }. `
|
|
1467
|
-
+ `Expected status: ${scope.expected_status}. `
|
|
1475
|
+
+ `Expected status: ${ scope.expected_status }. `
|
|
1468
1476
|
+ `See the report for details.`;
|
|
1469
1477
|
let code = `ALK0099`;
|
|
1470
1478
|
expected_status_err_msg = log.debug({ code: code, level: `error`, },
|
|
1471
1479
|
msg
|
|
1472
1480
|
);
|
|
1481
|
+
|
|
1482
|
+
// Flag this failure to trigger an error later, so cucumber shows
|
|
1483
|
+
// it as failed too.
|
|
1484
|
+
changeable_test_status = `FAILED`;
|
|
1485
|
+
expected_status_was_met = false;
|
|
1473
1486
|
}
|
|
1474
1487
|
}
|
|
1475
1488
|
scope.expected_status = null; // reset for next Scenario
|
package/package.json
CHANGED