@suffolklitlab/alkiln 5.0.2-feat-github-server-14 → 5.1.0-sources-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -37,9 +37,19 @@ Format:
37
37
 
38
38
  ### Security
39
39
  -
40
+
41
+ ### Internal
42
+ -
40
43
  -->
41
44
  <!-- ## [Unreleased] -->
42
45
 
46
+ ## [5.1.0] - 2023-08-12
47
+ ### Added
48
+ - Comboboxes - can now fill in comboboxes on the latest Docassemble.
49
+
50
+ ### Fixed
51
+ - Filling out a hidden field right after a date field triggered the date's calendar popup, which I believe ate the input for the next visible field.
52
+
43
53
  ## [5.0.2] - 2023-07-20
44
54
  ### Fixed
45
55
  - Accept `SERVER_RELOAD_TIMEOUT_SECONDS` as an env var input in GitHub.
@@ -89,6 +99,10 @@ Format:
89
99
  server
90
100
  - Update dependencies. See [#727](https://github.com/SuffolkLITLab/ALKiln/pull/727).
91
101
 
102
+ ### Internal
103
+ - added a test for multiline json values (only ones set by code not by user input)
104
+ - added an internal section to the change log
105
+
92
106
  ## [4.11.1] - 2023-03-21
93
107
  ### Changed
94
108
  - Get error data from server errors
package/action.yml CHANGED
@@ -23,12 +23,6 @@ inputs:
23
23
  description: "Max amount of time to give the server to reload if it needs to"
24
24
  required: false
25
25
  # no default to let the custom Scenario timeouts create the reload timeout
26
- # INSTALL_LOCATION? Will that get confused with the location passed into the command line?
27
- # Maybe `INSTALL_LOCATION` could be "playground", "server", or a custom path and we could handle all those.
28
- INSTALL_METHOD:
29
- description: "Default is 'playground'. Other option: 'server'. How to install the package onto the server."
30
- required: false
31
- default: "playground"
32
26
 
33
27
  runs:
34
28
  using: "composite"
@@ -53,43 +47,29 @@ runs:
53
47
  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\nSERVER_RELOAD_TIMEOUT_SECONDS is $SERVER_RELOAD_TIMEOUT_SECONDS\n"
54
48
  shell: bash
55
49
 
56
- # Install
50
+ # Install node
57
51
  - name: "ALKiln: Install node packages"
58
52
  uses: actions/setup-node@v3
59
53
  with:
60
54
  node-version: "18"
61
55
  - name: Install ALKiln directly from npm
62
56
  run: |
63
- npm install -g @suffolklitlab/alkiln@$ALKILN_VERSION
57
+ npm install -g @suffolklitlab/alkiln
64
58
  shell: bash
65
59
 
66
- # Install on playground
60
+ # Run tests
67
61
  - name: "ALKiln: Create a Project and pull the package from GitHub"
68
- if: ${{ inputs.INSTALL_METHOD == 'playground' }}
69
62
  run: alkiln-setup
70
63
  shell: bash
71
- - run: echo -e "\n\n====\nALKiln 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"
72
- if: ${{ inputs.INSTALL_METHOD == 'playground' && failure() }}
73
- shell: bash
74
-
75
- # Install on server
76
- - name: "ALKiln: Install the GitHub package onto the server"
77
- if: ${{ inputs.INSTALL_METHOD == 'server' }}
78
- run: alkiln-server-install
79
- shell: bash
80
- - run: echo -e "\n\n====\nALKiln could not install the package on the temporary GitHub docassemble server. Check the steps above this line.\n\n"
81
- if: ${{ inputs.INSTALL_METHOD == 'server' && failure() }}
82
- shell: bash
83
-
84
- # run tests
85
64
  - name: "ALKiln: Run tests"
86
65
  if: ${{ success() }}
87
66
  run: alkiln-run ${{ github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
88
67
  shell: bash
89
-
90
- # on playground, delete project
91
- - name: "ALKiln: Try to delete the project from the playground of the docassemble test account."
92
- if: ${{ inputs.INSTALL_METHOD == 'playground' }}
68
+ - 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"
69
+ if: ${{ failure() }}
70
+ shell: bash
71
+ - name: "ALKiln: Delete project from docassemble test account"
72
+ if: ${{ always() }}
93
73
  run: alkiln-takedown
94
74
  shell: bash
95
75
 
@@ -102,7 +82,7 @@ runs:
102
82
  path: ./alkiln-*
103
83
 
104
84
  # ##################################
105
- # # A developer's workflow should look similar to the below.
85
+ # # A developer's workflow should look similar to the below
106
86
  # # In place of `uses: suffolkLITLab/ALKiln@v5`, they
107
87
  # # might use another version.
108
88
  #
@@ -79,21 +79,13 @@ da.pull = async function ( timeout ) {
79
79
  }; // Ends da.pull()
80
80
 
81
81
 
82
- da.has_playground_task_finished = async function ( task_id, timeout ) {
83
- return await da.has_task_finished( "/api/restart_status", task_id, timeout);
84
- }
85
-
86
- da.has_package_task_finished = async function ( task_id, timeout ) {
87
- return await da.has_task_finished( "/api/package_update_status", task_id, timeout);
88
- }
89
-
90
82
  // node -e 'require("./lib/docassemble/docassemble_api_REST.js").has_task_finished()'
91
- da.has_task_finished = async function ( api_endpoint, task_id, timeout ) {
83
+ da.has_task_finished = async function ( task_id, timeout ) {
92
84
  /* Check task status based on `task_id`. */
93
85
  let options = {
94
86
  method: 'GET',
95
87
  headers: {'X-API-Key': session_vars.get_dev_api_key()},
96
- url: `${ session_vars.get_da_server_url() }${ api_endpoint }`,
88
+ url: `${ session_vars.get_da_server_url() }/api/restart_status`,
97
89
  params: {
98
90
  task_id: task_id,
99
91
  },
@@ -329,8 +329,9 @@ da_i.pull = async function ( pull_options ) {
329
329
  }
330
330
  }; // Ends da_i.pull()
331
331
 
332
+
332
333
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").has_task_finished()'
333
- da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
334
+ da_i.has_task_finished = async function ( task_id, options ) {
334
335
  /** Check task status if it exists. If not, return true as task did
335
336
  * not require a server restart. See da_i.pull().
336
337
  * See https://docassemble.org/docs/api.html#restart_status. */
@@ -341,7 +342,7 @@ da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
341
342
  try {
342
343
 
343
344
  // If server did need to restart, check if it's done.
344
- let response = await _da_REST.has_task_finished( api_endpoint, task_id, timeout );
345
+ let response = await _da_REST.has_task_finished( task_id, timeout );
345
346
  log.debug({ pre: `Task status: ${ response.status }. Docassemble status text: ${ response.data.status }. Task id: ${ task_id }` });
346
347
 
347
348
  if ( response.data.status === 'completed' ) {
@@ -352,15 +353,16 @@ da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
352
353
  return false;
353
354
 
354
355
  } else if ( response.data.status === 'unknown' ) {
355
- // Server is working, but status 'unknown'? Bad design, but this
356
- // does get returned when install packages, so return true
356
+ // Server is working, but status 'unknown'? We shouldn't even
357
+ // get here according to the API docs, but it feels weird to have
358
+ // no default behavior. What to return? Throw?
357
359
  log.info({
358
360
  pre: `\`task_id\` "${ task_id }" has expired. If it's been `
359
361
  + `less than 1 hour since the task started, the task is `
360
362
  + `complete. See https://docassemble.org/docs/api.html#restart_status.`,
361
- data: response.data,
363
+ data: response,
362
364
  });
363
- return true;
365
+ return null;
364
366
  } // ends status type
365
367
 
366
368
  } catch ( error ) {
@@ -376,15 +378,8 @@ da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
376
378
 
377
379
  }; // Ends da_i.has_task_finished()
378
380
 
379
- da_i.wait_for_playground_to_restart = async ( task_id, options ) => {
380
- return await da_i.wait_for_server_to_restart( "/api/restart_status", task_id, options );
381
- };
382
-
383
- da_i.wait_for_package_to_restart = async ( task_id, options ) => {
384
- return await da_i.wait_for_server_to_restart( "/api/package_update_status", task_id, options );
385
- };
386
381
 
387
- da_i.wait_for_server_to_restart = async ( api_endpoint, task_id, options ) => {
382
+ da_i.wait_for_server_to_restart = async ( task_id, options ) => {
388
383
  /** Wait for the server to finish restarting in case a module needs to load.
389
384
  * This is a polling function because things may be returned
390
385
  * that aren't equivalent to the server being done with a task. */
@@ -415,7 +410,7 @@ da_i.wait_for_server_to_restart = async ( api_endpoint, task_id, options ) => {
415
410
  while ( !done_restarting && num_tries <= max_loops ) {
416
411
  num_tries++;
417
412
  // Wait only a short time for the task to finish, not the full default timeout
418
- done_restarting = await da_i.has_task_finished( api_endpoint, task_id, { timeout: wait_for_response } );
413
+ done_restarting = await da_i.has_task_finished( task_id, { timeout: wait_for_response } );
419
414
  if ( !done_restarting ) {
420
415
  if ( num_tries < max_loops ) {
421
416
  // Show a slightly different message after a number of tries to reassure
@@ -11,13 +11,9 @@ 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
- if (session_vars.get_install_type() === "server") {
15
- return `${ server_url }/interview?new_session=1&i=docassemble.${session_vars.get_package_name().replace("-", ".")}%3Adata/questions/`;
16
- } else {
17
- let dev_id = await docassemble_api_interface.get_dev_id();
18
- let project_name = session_vars.get_project_name();
19
- return `${ server_url }/interview?new_session=1&i=docassemble.playground${ dev_id }${ project_name }%3A`;
20
- }
14
+ let dev_id = await docassemble_api_interface.get_dev_id();
15
+ let project_name = session_vars.get_project_name();
16
+ return `${ server_url }/interview?new_session=1&i=docassemble.playground${ dev_id }${ project_name }%3A`;
21
17
  };
22
18
 
23
19
  module.exports = get_base_interview_url;
@@ -39,7 +39,7 @@ const setup = async () => {
39
39
  let task_id = await da_i.pull();
40
40
  // Task id will be null if server did not need to restart
41
41
  if ( task_id ) {
42
- await da_i.wait_for_playground_to_restart( task_id );
42
+ await da_i.wait_for_server_to_restart( task_id );
43
43
  }
44
44
 
45
45
  // Finish
@@ -6,64 +6,87 @@ const session_vars = require('./utils/session_vars');
6
6
 
7
7
  const cucumber_api = require('@cucumber/cucumber/api');
8
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);
9
+ function build_config(argv) {
10
+ // The default provided config
14
11
  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,
12
+ publishQuiet: true,
13
+ require: `./lib/index.js`,
14
+ format: [`progress`, `summary:cucumber-report.txt`],
15
+ paths: [`${ argv.sources }/*.feature`],
16
+ retry: 1,
20
17
  }
21
18
 
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
- }
19
+ let tags = argv[`_`];
20
+ // if ( session_vars.get_origin() === `playground` ) {
21
+ // tags = session_vars.get_tags();
22
+ // }
34
23
 
35
24
  if (tags.length > 0) {
36
25
  let joined_tags = '';
37
26
  // Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
38
27
  // If we get a list of only tags, then "or" them all together
39
- if (tags.every((val) => val.startsWith("@"))) {
40
- joined_tags = tags.join(' or ');
28
+ if (tags.every((val) => val.startsWith(`@`))) {
29
+ joined_tags = tags.join(` or `);
41
30
  }
42
31
  // If we get a list of seemingly no tags, assume they meant to use tags
43
- else if (tags.every((val) => !val.startsWith("@"))) {
44
- joined_tags = tags.map((val) => "@" + val).join(' or ');
32
+ else if (tags.every((val) => !val.startsWith(`@`))) {
33
+ joined_tags = tags.map((val) => `@` + val).join(` or `);
45
34
  }
46
35
  // If it's a mix of tags and no tags, it's probably a boolean expression!
47
36
  else {
48
- joined_tags = tags.join(' ');
37
+ joined_tags = tags.join(` `);
49
38
  }
50
39
 
51
40
  provided_config.tags = joined_tags;
52
41
  } // end if there are tags
53
42
 
43
+ return provided_config;
44
+ }; // Ends build_config()
45
+
46
+
47
+ (async function main() {
48
+ /** Create the cucumber config, run the tests, create and
49
+ * manipluate the output. */
50
+ const environment = { cwd: process.cwd() };
51
+ console.log("alkiln-run: environment: %o", environment);
52
+
53
+ // process.argv is a list of strings of commands and args.
54
+ let argv = require(`minimist`)( process.argv.slice(2), {
55
+ default: {
56
+ // Testing from GitHub. TODO: Do we want to allow multiple sources paths?
57
+ sources: `./docassemble/*/data/sources`,
58
+ // From playground: `/usr/share/docassemble/files/playgroundsources/<user_id>/<project>/*.feature`
59
+ // From playground with S3: `/tmp/playgroundsources/<user_id>/<project>/*.feature`
60
+
61
+ // // Installed in a user Playground
62
+ // url: `<server>/interview?new_session=1&i=docassemble.playground<user_id><project>%3A`,
63
+ // // server install: `<server>/interview?new_session=1&i=docassemble.<package>%3Adata/questions/`
64
+ }
65
+ });
66
+
67
+ let dir_exists = session_vars.set_sources_path( argv.sources );
68
+ if ( !dir_exists ) {
69
+ // If we don't throw, it'll be more confusing when no tests run
70
+ throw new ReferenceError( `No folder exists at "${ argv.sources }", where ALKiln looks for your \`.feature\` files.`)
71
+ }
72
+
73
+ const provided_config = build_config( argv );
74
+
54
75
  const { runConfiguration } = await cucumber_api.loadConfiguration({
55
76
  provided: provided_config
56
77
  }, environment);
78
+
57
79
  console.log("alkiln-run: runConfiguration: %o", runConfiguration);
80
+
58
81
  var path = require('path');
59
82
  var codeDir = path.dirname(require.main.filename);
83
+ console.log("alkiln-run: working directory: %o", codeDir);
60
84
  const support = await cucumber_api.loadSupport({ support: {
61
- requireModules: [`${ codeDir }/index.js`],
62
- requirePaths: [ `${ codeDir }/index.js`],
85
+ requireModules: [ `${ codeDir }/index.js` ],
86
+ requirePaths: [ `${ codeDir }/index.js` ],
63
87
  importPaths: [],
64
88
  }, sources: {paths: []}}, environment);
65
89
 
66
- console.log("alkiln-run: working directory: %o", codeDir);
67
90
  console.log(`\n\n`);
68
91
 
69
92
  const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
@@ -86,6 +109,6 @@ const cucumber_api = require('@cucumber/cucumber/api');
86
109
 
87
110
  // Put it in the correct file in the correct folder
88
111
  let artifacts_path = session_vars.get_artifacts_path_name();
89
- fs.appendFileSync( `${ artifacts_path}/${ log.debug_log_file }`, data);
112
+ fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, data);
90
113
  });
91
114
  })();
package/lib/scope.js CHANGED
@@ -44,9 +44,6 @@ let printable_var_value = function(row) {
44
44
  /* `row` might be a row, a source row, or an original row. */
45
45
  let original = get_original_row(row);
46
46
  if ( is_secret_var(original) ) {
47
- if ( session_vars.get_dangerous_debug() ) {
48
- return `alkiln_dangerous ${ row.value }`;
49
- }
50
47
  return original.flags.secret_name;
51
48
  }
52
49
  return row.value;
@@ -1001,6 +998,11 @@ module.exports = {
1001
998
  }
1002
999
  }
1003
1000
 
1001
+ if ( !encoded_name ) {
1002
+ // Some fields, like comboboxes, have a weird var name setup
1003
+ encoded_name = $node.attr( `id` );
1004
+ }
1005
+
1004
1006
  // ====================
1005
1007
  // Table rows
1006
1008
  // ====================
@@ -1475,6 +1477,11 @@ module.exports = {
1475
1477
  */
1476
1478
  let row_used = null;
1477
1479
 
1480
+ // Skip hidden fields right away. When a date field was above a combobox,
1481
+ // interacting with the hidden field caused the date field to open its
1482
+ // calendar which blocked input to the combobox
1483
+ if ( field.type === `hidden` ) { return row_used; }
1484
+
1478
1485
  let matching_input_rows = await scope.getMatchingRows( scope, { var_data, field });
1479
1486
 
1480
1487
  // Nothing to set if there are no matching table rows
@@ -1489,6 +1496,8 @@ module.exports = {
1489
1496
 
1490
1497
  let handle = await scope.get_handle_from_field( scope, { field: field });
1491
1498
 
1499
+ // After each previous input, figure out if this input is (now/still) disabled
1500
+ // TODO: Consider testing for disabled this before we get the matching row
1492
1501
  let disabled = await handle.evaluate(( elem )=> {
1493
1502
  // da's header covers the top of the section containing the fields which means
1494
1503
  // sometimes it blocks nodes from clicks. Scroll item to the bottom instead.
@@ -1778,73 +1787,34 @@ module.exports = {
1778
1787
  // allow a typo where someone forgot to include a space after a comma.
1779
1788
  let uploads_names = answer.split(`,`);
1780
1789
  if ( uploads_names.length === 0 ) {
1781
- await scope.addToReport( scope, {
1782
- type: `error`,
1783
- value: `Your list of files to upload is empty.`
1784
- });
1785
- return false; // The row wasn't used
1786
- }
1787
-
1788
- let dirs = [];
1789
- if ( session_vars.get_origin() === `playground` ) {
1790
- let files_playground = fg.sync(
1791
- [ `/usr/share/docassemble/files/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}` ],
1792
- { onlyFiles: false, suppressErrors: true }
1793
- );
1794
- let files_s3 = fg.sync(
1795
- [ `/tmp/playgroundsources/${session_vars.get_user_id()}/${session_vars.get_user_project_name()}` ],
1796
- { onlyFiles: false, suppressErrors: true }
1797
- );
1798
-
1799
- dirs = files_playground.concat( files_s3 );
1800
- } else {
1801
- dirs = fg.sync(
1802
- [ `**/docassemble/**/data/sources` ],
1803
- { onlyFiles: false, suppressErrors: true }
1804
- );
1805
- }
1806
-
1807
- if ( session_vars.get_debug() ) {
1808
- console.log( `All discovered dirs to upload from:`, dirs );
1809
- }
1810
-
1811
- // Make sure sources directory exists and has files in it
1812
- if ( dirs.length === 0 ) {
1813
1790
  await scope.addToReport( scope, {
1814
1791
  type: `warning`,
1815
- value: `The test files need to be in the "sources" directory, `
1816
- + `but ALKiln couldn't find the directory or it was empty.`
1792
+ value: `Your list of files to upload is empty.`
1817
1793
  });
1818
1794
  return false; // The row wasn't used
1819
1795
  }
1820
1796
 
1821
- // Get the full paths of all the files
1822
- let dir = null;
1797
+ let dir = session_vars.get_sources_path();
1798
+ // Get the full paths of all the existing files
1823
1799
  let paths = [];
1824
1800
  let missing_files = []; // For any user warnings
1825
1801
  for ( let name of uploads_names ) {
1826
-
1827
1802
  let trimmed_name = name.trim();
1828
- let matching_paths = [];
1829
-
1830
- for ( let one_dir of dirs ) {
1831
- // accumulate all matching paths
1832
- let new_paths = fg.sync([ `${ one_dir }/${ trimmed_name }` ]);
1833
- matching_paths.push( ...new_paths );
1834
- } // ends for each dir
1835
-
1803
+ let matching_paths = fg.sync(
1804
+ [ `${ dir }/${ trimmed_name }` ],
1805
+ { suppressErrors: true }
1806
+ );
1836
1807
  if ( matching_paths.length === 0 ) {
1837
1808
  missing_files.push( trimmed_name );
1838
1809
  } else {
1839
1810
  paths.push( ...matching_paths );
1840
1811
  }
1841
-
1842
1812
  } // ends for each file
1843
1813
 
1844
1814
  if ( missing_files.length > 0 ) {
1845
1815
  await scope.addToReport( scope, {
1846
1816
  type: `warning`,
1847
- value: `ALKiln could not find "${ missing_files.join('", "') }" in any of these folders: "${ dirs.join('", "') }".`
1817
+ value: `ALKiln could not find "${ missing_files.join('", "') }" in "${ dir }".`
1848
1818
  });
1849
1819
  }
1850
1820
 
@@ -1891,8 +1861,6 @@ module.exports = {
1891
1861
  await handle.evaluate( el => { el.value = '' });
1892
1862
  await handle.focus();
1893
1863
  await handle.type( answer );
1894
- setTimeout(async () => {
1895
- }, 500)
1896
1864
  },
1897
1865
 
1898
1866
  draw_signature: async function ( scope, name ) {
@@ -2523,10 +2491,6 @@ module.exports = {
2523
2491
  /** Sets a non-story table variable to a "secret" value (i.e. an env var) */
2524
2492
  // Prevent pictures of a screen with a secret.
2525
2493
  scope.disable_error_screenshot = true;
2526
- if ( session_vars.get_dangerous_debug() ) {
2527
- scope.disable_error_screenshot = false;
2528
- }
2529
-
2530
2494
  let var_data = await scope.normalizeTable( scope, {
2531
2495
  var_data: [{
2532
2496
  var: var_name,
@@ -2670,10 +2634,7 @@ module.exports = {
2670
2634
  */
2671
2635
  // Don't take a picture of a failed login in case one of the inputs is correct
2672
2636
  scope.disable_error_screenshot = true;
2673
- if ( session_vars.get_dangerous_debug() ) {
2674
- scope.disable_error_screenshot = false;
2675
- }
2676
-
2637
+
2677
2638
  // If there is no browser open, start a new one
2678
2639
  if (!scope.browser) {
2679
2640
  scope.browser = await scope.driver.launch({ headless: !session_vars.DEBUG });
@@ -2706,10 +2667,6 @@ module.exports = {
2706
2667
  let email = process.env[ email_secret_name ];
2707
2668
  let password = process.env[ password_secret_name ];
2708
2669
 
2709
- if ( session_vars.get_dangerous_debug() ) {
2710
- log.debug({ type: `unsafe`, data: `login: ${ email }, ${ password }` });
2711
- }
2712
-
2713
2670
  // Give as many useful messages to the user as possible if the env vars
2714
2671
  // aren't defined. Eventually, just provide a link to the docs because
2715
2672
  // this is complex
package/lib/utils/log.js CHANGED
@@ -42,7 +42,7 @@ log.error = function ({ type='', pre='', data='', post='' }) {
42
42
 
43
43
  log.debug = function ({ type='', pre='', data='', post='' }) {
44
44
  /* If debugging is turned on, log a message that includes `ALKiln` and `debug` */
45
- if ( session_vars.get_debug() || session_vars.get_dangerous_debug() ) {
45
+ if ( session_vars.get_debug() ) {
46
46
  let start = create_msg_start([ type, 'debug' ], pre );
47
47
  // Log them each on a separate line
48
48
  console.log( `ALKiln ${ type } debug: ${ pre }` );
@@ -1,4 +1,5 @@
1
1
  const fs = require('fs');
2
+ const fg = require('fast-glob');
2
3
  const path = require('path');
3
4
  require('dotenv').config();
4
5
 
@@ -23,7 +24,6 @@ module.exports = session_vars;
23
24
  // These default to null
24
25
  // TODO: How to isolate debug elsewhere so this file can use the logger?
25
26
  session_vars.get_debug = function () { return process.env.DEBUG || null; };
26
- session_vars.get_dangerous_debug = function () { return process.env._ALKILN_DANGEROUS_DEBUG || null; };
27
27
  session_vars.is_dev_env = function () { return process.env.DEV || null; }
28
28
 
29
29
  session_vars.get_dev_api_key = function () { return process.env.DOCASSEMBLE_DEVELOPER_API_KEY || null; };
@@ -45,6 +45,7 @@ session_vars.get_tags = function () {
45
45
  return [];
46
46
  }
47
47
  };
48
+ session_vars.get_sources = function () { return process.env._ALKILN_SOURCES || null; };
48
49
 
49
50
  // More complex logic
50
51
  session_vars.get_server_reload_timeout = function () {
@@ -116,7 +117,6 @@ session_vars.get_languages = function () {
116
117
 
117
118
  const runtime_config_path = `runtime_config.json`;
118
119
  const project_name_key = `da_project_name`;
119
- const install_type_key = `da_install_type`;
120
120
  session_vars.save_project_name = function ( project_name ) {
121
121
  /* Saves the name of the project to a json obj so deletion can use it later.
122
122
  * Built for local as well as remote testing. Assumes a kosher string.
@@ -199,36 +199,6 @@ session_vars.save_artifacts_path_name = function ( path_name ) {
199
199
  return file;
200
200
  }; // Ends save_artifacts_path_name()
201
201
 
202
-
203
- session_vars.save_install_type = function (install_type) {
204
- /* Save a key with the name of the current artifacts folder in case it doesn't exist. */
205
-
206
- // Ensure artifacts path file exists
207
- if ( !fs.existsSync( runtime_config_path ) ) {
208
- // If the file doesn't exist, make one because next we need to read it
209
- fs.writeFileSync( runtime_config_path, JSON.stringify( {}, null, 2 ) );
210
- }
211
-
212
- // Get the json, changing it, and re-write the file from scratch
213
- let json = JSON.parse( fs.readFileSync( runtime_config_path ) );
214
- json[ install_type_key ] = install_type;
215
- let file = fs.writeFileSync( runtime_config_path, JSON.stringify( json, null, 2 ) );
216
-
217
- return file;
218
- }; // Ends save_artifacts_path_name()
219
-
220
- session_vars.get_install_type = function () {
221
- /* Get the name of the current artifacts path. */
222
- try {
223
- let json = JSON.parse( fs.readFileSync( runtime_config_path ));
224
- let install_type = json[ install_type_key ];
225
-
226
- return install_type;
227
- } catch ( error ) {
228
- return null;
229
- }
230
- }; // Ends get_artifacts_path_name()
231
-
232
202
  session_vars.get_artifacts_path_name = function () {
233
203
  /* Get the name of the current artifacts path. */
234
204
  try {
@@ -242,6 +212,39 @@ session_vars.get_artifacts_path_name = function () {
242
212
  }
243
213
  }; // Ends get_artifacts_path_name()
244
214
 
215
+ session_vars.set_sources_path = function ( path ) {
216
+ /** Returns whether the given path exists or not. If it does, store
217
+ * the path to the sources folder. Can be used for getting, e.g
218
+ * .feature files, files to upload, etc. May be annoying repetition
219
+ * for some local testing.
220
+ *
221
+ * Our code should ensure this gets run before cucumber gets triggered.
222
+ *
223
+ * @returns bool */
224
+
225
+ let sources = fg.sync(
226
+ [ path ],
227
+ { onlyFiles: false, suppressErrors: true }
228
+ );
229
+
230
+ if ( sources.length > 0 ) {
231
+ session_vars._sources_path = path;
232
+ return true;
233
+ } else {
234
+ session_vars._sources_path = null;
235
+ return false;
236
+ }
237
+ };
238
+
239
+ session_vars.get_sources_path = function () {
240
+ /** Get the "sources" folder path. Should never be undefined during
241
+ * a run of the test. run_cucumber.js should make sure it has
242
+ * a default value.
243
+ *
244
+ * @returns str || null */
245
+ return session_vars._sources_path;
246
+ };
247
+
245
248
  session_vars.validateEnvironment = function() {
246
249
  /** Throw a useful error for the developer if any of the env vars
247
250
  * required by absolutely all tests are missing. Gather all
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.0.0-pre-11",
3
+ "version": "5.1.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@suffolklitlab/alkiln",
9
- "version": "5.0.0-pre-11",
9
+ "version": "5.1.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@axe-core/puppeteer": "4.7.3",
@@ -19,6 +19,7 @@
19
19
  "dotenv": "8.2.0",
20
20
  "fast-glob": "3.2.12",
21
21
  "minimatch": "3.0.5",
22
+ "minimist": "1.2.8",
22
23
  "mocha": "9.2.2",
23
24
  "puppeteer": "20.8.2",
24
25
  "qs": "6.10.3",
@@ -1940,6 +1941,14 @@
1940
1941
  "node": "*"
1941
1942
  }
1942
1943
  },
1944
+ "node_modules/minimist": {
1945
+ "version": "1.2.8",
1946
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1947
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1948
+ "funding": {
1949
+ "url": "https://github.com/sponsors/ljharb"
1950
+ }
1951
+ },
1943
1952
  "node_modules/mitt": {
1944
1953
  "version": "3.0.0",
1945
1954
  "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
@@ -4516,6 +4525,11 @@
4516
4525
  "brace-expansion": "^1.1.7"
4517
4526
  }
4518
4527
  },
4528
+ "minimist": {
4529
+ "version": "1.2.8",
4530
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
4531
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
4532
+ },
4519
4533
  "mitt": {
4520
4534
  "version": "3.0.0",
4521
4535
  "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.0.2-feat-github-server-14",
3
+ "version": "5.1.0-sources-1",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -9,7 +9,6 @@
9
9
  "local": "npm run setup && npm run cucumber; npm run takedown",
10
10
  "setup": "./lib/docassemble/setup.js",
11
11
  "takedown": "./lib/docassemble/takedown.js",
12
- "server_install": "./lib/docassemble/server_install.js",
13
12
  "unit": "mocha tests/unit_tests/*.test.*"
14
13
  },
15
14
  "repository": {
@@ -41,6 +40,7 @@
41
40
  "dotenv": "8.2.0",
42
41
  "fast-glob": "3.2.12",
43
42
  "minimatch": "3.0.5",
43
+ "minimist": "1.2.8",
44
44
  "mocha": "9.2.2",
45
45
  "puppeteer": "20.8.2",
46
46
  "qs": "6.10.3",
@@ -48,7 +48,6 @@
48
48
  "uuid": "8.3.2"
49
49
  },
50
50
  "bin": {
51
- "alkiln-server-install": "./lib/docassemble/server_install.js",
52
51
  "alkiln-setup": "./lib/docassemble/setup.js",
53
52
  "alkiln-takedown": "./lib/docassemble/takedown.js",
54
53
  "alkiln-run": "./lib/run_cucumber.js"
package/setup.py CHANGED
@@ -50,10 +50,10 @@ setup(name='docassemble.ALKilnTests',
50
50
  author='plocket',
51
51
  author_email='example@example.com',
52
52
  license='The MIT License (MIT)',
53
- url='https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln',
53
+ url='https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/automated_integrated_testing',
54
54
  packages=find_packages(),
55
55
  namespace_packages=['docassemble'],
56
- install_requires=['docassemble.ALToolbox>=0.8.2', 'docassemble.AssemblyLine>=2.24.0'],
56
+ install_requires=['docassemble.ALToolbox>=0.5.1'],
57
57
  zip_safe=False,
58
58
  package_data=find_package_data(where='docassemble/ALKilnTests/', package='docassemble.ALKilnTests'),
59
59
  )
@@ -1,83 +0,0 @@
1
- name: GitHub server tests
2
-
3
- on:
4
- push:
5
- # why is workflow dispatch not showing up in github?
6
- workflow_dispatch:
7
- inputs:
8
- tags:
9
- required: False
10
- description: 'Optional. Use a "tag expression" specify which tagged tests to run (https://cucumber.io/docs/cucumber/api/#tag-expressions)'
11
- # # Only test 2 tests - one uses an env var, the other uses AL.
12
- # # They test if env vars get set correctly and depencencies get
13
- # # pulled in correctly.
14
- # default: '@al1'
15
- enable_tmate:
16
- type: boolean
17
- description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
18
- required: false
19
- default: false
20
- # TODO: alkiln_version:
21
-
22
- jobs:
23
-
24
- github-server-tests:
25
- runs-on: ubuntu-latest
26
- strategy:
27
- matrix:
28
- node-version: [18.x]
29
-
30
- env:
31
- # How would a dev make another user/password if they wanted to?
32
- SECRET_VAR1: secret-var1-value
33
- SECRET_VAR2: secret-var2-value
34
- SECRET_FOR_MISSING_FIELD: secret for missing field
35
- #_ALKILN_DANGEROUS_DEBUG: true
36
-
37
- steps:
38
- # Place the root directory in this branch
39
- - uses: actions/checkout@v3
40
-
41
- # Debugging. Maybe add a long sleep after whatever it is we want to check
42
- # to give us time to explore.
43
- - name: Setup tmate session
44
- if: ${{ inputs.enable_tmate }}
45
- uses: mxschmitt/action-tmate@v3
46
- with:
47
- detached: true
48
-
49
- - name: Start the temporary GitHub server
50
- id: github_server
51
- uses: ./github_server
52
- with:
53
- CONFIG_CONTENTS: "${{ secrets.CONFIG_CONTENTS }}"
54
- - run: echo "Finished starting the GitHub server"
55
-
56
- # Debugging on failure
57
- # Trying to catch that intermittent bug mentioned in ./github_server/action.yml
58
- - name: Docker failure tmate session
59
- if: ${{ failure() }}
60
- uses: mxschmitt/action-tmate@v3
61
-
62
- - name: Env vars from server output
63
- run: |
64
- echo "USER1_EMAIL=${{ steps.github_server.outputs.DA_ADMIN_EMAIL }}" >> $GITHUB_ENV
65
- echo "USER1_PASSWORD=${{ steps.github_server.outputs.DA_ADMIN_PASSWORD }}" >> $GITHUB_ENV
66
- #- name: Work with docker
67
- # run: |
68
- # # Authors can use this to do more with the docker container.
69
- # # There may be a better way. Would take research. A docker action?
70
- # container_name=$(docker ps --format '{{.Names}}' | head -n 1)
71
- # echo "container_name is $container_name"
72
-
73
- - name: "Run ALKiln tests"
74
- if: ${{ success() }}
75
- uses: ./
76
- with:
77
- SERVER_URL: "${{ steps.github_server.outputs.SERVER_URL }}"
78
- DOCASSEMBLE_DEVELOPER_API_KEY: "${{ steps.github_server.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}"
79
- INSTALL_METHOD: "server"
80
- # TODO: See what happens if this is an empty string
81
- ALKILN_VERSION: "5.0.2-feat-github-server-14"
82
- - run: echo "Finished running ALKiln tests"
83
-
@@ -1,143 +0,0 @@
1
- name: "ALKiln/github_server: Isolated docassemble tests"
2
- description: "Download and start up a temporary docassemble server on GitHub's servers, in an isolated environment. Avoids docassemble reload errors and other external factors."
3
-
4
- # Bug? Race condition? Exit with 52 when waiting for container to finish starting
5
- # https://github.com/SuffolkLITLab/ALKiln/actions/runs/5912900256/job/16036751264#step:4:188
6
- # Had no completion messages, but it exited with an error anyway
7
- # Took 121 seconds
8
- # The rerun of the test completed that step just fine, though it took less time
9
- # Took 120 and was fine: https://github.com/SuffolkLITLab/ALKiln/actions/runs/5913167036/job/16037288791
10
- # 150 secs with exit code 56:
11
- # https://github.com/SuffolkLITLab/ALKiln/actions/runs/5914677262/job/16040233419#step:4:196
12
- # 120 sec with exit code 56:
13
- # https://github.com/SuffolkLITLab/ALKiln/actions/runs/5914963614/job/16040773624#step:4:206
14
-
15
- # If we want to control the version of the docassemble image, we'd have
16
- # to make our own image:
17
- # You can build your own image by doing git clone jhpyle/docassemble and then
18
- # using git checkout to select a particular version, and then using docker build
19
- # to build an image. https://docassemble.org/docs/docker.html#downgrading
20
- # If we just want to use an older version of da (as long as it works with
21
- # the latest image):
22
- # If you have an existing container you can use dainstall from the command
23
- # line to install docassemble.webapp==1.4.68
24
-
25
- inputs:
26
- CONFIG_CONTENTS:
27
- description: "The contents of the config file of the docassemble server"
28
- required: false
29
- MAX_SECONDS_FOR_DOCKER:
30
- description: "Maximum amount of seconds to give the docassemble docker container to serve the site."
31
- required: false
32
- default: 600 # 10 min
33
-
34
- outputs:
35
- DOCASSEMBLE_DEVELOPER_API_KEY:
36
- description: "Use this inside `with:` for the ALKiln step. Temporary admin api key. It will be destroyed after the tests are done."
37
- value: ${{ steps.api_key_output_step.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}
38
- SERVER_URL:
39
- description: "Use this inside `with:` for the ALKiln step. URL of the temporary docassemble server."
40
- value: "http://localhost"
41
- DA_ADMIN_EMAIL:
42
- value: ${{ steps.api_key_output_step.outputs.DA_ADMIN_EMAIL }}
43
- DA_ADMIN_PASSWORD:
44
- value: ${{ steps.api_key_output_step.outputs.DA_ADMIN_PASSWORD }}
45
-
46
-
47
- runs:
48
- using: "composite"
49
- steps:
50
- - uses: actions/checkout@v3
51
-
52
- - name: "ALKiln github_server: Set environment variables"
53
- run: |
54
- echo "DA_ADMIN_API_KEY=abcd1234abcd1234abcd5678abdc5678" >> $GITHUB_ENV
55
- echo "MAX_SECONDS_FOR_DOCKER=${{ inputs.MAX_SECONDS_FOR_DOCKER }}" >> $GITHUB_ENV
56
- echo "DA_ADMIN_EMAIL=admin@example.com" >> $GITHUB_ENV
57
- # Login will fail if this is "password"
58
- echo "DA_ADMIN_PASSWORD=@123abcdefg" >> $GITHUB_ENV
59
- shell: bash
60
- - id: api_key_output_step
61
- run: |
62
- echo "DOCASSEMBLE_DEVELOPER_API_KEY=$DA_ADMIN_API_KEY" >> "$GITHUB_OUTPUT"
63
- echo "DA_ADMIN_EMAIL=$DA_ADMIN_EMAIL" >> "$GITHUB_OUTPUT"
64
- echo "DA_ADMIN_PASSWORD=$DA_ADMIN_PASSWORD" >> "$GITHUB_OUTPUT"
65
- shell: bash
66
-
67
- - name: "ALKiln github_server: Download and start docker"
68
- run: |
69
- mkdir /tmp/config
70
- echo "${{ inputs.CONFIG_CONTENTS }}" > /tmp/config/myconfig.yml
71
- docker pull jhpyle/docassemble
72
- docker run --env DA_ADMIN_EMAIL="${{ env.DA_ADMIN_EMAIL }}" --env DA_ADMIN_PASSWORD="${{ env.DA_ADMIN_PASSWORD }}" --env DA_ADMIN_API_KEY="${{ env.DA_ADMIN_API_KEY }}" --env DA_CONFIG=/tmp/config/myconfig.yml --volume /tmp/config:/tmp/config --cap-add SYS_PTRACE --memory="4gb" -d -p 80:80 jhpyle/docassemble
73
- shell: bash
74
- - name: "ALKiln github_server: Wait for server to start"
75
- run: |
76
- # ALKiln github_server: Wait for server to start
77
-
78
- # Reset the SECONDS variable
79
- SECONDS=0
80
- # Wait 1 minute before pinging the server constantly
81
- sleep 60
82
-
83
- # Use a variable instead of checking $response so we can control to avoid race
84
- # conditions from preventing the success log message.
85
- ready="false"
86
-
87
- while [[ $ready == "false" ]]; do
88
- echo "Time ellapsed: $SECONDS seconds"
89
-
90
- # Check if max seconds till loading has passed
91
- if [[ $SECONDS -ge $MAX_SECONDS_FOR_DOCKER ]]; then
92
- echo "ALKiln GitHub Server Error: Timed out waiting for docker container to serve the site. If you want to give your docker container more timem to install, set the MAX_SECONDS_FOR_DOCKER input for this action."
93
- # Exit with failure, or allow this to keep going?
94
- break
95
- fi
96
-
97
- # Set the variable to a new string value in each iteration
98
- response=$(curl -o /dev/null -s -w "%{http_code}\n" http://localhost:80/health_check?ready=1)
99
- echo "response is $response"
100
-
101
- # Check if the string includes the desired text
102
- if [[ $response == *"200"* ]]; then
103
- echo "Success! The docker container is ready!"
104
- ready="true"
105
- else
106
- echo "The docker container isn't ready yet. Will check again in 30 seconds."
107
- sleep 30
108
- fi
109
-
110
- done
111
- shell: bash
112
-
113
- # Don't rely on the environment's version of python
114
- - uses: actions/setup-python@v4
115
- with:
116
- python-version: '3.10'
117
- - run: pip install docassemblecli
118
- shell: bash
119
- # Install the current directory into docassemble
120
- - run: dainstall . --apiurl http://localhost --apikey $DA_ADMIN_API_KEY
121
- shell: bash
122
-
123
- # Upload artifacts that subscribers can download on the Actions summary page
124
- - name: "ALKiln github_server: Docker logs"
125
- if: ${{ always() }}
126
- run: |
127
- docker_logs=$(docker logs $(docker ps --filter "ancestor=jhpyle/docassemble" --format {{.ID}}))
128
- # Should this name have a date/time attached? The logs themselves have timestamps
129
- echo "$docker_logs" > /tmp/alkiln_docker_logs.txt
130
- shell: bash
131
-
132
- # Should this date/time be created at the start of this action?
133
- - name: "ALKiln github_server: Get folder name with date and time"
134
- if: ${{ always() }}
135
- run: |
136
- echo "GITHUB_SERVER_ARTIFACT_NAME=alkiln-github-server-$(date +'%Y-%m-%d at %Hh%Mm%Ss' -u)UTC" >> $GITHUB_ENV
137
- shell: bash
138
- - name: "ALKiln github_server: Upload artifacts folder"
139
- if: ${{ always() }}
140
- uses: actions/upload-artifact@v3
141
- with:
142
- name: ${{ env.GITHUB_SERVER_ARTIFACT_NAME }}
143
- path: /tmp/alkiln_docker_logs.txt
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const session_vars = require('../utils/session_vars');
4
-
5
- const server_install = async () => {
6
- session_vars.save_install_type("server");
7
- }; // Ends server_install();
8
-
9
- server_install();