@suffolklitlab/alkiln 4.11.5 → 5.0.0-experimental1

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,17 @@
1
+ In this PR, I have:
2
+
3
+ * [ ] Added tests for any new features or bug fixes (if relevant)
4
+ * [ ] Added my changes to the CHANGELOG.md at the top, under the "Unreleased" section
5
+ * [ ] Ensured issues that this PR closes will be [automatically closed](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
6
+
7
+ ### Reason for this PR
8
+
9
+ <Insert reasons for this PR>
10
+
11
+ ### Links to any solved or related issues
12
+
13
+ <Insert links>
14
+
15
+ ### Any manual testing I have done to ensure my PR is working
16
+
17
+ <Insert description>
@@ -20,7 +20,7 @@ jobs:
20
20
 
21
21
  strategy:
22
22
  matrix:
23
- node-version: [17.x]
23
+ node-version: [18.x]
24
24
 
25
25
  env:
26
26
  BASE_URL: ${{ secrets.SERVER_URL }}
@@ -34,6 +34,7 @@ jobs:
34
34
  SECRET_VAR1: secret-var1-value
35
35
  SECRET_VAR2: secret-var2-value
36
36
  SECRET_FOR_MISSING_FIELD: secret for missing field
37
+ _ORIGIN: github
37
38
 
38
39
  name: Run Puppeteer tests
39
40
  steps:
@@ -52,24 +53,24 @@ jobs:
52
53
  # For the testing repo, the `REPO_URL` is that of a different repo and is created in `env:`
53
54
  - run: echo "Repo address is $REPO_URL"
54
55
  - name: Checkout
55
- uses: actions/checkout@v2
56
+ uses: actions/checkout@v3
56
57
  - name: Setup environment
57
- uses: actions/setup-node@v1
58
+ uses: actions/setup-node@v3
58
59
  with:
59
60
  node-version: ${{ matrix.node-version }}
60
61
  - run: npm install
61
62
  - run: npm run setup
62
- - run: npm run test -- ${{ github.event.inputs.tags && format('"--tags" "{0}"', github.event.inputs.tags) }}
63
+ - run: npm run test ${{ github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
63
64
  # Artifacts
64
65
  - name: upload unit tests artifacts folder
65
- uses: actions/upload-artifact@v2
66
+ uses: actions/upload-artifact@v3
66
67
  # This will upload even if a previous step has failed
67
68
  if: ${{ always() }}
68
69
  with:
69
70
  name: ${{ env.UNIT_TESTS_ARTIFACT_NAME }}
70
71
  path: ./_alkiln-*
71
72
  - name: upload integration tests artifacts folder
72
- uses: actions/upload-artifact@v2
73
+ uses: actions/upload-artifact@v3
73
74
  if: ${{ always() }}
74
75
  with:
75
76
  name: ${{ env.ARTIFACT_NAME }}
package/CHANGELOG.md CHANGED
@@ -38,26 +38,36 @@ Format:
38
38
  ### Security
39
39
  -
40
40
  -->
41
- <!-- ## [Unreleased] -->
42
-
43
- ## [4.11.4] - 2023-04-19
44
- ### Fixed
45
- - Shorten path names to try to accommodate limitations of windows systems while still keeping enough useful information to help devs identify the test outputs. https://github.com/SuffolkLITLab/ALKiln/issues/618
41
+ ## [Unreleased]
42
+ ### Added
43
+ - added additional functionality to the sign method to allow developers to take a name argument to sign on canvas: issue #596
44
+ - 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
+ - Additional environment variables and their validation to allow for tests that run on a developer's server/Playground instead of through GitHub. Also, other functionality for that purpose. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
47
+ - Tests for new session_vars behavior and improve previous tests.
48
+ - Adds `npm-shrinkwrap.json`, so installs from npm will have fixed version dependencies
46
49
 
47
- ## [4.11.3] - 2023-04-06
48
50
  ### Changed
49
- - Project name prefix now includes ALKiln in it for clarity
51
+ - BREAKING: the github action no longer runs `npm run XYZ`; it directly calls scripts,
52
+ e.g. `alkiln-setup`, `alkiln-run`, `alkiln-takedown`
53
+ - upgraded cucumber v8.6.0
54
+ - using cucumber's JS API to run tests. For more details on how it works,
55
+ see [the cucumber-js docs](https://github.com/cucumber/cucumber-js/blob/main/docs/javascript_api.md).
56
+ - don't print the ["publish this cucumber report" message](https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#options)
57
+ - Adjusted validation of some environment variables to account for Playground vs. GitHub or local test runs. [Issue #661](https://github.com/SuffolkLITLab/ALKiln/issues/661)
58
+ - Docassemble Project name prefix now includes ALKiln in it for clarity
50
59
 
51
60
  ### Fixed
52
61
  - Projects created in da each have a unique name. https://github.com/SuffolkLITLab/ALKiln/issues/663
62
+ - Shorten path names to try to accommodate limitations of windows systems while still keeping enough useful information to help devs identify the test outputs. https://github.com/SuffolkLITLab/ALKiln/issues/618
63
+ - Updated field decoding to handle new object field encoding. See [#711](https://github.com/SuffolkLITLab/ALKiln/issues/711)
64
+ - Allows ALKilnInThePlayground to upload files. See https://github.com/SuffolkLITLab/docassemble-ALKilnInThePlayground/issues/18.
53
65
 
54
- ## [4.11.2] - 2023-03-22
55
66
  ### Security
56
67
  - Pass docassemble API keys through HTTP headers instead of as parameters.
57
- - ACTION: all users should delete any uWSGI logs that show their API keys.
58
68
  - Parameters to certain HTTP requests are printed directly in docassemble's
59
69
  uWSGI log, leaking API keys to actors with log access on your docassemble
60
- server.
70
+ server
61
71
 
62
72
  ## [4.11.1] - 2023-03-21
63
73
  ### Changed
@@ -595,4 +605,4 @@ Format:
595
605
  - `example.feature` - an example test to get a project started
596
606
  - `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
597
607
  - `.gitignore` - ignores files that are generated by the tests and shouldn't be added to the repo
598
- - `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
608
+ - `package.json` - keep up-to-date version of this test repository and hold scripts that the automated tests run
package/CONTRIBUTING.md CHANGED
@@ -112,6 +112,12 @@ Our tests require the user/developer to have a docassemble server on which they
112
112
 
113
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
114
 
115
+ ## Updating dependencies
116
+
117
+ Sometimes you will have to add dependencies to ALKiln. You can do so by adding the dependency in the `package.json` "dependencies" section, specifying an exact version. Then, run `npm install` to update `npm-shrinkwrap.json` to have the new dependency.
118
+
119
+ If you have to update an existing dependency, you can change the version in `package.json`, and run `npm install` to update `npm-shrankwrap.json`.
120
+
115
121
  ## Very general architecture of files and folders
116
122
 
117
123
  An honest look at our current project architecture—some of our files and folders, what they do, and how they interact.
package/action.yml CHANGED
@@ -61,6 +61,7 @@ runs:
61
61
  echo "DOCASSEMBLE_DEVELOPER_API_KEY=${{ inputs.DOCASSEMBLE_DEVELOPER_API_KEY }}" >> $GITHUB_ENV
62
62
  echo "ALKILN_VERSION=${{ inputs.ALKILN_VERSION }}" >> $GITHUB_ENV
63
63
  echo "MAX_SECONDS_FOR_SETUP=${{ inputs.MAX_SECONDS_FOR_SETUP }}" >> $GITHUB_ENV
64
+ echo "_ORIGIN=github" >> $GITHUB_ENV
64
65
  shell: bash
65
66
  - name: "ALKiln: confirm info"
66
67
  run: |
@@ -68,64 +69,36 @@ 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
- uses: actions/setup-node@v1
74
+ uses: actions/setup-node@v3
104
75
  with:
105
- node-version: "17"
106
- - run: npm install
76
+ node-version: "18"
77
+ - name: Install ALKiln directly from npm
78
+ run: |
79
+ npm install -g github:SuffolkLITLab/ALKiln#v5
107
80
  shell: bash
108
81
 
109
82
  # Run tests
110
83
  - name: "ALKiln: Create a Project and pull the package from GitHub"
111
- run: npm run setup
84
+ run: alkiln-setup
112
85
  shell: bash
113
86
  - name: "ALKiln: Run tests"
114
87
  if: ${{ success() }}
115
- run: npm run test -- ${{ github.event.inputs.tags && format('"--tags" "{0}"', github.event.inputs.tags) }}
88
+ run: alkiln-run ${{ github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
116
89
  shell: bash
117
90
  - 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
91
  if: ${{ failure() }}
119
92
  shell: bash
120
93
  - name: "ALKiln: Delete project from docassemble test account"
121
94
  if: ${{ always() }}
122
- run: npm run takedown
95
+ run: alkiln-takedown
123
96
  shell: bash
124
97
 
125
98
  # Upload artifacts that subscribers can download on the Actions summary page
126
99
  - name: "ALKiln: Upload artifacts folder"
127
100
  if: ${{ always() }}
128
- uses: actions/upload-artifact@v2
101
+ uses: actions/upload-artifact@v3
129
102
  with:
130
103
  name: ${{ env.ARTIFACT_NAME }}
131
104
  path: ./alkiln-*
@@ -27,7 +27,8 @@ da.get_dev_id = async function ( timeout ) {
27
27
  try {
28
28
  return await axios.request( options );
29
29
  } catch (error) {
30
- throw {...error.toJSON(), data: error.response?.data};
30
+ let response = error.response || {data: null}
31
+ throw {...error.toJSON(), data: response.data};
31
32
  }
32
33
  }; // Ends da.get_dev_id()
33
34
 
@@ -48,7 +49,8 @@ da.create_project = async function ( project_name, timeout ) {
48
49
  try {
49
50
  return await axios.request( options );
50
51
  } catch (error) {
51
- throw {...error.toJSON(), data: error.response?.data};
52
+ let response = error.response || {data: null}
53
+ throw {...error.toJSON(), data: response.data};
52
54
  }
53
55
  }; // Ends da.create_project()
54
56
 
@@ -71,7 +73,8 @@ da.pull = async function ( timeout ) {
71
73
  try {
72
74
  return await axios.request( options );
73
75
  } catch (error) {
74
- throw {...error.toJSON(), data: error.response?.data};
76
+ let response = error.response || {data: null}
77
+ throw {...error.toJSON(), data: response.data};
75
78
  }
76
79
  }; // Ends da.pull()
77
80
 
@@ -92,7 +95,8 @@ da.has_task_finished = async function ( task_id, timeout ) {
92
95
  try {
93
96
  return await axios.request( options );
94
97
  } catch (error) {
95
- throw {...error.toJSON(), data: error.response?.data};
98
+ let response = error.response || {data: null}
99
+ throw {...error.toJSON(), data: response.data};
96
100
  }
97
101
  }; // Ends da.has_task_finished()
98
102
 
@@ -113,7 +117,8 @@ da.delete_project = async function ( timeout ) {
113
117
  try {
114
118
  return await axios.request( options );
115
119
  } catch (error) {
116
- throw {...error.toJSON(), data: error.response?.data};
120
+ let response = error.response || {data: null}
121
+ throw {...error.toJSON(), data: response.data};
117
122
  }
118
123
  }; // Ends da.delete_project()
119
124
 
@@ -142,7 +147,8 @@ da.__delete_projects_starting_with = async function ( base_name, starting_num=1,
142
147
 
143
148
  await axios.request( options );
144
149
  } catch ( error ) {
145
- console.log( {...error.toJSON(), data: error.response?.data});
150
+ let response = error.response || {data: null}
151
+ console.log( {...error.toJSON(), data: response.data});
146
152
  }
147
153
  name_incrementor++;
148
154
  }
@@ -122,9 +122,15 @@ da_i.throw_an_error_if_server_is_not_responding = async function ( options ) {
122
122
 
123
123
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").is_server_responding()'
124
124
  da_i.is_server_responding = async function ( dev_id_options ) {
125
- /** Use _da_REST.get_dev_id() to test if server is up.
125
+ /** For non-playground interviews, use _da_REST.get_dev_id() to
126
+ * return if server is up. Otherwise, return true.
126
127
  * See https://docassemble.org/docs/api.html#user. */
127
128
 
129
+ // Playground interview will itself timeout. No way to detect that.
130
+ if ( session_vars.get_origin() === 'playground' ) {
131
+ return true;
132
+ }
133
+
128
134
  // Default timeouts
129
135
  let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
130
136
  try {
@@ -141,13 +147,28 @@ da_i.is_server_responding = async function ( dev_id_options ) {
141
147
 
142
148
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").get_dev_id()'
143
149
  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. */
150
+ /** Get user id, either from the server or from env vars (for Playground runs).
151
+ * See https://docassemble.org/docs/api.html#user.
152
+ *
153
+ * @param dev_id_options {obj} Options for getting dev_id
154
+ * @param dev_id_options.timeout {int} Required if options is defined. Max MS
155
+ * to allow for server request.
156
+ */
146
157
 
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;
158
+ let dev_id = null;
159
+
160
+ // For tests running in the dev Playground, use the appropriate env var
161
+ if ( session_vars.get_origin() === 'playground' ) {
162
+ dev_id = session_vars.get_user_id();
163
+
164
+ } else {
165
+ // Default timeouts
166
+ let { timeout } = dev_id_options || { timeout: DEFAULT_MAX_REQUEST_MS };
167
+ let response = await _da_REST.get_dev_id( timeout );
168
+ dev_id = response.data.id;
169
+ }
170
+
171
+ return dev_id;
151
172
  }; // Ends da_i.get_dev_id();
152
173
 
153
174
 
@@ -0,0 +1,88 @@
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
+ const cucumber_api = require('@cucumber/cucumber/api');
8
+
9
+ (async function main() {
10
+ let argv = process.argv;
11
+
12
+ const environment = { cwd: process.cwd() };
13
+ console.log("alkiln-run: environment: %o", environment);
14
+ const provided_config = {
15
+ "publishQuiet": true,
16
+ "require": "./lib/index.js",
17
+ "format": ["progress", "summary:cucumber-report.txt"],
18
+ "paths": ["docassemble/*/data/sources/*.feature"],
19
+ "retry": 1,
20
+ }
21
+
22
+
23
+ // If they're running it from an interview on their server instead of from GitHub
24
+ if ( session_vars.get_origin() === `playground` ) {
25
+ provided_config.paths.push(`/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
26
+ // For S3
27
+ provided_config.paths.push(`/tmp/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}/*.feature`);
28
+ }
29
+
30
+ let tags = argv.slice(2);
31
+ if ( session_vars.get_origin() === `playground` ) {
32
+ tags = session_vars.get_tags();
33
+ }
34
+
35
+ if (tags.length > 0) {
36
+ // Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
37
+ // If we get a list of only tags, then "or" them all together
38
+ if (tags.every((val) => val.startsWith("@"))) {
39
+ provided_config.tags = tags.join(' or ');
40
+ }
41
+ // If we get a list of seemingly no tags, assume they meant to use tags
42
+ else if (tags.every((val) => !val.startsWith("@"))) {
43
+ provided_config.tags = tags.map((val) => "@" + val).join(' or ');
44
+ }
45
+ // If it's a mix of tags and no tags, it's probably a boolean expression!
46
+ else {
47
+ provided_config.tags = tags.join(' ');
48
+ }
49
+ }
50
+
51
+ const { runConfiguration } = await cucumber_api.loadConfiguration({
52
+ provided: provided_config
53
+ }, environment);
54
+ console.log("alkiln-run: runConfiguration: %o", runConfiguration);
55
+ var path = require('path');
56
+ var codeDir = path.dirname(require.main.filename);
57
+ const support = await cucumber_api.loadSupport({ support: {
58
+ requireModules: [`${ codeDir }/index.js`],
59
+ requirePaths: [ `${ codeDir }/index.js`],
60
+ importPaths: [],
61
+ }, sources: {paths: []}}, environment);
62
+
63
+ console.log("alkiln-run: working directory: %o", codeDir);
64
+ console.log(`\n\n`);
65
+
66
+ const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
67
+ console.log("alkiln-run: success: %b", success);
68
+ const exitCode = success ? 0 : 1;
69
+ process.exitCode = exitCode;
70
+
71
+ // This is hardcoded information from `provide_config` above.
72
+ // This code isn't used for anything other than detecting and removing color codes.
73
+ let summary_file_name = 'cucumber-report.txt';
74
+ fs.readFile(summary_file_name, 'utf8', (err, data) => {
75
+ if (err) {
76
+ console.error(err);
77
+ return;
78
+ }
79
+
80
+ // Replace terminal color codes, if present
81
+ // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
82
+ data = data.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
83
+
84
+ // Put it in the correct file in the correct folder
85
+ let artifacts_path = session_vars.get_artifacts_path_name();
86
+ fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
87
+ });
88
+ })();
package/lib/scope.js CHANGED
@@ -1212,7 +1212,7 @@ module.exports = {
1212
1212
  // Some fields have a key hidden inside them.
1213
1213
  // This gets the variable name before the final brackets, and the likely
1214
1214
  // still base64 encoded string inside the brackets
1215
- let before_and_in_brackets = expr_str.match( /^(.*)\[[BR]'(.*)'\]/ );
1215
+ let before_and_in_brackets = expr_str.match( /^(.*)\[[BRO]'(.*)'\]/ );
1216
1216
 
1217
1217
  // Of the format `foo[B'encoded']` or `foo[R'encoded']`
1218
1218
  // The B means internal to docassemble, the encoded portion is a string,
@@ -1877,16 +1877,26 @@ module.exports = {
1877
1877
  }, 500)
1878
1878
  },
1879
1879
 
1880
- draw_signature: async function ( scope ) {
1880
+ draw_signature: async function ( scope, name ) {
1881
1881
  /* On a signature page, there should only be a single signature canvas
1882
1882
  * and a continue button. Draw in the #dasigcanvas element. `<canvas>`
1883
1883
  * takes a while to load. */
1884
- let handle = await scope.page.waitForSelector( `#dasigcanvas` );
1884
+ let canvas = await scope.page.waitForSelector( `#dasigcanvas` );
1885
1885
  let msg = `Could not find a signature field.`;
1886
- if ( !handle ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
1887
- expect( handle, msg ).to.exist;
1888
-
1889
- let bounding_box = await handle.boundingBox();
1886
+ if ( !canvas ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
1887
+ expect( canvas, msg ).to.exist;
1888
+
1889
+ // Provides functionality to draw name arg on canvas
1890
+ if (name !== undefined) {
1891
+ await scope.page.$eval(`#dasigcanvas`, (canvas, name) => {
1892
+ const ctx = canvas.getContext("2d");
1893
+ ctx.font = "24px serif";
1894
+ ctx.fillText(name, 10, 50);
1895
+ }, name);
1896
+ }
1897
+ let bounding_box = await canvas.boundingBox();
1898
+ // We draw the dot because it allows the developer to tap continue
1899
+ // Docassemble does not allow to tap continue unless the user interacts with the canvas
1890
1900
  await scope.page.mouse.move(bounding_box.x + bounding_box.width / 2, bounding_box.y + bounding_box.height / 2);
1891
1901
  await scope.page.mouse.down();
1892
1902
  // await scope.page.mouse.move(1, 1); // Too big? Wait a while before drawing?
@@ -2415,9 +2425,9 @@ module.exports = {
2415
2425
  await scope.afterStep(scope, {waitForShowIf: true});
2416
2426
  }, // Ends scope.steps.tap_term()
2417
2427
 
2418
- sign: async ( scope ) => {
2428
+ sign: async ( scope, name ) => {
2419
2429
  /** Sign on a signature page. */
2420
- await scope.draw_signature( scope );
2430
+ await scope.draw_signature( scope, name );
2421
2431
  await scope.afterStep(scope);
2422
2432
  }, // Ends scope.steps.sign()
2423
2433
 
package/lib/steps.js CHANGED
@@ -92,7 +92,7 @@ BeforeAll(async () => {
92
92
  // Make the folder to store all the files for this run of the tests
93
93
  scope.paths.artifacts = files.make_artifacts_folder();
94
94
  // Save its name in a file. `session_vars` can get it back later.
95
- // This is used by at least clean_reports.js.
95
+ // The value we are saving is used by at least run_cucumber.js.
96
96
  session_vars.save_artifacts_path_name( scope.paths.artifacts );
97
97
 
98
98
  scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
@@ -103,7 +103,14 @@ BeforeAll(async () => {
103
103
  Before(async (scenario) => {
104
104
  // Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
105
105
  if (!scope.browser) {
106
- scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
106
+ if (session_vars.get_origin() === 'playground') {
107
+ // Will only run in the Playground outside of a sandbox. TODO: There's a
108
+ // better way to do this, though it's more complicated. See comments in
109
+ // https://github.com/SuffolkLITLab/ALKiln/issues/661
110
+ scope.browser = await scope.driver.launch({ args: ['--no-sandbox'] });
111
+ } else {
112
+ scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
113
+ }
107
114
  }
108
115
  // Clean up all previously existing pages
109
116
  for (const page of await scope.browser.pages()) {
@@ -973,13 +980,21 @@ When('I tap the defined text link {string}', { timeout: -1 }, async ( phrase ) =
973
980
  await scope.steps.tap_term( scope, phrase );
974
981
  });
975
982
 
976
- Then('I sign', { timeout: -1 }, async () => {
983
+ Then(/^I sign$/, { timeout: -1 }, async () => {
977
984
  return wrapPromiseWithTimeout(
978
985
  scope.steps.sign( scope ),
979
986
  scope.timeout
980
987
  );
981
988
  });
982
989
 
990
+ Then('I sign with the name {string}', { timeout: -1 }, async ( name ) => {
991
+ /* Signs with the string argument */
992
+ return wrapPromiseWithTimeout(
993
+ scope.steps.sign( scope, name ),
994
+ scope.timeout
995
+ );
996
+ });
997
+
983
998
  When(/I download "([^"]+)"$/, { timeout: -1 }, async ( filename ) => {
984
999
  /* Taps the link that leads to the given filename to trigger downloading.
985
1000
  * and waits till the file has been downloaded before allowing the tests to continue.