@suffolklitlab/alkiln 5.7.0 → 5.8.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/.github/workflows/github_server.yml +2 -2
- package/.github/workflows/playground.yml +2 -0
- package/CHANGELOG.md +22 -0
- package/action.yml +29 -14
- package/action_for_github_server/action.yml +37 -21
- package/lib/scope.js +81 -70
- package/lib/steps.js +21 -13
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ name: GitHub server installation
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
# Default opened, reopened, synchronize. "edited"
|
|
5
|
+
# Default "opened", "reopened", "synchronize". "edited" fires when the PR title gets edited, etc.
|
|
6
6
|
pull_request_target:
|
|
7
7
|
workflow_dispatch:
|
|
8
8
|
inputs:
|
|
@@ -44,7 +44,7 @@ jobs:
|
|
|
44
44
|
id: github_server
|
|
45
45
|
#### Developer note: you'll need to replace `.` with the path to
|
|
46
46
|
#### the repo and branch. For example:
|
|
47
|
-
#### suffolkLITLab/ALKiln@v5
|
|
47
|
+
#### suffolkLITLab/ALKiln/action_for_github_server@v5
|
|
48
48
|
uses: ./action_for_github_server
|
|
49
49
|
with:
|
|
50
50
|
CONFIG_CONTENTS: "${{ secrets.CONFIG_CONTENTS }}"
|
package/CHANGELOG.md
CHANGED
|
@@ -45,6 +45,28 @@ Format:
|
|
|
45
45
|
|
|
46
46
|
<!-- ## [Unreleased] -->
|
|
47
47
|
|
|
48
|
+
## [5.8.1] - 2023-12-28
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- Restored previous behavior - Projects are now deleted even when a workflow fails. See [#831](https://github.com/SuffolkLITLab/ALKiln/issues/831).
|
|
52
|
+
- Fixed typo of comment explaining how to reference the isolated GitHub server action.
|
|
53
|
+
|
|
54
|
+
### Security
|
|
55
|
+
- Harden security for action input handling. That said, the source of the problem would come from author workflows, so there's a lot more authors can do to help this situation than we can. Workflows triggered by pull requests are a specific place to watch. GitHub already has [some default safeguards](https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks) to prevent strangers, specifically first-time contributors, from triggering workflows like that. They can also set their [org](https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization#configuring-required-approval-for-workflows-from-public-forks) or [repo](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#controlling-changes-from-forks-to-workflows-in-public-repositories) to be more strict about pull requests from outside collaborators.
|
|
56
|
+
|
|
57
|
+
### Internal
|
|
58
|
+
- Freeze `docassemblecli` version at 0.0.17. See [#827](https://github.com/SuffolkLITLab/ALKiln/issues/827).
|
|
59
|
+
|
|
60
|
+
## [5.8.0] - 2023-12-18
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Step to tap an element without navigating. See [#834](https://github.com/SuffolkLITLab/ALKiln/issues/834).
|
|
64
|
+
|
|
65
|
+
### Internal
|
|
66
|
+
- Use puppeteer `.click()`` again
|
|
67
|
+
- Re-activate looking for terms on the page.
|
|
68
|
+
|
|
69
|
+
|
|
48
70
|
## [5.7.0] - 2023-11-14
|
|
49
71
|
|
|
50
72
|
### Added
|
package/action.yml
CHANGED
|
@@ -22,8 +22,6 @@ inputs:
|
|
|
22
22
|
description: "Max amount of time to give the server to reload if it needs to"
|
|
23
23
|
required: false
|
|
24
24
|
# no default to let the custom Scenario timeouts create the reload timeout
|
|
25
|
-
# INSTALL_LOCATION? Will that get confused with the location passed into the command line?
|
|
26
|
-
# Maybe `INSTALL_LOCATION` could be "playground", "server", or a custom path and we could handle all those.
|
|
27
25
|
INSTALL_METHOD:
|
|
28
26
|
description: "Default is 'playground'. Other option: 'server'. How to install the package onto the server."
|
|
29
27
|
required: false
|
|
@@ -31,6 +29,10 @@ inputs:
|
|
|
31
29
|
ALKILN_TAG_EXPRESSION:
|
|
32
30
|
description: "The tag expression to use to limit which tests to run. Default is no tag expression."
|
|
33
31
|
required: false
|
|
32
|
+
DOCASSEMBLECLI_VERSION:
|
|
33
|
+
description: "The version of docassemblecli to install."
|
|
34
|
+
required: false
|
|
35
|
+
default: "0.0.17"
|
|
34
36
|
|
|
35
37
|
runs:
|
|
36
38
|
using: "composite"
|
|
@@ -40,18 +42,29 @@ runs:
|
|
|
40
42
|
|
|
41
43
|
# Set environment variables
|
|
42
44
|
- name: "ALKiln setup info: Set environment variables"
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
+
# Safe input handling. Avoid evaluating in bash.
|
|
46
|
+
env:
|
|
47
|
+
BRANCH_PATH: ${{ github.ref }}
|
|
48
|
+
BASE_URL: ${{ inputs.SERVER_URL }}
|
|
49
|
+
DOCASSEMBLE_DEVELOPER_API_KEY: ${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}
|
|
50
|
+
ALKILN_VERSION: ${{ inputs.ALKILN_VERSION }}
|
|
51
|
+
MAX_SECONDS_FOR_SETUP: ${{ inputs.MAX_SECONDS_FOR_SETUP }}
|
|
52
|
+
SERVER_RELOAD_TIMEOUT_SECONDS: ${{ inputs.SERVER_RELOAD_TIMEOUT_SECONDS }}
|
|
53
|
+
DOCASSEMBLECLI_VERSION: ${{ inputs.DOCASSEMBLECLI_VERSION }}
|
|
45
54
|
run: |
|
|
46
55
|
# ALKiln setup info: Set environment variables
|
|
56
|
+
# Human-readable date/time: https://www.shell-tips.com/linux/how-to-format-date-and-time-in-linux-macos-and-bash/#how-to-format-a-date-in-bash
|
|
47
57
|
echo "ARTIFACT_NAME=alkiln-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
|
|
48
58
|
echo "REPO_URL=${{ github.server_url }}/${{ github.repository }}" >> $GITHUB_ENV
|
|
49
|
-
echo "BRANCH_PATH=$
|
|
50
|
-
|
|
51
|
-
echo "
|
|
52
|
-
echo "
|
|
53
|
-
echo "
|
|
54
|
-
echo "
|
|
59
|
+
echo "BRANCH_PATH=$BRANCH_PATH" >> $GITHUB_ENV
|
|
60
|
+
# Workflow inputs
|
|
61
|
+
echo "BASE_URL=$BASE_URL" >> $GITHUB_ENV
|
|
62
|
+
echo "DOCASSEMBLE_DEVELOPER_API_KEY=$DOCASSEMBLE_DEVELOPER_API_KEY" >> $GITHUB_ENV
|
|
63
|
+
echo "ALKILN_VERSION=$ALKILN_VERSION" >> $GITHUB_ENV
|
|
64
|
+
echo "MAX_SECONDS_FOR_SETUP=$MAX_SECONDS_FOR_SETUP" >> $GITHUB_ENV
|
|
65
|
+
echo "SERVER_RELOAD_TIMEOUT_SECONDS=$SERVER_RELOAD_TIMEOUT_SECONDS" >> $GITHUB_ENV
|
|
66
|
+
echo "DOCASSEMBLECLI_VERSION=$DOCASSEMBLECLI_VERSION" >> $GITHUB_ENV
|
|
67
|
+
# Hard-coded internal ALKiln vars
|
|
55
68
|
echo "_ORIGIN=github" >> $GITHUB_ENV
|
|
56
69
|
shell: bash
|
|
57
70
|
- name: "ALKiln setup info: confirm environment variables"
|
|
@@ -68,7 +81,7 @@ runs:
|
|
|
68
81
|
- name: Install ALKiln directly from npm
|
|
69
82
|
run: |
|
|
70
83
|
# ALKiln setup info: Install ALKiln directly from npm
|
|
71
|
-
npm install -g @suffolklitlab/alkiln@$ALKILN_VERSION
|
|
84
|
+
npm install -g "@suffolklitlab/alkiln@$ALKILN_VERSION"
|
|
72
85
|
shell: bash
|
|
73
86
|
|
|
74
87
|
# Install on playground
|
|
@@ -82,7 +95,7 @@ runs:
|
|
|
82
95
|
if: ${{ inputs.INSTALL_METHOD == 'playground' }}
|
|
83
96
|
run: |
|
|
84
97
|
# ALKiln setup info: Create a Project and install the package from GitHub
|
|
85
|
-
|
|
98
|
+
pip install "docassemblecli==$DOCASSEMBLECLI_VERSION"
|
|
86
99
|
alkiln-setup
|
|
87
100
|
shell: bash
|
|
88
101
|
- name: "ALKiln setup ERROR"
|
|
@@ -103,12 +116,14 @@ runs:
|
|
|
103
116
|
# run tests
|
|
104
117
|
- name: "ALKiln info: Run tests"
|
|
105
118
|
if: ${{ success() }}
|
|
106
|
-
|
|
119
|
+
env:
|
|
120
|
+
ALKILN_TAG_EXPRESSION: ${{ inputs.ALKILN_TAG_EXPRESSION || github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
|
|
121
|
+
run: alkiln-run $ALKILN_TAG_EXPRESSION
|
|
107
122
|
shell: bash
|
|
108
123
|
|
|
109
124
|
# on playground, delete project
|
|
110
125
|
- name: "ALKiln info: Try to delete the project from the playground of the docassemble test account."
|
|
111
|
-
if: ${{ inputs.INSTALL_METHOD == 'playground' }}
|
|
126
|
+
if: ${{ always() && inputs.INSTALL_METHOD == 'playground' }}
|
|
112
127
|
run: alkiln-takedown
|
|
113
128
|
shell: bash
|
|
114
129
|
|
|
@@ -23,6 +23,10 @@ inputs:
|
|
|
23
23
|
description: "Default is 600 seconds. Maximum amount of seconds to give the docassemble docker container to serve the site."
|
|
24
24
|
required: false
|
|
25
25
|
default: 600 # 10 min
|
|
26
|
+
DOCASSEMBLECLI_VERSION:
|
|
27
|
+
description: "The version of docassemblecli to install."
|
|
28
|
+
required: false
|
|
29
|
+
default: "0.0.17"
|
|
26
30
|
|
|
27
31
|
outputs:
|
|
28
32
|
DOCASSEMBLE_DEVELOPER_API_KEY:
|
|
@@ -44,11 +48,16 @@ runs:
|
|
|
44
48
|
- uses: actions/checkout@v3
|
|
45
49
|
|
|
46
50
|
- name: "ALKiln GitHub server - Set environment variables"
|
|
51
|
+
env:
|
|
52
|
+
MAX_SECONDS_FOR_DOCKER: ${{ inputs.MAX_SECONDS_FOR_DOCKER }}
|
|
53
|
+
DOCASSEMBLECLI_VERSION: ${{ inputs.DOCASSEMBLECLI_VERSION }}
|
|
47
54
|
run: |
|
|
55
|
+
# ALKiln GitHub server info: Set environment variables
|
|
56
|
+
echo "MAX_SECONDS_FOR_DOCKER=$MAX_SECONDS_FOR_DOCKER" >> $GITHUB_ENV
|
|
57
|
+
echo "DOCASSEMBLECLI_VERSION=$DOCASSEMBLECLI_VERSION" >> $GITHUB_ENV
|
|
48
58
|
echo "DA_ADMIN_API_KEY=abcd1234abcd1234abcd5678abdc5678" >> $GITHUB_ENV
|
|
49
|
-
echo "MAX_SECONDS_FOR_DOCKER=${{ inputs.MAX_SECONDS_FOR_DOCKER }}" >> $GITHUB_ENV
|
|
50
59
|
echo "DA_ADMIN_EMAIL=admin@example.com" >> $GITHUB_ENV
|
|
51
|
-
# Login will fail if this is "password"
|
|
60
|
+
# Login will fail if this value is "password"
|
|
52
61
|
echo "DA_ADMIN_PASSWORD=@123abcdefg" >> $GITHUB_ENV
|
|
53
62
|
shell: bash
|
|
54
63
|
- id: api_key_output_step
|
|
@@ -59,14 +68,16 @@ runs:
|
|
|
59
68
|
shell: bash
|
|
60
69
|
|
|
61
70
|
- name: "ALKiln GitHub server - Get config"
|
|
71
|
+
env:
|
|
72
|
+
CONFIG: ${{ inputs.CONFIG_CONTENTS }}
|
|
62
73
|
run: |
|
|
63
74
|
# ALKiln GitHub server info: config values
|
|
64
75
|
mkdir /tmp/config
|
|
65
76
|
# Worth making this silent by defining a variable with > /dev/null...?
|
|
66
|
-
if [ -n "$
|
|
77
|
+
if [ -n "$CONFIG" ]
|
|
67
78
|
then
|
|
68
79
|
echo "ALKiln GitHub server - The developer did provide a custom docassemble config file as an input into this action."
|
|
69
|
-
echo "$
|
|
80
|
+
echo "$CONFIG" > /tmp/config/myconfig.yml
|
|
70
81
|
echo "CONFIG_ARGS=--env DA_CONFIG=/tmp/config/myconfig.yml --volume /tmp/config:/tmp/config " >> $GITHUB_ENV
|
|
71
82
|
else
|
|
72
83
|
echo "ALKiln GitHub server - The developer did NOT provide a custom docassemble config file as an input into this action. Docassemble will use its default config."
|
|
@@ -74,20 +85,20 @@ runs:
|
|
|
74
85
|
fi
|
|
75
86
|
shell: bash
|
|
76
87
|
|
|
77
|
-
#
|
|
78
|
-
# need to stop that yet, but users may start using variables, so
|
|
79
|
-
# a question: If in future we allow variables instead of just secrets,
|
|
88
|
+
# Question: If in future we allow GitHub variables instead of just secrets,
|
|
80
89
|
# should we let those be visible or prevent them along with the docker logs?
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
# hiding the output from the job console?
|
|
90
|
+
# Also, does GitHub hide job environment output of secrets as well as
|
|
91
|
+
# hiding the secrets' output from the job console?
|
|
84
92
|
|
|
85
93
|
# Define the var to prevent/show docker output
|
|
94
|
+
# TODO: Why are we only doing this for manually triggered workflows?
|
|
86
95
|
- name: Env var for showing docker output
|
|
96
|
+
env:
|
|
97
|
+
SHOW_DOCKER_OUTPUT: ${{ inputs.SHOW_DOCKER_OUTPUT }}
|
|
87
98
|
run: |
|
|
88
99
|
# ALKiln GitHub server info: Docker logs visibility decision
|
|
89
100
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
90
|
-
echo "OUTPUT_DOCKER=$
|
|
101
|
+
echo "OUTPUT_DOCKER=$SHOW_DOCKER_OUTPUT" >> $GITHUB_ENV
|
|
91
102
|
else
|
|
92
103
|
echo "OUTPUT_DOCKER=false" >> $GITHUB_ENV
|
|
93
104
|
fi
|
|
@@ -105,31 +116,36 @@ runs:
|
|
|
105
116
|
printf '===\nALKiln GitHub server - GitHub will now show the output of docker creation. 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==='
|
|
106
117
|
shell: bash
|
|
107
118
|
|
|
119
|
+
# TODO: Reduce these to 1 block by putting ` > /dev/null 2>&1` into an env var
|
|
108
120
|
# No output
|
|
109
121
|
- name: "ALKiln GitHub server - Download and start docker silently"
|
|
110
122
|
if: ${{ env.OUTPUT_DOCKER == 'false' }}
|
|
123
|
+
env:
|
|
124
|
+
CONFIG_ARGS: ${{ env.CONFIG_ARGS }}
|
|
111
125
|
run: |
|
|
112
126
|
# ALKiln GitHub server info: Silent docker pull/run
|
|
113
127
|
docker pull jhpyle/docassemble > /dev/null 2>&1
|
|
114
128
|
docker run --name docassemble_container \
|
|
115
|
-
--env DA_ADMIN_EMAIL="$
|
|
116
|
-
--env DA_ADMIN_PASSWORD="$
|
|
117
|
-
--env DA_ADMIN_API_KEY="$
|
|
118
|
-
$
|
|
129
|
+
--env DA_ADMIN_EMAIL="$DA_ADMIN_EMAIL" \
|
|
130
|
+
--env DA_ADMIN_PASSWORD="$DA_ADMIN_PASSWORD" \
|
|
131
|
+
--env DA_ADMIN_API_KEY="$DA_ADMIN_API_KEY" \
|
|
132
|
+
$CONFIG_ARGS \
|
|
119
133
|
--cap-add SYS_PTRACE \
|
|
120
134
|
--memory="4gb" -d -p 80:80 jhpyle/docassemble > /dev/null 2>&1
|
|
121
135
|
shell: bash
|
|
122
136
|
# With output
|
|
123
137
|
- name: "ALKiln GitHub server - Download and start docker"
|
|
124
138
|
if: ${{ env.OUTPUT_DOCKER == 'true' }}
|
|
139
|
+
env:
|
|
140
|
+
CONFIG_ARGS: ${{ env.CONFIG_ARGS }}
|
|
125
141
|
run: |
|
|
126
142
|
# ALKiln GitHub server info: Docker pull/run
|
|
127
143
|
docker pull jhpyle/docassemble
|
|
128
144
|
docker run --name docassemble_container \
|
|
129
|
-
--env DA_ADMIN_EMAIL="$
|
|
130
|
-
--env DA_ADMIN_PASSWORD="$
|
|
131
|
-
--env DA_ADMIN_API_KEY="$
|
|
132
|
-
$
|
|
145
|
+
--env DA_ADMIN_EMAIL="$DA_ADMIN_EMAIL" \
|
|
146
|
+
--env DA_ADMIN_PASSWORD="$DA_ADMIN_PASSWORD" \
|
|
147
|
+
--env DA_ADMIN_API_KEY="$DA_ADMIN_API_KEY" \
|
|
148
|
+
$CONFIG_ARGS \
|
|
133
149
|
--cap-add SYS_PTRACE \
|
|
134
150
|
--memory="4gb" -d -p 80:80 jhpyle/docassemble
|
|
135
151
|
shell: bash
|
|
@@ -188,7 +204,7 @@ runs:
|
|
|
188
204
|
with:
|
|
189
205
|
python-version: '3.10'
|
|
190
206
|
# No output
|
|
191
|
-
- run: pip install docassemblecli > /dev/null 2>&1
|
|
207
|
+
- run: pip install "docassemblecli==$DOCASSEMBLECLI_VERSION" > /dev/null 2>&1
|
|
192
208
|
if: ${{ env.OUTPUT_DOCKER == 'false' }}
|
|
193
209
|
shell: bash
|
|
194
210
|
# Install the package, the current directory (.), onto the docassemble server
|
|
@@ -197,7 +213,7 @@ runs:
|
|
|
197
213
|
shell: bash
|
|
198
214
|
|
|
199
215
|
# With output
|
|
200
|
-
- run: pip install docassemblecli
|
|
216
|
+
- run: pip install "docassemblecli==$DOCASSEMBLECLI_VERSION"
|
|
201
217
|
if: ${{ env.OUTPUT_DOCKER == 'true' }}
|
|
202
218
|
shell: bash
|
|
203
219
|
# Install the package, the current directory (.), onto the docassemble server
|
package/lib/scope.js
CHANGED
|
@@ -174,14 +174,14 @@ module.exports = {
|
|
|
174
174
|
if ( showif ) { await scope.page.waitForTimeout( scope.showif_timeout ); }
|
|
175
175
|
},
|
|
176
176
|
|
|
177
|
-
waitForTab: async function waitForTab(scope,
|
|
177
|
+
waitForTab: async function waitForTab(scope, { tab_id }) {
|
|
178
178
|
/**
|
|
179
|
-
* @param {
|
|
179
|
+
* @param { tab_id } the aria-labeledby text of the tab panel, which
|
|
180
180
|
* we use to query and wait for that tab panel to be shown.
|
|
181
181
|
*/
|
|
182
182
|
// Implementation linked to https://github.com/SuffolkLITLab/docassemble-ALToolbox/blob/4aa1069389d2ca9cadb586f309dd73d791f081d1/docassemble/ALToolbox/misc.py#L127
|
|
183
183
|
// I think `.$()` does not use a timeout
|
|
184
|
-
if (
|
|
184
|
+
if ( tab_id.length == 0) { return }
|
|
185
185
|
// waiting for the transitionend event: https://stackoverflow.com/a/57955251/11416267
|
|
186
186
|
await scope.page.evaluate((element_query) => {
|
|
187
187
|
return new Promise((resolve) => {
|
|
@@ -198,13 +198,16 @@ module.exports = {
|
|
|
198
198
|
resolve();
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
|
-
}, `div[aria-labelledby="${
|
|
201
|
+
}, `div[aria-labelledby="${ tab_id }"]`);
|
|
202
202
|
},
|
|
203
203
|
|
|
204
|
-
afterStep: async function afterStep(scope, options = {waitForShowIf: false,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
afterStep: async function afterStep(scope, options = {waitForShowIf: false, navigated: false, waitForTimeout: 0}) {
|
|
205
|
+
/** Common things to do after a step, including take care of errors.
|
|
206
|
+
* `waitForShowIf` is automatically `false` for observation/non-interactive steps, etc.
|
|
207
|
+
*
|
|
208
|
+
* TODO: discuss splitting into `afterField` and `afterStep` or something. Or just change name
|
|
209
|
+
* TODO: Update to latest cucumberjs, which has `AfterStep`
|
|
210
|
+
*/
|
|
208
211
|
|
|
209
212
|
if ( scope.page ) {
|
|
210
213
|
// Use a while loop until the below does not error with `Error: Execution context was destroyed`
|
|
@@ -231,7 +234,6 @@ module.exports = {
|
|
|
231
234
|
|
|
232
235
|
// Wait for elements that might get revealed
|
|
233
236
|
if ( options.waitForShowIf ) { await scope.waitForShowIf( scope ); }
|
|
234
|
-
if ( options.tabToWaitFor ) { await scope.waitForTab( scope, options.tabToWaitFor); }
|
|
235
237
|
} // ends if scope.page
|
|
236
238
|
|
|
237
239
|
if ( options.navigated ) {
|
|
@@ -248,39 +250,56 @@ module.exports = {
|
|
|
248
250
|
if ( options.waitForTimeout ) { await time.waitForTimeout( options.waitForTimeout ); }
|
|
249
251
|
}, // Ends afterStep
|
|
250
252
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
tapElementAndNavigate: async function(scope, { elem, waitForTimeout=0 }) {
|
|
254
|
+
/** Tap a given element, detect navigation, and do appropriate related things.
|
|
255
|
+
* TODO: not quite sure if we'll be using `waitForTimeout`. */
|
|
253
256
|
|
|
254
|
-
|
|
255
|
-
// first in user-triggered cases - users used to be able to access this almost
|
|
256
|
-
// directly, but they can't anymore. Our functions should know to only pass good things.
|
|
257
|
-
let msg = `Cannot find the element to tap.`;
|
|
258
|
-
if ( !elem ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
257
|
+
let start_url = await scope.page.url(); // so we can later check for navigation
|
|
259
258
|
|
|
260
|
-
|
|
259
|
+
await scope.tapElementAndStayOnPage(scope, {
|
|
260
|
+
elem,
|
|
261
|
+
click_promise: Promise.all([
|
|
262
|
+
scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
|
263
|
+
elem.evaluate( (el) => { return el.click(); })
|
|
264
|
+
])
|
|
265
|
+
});
|
|
261
266
|
|
|
262
|
-
//
|
|
263
|
-
|
|
267
|
+
// TODO: Not sure how to detect navigation landing at same page, such as
|
|
268
|
+
// with a user input validation error. Does that count as navigating?
|
|
269
|
+
// Maybe url change + trigger var change? Maybe just trigger var?
|
|
270
|
+
let end_url = await scope.page.url();
|
|
271
|
+
let navigated = start_url !== end_url;
|
|
264
272
|
|
|
265
|
-
|
|
273
|
+
// Might be able to handle this in `scope.afterStep`
|
|
274
|
+
if ( navigated ) {
|
|
266
275
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
276
|
+
// TODO: Can we remove this?
|
|
277
|
+
// Save the id that comes next
|
|
278
|
+
let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
|
|
279
|
+
|
|
280
|
+
// If navigation, wait for 200ms to let things finish loading and moving
|
|
281
|
+
await scope.afterStep(scope, { waitForTimeout: 200, navigated: true });
|
|
282
|
+
} else {
|
|
283
|
+
await scope.afterStep(scope, { waitForTimeout, navigated: false });
|
|
273
284
|
}
|
|
274
|
-
|
|
285
|
+
}, // Ends scope.tapElementAndNavigate()
|
|
275
286
|
|
|
276
|
-
|
|
287
|
+
tapElementAndStayOnPage: async function(scope, { elem, click_promise }) {
|
|
288
|
+
/** Tap a given element, detect navigation, and do appropriate related things.
|
|
289
|
+
* `click_promise can be any kind of Promise, including a Promise.all()
|
|
290
|
+
*/
|
|
291
|
+
let msg = `Cannot find the element to tap.`;
|
|
292
|
+
if ( !elem ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
293
|
+
expect( elem, msg ).to.exist;
|
|
277
294
|
|
|
295
|
+
click_promise = click_promise || elem.click();
|
|
296
|
+
|
|
278
297
|
// TODO: implement and use scope.handle_possible_timeout()
|
|
279
298
|
try {
|
|
280
|
-
//
|
|
281
|
-
// Also, puppeteer will ensure proper timeout.
|
|
282
|
-
|
|
283
|
-
|
|
299
|
+
// Serever error loads page, so keeping things shorter by not looking
|
|
300
|
+
// for that text too. Also, puppeteer will ensure proper timeout.
|
|
301
|
+
await Promise.race([
|
|
302
|
+
click_promise,
|
|
284
303
|
// This is meant to detect user-visible alerts/errors,
|
|
285
304
|
// not breaking errors in the testing code.
|
|
286
305
|
scope.page.waitForSelector('.alert-danger', { visible: true }),
|
|
@@ -292,6 +311,9 @@ module.exports = {
|
|
|
292
311
|
let non_reload_report_msg = `ALKiln tapped "${ tapperText }", but the interview took to long to finish.`;
|
|
293
312
|
await scope.handle_page_timeout_error( scope, { non_reload_report_msg, error });
|
|
294
313
|
} else {
|
|
314
|
+
|
|
315
|
+
// Get the text on the button or link for a potential error message later
|
|
316
|
+
let tapperText = await (await elem.getProperty('textContent')).jsonValue();
|
|
295
317
|
// Throw any non-timeout error
|
|
296
318
|
let err_msg = `Error occurred when ALKiln tried to tap "${ tapperText }".`
|
|
297
319
|
reports.addToReport( scope, { type: `error`, value: err_msg });
|
|
@@ -299,30 +321,7 @@ module.exports = {
|
|
|
299
321
|
} // ends if error is timeout error
|
|
300
322
|
|
|
301
323
|
} // ends try/catch
|
|
302
|
-
|
|
303
|
-
// TODO: Not sure how to detect navigation landing at same page, such as
|
|
304
|
-
// with a user input validation error. Does that count as navigating?
|
|
305
|
-
// Maybe url change + trigger var change? Maybe just trigger var?
|
|
306
|
-
let end_url = await scope.page.url();
|
|
307
|
-
let navigated = start_url !== end_url;
|
|
308
|
-
|
|
309
|
-
// Might be able to handle this in `scope.afterStep`
|
|
310
|
-
if ( navigated ) {
|
|
311
|
-
// Save the id that comes next
|
|
312
|
-
let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
|
|
313
|
-
// If navigation, wait for 200ms to let things settle down
|
|
314
|
-
await scope.afterStep(scope, {waitForTimeout: 200, navigated: navigated});
|
|
315
|
-
} else {
|
|
316
|
-
// If stay on page, wait for `show if`, the new tab to be loaded, or feedback elements to be move around
|
|
317
|
-
options = {waitForShowIf: !tabToWaitFor, tabToWaitFor: tabToWaitFor, navigated: navigated};
|
|
318
|
-
if (waitForTimeout > 0) {
|
|
319
|
-
options = {waitForTimeout: waitForTimeout, navigated: navigated};
|
|
320
|
-
}
|
|
321
|
-
await scope.afterStep(scope, options);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return winner;
|
|
325
|
-
}, // Ends scope.tapElement()
|
|
324
|
+
}, // Ends scope.tapElementAndStayOnPage()
|
|
326
325
|
|
|
327
326
|
// Handle server reload errors
|
|
328
327
|
server_statuses: [],
|
|
@@ -516,7 +515,7 @@ module.exports = {
|
|
|
516
515
|
]);
|
|
517
516
|
await elem.evaluate( el => { return el.className });
|
|
518
517
|
// Waits for navigation or user error
|
|
519
|
-
await scope.
|
|
518
|
+
await scope.tapElementAndNavigate( scope, { elem });
|
|
520
519
|
}, // Ends scope.continue()
|
|
521
520
|
|
|
522
521
|
continue_exists: async function ( scope ) {
|
|
@@ -545,7 +544,8 @@ module.exports = {
|
|
|
545
544
|
reports.addToReport(scope, { type: `error`, value: error_msg });
|
|
546
545
|
}
|
|
547
546
|
expect( elem, error_msg ).to.exist;
|
|
548
|
-
await scope.
|
|
547
|
+
await scope.tapElementAndStayOnPage(scope, { elem });
|
|
548
|
+
await scope.waitForTab( scope, { tab_id });
|
|
549
549
|
},
|
|
550
550
|
|
|
551
551
|
checkForA11y: async function (scope ) {
|
|
@@ -586,14 +586,25 @@ module.exports = {
|
|
|
586
586
|
};
|
|
587
587
|
},
|
|
588
588
|
|
|
589
|
-
|
|
590
|
-
let elem = await scope.page.$(`${selector}`);
|
|
591
|
-
let error_msg = `Couldn't find "${selector}" on the page, is there a typo?`;
|
|
589
|
+
tapSelectorToNavigate: async function ( scope, selector, waitForTimeout ) {
|
|
590
|
+
let elem = await scope.page.$(`${ selector }`);
|
|
591
|
+
let error_msg = `Couldn't find "${ selector }" on the page, is there a typo?`;
|
|
592
|
+
if ( !elem ) {
|
|
593
|
+
reports.addToReport(scope, { type: `error`, value: error_msg });
|
|
594
|
+
}
|
|
595
|
+
expect( elem, error_msg ).to.exist;
|
|
596
|
+
await scope.tapElementAndNavigate( scope, { elem, waitForTimeout });
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
tapSelectorAndStay: async function ( scope, { selector }) {
|
|
600
|
+
let elem = await scope.page.$(`${ selector }`);
|
|
601
|
+
let error_msg = `Couldn't find "${ selector }" on the page, is there a typo?`;
|
|
592
602
|
if ( !elem ) {
|
|
593
603
|
reports.addToReport(scope, { type: `error`, value: error_msg });
|
|
594
604
|
}
|
|
595
605
|
expect( elem, error_msg ).to.exist;
|
|
596
|
-
await scope.
|
|
606
|
+
await scope.tapElementAndStayOnPage(scope, { elem });
|
|
607
|
+
await scope.afterStep(scope, { waitForShowIf: false, navigated: false, waitForTimeout: 0 });
|
|
597
608
|
},
|
|
598
609
|
|
|
599
610
|
load: async function ( scope, file_name ) {
|
|
@@ -1539,7 +1550,7 @@ module.exports = {
|
|
|
1539
1550
|
|
|
1540
1551
|
enter_answer: {
|
|
1541
1552
|
button: async function ( scope, { handle, answer }) {
|
|
1542
|
-
await scope.
|
|
1553
|
+
await scope.tapElementAndNavigate( scope, { elem: handle });
|
|
1543
1554
|
return true;
|
|
1544
1555
|
},
|
|
1545
1556
|
textarea: async function ( scope, { handle, answer }) {
|
|
@@ -2466,16 +2477,16 @@ module.exports = {
|
|
|
2466
2477
|
}, // Ends scope.steps.set_secret_var
|
|
2467
2478
|
|
|
2468
2479
|
tap_term: async ( scope, phrase ) => {
|
|
2469
|
-
/** Tap a link that doesn't navigate. Depends on the language of the text.
|
|
2480
|
+
/** Tap a link that doesn't navigate. Depends on the language of the text.
|
|
2481
|
+
* TODO: This may be broken at this point.
|
|
2482
|
+
* */
|
|
2470
2483
|
const [link] = await scope.page.$x(`//a[contains(text(), "${phrase}")]`);
|
|
2471
2484
|
|
|
2472
2485
|
let msg = `The term "${ phrase }" seems to be missing.`;
|
|
2473
2486
|
if ( !link ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
2474
2487
|
expect( link, msg ).to.exist;
|
|
2475
|
-
|
|
2476
|
-
await scope.
|
|
2477
|
-
|
|
2478
|
-
await scope.afterStep(scope, {waitForShowIf: true});
|
|
2488
|
+
await scope.tapElementAndStayOnPage(scope, { elem: link });
|
|
2489
|
+
await scope.page.waitForTimeout(10000);
|
|
2479
2490
|
}, // Ends scope.steps.tap_term()
|
|
2480
2491
|
|
|
2481
2492
|
sign: async ( scope, name ) => {
|
|
@@ -2529,7 +2540,7 @@ module.exports = {
|
|
|
2529
2540
|
if (failed_to_download) {
|
|
2530
2541
|
reports.addToReport(scope, { type: `warning`, value: `Could not download file using fetch (${ err_msg }). ALKiln will now fallback to the click download method` });
|
|
2531
2542
|
scope.toDownload = filename;
|
|
2532
|
-
// Should this be using `scope.tapElement
|
|
2543
|
+
// Should this be using `scope.tapElement`?
|
|
2533
2544
|
await elem.evaluate( elem => { return elem.click(); });
|
|
2534
2545
|
await scope.detectDownloadComplete( scope );
|
|
2535
2546
|
}
|
|
@@ -2702,7 +2713,7 @@ module.exports = {
|
|
|
2702
2713
|
await scope.page.type( `#email`, email );
|
|
2703
2714
|
await scope.page.type( `#password`, password );
|
|
2704
2715
|
let elem = await scope.page.$( `button[name="submit"]` );
|
|
2705
|
-
await scope.
|
|
2716
|
+
await scope.tapElementAndNavigate( scope, { elem });
|
|
2706
2717
|
|
|
2707
2718
|
// Wait to see the success message.
|
|
2708
2719
|
// This is a bit of an assumption and needs success and failure testing
|
package/lib/steps.js
CHANGED
|
@@ -254,7 +254,7 @@ Given(/I (?:sign|log) ?(?:in)?(?:on)?(?:to the server)? with(?: the email)? "([^
|
|
|
254
254
|
* I sign on with the email "USER1_EMAIL" and the password "USER1_PASSWORD" SECRETs
|
|
255
255
|
* I log in with "USER1_EMAIL" and "USER1_PASSWORD"
|
|
256
256
|
*/
|
|
257
|
-
//
|
|
257
|
+
// `page` timeout will deal with custom timeout
|
|
258
258
|
// Couldn't test the timeout for tapping the button because there's not
|
|
259
259
|
// enough of a pause between initial navigation and pressing button.
|
|
260
260
|
await scope.steps.sign_in( scope, {
|
|
@@ -745,24 +745,32 @@ When(/I tap to continue/i, { timeout: -1 }, async () => {
|
|
|
745
745
|
// Any selectors for this seem somewhat precarious
|
|
746
746
|
// No data will cause an automatic continue when `ensure_navigation` is true
|
|
747
747
|
|
|
748
|
-
//
|
|
748
|
+
// `page` timeout will deal with custom timeout
|
|
749
749
|
// check if the server is reloading
|
|
750
750
|
await scope.setFields( scope, { var_data: [], ensure_navigation: true });
|
|
751
751
|
});
|
|
752
752
|
|
|
753
753
|
When(/I tap the "([^"]+)" tab/i, { timeout: -1 }, async ( tabId ) => {
|
|
754
|
-
//
|
|
754
|
+
// `page` timeout will deal with custom timeout
|
|
755
755
|
await scope.tapTab( scope, tabId );
|
|
756
756
|
});
|
|
757
757
|
|
|
758
|
-
When(/I tap the "([^"]+)" element
|
|
759
|
-
//
|
|
760
|
-
await scope.
|
|
758
|
+
When(/I tap the "([^"]+)" element(?: and go to (?:a new|the next) page)?$/i, { timeout: -1 }, async ( elemSelector ) => {
|
|
759
|
+
// `page` timeout will deal with custom timeout
|
|
760
|
+
await scope.tapSelectorToNavigate( scope, elemSelector, 2000 );
|
|
761
761
|
});
|
|
762
762
|
|
|
763
|
-
When(/I tap the "([^"]+)" element and
|
|
764
|
-
//
|
|
765
|
-
await scope.
|
|
763
|
+
When(/I tap the "([^"]+)" element and stay(?: on the same page)?$/i, { timeout: -1 }, async ( selector ) => {
|
|
764
|
+
// `page` timeout will deal with custom timeout
|
|
765
|
+
await scope.tapSelectorAndStay( scope, { selector });
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
When(/I tap the "([^"]+)" element and wait (?:for )?([0-9]+) second(?:s)?/i, { timeout: -1 }, async ( elemSelector, waitTime ) => {
|
|
769
|
+
// TODO: Why is it the developer can't just wait after the step?
|
|
770
|
+
// Also, this is always supposed to navigate, and that handles its own waiting.
|
|
771
|
+
|
|
772
|
+
// `page` timeout will deal with custom timeout
|
|
773
|
+
await scope.tapSelectorToNavigate( scope, elemSelector, waitTime * 1000 )
|
|
766
774
|
});
|
|
767
775
|
|
|
768
776
|
When(/I check the page for (?:accessibility|a11y) issues/i, {timeout: -1}, async () => {
|
|
@@ -967,7 +975,7 @@ When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async (
|
|
|
967
975
|
* 1. I set the var "benefits['SSI']" to "false"
|
|
968
976
|
* 1. I set the variable "rent_interval" to "weekly"
|
|
969
977
|
*/
|
|
970
|
-
//
|
|
978
|
+
// `page` timeout will deal with custom timeout
|
|
971
979
|
await scope.steps.set_regular_var( scope, var_name, answer )
|
|
972
980
|
|
|
973
981
|
});
|
|
@@ -975,7 +983,7 @@ When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async (
|
|
|
975
983
|
When(/I set the var(?:iable)? "([^"]+)" to ?(?:the)? ?(?:GitHub)? secret "([^"]+)"/i, { timeout: -1 }, async ( var_name, set_to_env_name ) => {
|
|
976
984
|
/* Set a non-story table variable to a secrete value. Same as the above, but reads the value from `process.env` */
|
|
977
985
|
// This could theoretically tap an element, so
|
|
978
|
-
//
|
|
986
|
+
// `page` timeout will deal with custom timeout
|
|
979
987
|
await scope.steps.set_secret_var( scope, var_name, set_to_env_name )
|
|
980
988
|
});
|
|
981
989
|
|
|
@@ -995,7 +1003,7 @@ Then(/I upload "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async ( filenames, var_
|
|
|
995
1003
|
|
|
996
1004
|
When('I tap the defined text link {string}', { timeout: -1 }, async ( phrase ) => {
|
|
997
1005
|
/** Tap a link that doesn't navigate. Depends on the language of the text. */
|
|
998
|
-
//
|
|
1006
|
+
// `page` timeout will deal with custom timeout
|
|
999
1007
|
await scope.steps.tap_term( scope, phrase );
|
|
1000
1008
|
});
|
|
1001
1009
|
|
|
@@ -1022,7 +1030,7 @@ When(/I download "([^"]+)"$/, { timeout: -1 }, async ( filename ) => {
|
|
|
1022
1030
|
* page load. Just stick to the name of the file.
|
|
1023
1031
|
*/
|
|
1024
1032
|
// TODO:
|
|
1025
|
-
// This should be using `scope.
|
|
1033
|
+
// This should be using `scope.tapElementAndStayOnPage()`? When someone clicks on a link to
|
|
1026
1034
|
// download a document, it sends a request to the server, which could be reloading
|
|
1027
1035
|
return wrapPromiseWithTimeout(
|
|
1028
1036
|
scope.steps.download( scope, filename ),
|