@suffolklitlab/alkiln 5.13.2 → 5.13.3-feat-random-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/github_server.yml +10 -8
- package/.github/workflows/playground.yml +5 -2
- package/.github/workflows/unit_tests.yml +1 -1
- package/CHANGELOG.md +32 -1
- package/_alkiln_meta/shift_logs.js +75 -0
- package/action.yml +89 -31
- package/action_for_github_server/action.yml +65 -29
- package/docs/4_environments.md +243 -0
- package/docs/artifacts_folder_name.md +135 -0
- package/docs/constrained_random_tests.md +254 -0
- package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +70 -0
- package/docs/decisions/007_getting_and_storing_logs.md +179 -0
- package/docs/decisions/move_runtime_config_to_sources_folder.md +5 -0
- package/docs/decisions/reports_and_logs_2024_08_11.md +161 -0
- package/docs/kinds_of_errors.md +26 -0
- package/docs/prioritize_constrained_random_tests.md +60 -0
- package/lib/docassemble/docassemble_api_interface.js +77 -51
- package/lib/docassemble/server_install.js +32 -9
- package/lib/docassemble/setup.js +40 -25
- package/lib/docassemble/takedown.js +33 -10
- package/lib/globals.js +10 -0
- package/lib/run_cucumber.js +141 -69
- package/lib/scope.js +101 -77
- package/lib/setup/TestGenerator.js +1755 -0
- package/lib/setup/artifacts.js +20 -0
- package/lib/setup/generate.js +42 -0
- package/lib/setup/get_Gherkin_AST.js +26 -0
- package/lib/setup/validate.js +89 -0
- package/lib/steps.js +132 -54
- package/lib/utils/Log.js +1145 -0
- package/lib/utils/_alkiln-constrained_random_tests_old.js +1776 -0
- package/lib/utils/files.js +18 -11
- package/lib/utils/get_error_stacks.js +16 -0
- package/lib/utils/reports.js +100 -34
- package/lib/utils/session_vars.js +107 -73
- package/lib/utils/set_sources_paths.js +98 -0
- package/lib/utils/time.js +44 -0
- package/package.json +18 -7
- package/tests/log_codes/check_codes.sh +18 -13
- package/tests/log_codes/log_code_expected_instances.txt +91 -9
- package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +177 -0
- package/tests/unit_tests/constrained_random_answers/invalid.test.js +251 -0
- package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +936 -0
- package/tests/unit_tests/constrained_random_answers/valid.test.js +541 -0
- package/tests/unit_tests/example_of_chai_test_lifecycle.js +78 -0
- package/tests/unit_tests/getAllFields.test.js +5 -0
- package/tests/unit_tests/getMatchingRows.test.js +5 -0
- package/tests/unit_tests/log.test.js +557 -0
- package/tests/unit_tests/normalizeTable.test.js +5 -0
- package/lib/utils/log.js +0 -96
- /package/docs/{expected_inernal_results.md → expected_internal_results.md} +0 -0
package/lib/run_cucumber.js
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const fast_glob = require(`fast-glob`);
|
|
5
|
+
const path = require(`path`);
|
|
4
6
|
const cucumber_api = require('@cucumber/cucumber/api');
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const globals = require(`./globals`);
|
|
9
|
+
const Log = require('./utils/Log');
|
|
7
10
|
const session_vars = require('./utils/session_vars');
|
|
11
|
+
const set_sources_paths = require(`./utils/set_sources_paths`);
|
|
8
12
|
|
|
9
13
|
const SUMMARY_FILENAME = `cucumber-report.txt`;
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
function build_config(
|
|
16
|
+
function build_config() {
|
|
17
|
+
/** Use the command line arguments to build the cucumberjs config object.
|
|
18
|
+
*
|
|
19
|
+
* @param {string?} - Arguments passed to the command line.
|
|
20
|
+
*
|
|
21
|
+
* @returns {object} - cucumberjs config object
|
|
22
|
+
* */
|
|
13
23
|
// Turn all the paths to the sources folders into paths to the feature files
|
|
14
24
|
let sources_paths = session_vars.get_sources_paths();
|
|
15
25
|
let feature_paths = [];
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
|
|
27
|
+
// Get all the paths for the right feature files (for cucumber to use)
|
|
28
|
+
for ( let sources_path of sources_paths ) {
|
|
29
|
+
let glob = fast_glob.sync(
|
|
30
|
+
`${ sources_path }/**/*.feature`,
|
|
31
|
+
// Ignore "generator" feature files that we used to generate actual tests
|
|
32
|
+
{ ignore: [`${ sources_path }/${ globals.generators_prefix }*.feature`] }
|
|
33
|
+
);
|
|
34
|
+
feature_paths.push( ...glob );
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
const config = {
|
|
@@ -25,9 +42,10 @@ function build_config( command_args ) {
|
|
|
25
42
|
retry: 1,
|
|
26
43
|
}
|
|
27
44
|
|
|
45
|
+
const cli_args = require(`minimist`)( process.argv.slice(2) );
|
|
28
46
|
// Normalize tag expressions, if there are any
|
|
29
47
|
// Joining and splitting handles both tags surrounded with `""` or those without `""`
|
|
30
|
-
let tags =
|
|
48
|
+
let tags = cli_args[`_`].join(` `).split(` `);
|
|
31
49
|
// If there was no tag expression, `tags` is ['']. Remove that item.
|
|
32
50
|
tags = tags.filter(function(entry) { return entry !== ""; })
|
|
33
51
|
if ( tags.length > 0 ) {
|
|
@@ -53,80 +71,73 @@ function build_config( command_args ) {
|
|
|
53
71
|
}; // Ends build_config()
|
|
54
72
|
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
async function main() {
|
|
57
75
|
/** Create the cucumber config, run the tests, create and
|
|
58
|
-
*
|
|
59
|
-
const environment = { cwd: process.cwd() };
|
|
60
|
-
log.info({ type: `run`, code: `ALK0049`, pre: `environment`, data: environment });
|
|
61
|
-
|
|
62
|
-
// We'll use the user info to construct the possible paths to /sources
|
|
63
|
-
let id = session_vars.get_user_id();
|
|
64
|
-
// `project` is the name in the server docassemble "Project" page
|
|
65
|
-
let project = session_vars.get_user_project_name();
|
|
66
|
-
|
|
67
|
-
// process.argv is a list of strings of commands and args.
|
|
68
|
-
// If using `npm run cucumber`, use `--` before `--sources=./foo`
|
|
69
|
-
let argv = require(`minimist`)( process.argv.slice(2), {
|
|
70
|
-
default: {
|
|
71
|
-
sources: [
|
|
72
|
-
`./docassemble/*/data/sources`, // In GitHub folder
|
|
73
|
-
`/usr/share/docassemble/files/playgroundsources/${id}/${project}/*.feature`, // From playground without S3
|
|
74
|
-
`/tmp/playgroundsources/${id}/${project}/*.feature` // From playground with S3
|
|
75
|
-
],
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Make sure `sources` is always a list
|
|
80
|
-
let sources_list = argv.sources;
|
|
81
|
-
if ( typeof argv.sources === `string` ) {
|
|
82
|
-
sources_list = [ argv.sources ];
|
|
83
|
-
}
|
|
76
|
+
* manipulate the output. */
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
const environment = { cwd: process.cwd() };
|
|
79
|
+
log.info({ code: `ALK0049`, context: `pre_run environment` },
|
|
80
|
+
environment
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// TODO: These are not used as far as I can tell. Delete them
|
|
84
|
+
// // We'll use the user info to construct the possible paths to /sources
|
|
85
|
+
// let id = session_vars.get_user_id();
|
|
86
|
+
// // `project` is the name in the server docassemble "Project" page
|
|
87
|
+
// let project = session_vars.get_user_project_name();
|
|
93
88
|
|
|
94
89
|
const { runConfiguration } = await cucumber_api.loadConfiguration({
|
|
95
90
|
provided: build_config( argv ),
|
|
96
91
|
}, environment);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
log.info({
|
|
92
|
+
log.info({ code: `ALK0051`, context: `pre_run config`, },
|
|
93
|
+
runConfiguration
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const codeDir = path.dirname(require.main.filename);
|
|
97
|
+
log.info({ code: `ALK0052`, context: `pre_run directory`, },
|
|
98
|
+
codeDir, `\n\n`
|
|
99
|
+
);
|
|
103
100
|
const support = await cucumber_api.loadSupport({ support: {
|
|
104
101
|
requireModules: [ `${ codeDir }/index.js` ],
|
|
105
102
|
requirePaths: [ `${ codeDir }/index.js` ],
|
|
106
103
|
importPaths: [],
|
|
107
104
|
}, sources: {paths: []}}, environment);
|
|
108
105
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
// ====================
|
|
107
|
+
/** Run */
|
|
108
|
+
// ====================
|
|
112
109
|
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
110
|
+
// ====================
|
|
111
|
+
|
|
112
|
+
/** Write to files.
|
|
113
|
+
*
|
|
114
|
+
* Note: Avoid adding to the purely report-focused files. Those are files
|
|
115
|
+
* where authors can focus on the info we give them, which they often miss
|
|
116
|
+
* in the GitHub action.
|
|
117
|
+
*/
|
|
118
|
+
|
|
113
119
|
if ( success ) {
|
|
114
|
-
log.success({
|
|
120
|
+
log.success({ code: `ALK0054`, context: `post_run`, },
|
|
121
|
+
`Tests succeeded!`
|
|
122
|
+
);
|
|
115
123
|
} else {
|
|
116
|
-
|
|
124
|
+
// Don't want to throw error here, so don't pass error object
|
|
125
|
+
log.warn({ code: `ALK0055`, context: `post_run`, },
|
|
126
|
+
`The tests ran into something unexpected. See warnings and errors above.`
|
|
127
|
+
);
|
|
117
128
|
}
|
|
118
129
|
// Custom exit code. May overlap with other systems.
|
|
119
130
|
// Best info I've found via stack overflow: https://tldp.org/LDP/abs/html/exitcodes.html
|
|
120
131
|
const exitCode = success ? 0 : 50;
|
|
121
132
|
process.exitCode = exitCode;
|
|
122
133
|
|
|
123
|
-
// This is hardcoded information from `provide_config` above.
|
|
124
|
-
// This code isn't used for anything other than detecting and removing color codes.
|
|
125
134
|
let summary_file_name = SUMMARY_FILENAME;
|
|
126
|
-
fs.readFile(summary_file_name, 'utf8', (
|
|
127
|
-
if (
|
|
135
|
+
fs.readFile(summary_file_name, 'utf8', (read_error, text) => {
|
|
136
|
+
if ( read_error ) {
|
|
128
137
|
// Something else will probably show an error that's more relevant
|
|
129
|
-
log.
|
|
138
|
+
log.warn({ code: `ALK0056`, context: `post_run error`, },
|
|
139
|
+
`Failed to read the report of the 3rd party library, cucumberjs. There may be more information above or below.`, read_error
|
|
140
|
+
);
|
|
130
141
|
return;
|
|
131
142
|
}
|
|
132
143
|
|
|
@@ -134,20 +145,81 @@ function build_config( command_args ) {
|
|
|
134
145
|
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
|
|
135
146
|
text = text.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
|
|
136
147
|
|
|
137
|
-
// Put it in the correct file in the correct folder
|
|
138
|
-
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
139
148
|
try {
|
|
140
|
-
|
|
141
|
-
} catch ( error ) {
|
|
142
|
-
log.info({ type: `run`, code: `ALK0182`, pre: `Something probably went wrong earlier. Look above for more details, but here is more information just in case: ALKiln was unable to write to ${ artifacts_path }/${ log.debug_log_file }.`, data: error });
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
// Add to a unexpected results file when tests themselves fail
|
|
149
|
+
// Add to the debug and non-passed test output files
|
|
146
150
|
if ( exitCode === 50 ) {
|
|
147
|
-
|
|
151
|
+
// Save only non-passing test output to cut out cruft.
|
|
152
|
+
// Our reports save only non-passing tests to this file.
|
|
153
|
+
log.debug({ icon: `🔎`, code: `ALK0216`, context: `post_run`, level: `warn` },
|
|
154
|
+
`\n\n`, text
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
log.debug({ code: `ALK0225`, context: `post_run`, },
|
|
158
|
+
`\n\n`, text
|
|
159
|
+
);
|
|
148
160
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
|
|
162
|
+
/** Include debug info at the bottom of the unexpected results file if
|
|
163
|
+
* needed. Avoid creating the file if its not necessary. */
|
|
164
|
+
let temp_debug_unexpected_content = ``;
|
|
165
|
+
try {
|
|
166
|
+
temp_debug_unexpected_content = fs.readFileSync(`${ log.path }/${ log.temp_unexpected_debug_log_filename }`);
|
|
167
|
+
} catch ( ignore_err ) { /* Missing debug info is ok */ }
|
|
168
|
+
|
|
169
|
+
if ( temp_debug_unexpected_content !== `` ) {
|
|
170
|
+
// Add spacing before the debug content if file already has content
|
|
171
|
+
try {
|
|
172
|
+
fs.readFileSync(`${ log.path }/${ log.unexpected_filename }`); // Does it have contents?
|
|
173
|
+
fs.appendFileSync(`${ log.path }/${ log.unexpected_filename }`, `\n\n\n\n\n` );
|
|
174
|
+
} catch ( ignore_err ) {
|
|
175
|
+
/* Missing "unexpected results" file is ok */
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// TODO: Is this meant to write to the debug file or is this debug info
|
|
179
|
+
// really supposed to be in the "unexpected results" file? Or are these
|
|
180
|
+
// comments old?
|
|
181
|
+
|
|
182
|
+
// Add the heading for debug content
|
|
183
|
+
fs.appendFileSync( `${ log.path }/${ log.unexpected_filename }`,
|
|
184
|
+
`≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
|
185
|
+
🐛 Full DEBUG info (some repeated content) 🐛
|
|
186
|
+
≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n\n`
|
|
187
|
+
);
|
|
188
|
+
// Add the debug content itself
|
|
189
|
+
fs.appendFileSync(`${ log.path }/${ log.unexpected_filename }`, temp_debug_unexpected_content );
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
} catch ( writing_cucumber_error ) {
|
|
193
|
+
let code = `ALK0182`;
|
|
194
|
+
let context = `post_run`
|
|
195
|
+
let msg = `Something probably went wrong earlier. See warnings and errors above. Skipped writing to ${ log.path }/${ log.debug_log_filename }.`;
|
|
196
|
+
try {
|
|
197
|
+
log.warn({ code, context, }, msg, writing_cucumber_error );
|
|
198
|
+
} catch ( all_writing_error ) {
|
|
199
|
+
console.warn( `🔎 ${ code } ${ context } WARNING ${ Date.now() }: ${ msg }` );
|
|
200
|
+
console.warn( writing_cucumber_error );
|
|
201
|
+
console.warn( all_writing_error );
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
} // ends try writing to debug files
|
|
205
|
+
|
|
206
|
+
return;
|
|
152
207
|
});
|
|
153
|
-
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// 1. session_vars - make sure all the required env vars exist
|
|
212
|
+
session_vars.validateEnvironment();
|
|
213
|
+
|
|
214
|
+
// 2. Prepare logs
|
|
215
|
+
// process.argv is a list of strings of commands and args.
|
|
216
|
+
// If using `npm run cucumber` (as opposed to the bin commands),
|
|
217
|
+
// use `--` before `--sources=./foo`
|
|
218
|
+
const argv = require(`minimist`)( process.argv.slice(2) );
|
|
219
|
+
const log = new Log({ path: argv.path, context: `run` });
|
|
220
|
+
|
|
221
|
+
// 3. Prep the sources paths to find .feature files
|
|
222
|
+
let sources_paths = set_sources_paths({ log });
|
|
223
|
+
|
|
224
|
+
// 4. run
|
|
225
|
+
main();
|
package/lib/scope.js
CHANGED
|
@@ -13,14 +13,12 @@ const pdf_reader = require('./utils/pdf_reader');
|
|
|
13
13
|
const session_vars = require('./utils/session_vars');
|
|
14
14
|
const get_base_interview_url = require(`./docassemble/get_base_interview_url`);
|
|
15
15
|
const time = require('./utils/time');
|
|
16
|
-
const log = require('./utils/log');
|
|
17
16
|
const { waitForTimeout } = require('./utils/time');
|
|
18
17
|
const files = require('./utils/files' );
|
|
19
18
|
const da_api = require('./docassemble/docassemble_api_interface');
|
|
20
19
|
const wrapPromiseWithTimeout = require('./cucumber_8_shim');
|
|
21
20
|
const reports = require(`./utils/reports`);
|
|
22
21
|
|
|
23
|
-
|
|
24
22
|
let ordinal_to_integer = {
|
|
25
23
|
only: 0, first: 0, second: 1, third: 2, fourth: 3, fifth: 4,
|
|
26
24
|
sixth: 5, seventh: 6, eighth: 7, ninth: 8, tenth: 9,
|
|
@@ -84,10 +82,16 @@ let toBase64 = function ( utf8_str, exclude_padding=true) {
|
|
|
84
82
|
|
|
85
83
|
let proxies_regex = /(\bx\.)|(\bx\[)|(\[[ijklmn]\])/g;
|
|
86
84
|
let da_base64_regex = /^[A-Za-z0-9+/]+$/;
|
|
85
|
+
let log = null;
|
|
87
86
|
|
|
88
87
|
module.exports = {
|
|
89
88
|
trigger_not_needed_flag: `ALKiln: no trigger variable needed`,
|
|
90
89
|
|
|
90
|
+
|
|
91
|
+
set_log: function ({ log_obj }) {
|
|
92
|
+
log = log_obj;
|
|
93
|
+
},
|
|
94
|
+
|
|
91
95
|
getSafeScenarioBaseFilename: async function( scope, { scenario }) {
|
|
92
96
|
/** Return a string that's safe to be a filename. Include the Scenario
|
|
93
97
|
* description and tags.
|
|
@@ -514,13 +518,17 @@ module.exports = {
|
|
|
514
518
|
},
|
|
515
519
|
|
|
516
520
|
continue_button_selector: `fieldset.da-field-buttons button[type="submit"].btn-primary`,
|
|
521
|
+
continue_button_selector_backup: `.da-fieldset.da-field-buttons button[type="submit"].btn-primary`,
|
|
517
522
|
signature_selector: `fieldset .dasigsave`,
|
|
523
|
+
signature_selector_backup: `.da-fieldset .dasigsave`,
|
|
518
524
|
continue: async function ( scope ) {
|
|
519
525
|
/* Presses whatever button it finds that might lead to the next page. */
|
|
520
526
|
// Any selectors I find seem somewhat precarious.
|
|
521
527
|
let elem = await Promise.race([
|
|
522
528
|
scope.page.waitForSelector(scope.continue_button_selector), // other pages (this is the most consistent way)
|
|
529
|
+
scope.page.waitForSelector(scope.continue_button_selector_backup),
|
|
523
530
|
scope.page.waitForSelector(scope.signature_selector), // signature page
|
|
531
|
+
scope.page.waitForSelector(scope.signature_selector_backup),
|
|
524
532
|
]);
|
|
525
533
|
await elem.evaluate( el => { return el.className });
|
|
526
534
|
// Waits for navigation or user error
|
|
@@ -540,9 +548,11 @@ module.exports = {
|
|
|
540
548
|
// <button type="submit" class="btn btn-da btn-danger" name="X211bHRpcGxlX2Nob2ljZQ" value="0">exit</button>
|
|
541
549
|
// `buttons:` can be used in question blocks as choices
|
|
542
550
|
let regular = await scope.page.$(scope.continue_button_selector);
|
|
551
|
+
let regular_backup = await scope.page.$(scope.continue_button_selector_backup);
|
|
543
552
|
let signature = await scope.page.$(scope.signature_selector);
|
|
553
|
+
let signature_backup = await scope.page.$(scope.signature_selector_backup);
|
|
544
554
|
|
|
545
|
-
return regular !== null || signature !== null;
|
|
555
|
+
return regular !== null || regular_backup !== null || signature !== null || signature_backup !== null;
|
|
546
556
|
|
|
547
557
|
}, // Ends scope.continue_exists()
|
|
548
558
|
|
|
@@ -692,7 +702,7 @@ module.exports = {
|
|
|
692
702
|
});
|
|
693
703
|
expect( load_result.error ).to.equal( '' );
|
|
694
704
|
} else {
|
|
695
|
-
reports.addToReport(scope, { type: `row info`, code: `ALK0117`, value: `Successfully found the interview
|
|
705
|
+
reports.addToReport(scope, { type: `row info`, code: `ALK0117`, value: `Successfully found the interview` });
|
|
696
706
|
}
|
|
697
707
|
|
|
698
708
|
return scope.page;
|
|
@@ -711,19 +721,19 @@ module.exports = {
|
|
|
711
721
|
if ( !scenario.interviews ) { scenario.interviews = {}; }
|
|
712
722
|
if ( !scenario.interviews[ vars.uid ] ) {
|
|
713
723
|
scenario.interviews[ vars.uid ] = vars;
|
|
714
|
-
log.debug({
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
});
|
|
724
|
+
log.debug({ code: `ALK0185`, context: `session_id_etc` },
|
|
725
|
+
`Saved new interview data. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
726
|
+
);
|
|
718
727
|
} else {
|
|
719
|
-
log.debug({
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
});
|
|
728
|
+
log.debug({ code: `ALK0186`, context: `session_id_etc` },
|
|
729
|
+
`Interview data already existed. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
730
|
+
);
|
|
723
731
|
}
|
|
724
732
|
return vars;
|
|
725
733
|
} else {
|
|
726
|
-
log.debug({ code: `ALK0187`,
|
|
734
|
+
log.debug({ code: `ALK0187`, context: `session_id_etc` },
|
|
735
|
+
`Found no interview data.`
|
|
736
|
+
);
|
|
727
737
|
return null;
|
|
728
738
|
}
|
|
729
739
|
}, // Ends scope.set_interview_session_data()
|
|
@@ -870,11 +880,9 @@ module.exports = {
|
|
|
870
880
|
supported_table.push( result );
|
|
871
881
|
} // ends for every row
|
|
872
882
|
|
|
873
|
-
log.debug({
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
pre: `scope.normalizedTable():\n ${JSON.stringify( supported_table )}`
|
|
877
|
-
});
|
|
883
|
+
log.debug({ code: `ALK0122`, context: `table`, },
|
|
884
|
+
`scope.normalizedTable():`, supported_table
|
|
885
|
+
);
|
|
878
886
|
|
|
879
887
|
return supported_table;
|
|
880
888
|
}, // Ends scope.normalizeTable()
|
|
@@ -916,7 +924,7 @@ module.exports = {
|
|
|
916
924
|
// All the different types of fields
|
|
917
925
|
// buttons, canvases, inputs of all kinds, selects (dropdowns), textareas. Are there more?
|
|
918
926
|
// Will deal with `option` once inside `select`
|
|
919
|
-
let all_nodes = $( `#dasigpage, fieldset button, .daquestionactionbutton, fieldset input, .da-form-group input, .da-form-group select, form select, .da-form-group textarea` );
|
|
927
|
+
let all_nodes = $( `#dasigpage, fieldset button, .da-fieldset button, .daquestionactionbutton, fieldset input, da-fieldset input, .da-form-group input, .da-form-group select, form select, .da-form-group textarea` );
|
|
920
928
|
for ( let node of all_nodes ) {
|
|
921
929
|
// Decision: Do not abstract the below. There's too much data to pass around for it to make sense
|
|
922
930
|
let $node = $( node );
|
|
@@ -1024,11 +1032,9 @@ module.exports = {
|
|
|
1024
1032
|
fields.push( field_data );
|
|
1025
1033
|
} // ends for every node
|
|
1026
1034
|
|
|
1027
|
-
log.debug({
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
pre: `page fields:\n${ JSON.stringify( fields )}`
|
|
1031
|
-
});
|
|
1035
|
+
log.debug({ code: `ALK0124`, context: `page fields`, },
|
|
1036
|
+
fields
|
|
1037
|
+
);
|
|
1032
1038
|
return fields;
|
|
1033
1039
|
}, // Ends scope.getAllFields()
|
|
1034
1040
|
|
|
@@ -1078,11 +1084,9 @@ module.exports = {
|
|
|
1078
1084
|
/* Return variable names and values combined in ways that can
|
|
1079
1085
|
* try to match table rows. Tables can have two properties:
|
|
1080
1086
|
* variable name and value. */
|
|
1081
|
-
log.debug({
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
pre: `scope.getPossibleTableRowMatchers() var_names: ${JSON.stringify(var_names)} values: ${JSON.stringify(values)}`
|
|
1085
|
-
});
|
|
1087
|
+
log.debug({ code: `ALK0126`, context: `matching`, },
|
|
1088
|
+
`scope.getPossibleTableRowMatchers() var_names:`, var_names, `values:`, values
|
|
1089
|
+
);
|
|
1086
1090
|
let possible_table_rows = [];
|
|
1087
1091
|
for ( let var_name of var_names ) {
|
|
1088
1092
|
// Inputs like textareas don't have values
|
|
@@ -1557,17 +1561,13 @@ module.exports = {
|
|
|
1557
1561
|
reports.addToReport(scope, { type: `warning`, code: `ALK0130`, value: multiple_matches_msg });
|
|
1558
1562
|
}
|
|
1559
1563
|
|
|
1560
|
-
log.debug({
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
pre: `final_matching_rows:\n${JSON.stringify( final_matching_rows )}`
|
|
1564
|
-
});
|
|
1564
|
+
log.debug({ code: `ALK0131`, context: `matching`, },
|
|
1565
|
+
`final_matching_rows:`, final_matching_rows
|
|
1566
|
+
);
|
|
1565
1567
|
if ( final_matching_rows.length == 0) {
|
|
1566
|
-
log.debug({
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
pre: `field:\n${JSON.stringify( field )}`
|
|
1570
|
-
});
|
|
1568
|
+
log.debug({ code: `ALK0132`, context: `matching`, },
|
|
1569
|
+
`0 matching rows. Field:`, field
|
|
1570
|
+
);
|
|
1571
1571
|
}
|
|
1572
1572
|
|
|
1573
1573
|
return final_matching_rows;
|
|
@@ -1653,11 +1653,9 @@ module.exports = {
|
|
|
1653
1653
|
|
|
1654
1654
|
get_handle_from_field: async function ( scope, { field }) {
|
|
1655
1655
|
/** Get the element's puppeteer handle using the field's selector. */
|
|
1656
|
-
log.debug({
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
pre: `scope.get_handle_from_field() field.selector: ${JSON.stringify(field.selector)}`
|
|
1660
|
-
});
|
|
1656
|
+
log.debug({ code: `ALK0133`, },
|
|
1657
|
+
`scope.get_handle_from_field() field.selector:`, field.selector
|
|
1658
|
+
);
|
|
1661
1659
|
let handle = await scope.page.evaluateHandle(( selector ) => {
|
|
1662
1660
|
let elem = document.querySelector( selector );
|
|
1663
1661
|
// If the element has a sibling that's a label, we need to
|
|
@@ -2120,6 +2118,38 @@ module.exports = {
|
|
|
2120
2118
|
// await scope.page.mouse.move(1, 1); // Too big? Wait a while before drawing?
|
|
2121
2119
|
await scope.page.mouse.up();
|
|
2122
2120
|
},
|
|
2121
|
+
|
|
2122
|
+
get_page_id: async function ( scope ) {
|
|
2123
|
+
/** Return the current page's docassemble `question` block ID or `null`
|
|
2124
|
+
*
|
|
2125
|
+
* Note: The ID is already sanitized by docassemble.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {object} scope - Tests state object
|
|
2128
|
+
* @param {object} scope.page - puppeteerjs page object
|
|
2129
|
+
*
|
|
2130
|
+
* @returns {null | string}
|
|
2131
|
+
* */
|
|
2132
|
+
let question_id_match = await scope.page.$eval(`body`, function (elem) {
|
|
2133
|
+
return elem.className.match(/question-([^ ]+)/);
|
|
2134
|
+
});
|
|
2135
|
+
if ( question_id_match !== null ) {
|
|
2136
|
+
return question_id_match[ 1 ];
|
|
2137
|
+
}
|
|
2138
|
+
return null;
|
|
2139
|
+
},
|
|
2140
|
+
|
|
2141
|
+
to_docassemble_sanitized_id: function ( risky_id ) {
|
|
2142
|
+
/** Return a version of `risky_id` that's sanitized the same way docassemble
|
|
2143
|
+
* sanitizes its IDs to turn them into HTML class names in python:
|
|
2144
|
+
* re.sub(r'[^A-Za-z0-9]+', '-', interview_status.question.id.lower())
|
|
2145
|
+
* (reference needed)
|
|
2146
|
+
*
|
|
2147
|
+
* @param {string} risky_id - The un-sanitized id
|
|
2148
|
+
*
|
|
2149
|
+
* @returns {string} - Sanitized id
|
|
2150
|
+
* */
|
|
2151
|
+
return risky_id.toLowerCase().replace(/[^A-Za-z0-9]+/g, '-');
|
|
2152
|
+
},
|
|
2123
2153
|
|
|
2124
2154
|
examinePageID: async function ( scope, question_id = '' ) {
|
|
2125
2155
|
/* Looks for a sanitized version of the question id as it's written
|
|
@@ -2169,7 +2199,9 @@ module.exports = {
|
|
|
2169
2199
|
* @param ensure_all_vars_used {bool} [false] = Default false. If true, errors if any
|
|
2170
2200
|
* object in var_data is not used during running of this function.
|
|
2171
2201
|
*/
|
|
2172
|
-
log.debug({
|
|
2202
|
+
log.debug({ code: `ALK0221`, before: `~~~~~~~~~~~~~~\n`, },
|
|
2203
|
+
`scope.setFields()`
|
|
2204
|
+
);
|
|
2173
2205
|
|
|
2174
2206
|
// Only record the page id once for each page to which we navigate
|
|
2175
2207
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
@@ -2213,8 +2245,7 @@ module.exports = {
|
|
|
2213
2245
|
// Accumulate number of times this row was used. Esp important for `.target_number`
|
|
2214
2246
|
await scope.increment_row_use( scope, { row: row_used });
|
|
2215
2247
|
|
|
2216
|
-
|
|
2217
|
-
|
|
2248
|
+
log.stdout({}, `\x1b[36m${ '*' }\x1b[0m`); // assumes var was set if no error occurred
|
|
2218
2249
|
}
|
|
2219
2250
|
} // ends for every non-submit-button field on the page
|
|
2220
2251
|
} // ends while fields are still being set
|
|
@@ -2259,7 +2290,7 @@ module.exports = {
|
|
|
2259
2290
|
// }
|
|
2260
2291
|
|
|
2261
2292
|
did_navigate = true;
|
|
2262
|
-
|
|
2293
|
+
log.stdout({}, `\x1b[36m${ 'v' }\x1b[0m`); // pressed button successfully
|
|
2263
2294
|
break;
|
|
2264
2295
|
|
|
2265
2296
|
} // ends if row_used
|
|
@@ -2306,7 +2337,7 @@ module.exports = {
|
|
|
2306
2337
|
// }
|
|
2307
2338
|
|
|
2308
2339
|
did_navigate = true;
|
|
2309
|
-
|
|
2340
|
+
log.stdout({}, `\x1b[36m${ '>' }\x1b[0m`); // continued successfully
|
|
2310
2341
|
} // ends if !did_navigate && ensure_navigation
|
|
2311
2342
|
|
|
2312
2343
|
if ( did_navigate && scope.check_all_for_a11y ) {
|
|
@@ -2635,6 +2666,7 @@ module.exports = {
|
|
|
2635
2666
|
|
|
2636
2667
|
let fullPage = true;
|
|
2637
2668
|
let signature_elem = await scope.page.$(scope.signature_selector);
|
|
2669
|
+
signature_elem = signature_elem || await scope.page.$(scope.signature_selector_backup);
|
|
2638
2670
|
if ( signature_elem !== null ) {
|
|
2639
2671
|
fullPage = false;
|
|
2640
2672
|
}
|
|
@@ -3102,36 +3134,30 @@ module.exports = {
|
|
|
3102
3134
|
let redirect_promise = scope.page.waitForResponse(function ( response ) {
|
|
3103
3135
|
return response.status() === 302;
|
|
3104
3136
|
}, { signal: controller.signal })
|
|
3105
|
-
.catch((
|
|
3106
|
-
log.debug({
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
data: error,
|
|
3110
|
-
});
|
|
3137
|
+
.catch(( catch_info ) => {
|
|
3138
|
+
log.debug({ code: `ALK0204`, context: `nav`, },
|
|
3139
|
+
`302 sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3140
|
+
);
|
|
3111
3141
|
});
|
|
3112
3142
|
|
|
3113
3143
|
// Invalid credentials, no navigation, no sign in
|
|
3114
3144
|
let wrong_sign_in_promise = scope.page.waitForResponse(function ( response ) {
|
|
3115
3145
|
return response.status() >= 200 && response.status() < 300 && response.url().includes(`/sign-in`);
|
|
3116
3146
|
}, { signal: controller.signal })
|
|
3117
|
-
.catch((
|
|
3118
|
-
log.debug({
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
data: error,
|
|
3122
|
-
});
|
|
3147
|
+
.catch(( catch_info ) => {
|
|
3148
|
+
log.debug({ code: `ALK0205`, context: `nav`, },
|
|
3149
|
+
`200s sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3150
|
+
);
|
|
3123
3151
|
});
|
|
3124
3152
|
|
|
3125
3153
|
// System error
|
|
3126
3154
|
let error_promise = scope.page.waitForResponse(function ( response ) {
|
|
3127
3155
|
return response.status() >= 500 && response.status() < 600;
|
|
3128
3156
|
}, { signal: controller.signal })
|
|
3129
|
-
.catch((
|
|
3130
|
-
log.debug({
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
data: error,
|
|
3134
|
-
});
|
|
3157
|
+
.catch(( catch_info ) => {
|
|
3158
|
+
log.debug({ code: `ALK0206`, context: `nav`, },
|
|
3159
|
+
`500s sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3160
|
+
);
|
|
3135
3161
|
});
|
|
3136
3162
|
|
|
3137
3163
|
let click_promise = elem.click(); // MUST complete
|
|
@@ -3150,10 +3176,9 @@ module.exports = {
|
|
|
3150
3176
|
// Clean up unresolved promises
|
|
3151
3177
|
controller.abort();
|
|
3152
3178
|
|
|
3153
|
-
log.debug({
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
});
|
|
3179
|
+
log.debug({ code: `ALK0208`, context: `nav` },
|
|
3180
|
+
`Sign-in winner:`, winner
|
|
3181
|
+
);
|
|
3157
3182
|
|
|
3158
3183
|
return winner;
|
|
3159
3184
|
},
|
|
@@ -3176,7 +3201,7 @@ module.exports = {
|
|
|
3176
3201
|
} else {
|
|
3177
3202
|
// Try to give a random answer for that element tag
|
|
3178
3203
|
await scope.set_random_input_for[ field.tag ]( scope, { field: field });
|
|
3179
|
-
|
|
3204
|
+
log.stdout({}, `\x1b[36m${ '*' }\x1b[0m`); // assumes var was set if no error occurred
|
|
3180
3205
|
}
|
|
3181
3206
|
|
|
3182
3207
|
} // End for all page fields
|
|
@@ -3212,11 +3237,10 @@ module.exports = {
|
|
|
3212
3237
|
}
|
|
3213
3238
|
if (field == null) {
|
|
3214
3239
|
// Just skip it? Shouldn't happen but has before. Dig in more
|
|
3215
|
-
log.debug({
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
});
|
|
3240
|
+
log.debug({ code: `ALK0164`, context: `random`, },
|
|
3241
|
+
`A random button field choice was null. These were the fields to choose from:`,
|
|
3242
|
+
fields
|
|
3243
|
+
);
|
|
3220
3244
|
return;
|
|
3221
3245
|
}
|
|
3222
3246
|
let handle = await scope.get_handle_from_field( scope, { field: field });
|