@suffolklitlab/alkiln 5.0.0-playground-2 → 5.0.0-pre-4
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/pull_request_template.md +17 -0
- package/.github/workflows/test_tests.yml +3 -3
- package/CHANGELOG.md +6 -1
- package/action.yml +1 -1
- package/lib/docassemble/docassemble_api_interface.js +9 -8
- package/lib/docassemble/setup.js +1 -0
- package/lib/run_cucumber.js +1 -0
- package/lib/scope.js +49 -30
- package/lib/steps.js +20 -10
- package/lib/utils/files.js +1 -2
- package/lib/utils/session_vars.js +1 -1
- package/package.json +2 -2
- package/tests/unit_tests/getSafeScenarioBaseFilename.fixtures.js +3 -0
- package/tests/unit_tests/getSafeScenarioBaseFilename.test.js +6 -0
- package/tests/unit_tests/getSafeScenarioFilename.test.js +29 -5
- package/tests/unit_tests/names.fixtures.js +4 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
In this PR, I have:
|
|
2
|
+
|
|
3
|
+
* [ ] Added tests for any new features or bug fixes (if relevant)
|
|
4
|
+
* [ ] Added my changes to the CHANGELOG.md at the top, under the "Unreleased" section
|
|
5
|
+
* [ ] Ensured issues that this PR closes will be [automatically closed](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
|
|
6
|
+
|
|
7
|
+
### Reason for this PR
|
|
8
|
+
|
|
9
|
+
<Insert reasons for this PR>
|
|
10
|
+
|
|
11
|
+
### Links to any solved or related issues
|
|
12
|
+
|
|
13
|
+
<Insert links>
|
|
14
|
+
|
|
15
|
+
### Any manual testing I have done to ensure my PR is working
|
|
16
|
+
|
|
17
|
+
<Insert description>
|
|
@@ -45,8 +45,8 @@ jobs:
|
|
|
45
45
|
- name: Set name of artifact folder with date and UTC time
|
|
46
46
|
# https://www.shell-tips.com/linux/how-to-format-date-and-time-in-linux-macos-and-bash/#how-to-format-a-date-in-bash
|
|
47
47
|
run: |
|
|
48
|
-
echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)
|
|
49
|
-
echo "UNIT_TESTS_ARTIFACT_NAME=_alkiln-misc-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)
|
|
48
|
+
echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
|
|
49
|
+
echo "UNIT_TESTS_ARTIFACT_NAME=_alkiln-misc-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
|
|
50
50
|
- name: Set languages
|
|
51
51
|
run: echo "EXTRA_LANGUAGES=${{ github.event.inputs.extra_languages }}" >> $GITHUB_ENV
|
|
52
52
|
if: ${{ github.event.inputs.extra_languages != '' }}
|
|
@@ -60,7 +60,7 @@ jobs:
|
|
|
60
60
|
node-version: ${{ matrix.node-version }}
|
|
61
61
|
- run: npm install
|
|
62
62
|
- run: npm run setup
|
|
63
|
-
- run: npm run test
|
|
63
|
+
- run: npm run test ${{ github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
|
|
64
64
|
# Artifacts
|
|
65
65
|
- name: upload unit tests artifacts folder
|
|
66
66
|
uses: actions/upload-artifact@v3
|
package/CHANGELOG.md
CHANGED
|
@@ -39,8 +39,8 @@ Format:
|
|
|
39
39
|
-
|
|
40
40
|
-->
|
|
41
41
|
## [Unreleased]
|
|
42
|
-
|
|
43
42
|
### Added
|
|
43
|
+
- added additional functionality to the sign method to allow developers to take a name argument to sign on canvas: issue #596
|
|
44
44
|
- A new script: `alkiln-run`, which acts like `npm run cucumber`, but can
|
|
45
45
|
be run in any directory, not just in an npm package.
|
|
46
46
|
- Additional environment variables and their validation to allow for tests that run on a developer's server/Playground instead of through GitHub. Also, other functionality for that purpose. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
|
|
@@ -54,6 +54,11 @@ Format:
|
|
|
54
54
|
e.g. `alkiln-setup`, `alkiln-run`, `alkiln-takedown`
|
|
55
55
|
- don't print the ["publish this cucumber report" message](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options)
|
|
56
56
|
- Adjusted validation of some environment variables to account for Playground vs. GitHub or local test runs. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
|
|
57
|
+
- Project name prefix now includes ALKiln in it for clarity
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- Projects created in da each have a unique name. https://github.com/SuffolkLITLab/ALKiln/issues/663
|
|
61
|
+
- Shorten path names to try to accommodate limitations of windows systems while still keeping enough useful information to help devs identify the test outputs. https://github.com/SuffolkLITLab/ALKiln/issues/618
|
|
57
62
|
|
|
58
63
|
### Security
|
|
59
64
|
- Pass docassemble API keys through HTTP headers instead of as parameters.
|
package/action.yml
CHANGED
|
@@ -43,7 +43,7 @@ runs:
|
|
|
43
43
|
# Human-readable date/time:
|
|
44
44
|
# https://www.shell-tips.com/linux/how-to-format-date-and-time-in-linux-macos-and-bash/#how-to-format-a-date-in-bash
|
|
45
45
|
run: |
|
|
46
|
-
echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)
|
|
46
|
+
echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
|
|
47
47
|
if ${{ github.event.inputs.extra_languages != '' }}
|
|
48
48
|
then
|
|
49
49
|
echo "Manually set languages: ${{ github.event.inputs.extra_languages }}"
|
|
@@ -225,16 +225,17 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
225
225
|
let wait_seconds = creation_options.wait_seconds || 5;
|
|
226
226
|
|
|
227
227
|
let project_name = null;
|
|
228
|
-
|
|
228
|
+
// Unlikely, but 2 devs could run locally and create the same Project name
|
|
229
|
+
let num_tries = 0;
|
|
229
230
|
|
|
230
231
|
// Try to create a project. If the project name is already taken, increment and loop again.
|
|
231
|
-
while (
|
|
232
|
-
|
|
232
|
+
while ( num_tries <= max_tries ) {
|
|
233
|
+
num_tries++;
|
|
233
234
|
try {
|
|
234
235
|
|
|
235
236
|
// Try next name that could be unique. Must remove all non-valid characters
|
|
236
237
|
// TODO: This sanitization is already done in session_vars, so remove it from here
|
|
237
|
-
project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') +
|
|
238
|
+
project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + `${Date.now()}`;
|
|
238
239
|
log.info({ pre: `Trying to create a docassemble Project with the name ${ project_name }` });
|
|
239
240
|
let response = await _da_REST.create_project( project_name, timeout );
|
|
240
241
|
log.info({ pre: `The docassemble server is ready. Created a Project named ${ project_name }` });
|
|
@@ -247,7 +248,7 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
247
248
|
|
|
248
249
|
if ( response && response.data ) {
|
|
249
250
|
if ( response.data.includes( `Invalid project` ) ) {
|
|
250
|
-
if (
|
|
251
|
+
if ( num_tries < max_tries ) {
|
|
251
252
|
// A non-unique project name. Try the next project name.
|
|
252
253
|
continue;
|
|
253
254
|
|
|
@@ -275,11 +276,11 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
275
276
|
|
|
276
277
|
// If it's a timeout error, server is busy. Try again after waiting.
|
|
277
278
|
// Why are we using `max_tries` for timeout error as well?
|
|
278
|
-
if ( is_timeout_error( error ) &&
|
|
279
|
+
if ( is_timeout_error( error ) && num_tries < max_tries ) {
|
|
279
280
|
log.info({ pre: `The docassemble server was busy when we tried to `
|
|
280
|
-
+ `create the Project. Attempt ${
|
|
281
|
+
+ `create the Project. Attempt ${ num_tries } of `
|
|
281
282
|
+ `${ max_tries }. Will try again in ${ wait_seconds } seconds.` });
|
|
282
|
-
|
|
283
|
+
num_tries--; // Don't count this try
|
|
283
284
|
await time.waitForTimeout( wait_seconds * 1000 );
|
|
284
285
|
continue;
|
|
285
286
|
|
package/lib/docassemble/setup.js
CHANGED
|
@@ -25,6 +25,7 @@ const setup = async () => {
|
|
|
25
25
|
// avoids showing confusing node errors to users that they shouldn't have to fix
|
|
26
26
|
try {
|
|
27
27
|
project_name = await da_i.loop_to_create_project();
|
|
28
|
+
log.info({type: `setup`, pre: `Created a Project named ${ project_name }`});
|
|
28
29
|
} catch (error) {
|
|
29
30
|
log.error({type: `setup`, pre: `an unexpected error occurred while trying to create a docassemble project in the user's account`, data: JSON.stringify(error, null, 4)})
|
|
30
31
|
// we don't re-throw the exception, so make sure to exit with 1 to let Github Actions know the script failed
|
package/lib/run_cucumber.js
CHANGED
|
@@ -61,6 +61,7 @@ const cucumber_api = require('@cucumber/cucumber/api');
|
|
|
61
61
|
}, sources: {paths: []}}, environment);
|
|
62
62
|
|
|
63
63
|
console.log("alkiln-run: working directory: %o", codeDir);
|
|
64
|
+
console.log(`\n\n`);
|
|
64
65
|
|
|
65
66
|
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
66
67
|
console.log("alkiln-run: success: %b", success);
|
package/lib/scope.js
CHANGED
|
@@ -121,8 +121,7 @@ module.exports = {
|
|
|
121
121
|
* create it. */
|
|
122
122
|
if ( !scope.report ) { scope.report = new Map(); }
|
|
123
123
|
if ( !scope.scenario_id ) {
|
|
124
|
-
|
|
125
|
-
scope.scenario_id = `_report_${ date }`;
|
|
124
|
+
scope.scenario_id = `_report_${ Date.now() }`;
|
|
126
125
|
}
|
|
127
126
|
if ( !scope.report.get( scope.scenario_id )) {
|
|
128
127
|
scope.report.set( scope.scenario_id, {} )
|
|
@@ -243,9 +242,9 @@ module.exports = {
|
|
|
243
242
|
let filename = name_parts.join(`-`);
|
|
244
243
|
let safe_name = safe_filename( filename, { replacement: `_` });
|
|
245
244
|
// Allow room for extensions, date, etc.
|
|
246
|
-
let
|
|
245
|
+
let short_name = `${ safe_name }`.substring(0, 70);
|
|
247
246
|
|
|
248
|
-
return
|
|
247
|
+
return short_name;
|
|
249
248
|
}, // Ends scope.getSafeScenarioBaseFilename()
|
|
250
249
|
|
|
251
250
|
// TODO: Does this need a name change? getSafeScenarioBasedFilename?
|
|
@@ -254,6 +253,14 @@ module.exports = {
|
|
|
254
253
|
* a prefix the caller wants, and the base filename. We hope it can
|
|
255
254
|
* contain non-english characters too. We do not offer a suffix option
|
|
256
255
|
* because it may be cut off if the filename is too long.
|
|
256
|
+
*
|
|
257
|
+
* @Examples
|
|
258
|
+
* Return value possible formats
|
|
259
|
+
* prefix-id-scenario_description-timestamp
|
|
260
|
+
* id-scenario_description-timestamp
|
|
261
|
+
* scenario_description-timestamp
|
|
262
|
+
*
|
|
263
|
+
* @returns str
|
|
257
264
|
*/
|
|
258
265
|
let name = options.prefix || '';
|
|
259
266
|
|
|
@@ -264,23 +271,21 @@ module.exports = {
|
|
|
264
271
|
|
|
265
272
|
if ( scope.page ) {
|
|
266
273
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
267
|
-
|
|
274
|
+
let short_id = `${ id }`.substring(0, 20);
|
|
275
|
+
name += `${ short_id }-`;
|
|
268
276
|
}
|
|
269
277
|
|
|
270
278
|
name += scope.base_filename;
|
|
271
|
-
name += `-${
|
|
279
|
+
name += `-${ Date.now() }`;
|
|
272
280
|
|
|
273
281
|
let safe_name = safe_filename( name, { replacement: `_` });
|
|
274
|
-
|
|
275
|
-
let safer_name = safe_name.substring(0, (255 - 10));
|
|
276
|
-
return safer_name;
|
|
282
|
+
return safe_name;
|
|
277
283
|
}, // Ends scope.getSafeScenarioFilename()
|
|
278
284
|
|
|
279
285
|
getJSONFilename: async function( scope ) {
|
|
280
286
|
// Get a unique path for a json file
|
|
281
287
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
282
|
-
let
|
|
283
|
-
let json_path = path.join( scope.paths.scenario, `json_for-${ id }-${ date }.json` );
|
|
288
|
+
let json_path = path.join( scope.paths.scenario, `json_for-${ id }-${ Date.now() }.json` );
|
|
284
289
|
return json_path;
|
|
285
290
|
},
|
|
286
291
|
|
|
@@ -840,7 +845,7 @@ module.exports = {
|
|
|
840
845
|
|
|
841
846
|
// Error types we haven't accounted for
|
|
842
847
|
} else {
|
|
843
|
-
result.error = "Docassemble ran into an error on the page, but ALKiln does not know what the error is. If a
|
|
848
|
+
result.error = "Docassemble ran into an error on the page, but ALKiln does not know what the error is. Check the picture of this page. If a secret variable was used, there will be no picture.";
|
|
844
849
|
} // ends if error_elem !== null
|
|
845
850
|
} // ends if error_id_elem
|
|
846
851
|
|
|
@@ -1871,16 +1876,26 @@ module.exports = {
|
|
|
1871
1876
|
}, 500)
|
|
1872
1877
|
},
|
|
1873
1878
|
|
|
1874
|
-
draw_signature: async function ( scope ) {
|
|
1879
|
+
draw_signature: async function ( scope, name ) {
|
|
1875
1880
|
/* On a signature page, there should only be a single signature canvas
|
|
1876
1881
|
* and a continue button. Draw in the #dasigcanvas element. `<canvas>`
|
|
1877
1882
|
* takes a while to load. */
|
|
1878
|
-
let
|
|
1883
|
+
let canvas = await scope.page.waitForSelector( `#dasigcanvas` );
|
|
1879
1884
|
let msg = `Could not find a signature field.`;
|
|
1880
|
-
if ( !
|
|
1881
|
-
expect(
|
|
1882
|
-
|
|
1883
|
-
|
|
1885
|
+
if ( !canvas ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
|
|
1886
|
+
expect( canvas, msg ).to.exist;
|
|
1887
|
+
|
|
1888
|
+
// Provides functionality to draw name arg on canvas
|
|
1889
|
+
if (name !== undefined) {
|
|
1890
|
+
await scope.page.$eval(`#dasigcanvas`, (canvas, name) => {
|
|
1891
|
+
const ctx = canvas.getContext("2d");
|
|
1892
|
+
ctx.font = "24px serif";
|
|
1893
|
+
ctx.fillText(name, 10, 50);
|
|
1894
|
+
}, name);
|
|
1895
|
+
}
|
|
1896
|
+
let bounding_box = await canvas.boundingBox();
|
|
1897
|
+
// We draw the dot because it allows the developer to tap continue
|
|
1898
|
+
// Docassemble does not allow to tap continue unless the user interacts with the canvas
|
|
1884
1899
|
await scope.page.mouse.move(bounding_box.x + bounding_box.width / 2, bounding_box.y + bounding_box.height / 2);
|
|
1885
1900
|
await scope.page.mouse.down();
|
|
1886
1901
|
// await scope.page.mouse.move(1, 1); // Too big? Wait a while before drawing?
|
|
@@ -1890,7 +1905,10 @@ module.exports = {
|
|
|
1890
1905
|
examinePageID: async function ( scope, question_id = '' ) {
|
|
1891
1906
|
/* Looks for a sanitized version of the question id as it's written
|
|
1892
1907
|
* in the .yml. docassemble's way in python:
|
|
1893
|
-
* re.sub(r'[^A-Za-z0-9]+', '-', interview_status.question.id.lower())
|
|
1908
|
+
* re.sub(r'[^A-Za-z0-9]+', '-', interview_status.question.id.lower())
|
|
1909
|
+
*
|
|
1910
|
+
* @returns { question_has_id: bool, id: str, id_matches: bool }
|
|
1911
|
+
*/
|
|
1894
1912
|
let id_as_class = question_id.toLowerCase().replace(/[^A-Za-z0-9]+/g, '-');
|
|
1895
1913
|
// `.className` is the node's list of classes as a string, separated by spaces
|
|
1896
1914
|
let classes_str = await scope.page.$eval(`body`, function (elem) { return elem.className });
|
|
@@ -2225,7 +2243,7 @@ module.exports = {
|
|
|
2225
2243
|
error = await scope.checkForError( scope );
|
|
2226
2244
|
}
|
|
2227
2245
|
|
|
2228
|
-
let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. Check the
|
|
2246
|
+
let timeout_err_message = `The test tried to continue to the next page, but it took longer than ${ (scope.timeout/1000)/60 } min. Check the picture of this page. If a secret variable was used, there will be no picture.`
|
|
2229
2247
|
if ( timedOut ) { await scope.addToReport(scope, { type: `error`, value: timeout_err_message }); }
|
|
2230
2248
|
expect( timedOut, timeout_err_message ).to.be.false;
|
|
2231
2249
|
|
|
@@ -2265,13 +2283,13 @@ module.exports = {
|
|
|
2265
2283
|
throwPageError: async function ( scope, { error_handlers = {}, id = '' }) {
|
|
2266
2284
|
/* Throw a cucumber error with the error that appeared on the page. */
|
|
2267
2285
|
if ( error_handlers.system_error !== undefined ) {
|
|
2268
|
-
let sys_err_message = `The test tried to continue to the next page after ${ id }, but there was a system error. Check the
|
|
2286
|
+
let sys_err_message = `The test tried to continue to the next page after ${ id }, but there was a system error. Check the picture of this page.`
|
|
2269
2287
|
await scope.addToReport(scope, { type: `error`, value: sys_err_message });
|
|
2270
2288
|
expect( was_system_error, sys_err_message ).to.be.false;
|
|
2271
2289
|
|
|
2272
2290
|
} else {
|
|
2273
2291
|
// If it wasn't a system error, it was a user error
|
|
2274
|
-
let user_err_message = `The test tried to continue to the next page after ${ id }, but the user got an error. Check the
|
|
2292
|
+
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.`
|
|
2275
2293
|
await scope.addToReport(scope, { type: `error`, value: user_err_message });
|
|
2276
2294
|
expect( true, user_err_message ).to.be.false;
|
|
2277
2295
|
}
|
|
@@ -2307,18 +2325,19 @@ module.exports = {
|
|
|
2307
2325
|
screenshot: async ( scope, name ) => {
|
|
2308
2326
|
/* Download and save a screenshot. `name` does not have to be
|
|
2309
2327
|
* unique as the filename will be made unique. */
|
|
2310
|
-
name = name || '';
|
|
2311
2328
|
|
|
2312
|
-
let
|
|
2329
|
+
let timestamp = Date.now();
|
|
2313
2330
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
2331
|
+
let short_id = `${ id }`.substring(0, 20);
|
|
2314
2332
|
// Include the custom name if it has one
|
|
2315
|
-
if ( name.length > 0 ) {
|
|
2316
|
-
|
|
2333
|
+
if ( name && name.length > 0 ) {
|
|
2334
|
+
let short_name = `${ name }`.substring(0, 70);
|
|
2335
|
+
pic_name = `pic-${ short_name }-on-${ short_id }-${ timestamp }`;
|
|
2317
2336
|
} else {
|
|
2318
|
-
|
|
2337
|
+
pic_name = `pic_on-${ short_id }-${ timestamp }`;
|
|
2319
2338
|
}
|
|
2320
2339
|
|
|
2321
|
-
await scope.take_a_screenshot( scope, { path: `./${ scope.paths.scenario }/${
|
|
2340
|
+
await scope.take_a_screenshot( scope, { path: `./${ scope.paths.scenario }/${ pic_name }.jpg` });
|
|
2322
2341
|
|
|
2323
2342
|
await scope.afterStep( scope );
|
|
2324
2343
|
}, // Ends scope.steps.screenshot()
|
|
@@ -2405,9 +2424,9 @@ module.exports = {
|
|
|
2405
2424
|
await scope.afterStep(scope, {waitForShowIf: true});
|
|
2406
2425
|
}, // Ends scope.steps.tap_term()
|
|
2407
2426
|
|
|
2408
|
-
sign: async ( scope ) => {
|
|
2427
|
+
sign: async ( scope, name ) => {
|
|
2409
2428
|
/** Sign on a signature page. */
|
|
2410
|
-
await scope.draw_signature( scope );
|
|
2429
|
+
await scope.draw_signature( scope, name );
|
|
2411
2430
|
await scope.afterStep(scope);
|
|
2412
2431
|
}, // Ends scope.steps.sign()
|
|
2413
2432
|
|
package/lib/steps.js
CHANGED
|
@@ -129,7 +129,7 @@ Before(async (scenario) => {
|
|
|
129
129
|
// Make folder for this Scenario in the all-tests artifacts folder
|
|
130
130
|
scope.base_filename = await scope.getSafeScenarioBaseFilename(scope, {scenario});
|
|
131
131
|
// Add a date for uniqueness in case dev has accidentally copied a Scenario description
|
|
132
|
-
let date =
|
|
132
|
+
let date = Date.now();
|
|
133
133
|
scope.paths.scenario = `${ scope.paths.artifacts }/${ scope.base_filename }-${ date }`;
|
|
134
134
|
fs.mkdirSync( scope.paths.scenario );
|
|
135
135
|
// Store path name for this Scenario's individualized report
|
|
@@ -450,7 +450,7 @@ When(/I wait ?(?:for)? (\d*\.?\d+) seconds?/, { timeout: -1 }, async ( seconds )
|
|
|
450
450
|
);
|
|
451
451
|
});
|
|
452
452
|
|
|
453
|
-
Then(/I take a screenshot ?(?:named "([^"]+)")?/, { timeout: -1 }, async ( name ) => {
|
|
453
|
+
Then(/I take a (?:screenshot|pic) ?(?:named "([^"]+)")?/, { timeout: -1 }, async ( name ) => {
|
|
454
454
|
/* Download and save a screenshot. `name` does not have to be
|
|
455
455
|
* unique as the filename will be made unique. */
|
|
456
456
|
return wrapPromiseWithTimeout(
|
|
@@ -585,7 +585,7 @@ Then('I will be told an answer is invalid', async () => { // √
|
|
|
585
585
|
expect( was_found, no_error_msg ).to.be.true;
|
|
586
586
|
|
|
587
587
|
let was_user_error = error_handlers.complex_user_error !== undefined || error_handlers.simple_user_error !== undefined;
|
|
588
|
-
let not_user_error_msg = `The error was a system error, not an
|
|
588
|
+
let not_user_error_msg = `The error was a system error, not an invalid user answer. Check for the picture of the page's error if the step did not use any secret variables.`;
|
|
589
589
|
if ( !was_user_error ) { await scope.addToReport(scope, { type: `error`, value: not_user_error_msg }); }
|
|
590
590
|
expect( was_user_error, not_user_error_msg ).to.be.true;
|
|
591
591
|
|
|
@@ -790,8 +790,8 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
790
790
|
*/
|
|
791
791
|
// Give each screenshots folder a time-stamped name in case the dev gets clever
|
|
792
792
|
// and use the same Scenario to run multiple randomized tests.
|
|
793
|
-
let timestamp =
|
|
794
|
-
let random_input_path = `${ scope.paths.scenario }/
|
|
793
|
+
let timestamp = Date.now();
|
|
794
|
+
let random_input_path = `${ scope.paths.scenario }/random${ timestamp }`
|
|
795
795
|
scope.paths.random_screenshots = random_input_path;
|
|
796
796
|
fs.mkdirSync( scope.paths.random_screenshots );
|
|
797
797
|
await scope.addToReport( scope, {
|
|
@@ -813,7 +813,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
813
813
|
|
|
814
814
|
let no_content_err = `The interview seemed to run into an error page`
|
|
815
815
|
+ ` after the page with the question id "${ id }".`
|
|
816
|
-
+ ` See the
|
|
816
|
+
+ ` See the picture of the page in "${ random_input_path }".`;
|
|
817
817
|
|
|
818
818
|
let has_thrown = false;
|
|
819
819
|
// Ensure no infinite loop, but each page has a full timeout to run
|
|
@@ -853,7 +853,8 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
853
853
|
let buttons = await scope.steps.set_random_page_vars( scope );
|
|
854
854
|
|
|
855
855
|
// Take a screenshot of the page before continuing or ending
|
|
856
|
-
let
|
|
856
|
+
let short_id = `${ id }`.substring(0, 20);
|
|
857
|
+
let path = `${ scope.paths.random_screenshots }/random-${ short_id }${ Date.now() }.jpg`;
|
|
857
858
|
await scope.take_a_screenshot( scope, { path: path });
|
|
858
859
|
|
|
859
860
|
// Prep for the next loop to possibly stop. Signature page buttons
|
|
@@ -979,13 +980,21 @@ When('I tap the defined text link {string}', { timeout: -1 }, async ( phrase ) =
|
|
|
979
980
|
await scope.steps.tap_term( scope, phrase );
|
|
980
981
|
});
|
|
981
982
|
|
|
982
|
-
Then(
|
|
983
|
+
Then(/^I sign$/, { timeout: -1 }, async () => {
|
|
983
984
|
return wrapPromiseWithTimeout(
|
|
984
985
|
scope.steps.sign( scope ),
|
|
985
986
|
scope.timeout
|
|
986
987
|
);
|
|
987
988
|
});
|
|
988
989
|
|
|
990
|
+
Then('I sign with the name {string}', { timeout: -1 }, async ( name ) => {
|
|
991
|
+
/* Signs with the string argument */
|
|
992
|
+
return wrapPromiseWithTimeout(
|
|
993
|
+
scope.steps.sign( scope, name ),
|
|
994
|
+
scope.timeout
|
|
995
|
+
);
|
|
996
|
+
});
|
|
997
|
+
|
|
989
998
|
When(/I download "([^"]+)"$/, { timeout: -1 }, async ( filename ) => {
|
|
990
999
|
/* Taps the link that leads to the given filename to trigger downloading.
|
|
991
1000
|
* and waits till the file has been downloaded before allowing the tests to continue.
|
|
@@ -1123,7 +1132,7 @@ After(async function(scenario) {
|
|
|
1123
1132
|
if ( !!scope.disable_error_screenshot ) {
|
|
1124
1133
|
scope.addToReport(scope, {
|
|
1125
1134
|
type: `row`,
|
|
1126
|
-
value: `For security, ALKiln will avoid creating a
|
|
1135
|
+
value: `For security, ALKiln will avoid creating a picture of the page for this error. It's possible a secret is being used on this screen.`
|
|
1127
1136
|
});
|
|
1128
1137
|
} else {
|
|
1129
1138
|
|
|
@@ -1136,7 +1145,8 @@ After(async function(scenario) {
|
|
|
1136
1145
|
// Save another copy in the artifact's Scenario folder
|
|
1137
1146
|
let screenshot_name = `error_on`;
|
|
1138
1147
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
1139
|
-
|
|
1148
|
+
let short_id = `${ id }`.substring(0, 20);
|
|
1149
|
+
screenshot_name += `-${ short_id }`;
|
|
1140
1150
|
let path_scenario = `${ scope.paths.scenario }/${ screenshot_name }.jpg`;
|
|
1141
1151
|
await scope.take_a_screenshot( scope, { path: path_scenario });
|
|
1142
1152
|
|
package/lib/utils/files.js
CHANGED
|
@@ -16,9 +16,8 @@ files.readable_date = function() {
|
|
|
16
16
|
let hours = ("0" + date.getUTCHours()).slice(-2);
|
|
17
17
|
let mins = ("0" + date.getUTCMinutes()).slice(-2);
|
|
18
18
|
let secs = ("0" + date.getUTCSeconds()).slice(-2);
|
|
19
|
-
let msecs = ("00" + date.getUTCMilliseconds()).slice(-3);
|
|
20
19
|
|
|
21
|
-
let fancy_str = `${ year }-${ month }-${ day } at ${ hours }h${ mins }m${ secs }
|
|
20
|
+
let fancy_str = `${ year }-${ month }-${ day } at ${ hours }h${ mins }m${ secs }sUTC`;
|
|
22
21
|
return fancy_str;
|
|
23
22
|
}; // Ends files.readable_date()
|
|
24
23
|
|
|
@@ -93,7 +93,7 @@ session_vars.get_package_name = function () {
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
session_vars.get_project_base_name = function () {
|
|
96
|
-
let name = `
|
|
96
|
+
let name = `ALKilnTest${ session_vars.get_package_name() }${ session_vars.get_branch_name() }`;
|
|
97
97
|
let sanitized_name = name.replace( /[^A-Za-z0-9]/g, '' );
|
|
98
98
|
return sanitized_name;
|
|
99
99
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suffolklitlab/alkiln",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-pre-4",
|
|
4
4
|
"description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "npm run unit && npm run langs_setup && npm run cucumber
|
|
7
|
+
"test": "npm run unit && npm run langs_setup && npm run cucumber",
|
|
8
8
|
"cucumber": "./lib/run_cucumber.js",
|
|
9
9
|
"langs": "npm run langs_setup && npm run cucumber",
|
|
10
10
|
"langs_setup": "./lib/utils/langs.js 'docassemble/*/data/sources/*.feature'",
|
|
@@ -7,4 +7,7 @@ names.non_english_chars = `是汉字`;
|
|
|
7
7
|
names.colon_input = `invalid:colon`;
|
|
8
8
|
names.colon_output = `invalid_colon`;
|
|
9
9
|
|
|
10
|
+
names.long_input = `Longest scenario description should be 70 characters or less 0123456789`;
|
|
11
|
+
names.long_output = `Longest scenario description should be 70 characters or less 012345678`;
|
|
12
|
+
|
|
10
13
|
module.exports = names;
|
|
@@ -39,6 +39,12 @@ describe(`When I use scope.getSafeScenarioBaseFilename()`, function() {
|
|
|
39
39
|
expect( result ).to.equal( names.colon_output );
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it(`shortens a name over 70 chars`, async function() {
|
|
43
|
+
let scenario = mock_scenario( names.long_input );
|
|
44
|
+
let result = await getSafeScenarioBaseFilename( scope, { scenario });
|
|
45
|
+
expect( result ).to.equal( names.long_output );
|
|
46
|
+
});
|
|
47
|
+
|
|
42
48
|
// language
|
|
43
49
|
it(`adds a language correctly to a description`, async function() {
|
|
44
50
|
scope.language = names.non_english_chars;
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
const names = require('./names.fixtures.js');
|
|
6
6
|
const scope = require('../../lib/scope.js');
|
|
7
7
|
const getSafeScenarioFilename = scope.getSafeScenarioFilename;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
|
|
10
9
|
let test_filename = function ( expected_prefix, result ) {
|
|
11
10
|
/* Test a name to see if it's valid output with a language defined. */
|
|
12
|
-
let expected_name_regex_str = `^${ expected_prefix }-${ names.
|
|
11
|
+
let expected_name_regex_str = `^${ expected_prefix }-${ names.description }-\.{13}$`;
|
|
13
12
|
if ( expected_prefix == `` ) {
|
|
14
|
-
expected_name_regex_str = `^${ names.
|
|
13
|
+
expected_name_regex_str = `^${ names.description }-\.{13}$`;
|
|
15
14
|
}
|
|
16
15
|
let regex = new RegExp( expected_name_regex_str );
|
|
17
16
|
|
|
@@ -29,7 +28,7 @@ let test_filename = function ( expected_prefix, result ) {
|
|
|
29
28
|
describe(`When I use scope.getSafeScenarioFilename()`, function() {
|
|
30
29
|
|
|
31
30
|
beforeEach(function() {
|
|
32
|
-
scope.base_filename = names.
|
|
31
|
+
scope.base_filename = names.description;
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
it(`preserves chinese characters`, async function() {
|
|
@@ -81,6 +80,31 @@ let test_filename = function ( expected_prefix, result ) {
|
|
|
81
80
|
let result = await getSafeScenarioFilename( scope, { prefix: names.numerical_input });
|
|
82
81
|
test_filename( names.numerical_output, result );
|
|
83
82
|
});
|
|
83
|
+
|
|
84
|
+
it(`handles a missing prefix`, async function() {
|
|
85
|
+
let result = await getSafeScenarioFilename( scope );
|
|
86
|
+
test_filename( ``, result );
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it(`shortens ids over 20 chars long`, async function() {
|
|
90
|
+
// Necessary to set id correctly
|
|
91
|
+
scope.page = true;
|
|
92
|
+
let old_examine = scope.examinePageID;
|
|
93
|
+
scope.examinePageID = function () { return {id: names.long_id_input}; }
|
|
94
|
+
let result = await getSafeScenarioFilename( scope );
|
|
95
|
+
scope.examinePageID = old_examine;
|
|
96
|
+
|
|
97
|
+
let regext_str = `^${ names.long_id_output }-${ scope.base_filename }-\.*$`;
|
|
98
|
+
let regex = new RegExp( regext_str );
|
|
99
|
+
|
|
100
|
+
// Log more info if it failed
|
|
101
|
+
let found_match = regex.test( result );
|
|
102
|
+
if ( !found_match ) {
|
|
103
|
+
console.log( 'regex:', regex.source );
|
|
104
|
+
console.log( 'result:', result );
|
|
105
|
+
}
|
|
106
|
+
expect( found_match ).to.be.true;
|
|
107
|
+
});
|
|
84
108
|
|
|
85
109
|
});
|
|
86
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
let names = {};
|
|
2
2
|
|
|
3
|
-
names.
|
|
3
|
+
names.description = `a_safe_scenario_description`;
|
|
4
4
|
|
|
5
5
|
names.chinese_input = `是汉字`;
|
|
6
6
|
names.chinese_output = `是汉字`;
|
|
@@ -32,4 +32,7 @@ names.two_consecutive_invalid_output = `two_consecutive__invalid`;
|
|
|
32
32
|
names.numerical_input = `scenario_w1th_number00`;
|
|
33
33
|
names.numerical_output = `scenario_w1th_number00`;
|
|
34
34
|
|
|
35
|
+
names.long_id_input = `over_20_chars_123456789`;
|
|
36
|
+
names.long_id_output = `over_20_chars_123456`;
|
|
37
|
+
|
|
35
38
|
module.exports = names;
|