@suffolklitlab/alkiln 5.11.2-fix-root-dir-1 → 5.12.0-delete1
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 +40 -16
- package/.github/workflows/playground.yml +5 -10
- package/CHANGELOG.md +35 -1
- package/README.md +1 -1
- package/action.yml +75 -2
- package/action_for_github_server/action.yml +5 -3
- package/docs/story_tables.md +1 -1
- package/lib/docassemble/docassemble_api_REST.js +46 -1
- package/lib/docassemble/docassemble_api_interface.js +89 -1
- package/lib/run_cucumber.js +31 -10
- package/lib/scope.js +58 -13
- package/lib/steps.js +105 -24
- package/lib/utils/log.js +3 -1
- package/lib/utils/reports.js +72 -27
- package/lib/utils/session_vars.js +1 -1
- package/package.json +4 -2
- package/setup.py +1 -1
- package/tests/log_codes/check_codes.sh +2 -1
|
@@ -23,8 +23,8 @@ on:
|
|
|
23
23
|
jobs:
|
|
24
24
|
|
|
25
25
|
github-server-tests:
|
|
26
|
-
#### Developer note: If
|
|
27
|
-
#### `
|
|
26
|
+
#### Developer note: If you have both `push` and `pull_request` in the
|
|
27
|
+
#### `on` section (above), this avoids running double tests when someone
|
|
28
28
|
#### on the team makes a pull request
|
|
29
29
|
# 1: Trigger job on push or dispatch from this repository
|
|
30
30
|
# 2: Trigger job on pull request from a fork
|
|
@@ -71,10 +71,10 @@ jobs:
|
|
|
71
71
|
shell: bash
|
|
72
72
|
|
|
73
73
|
#### Developer note: You can probably leave this out
|
|
74
|
-
# Optional debugging to explore things like docker issues
|
|
75
|
-
- name: Docker debug tmate session
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
# # Optional debugging to explore things like docker issues
|
|
75
|
+
# - name: Docker debug tmate session
|
|
76
|
+
# if: ${{ failure() && github.event.inputs.enable_tmate == 'true' }}
|
|
77
|
+
# uses: mxschmitt/action-tmate@v3
|
|
78
78
|
|
|
79
79
|
#### Developer note: You can probably leave this out
|
|
80
80
|
- name: Run ALKiln - Env vars for our own tests from server output
|
|
@@ -93,25 +93,49 @@ jobs:
|
|
|
93
93
|
|
|
94
94
|
- name: "Run ALKiln tests"
|
|
95
95
|
if: ${{ success() }}
|
|
96
|
+
id: alkiln
|
|
96
97
|
#### Developer note: you'll need to replace `uses: ./`
|
|
97
98
|
#### with the path to ALKiln’s repository name and branch name. For example,
|
|
98
99
|
#### for version 5, use the branch name `v5`: `uses: suffolkLITLab/ALKiln@v5`
|
|
99
100
|
uses: ./
|
|
100
|
-
#### Developer note: You can probably leave this
|
|
101
|
+
#### Developer note: You can probably leave out this `env` section
|
|
101
102
|
env:
|
|
102
|
-
|
|
103
|
+
# Internal: docassemble changes sometimes break @a11y tests. That is
|
|
104
|
+
# a da problem, not ours. We will trigger these tests manually when we
|
|
105
|
+
# want to check up on this.
|
|
106
|
+
ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || '(not @error) and (not @temp_error)' }}"
|
|
103
107
|
with:
|
|
104
108
|
#### Developer note: Required inputs. See
|
|
105
109
|
#### https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#sandbox-inputs
|
|
106
110
|
SERVER_URL: "${{ steps.github_server.outputs.SERVER_URL }}"
|
|
107
111
|
DOCASSEMBLE_DEVELOPER_API_KEY: "${{ steps.github_server.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}"
|
|
108
112
|
INSTALL_METHOD: "server"
|
|
109
|
-
#### Developer note:
|
|
110
|
-
####
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
#### Developer note: You can probably leave the rest out
|
|
114
|
+
#### To learn more, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#optional-inputs
|
|
115
|
+
ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
|
|
116
|
+
ALKILN_VERSION: delete
|
|
117
|
+
|
|
118
|
+
#### Developer note: Example of making an issue when tests fail
|
|
119
|
+
#### that includes the text of the failure output file
|
|
120
|
+
#### See https://cli.github.com/manual/gh_issue_create for more details
|
|
121
|
+
- shell: bash
|
|
122
|
+
if: ${{ failure() }}
|
|
123
|
+
env:
|
|
124
|
+
GH_TOKEN: ${{ github.token }}
|
|
125
|
+
PATH_TO_REPORT_FILE: "${{ steps.alkiln.outputs.PATH_TO_REPORT_FILE }}"
|
|
126
|
+
PATH_TO_UNEXPECTED_RESULTS_FILE: "${{ steps.alkiln.outputs.PATH_TO_UNEXPECTED_RESULTS_FILE }}"
|
|
127
|
+
PATH_TO_DEBUG_LOG_FILE: "${{ steps.alkiln.outputs.PATH_TO_DEBUG_LOG_FILE }}"
|
|
128
|
+
run: |
|
|
129
|
+
# Make an issue with file contents or 'no file' message as part of the body
|
|
130
|
+
|
|
131
|
+
if [ -f "${{ env.PATH_TO_UNEXPECTED_RESULTS_FILE }}" ]; then
|
|
132
|
+
UNEXPECTED_2=$(cat "${{ env.PATH_TO_UNEXPECTED_RESULTS_FILE }}")
|
|
133
|
+
else
|
|
134
|
+
UNEXPECTED_2="unexpected_results.txt does not exist. Something other than the interview tests may have failed."
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
gh issue create --body "An ALKiln test run failed. See the action at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.
|
|
138
|
+
|
|
139
|
+
### Results:
|
|
117
140
|
|
|
141
|
+
$UNEXPECTED_2" --repo "$GITHUB_REPOSITORY" --label "testing" --title "ALKiln tests failed"
|
|
@@ -13,8 +13,8 @@ on:
|
|
|
13
13
|
jobs:
|
|
14
14
|
|
|
15
15
|
puppeteer-tests:
|
|
16
|
-
#### Developer note: If
|
|
17
|
-
#### `
|
|
16
|
+
#### Developer note: If you have both `push` and `pull_request` in the
|
|
17
|
+
#### `on` section (above), this avoids running double tests when someone
|
|
18
18
|
#### editing the repository makes a pull request
|
|
19
19
|
# 1: Trigger job on push or dispatch from this repository
|
|
20
20
|
# 2: Trigger job on pull request from a fork
|
|
@@ -55,18 +55,13 @@ jobs:
|
|
|
55
55
|
# Place the root directory in this branch to access relative paths to action files
|
|
56
56
|
- uses: actions/checkout@v4
|
|
57
57
|
|
|
58
|
-
- name: Pause between triggers
|
|
59
|
-
if: (github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork)
|
|
60
|
-
shell: bash
|
|
61
|
-
run: sleep 60
|
|
62
|
-
|
|
63
58
|
#### Developer note: you'll need to replace `uses: ./`
|
|
64
59
|
#### with the path to ALKiln’s repository name and branch name. For example,
|
|
65
60
|
#### for version 5, use the branch name `v5`: `uses: suffolkLITLab/ALKiln@v5`
|
|
66
61
|
- uses: ./
|
|
67
62
|
#### Developer note: You can probably leave this out
|
|
68
63
|
env:
|
|
69
|
-
ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || 'not @
|
|
64
|
+
ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || '(not @error) and (not @temp_error)' }}"
|
|
70
65
|
with:
|
|
71
66
|
#### Developer note: Required ALKiln inputs. You can read more at
|
|
72
67
|
#### https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#githubNyou-inputs
|
|
@@ -77,6 +72,6 @@ jobs:
|
|
|
77
72
|
# Internal: docassemble changes sometimes break @a11y tests. That is
|
|
78
73
|
# a da problem, not ours. We will trigger these tests manually when we
|
|
79
74
|
# want to check up on this.
|
|
80
|
-
ALKILN_TAG_EXPRESSION: $ALKILN_TAG_EXPRESSION
|
|
75
|
+
ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
|
|
81
76
|
#### Developer note: You can probably leave this out
|
|
82
|
-
|
|
77
|
+
ALKILN_VERSION: delete
|
package/CHANGELOG.md
CHANGED
|
@@ -47,7 +47,41 @@ Format:
|
|
|
47
47
|
|
|
48
48
|
### Changed
|
|
49
49
|
|
|
50
|
-
-
|
|
50
|
+
- Update links in logs to new Suffolk docs pages
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- Now delete interviews upon interview completion
|
|
55
|
+
|
|
56
|
+
### Internal
|
|
57
|
+
|
|
58
|
+
- Test deleting interviews upon interview completion
|
|
59
|
+
- Added @temp_error to neutralize tests that are getting undesired errors right now, but which we anticipate being fixed soon. We want to ignore them for a short while. For example, an upstream change temporarily breaking some tests.
|
|
60
|
+
|
|
61
|
+
## [5.12.0] - 2024-06-06
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- New file with just failure results, including cucumber results when possible. It accumulates Scenario info as the tests run. Even if a test run stops early, there will still be some results.
|
|
66
|
+
- New output from ALKiln action - `PATH_TO_ARTIFACTS`. The value is `""` or the path to the folder that contains the output of the test run, if the test run actually happened. Sometimes this folder doesn't get created. For example, if there was a typo in one or more `.feature` file. For example, `"alkiln-2024-06-03 at 02h29m44sUTC"`.
|
|
67
|
+
- New outputs from the ALKiln action - `PATH_TO_REPORT_FILE`, `PATH_TO_UNEXPECTED_RESULTS_FILE`, and `PATH_TO_DEBUG_LOG_FILE`. For example, `"alkiln-2024-06-03 at 02h29m44sUTC/report.txt"`. Authors can choose to use these as they wish. For example, they can put the contents of these files in issues they create from their workflow. Closes [#701](https://github.com/SuffolkLITLab/ALKiln/issues/701).
|
|
68
|
+
- Added cucumber output to report.txt.
|
|
69
|
+
- Added more explanation for the logs that appear when test runs error in a way that prevents debug_log.txt and such files from being created. We will watch for feedback about the error data itself. It may still be too confusing.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- report.txt accumulates over time instead of being created all at once at the end. Even if a test run stops early, there will still be some results.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- Tag expression not being parsed correctly. Surrounded `ALKILN_TAG_EXPRESSION` with `"`.
|
|
78
|
+
|
|
79
|
+
### Internal
|
|
80
|
+
|
|
81
|
+
- Download ALKiln's artifacts back into the running job.
|
|
82
|
+
- Removed our own workflow's pause between triggers.
|
|
83
|
+
- Improved our use of ALKIN_TAG_EXPRESSION env var.
|
|
84
|
+
- Exclude tests that we are aware fail. We will test those manually.
|
|
51
85
|
|
|
52
86
|
## [5.11.2] - 2024-05-17
|
|
53
87
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ https://github.com/18F/open-source-guide/blob/18f-pages/pages/making-readmes-rea
|
|
|
12
12
|
|
|
13
13
|
## Use ALKiln
|
|
14
14
|
|
|
15
|
-
Read about how to use this framework in
|
|
15
|
+
Read about how to use [this framework in Assembly Line's documentation](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/intro).
|
|
16
16
|
|
|
17
17
|
If you have questions or need to report a bug, [start a new issue](https://github.com/SuffolkLITLab/ALKiln/issues/new).
|
|
18
18
|
|
package/action.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: "ALKiln: Automated docassemble tests"
|
|
2
|
-
description: "Automatically test any docassemble interview in a any branch on your GitHub repository whenever you commit or push. See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
2
|
+
description: "Automatically test any docassemble interview in a any branch on your GitHub repository whenever you commit or push. See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/setup/#types."
|
|
3
3
|
|
|
4
4
|
inputs:
|
|
5
5
|
SERVER_URL:
|
|
@@ -34,6 +34,20 @@ inputs:
|
|
|
34
34
|
required: false
|
|
35
35
|
default: "0.0.17"
|
|
36
36
|
|
|
37
|
+
outputs:
|
|
38
|
+
PATH_TO_ARTIFACTS:
|
|
39
|
+
description: 'An empty string ("") or the path to the artifacts folder that contains the test run output. Remember to check for the existence of the folder before using it.'
|
|
40
|
+
value: ${{ steps.alkiln_outputs.outputs.PATH_TO_ARTIFACTS }}
|
|
41
|
+
PATH_TO_REPORT_FILE:
|
|
42
|
+
description: 'An empty string ("") or the path to report.txt. Remember to check for the existence of the file before using it.'
|
|
43
|
+
value: ${{ steps.alkiln_outputs.outputs.PATH_TO_REPORT_FILE }}
|
|
44
|
+
PATH_TO_UNEXPECTED_RESULTS_FILE:
|
|
45
|
+
description: 'An empty string ("") or the path to unexpected_results.txt. Remember to check for the existence of the file before using it.'
|
|
46
|
+
value: ${{ steps.alkiln_outputs.outputs.PATH_TO_UNEXPECTED_RESULTS_FILE }}
|
|
47
|
+
PATH_TO_DEBUG_LOG_FILE:
|
|
48
|
+
description: 'An empty string ("") or the path to debug_log.txt. Remember to check for the existence of the file before using it.'
|
|
49
|
+
value: ${{ steps.alkiln_outputs.outputs.PATH_TO_DEBUG_LOG_FILE }}
|
|
50
|
+
|
|
37
51
|
runs:
|
|
38
52
|
using: "composite"
|
|
39
53
|
steps:
|
|
@@ -102,6 +116,7 @@ runs:
|
|
|
102
116
|
if: ${{ inputs.INSTALL_METHOD == 'playground' && failure() }}
|
|
103
117
|
shell: bash
|
|
104
118
|
run: |
|
|
119
|
+
# 😞 ALK0028 ERROR: Unable to create a Project
|
|
105
120
|
echo -e "\n\n====\n😞 ALK0028 ERROR: Unable to create a Project on your server's testing account or pull your package into it. Check the messages above this line.\n\n"
|
|
106
121
|
|
|
107
122
|
# Server installations - find folders and save session vars
|
|
@@ -121,7 +136,7 @@ runs:
|
|
|
121
136
|
env:
|
|
122
137
|
ALKILN_TAG_EXPRESSION: ${{ inputs.ALKILN_TAG_EXPRESSION || github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
|
|
123
138
|
shell: bash
|
|
124
|
-
run: alkiln-run $ALKILN_TAG_EXPRESSION
|
|
139
|
+
run: alkiln-run "$ALKILN_TAG_EXPRESSION"
|
|
125
140
|
|
|
126
141
|
# on playground, delete project
|
|
127
142
|
- name: "💡 ALK0032 INFO: Delete the Project from the Playground"
|
|
@@ -137,6 +152,64 @@ runs:
|
|
|
137
152
|
name: ${{ env.ARTIFACT_NAME }}
|
|
138
153
|
path: ./alkiln-*
|
|
139
154
|
|
|
155
|
+
# Download artifacts folder internally to create action output
|
|
156
|
+
- name: "💡 ALK0183 INFO: Get uploaded artifacts folder"
|
|
157
|
+
if: ${{ always() }}
|
|
158
|
+
uses: actions/download-artifact@v4
|
|
159
|
+
with:
|
|
160
|
+
name: ${{ env.ARTIFACT_NAME }}
|
|
161
|
+
|
|
162
|
+
- name: "💡 ALK0184 INFO: Prepare ALKiln output file paths"
|
|
163
|
+
if: ${{ always() }}
|
|
164
|
+
id: alkiln_outputs
|
|
165
|
+
shell: bash
|
|
166
|
+
run: |
|
|
167
|
+
# 💡 ALK0184 INFO: Prepare ALKiln output file paths
|
|
168
|
+
|
|
169
|
+
FOLDER=$(ls -d */ | grep -E '^alkiln-*' || true)
|
|
170
|
+
|
|
171
|
+
echo "Test results artifacts folder is $FOLDER"
|
|
172
|
+
|
|
173
|
+
if [[ "$FOLDER" = "" ]]; then
|
|
174
|
+
echo "Artifacts folder is missing. The path is an empty string: '$FOLDER'"
|
|
175
|
+
|
|
176
|
+
else
|
|
177
|
+
REPORT_PATH="${FOLDER}report.txt"
|
|
178
|
+
UNEXPECTED_RESULTS_PATH="${FOLDER}unexpected_results.txt"
|
|
179
|
+
DEBUG_LOG_PATH="${FOLDER}debug_log.txt"
|
|
180
|
+
|
|
181
|
+
if [ -f "$REPORT_PATH" ]; then
|
|
182
|
+
echo "$REPORT_PATH exists"
|
|
183
|
+
else
|
|
184
|
+
echo "$REPORT_PATH is missing"
|
|
185
|
+
REPORT_PATH=""
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
if [ -f "$UNEXPECTED_RESULTS_PATH" ]; then
|
|
189
|
+
echo "$UNEXPECTED_RESULTS_PATH exists"
|
|
190
|
+
else
|
|
191
|
+
echo "$UNEXPECTED_RESULTS_PATH is missing"
|
|
192
|
+
UNEXPECTED_RESULTS_PATH=""
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
if [ -f "$DEBUG_LOG_PATH" ]; then
|
|
196
|
+
echo "$DEBUG_LOG_PATH exists"
|
|
197
|
+
else
|
|
198
|
+
echo "$DEBUG_LOG_PATH is missing"
|
|
199
|
+
DEBUG_LOG_PATH=""
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
echo "PATH_TO_ARTIFACTS=$FOLDER" >> "$GITHUB_OUTPUT"
|
|
205
|
+
echo "PATH_TO_REPORT_FILE=$REPORT_PATH" >> "$GITHUB_OUTPUT"
|
|
206
|
+
echo "PATH_TO_UNEXPECTED_RESULTS_FILE=$UNEXPECTED_RESULTS_PATH" >> "$GITHUB_OUTPUT"
|
|
207
|
+
echo "PATH_TO_DEBUG_LOG_FILE=$DEBUG_LOG_PATH" >> "$GITHUB_OUTPUT"
|
|
208
|
+
|
|
209
|
+
# Avoid creating output from text in files
|
|
210
|
+
# Cannot properly create outputs with multi-line text. Collapses onto one line.
|
|
211
|
+
|
|
140
212
|
- shell: bash
|
|
213
|
+
if: ${{ always() }}
|
|
141
214
|
run: |
|
|
142
215
|
echo "💡 ALK0034 INFO: ALkiln finished running tests"
|
|
@@ -109,12 +109,14 @@ runs:
|
|
|
109
109
|
if: ${{ env.OUTPUT_DOCKER == 'false' }}
|
|
110
110
|
shell: bash
|
|
111
111
|
run: |
|
|
112
|
-
|
|
112
|
+
# 💡 ALK0007 INFO: ALKiln will now hide docker output
|
|
113
|
+
echo -e '===\n💡 ALK0007 INFO: ALKiln will now hide docker output to avoid possibly showing details about your config. If you want to get output to see more of what is going on, use "with:" and set the input "SHOW_DOCKER_OUTPUT" to "true". This output will only be visible to those who can already see the action output, like people with admin or write permissions.\n==='
|
|
113
114
|
- name: "💡 ALK0008 INFO: ALKiln will show docker output"
|
|
114
115
|
if: ${{ env.OUTPUT_DOCKER == 'true' }}
|
|
115
116
|
shell: bash
|
|
116
117
|
run: |
|
|
117
|
-
|
|
118
|
+
# 💡 ALK0008 INFO: ALKiln will show docker output
|
|
119
|
+
echo -e '===\n💡 ALK0008 INFO: ALKiln will show docker output. ALKiln cannot control this output. If you want to prevent output, use "with:" and set the input "SHOW_DOCKER_OUTPUT" to "false". This output will only be visible to those who can already see the action output, like people with admin or write permissions.\n==='
|
|
118
120
|
|
|
119
121
|
# TODO: Reduce these to 1 block by putting ` > /dev/null 2>&1` into an env var
|
|
120
122
|
# No docker output
|
|
@@ -156,6 +158,7 @@ runs:
|
|
|
156
158
|
if: ${{ env.OUTPUT_DOCKER == 'false' }}
|
|
157
159
|
shell: bash
|
|
158
160
|
run: |
|
|
161
|
+
# 💡 ALK0011 INFO: The following messages are not docker output
|
|
159
162
|
echo "💡 ALK0011 INFO: The following messages are not docker output, just ALKiln waiting for the server to start"
|
|
160
163
|
|
|
161
164
|
- name: "💡 ALK0012 INFO: Waiting for server to start"
|
|
@@ -245,4 +248,3 @@ runs:
|
|
|
245
248
|
if: ${{ failure() }}
|
|
246
249
|
run: |
|
|
247
250
|
echo "🤕 ALK0022 ERROR: Something went wrong. See above for more details."
|
|
248
|
-
|
package/docs/story_tables.md
CHANGED
|
@@ -4,7 +4,7 @@ The Story Table is a very flexible tool. It lets the author answer interview (fo
|
|
|
4
4
|
|
|
5
5
|
When the robot arrives on each interview page, it finds all the fields on the page and loops through the whole Story Table trying to match rows with fields.
|
|
6
6
|
|
|
7
|
-
Here's some [documentation on the use of Story Table rows](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln
|
|
7
|
+
Here's some [documentation on the use of Story Table rows](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#story-table).
|
|
8
8
|
|
|
9
9
|
## Tracking use of rows
|
|
10
10
|
|
|
@@ -31,7 +31,7 @@ da.get_dev_id = async function ( timeout ) {
|
|
|
31
31
|
}; // Ends da.get_dev_id()
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_project(
|
|
34
|
+
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_project()'
|
|
35
35
|
da.delete_project = async function ( timeout ) {
|
|
36
36
|
/* Delete a project on the user's da server. */
|
|
37
37
|
let options = {
|
|
@@ -53,6 +53,51 @@ da.delete_project = async function ( timeout ) {
|
|
|
53
53
|
}; // Ends da.delete_project()
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").delete_interview(...)'
|
|
57
|
+
da.delete_interview = async function ( timeout, { uid = null }) {
|
|
58
|
+
/* Delete an interview with the given session id. */
|
|
59
|
+
let options = {
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
headers: { 'X-API-Key': session_vars.get_dev_api_key() },
|
|
62
|
+
// Can't use `api/session` that uses the API key to identify the user's
|
|
63
|
+
// interview sessions. The test may have signed in with an account
|
|
64
|
+
// that's different than the ALKiln dev account that sends this request.
|
|
65
|
+
url: `${ session_vars.get_da_server_url() }/api/interviews`,
|
|
66
|
+
params: { session: uid },
|
|
67
|
+
timeout: timeout,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return await axios.request( options );
|
|
72
|
+
} catch (error) {
|
|
73
|
+
let response = error.response || {data: null}
|
|
74
|
+
throw {...error.toJSON(), data: response.data};
|
|
75
|
+
}
|
|
76
|
+
}; // Ends da.delete_interview()
|
|
77
|
+
|
|
78
|
+
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").get_interview(...)'
|
|
79
|
+
da.get_interview = async function ( timeout, { uid = null }) {
|
|
80
|
+
/* Get a specific interview. */
|
|
81
|
+
let options = {
|
|
82
|
+
method: 'GET',
|
|
83
|
+
headers: { 'X-API-Key': session_vars.get_dev_api_key() },
|
|
84
|
+
// Can't use `/api/user/interviews` that uses the API key to identify the user's
|
|
85
|
+
// interview sessions. The test may have signed in with an account
|
|
86
|
+
// that's different than the ALKiln dev account that sends this request.
|
|
87
|
+
url: `${ session_vars.get_da_server_url() }/api/interviews`,
|
|
88
|
+
params: { session: uid },
|
|
89
|
+
timeout: timeout,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
return await axios.request( options );
|
|
94
|
+
} catch ( error ) {
|
|
95
|
+
let response = error.response || { data: null }
|
|
96
|
+
throw { ...error.toJSON(), data: response.data };
|
|
97
|
+
}
|
|
98
|
+
}; // Ends da.get_interview()
|
|
99
|
+
|
|
100
|
+
|
|
56
101
|
// node -e 'require("./lib/docassemble/docassemble_api_REST.js").__delete_projects_starting_with( "testingdocassembleALAutomatedTestingTestsmain", 1, 1 )'
|
|
57
102
|
da.__delete_projects_starting_with = async function ( base_name, starting_num=1, ending_num ) {
|
|
58
103
|
/** FOR DEVELOPMENT ONLY. Deletes a set of Projects that start with a shared
|
|
@@ -15,6 +15,9 @@ const time = require('../utils/time');
|
|
|
15
15
|
// to specific functions, which can run in a very small
|
|
16
16
|
// window.
|
|
17
17
|
|
|
18
|
+
// TODO: Move get_dev_id() and such into here instead of in
|
|
19
|
+
// docassemble_REST - args should be the same vals as the request expects
|
|
20
|
+
|
|
18
21
|
/** Notes:
|
|
19
22
|
* - Checking for a busy server is vulnerable to race conditions, but
|
|
20
23
|
* there's nothing we can do about that.
|
|
@@ -180,7 +183,7 @@ da_i.delete_project = async function ( delete_options ) {
|
|
|
180
183
|
* for a timeout? */
|
|
181
184
|
|
|
182
185
|
// Defaults
|
|
183
|
-
let { timeout } = delete_options ||
|
|
186
|
+
let { timeout } = delete_options || { timeout: DEFAULT_MAX_REQUEST_MS };
|
|
184
187
|
|
|
185
188
|
let project_name = session_vars.get_project_name()
|
|
186
189
|
log.info({ type: `da api`, code: `ALK0170`, pre: `Trying to delete Project ${ project_name }` });
|
|
@@ -196,3 +199,88 @@ da_i.delete_project = async function ( delete_options ) {
|
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
}; // Ends da_i.delete_project()
|
|
202
|
+
|
|
203
|
+
// node -e 'require("./lib/docassemble/docassemble_api_interface.js").quietly_try_to_delete_interviews(...)'
|
|
204
|
+
da_i.quietly_try_to_delete_interviews = async function ({ interviews = {}, options = {} }) {
|
|
205
|
+
/** Delete a user's interview. Do we need to watch for a timeout? */
|
|
206
|
+
|
|
207
|
+
// Defaults
|
|
208
|
+
let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
|
|
209
|
+
|
|
210
|
+
for ( let id in interviews ) {
|
|
211
|
+
let interview_vars = interviews[ id ];
|
|
212
|
+
if ( interview_vars && interview_vars.uid ) {
|
|
213
|
+
try {
|
|
214
|
+
log.debug({ type: `da api info`, code: `ALK0188`, pre: `Trying to delete the interview with the id ${ interview_vars.uid }.` });
|
|
215
|
+
|
|
216
|
+
// Actual work:
|
|
217
|
+
let response = await _da_REST.delete_interview( timeout, interview_vars );
|
|
218
|
+
log.debug({ type: `da api info`, code: `ALK0189`,
|
|
219
|
+
pre: `No error when trying to delete interview ${ interview_vars.uid } at ${ interview_vars.i }.`
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
} catch ( error ) {
|
|
223
|
+
error.interview_vars = interview_vars;
|
|
224
|
+
let msg = `Error when deleting the interview. See https://docassemble.org/docs/api.html#session_delete.`
|
|
225
|
+
log.debug({ type: `da api info`, code: `ALK0190`, pre: msg, data: error });
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
log.debug({ type: `da api info`, code: `ALK0191`, pre: `There is no interview to delete.` });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}; // Ends da_i.quietly_try_to_delete_interviews()
|
|
233
|
+
|
|
234
|
+
// node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_interviews(...)'
|
|
235
|
+
da_i.get_interviews = async function ({ interviews = {}, options = {} }) {
|
|
236
|
+
/** Return an object of interviews that exist on the server
|
|
237
|
+
* that match the given interviews. Keys are the session ids. */
|
|
238
|
+
// Defaults
|
|
239
|
+
let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
|
|
240
|
+
|
|
241
|
+
let fetched_interviews = {};
|
|
242
|
+
if ( interviews ) {
|
|
243
|
+
for ( let id in interviews ) {
|
|
244
|
+
let interview_vars = interviews[ id ];
|
|
245
|
+
let fetched = await da_i.get_interview({ interview_vars });
|
|
246
|
+
if ( fetched && fetched.session ) {
|
|
247
|
+
fetched_interviews[ fetched.session ] = fetched;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} // ends if interviews
|
|
251
|
+
|
|
252
|
+
return fetched_interviews;
|
|
253
|
+
}; // Ends da_i.get_interviews()
|
|
254
|
+
|
|
255
|
+
// node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_interview(...)'
|
|
256
|
+
da_i.get_interview = async function ({ interview_vars, options = {} }) {
|
|
257
|
+
/** Try to get an interview from the server that matches the given interview data. */
|
|
258
|
+
|
|
259
|
+
// Defaults
|
|
260
|
+
let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
|
|
261
|
+
|
|
262
|
+
if ( interview_vars && interview_vars.uid ) {
|
|
263
|
+
try {
|
|
264
|
+
log.debug({ type: `da api info`, code: `ALK0192`, pre: `Trying to get the interview with the id ${ interview_vars.uid }.` });
|
|
265
|
+
|
|
266
|
+
// Actual work:
|
|
267
|
+
let response = await _da_REST.get_interview( timeout, interview_vars );
|
|
268
|
+
if ( response && response.data && response.data.items && response.data.items[0] && response.data.items[0].session ) {
|
|
269
|
+
log.debug({ type: `da api info`, code: `ALK0193`, pre: `Status ${ response.status }. Got ${ response.data.items[0].session }.`});
|
|
270
|
+
return response.data.items[0];
|
|
271
|
+
} else {
|
|
272
|
+
log.debug({ type: `da api info`, code: `ALK0194`, pre: `Status ${ response.status }. Cannot find ${ interview_vars.uid }`});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} catch ( error ) {
|
|
276
|
+
error.interview_vars = interview_vars;
|
|
277
|
+
let msg = `${ error.status } "${ error.statusText }" Ran into an error when getting the interview ${ interview_vars.uid }. See https://docassemble.org/docs/api.html#interviews.`
|
|
278
|
+
log.debug({ type: `da api info`, code: `ALK0195`, pre: msg, data: error });
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
log.debug({ type: `da api info`, code: `ALK0196`, pre: `There is no interview to get.` });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null;
|
|
285
|
+
|
|
286
|
+
}; // Ends da_i.get_interview()
|
package/lib/run_cucumber.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const cucumber_api = require('@cucumber/cucumber/api');
|
|
5
5
|
|
|
6
|
-
const log = require('./utils/log');
|
|
6
|
+
const log = require('./utils/log'); // These always log in the console
|
|
7
7
|
const session_vars = require('./utils/session_vars');
|
|
8
8
|
|
|
9
|
+
const SUMMARY_FILENAME = `cucumber-report.txt`;
|
|
10
|
+
|
|
11
|
+
|
|
9
12
|
function build_config( command_args ) {
|
|
10
13
|
// Turn all the paths to the sources folders into paths to the feature files
|
|
11
14
|
let sources_paths = session_vars.get_sources_paths();
|
|
@@ -17,14 +20,17 @@ function build_config( command_args ) {
|
|
|
17
20
|
const config = {
|
|
18
21
|
publishQuiet: true,
|
|
19
22
|
require: `./lib/index.js`,
|
|
20
|
-
format: [`progress`, `summary
|
|
23
|
+
format: [`progress`, `summary:${ SUMMARY_FILENAME }`],
|
|
21
24
|
paths: feature_paths,
|
|
22
25
|
retry: 1,
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// Normalize tag expressions, if there are any
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
// Joining and splitting handles both tags surrounded with `""` or those without `""`
|
|
30
|
+
let tags = command_args[`_`].join(` `).split(` `);
|
|
31
|
+
// If there was no tag expression, `tags` is ['']. Remove that item.
|
|
32
|
+
tags = tags.filter(function(entry) { return entry !== ""; })
|
|
33
|
+
if ( tags.length > 0 ) {
|
|
28
34
|
let joined_tags = '';
|
|
29
35
|
// Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
|
|
30
36
|
// If we get a list of only tags, then "or" them all together
|
|
@@ -105,19 +111,22 @@ function build_config( command_args ) {
|
|
|
105
111
|
|
|
106
112
|
const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
|
|
107
113
|
if ( success ) {
|
|
108
|
-
log.
|
|
114
|
+
log.success({ type: `run`, code: `ALK0054`, pre: `Tests succeeded!` });
|
|
109
115
|
} else {
|
|
110
|
-
log.error({ type: `run`, code: `ALK0055`, pre: `
|
|
116
|
+
log.error({ type: `run`, code: `ALK0055`, pre: `The test run ran into something unexpected. Look above for more details.` });
|
|
111
117
|
}
|
|
112
|
-
|
|
118
|
+
// Custom exit code. May overlap with other systems.
|
|
119
|
+
// Best info I've found via stack overflow: https://tldp.org/LDP/abs/html/exitcodes.html
|
|
120
|
+
const exitCode = success ? 0 : 50;
|
|
113
121
|
process.exitCode = exitCode;
|
|
114
122
|
|
|
115
123
|
// This is hardcoded information from `provide_config` above.
|
|
116
124
|
// This code isn't used for anything other than detecting and removing color codes.
|
|
117
|
-
let summary_file_name =
|
|
125
|
+
let summary_file_name = SUMMARY_FILENAME;
|
|
118
126
|
fs.readFile(summary_file_name, 'utf8', (err, text) => {
|
|
119
127
|
if (err) {
|
|
120
|
-
|
|
128
|
+
// Something else will probably show an error that's more relevant
|
|
129
|
+
log.info({ type: `run`, code: `ALK0056`, pre: `Failed to read cucumber report`, data: err });
|
|
121
130
|
return;
|
|
122
131
|
}
|
|
123
132
|
|
|
@@ -127,6 +136,18 @@ function build_config( command_args ) {
|
|
|
127
136
|
|
|
128
137
|
// Put it in the correct file in the correct folder
|
|
129
138
|
let artifacts_path = session_vars.get_artifacts_path_name();
|
|
130
|
-
|
|
139
|
+
try {
|
|
140
|
+
fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, `\n\n${text}`);
|
|
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
|
|
146
|
+
if ( exitCode === 50 ) {
|
|
147
|
+
fs.appendFileSync( `${ artifacts_path }/${ log.unexpected_filename }`, `\n\n${ text }` );
|
|
148
|
+
}
|
|
149
|
+
} catch ( error ) {
|
|
150
|
+
log.info({ type: `run`, code: `ALK0181`, 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.unexpected_filename }.`, data: error });
|
|
151
|
+
}
|
|
131
152
|
});
|
|
132
153
|
})();
|
package/lib/scope.js
CHANGED
|
@@ -627,7 +627,7 @@ module.exports = {
|
|
|
627
627
|
interview_url = `${ base_url }${ file_name }`;
|
|
628
628
|
}
|
|
629
629
|
|
|
630
|
-
reports.addToReport(scope,{
|
|
630
|
+
reports.addToReport(scope, {
|
|
631
631
|
code: `ALK0113`,
|
|
632
632
|
type: `row info`,
|
|
633
633
|
value: `Trying to load the interview at "${ interview_url }"`
|
|
@@ -638,7 +638,17 @@ module.exports = {
|
|
|
638
638
|
// TODO: implement and use scope.handle_possible_timeout()
|
|
639
639
|
try {
|
|
640
640
|
|
|
641
|
-
await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout })
|
|
641
|
+
await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout });
|
|
642
|
+
// Get interview session values, if possible, so we can delete the session later
|
|
643
|
+
let interview_vars = await scope.page.evaluate(async function() {
|
|
644
|
+
try {
|
|
645
|
+
// won't exist on non-da interview pages
|
|
646
|
+
return await get_interview_variables();
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
scope.set_interview_session_data( scope, { vars: interview_vars });
|
|
642
652
|
|
|
643
653
|
} catch ( error ) {
|
|
644
654
|
|
|
@@ -682,6 +692,36 @@ module.exports = {
|
|
|
682
692
|
return scope.page;
|
|
683
693
|
}, // Ends scope.load()
|
|
684
694
|
|
|
695
|
+
set_interview_session_data: function ( scope, { vars = null }) {
|
|
696
|
+
/** Sets this interview's session data for this scenario if it doesn't
|
|
697
|
+
* already exist.
|
|
698
|
+
*
|
|
699
|
+
* @returns final_vars { object | null }
|
|
700
|
+
* @returns final_vars.i { str } File path of interview
|
|
701
|
+
* @returns final_vars.uid { str } UID of interview
|
|
702
|
+
* */
|
|
703
|
+
if ( vars && vars.i ) {
|
|
704
|
+
let scenario = scope.scenarios.get( scope.scenario_id );
|
|
705
|
+
if ( !scenario.interviews ) { scenario.interviews = {}; }
|
|
706
|
+
if ( !scenario.interviews[ vars.uid ] ) {
|
|
707
|
+
scenario.interviews[ vars.uid ] = vars;
|
|
708
|
+
log.debug({
|
|
709
|
+
code: `ALK0185`, type: `info`,
|
|
710
|
+
pre: `Saved new interview data. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
711
|
+
});
|
|
712
|
+
} else {
|
|
713
|
+
log.debug({
|
|
714
|
+
code: `ALK0186`, type: `info`,
|
|
715
|
+
pre: `Interview data already existed. File path: ${ vars.i }. Session id: ${ vars.uid }.`
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return vars;
|
|
719
|
+
} else {
|
|
720
|
+
log.debug({ code: `ALK0187`, type: `info`, pre: `Found no interview data.` });
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
}, // Ends scope.set_interview_session_data()
|
|
724
|
+
|
|
685
725
|
getLoadData: async function ( scope ) {
|
|
686
726
|
/* Return elements that could load on the page, like #daMainQuestion
|
|
687
727
|
* and #da-retry. If there's a system error on the page, return its main text.
|
|
@@ -771,7 +811,7 @@ module.exports = {
|
|
|
771
811
|
if ( row_raw.length > 3 ) { more_or_less = `less`; }
|
|
772
812
|
let bad_table_msg = `Your Story Table should have ${ more_or_less } than`
|
|
773
813
|
+ ` ${ row_raw.length } columns. Take another look at Story Table documentation at`
|
|
774
|
-
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln
|
|
814
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#story-table.`
|
|
775
815
|
reports.addToReport( scope, { type: `error`, code: `ALK0120`, value: bad_table_msg})
|
|
776
816
|
throw Error(bad_table_msg);
|
|
777
817
|
}
|
|
@@ -798,7 +838,7 @@ module.exports = {
|
|
|
798
838
|
let not_in_env_msg = `ALKiln could not find the GitHub secret "${ row.flags.secret_name }".`
|
|
799
839
|
+ ` Check for a typo and check your workflow file.`
|
|
800
840
|
+ ` Read more about using GitHub secrets in your workflow file at`
|
|
801
|
-
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln
|
|
841
|
+
+ ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#secrets.`
|
|
802
842
|
reports.addToReport( scope, { type: `error`, code: `ALK0121`, value: not_in_env_msg });
|
|
803
843
|
throw ReferenceError(not_in_env_msg);
|
|
804
844
|
}
|
|
@@ -1317,6 +1357,7 @@ module.exports = {
|
|
|
1317
1357
|
* setting fields, and an object for a DOM field, returns list of the
|
|
1318
1358
|
* rows that match the DOM field. That list of story table rows may be empty.
|
|
1319
1359
|
*
|
|
1360
|
+
* Using a `trigger` column is deprecated, but still used in many tests in the wild.
|
|
1320
1361
|
* For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger
|
|
1321
1362
|
*
|
|
1322
1363
|
* @param var_data {arr} - fields and the values they can be set to
|
|
@@ -1411,9 +1452,13 @@ module.exports = {
|
|
|
1411
1452
|
|
|
1412
1453
|
// field/page trigger name is needed but is missing.
|
|
1413
1454
|
if ( !page_trigger_name ) {
|
|
1414
|
-
let page_trigger_warning = `
|
|
1415
|
-
+ `
|
|
1416
|
-
+ `
|
|
1455
|
+
let page_trigger_warning = `You are using a 3-column Story Table. If possible, you should `
|
|
1456
|
+
+ `think about switching to the newer, easier, 2-column Story Table. That will probably `
|
|
1457
|
+
+ `get rid of this warning. Read more at `
|
|
1458
|
+
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/trouble#deprecated-story-tables. `
|
|
1459
|
+
+ `If you still want to use this 3-column format, here is more info about the warning: `
|
|
1460
|
+
+ `A field on this page uses an index variable or a generic object in a 3-column `
|
|
1461
|
+
+ `Story Table, but the page is missing the ALKiln "trigger" variable's name. See `
|
|
1417
1462
|
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#a-missing-trigger-variable.`;
|
|
1418
1463
|
reports.addToReport(scope, { type: `warning`, code: `ALK0127`, value: page_trigger_warning });
|
|
1419
1464
|
}
|
|
@@ -1430,7 +1475,7 @@ module.exports = {
|
|
|
1430
1475
|
|
|
1431
1476
|
// First, make the row data more readable to humans.
|
|
1432
1477
|
let row_as_story_table = reports.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
|
|
1433
|
-
let row_trigger_warning = `This Story Table row needs a value in the "trigger" `
|
|
1478
|
+
let row_trigger_warning = `This 3-column Story Table row needs a value in the "trigger" `
|
|
1434
1479
|
+ `column, but it is empty. The test might get unexpected results. See `
|
|
1435
1480
|
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1436
1481
|
+ `\nThe rows:\n${ row_as_story_table }`;
|
|
@@ -1467,7 +1512,7 @@ module.exports = {
|
|
|
1467
1512
|
|
|
1468
1513
|
let all_trigger_names_conflict_msg = `${ rows_that_match_field_names.length } `
|
|
1469
1514
|
+ `Story Table row(s) matched a field name, but didn't match a trigger `
|
|
1470
|
-
+ `variable.
|
|
1515
|
+
+ `variable. This 3-column Story Table might be missing a row or a trigger variable. `
|
|
1471
1516
|
+ `The test will have to ignore this field. See `
|
|
1472
1517
|
+ `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
|
|
1473
1518
|
+ `\nThe row(s) data:\n${ conflicts_as_story_table_rows.join( `\n` )}`
|
|
@@ -3228,8 +3273,7 @@ module.exports = {
|
|
|
3228
3273
|
*
|
|
3229
3274
|
* @param expected {array} Arr of strs that should appear in the report.
|
|
3230
3275
|
*/
|
|
3231
|
-
let
|
|
3232
|
-
let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
3276
|
+
let scenario = scope.report.get( scope.scenario_id );
|
|
3233
3277
|
let report = reports.getPrintableScenario( scenario );
|
|
3234
3278
|
let all_are_included = true;
|
|
3235
3279
|
for ( let one_expectation of expected ) {
|
|
@@ -3248,8 +3292,7 @@ module.exports = {
|
|
|
3248
3292
|
*
|
|
3249
3293
|
* @param not_expected {array} Arr of strs that should not appear in the report.
|
|
3250
3294
|
*/
|
|
3251
|
-
let
|
|
3252
|
-
let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
3295
|
+
let scenario = scope.report.get( scope.scenario_id );
|
|
3253
3296
|
let report = reports.getPrintableScenario( scenario );
|
|
3254
3297
|
let none_included = true;
|
|
3255
3298
|
for ( let one_expectation of not_expected ) {
|
|
@@ -3262,4 +3305,6 @@ module.exports = {
|
|
|
3262
3305
|
return none_included;
|
|
3263
3306
|
},
|
|
3264
3307
|
|
|
3308
|
+
// Test that all interviews have been deleted
|
|
3309
|
+
|
|
3265
3310
|
};
|
package/lib/steps.js
CHANGED
|
@@ -22,6 +22,7 @@ const log = require('./utils/log');
|
|
|
22
22
|
const session_vars = require('./utils/session_vars');
|
|
23
23
|
const files = require('./utils/files' );
|
|
24
24
|
const reports = require(`./utils/reports`);
|
|
25
|
+
const da_api = require('./docassemble/docassemble_api_interface');
|
|
25
26
|
|
|
26
27
|
/* Of Note:
|
|
27
28
|
- We're using `*=` for selectors because sometimes da text has funny characters in it that are hard to anticipate
|
|
@@ -83,6 +84,9 @@ let click_with = {
|
|
|
83
84
|
|
|
84
85
|
BeforeAll(async () => {
|
|
85
86
|
scope.showif_timeout = 600;
|
|
87
|
+
// TODO: Do these really need to be maps? As sets, they may make the output
|
|
88
|
+
// more predictable to the user, but they add complexity.
|
|
89
|
+
scope.scenarios = new Map();
|
|
86
90
|
scope.report = new Map();
|
|
87
91
|
|
|
88
92
|
// Start tracking whether server is up and running
|
|
@@ -93,7 +97,7 @@ BeforeAll(async () => {
|
|
|
93
97
|
|
|
94
98
|
// TODO: figure out how to test file and folder creation stuff,
|
|
95
99
|
// especially when setup is excluded from the flow.
|
|
96
|
-
|
|
100
|
+
|
|
97
101
|
// Make the folder to store all the files for this run of the tests
|
|
98
102
|
scope.paths.artifacts = files.make_artifacts_folder();
|
|
99
103
|
// Save its name in a file. `session_vars` can get it back later.
|
|
@@ -101,8 +105,11 @@ BeforeAll(async () => {
|
|
|
101
105
|
session_vars.save_artifacts_path_name( scope.paths.artifacts );
|
|
102
106
|
|
|
103
107
|
scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
|
|
108
|
+
scope.paths.failures = `${ scope.paths.artifacts }/${ log.unexpected_filename }`;
|
|
104
109
|
scope.paths.debug_log = `${ scope.paths.artifacts }/${ log.debug_log_file }`;
|
|
105
110
|
fs.writeFileSync( scope.paths.debug_log, "" );
|
|
111
|
+
|
|
112
|
+
reports.create( scope );
|
|
106
113
|
});
|
|
107
114
|
|
|
108
115
|
Before(async (scenario) => {
|
|
@@ -124,13 +131,15 @@ Before(async (scenario) => {
|
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
// Clean up all previously existing pages
|
|
127
|
-
for (const page of await scope.browser.pages()) {
|
|
134
|
+
for (const page of await scope.browser.pages()) {
|
|
128
135
|
await page.close();
|
|
129
136
|
}
|
|
130
137
|
// Make a new page
|
|
131
138
|
scope.page = await scope.browser.newPage()
|
|
132
139
|
|
|
133
140
|
scope.scenario_id = uuidv4();
|
|
141
|
+
scope.scenarios.set( scope.scenario_id, {} );
|
|
142
|
+
scope.scenarios.get( scope.scenario_id ).interviews = {};
|
|
134
143
|
scope.expected_status = null;
|
|
135
144
|
scope.expected_in_report = null;
|
|
136
145
|
scope.expected_not_in_report = null;
|
|
@@ -176,7 +185,7 @@ Given(/I start the interview at "([^"]+)"/, {timeout: -1}, async (file_name) =>
|
|
|
176
185
|
|
|
177
186
|
// Load the right interview page
|
|
178
187
|
await scope.load( scope, file_name );
|
|
179
|
-
|
|
188
|
+
|
|
180
189
|
// The page timeout is set in here too in case someone set a custom timeout
|
|
181
190
|
// just before a page was loaded for the Scenario. Move this to just after page creation?
|
|
182
191
|
// The function itself won't really timeout here, so we don't need to wrap it in
|
|
@@ -310,7 +319,7 @@ Given(/the max(?:imum)? sec(?:ond)?s for each step (?:in this scenario )?is (\d+
|
|
|
310
319
|
/* At any time, set a custom timeout for the scenario. */
|
|
311
320
|
// TODO: Add 1 second to this to allow other steps to not fail, like "I wait n seconds"
|
|
312
321
|
scope.timeout = parseInt( seconds ) * 1000;
|
|
313
|
-
|
|
322
|
+
|
|
314
323
|
// Cucumber timeouts are made custom for each step that needs it
|
|
315
324
|
|
|
316
325
|
// If there is no page yet, we've ensured custom timeout will still be set
|
|
@@ -650,7 +659,7 @@ Then('I should see the link to {string}', async ( url ) => { // √
|
|
|
650
659
|
let msg = `Cannot find a link to "${ url }".`;
|
|
651
660
|
if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0080`, value: msg }); }
|
|
652
661
|
expect(link, msg ).to.exist;
|
|
653
|
-
|
|
662
|
+
|
|
654
663
|
await scope.afterStep(scope);
|
|
655
664
|
});
|
|
656
665
|
|
|
@@ -659,7 +668,7 @@ Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) =>
|
|
|
659
668
|
let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
|
|
660
669
|
// TODO: Possibly add a check here for the link text, though note this
|
|
661
670
|
// Step is not currently supported.
|
|
662
|
-
|
|
671
|
+
|
|
663
672
|
let prop_obj = await link.getProperty('href');
|
|
664
673
|
let actual_url = await prop_obj.jsonValue();
|
|
665
674
|
|
|
@@ -753,7 +762,7 @@ Then(/I expect the baseline PDF "([^"]+)" and the new PDF "([^"]+)" to be the sa
|
|
|
753
762
|
* Variations:
|
|
754
763
|
* Then I expect the baseline PDF "simple-doc-Baseline.pdf" and the new PDF "simple-doc.pdf" to be the same
|
|
755
764
|
*/
|
|
756
|
-
|
|
765
|
+
|
|
757
766
|
return await wrapPromiseWithTimeout(
|
|
758
767
|
scope.steps.compare_pdfs(scope, {existing_pdf_path, new_pdf_path}),
|
|
759
768
|
scope.timeout
|
|
@@ -877,7 +886,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
877
886
|
|
|
878
887
|
// Set up timeout to stop infinite loops
|
|
879
888
|
let page_timeout_id = setTimeout(async function() {
|
|
880
|
-
|
|
889
|
+
|
|
881
890
|
// Make sure to only throw once
|
|
882
891
|
if ( has_thrown ) { return; }
|
|
883
892
|
else { has_thrown = true; }
|
|
@@ -1175,6 +1184,57 @@ Given(/the final Scenario status should be "([^"]+)"/i, async ( expected_status
|
|
|
1175
1184
|
scope.expected_status = expected_status.toUpperCase();
|
|
1176
1185
|
});
|
|
1177
1186
|
|
|
1187
|
+
Then(/of two interviews, I delete one __internal__/i, async () => {
|
|
1188
|
+
/** Test that an interview does get successfully deleted. This tests whether
|
|
1189
|
+
* the API code is working correctly.
|
|
1190
|
+
*/
|
|
1191
|
+
// Make sure we have two interviews in `scope.scenario.interviews` (two session ids)
|
|
1192
|
+
let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
|
|
1193
|
+
let internal_keys = Object.keys( interviews );
|
|
1194
|
+
expect(
|
|
1195
|
+
internal_keys.length,
|
|
1196
|
+
`Of 2 interview sessions, ALKiln only saved ${ internal_keys.length }`
|
|
1197
|
+
).to.equal( 2 );
|
|
1198
|
+
|
|
1199
|
+
// Check that both interviews exist on the server. 1 √ 2 √
|
|
1200
|
+
let existing_interviews = await da_api.get_interviews({ interviews });
|
|
1201
|
+
let existing_keys = Object.keys( existing_interviews );
|
|
1202
|
+
expect(
|
|
1203
|
+
existing_keys.length,
|
|
1204
|
+
`2 interviews should be on the server. Instead: ${ existing_keys.join(', ') }`
|
|
1205
|
+
).to.equal( 2 );
|
|
1206
|
+
|
|
1207
|
+
// Delete one. 1 x 2 √
|
|
1208
|
+
let to_delete = interviews[ internal_keys[ 0 ]];
|
|
1209
|
+
await da_api.quietly_try_to_delete_interviews({
|
|
1210
|
+
interviews: { [internal_keys[ 0 ]]: to_delete }
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
// Check that only one session exists on the server. This tests that
|
|
1214
|
+
// 1. An interview has been deleted and
|
|
1215
|
+
// 2. We are fetching interviews correctly because we are getting one
|
|
1216
|
+
// back (e.g. not passing null into the api function)
|
|
1217
|
+
// 1 x 2 √
|
|
1218
|
+
let after_deletion = await da_api.get_interviews({ interviews });
|
|
1219
|
+
let keys_after_deletion = Object.keys( after_deletion );
|
|
1220
|
+
expect(
|
|
1221
|
+
keys_after_deletion.length,
|
|
1222
|
+
`1 interviews should be on the server: ${ keys_after_deletion.join(', ') }`
|
|
1223
|
+
).to.equal( 1 )
|
|
1224
|
+
// In After(), the other session should get deleted
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
// Then(/there are no interview_vars saved __internal__/i, async () => {
|
|
1228
|
+
// /** Test that no interviews were saved internally */
|
|
1229
|
+
// // Check the number of interviews
|
|
1230
|
+
// let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
|
|
1231
|
+
// let keys = Object.keys( interviews );
|
|
1232
|
+
// expect(
|
|
1233
|
+
// keys.length,
|
|
1234
|
+
// `0 interviews should exist, but at least one does: ${ keys.join(', ') }`
|
|
1235
|
+
// ).to.equal( 0 );
|
|
1236
|
+
// });
|
|
1237
|
+
|
|
1178
1238
|
|
|
1179
1239
|
//#####################################
|
|
1180
1240
|
//#####################################
|
|
@@ -1202,17 +1262,17 @@ After(async function(scenario) {
|
|
|
1202
1262
|
}
|
|
1203
1263
|
|
|
1204
1264
|
if ( scope.failed_pdf_compares.length > 0) {
|
|
1205
|
-
let msg = scope.failed_pdf_compares.reduce((str, new_msg) => `${ str }\n
|
|
1265
|
+
let msg = scope.failed_pdf_compares.reduce((str, new_msg) => `${ str }\n―――\n${ new_msg }`)
|
|
1206
1266
|
changeable_test_status = `FAILED`;
|
|
1207
1267
|
reports.addToReport(scope, {
|
|
1208
1268
|
type: `error`,
|
|
1209
1269
|
code: `ALK0093`,
|
|
1210
|
-
value: `PDF comparison failed ${ scope.failed_pdf_compares.length } time(s)\n
|
|
1270
|
+
value: `PDF comparison failed ${ scope.failed_pdf_compares.length } time(s)\n―――\n${ msg }\n―――\n`
|
|
1211
1271
|
});
|
|
1212
1272
|
}
|
|
1213
1273
|
|
|
1214
1274
|
// Record status before internal report tests can interfere with values
|
|
1215
|
-
reports.
|
|
1275
|
+
reports.saveFinalScenarioData( scope, { status: changeable_test_status });
|
|
1216
1276
|
|
|
1217
1277
|
if (changeable_test_status !== `PASSED`) {
|
|
1218
1278
|
log.debug({
|
|
@@ -1256,11 +1316,11 @@ After(async function(scenario) {
|
|
|
1256
1316
|
} // ends if not passed
|
|
1257
1317
|
|
|
1258
1318
|
// Add report text to Scenario folder after everything has been added to the report
|
|
1259
|
-
let
|
|
1260
|
-
let scenario_report_obj = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
1319
|
+
let scenario_report_obj = scope.report.get( scope.scenario_id );
|
|
1261
1320
|
let report = reports.getPrintableScenario( scenario_report_obj );
|
|
1262
1321
|
// Save the report as in the Scenario folder
|
|
1263
1322
|
fs.writeFileSync( scope.paths.scenario_report, report );
|
|
1323
|
+
// TODO: Save any cucumber failure message for the Scenario here (instead of waiting till run_cucumber.js)
|
|
1264
1324
|
|
|
1265
1325
|
// ---------------- Check internal test results ----------------
|
|
1266
1326
|
|
|
@@ -1273,7 +1333,7 @@ After(async function(scenario) {
|
|
|
1273
1333
|
// Reset report values no matter what so they don't mess up future scenarios
|
|
1274
1334
|
scope.expected_in_report = null;
|
|
1275
1335
|
}
|
|
1276
|
-
|
|
1336
|
+
|
|
1277
1337
|
if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
|
|
1278
1338
|
let all_prohibited_strings_were_absent = await scope.reportDoesNotInclude(
|
|
1279
1339
|
scope,
|
|
@@ -1311,8 +1371,8 @@ After(async function(scenario) {
|
|
|
1311
1371
|
// it as failed too.
|
|
1312
1372
|
expected_status_was_met = false;
|
|
1313
1373
|
// Keep it general. Users may see this message, e.g. for a11y tests.
|
|
1314
|
-
let msg = `Test
|
|
1315
|
-
+ `
|
|
1374
|
+
let msg = `Test ${ changeable_test_status }. `
|
|
1375
|
+
+ `Expected status: ${scope.expected_status}. `
|
|
1316
1376
|
+ `See the report for details.`;
|
|
1317
1377
|
let code = `ALK0099`;
|
|
1318
1378
|
log.debug({ type: `error`, code: code, pre: msg, })
|
|
@@ -1354,7 +1414,7 @@ After(async function(scenario) {
|
|
|
1354
1414
|
// Catch a possible reload error.
|
|
1355
1415
|
try {
|
|
1356
1416
|
// puppeteer will ensure proper timeout.
|
|
1357
|
-
await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
|
|
1417
|
+
await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
|
|
1358
1418
|
|
|
1359
1419
|
} catch ( error ) {
|
|
1360
1420
|
signout_succeeded = false;
|
|
@@ -1369,7 +1429,7 @@ After(async function(scenario) {
|
|
|
1369
1429
|
code: code,
|
|
1370
1430
|
message: non_reload_report_msg,
|
|
1371
1431
|
}, error });
|
|
1372
|
-
} catch {
|
|
1432
|
+
} catch ( error ) {
|
|
1373
1433
|
signout_error_msg = `🤕 ${ code } ERROR: ${ non_reload_report_msg }`;
|
|
1374
1434
|
}
|
|
1375
1435
|
} else {
|
|
@@ -1384,10 +1444,34 @@ After(async function(scenario) {
|
|
|
1384
1444
|
|
|
1385
1445
|
} // ends if scope.page
|
|
1386
1446
|
|
|
1447
|
+
// Now that we're no longer on the interview page, one way or
|
|
1448
|
+
// another, try to delete the interviews created during the test
|
|
1449
|
+
try {
|
|
1450
|
+
let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
|
|
1451
|
+
await da_api.quietly_try_to_delete_interviews({ interviews });
|
|
1452
|
+
let existing_interviews = await da_api.get_interviews({ interviews });
|
|
1453
|
+
let existing_keys = Object.keys( existing_interviews );
|
|
1454
|
+
// Deleting interviews is nice, but not non-critical. Don't bother the
|
|
1455
|
+
// user with it. Don't want to cover up a more meaningful failure (like
|
|
1456
|
+
// going to the wrong file). If it becomes a problem we can track it down later.
|
|
1457
|
+
if ( existing_keys.length > 0 ) {
|
|
1458
|
+
log.debug({ type: `internal warning`, code: `ALK0197`,
|
|
1459
|
+
pre: `${ existing_keys.length } interviews were not deleted: ${ existing_keys.join( ', ' ) }`
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
} catch ( error ) {
|
|
1463
|
+
// Deleting interviews is nice, but not non-critical. Don't bother the
|
|
1464
|
+
// user with it. Don't want to cover up a more meaningful failure (like
|
|
1465
|
+
// going to the wrong file). If it becomes a problem we can track it down later.
|
|
1466
|
+
// Consider add "internal" to docs: "If you see this, make an issue in ALKiln with your debug/verbose log"
|
|
1467
|
+
log.debug({ type: `internal warning`, code: `ALK0198`, pre: `Error while cleaning up interviews.`, data: error });
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// Handle all the accumulated errors
|
|
1387
1471
|
let errors = []
|
|
1388
1472
|
if ( !expected_status_was_met ) { errors.push( expected_status_err_msg ); }
|
|
1389
1473
|
if ( !signout_succeeded ) { errors.push( signout_error_msg ); }
|
|
1390
|
-
if (
|
|
1474
|
+
if ( errors.length > 0 ) {
|
|
1391
1475
|
throw new Error( `${errors.join( errors, '\n')}` );
|
|
1392
1476
|
}
|
|
1393
1477
|
|
|
@@ -1405,17 +1489,14 @@ After(async function(scenario) {
|
|
|
1405
1489
|
AfterAll(async function() {
|
|
1406
1490
|
// Stop collecting server response statuses
|
|
1407
1491
|
scope.stop_tracking_server_status = true;
|
|
1408
|
-
|
|
1492
|
+
|
|
1409
1493
|
let report = reports.getPrintableReport( scope );
|
|
1410
1494
|
// Log the report. Can't get this.attach() or this.log() to work in `After()` or here.
|
|
1411
1495
|
// May need newest version of cucumberjs
|
|
1412
1496
|
console.log( `\n\n${ report }` );
|
|
1413
1497
|
|
|
1414
|
-
// Save/download the report as an artifact
|
|
1415
|
-
fs.writeFileSync( scope.paths.report, report );
|
|
1416
|
-
|
|
1417
1498
|
// If there is a browser open, then close it
|
|
1418
1499
|
if (scope.browser) {
|
|
1419
1500
|
await scope.browser.close();
|
|
1420
1501
|
}
|
|
1421
|
-
});
|
|
1502
|
+
});
|
package/lib/utils/log.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const session_vars = require( './session_vars' );
|
|
3
3
|
|
|
4
4
|
// ? 🌈 💡 🔎 🤕 🐞
|
|
5
|
+
// Alternatives: 🩹 ❤️🩹 🚑
|
|
5
6
|
// TODO: return full message so it can be passed to manually
|
|
6
7
|
// thrown errors if needed.
|
|
7
8
|
|
|
@@ -91,4 +92,5 @@ log.add_to_debug_log = function(value) {
|
|
|
91
92
|
fs.appendFileSync(log.debug_log_file, value);
|
|
92
93
|
};
|
|
93
94
|
|
|
94
|
-
log.debug_log_file =
|
|
95
|
+
log.debug_log_file = `debug_log.txt`;
|
|
96
|
+
log.unexpected_filename = `unexpected_results.txt`;
|
package/lib/utils/reports.js
CHANGED
|
@@ -7,6 +7,13 @@ const files = require(`./files` );
|
|
|
7
7
|
|
|
8
8
|
module.exports = reports = {};
|
|
9
9
|
|
|
10
|
+
reports.title = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
|
|
11
|
+
reports.failures = [ `\n\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n🤕 Failed scenarios\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n` ];
|
|
12
|
+
reports.successes = [ `\n\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n🌈 Passed scenarios\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n` ];
|
|
13
|
+
// reports.warnings = [ `\n\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n🔎 Scenarios to check\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n` ];
|
|
14
|
+
// reports.undefineds = [ `\n\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n[langs icon] Scenarios with typos\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n` ];
|
|
15
|
+
reports.others = [ `\n\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n🔎 Other scenarios that did not pass\n≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n` ];
|
|
16
|
+
|
|
10
17
|
reports.addReportHeading = function (scope, { scenario }) {
|
|
11
18
|
/** Return printable heading based on Scenario name and tags.
|
|
12
19
|
* Allow characters from other languages.
|
|
@@ -19,14 +26,14 @@ reports.addReportHeading = function (scope, { scenario }) {
|
|
|
19
26
|
tag_names.push( tag.name );
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
let heading = `\n
|
|
29
|
+
let heading = `\n―――――――――――――――\n`
|
|
23
30
|
+ `Scenario: ${ scenario.pickle.name }`;
|
|
24
31
|
|
|
25
32
|
if ( tag_names.length > 0 ) {
|
|
26
33
|
heading += `\nTags: ${ tag_names.join(' ') }`;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
heading += `\n
|
|
36
|
+
heading += `\n―――――――――――――――`;
|
|
30
37
|
|
|
31
38
|
reports.addToReport( scope, {
|
|
32
39
|
code: `ALK0176`,
|
|
@@ -71,21 +78,30 @@ reports.addToReport = function( scope, { type, code, value }) {
|
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
// Report stuff
|
|
74
|
-
let scenario =
|
|
81
|
+
let scenario = reports.makeSureScenarioExists( scope );
|
|
75
82
|
scenario.lines.push({ type, code, value });
|
|
76
83
|
|
|
77
84
|
return scenario;
|
|
78
85
|
}; // Ends reports.addToReport()
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
reports.create = function( scope ) {
|
|
88
|
+
reports.makeSureReportExists( scope );
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
reports.makeSureReportExists = function( scope ) {
|
|
81
92
|
/** If a part of the report needs to exist right now, but doesn't exist,
|
|
82
93
|
* create it. */
|
|
83
94
|
if ( !scope.report ) { scope.report = new Map(); }
|
|
95
|
+
return this;
|
|
96
|
+
}; // Ends reports.makeSureReportExists()
|
|
97
|
+
|
|
98
|
+
reports.makeSureScenarioExists = function ( scope ) {
|
|
99
|
+
reports.makeSureReportExists( scope );
|
|
84
100
|
if ( !scope.scenario_id ) {
|
|
85
101
|
scope.scenario_id = `_report_${ Date.now() }`;
|
|
86
102
|
}
|
|
87
103
|
if ( !scope.report.get( scope.scenario_id )) {
|
|
88
|
-
scope.report.set( scope.scenario_id, {} )
|
|
104
|
+
scope.report.set( scope.scenario_id, {} );
|
|
89
105
|
scope.report.get( scope.scenario_id ).status = `in progress`;
|
|
90
106
|
}
|
|
91
107
|
|
|
@@ -93,11 +109,36 @@ let makeSureReportPartsExist = function( scope ) {
|
|
|
93
109
|
if ( !scenario.lines ) { scenario.lines = []; }
|
|
94
110
|
|
|
95
111
|
return scenario;
|
|
96
|
-
}; // Ends
|
|
112
|
+
}; // Ends reports.makeSureScenarioExists()
|
|
113
|
+
|
|
114
|
+
reports.saveFinalScenarioData = function( scope, { status=`in progress` }) {
|
|
115
|
+
this.setReportScenarioStatus( scope, { status });
|
|
116
|
+
let scenario = scope.report.get( scope.scenario_id );
|
|
117
|
+
let report = this.getPrintableScenario( scenario );
|
|
118
|
+
if ( scenario.status === `FAILED` ) { this.failures.push( report ); }
|
|
119
|
+
else if ( scenario.status === `PASSED` ) { this.successes.push( report ); }
|
|
120
|
+
// else if ( scenario.status === `WARNING` ) { passed_reports += report; this.warnings.push( report ); }
|
|
121
|
+
// else if ( scenario.status === `UNDEFINED` ) { passed_reports += report; this.undefineds.push( report ); }
|
|
122
|
+
else { this.others.push( report ); }
|
|
123
|
+
|
|
124
|
+
// Save to failures.txt (worry about optimization later)
|
|
125
|
+
if ( this.failures.length > 1 || this.others.length > 1 ) {
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
scope.paths.failures,
|
|
128
|
+
this.title + this.getPrintableUnexpected( scope )
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
// Save to reports.txt (worry about optimization later)
|
|
132
|
+
fs.writeFileSync( scope.paths.report, this.getPrintableReport( scope ) );
|
|
133
|
+
|
|
134
|
+
return scenario;
|
|
135
|
+
}; // Ends reports.saveFinalScenarioData()
|
|
136
|
+
|
|
97
137
|
|
|
98
138
|
reports.setReportScenarioStatus = function( scope, { status=`PASSED` }) {
|
|
99
139
|
/** Add final status of scenario to scenario report object - passed, failed, etc. */
|
|
100
140
|
scope.report.get( scope.scenario_id ).status = status;
|
|
141
|
+
return scope.report.get( scope.scenario_id );
|
|
101
142
|
}; // Ends reports.setReportScenarioStatus()
|
|
102
143
|
|
|
103
144
|
reports.convertToOriginalStoryTableRow = function( scope, { row_data }) {
|
|
@@ -124,34 +165,38 @@ let trimJSON = function( str=`` ) {
|
|
|
124
165
|
}; // Ends trimJSON()
|
|
125
166
|
|
|
126
167
|
reports.getPrintableReport = function( scope ) {
|
|
127
|
-
/** Return a string generated from test report data.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
let [ scenario_id, data ] = scenario;
|
|
138
|
-
if ( data.status === `FAILED` ) { failed_reports += report; }
|
|
139
|
-
else if ( data.status === `PASSED` ) { passed_reports += report; }
|
|
140
|
-
else { other_reports += report; }
|
|
141
|
-
}; // ends for every scenario
|
|
142
|
-
|
|
143
|
-
if ( failed_reports !== `` ) { report += `\n\n===============================\n===============================\n🤕 Failed scenarios:\n${ failed_reports }`; }
|
|
144
|
-
if ( other_reports !== `` ) { report += `\n\n===============================\n===============================\n🔎 Scenarios that did not pass:\n${ other_reports }`; }
|
|
145
|
-
if ( passed_reports !== `` ) { report += `\n\n===============================\n===============================\n🌈 Passed scenarios:\n${ passed_reports }`; }
|
|
168
|
+
/** Return a string generated from test report data.
|
|
169
|
+
*
|
|
170
|
+
* First list failed scenarios, then non-passed scenarios, then passed scenarios. */
|
|
171
|
+
let report = this.getTitle( scope );
|
|
172
|
+
|
|
173
|
+
if ( this.failures.length > 1 ) { report += this.failures.join( `` ); }
|
|
174
|
+
if ( this.successes.length > 1 ) { report += this.successes.join( `` ); }
|
|
175
|
+
// if ( this.warnings.length > 1 ) {}
|
|
176
|
+
// if ( this.undefineds.length > 1 ) {}
|
|
177
|
+
if ( this.others.length > 1 ) { report += this.others.join( `` ); }
|
|
146
178
|
|
|
147
179
|
return report;
|
|
148
180
|
}; // Ends reports.getPrintableReport()
|
|
149
181
|
|
|
182
|
+
reports.getTitle = function( scope ) { return this.title; }
|
|
183
|
+
|
|
184
|
+
reports.getPrintableUnexpected = function( scope ) {
|
|
185
|
+
// Note about `> 1`: The first line of the report section always exists
|
|
186
|
+
let output = ``;
|
|
187
|
+
if ( this.failures.length > 1 ) {
|
|
188
|
+
output += this.failures.join(``);
|
|
189
|
+
}
|
|
190
|
+
if ( this.others.length > 1 ) {
|
|
191
|
+
output += this.others.join(``);
|
|
192
|
+
}
|
|
193
|
+
return output;
|
|
194
|
+
}; // Ends reports.getPrintableUnexpected()
|
|
195
|
+
|
|
150
196
|
reports.getPrintableScenario = function( scenario ) {
|
|
151
197
|
/** Return a string generated from scenario list of report items. */
|
|
152
|
-
let [ scenario_id, data ] = scenario;
|
|
153
198
|
let report = ``;
|
|
154
|
-
for ( let line of
|
|
199
|
+
for ( let line of scenario.lines ) {
|
|
155
200
|
let { type, code, value } = line;
|
|
156
201
|
report += `${ getLineText({ type, code, value }) }\n`;
|
|
157
202
|
}
|
|
@@ -107,7 +107,7 @@ session_vars.get_languages = function () {
|
|
|
107
107
|
}; // Ends session_vars.get_languages()
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
const runtime_config_path =
|
|
110
|
+
const runtime_config_path = `runtime_config.json`;
|
|
111
111
|
const project_name_key = `da_project_name`;
|
|
112
112
|
session_vars.save_project_name = function ( project_name ) {
|
|
113
113
|
/* Saves the name of the project to a json obj so deletion can use it later.
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suffolklitlab/alkiln",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.0-delete1",
|
|
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 codes && npm run cucumber",
|
|
8
8
|
"cucumber": "./lib/run_cucumber.js",
|
|
9
9
|
"local": "npm run setup && npm run cucumber; npm run takedown",
|
|
10
|
+
"pass": "npm run cucumber 'not @error and not @temp_error'",
|
|
11
|
+
"fail": "npm run cucumber '@error'",
|
|
10
12
|
"setup": "./lib/docassemble/setup.js",
|
|
11
13
|
"takedown": "./lib/docassemble/takedown.js",
|
|
12
14
|
"server_install": "./lib/docassemble/server_install.js",
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
"bugs": {
|
|
31
33
|
"url": "https://github.com/SuffolkLITLab/ALKiln/issues"
|
|
32
34
|
},
|
|
33
|
-
"homepage": "https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/
|
|
35
|
+
"homepage": "https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/intro",
|
|
34
36
|
"dependencies": {
|
|
35
37
|
"@axe-core/puppeteer": "4.7.3",
|
|
36
38
|
"@cucumber/cucumber": "9.3.0",
|
package/setup.py
CHANGED
|
@@ -50,7 +50,7 @@ setup(name='docassemble.ALKilnTests',
|
|
|
50
50
|
author='plocket',
|
|
51
51
|
author_email='example@example.com',
|
|
52
52
|
license='The MIT License (MIT)',
|
|
53
|
-
url='https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln',
|
|
53
|
+
url='https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/intro',
|
|
54
54
|
packages=find_packages(),
|
|
55
55
|
namespace_packages=['docassemble'],
|
|
56
56
|
install_requires=['docassemble.ALToolbox>=0.8.2', 'docassemble.AssemblyLine>=2.24.0'],
|