@suffolklitlab/alkiln 5.12.0-delete6 → 5.13.0-logs1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +29 -0
- package/.github/workflows/github_server.yml +11 -9
- package/.github/workflows/log_code_tests.yml +0 -2
- package/.github/workflows/playground.yml +7 -5
- package/CHANGELOG.md +30 -5
- package/CONTRIBUTING.md +98 -83
- package/action.yml +66 -28
- package/action_for_github_server/action.yml +59 -31
- package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +46 -0
- package/docs/decisions/adopt_architectural_decision_records.md +58 -0
- package/docs/decisions/detect_sign_in.md +124 -0
- package/docs/decisions/pdfjs_version_2024_08_08.md +18 -0
- package/docs/decisions/reports_and_logs_2024_08_11.md +141 -0
- package/docs/decisions/update_puppeteer_2024_07_28.md +62 -0
- package/lib/docassemble/docassemble_api_interface.js +63 -34
- package/lib/docassemble/server_install.js +23 -9
- package/lib/docassemble/setup.js +33 -20
- package/lib/docassemble/takedown.js +27 -7
- package/lib/run_cucumber.js +81 -33
- package/lib/scope.js +152 -79
- package/lib/steps.js +84 -51
- package/lib/utils/files.js +8 -6
- package/lib/utils/log.js +477 -84
- package/lib/utils/reports.js +14 -5
- package/lib/utils/session_vars.js +25 -18
- package/lib/utils/time.js +44 -0
- package/npm-shrinkwrap.json +635 -193
- package/package.json +6 -6
- package/tests/log_codes/check_codes.sh +163 -108
- package/tests/log_codes/log_code_expected_instances.txt +213 -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 +20 -0
- package/tests/unit_tests/normalizeTable.test.js +5 -0
- package/tests/log_codes/deleted_codes.json +0 -8
package/lib/run_cucumber.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require(`path`);
|
|
4
5
|
const cucumber_api = require('@cucumber/cucumber/api');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const Log = require('./utils/log');
|
|
7
8
|
const session_vars = require('./utils/session_vars');
|
|
8
9
|
|
|
9
10
|
const SUMMARY_FILENAME = `cucumber-report.txt`;
|
|
@@ -53,11 +54,14 @@ function build_config( command_args ) {
|
|
|
53
54
|
}; // Ends build_config()
|
|
54
55
|
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
async function main() {
|
|
57
58
|
/** Create the cucumber config, run the tests, create and
|
|
58
59
|
* manipluate the output. */
|
|
60
|
+
|
|
59
61
|
const environment = { cwd: process.cwd() };
|
|
60
|
-
log.info({
|
|
62
|
+
log.info({ code: `ALK0049`, context: `pre_run environment` },
|
|
63
|
+
environment
|
|
64
|
+
);
|
|
61
65
|
|
|
62
66
|
// We'll use the user info to construct the possible paths to /sources
|
|
63
67
|
let id = session_vars.get_user_id();
|
|
@@ -65,8 +69,9 @@ function build_config( command_args ) {
|
|
|
65
69
|
let project = session_vars.get_user_project_name();
|
|
66
70
|
|
|
67
71
|
// process.argv is a list of strings of commands and args.
|
|
68
|
-
// If using `npm run cucumber
|
|
69
|
-
|
|
72
|
+
// If using `npm run cucumber` (as opposed to the bin commands),
|
|
73
|
+
// use `--` before `--sources=./foo`
|
|
74
|
+
const argv = require(`minimist`)( process.argv.slice(2), {
|
|
70
75
|
default: {
|
|
71
76
|
sources: [
|
|
72
77
|
`./docassemble/*/data/sources`, // In GitHub folder
|
|
@@ -85,35 +90,45 @@ function build_config( command_args ) {
|
|
|
85
90
|
let one_dir_exists = session_vars.set_sources_paths( sources_list );
|
|
86
91
|
if ( !one_dir_exists ) {
|
|
87
92
|
// If we don't throw, it'll be more confusing when no tests run
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
log.throw({ code: `ALK0050`, context: `pre_run`,
|
|
94
|
+
error: new ReferenceError(
|
|
95
|
+
`ALKiln can't find your "sources" folder(s). See above for details.`
|
|
96
|
+
),
|
|
97
|
+
});
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
const { runConfiguration } = await cucumber_api.loadConfiguration({
|
|
95
101
|
provided: build_config( argv ),
|
|
96
102
|
}, environment);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
log.info({
|
|
103
|
+
log.info({ code: `ALK0051`, context: `pre_run config`, },
|
|
104
|
+
runConfiguration
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const codeDir = path.dirname(require.main.filename);
|
|
108
|
+
log.info({ code: `ALK0052`, context: `pre_run directory`, },
|
|
109
|
+
codeDir, `\n\n`
|
|
110
|
+
);
|
|
103
111
|
const support = await cucumber_api.loadSupport({ support: {
|
|
104
112
|
requireModules: [ `${ codeDir }/index.js` ],
|
|
105
113
|
requirePaths: [ `${ codeDir }/index.js` ],
|
|
106
114
|
importPaths: [],
|
|
107
115
|
}, sources: {paths: []}}, environment);
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
// ====================
|
|
118
|
+
// Run
|
|
119
|
+
// ====================
|
|
112
120
|
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
121
|
+
// ====================
|
|
122
|
+
|
|
113
123
|
if ( success ) {
|
|
114
|
-
log.success({
|
|
124
|
+
log.success({ code: `ALK0054`, context: `post_run`, },
|
|
125
|
+
`Tests succeeded!`
|
|
126
|
+
);
|
|
115
127
|
} else {
|
|
116
|
-
|
|
128
|
+
// Don't want to throw error here, so don't pass error object
|
|
129
|
+
log.warn({ code: `ALK0055`, context: `post_run`, },
|
|
130
|
+
`The tests ran into something unexpected. Look above for more details.`
|
|
131
|
+
);
|
|
117
132
|
}
|
|
118
133
|
// Custom exit code. May overlap with other systems.
|
|
119
134
|
// Best info I've found via stack overflow: https://tldp.org/LDP/abs/html/exitcodes.html
|
|
@@ -123,31 +138,64 @@ function build_config( command_args ) {
|
|
|
123
138
|
// This is hardcoded information from `provide_config` above.
|
|
124
139
|
// This code isn't used for anything other than detecting and removing color codes.
|
|
125
140
|
let summary_file_name = SUMMARY_FILENAME;
|
|
126
|
-
fs.readFile(summary_file_name, 'utf8', (
|
|
127
|
-
if (
|
|
141
|
+
fs.readFile(summary_file_name, 'utf8', (read_error, text) => {
|
|
142
|
+
if ( read_error ) {
|
|
128
143
|
// Something else will probably show an error that's more relevant
|
|
129
|
-
log.
|
|
144
|
+
log.warn({ code: `ALK0056`, context: `post_run error`, },
|
|
145
|
+
`Failed to read cucumber report`, read_error
|
|
146
|
+
);
|
|
130
147
|
return;
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
// Replace terminal color codes, if present
|
|
134
151
|
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
|
|
135
152
|
text = text.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
|
|
136
|
-
|
|
137
|
-
// Put it in the correct file in the correct folder
|
|
138
|
-
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
153
|
+
// Add to the debug file
|
|
139
154
|
try {
|
|
140
|
-
fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, `\n\n${text}`);
|
|
155
|
+
// fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, `\n\n${text}`);
|
|
156
|
+
log.debug({ code: `ALK0216`, context: `post_run`, },
|
|
157
|
+
`\n\n`, text
|
|
158
|
+
);
|
|
141
159
|
} catch ( error ) {
|
|
142
|
-
log.
|
|
160
|
+
log.warn({ code: `ALK0182`, context: `post_run error`, },
|
|
161
|
+
`Something probably went wrong earlier. Look above for more details. Problem when writing to ${ log.path }/${ log.debug_log_filename }.`,
|
|
162
|
+
error
|
|
163
|
+
);
|
|
143
164
|
}
|
|
165
|
+
|
|
166
|
+
// Add to the non-passed test output file
|
|
144
167
|
try {
|
|
145
|
-
//
|
|
168
|
+
// Save only non-passing test output to cut out cruft.
|
|
169
|
+
// cucumber only outputs non-success details anyway.
|
|
170
|
+
// Our reports only non-passing tests to this file.
|
|
146
171
|
if ( exitCode === 50 ) {
|
|
147
|
-
fs.appendFileSync( `${
|
|
172
|
+
// fs.appendFileSync( `${ log.path }/${ Log.unexpected_filename }`, `\n\n${ text }` );
|
|
173
|
+
log.unexpected({ text });
|
|
148
174
|
}
|
|
149
|
-
} catch (
|
|
150
|
-
|
|
175
|
+
} catch ( unexpected_file_error ) {
|
|
176
|
+
console.warn(
|
|
177
|
+
`🔎 ALK0181 internal WARNING: Something probably went wrong earlier. Problem when writing to ${ log.path }/${ log.unexpected_filename }`,
|
|
178
|
+
unexpected_file_error
|
|
179
|
+
);
|
|
151
180
|
}
|
|
181
|
+
|
|
182
|
+
return;
|
|
152
183
|
});
|
|
153
|
-
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
// 1. session_vars - make sure all the required env vars exist
|
|
188
|
+
session_vars.validateEnvironment();
|
|
189
|
+
|
|
190
|
+
// 2. Prepare logs
|
|
191
|
+
// process.argv is a list of strings of commands and args.
|
|
192
|
+
// If using `npm run cucumber` (as opposed to the bin commands),
|
|
193
|
+
// use `--` before `--sources=./foo`
|
|
194
|
+
const argv = require(`minimist`)( process.argv.slice(2) );
|
|
195
|
+
// The artifacts folder might be getting created for the first
|
|
196
|
+
// time right here. The tests themselves should avoid creating
|
|
197
|
+
// their own file.
|
|
198
|
+
const log = new Log({ path: argv.path, context: `run` });
|
|
199
|
+
|
|
200
|
+
// 3. run
|
|
201
|
+
main();
|
package/lib/scope.js
CHANGED
|
@@ -13,14 +13,13 @@ 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');
|
|
16
|
+
// const log = require('./utils/log');
|
|
17
17
|
const { waitForTimeout } = require('./utils/time');
|
|
18
18
|
const files = require('./utils/files' );
|
|
19
19
|
const da_api = require('./docassemble/docassemble_api_interface');
|
|
20
20
|
const wrapPromiseWithTimeout = require('./cucumber_8_shim');
|
|
21
21
|
const reports = require(`./utils/reports`);
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
let ordinal_to_integer = {
|
|
25
24
|
only: 0, first: 0, second: 1, third: 2, fourth: 3, fifth: 4,
|
|
26
25
|
sixth: 5, seventh: 6, eighth: 7, ninth: 8, tenth: 9,
|
|
@@ -84,10 +83,16 @@ let toBase64 = function ( utf8_str, exclude_padding=true) {
|
|
|
84
83
|
|
|
85
84
|
let proxies_regex = /(\bx\.)|(\bx\[)|(\[[ijklmn]\])/g;
|
|
86
85
|
let da_base64_regex = /^[A-Za-z0-9+/]+$/;
|
|
86
|
+
let log = null;
|
|
87
87
|
|
|
88
88
|
module.exports = {
|
|
89
89
|
trigger_not_needed_flag: `ALKiln: no trigger variable needed`,
|
|
90
90
|
|
|
91
|
+
|
|
92
|
+
set_log: function ({ log_obj }) {
|
|
93
|
+
log = log_obj;
|
|
94
|
+
},
|
|
95
|
+
|
|
91
96
|
getSafeScenarioBaseFilename: async function( scope, { scenario }) {
|
|
92
97
|
/** Return a string that's safe to be a filename. Include the Scenario
|
|
93
98
|
* description and tags.
|
|
@@ -171,7 +176,7 @@ module.exports = {
|
|
|
171
176
|
// If something might be hidden or shown, wait extra time to let it hide or show
|
|
172
177
|
// I think `.$()` does not use a timeout
|
|
173
178
|
let showif = await scope.page.$('.dashowif');
|
|
174
|
-
if ( showif ) { await
|
|
179
|
+
if ( showif ) { await time.waitForTimeout( scope.showif_timeout ); }
|
|
175
180
|
},
|
|
176
181
|
|
|
177
182
|
waitForTab: async function waitForTab(scope, { tab_id }) {
|
|
@@ -288,9 +293,7 @@ module.exports = {
|
|
|
288
293
|
/** Tap a given element, detect navigation, and do appropriate related things.
|
|
289
294
|
* `click_promise can be any kind of Promise, including a Promise.all()
|
|
290
295
|
*/
|
|
291
|
-
|
|
292
|
-
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0102`, value: msg }); }
|
|
293
|
-
expect( elem, msg ).to.exist;
|
|
296
|
+
await scope.guard_against_missing_tap_element(scope, { elem });
|
|
294
297
|
|
|
295
298
|
// Get the text on the button or link for a potential error message later
|
|
296
299
|
let tapperText = await (await elem.getProperty('textContent')).jsonValue();
|
|
@@ -326,6 +329,14 @@ module.exports = {
|
|
|
326
329
|
} // ends try/catch
|
|
327
330
|
}, // Ends scope.tapElementAndStayOnPage()
|
|
328
331
|
|
|
332
|
+
guard_against_missing_tap_element: async function(scope, { elem }) {
|
|
333
|
+
/** Handle the error message for a missing tap element, if it's missing. */
|
|
334
|
+
let msg = `Cannot find the element to tap.`;
|
|
335
|
+
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0102`, value: msg }); }
|
|
336
|
+
expect( elem, msg ).to.exist;
|
|
337
|
+
return elem;
|
|
338
|
+
},
|
|
339
|
+
|
|
329
340
|
// Handle server reload errors
|
|
330
341
|
server_statuses: [],
|
|
331
342
|
server_check_interval_ms: 1 * 1000,
|
|
@@ -501,7 +512,7 @@ module.exports = {
|
|
|
501
512
|
if ( diff <= 0 ) { return false; }
|
|
502
513
|
|
|
503
514
|
// If time hasn't expired, wait and then check again
|
|
504
|
-
await
|
|
515
|
+
await time.waitForTimeout( 100 );
|
|
505
516
|
await scope.detectDownloadComplete(scope, endTime);
|
|
506
517
|
|
|
507
518
|
return false;
|
|
@@ -686,7 +697,7 @@ module.exports = {
|
|
|
686
697
|
});
|
|
687
698
|
expect( load_result.error ).to.equal( '' );
|
|
688
699
|
} else {
|
|
689
|
-
reports.addToReport(scope, { type: `row info`, code: `ALK0117`, value: `Successfully found the interview
|
|
700
|
+
reports.addToReport(scope, { type: `row info`, code: `ALK0117`, value: `Successfully found the interview` });
|
|
690
701
|
}
|
|
691
702
|
|
|
692
703
|
return scope.page;
|
|
@@ -705,19 +716,19 @@ module.exports = {
|
|
|
705
716
|
if ( !scenario.interviews ) { scenario.interviews = {}; }
|
|
706
717
|
if ( !scenario.interviews[ vars.uid ] ) {
|
|
707
718
|
scenario.interviews[ vars.uid ] = vars;
|
|
708
|
-
log.debug({
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
});
|
|
719
|
+
log.debug({ code: `ALK0185`, context: `session_id_etc` },
|
|
720
|
+
`Saved new interview data. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
721
|
+
);
|
|
712
722
|
} else {
|
|
713
|
-
log.debug({
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
});
|
|
723
|
+
log.debug({ code: `ALK0186`, context: `session_id_etc` },
|
|
724
|
+
`Interview data already existed. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
725
|
+
);
|
|
717
726
|
}
|
|
718
727
|
return vars;
|
|
719
728
|
} else {
|
|
720
|
-
log.debug({ code: `ALK0187`,
|
|
729
|
+
log.debug({ code: `ALK0187`, context: `session_id_etc` },
|
|
730
|
+
`Found no interview data.`
|
|
731
|
+
);
|
|
721
732
|
return null;
|
|
722
733
|
}
|
|
723
734
|
}, // Ends scope.set_interview_session_data()
|
|
@@ -864,11 +875,9 @@ module.exports = {
|
|
|
864
875
|
supported_table.push( result );
|
|
865
876
|
} // ends for every row
|
|
866
877
|
|
|
867
|
-
log.debug({
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
pre: `scope.normalizedTable():\n ${JSON.stringify( supported_table )}`
|
|
871
|
-
});
|
|
878
|
+
log.debug({ code: `ALK0122`, context: `table`, },
|
|
879
|
+
`scope.normalizedTable():`, supported_table
|
|
880
|
+
);
|
|
872
881
|
|
|
873
882
|
return supported_table;
|
|
874
883
|
}, // Ends scope.normalizeTable()
|
|
@@ -1018,11 +1027,9 @@ module.exports = {
|
|
|
1018
1027
|
fields.push( field_data );
|
|
1019
1028
|
} // ends for every node
|
|
1020
1029
|
|
|
1021
|
-
log.debug({
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
pre: `page fields:\n${ JSON.stringify( fields )}`
|
|
1025
|
-
});
|
|
1030
|
+
log.debug({ code: `ALK0124`, context: `page fields`, },
|
|
1031
|
+
fields
|
|
1032
|
+
);
|
|
1026
1033
|
return fields;
|
|
1027
1034
|
}, // Ends scope.getAllFields()
|
|
1028
1035
|
|
|
@@ -1072,11 +1079,9 @@ module.exports = {
|
|
|
1072
1079
|
/* Return variable names and values combined in ways that can
|
|
1073
1080
|
* try to match table rows. Tables can have two properties:
|
|
1074
1081
|
* variable name and value. */
|
|
1075
|
-
log.debug({
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
pre: `scope.getPossibleTableRowMatchers() var_names: ${JSON.stringify(var_names)} values: ${JSON.stringify(values)}`
|
|
1079
|
-
});
|
|
1082
|
+
log.debug({ code: `ALK0126`, context: `matching`, },
|
|
1083
|
+
`scope.getPossibleTableRowMatchers() var_names:`, var_names, `values:`, values
|
|
1084
|
+
);
|
|
1080
1085
|
let possible_table_rows = [];
|
|
1081
1086
|
for ( let var_name of var_names ) {
|
|
1082
1087
|
// Inputs like textareas don't have values
|
|
@@ -1551,17 +1556,13 @@ module.exports = {
|
|
|
1551
1556
|
reports.addToReport(scope, { type: `warning`, code: `ALK0130`, value: multiple_matches_msg });
|
|
1552
1557
|
}
|
|
1553
1558
|
|
|
1554
|
-
log.debug({
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
pre: `final_matching_rows:\n${JSON.stringify( final_matching_rows )}`
|
|
1558
|
-
});
|
|
1559
|
+
log.debug({ code: `ALK0131`, context: `matching`, },
|
|
1560
|
+
`final_matching_rows:`, final_matching_rows
|
|
1561
|
+
);
|
|
1559
1562
|
if ( final_matching_rows.length == 0) {
|
|
1560
|
-
log.debug({
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
pre: `field:\n${JSON.stringify( field )}`
|
|
1564
|
-
});
|
|
1563
|
+
log.debug({ code: `ALK0132`, context: `matching`, },
|
|
1564
|
+
`0 matching rows. Field:`, field
|
|
1565
|
+
);
|
|
1565
1566
|
}
|
|
1566
1567
|
|
|
1567
1568
|
return final_matching_rows;
|
|
@@ -1627,7 +1628,7 @@ module.exports = {
|
|
|
1627
1628
|
catch(err){ elem.style.background = 'Khaki'; }
|
|
1628
1629
|
return elem.getAttribute('for') || elem.getAttribute('name');
|
|
1629
1630
|
});
|
|
1630
|
-
await
|
|
1631
|
+
await time.waitForTimeout( 400 );
|
|
1631
1632
|
}
|
|
1632
1633
|
|
|
1633
1634
|
// We use the last match. We wrote a warning for the user if there were
|
|
@@ -1647,11 +1648,9 @@ module.exports = {
|
|
|
1647
1648
|
|
|
1648
1649
|
get_handle_from_field: async function ( scope, { field }) {
|
|
1649
1650
|
/** Get the element's puppeteer handle using the field's selector. */
|
|
1650
|
-
log.debug({
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
pre: `scope.get_handle_from_field() field.selector: ${JSON.stringify(field.selector)}`
|
|
1654
|
-
});
|
|
1651
|
+
log.debug({ code: `ALK0133`, },
|
|
1652
|
+
`scope.get_handle_from_field() field.selector:`, field.selector
|
|
1653
|
+
);
|
|
1655
1654
|
let handle = await scope.page.evaluateHandle(( selector ) => {
|
|
1656
1655
|
let elem = document.querySelector( selector );
|
|
1657
1656
|
// If the element has a sibling that's a label, we need to
|
|
@@ -1850,7 +1849,7 @@ module.exports = {
|
|
|
1850
1849
|
});
|
|
1851
1850
|
if ( type === `radio` || type === `checkbox` ) {
|
|
1852
1851
|
|
|
1853
|
-
let [label] = await handle
|
|
1852
|
+
let [label] = await handle.$$(`xpath/.//following-sibling::label`);
|
|
1854
1853
|
|
|
1855
1854
|
if ( session_vars.get_debug() ) {
|
|
1856
1855
|
if ( answer.toLowerCase() === 'false' ) { await label.evaluate( elem => { elem.style.background = 'tomato'; }); }
|
|
@@ -2163,7 +2162,9 @@ module.exports = {
|
|
|
2163
2162
|
* @param ensure_all_vars_used {bool} [false] = Default false. If true, errors if any
|
|
2164
2163
|
* object in var_data is not used during running of this function.
|
|
2165
2164
|
*/
|
|
2166
|
-
log.debug({
|
|
2165
|
+
log.debug({ code: `ALK0221`, before: `~~~~~~~~~~~~~~\n`, },
|
|
2166
|
+
`scope.setFields()`
|
|
2167
|
+
);
|
|
2167
2168
|
|
|
2168
2169
|
// Only record the page id once for each page to which we navigate
|
|
2169
2170
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
@@ -2207,13 +2208,12 @@ module.exports = {
|
|
|
2207
2208
|
// Accumulate number of times this row was used. Esp important for `.target_number`
|
|
2208
2209
|
await scope.increment_row_use( scope, { row: row_used });
|
|
2209
2210
|
|
|
2210
|
-
|
|
2211
|
-
|
|
2211
|
+
log.stdout({}, `\x1b[36m${ '*' }\x1b[0m`); // assumes var was set if no error occurred
|
|
2212
2212
|
}
|
|
2213
2213
|
} // ends for every non-submit-button field on the page
|
|
2214
2214
|
} // ends while fields are still being set
|
|
2215
2215
|
|
|
2216
|
-
if ( session_vars.get_debug() ) { await
|
|
2216
|
+
if ( session_vars.get_debug() ) { await time.waitForTimeout(2000); }
|
|
2217
2217
|
|
|
2218
2218
|
// Press a continue button with a story table variable name if it exists
|
|
2219
2219
|
let did_navigate = false;
|
|
@@ -2253,7 +2253,7 @@ module.exports = {
|
|
|
2253
2253
|
// }
|
|
2254
2254
|
|
|
2255
2255
|
did_navigate = true;
|
|
2256
|
-
|
|
2256
|
+
log.stdout({}, `\x1b[36m${ 'v' }\x1b[0m`); // pressed button successfully
|
|
2257
2257
|
break;
|
|
2258
2258
|
|
|
2259
2259
|
} // ends if row_used
|
|
@@ -2300,7 +2300,7 @@ module.exports = {
|
|
|
2300
2300
|
// }
|
|
2301
2301
|
|
|
2302
2302
|
did_navigate = true;
|
|
2303
|
-
|
|
2303
|
+
log.stdout({}, `\x1b[36m${ '>' }\x1b[0m`); // continued successfully
|
|
2304
2304
|
} // ends if !did_navigate && ensure_navigation
|
|
2305
2305
|
|
|
2306
2306
|
if ( did_navigate && scope.check_all_for_a11y ) {
|
|
@@ -2561,7 +2561,7 @@ module.exports = {
|
|
|
2561
2561
|
|
|
2562
2562
|
// Attempt to solve weird race condition issues where page has not properly continued
|
|
2563
2563
|
while ( curr_url === prev_url && !error.was_found && !timedOut ) {
|
|
2564
|
-
await
|
|
2564
|
+
await time.waitForTimeout( 50 );
|
|
2565
2565
|
curr_url = await scope.page.url();
|
|
2566
2566
|
error = await scope.checkForError( scope );
|
|
2567
2567
|
}
|
|
@@ -2582,10 +2582,10 @@ module.exports = {
|
|
|
2582
2582
|
|
|
2583
2583
|
// All results are lists to match `$x`
|
|
2584
2584
|
let possible_errors = {
|
|
2585
|
-
complex_user_error: await scope.page.$$(
|
|
2585
|
+
complex_user_error: await scope.page.$$(`.alert-danger`),
|
|
2586
2586
|
// The below does not work if there's not a space between ':' and 'none'
|
|
2587
|
-
simple_user_error: await scope.page
|
|
2588
|
-
system_error: await scope.page
|
|
2587
|
+
simple_user_error: await scope.page.$$(`xpath/.//*[contains(@class, "da-has-error")][not(contains(@style,"display: none"))]`),
|
|
2588
|
+
system_error: await scope.page.$$(`xpath/.//meta[@content="docassemble: Error"]` ),
|
|
2589
2589
|
};
|
|
2590
2590
|
|
|
2591
2591
|
let error_handlers = {};
|
|
@@ -2597,7 +2597,7 @@ module.exports = {
|
|
|
2597
2597
|
// Also, if you're debugging and you close the window early, the process will still
|
|
2598
2598
|
// take this long to exit after the tests finish. Not sure how to account for that.
|
|
2599
2599
|
if ( session_vars.get_debug() ) {
|
|
2600
|
-
await
|
|
2600
|
+
await time.waitForTimeout( ( scope.timeout - (1 * 1000)) );
|
|
2601
2601
|
}
|
|
2602
2602
|
was_found = true;
|
|
2603
2603
|
error_handlers[ error_key ] = possible_errors[ error_key ][0];
|
|
@@ -2702,7 +2702,7 @@ module.exports = {
|
|
|
2702
2702
|
}, // Ends scope.steps.add_json_vars_to_report()
|
|
2703
2703
|
|
|
2704
2704
|
link_works: async ( scope, linkText ) => {
|
|
2705
|
-
let [link] = await scope.page
|
|
2705
|
+
let [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${ linkText }")]`);
|
|
2706
2706
|
let prop_obj = await link.getProperty( 'href' );
|
|
2707
2707
|
let actual_url = await prop_obj.jsonValue();
|
|
2708
2708
|
|
|
@@ -2760,13 +2760,13 @@ module.exports = {
|
|
|
2760
2760
|
/** Tap a link that doesn't navigate. Depends on the language of the text.
|
|
2761
2761
|
* TODO: This may be broken at this point.
|
|
2762
2762
|
* */
|
|
2763
|
-
const [link] = await scope.page
|
|
2763
|
+
const [link] = await scope.page.$$(`xpath/.//a[contains(text(), "${phrase}")]`);
|
|
2764
2764
|
|
|
2765
2765
|
let msg = `The term "${ phrase }" seems to be missing.`;
|
|
2766
2766
|
if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0151`, value: msg }); }
|
|
2767
2767
|
expect( link, msg ).to.exist;
|
|
2768
2768
|
await scope.tapElementAndStayOnPage(scope, { elem: link });
|
|
2769
|
-
await
|
|
2769
|
+
await time.waitForTimeout(10000);
|
|
2770
2770
|
}, // Ends scope.steps.tap_term()
|
|
2771
2771
|
|
|
2772
2772
|
sign: async ( scope, name ) => {
|
|
@@ -2785,7 +2785,7 @@ module.exports = {
|
|
|
2785
2785
|
* TODO: Properly wait for download to complete. See notes in
|
|
2786
2786
|
* scope.js scope.detectDownloadComplete()
|
|
2787
2787
|
*/
|
|
2788
|
-
let [elem] = await scope.page
|
|
2788
|
+
let [elem] = await scope.page.$$(`xpath/.//a[contains(@href, "${ filename }")]`);
|
|
2789
2789
|
|
|
2790
2790
|
let msg = `"${ filename }" seems to be missing. Cannot find a link to that document.`;
|
|
2791
2791
|
if ( !elem ) { reports.addToReport(scope, { type: `error`, code: `ALK0152`, value: msg }); }
|
|
@@ -3017,20 +3017,94 @@ module.exports = {
|
|
|
3017
3017
|
expect( email, email_msg ).to.not.equal( undefined );
|
|
3018
3018
|
expect( password, password_msg ).to.not.equal( undefined );
|
|
3019
3019
|
|
|
3020
|
-
// Try to
|
|
3020
|
+
// Try to enter the information
|
|
3021
3021
|
await scope.page.type( `#email`, email );
|
|
3022
3022
|
await scope.page.type( `#password`, password );
|
|
3023
|
-
let elem = await scope.page.$( `button[
|
|
3024
|
-
await scope.
|
|
3023
|
+
let elem = await scope.page.$( `button[type="submit"]` );
|
|
3024
|
+
await scope.guard_against_missing_tap_element(scope, { elem });
|
|
3025
3025
|
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3026
|
+
// Submit and see what happens
|
|
3027
|
+
let winner = await scope.steps.race_sign_in_navigation( scope, { elem });
|
|
3028
|
+
// Add the result to the report and possibly throw errors
|
|
3029
|
+
if ( winner[0] === `success` ) {
|
|
3030
|
+
reports.addToReport( scope, {
|
|
3031
|
+
type: `row info`, code: `ALK0163`,
|
|
3032
|
+
value: `Successfully signed into ${ login_url }`
|
|
3033
|
+
});
|
|
3034
|
+
await scope.afterStep(scope, {waitForShowIf: false});
|
|
3035
|
+
} else if ( winner[0] === `failure` ) {
|
|
3036
|
+
let error_msg = reports.addToReport( scope, {
|
|
3037
|
+
type: `error`, code: `ALK0209`,
|
|
3038
|
+
value: `Failed to sign into ${ login_url }. Make sure you followed the instructions at https://assemblyline.suffolklitlab.org/docs/alkiln/writing/#sign-in.`
|
|
3039
|
+
});
|
|
3040
|
+
throw new Error( error_msg );
|
|
3041
|
+
} else if ( winner[0] === `error` ) {
|
|
3042
|
+
let error_msg = reports.addToReport( scope, {
|
|
3043
|
+
type: `error`, code: `ALK0210`,
|
|
3044
|
+
value: `Error on sign in page at ${ login_url }. Try this by hand and then run the test again.`
|
|
3045
|
+
});
|
|
3046
|
+
throw new Error( error_msg );
|
|
3047
|
+
}
|
|
3048
|
+
}, // Ends scope.steps.sign_in()
|
|
3049
|
+
|
|
3050
|
+
race_sign_in_navigation: async function ( scope, { elem }) {
|
|
3051
|
+
/** Wait for sign in navigation success or failure, or system error. */
|
|
3052
|
+
|
|
3053
|
+
// After everything, clean up incomplete promises
|
|
3054
|
+
const controller = new AbortController;
|
|
3055
|
+
|
|
3056
|
+
// Redirect
|
|
3057
|
+
let redirect_promise = scope.page.waitForResponse(function ( response ) {
|
|
3058
|
+
return response.status() === 302;
|
|
3059
|
+
}, { signal: controller.signal })
|
|
3060
|
+
.catch(( catch_info ) => {
|
|
3061
|
+
log.debug({ code: `ALK0204`, context: `nav`, },
|
|
3062
|
+
`302 sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3063
|
+
);
|
|
3030
3064
|
});
|
|
3031
3065
|
|
|
3032
|
-
|
|
3033
|
-
|
|
3066
|
+
// Invalid credentials, no navigation, no sign in
|
|
3067
|
+
let wrong_sign_in_promise = scope.page.waitForResponse(function ( response ) {
|
|
3068
|
+
return response.status() >= 200 && response.status() < 300 && response.url().includes(`/sign-in`);
|
|
3069
|
+
}, { signal: controller.signal })
|
|
3070
|
+
.catch(( catch_info ) => {
|
|
3071
|
+
log.debug({ code: `ALK0205`, context: `nav`, },
|
|
3072
|
+
`200s sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3073
|
+
);
|
|
3074
|
+
});
|
|
3075
|
+
|
|
3076
|
+
// System error
|
|
3077
|
+
let error_promise = scope.page.waitForResponse(function ( response ) {
|
|
3078
|
+
return response.status() >= 500 && response.status() < 600;
|
|
3079
|
+
}, { signal: controller.signal })
|
|
3080
|
+
.catch(( catch_info ) => {
|
|
3081
|
+
log.debug({ code: `ALK0206`, context: `nav`, },
|
|
3082
|
+
`500s sign-in wait catch error:`, catch_info.name, catch_info,
|
|
3083
|
+
);
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
let click_promise = elem.click(); // MUST complete
|
|
3087
|
+
const winner = await Promise.race([
|
|
3088
|
+
Promise.all([ `success`, click_promise, redirect_promise ]),
|
|
3089
|
+
Promise.all([ `failure`, click_promise, wrong_sign_in_promise ]),
|
|
3090
|
+
Promise.all([ `error`, click_promise, error_promise ]),
|
|
3091
|
+
]).catch(function ( error ) {
|
|
3092
|
+
let error_msg = reports.addToReport( scope, {
|
|
3093
|
+
type: `error`, code: `ALK0207`,
|
|
3094
|
+
value: `Unknown error waiting for results during sign in at ${ login_url }.`
|
|
3095
|
+
});
|
|
3096
|
+
throw new Error( error );
|
|
3097
|
+
});
|
|
3098
|
+
|
|
3099
|
+
// Clean up unresolved promises
|
|
3100
|
+
controller.abort();
|
|
3101
|
+
|
|
3102
|
+
log.debug({ code: `ALK0208`, context: `nav` },
|
|
3103
|
+
`Sign-in winner:`, winner
|
|
3104
|
+
);
|
|
3105
|
+
|
|
3106
|
+
return winner;
|
|
3107
|
+
},
|
|
3034
3108
|
|
|
3035
3109
|
set_random_page_vars: async ( scope ) => {
|
|
3036
3110
|
/** Answer inputs randomly. */
|
|
@@ -3050,7 +3124,7 @@ module.exports = {
|
|
|
3050
3124
|
} else {
|
|
3051
3125
|
// Try to give a random answer for that element tag
|
|
3052
3126
|
await scope.set_random_input_for[ field.tag ]( scope, { field: field });
|
|
3053
|
-
|
|
3127
|
+
log.stdout({}, `\x1b[36m${ '*' }\x1b[0m`); // assumes var was set if no error occurred
|
|
3054
3128
|
}
|
|
3055
3129
|
|
|
3056
3130
|
} // End for all page fields
|
|
@@ -3086,11 +3160,10 @@ module.exports = {
|
|
|
3086
3160
|
}
|
|
3087
3161
|
if (field == null) {
|
|
3088
3162
|
// Just skip it? Shouldn't happen but has before. Dig in more
|
|
3089
|
-
log.debug({
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
});
|
|
3163
|
+
log.debug({ code: `ALK0164`, context: `random`, },
|
|
3164
|
+
`A random button field choice was null. These were the fields to choose from:`,
|
|
3165
|
+
fields
|
|
3166
|
+
);
|
|
3094
3167
|
return;
|
|
3095
3168
|
}
|
|
3096
3169
|
let handle = await scope.get_handle_from_field( scope, { field: field });
|