@suffolklitlab/alkiln 4.10.3-feat-debug-cache → 4.10.4-cli-playground-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -5
- package/action.yml +20 -46
- package/cucumber.js +1 -1
- package/lib/docassemble/docassemble_api_interface.js +9 -11
- package/lib/run_cucumber.js +92 -0
- package/lib/scope.js +18 -21
- package/lib/steps.js +11 -24
- package/lib/utils/log.js +2 -1
- package/lib/utils/session_vars.js +18 -4
- package/package.json +7 -8
- package/lib/utils/clean_reports.js +0 -29
package/CHANGELOG.md
CHANGED
|
@@ -39,10 +39,26 @@ Format:
|
|
|
39
39
|
-
|
|
40
40
|
-->
|
|
41
41
|
## [Unreleased]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- A new script: `alkiln-run`, which acts like `npm run cucumber`, but can
|
|
45
|
+
be run in any directory, not just in an npm package.
|
|
46
|
+
- Ability to run ALKiln from an interview by a logged-in user
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
- upgraded cucumber v8.6.0
|
|
50
|
+
- using cucumber's JS API to run tests. For more details on how it works,
|
|
51
|
+
see [the cucumber-js docs](https://github.com/cucumber/cucumber-js/blob/main/docs/javascript_api.md).
|
|
52
|
+
- the github action no longer runs `npm run XYZ`; it directly calls scripts,
|
|
53
|
+
e.g. `alkiln-setup`, `alkiln-run`, `alkiln-takedown`
|
|
54
|
+
- don't print the ["publish this cucumber report" message](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options)
|
|
55
|
+
|
|
56
|
+
## [4.10.4] - 2023-02-15
|
|
57
|
+
### Added
|
|
58
|
+
- Contribution docs
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- #511, couldn't take screenshots of signature pages https://github.com/SuffolkLITLab/ALKiln/issues/511
|
|
46
62
|
|
|
47
63
|
## [4.10.3] - 2023-01-11
|
|
48
64
|
### Removed
|
|
@@ -565,4 +581,4 @@ Format:
|
|
|
565
581
|
- `example.feature` - an example test to get a project started
|
|
566
582
|
- `github/workflows/run_form_tests.yml` - Automated github workflow to run tests when code is pushed and a manual trigger for testing without needing to push
|
|
567
583
|
- `.gitignore` - ignores files that are generated by the tests and shouldn't be added to the repo
|
|
568
|
-
- `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
|
|
584
|
+
- `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
|
package/action.yml
CHANGED
|
@@ -5,15 +5,6 @@ inputs:
|
|
|
5
5
|
SERVER_URL:
|
|
6
6
|
description: 'The url of your docassemble server. This should be in your GitHub SECRETS or the SECRETS of your org.'
|
|
7
7
|
required: true
|
|
8
|
-
PLAYGROUND_EMAIL:
|
|
9
|
-
description: 'The email of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
|
|
10
|
-
required: true
|
|
11
|
-
PLAYGROUND_PASSWORD:
|
|
12
|
-
description: 'The password of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
|
|
13
|
-
required: true
|
|
14
|
-
PLAYGROUND_ID:
|
|
15
|
-
description: 'The id of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
|
|
16
|
-
required: true
|
|
17
8
|
DOCASSEMBLE_DEVELOPER_API_KEY:
|
|
18
9
|
description: 'API key of the testing account with developer permissions on your docassemble server.'
|
|
19
10
|
required: true
|
|
@@ -33,6 +24,16 @@ inputs:
|
|
|
33
24
|
required: false
|
|
34
25
|
default: '120'
|
|
35
26
|
|
|
27
|
+
## Testing by hand on the server. Should only be created by interview code.
|
|
28
|
+
#_USER_ID:
|
|
29
|
+
# description: 'AVOID SETTING THIS YOURSELF. The id of your docassemble playground account. This is created and used by ALKiln when testing manually on your server.'
|
|
30
|
+
# required: false
|
|
31
|
+
#_PROJECT_NAME:
|
|
32
|
+
# description: 'AVOID SETTING THIS YOURSELF. The name of the Playground Project to test manually on your server. ALKiln will ask you for this information.'
|
|
33
|
+
# required: false
|
|
34
|
+
#_ORIGIN:
|
|
35
|
+
# description: 'AVOID SETTING THIS YOURSELF. The origin that's triggering this round of tests.'
|
|
36
|
+
|
|
36
37
|
runs:
|
|
37
38
|
using: "composite"
|
|
38
39
|
steps:
|
|
@@ -55,12 +56,12 @@ runs:
|
|
|
55
56
|
echo "REPO_URL=${{ github.server_url }}/${{ github.repository }}" >> $GITHUB_ENV
|
|
56
57
|
echo "BRANCH_PATH=${{ github.ref }}" >> $GITHUB_ENV
|
|
57
58
|
echo "BASE_URL=${{ inputs.SERVER_URL }}" >> $GITHUB_ENV
|
|
58
|
-
echo "PLAYGROUND_EMAIL=${{ inputs.PLAYGROUND_EMAIL }}" >> $GITHUB_ENV
|
|
59
|
-
echo "PLAYGROUND_PASSWORD=${{ inputs.PLAYGROUND_PASSWORD }}" >> $GITHUB_ENV
|
|
60
|
-
echo "PLAYGROUND_ID=${{ inputs.PLAYGROUND_ID }}" >> $GITHUB_ENV
|
|
61
59
|
echo "DOCASSEMBLE_DEVELOPER_API_KEY=${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}" >> $GITHUB_ENV
|
|
62
60
|
echo "ALKILN_VERSION=${{ inputs.ALKILN_VERSION }}" >> $GITHUB_ENV
|
|
63
61
|
echo "MAX_SECONDS_FOR_SETUP=${{ inputs.MAX_SECONDS_FOR_SETUP }}" >> $GITHUB_ENV
|
|
62
|
+
|
|
63
|
+
# echo "_USER_ID=${{ inputs._USER_ID }}" >> $GITHUB_ENV
|
|
64
|
+
# echo "_PROJECT_NAME=${{ inputs._PROJECT_NAME }}" >> $GITHUB_ENV
|
|
64
65
|
shell: bash
|
|
65
66
|
- name: "ALKiln: confirm info"
|
|
66
67
|
run: |
|
|
@@ -68,58 +69,31 @@ runs:
|
|
|
68
69
|
echo -e "\nALKiln version is $ALKILN_VERSION\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nMAX_SECONDS_FOR_SETUP is $MAX_SECONDS_FOR_SETUP\nManually added languages are $EXTRA_LANGUAGES\n"
|
|
69
70
|
shell: bash
|
|
70
71
|
|
|
71
|
-
# This doesn't use an already established package-lock.json and that
|
|
72
|
-
# comes with some types of test breaking risks. Same problem as before.
|
|
73
|
-
- name: Temporarily overwrite or create package.json
|
|
74
|
-
run: |
|
|
75
|
-
cat << EOF > "package.json"
|
|
76
|
-
{
|
|
77
|
-
"scripts": {
|
|
78
|
-
"test": "npm run langs_setup && npm run cucumber -- $@",
|
|
79
|
-
"cucumber_base": "./node_modules/.bin/cucumber-js --require ./node_modules/@suffolklitlab/alkiln/lib/index.js",
|
|
80
|
-
"cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature --retry 1; }; run_cucumber",
|
|
81
|
-
"langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./node_modules/.bin/alkiln-cucumber docassemble/*/data/sources/*_lang*.feature",
|
|
82
|
-
"langs_setup": "node_modules/.bin/alkiln-langs 'docassemble/*/data/sources/*.feature'",
|
|
83
|
-
"setup": "node_modules/.bin/alkiln-setup",
|
|
84
|
-
"takedown": "node_modules/.bin/alkiln-takedown"
|
|
85
|
-
},
|
|
86
|
-
"dependencies": {
|
|
87
|
-
"colors": "1.4.0",
|
|
88
|
-
"@cucumber/cucumber": "7.3.2",
|
|
89
|
-
"@suffolklitlab/alkiln": "$ALKILN_VERSION"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
EOF
|
|
93
|
-
# Keep in sync with the cucumber.js in root
|
|
94
|
-
cat << EOF > "cucumber.js"
|
|
95
|
-
module.exports = {
|
|
96
|
-
default: "--require ./lib/index.js --format progress --format summary:cucumber-report.txt"
|
|
97
|
-
}
|
|
98
|
-
EOF
|
|
99
|
-
shell: bash
|
|
100
|
-
|
|
101
72
|
# Install node
|
|
102
73
|
- name: "ALKiln: Install node packages"
|
|
103
74
|
uses: actions/setup-node@v1
|
|
104
75
|
with:
|
|
105
76
|
node-version: "17"
|
|
106
|
-
|
|
77
|
+
# npm install -g "@suffolklitlab/alkiln@$ALKILN_VERSION"
|
|
78
|
+
- name: Install ALKiln directly from npm
|
|
79
|
+
run: |
|
|
80
|
+
npm install -g github:SuffolkLITLab/ALKiln#lighter_package_json
|
|
107
81
|
shell: bash
|
|
108
82
|
|
|
109
83
|
# Run tests
|
|
110
84
|
- name: "ALKiln: Create a Project and pull the package from GitHub"
|
|
111
|
-
run:
|
|
85
|
+
run: alkiln-setup
|
|
112
86
|
shell: bash
|
|
113
87
|
- name: "ALKiln: Run tests"
|
|
114
88
|
if: ${{ success() }}
|
|
115
|
-
run:
|
|
89
|
+
run: alkiln-run ${{ github.event.inputs.tags && format('"{0}"', github.event.inputs.tags) }}
|
|
116
90
|
shell: bash
|
|
117
91
|
- run: echo -e "\n\n====ALKiln could not create a project on your server's testing account or pull your package into it. Check the messages above this line.\n\n"
|
|
118
92
|
if: ${{ failure() }}
|
|
119
93
|
shell: bash
|
|
120
94
|
- name: "ALKiln: Delete project from docassemble test account"
|
|
121
95
|
if: ${{ always() }}
|
|
122
|
-
run:
|
|
96
|
+
run: alkiln-takedown
|
|
123
97
|
shell: bash
|
|
124
98
|
|
|
125
99
|
# Upload artifacts that subscribers can download on the Actions summary page
|
package/cucumber.js
CHANGED
|
@@ -204,16 +204,16 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
204
204
|
let wait_seconds = creation_options.wait_seconds || 5;
|
|
205
205
|
|
|
206
206
|
let project_name = null;
|
|
207
|
-
let
|
|
207
|
+
let name_incrementor = 0;
|
|
208
208
|
|
|
209
209
|
// Try to create a project. If the project name is already taken, increment and loop again.
|
|
210
|
-
while (
|
|
211
|
-
|
|
210
|
+
while ( name_incrementor <= max_tries ) {
|
|
211
|
+
name_incrementor++;
|
|
212
212
|
try {
|
|
213
213
|
|
|
214
|
-
// Try
|
|
214
|
+
// Try next name that could be unique. Must remove all non-valid characters
|
|
215
215
|
// TODO: This sanitization is already done in session_vars, so remove it from here
|
|
216
|
-
project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') +
|
|
216
|
+
project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + name_incrementor;
|
|
217
217
|
log.info({ pre: `Trying to create a docassemble Project with the name ${ project_name }` });
|
|
218
218
|
let response = await _da_REST.create_project( project_name, timeout );
|
|
219
219
|
log.info({ pre: `The docassemble server is ready. Created a Project named ${ project_name }` });
|
|
@@ -226,7 +226,7 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
226
226
|
|
|
227
227
|
if ( response && response.data ) {
|
|
228
228
|
if ( response.data.includes( `Invalid project` ) ) {
|
|
229
|
-
if (
|
|
229
|
+
if ( name_incrementor < max_tries ) {
|
|
230
230
|
// A non-unique project name. Try the next project name.
|
|
231
231
|
continue;
|
|
232
232
|
|
|
@@ -256,11 +256,11 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
256
256
|
|
|
257
257
|
// If it's a timeout error, server is busy. Try again after waiting.
|
|
258
258
|
// Why are we using `max_tries` for timeout error as well?
|
|
259
|
-
if ( is_timeout_error( error ) &&
|
|
259
|
+
if ( is_timeout_error( error ) && name_incrementor < max_tries ) {
|
|
260
260
|
log.info({ pre: `The docassemble server was busy when we tried to `
|
|
261
|
-
+ `create the Project. Attempt ${
|
|
261
|
+
+ `create the Project. Attempt ${ name_incrementor } of `
|
|
262
262
|
+ `${ max_tries }. Will try again in ${ wait_seconds } seconds.` });
|
|
263
|
-
|
|
263
|
+
name_incrementor--; // Try this name again next time
|
|
264
264
|
await time.waitForTimeout( wait_seconds * 1000 );
|
|
265
265
|
continue;
|
|
266
266
|
|
|
@@ -272,8 +272,6 @@ da_i.loop_to_create_project = async function( creation_options ) {
|
|
|
272
272
|
} // ends type of non-response error
|
|
273
273
|
} // ends type of response error
|
|
274
274
|
} // ends try
|
|
275
|
-
|
|
276
|
-
// TODO: Add a message that we're done trying to create a project, successful or not
|
|
277
275
|
} // ends while
|
|
278
276
|
}; // Ends da_i.loop_to_create_project()
|
|
279
277
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const log = require('./utils/log');
|
|
5
|
+
const session_vars = require('./utils/session_vars');
|
|
6
|
+
|
|
7
|
+
// import {loadConfiguration, loadSupport, runCucumber } from '@cucumber/cucumber/api';
|
|
8
|
+
const cucumber_api = require('@cucumber/cucumber/api');
|
|
9
|
+
|
|
10
|
+
(async function main() {
|
|
11
|
+
let argv = process.argv;
|
|
12
|
+
|
|
13
|
+
let just_args = argv.slice(2);
|
|
14
|
+
|
|
15
|
+
const environment = { cwd: process.cwd() };
|
|
16
|
+
const provided_config = {
|
|
17
|
+
"publishQuiet": true,
|
|
18
|
+
"require": "./lib/index.js",
|
|
19
|
+
"format": ["progress", "summary:cucumber-report.txt"],
|
|
20
|
+
"paths": ["docassemble/*/data/sources/*.feature"],
|
|
21
|
+
"retry": 1,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// If they're running it from an interview on their server instead of from GitHub
|
|
25
|
+
if ( session_vars.get_origin() === `interview` ) {
|
|
26
|
+
provided_config.paths.append(`/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let tag_vals = just_args;
|
|
30
|
+
if ( session_vars.get_origin() === `interview` ) {
|
|
31
|
+
// Should tags be given as a string or a list? String would give option to
|
|
32
|
+
// give a boolean expression. TODO: offer a dropdown with available tags.
|
|
33
|
+
tag_vals = session_vars.get_tags().split(` `);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (tag_vals.length > 0) {
|
|
37
|
+
// Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
|
|
38
|
+
// If we get a list of only tags, then "or" them all together
|
|
39
|
+
if (tag_vals.every((val) => val.startsWith(`@`))) {
|
|
40
|
+
provided_config.tags = tag_vals.join(` or `);
|
|
41
|
+
}
|
|
42
|
+
// If we get a list of seemingly no tags, assume they meant to use tags
|
|
43
|
+
else if (tag_vals.every((val) => !val.startsWith(`@`))) {
|
|
44
|
+
provided_config.tags = tag_vals.map((val) => `@${ val }`).join(` or `);
|
|
45
|
+
}
|
|
46
|
+
// If it's a mix of tags and no tags, it's probably a boolean expression!
|
|
47
|
+
else {
|
|
48
|
+
provided_config.tags = tag_vals.join(` `);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
const { runConfiguration } = await cucumber_api.loadConfiguration({
|
|
54
|
+
provided: provided_config
|
|
55
|
+
}, environment);
|
|
56
|
+
var path = require('path');
|
|
57
|
+
var codeDir = path.dirname(require.main.filename);
|
|
58
|
+
const support = await cucumber_api.loadSupport({ support: {
|
|
59
|
+
requireModules: [`${ codeDir }/index.js`],
|
|
60
|
+
requirePaths: [ `${ codeDir }/index.js`],
|
|
61
|
+
importPaths: [],
|
|
62
|
+
}, sources: {paths: []}}, environment);
|
|
63
|
+
|
|
64
|
+
// TODO: Maybe only log these for debug?
|
|
65
|
+
console.log(`ALKiln run_cucumber.js configuration:`);
|
|
66
|
+
console.log(`%o`, runConfiguration);
|
|
67
|
+
|
|
68
|
+
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
69
|
+
console.log(`ALKiln run_cucumber.js succeeded:`);
|
|
70
|
+
console.log(`%o`, success);
|
|
71
|
+
const exitCode = success ? 0 : 1;
|
|
72
|
+
process.exitCode = exitCode;
|
|
73
|
+
|
|
74
|
+
// This is hardcoded information from `cucumber.js`. Now we on cucumber 8, we should
|
|
75
|
+
// change that the cucumber.json and actually parse the info from it.
|
|
76
|
+
// This code isn't used for anything other than detecting and removing color codes
|
|
77
|
+
let summary_file_name = 'cucumber-report.txt';
|
|
78
|
+
fs.readFile(summary_file_name, 'utf8', (err, data) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
console.error(err);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Replace terminal color codes, if present
|
|
85
|
+
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
|
|
86
|
+
data = data.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
|
|
87
|
+
|
|
88
|
+
// Put it in the correct file in the correct folder
|
|
89
|
+
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
90
|
+
fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
|
|
91
|
+
});
|
|
92
|
+
})();
|
package/lib/scope.js
CHANGED
|
@@ -2002,13 +2002,6 @@ module.exports = {
|
|
|
2002
2002
|
if ( row_used.source ) { row_used.source.times_used += 1 }
|
|
2003
2003
|
row_used.times_used += 1;
|
|
2004
2004
|
|
|
2005
|
-
let { id } = scope.examinePageID( scope );
|
|
2006
|
-
await scope.page.screenshot({
|
|
2007
|
-
path: `./${ scope.paths.scenario }/pic-${Date.now()}-${ id }.jpg`,
|
|
2008
|
-
type: 'jpeg',
|
|
2009
|
-
fullPage: true
|
|
2010
|
-
});
|
|
2011
|
-
|
|
2012
2005
|
// TODO: special continue/navigation button function that handles url and all?
|
|
2013
2006
|
|
|
2014
2007
|
// Have to catch errors right now or may get stuck in an infinite loop?
|
|
@@ -2022,7 +2015,6 @@ module.exports = {
|
|
|
2022
2015
|
// Discuss moving this out of here. Having to pass in `id` just for
|
|
2023
2016
|
// these messages smells bad. Con: Already a lot of code out there.
|
|
2024
2017
|
error = await scope.waitUntilContinued( scope, { url: page_url, id });
|
|
2025
|
-
|
|
2026
2018
|
// if ( error.was_found ) {
|
|
2027
2019
|
// await scope.throwPageError( scope, { id: id });
|
|
2028
2020
|
// }
|
|
@@ -2061,14 +2053,6 @@ module.exports = {
|
|
|
2061
2053
|
if ( !did_navigate && ensure_navigation ) {
|
|
2062
2054
|
// All fields on this page should be taken care of
|
|
2063
2055
|
let page_url = await scope.page.url();
|
|
2064
|
-
|
|
2065
|
-
let { id } = scope.examinePageID( scope );
|
|
2066
|
-
await scope.page.screenshot({
|
|
2067
|
-
path: `./${ scope.paths.scenario }/pic-${Date.now()}-${ id }.jpg`,
|
|
2068
|
-
type: 'jpeg',
|
|
2069
|
-
fullPage: true
|
|
2070
|
-
});
|
|
2071
|
-
|
|
2072
2056
|
await scope.continue( scope );
|
|
2073
2057
|
// Have to catch errors right now or may get stuck in an infinite loop?
|
|
2074
2058
|
// TODO: Don't catch errors - maybe the developer expected/wanted an error
|
|
@@ -2295,6 +2279,23 @@ module.exports = {
|
|
|
2295
2279
|
return scope;
|
|
2296
2280
|
}, // Ends scope.throwPageError()
|
|
2297
2281
|
|
|
2282
|
+
take_a_screenshot: async ( scope,{ path }) => {
|
|
2283
|
+
/* Takes a jpeg screenshot. Avoids destroying signatures. */
|
|
2284
|
+
|
|
2285
|
+
let fullPage = true;
|
|
2286
|
+
let signature_elem = await scope.page.$(scope.signature_selector);
|
|
2287
|
+
if ( signature_elem !== null ) {
|
|
2288
|
+
fullPage = false;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
await scope.page.screenshot({
|
|
2292
|
+
path: path,
|
|
2293
|
+
type: 'jpeg',
|
|
2294
|
+
fullPage: fullPage,
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
}, // Ends scope.take_a_screenshot()
|
|
2298
|
+
|
|
2298
2299
|
|
|
2299
2300
|
//#####################################
|
|
2300
2301
|
//#####################################
|
|
@@ -2317,11 +2318,7 @@ module.exports = {
|
|
|
2317
2318
|
name = `screenshot_on-${ id }-${ date }`;
|
|
2318
2319
|
}
|
|
2319
2320
|
|
|
2320
|
-
await scope.
|
|
2321
|
-
path: `./${ scope.paths.scenario }/${ name }.jpg`,
|
|
2322
|
-
type: 'jpeg',
|
|
2323
|
-
fullPage: true
|
|
2324
|
-
});
|
|
2321
|
+
await scope.take_a_screenshot( scope, { path: `./${ scope.paths.scenario }/${ name }.jpg` });
|
|
2325
2322
|
|
|
2326
2323
|
await scope.afterStep( scope );
|
|
2327
2324
|
}, // Ends scope.steps.screenshot()
|
package/lib/steps.js
CHANGED
|
@@ -92,7 +92,7 @@ BeforeAll(async () => {
|
|
|
92
92
|
// Make the folder to store all the files for this run of the tests
|
|
93
93
|
scope.paths.artifacts = files.make_artifacts_folder();
|
|
94
94
|
// Save its name in a file. `session_vars` can get it back later.
|
|
95
|
-
// This is used by at least
|
|
95
|
+
// This is used by at least run_cucumber.js.
|
|
96
96
|
session_vars.save_artifacts_path_name( scope.paths.artifacts );
|
|
97
97
|
|
|
98
98
|
scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
|
|
@@ -103,7 +103,7 @@ BeforeAll(async () => {
|
|
|
103
103
|
Before(async (scenario) => {
|
|
104
104
|
// Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
|
|
105
105
|
if (!scope.browser) {
|
|
106
|
-
scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
|
|
106
|
+
scope.browser = await scope.driver.launch({ args: ['--no-sandbox'], headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
|
|
107
107
|
}
|
|
108
108
|
// Clean up all previously existing pages
|
|
109
109
|
for (const page of await scope.browser.pages()) {
|
|
@@ -386,12 +386,6 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
386
386
|
if( row1.report_str > row2.report_str ) { return 1; }
|
|
387
387
|
return 0;
|
|
388
388
|
});
|
|
389
|
-
|
|
390
|
-
await scope.page.screenshot({
|
|
391
|
-
path: `./${ scope.paths.scenario }/pic-${Date.now()}-${ id }.jpg`,
|
|
392
|
-
type: 'jpeg',
|
|
393
|
-
fullPage: true
|
|
394
|
-
});
|
|
395
389
|
|
|
396
390
|
// Add full used table to report so it can be copied if needed.
|
|
397
391
|
// TODO: Should we/How do we get this in the final scenario report instead of here?
|
|
@@ -852,11 +846,8 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
852
846
|
let buttons = await scope.steps.set_random_page_vars( scope );
|
|
853
847
|
|
|
854
848
|
// Take a screenshot of the page before continuing or ending
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
type: `jpeg`,
|
|
858
|
-
fullPage: true
|
|
859
|
-
});
|
|
849
|
+
let path = `${ scope.paths.random_screenshots }/random-${ Date.now() }.jpg`;
|
|
850
|
+
await scope.take_a_screenshot( scope, { path: path });
|
|
860
851
|
|
|
861
852
|
// Prep for the next loop to possibly stop. Signature page buttons
|
|
862
853
|
// don't count as buttons in the way our field detects them
|
|
@@ -1125,27 +1116,23 @@ After(async function(scenario) {
|
|
|
1125
1116
|
if ( !!scope.disable_error_screenshot ) {
|
|
1126
1117
|
scope.addToReport(scope, {
|
|
1127
1118
|
type: `row`,
|
|
1128
|
-
value: `For security, ALKiln will
|
|
1119
|
+
value: `For security, ALKiln will avoid creating a screenshot for this error. It's possible a secret is being used on this screen.`
|
|
1129
1120
|
});
|
|
1130
1121
|
} else {
|
|
1131
1122
|
|
|
1132
1123
|
// Save/download a picture of the screen that's showing during the unexpected status
|
|
1133
1124
|
// Save one copy in the outer-most artifact folder
|
|
1134
1125
|
let scenario_filename = await scope.getSafeScenarioFilename( scope, { prefix: `error_on` });
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
fullPage: true
|
|
1139
|
-
});
|
|
1126
|
+
let path_outer = `${ scope.paths.artifacts }/${ scenario_filename }.jpg`;
|
|
1127
|
+
await scope.take_a_screenshot( scope, { path: path_outer });
|
|
1128
|
+
|
|
1140
1129
|
// Save another copy in the artifact's Scenario folder
|
|
1141
1130
|
let screenshot_name = `error_on`;
|
|
1142
1131
|
let { id } = await scope.examinePageID( scope, 'none to match' );
|
|
1143
1132
|
screenshot_name += `-${ id }`;
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
fullPage: true
|
|
1148
|
-
});
|
|
1133
|
+
let path_scenario = `${ scope.paths.scenario }/${ screenshot_name }.jpg`;
|
|
1134
|
+
await scope.take_a_screenshot( scope, { path: path_scenario });
|
|
1135
|
+
|
|
1149
1136
|
} // ends if scope.disable_error_screenshot
|
|
1150
1137
|
} // ends if scope.page exists
|
|
1151
1138
|
|
package/lib/utils/log.js
CHANGED
|
@@ -52,7 +52,8 @@ log.debug = function ({ type='', pre='', data='', post='' }) {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
log.add_to_debug_log = function(value) {
|
|
55
|
-
|
|
55
|
+
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
56
|
+
fs.appendFileSync(`${ artifacts_path }/${ log.debug_log_file}`, value);
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
log.debug_log_file = 'debug_log.txt'
|
|
@@ -23,9 +23,19 @@ module.exports = session_vars;
|
|
|
23
23
|
// These default to null
|
|
24
24
|
// TODO: How to isolate debug elsewhere so this file can use the logger?
|
|
25
25
|
session_vars.get_debug = function () { return process.env.DEBUG || null; };
|
|
26
|
+
session_vars.is_dev_env = function () { return process.env.DEV || null; }
|
|
27
|
+
|
|
26
28
|
session_vars.get_dev_api_key = function () { return process.env.DOCASSEMBLE_DEVELOPER_API_KEY || null; };
|
|
27
29
|
session_vars.get_repo_url = function () { return process.env.REPO_URL || null; };
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
// Created by interview code when tests through an interview manually on the server
|
|
32
|
+
session_vars.get_origin = function () {
|
|
33
|
+
/** Possible values: 'interview', 'github'. In future, maybe 'playground' or 'dashboard'. */
|
|
34
|
+
return process.env._ORIGIN || null;
|
|
35
|
+
};
|
|
36
|
+
session_vars.get_user_project_name = function () { return process.env._PROJECT_NAME || null; };
|
|
37
|
+
session_vars.get_user_id = function () { return process.env._USER_ID || null; };
|
|
38
|
+
session_vars.get_tags = function () { return process.env._TAGS || null; };
|
|
29
39
|
|
|
30
40
|
// More complex logic
|
|
31
41
|
session_vars.get_server_reload_timeout = function () {
|
|
@@ -129,8 +139,12 @@ session_vars.save_project_name = function ( project_name ) {
|
|
|
129
139
|
return file;
|
|
130
140
|
}; // Ends save_project_name()
|
|
131
141
|
|
|
142
|
+
// TODO next: Add use of env var in here in case not through GitHub cli_playground
|
|
132
143
|
session_vars.get_project_name = function () {
|
|
133
|
-
/* Get the name of the current docassemble Project
|
|
144
|
+
/* Get the name of the current docassemble Project */
|
|
145
|
+
if ( session_vars.get_origin() === 'interview' ) {
|
|
146
|
+
return session_vars.get_user_project_name();
|
|
147
|
+
}
|
|
134
148
|
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
|
|
135
149
|
let project_name = json[ project_name_key ] || null;
|
|
136
150
|
if ( session_vars.get_debug() ) { console.log( `ALKiln debug: Project name from file is "${ project_name }".` ); }
|
|
@@ -174,7 +188,7 @@ session_vars.save_artifacts_path_name = function ( path_name ) {
|
|
|
174
188
|
console.log( `ALKiln debug: Stored name of artifacts directory "${ path_name }" locally.` );
|
|
175
189
|
}
|
|
176
190
|
return file;
|
|
177
|
-
}; // Ends
|
|
191
|
+
}; // Ends save_artifacts_path_name()
|
|
178
192
|
|
|
179
193
|
session_vars.get_artifacts_path_name = function () {
|
|
180
194
|
/* Get the name of the current artifacts path. */
|
|
@@ -187,7 +201,7 @@ session_vars.get_artifacts_path_name = function () {
|
|
|
187
201
|
} catch ( error ) {
|
|
188
202
|
return null;
|
|
189
203
|
}
|
|
190
|
-
}; // Ends
|
|
204
|
+
}; // Ends get_artifacts_path_name()
|
|
191
205
|
|
|
192
206
|
/**
|
|
193
207
|
* Ensure all required environment variables exist. Add each missing variable
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suffolklitlab/alkiln",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.4-cli-playground-1",
|
|
4
4
|
"description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "npm run unit && npm run langs_setup && npm run cucumber -- $@",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"cucumber_external": "run_cucumber(){ tmp_path=`pwd`; ( new_path=\"$1\"; shift; cd \"$new_path\"; npm run --prefix \"${tmp_path}\" cucumber_base -- \"$@\" \"$new_path\"/docassemble/*/data/sources/*.feature;) }; run_cucumber",
|
|
11
|
-
"langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./lib/index.js docassemble/*/data/sources/*_lang*.feature",
|
|
8
|
+
"cucumber": "./lib/run_cucumber.js",
|
|
9
|
+
"langs": "npm run langs_setup && npm run cucumber",
|
|
12
10
|
"langs_setup": "./lib/utils/langs.js 'docassemble/*/data/sources/*.feature'",
|
|
13
11
|
"langs_local": "npm run langs_setup && npm run local",
|
|
14
12
|
"local": "npm run setup && npm run cucumber; npm run takedown",
|
|
@@ -36,13 +34,13 @@
|
|
|
36
34
|
"homepage": "https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/",
|
|
37
35
|
"dependencies": {
|
|
38
36
|
"@axe-core/puppeteer": "4.4.2",
|
|
39
|
-
"@cucumber/cucumber": "
|
|
37
|
+
"@cucumber/cucumber": "8.6.0",
|
|
40
38
|
"@faker-js/faker": "^7.3.0",
|
|
41
39
|
"axios": "0.24.0",
|
|
42
40
|
"chai": "4.2.0",
|
|
43
41
|
"cheerio": "1.0.0-rc.10",
|
|
44
|
-
"dotenv": "8.2.0",
|
|
45
42
|
"deep-equal-in-any-order": "^2.0.0",
|
|
43
|
+
"dotenv": "8.2.0",
|
|
46
44
|
"mocha": "9.2.0",
|
|
47
45
|
"puppeteer": "13.1.3",
|
|
48
46
|
"qs": "6.10.2",
|
|
@@ -52,6 +50,7 @@
|
|
|
52
50
|
"bin": {
|
|
53
51
|
"alkiln-setup": "./lib/docassemble/setup.js",
|
|
54
52
|
"alkiln-takedown": "./lib/docassemble/takedown.js",
|
|
55
|
-
"alkiln-langs": "./lib/utils/langs.js"
|
|
53
|
+
"alkiln-langs": "./lib/utils/langs.js",
|
|
54
|
+
"alkiln-run": "./lib/run_cucumber.js"
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const log = require('./log');
|
|
5
|
-
const session_vars = require('./session_vars');
|
|
6
|
-
|
|
7
|
-
// This is hardcoded information from `cucumber.js`. Once we move to cucumber 8, we should
|
|
8
|
-
// change that the cucumber.json and actually parse the info from it.
|
|
9
|
-
// The file won't be used for anything other than detecting and removing color codes
|
|
10
|
-
let summary_file_name = 'cucumber-report.txt';
|
|
11
|
-
fs.readFile(summary_file_name, 'utf8', (err, data) => {
|
|
12
|
-
if (err) {
|
|
13
|
-
console.error(err);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Replace terminal color codes, if present
|
|
18
|
-
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
|
|
19
|
-
data = data.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
|
|
20
|
-
|
|
21
|
-
// Put it in the correct file in the correct folder
|
|
22
|
-
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
23
|
-
fs.appendFile( `${ artifacts_path }/${ log.debug_log_file }`, data, err => {
|
|
24
|
-
if (err) {
|
|
25
|
-
console.error(err);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
}); // Ends appendFileSync
|
|
29
|
-
});
|