@suffolklitlab/alkiln 4.10.3 → 4.10.4-cli-playground-2

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.
@@ -0,0 +1 @@
1
+ * @plocket @BryceStevenWilley
package/CHANGELOG.md CHANGED
@@ -38,9 +38,27 @@ Format:
38
38
  ### Security
39
39
  -
40
40
  -->
41
- <!-- ## [Unreleased] -->
41
+ ## [Unreleased]
42
+
43
+ ### Added
44
+ - A new script: `alkiln-run`, which acts like `npm run cucumber`, but can
45
+ be run in any directory, not just in an npm package.
46
+ - Ability to run ALKiln from an interview by a logged-in user
42
47
 
48
+ ### Changed
49
+ - upgraded cucumber v8.6.0
50
+ - using cucumber's JS API to run tests. For more details on how it works,
51
+ see [the cucumber-js docs](https://github.com/cucumber/cucumber-js/blob/main/docs/javascript_api.md).
52
+ - the github action no longer runs `npm run XYZ`; it directly calls scripts,
53
+ e.g. `alkiln-setup`, `alkiln-run`, `alkiln-takedown`
54
+ - don't print the ["publish this cucumber report" message](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options)
43
55
 
56
+ ## [4.10.4] - 2023-02-15
57
+ ### Added
58
+ - Contribution docs
59
+
60
+ ### Fixed
61
+ - #511, couldn't take screenshots of signature pages https://github.com/SuffolkLITLab/ALKiln/issues/511
44
62
 
45
63
  ## [4.10.3] - 2023-01-11
46
64
  ### Removed
@@ -563,4 +581,4 @@ Format:
563
581
  - `example.feature` - an example test to get a project started
564
582
  - `github/workflows/run_form_tests.yml` - Automated github workflow to run tests when code is pushed and a manual trigger for testing without needing to push
565
583
  - `.gitignore` - ignores files that are generated by the tests and shouldn't be added to the repo
566
- - `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
584
+ - `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
@@ -0,0 +1,205 @@
1
+ # Contributing
2
+
3
+ For documentation changes, you can just edit and make a pull request. Ignore the instructions below.
4
+
5
+ ## Setup
6
+
7
+ If you are not already in contact with us, you can join the #assembly-line-test channel in the [docassemble Slack](https://docassemble.org/docs/support.html#tocAnchor-1-1) or [make a new issue](https://github.com/SuffolkLITLab/ALKiln/issues/new) and mention @niharikasingh to let us know you're interested.
8
+
9
+ 1. Asking to join as a contributor is simplest. You can fork, but you will have to change lines in your `.env` file and may have to set GitHub secrets in your repository. You have to talk to us either way.
10
+ 1. Clone the repository locally: `git clone git@github.com:SuffolkLITLab/ALKiln.git`
11
+ 1. Run `npm install`
12
+ 1. Create [your `.env` file](#env) at the root of the project.
13
+ 1. Contact @niharikasingh to get important information for your `.env` file.
14
+ 1. Run `npm run setup`
15
+
16
+ Advanced contributors: If you're going to work on our docassemble internal testing interviews, do so as a contributor on this repository. Avoid forking if you can. The reasons are complex. If you feel a need to fork, reach out to us.
17
+
18
+ ## PRs
19
+
20
+ Before making a pull request
21
+
22
+ 1. Search issues to see if what you are thinking of has been discussed yet.
23
+ 1. Make an issue about the change you are thinking of.
24
+ 1. Assign yourself to an issue.
25
+ 1. Make a new branch.
26
+ 1. Make your edits. Feel free to [run tests](#running-internal-tests) as you go.
27
+ 1. When you're ready to push
28
+ 1. Run `npm run test` to make sure your code passes all our tests.
29
+ 1. Make an entry in the `CHANGELOG.md` describing your change.
30
+ 1. AVOID changing the version number. We'll do that after merging your changes.
31
+ 1. If needed, update the `README.md` (e.g. the cheat sheet section) and/or `CONTRIBUTING.md` docs. Changes to our architecture might require editing `CONTRIBUTING.md`.
32
+ 1. Push to GitHub.
33
+ 1. Wait for the GitHub tests to pass. You may have to re-run failing tests—server restarts or race conditions may interfere with them. We have reduced them greatly, but some are impossible to avoid.
34
+ 1. Make your pull request. Work in progress pull requests are great too. A pull request must be reviewed before it can be merged.
35
+
36
+ ## .env
37
+
38
+ You need to make your own `.env` file. It contains sensitive variables, like passwords. We use them for our internal tests. The file should look something like the below:
39
+
40
+ ```bash
41
+ # For everyone
42
+
43
+ DOCASSEMBLE_DEVELOPER_API_KEY=<ask @niharikasingh>
44
+ SERVER_URL=https://apps-dev.suffolklitlab.org
45
+ REPO_URL=https://github.com/SuffolkLITLab/alkiln
46
+ SERVER_RELOAD_TIMEOUT_SECONDS=
47
+ MAX_SECONDS_FOR_SETUP=
48
+
49
+ # For internal tests
50
+
51
+ # Set DEBUG to 1 to run in non-headless mode and see the test run live
52
+ DEBUG=
53
+ # If you edit interview files (.yml files), you should push to a new
54
+ # branch and update BRANCH_PATH to that branch name
55
+ BRANCH_PATH=v4
56
+ USER1_EMAIL=<ask @niharikasingh>
57
+ USER1_PASSWORD=<ask @niharikasingh>
58
+ SECRET_VAR1=secret-var1-value
59
+ SECRET_VAR2=secret-var2-value
60
+ SECRET_FOR_MISSING_FIELD=secret for missing field
61
+ SECRET_INVALID_THERE_IS_ANOTHER=invalid value for there_is_another
62
+ ```
63
+
64
+ ## Running internal tests
65
+
66
+ Only run this the first time you run tests in this repository to make the code available to test on the docassemble server:
67
+
68
+ ```bash
69
+ npm run setup
70
+ ```
71
+
72
+ If you run `takedown`, you will then have to run `setup` again.
73
+
74
+ Use the syntax below to trigger all tests (cucumber and unit tests):
75
+
76
+ ```bash
77
+ npm run test
78
+ ```
79
+
80
+ Run only cucumber tests:
81
+
82
+ ```bash
83
+ npm run cucumber
84
+
85
+ # To test Features or Scenarios specific tags:
86
+ npm run cucumber -- "--tags" "@tagname"
87
+ ```
88
+
89
+ To run the unit tests in isolation:
90
+
91
+ ```bash
92
+ npm run unit
93
+ ```
94
+
95
+ If you or someone else changes the interview code in `./docassemble`, you have to clean up the old data on the server before running `setup` again:
96
+
97
+ ```bash
98
+ npm run takedown
99
+ ```
100
+
101
+ ## How the tests run
102
+
103
+ Our tests require the user/developer to have a docassemble server on which they host these interviews (online forms) and at least one developer account. With our help, our users set up the tests to run when they commit new code or to run manually. When GitHub runs the tests, it does the following:
104
+
105
+ 1. Sets up on the docassemble server. In a given developer account:
106
+ 1. Creates a new interview for this particular branch of the repository.
107
+ 1. Pulls in the code from that branch into the docassemble server.
108
+ 1. Runs the tests
109
+ 1. Pretends to be an anonymous account that comes to the form and inputs answers.
110
+ 1. Writes up reports and possibly takes screenshots to show to the developer.
111
+ 1. Cleans up by deleting the interview it created
112
+
113
+ When the developer commits code to GitHub, their account triggers our code, passing along variables that our code needs, like their GitHub secret of the API key for their docassemble server developer account.
114
+
115
+ ## Very general architecture of files and folders
116
+
117
+ An honest look at our current project architecture—some of our files and folders, what they do, and how they interact.
118
+
119
+ ### Logic architecture
120
+
121
+ ALKiln uses [cucumber](https://cucumber.io/docs/installation/javascript/)[^1] with [Gherkin](https://cucumber.io/docs/gherkin/reference/) syntax, [puppeteer](https://pptr.dev/), and [chai](https://www.chaijs.com/).
122
+
123
+ #### .feature files
124
+
125
+ The `.feature` files are written in Gherkin, a syntax cucumber uses. The "code" in there relies on the functions set up in `./lib/steps.js`.
126
+
127
+ ```js
128
+ // The test_something.feature file step
129
+ Then I sign
130
+
131
+ // relies on the code in steps.js
132
+ Then('I sign', { timeout: -1 }, async () => {
133
+ return wrapPromiseWithTimeout(
134
+ scope.steps.sign( scope ),
135
+ scope.timeout
136
+ );
137
+ });
138
+ ```
139
+
140
+ #### steps.js
141
+
142
+ `./lib/steps.js` defines all the Gherkin sentences that our users (the developers) can write. See [cucumber documentation on defining steps](https://cucumber.io/docs/cucumber/step-definitions/?lang=javascript). We sometimes use "cucumber expressions" and, but often use regular expressions as we often want to be more flexible about the user's input.
143
+
144
+ The file handles things like:
145
+
146
+ * Creating state
147
+ * Defining steps for the users/developers
148
+ * Defining steps for internal testing, like testing for appropriate errors and error messages
149
+ * Finishing a Scenario
150
+ * Finishing all tests
151
+
152
+ #### scope.js
153
+
154
+ `./lib/steps.js` makes heavy use of `./lib/scope.js`, where most of the action happens. We have decided to mostly keep it in one file to avoid bouncing back and forth between files.
155
+
156
+ Many `./lib/steps.js` steps make use of the `scope.steps` object. It is there to avoid duplicating code between steps.
157
+
158
+ The file handles things like:
159
+
160
+ * Finding form fields on a page
161
+ * Filling out form fields
162
+ * Generating random data for form fields
163
+ * Waiting for pages to load
164
+ * Checking for server restarts
165
+ * Generating test report content
166
+
167
+ ### Logging
168
+
169
+ `./lib/utils/log.js` takes care of logging information of various kinds (normal, debug, error) to the command line in ways we hope are useful. We should probably find a logging library instead, but this is what we have for now. An important feature is that it ensures the messages clearly belong to ALKiln and not to some other process.
170
+
171
+ ### session_vars.js
172
+
173
+ The `./lib/utils/session_var.js` file keeps track of what you might otherwise think of as environment variables. We like to think of them as constants, but some of them do need to go through functions, so for consistency we get them all through functions. Also because it was easier to test them when they are functions.
174
+
175
+ ### Setup and takedown logic
176
+
177
+ All of the setup and takedown logic is in the `./lib/docassemble` directory. It is named after the docassemble API code that is in there. Also, setup and takedown interact very closely with docassemble and the docassemble server.
178
+
179
+ Other files also use the docassemble API functions, so this folder is in a bit of a messy position.
180
+
181
+ ### Bash commands
182
+
183
+ When you `npm run` the scripts in our `./package.json` you can setup, run, or takedown tests. We are working on converting this to a command line tool to avoid some complexity in GitHub actions, increase security, and align more with what this framework has grown into. (TODO: Define this better)
184
+
185
+ ### GitHub composite action
186
+
187
+ Our `./action.yml` is a [composite action](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action). The users/developers let it do most of the work for them. It installs npm, installs our repo, then uses our repo to set up the tests, run the tests, generate and save reports, errors, and screenshots, and clean up the tests.
188
+
189
+ ### Internal tests
190
+
191
+ We have our own docassemble package in our repo to test our own end-to-end tests. Most of what the package needs is in `./docassemble`. That docassemble package is what the test setup pulls into the docassemble server. Our internal end-to-end cucumber tests are `.feature` files that are stored in `./docassemble/ALKilnTests/data/sources`. The interview (online form) `.yml` files that those `.feature` files use are also deep in the `./docassemble` package. You probably won't be touching the interview files. If you get curious, feel free to ask us.
192
+
193
+ There are other files the docassemble package needs, like `./setup.py`, so if you see them around, don't worry about them.
194
+
195
+ Our unit tests are in `./tests/unit_tests` and their filenames end in `.test.js`. Like the cucumber tests, they use chai for assertions. The fixtures for the tests are also contained in that folder and end in `.fixtures.js`.
196
+
197
+ If you want to know the commands, you can go to the [instructions for running tests](#running-internal-tests).
198
+
199
+ ### Files that you don't need to look at
200
+
201
+ `index.js` and `world.js` are short files that cucumber needs. `./lib/utils/langs.js` used to work, but we're not sure it does anymore. No one has used it yet, so we're waiting to experiment until someone expresses a need or we have some extra time.
202
+
203
+ ## Footnotes
204
+
205
+ [1] To be clear, our framework is a misuse of cucumberjs. cucumberjs is geared towards behavior driven development. We try to make BDD available to our developers, but it's not always possible and not necessarily our goal.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 plocket
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,26 +1,66 @@
1
1
  # ALKiln
2
- Integrated automated end-to-end testing with docassemble, puppeteer, cucumber, and chai.
3
2
 
4
- Works well with https://github.com/suffolklitlab/docassemble-AssemblyLine but isn't
5
- dependent on it.
3
+ ALKiln, or Assembly Line Kiln, is an open source project that helps [docassemble](https://docassemble.org/) developers create automated tests for their interviews (online forms). It works well with the [Document Assembly Line Project](https://suffolklitlab.org/docassemble-AssemblyLine-documentation) but isn't dependent on it.
6
4
 
7
- Formerly named "docassemble-cucumber"
5
+ <!--
6
+ https://github.com/18F/open-source-guide/blob/18f-pages/pages/making-readmes-readable.md
7
+ √ What is this repo or project? (You can reuse the repo description you used earlier because this section doesn’t have to be long.)
8
+ √ How does it work?
9
+ √ Who will use this repo or project?
10
+ √ What is the goal of this project?
11
+ -->
8
12
 
9
- ## Documentation
10
- Read about how to use this framework at https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/.
13
+ ## Use ALKiln
11
14
 
12
- ## Integration tests
13
- To setup for the integration tests, create the project on the server:
14
- ```
15
+ Read about how to use this framework in [Assembly Line's documentation](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/).
16
+
17
+ If you have questions or need to report a bug, [start a new issue](https://github.com/SuffolkLITLab/ALKiln/issues/new).
18
+
19
+ The following documentation is for internal developers—those working on ALKiln itself.
20
+
21
+ ## Our code
22
+
23
+ Our code pretends to be a human interacting with online forms (interviews) that have been made with the [docassemble](https://docassemble.org) open source platform. It looks at the HTML DOM, manipulates it, and interacts with it.
24
+
25
+ It uses [cucumber](https://cucumber.io/docs/installation/javascript/), [puppeteer](https://pptr.dev/), and [chai](https://www.chaijs.com/).
26
+
27
+ ## Our users
28
+
29
+ Our users are developers of online forms (interviews). Usually they're very new to coding and are unfamiliar with GitHub and with a lot of coding concepts. If they know how to code, it tends to be in Python because that's what docassemble uses. They usually don't have the resource or funding to put into learning these skills.
30
+
31
+ For example, a pro bono law firm is trying to write a form that people can use to create a restraining order. They're coding it using docassemble. Their users are already going through a hard time, and the forms make them outline their heart wrenching experiences in detail. The developers need to make sure their users don't get system errors right in the middle of that. They use our framework to run the end-to-end tests.
32
+
33
+ They do good work and we hope to give them a less code-focused way to automate their testing. They avoid having to learn Javascript, GitHub actions, and other such things.
34
+
35
+ To do that, we use [cucumberjs](https://cucumber.io/docs/installation/javascript/), which uses [Gherkin](https://cucumber.io/docs/gherkin/reference/) syntax to give a user/developer a more human-like syntax to instruct alkiln on what answers to give to the user's online form.
36
+
37
+ ## Contributing
38
+
39
+ Read about contributing in our [CONTRIBUTING.md document](CONTRIBUTING.md). Here's a quick cheat sheet for some commands once you're up and running:
40
+
41
+ ## Cheat sheet
42
+
43
+ *Once you've already read the contributing documentation, you can use these as quick reminders for running our internal tests.*
44
+ To set up for the integration tests, create the project on the server:
45
+
46
+ ```bash
15
47
  npm run setup
16
48
  ```
17
49
 
18
50
  Use the syntax below to trigger specific tags:
19
- ```
51
+
52
+ ```bash
20
53
  npm run cucumber -- "--tags" "@tagname"
21
54
  ```
22
55
 
23
56
  To run the unit tests in isolation:
24
- ```
57
+
58
+ ```bash
25
59
  npm run unit
26
60
  ```
61
+
62
+ If you or someone else changes the interview code in `./docassemble`, you have to clean up the old data on the server before running `setup` again:
63
+
64
+ ```bash
65
+ npm run takedown
66
+ ```
package/action.yml CHANGED
@@ -5,15 +5,6 @@ inputs:
5
5
  SERVER_URL:
6
6
  description: 'The url of your docassemble server. This should be in your GitHub SECRETS or the SECRETS of your org.'
7
7
  required: true
8
- PLAYGROUND_EMAIL:
9
- description: 'The email of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
10
- required: true
11
- PLAYGROUND_PASSWORD:
12
- description: 'The password of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
13
- required: true
14
- PLAYGROUND_ID:
15
- description: 'The id of your docassemble testing account. This should be in your GitHub SECRETS or the SECRETS of your org.'
16
- required: true
17
8
  DOCASSEMBLE_DEVELOPER_API_KEY:
18
9
  description: 'API key of the testing account with developer permissions on your docassemble server.'
19
10
  required: true
@@ -33,6 +24,16 @@ inputs:
33
24
  required: false
34
25
  default: '120'
35
26
 
27
+ ## Testing by hand on the server. Should only be created by interview code.
28
+ #_USER_ID:
29
+ # description: 'AVOID SETTING THIS YOURSELF. The id of your docassemble playground account. This is created and used by ALKiln when testing manually on your server.'
30
+ # required: false
31
+ #_PROJECT_NAME:
32
+ # description: 'AVOID SETTING THIS YOURSELF. The name of the Playground Project to test manually on your server. ALKiln will ask you for this information.'
33
+ # required: false
34
+ #_ORIGIN:
35
+ # description: 'AVOID SETTING THIS YOURSELF. The origin that's triggering this round of tests.'
36
+
36
37
  runs:
37
38
  using: "composite"
38
39
  steps:
@@ -55,12 +56,12 @@ runs:
55
56
  echo "REPO_URL=${{ github.server_url }}/${{ github.repository }}" >> $GITHUB_ENV
56
57
  echo "BRANCH_PATH=${{ github.ref }}" >> $GITHUB_ENV
57
58
  echo "BASE_URL=${{ inputs.SERVER_URL }}" >> $GITHUB_ENV
58
- echo "PLAYGROUND_EMAIL=${{ inputs.PLAYGROUND_EMAIL }}" >> $GITHUB_ENV
59
- echo "PLAYGROUND_PASSWORD=${{ inputs.PLAYGROUND_PASSWORD }}" >> $GITHUB_ENV
60
- echo "PLAYGROUND_ID=${{ inputs.PLAYGROUND_ID }}" >> $GITHUB_ENV
61
59
  echo "DOCASSEMBLE_DEVELOPER_API_KEY=${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}" >> $GITHUB_ENV
62
60
  echo "ALKILN_VERSION=${{ inputs.ALKILN_VERSION }}" >> $GITHUB_ENV
63
61
  echo "MAX_SECONDS_FOR_SETUP=${{ inputs.MAX_SECONDS_FOR_SETUP }}" >> $GITHUB_ENV
62
+
63
+ # echo "_USER_ID=${{ inputs._USER_ID }}" >> $GITHUB_ENV
64
+ # echo "_PROJECT_NAME=${{ inputs._PROJECT_NAME }}" >> $GITHUB_ENV
64
65
  shell: bash
65
66
  - name: "ALKiln: confirm info"
66
67
  run: |
@@ -68,58 +69,31 @@ runs:
68
69
  echo -e "\nALKiln version is $ALKILN_VERSION\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nMAX_SECONDS_FOR_SETUP is $MAX_SECONDS_FOR_SETUP\nManually added languages are $EXTRA_LANGUAGES\n"
69
70
  shell: bash
70
71
 
71
- # This doesn't use an already established package-lock.json and that
72
- # comes with some types of test breaking risks. Same problem as before.
73
- - name: Temporarily overwrite or create package.json
74
- run: |
75
- cat << EOF > "package.json"
76
- {
77
- "scripts": {
78
- "test": "npm run langs_setup && npm run cucumber -- $@",
79
- "cucumber_base": "./node_modules/.bin/cucumber-js --require ./node_modules/@suffolklitlab/alkiln/lib/index.js",
80
- "cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature --retry 1; }; run_cucumber",
81
- "langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./node_modules/.bin/alkiln-cucumber docassemble/*/data/sources/*_lang*.feature",
82
- "langs_setup": "node_modules/.bin/alkiln-langs 'docassemble/*/data/sources/*.feature'",
83
- "setup": "node_modules/.bin/alkiln-setup",
84
- "takedown": "node_modules/.bin/alkiln-takedown"
85
- },
86
- "dependencies": {
87
- "colors": "1.4.0",
88
- "@cucumber/cucumber": "7.3.2",
89
- "@suffolklitlab/alkiln": "$ALKILN_VERSION"
90
- }
91
- }
92
- EOF
93
- # Keep in sync with the cucumber.js in root
94
- cat << EOF > "cucumber.js"
95
- module.exports = {
96
- default: "--require ./lib/index.js --format progress --format summary:cucumber-report.txt"
97
- }
98
- EOF
99
- shell: bash
100
-
101
72
  # Install node
102
73
  - name: "ALKiln: Install node packages"
103
74
  uses: actions/setup-node@v1
104
75
  with:
105
76
  node-version: "17"
106
- - run: npm install
77
+ # npm install -g "@suffolklitlab/alkiln@$ALKILN_VERSION"
78
+ - name: Install ALKiln directly from npm
79
+ run: |
80
+ npm install -g github:SuffolkLITLab/ALKiln#lighter_package_json
107
81
  shell: bash
108
82
 
109
83
  # Run tests
110
84
  - name: "ALKiln: Create a Project and pull the package from GitHub"
111
- run: npm run setup
85
+ run: alkiln-setup
112
86
  shell: bash
113
87
  - name: "ALKiln: Run tests"
114
88
  if: ${{ success() }}
115
- run: npm run test -- ${{ github.event.inputs.tags && format('"--tags" "{0}"', github.event.inputs.tags) }}
89
+ run: alkiln-run ${{ github.event.inputs.tags && format('"{0}"', github.event.inputs.tags) }}
116
90
  shell: bash
117
91
  - run: echo -e "\n\n====ALKiln could not create a project on your server's testing account or pull your package into it. Check the messages above this line.\n\n"
118
92
  if: ${{ failure() }}
119
93
  shell: bash
120
94
  - name: "ALKiln: Delete project from docassemble test account"
121
95
  if: ${{ always() }}
122
- run: npm run takedown
96
+ run: alkiln-takedown
123
97
  shell: bash
124
98
 
125
99
  # Upload artifacts that subscribers can download on the Actions summary page
package/cucumber.js CHANGED
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
- default: "--require ./lib/index.js --format progress --format summary:cucumber-report.txt"
2
+ default: "--publish-quiet --require ./lib/index.js --format progress --format summary:cucumber-report.txt"
3
3
  }
@@ -141,13 +141,27 @@ da_i.is_server_responding = async function ( dev_id_options ) {
141
141
 
142
142
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_dev_id()'
143
143
  da_i.get_dev_id = async function ( dev_id_options ) {
144
- /** For consistency, wrap _da_REST.get_dev_id().
145
- * See https://docassemble.org/docs/api.html#user. */
144
+ /** Get user id, either locally or on the server.
145
+ * For consistency, wrap _da_REST.get_dev_id().
146
+ * See https://docassemble.org/docs/api.html#user.
147
+ *
148
+ * @param dev_id_options {obj} Options for getting dev_id
149
+ * @param dev_id_options.timeout {int} Required if options is defined. Max MS
150
+ * to allow for server request.
151
+ */
146
152
 
147
- // Default timeouts
148
- let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
149
- let response = await _da_REST.get_dev_id( timeout );
150
- return response.data.id;
153
+ let dev_id = null;
154
+
155
+ if ( session_vars.get_origin() === 'interview' ) {
156
+ dev_id = session_vars.get_user_id();
157
+ } else {
158
+ // Default timeouts
159
+ let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
160
+ let response = await _da_REST.get_dev_id( timeout );
161
+ dev_id = response.data.id;
162
+ }
163
+
164
+ return dev_id;
151
165
  }; // Ends da_i.get_dev_id();
152
166
 
153
167
 
@@ -11,6 +11,7 @@ let get_base_interview_url = async function () {
11
11
  * when the user is not logged in.) We can't use `reset=2` because it caused an
12
12
  * 'Unable to locate interview session' error. */
13
13
  let server_url = session_vars.get_da_server_url();
14
+
14
15
  let dev_id = await docassemble_api_interface.get_dev_id();
15
16
  let project_name = session_vars.get_project_name();
16
17
  return `${ server_url }/interview?new_session=1&i=docassemble.playground${ dev_id }${ project_name }%3A`;
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const log = require('./utils/log');
5
+ const session_vars = require('./utils/session_vars');
6
+
7
+ // import {loadConfiguration, loadSupport, runCucumber } from '@cucumber/cucumber/api';
8
+ const cucumber_api = require('@cucumber/cucumber/api');
9
+
10
+ (async function main() {
11
+ let argv = process.argv;
12
+
13
+ let just_args = argv.slice(2);
14
+
15
+ const environment = { cwd: process.cwd() };
16
+ const provided_config = {
17
+ "publishQuiet": true,
18
+ "require": "./lib/index.js",
19
+ "format": ["progress", "summary:cucumber-report.txt"],
20
+ "paths": ["docassemble/*/data/sources/*.feature",
21
+ // // "1" is the user id. We also need to get the Project name, etc.
22
+ // "/usr/share/docassemble/files/playgroundsources/1/test.feature"
23
+ ],
24
+ "retry": 1,
25
+ }
26
+
27
+ // If they're running it from an interview on their server instead of from GitHub
28
+ if ( session_vars.get_origin() === `interview` ) {
29
+ provided_config.paths.push(`/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
30
+ }
31
+
32
+ let tag_vals = just_args;
33
+ if ( session_vars.get_origin() === `interview` ) {
34
+ // Should tags be given as a string or a list? String would give option to
35
+ // give a boolean expression. TODO: offer a dropdown with available tags.
36
+ tag_vals = session_vars.get_tags();
37
+ }
38
+
39
+ if (tag_vals.length > 0) {
40
+ // Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
41
+ // If we get a list of only tags, then "or" them all together
42
+ if (tag_vals.every((val) => val.startsWith(`@`))) {
43
+ provided_config.tags = tag_vals.join(` or `);
44
+ }
45
+ // If we get a list of seemingly no tags, assume they meant to use tags
46
+ else if (tag_vals.every((val) => !val.startsWith(`@`))) {
47
+ provided_config.tags = tag_vals.map((val) => `@${ val }`).join(` or `);
48
+ }
49
+ // If it's a mix of tags and no tags, it's probably a boolean expression!
50
+ else {
51
+ provided_config.tags = tag_vals.join(` `);
52
+ }
53
+ }
54
+
55
+ const { runConfiguration } = await cucumber_api.loadConfiguration({
56
+ provided: provided_config
57
+ }, environment);
58
+ var path = require('path');
59
+ var codeDir = path.dirname(require.main.filename);
60
+ const support = await cucumber_api.loadSupport({ support: {
61
+ requireModules: [`${ codeDir }/index.js`],
62
+ requirePaths: [ `${ codeDir }/index.js`],
63
+ importPaths: [],
64
+ }, sources: {paths: []}}, environment);
65
+
66
+ // TODO: Maybe only log these for debug?
67
+ console.log(`ALKiln run_cucumber.js configuration:`);
68
+ console.log(`%o`, runConfiguration);
69
+
70
+ const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
71
+ console.log(`\nALKiln run_cucumber.js succeeded:`);
72
+ console.log(`%o`, success);
73
+ const exitCode = success ? 0 : 1;
74
+ process.exitCode = exitCode;
75
+
76
+ // This is hardcoded information from `cucumber.js`. Now we on cucumber 8, we should
77
+ // change that the cucumber.json and actually parse the info from it.
78
+ // This code isn't used for anything other than detecting and removing color codes
79
+ let summary_file_name = 'cucumber-report.txt';
80
+ fs.readFile(summary_file_name, 'utf8', (err, data) => {
81
+ if (err) {
82
+ console.error(err);
83
+ return;
84
+ }
85
+
86
+ // Replace terminal color codes, if present
87
+ // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
88
+ data = data.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
89
+
90
+ // Put it in the correct file in the correct folder
91
+ let artifacts_path = session_vars.get_artifacts_path_name();
92
+ fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
93
+ });
94
+ })();
package/lib/scope.js CHANGED
@@ -52,7 +52,7 @@ let is_secret_var = function(row) {
52
52
  /* `row` might be a row, a source row, or an original row. */
53
53
  let original = get_original_row(row);
54
54
  return original.flags !== undefined && original.flags.secret_name !== undefined;
55
- }
55
+ };
56
56
 
57
57
  let fromBase64 = function(base64_str) {
58
58
  /* Takes a base64 encoded string and returns it decoded.
@@ -636,54 +636,6 @@ module.exports = {
636
636
 
637
637
  return false;
638
638
  },
639
-
640
- setVar: async function ( scope, var_name, value ) {
641
- /* Use variable names and values to interact with buttons that
642
- * have values, text input elements, textareas, and dropdowns.
643
- * Returns the element that was found.
644
- *
645
- * TODO:
646
- * - Birthdate fields that will be showing up relatively soon
647
- * - Time fields?
648
- */
649
- let base64_var_name = toBase64( var_name );
650
-
651
- // Will catch some radio and checkbox inputs too, which will then be rejected
652
- // Note: I think `showif`s with `code` don't use `data-saveas`
653
- let to_manipulate = await Promise.race([
654
- scope.page.waitForSelector(`button[name="${base64_var_name}"][value="${value}"]`), // button
655
- scope.page.waitForSelector(`:not(button)[name="${base64_var_name}"]`), // others (not showif)
656
- scope.page.waitForSelector(`*[data-saveas="${base64_var_name}"] input`), // text input showif
657
- scope.page.waitForSelector(`*[data-saveas="${base64_var_name}"] textarea`), // textarea showif
658
- scope.page.waitForSelector(`*[data-saveas="${base64_var_name}"] select`), // select showif/hideif
659
- ]);
660
-
661
- // Throw error for checkboxes and radios
662
- let type = await to_manipulate.evaluate( elem => { return elem.getAttribute('type') });
663
- if ( type === `checkbox` || type === `radio` ) {
664
- throw new TypeError(`You need the ${type} step, e.g. 'I tap the ${type} variable "some_dict" with the value "choice_value"'`);
665
- }
666
-
667
- // Actions based on element
668
- let tagName = await to_manipulate.evaluate( elem => { return elem.tagName });
669
- tagName = tagName.toLowerCase();
670
-
671
- if ( tagName === `button` ) { // Tap buttons
672
- await scope.tapElement( scope, to_manipulate );
673
-
674
- } else if ( tagName === `select` ) { // Choose drop-down option
675
- await to_manipulate.select(value);
676
-
677
- } else if ( tagName === `input` || tagName === `textarea` ) {
678
- // Set text field values (includes any other input fields)
679
- await to_manipulate.evaluate( el => { el.value = '' });
680
- await to_manipulate.type( value );
681
- // TODO: Add hideif to this too
682
- }
683
-
684
- // Do your own waiting if you need to in the step
685
- return to_manipulate;
686
- }, // Ends scope.setVar()
687
639
 
688
640
  continue_button_selector: `fieldset.da-field-buttons button[type="submit"].btn-primary`,
689
641
  signature_selector: `fieldset .dasigsave`,
@@ -780,7 +732,6 @@ module.exports = {
780
732
  load: async function ( scope, file_name ) {
781
733
  /* Try to load the page. Should we also pass a `timeout` arg or just
782
734
  * use scope.timeout? */
783
-
784
735
  await scope.addToReport(scope,{
785
736
  type: `row`,
786
737
  value: `Trying to load the interview`
@@ -801,17 +752,15 @@ module.exports = {
801
752
  let base_url = await get_base_interview_url();
802
753
  interview_url = `${ base_url }${ file_name }`;
803
754
  }
755
+
804
756
  if ( session_vars.get_debug() ) { console.log( `interview_url:`, interview_url ); }
805
757
 
806
758
  if ( !scope.page ) { scope.page = await scope.browser.newPage(); }
807
759
 
808
760
  // TODO: implement and use scope.handle_possible_timeout()
809
761
  try {
810
-
811
762
  await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout })
812
-
813
763
  } catch ( error ) {
814
-
815
764
  // If the page didn't load because it took to long to load
816
765
  if ( error.name === `TimeoutError` ) {
817
766
 
@@ -820,7 +769,6 @@ module.exports = {
820
769
 
821
770
  // If the page didn't load because of any other reason
822
771
  } else {
823
-
824
772
  // Throw any non-timeout error
825
773
  scope.addToReport( scope, {
826
774
  type: `error`,
@@ -830,14 +778,12 @@ module.exports = {
830
778
  } // ends if error is timeout error
831
779
 
832
780
  } // ends try/catch
833
-
834
781
  let load_result = await scope.getLoadData( scope );
835
782
  // Errors, or reaching timeout, causes an exception and is shown to the developer at the end
836
783
  if ( load_result.error ) {
837
784
  await scope.addToReport(scope, { type: `error`, value: `On final attempt to load interview, got "${ load_result.error }"` });
838
785
  expect( load_result.error ).to.equal( '' );
839
786
  }
840
-
841
787
  return scope.page;
842
788
  }, // Ends scope.load()
843
789
 
@@ -1736,7 +1682,7 @@ module.exports = {
1736
1682
  await scope.afterStep(scope, { waitForShowIf: true });
1737
1683
  },
1738
1684
  canvas: async function ( scope, { handle, answer }) {
1739
- await scope.sign( scope );
1685
+ await scope.draw_signature( scope );
1740
1686
  await scope.afterStep( scope );
1741
1687
  },
1742
1688
  input: async function ( scope, { handle, answer }) {
@@ -1919,7 +1865,7 @@ module.exports = {
1919
1865
  }, 500)
1920
1866
  },
1921
1867
 
1922
- sign: async function ( scope ) {
1868
+ draw_signature: async function ( scope ) {
1923
1869
  /* On a signature page, there should only be a single signature canvas
1924
1870
  * and a continue button. Draw in the #dasigcanvas element. `<canvas>`
1925
1871
  * takes a while to load. */
@@ -2327,6 +2273,23 @@ module.exports = {
2327
2273
  return scope;
2328
2274
  }, // Ends scope.throwPageError()
2329
2275
 
2276
+ take_a_screenshot: async ( scope,{ path }) => {
2277
+ /* Takes a jpeg screenshot. Avoids destroying signatures. */
2278
+
2279
+ let fullPage = true;
2280
+ let signature_elem = await scope.page.$(scope.signature_selector);
2281
+ if ( signature_elem !== null ) {
2282
+ fullPage = false;
2283
+ }
2284
+
2285
+ await scope.page.screenshot({
2286
+ path: path,
2287
+ type: 'jpeg',
2288
+ fullPage: fullPage,
2289
+ });
2290
+
2291
+ }, // Ends scope.take_a_screenshot()
2292
+
2330
2293
 
2331
2294
  //#####################################
2332
2295
  //#####################################
@@ -2349,14 +2312,10 @@ module.exports = {
2349
2312
  name = `screenshot_on-${ id }-${ date }`;
2350
2313
  }
2351
2314
 
2352
- await scope.page.screenshot({
2353
- path: `./${ scope.paths.scenario }/${ name }.jpg`,
2354
- type: 'jpeg',
2355
- fullPage: true
2356
- });
2315
+ await scope.take_a_screenshot( scope, { path: `./${ scope.paths.scenario }/${ name }.jpg` });
2357
2316
 
2358
2317
  await scope.afterStep( scope );
2359
- }, // Ends screenshot()
2318
+ }, // Ends scope.steps.screenshot()
2360
2319
 
2361
2320
  add_json_vars_to_report: async ( scope ) => {
2362
2321
  /** Uses https://docassemble.org/docs/functions.html#js_get_interview_variables
@@ -2371,7 +2330,7 @@ module.exports = {
2371
2330
  type: `json`,
2372
2331
  value: JSON.stringify( data, null, 2 )
2373
2332
  });
2374
- }, // Ends add_json_vars_to_report()
2333
+ }, // Ends scope.steps.add_json_vars_to_report()
2375
2334
 
2376
2335
  link_works: async ( scope, linkText ) => {
2377
2336
  let [link] = await scope.page.$x(`//a[contains(text(), "${ linkText }")]`);
@@ -2390,7 +2349,7 @@ module.exports = {
2390
2349
  linkPage.close()
2391
2350
 
2392
2351
  await scope.afterStep(scope);
2393
- }, // Ends link_works()
2352
+ }, // Ends scope.steps.link_works()
2394
2353
 
2395
2354
  set_regular_var: async ( scope, var_name, answer ) => {
2396
2355
  /** Set a non-story table variable (with or without a choice associated with it) to a value. Set:
@@ -2408,7 +2367,7 @@ module.exports = {
2408
2367
  // Don't continue if the variable doesn't cause navigation itself. Also,
2409
2368
  // if this variable cannot be set on this page, trigger an error
2410
2369
  await scope.setFields( scope, { var_data, ensure_navigation: false, ensure_all_vars_used: true });
2411
- }, // Ends set_regular_var()
2370
+ }, // Ends scope.steps.set_regular_var()
2412
2371
 
2413
2372
  set_secret_var: async ( scope, var_name, answer_env_name ) => {
2414
2373
  /** Sets a non-story table variable to a "secret" value (i.e. an env var) */
@@ -2425,7 +2384,7 @@ module.exports = {
2425
2384
  // Don't continue if the variable doesn't cause navigation itself. Also,
2426
2385
  // if this variable cannot be set on this page, trigger an error
2427
2386
  await scope.setFields( scope, { var_data, ensure_navigation: false, ensure_all_vars_used: true });
2428
- },
2387
+ }, // Ends scope.steps.set_secret_var
2429
2388
 
2430
2389
  tap_term: async ( scope, phrase ) => {
2431
2390
  /** Tap a link that doesn't navigate. Depends on the language of the text. */
@@ -2438,13 +2397,13 @@ module.exports = {
2438
2397
  await scope.tapElement( scope, link );
2439
2398
 
2440
2399
  await scope.afterStep(scope, {waitForShowIf: true});
2441
- }, // Ends tap_term()
2400
+ }, // Ends scope.steps.tap_term()
2442
2401
 
2443
2402
  sign: async ( scope ) => {
2444
2403
  /** Sign on a signature page. */
2445
- await scope.sign( scope );
2404
+ await scope.draw_signature( scope );
2446
2405
  await scope.afterStep(scope);
2447
- }, // Ends sign()
2406
+ }, // Ends scope.steps.sign()
2448
2407
 
2449
2408
  download: async ( scope, filename ) => {
2450
2409
  /* Taps the link that leads to the given filename to trigger downloading.
@@ -2466,7 +2425,7 @@ module.exports = {
2466
2425
  // Should this be using `scope.tapElement()`?
2467
2426
  await elem.evaluate( elem => { return elem.click(); });
2468
2427
  await scope.detectDownloadComplete( scope );
2469
- }, // Ends download()
2428
+ }, // Ends scope.steps.download()
2470
2429
 
2471
2430
  set_al_name: async ( scope, var_base, name_str ) => {
2472
2431
  /** Given a variable name and a space-separated string, fills the
@@ -2503,15 +2462,20 @@ module.exports = {
2503
2462
 
2504
2463
  let desired_pattern = name_patterns[( name_parts.length - 1 )];
2505
2464
 
2465
+ let raw_data = [];
2506
2466
  for ( let parti = 0; parti < name_parts.length; parti++ ) {
2507
2467
  let var_name = `${ var_base }.name.${ desired_pattern[ parti ] }`
2508
2468
  let value = name_parts[ parti ];
2509
- await scope.setVar( scope, var_name, value );
2469
+ raw_data.push({ var: var_name, value: value, trigger: scope.trigger_not_needed_flag });
2510
2470
  }
2511
2471
 
2472
+ let var_data = await scope.normalizeTable( scope, { var_data: raw_data, });
2473
+ // Avoid continuing automatically. If this var cannot be set on this page, error
2474
+ await scope.setFields( scope, { var_data, ensure_navigation: false, ensure_all_vars_used: true });
2475
+
2512
2476
  // Continue to next page...?
2513
2477
  await scope.afterStep(scope, {waitForShowIf: true});
2514
- }, // Ends set_al_name()
2478
+ }, // Ends scope.steps.set_al_name()
2515
2479
 
2516
2480
  set_al_address: async ( scope, var_base, address_str ) => {
2517
2481
  /** Given a variable name and a four-part address formatted as below,
@@ -2530,16 +2494,21 @@ module.exports = {
2530
2494
  let address_parts = split.concat( last_parts );
2531
2495
  let start = `${ var_base }.address.`;
2532
2496
 
2533
- let index = 0;
2534
- await scope.setVar( scope, `${start}address`, address_parts[0] );
2535
- // Ensure it's without 'Unit '/'unit '
2536
- await scope.setVar( scope, `${start}unit`, (address_parts[1].toLowerCase()).replace(/unit\s*/, '') );
2537
- await scope.setVar( scope, `${start}city`, address_parts[2] );
2538
- await scope.setVar( scope, `${start}state`, address_parts[3] );
2539
- await scope.setVar( scope, `${start}zip`, address_parts[4] );
2497
+ let no_trigger = scope.trigger_not_needed_flag; // just making the lines shorter
2498
+ let raw_data = [
2499
+ { var: `${start}address`, value: address_parts[0], trigger: no_trigger },
2500
+ // Remove accidental 'Unit' or 'unit ' text in case dev included it accidentally
2501
+ { var: `${start}unit`, value: (address_parts[1].toLowerCase()).replace(/unit\s*/, ''), trigger: no_trigger },
2502
+ { var: `${start}city`, value: address_parts[2], trigger: no_trigger },
2503
+ { var: `${start}state`, value: address_parts[3], trigger: no_trigger },
2504
+ { var: `${start}zip`, value: address_parts[4], trigger: no_trigger },
2505
+ ];
2506
+ let var_data = await scope.normalizeTable( scope, { var_data: raw_data, });
2507
+ // Avoid continuing automatically. If this var cannot be set on this page, error
2508
+ await scope.setFields( scope, { var_data, ensure_navigation: false, ensure_all_vars_used: true });
2540
2509
 
2541
2510
  await scope.afterStep(scope, {waitForShowIf: true});
2542
- }, // Ends set_al_address()
2511
+ }, // Ends scope.steps.set_al_address()
2543
2512
 
2544
2513
  sign_in: async function ( scope, { email_secret_name, password_secret_name }) {
2545
2514
  /** Allow the developer to log a user into their server using GitHub
package/lib/steps.js CHANGED
@@ -91,8 +91,9 @@ BeforeAll(async () => {
91
91
 
92
92
  // Make the folder to store all the files for this run of the tests
93
93
  scope.paths.artifacts = files.make_artifacts_folder();
94
+ console.log(`process.cwd()`, process.cwd());
94
95
  // Save its name in a file. `session_vars` can get it back later.
95
- // This is used by at least clean_reports.js.
96
+ // This is used by at least run_cucumber.js.
96
97
  session_vars.save_artifacts_path_name( scope.paths.artifacts );
97
98
 
98
99
  scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
@@ -103,7 +104,7 @@ BeforeAll(async () => {
103
104
  Before(async (scenario) => {
104
105
  // Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
105
106
  if (!scope.browser) {
106
- scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
107
+ scope.browser = await scope.driver.launch({ args: ['--no-sandbox'], headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
107
108
  }
108
109
  // Clean up all previously existing pages
109
110
  for (const page of await scope.browser.pages()) {
@@ -846,11 +847,8 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
846
847
  let buttons = await scope.steps.set_random_page_vars( scope );
847
848
 
848
849
  // Take a screenshot of the page before continuing or ending
849
- await scope.page.screenshot({
850
- path: `${ scope.paths.random_screenshots }/random-${ Date.now() }.jpg`,
851
- type: `jpeg`,
852
- fullPage: true
853
- });
850
+ let path = `${ scope.paths.random_screenshots }/random-${ Date.now() }.jpg`;
851
+ await scope.take_a_screenshot( scope, { path: path });
854
852
 
855
853
  // Prep for the next loop to possibly stop. Signature page buttons
856
854
  // don't count as buttons in the way our field detects them
@@ -1119,27 +1117,23 @@ After(async function(scenario) {
1119
1117
  if ( !!scope.disable_error_screenshot ) {
1120
1118
  scope.addToReport(scope, {
1121
1119
  type: `row`,
1122
- value: `For security, ALKiln will not create a screenshot for this error. It's possible a secret is being used on this screen.`
1120
+ value: `For security, ALKiln will avoid creating a screenshot for this error. It's possible a secret is being used on this screen.`
1123
1121
  });
1124
1122
  } else {
1125
1123
 
1126
1124
  // Save/download a picture of the screen that's showing during the unexpected status
1127
1125
  // Save one copy in the outer-most artifact folder
1128
1126
  let scenario_filename = await scope.getSafeScenarioFilename( scope, { prefix: `error_on` });
1129
- await scope.page.screenshot({
1130
- path: `${ scope.paths.artifacts }/${ scenario_filename }.jpg`,
1131
- type: `jpeg`,
1132
- fullPage: true
1133
- });
1127
+ let path_outer = `${ scope.paths.artifacts }/${ scenario_filename }.jpg`;
1128
+ await scope.take_a_screenshot( scope, { path: path_outer });
1129
+
1134
1130
  // Save another copy in the artifact's Scenario folder
1135
1131
  let screenshot_name = `error_on`;
1136
1132
  let { id } = await scope.examinePageID( scope, 'none to match' );
1137
1133
  screenshot_name += `-${ id }`;
1138
- await scope.page.screenshot({
1139
- path: `${ scope.paths.scenario }/${ screenshot_name }.jpg`,
1140
- type: `jpeg`,
1141
- fullPage: true
1142
- });
1134
+ let path_scenario = `${ scope.paths.scenario }/${ screenshot_name }.jpg`;
1135
+ await scope.take_a_screenshot( scope, { path: path_scenario });
1136
+
1143
1137
  } // ends if scope.disable_error_screenshot
1144
1138
  } // ends if scope.page exists
1145
1139
 
package/lib/utils/log.js CHANGED
@@ -52,7 +52,8 @@ log.debug = function ({ type='', pre='', data='', post='' }) {
52
52
  };
53
53
 
54
54
  log.add_to_debug_log = function(value) {
55
- fs.appendFileSync(log.debug_log_file, value);
55
+ let artifacts_path = session_vars.get_artifacts_path_name();
56
+ fs.appendFileSync(`${ artifacts_path }/${ log.debug_log_file}`, value);
56
57
  };
57
58
 
58
59
  log.debug_log_file = 'debug_log.txt'
@@ -23,9 +23,25 @@ module.exports = session_vars;
23
23
  // These default to null
24
24
  // TODO: How to isolate debug elsewhere so this file can use the logger?
25
25
  session_vars.get_debug = function () { return process.env.DEBUG || null; };
26
+ session_vars.is_dev_env = function () { return process.env.DEV || null; }
27
+
26
28
  session_vars.get_dev_api_key = function () { return process.env.DOCASSEMBLE_DEVELOPER_API_KEY || null; };
27
29
  session_vars.get_repo_url = function () { return process.env.REPO_URL || null; };
28
- session_vars.is_dev_env = function () { return process.env.DEV || null; }
30
+
31
+ // Created by interview code when tests through an interview manually on the server
32
+ session_vars.get_origin = function () {
33
+ /** Possible values: 'interview', 'github'. In future, maybe 'playground' or 'dashboard'. */
34
+ return process.env._ORIGIN || null;
35
+ };
36
+ session_vars.get_user_project_name = function () { return process.env._PROJECT_NAME || null; };
37
+ session_vars.get_user_id = function () { return process.env._USER_ID || null; };
38
+ session_vars.get_tags = function () {
39
+ if ( process.env._TAGS ) {
40
+ return process.env._TAGS.split(` `);
41
+ } else {
42
+ return [];
43
+ }
44
+ };
29
45
 
30
46
  // More complex logic
31
47
  session_vars.get_server_reload_timeout = function () {
@@ -129,8 +145,12 @@ session_vars.save_project_name = function ( project_name ) {
129
145
  return file;
130
146
  }; // Ends save_project_name()
131
147
 
148
+ // TODO next: Add use of env var in here in case not through GitHub cli_playground
132
149
  session_vars.get_project_name = function () {
133
- /* Get the name of the current docassemble Project */
150
+ /* Get the name of the current docassemble Project */
151
+ if ( session_vars.get_origin() === 'interview' ) {
152
+ return session_vars.get_user_project_name();
153
+ }
134
154
  let json = JSON.parse( fs.readFileSync( runtime_config_path ));
135
155
  let project_name = json[ project_name_key ] || null;
136
156
  if ( session_vars.get_debug() ) { console.log( `ALKiln debug: Project name from file is "${ project_name }".` ); }
@@ -174,7 +194,7 @@ session_vars.save_artifacts_path_name = function ( path_name ) {
174
194
  console.log( `ALKiln debug: Stored name of artifacts directory "${ path_name }" locally.` );
175
195
  }
176
196
  return file;
177
- }; // Ends get_project_name()
197
+ }; // Ends save_artifacts_path_name()
178
198
 
179
199
  session_vars.get_artifacts_path_name = function () {
180
200
  /* Get the name of the current artifacts path. */
@@ -187,7 +207,7 @@ session_vars.get_artifacts_path_name = function () {
187
207
  } catch ( error ) {
188
208
  return null;
189
209
  }
190
- }; // Ends get_project_name()
210
+ }; // Ends get_artifacts_path_name()
191
211
 
192
212
  /**
193
213
  * Ensure all required environment variables exist. Add each missing variable
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "4.10.3",
3
+ "version": "4.10.4-cli-playground-2",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "test": "npm run unit && npm run langs_setup && npm run cucumber -- $@",
8
- "cucumber_base": "run_cucumber(){ ./node_modules/.bin/cucumber-js \"$@\"; status=$?; ./lib/utils/clean_reports.js; return $status; }; run_cucumber",
9
- "cucumber": "run_base(){ npm run cucumber_base -- \"$@\" docassemble/*/data/sources/*.feature --retry 1; }; run_base",
10
- "cucumber_external": "run_cucumber(){ tmp_path=`pwd`; ( new_path=\"$1\"; shift; cd \"$new_path\"; npm run --prefix \"${tmp_path}\" cucumber_base -- \"$@\" \"$new_path\"/docassemble/*/data/sources/*.feature;) }; run_cucumber",
11
- "langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./lib/index.js docassemble/*/data/sources/*_lang*.feature",
8
+ "cucumber": "./lib/run_cucumber.js",
9
+ "langs": "npm run langs_setup && npm run cucumber",
12
10
  "langs_setup": "./lib/utils/langs.js 'docassemble/*/data/sources/*.feature'",
13
11
  "langs_local": "npm run langs_setup && npm run local",
14
12
  "local": "npm run setup && npm run cucumber; npm run takedown",
@@ -36,13 +34,13 @@
36
34
  "homepage": "https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing/",
37
35
  "dependencies": {
38
36
  "@axe-core/puppeteer": "4.4.2",
39
- "@cucumber/cucumber": "7.3.2",
37
+ "@cucumber/cucumber": "8.6.0",
40
38
  "@faker-js/faker": "^7.3.0",
41
39
  "axios": "0.24.0",
42
40
  "chai": "4.2.0",
43
41
  "cheerio": "1.0.0-rc.10",
44
- "dotenv": "8.2.0",
45
42
  "deep-equal-in-any-order": "^2.0.0",
43
+ "dotenv": "8.2.0",
46
44
  "mocha": "9.2.0",
47
45
  "puppeteer": "13.1.3",
48
46
  "qs": "6.10.2",
@@ -52,6 +50,7 @@
52
50
  "bin": {
53
51
  "alkiln-setup": "./lib/docassemble/setup.js",
54
52
  "alkiln-takedown": "./lib/docassemble/takedown.js",
55
- "alkiln-langs": "./lib/utils/langs.js"
53
+ "alkiln-langs": "./lib/utils/langs.js",
54
+ "alkiln-run": "./lib/run_cucumber.js"
56
55
  }
57
56
  }
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const log = require('./log');
5
- const session_vars = require('./session_vars');
6
-
7
- // This is hardcoded information from `cucumber.js`. Once we move to cucumber 8, we should
8
- // change that the cucumber.json and actually parse the info from it.
9
- // The file won't be used for anything other than detecting and removing color codes
10
- let summary_file_name = 'cucumber-report.txt';
11
- fs.readFile(summary_file_name, 'utf8', (err, data) => {
12
- if (err) {
13
- console.error(err);
14
- return;
15
- }
16
-
17
- // Replace terminal color codes, if present
18
- // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
19
- data = data.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
20
-
21
- // Put it in the correct file in the correct folder
22
- let artifacts_path = session_vars.get_artifacts_path_name();
23
- fs.appendFile( `${ artifacts_path }/${ log.debug_log_file }`, data, err => {
24
- if (err) {
25
- console.error(err);
26
- return;
27
- }
28
- }); // Ends appendFileSync
29
- });