@suffolklitlab/alkiln 5.11.1 → 5.11.2-feat-output-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.
@@ -70,11 +70,11 @@ jobs:
70
70
  - run: echo "ALKiln finished starting the isolated GitHub docassemble server"
71
71
  shell: bash
72
72
 
73
- #### Developer note: You can probably leave this out
74
- # Optional debugging to explore things like docker issues
75
- - name: Docker debug tmate session
76
- if: ${{ github.event.inputs.enable_tmate }}
77
- uses: mxschmitt/action-tmate@v3
73
+ # #### Developer note: You can probably leave this out
74
+ # # Optional debugging to explore things like docker issues
75
+ # - name: Docker debug tmate session
76
+ # if: ${{ failure() && github.event.inputs.enable_tmate == 'true' }}
77
+ # uses: mxschmitt/action-tmate@v3
78
78
 
79
79
  #### Developer note: You can probably leave this out
80
80
  - name: Run ALKiln - Env vars for our own tests from server output
@@ -93,25 +93,50 @@ jobs:
93
93
 
94
94
  - name: "Run ALKiln tests"
95
95
  if: ${{ success() }}
96
+ id: alkiln
96
97
  #### Developer note: you'll need to replace `uses: ./`
97
98
  #### with the path to ALKiln’s repository name and branch name. For example,
98
99
  #### for version 5, use the branch name `v5`: `uses: suffolkLITLab/ALKiln@v5`
99
100
  uses: ./
100
- #### Developer note: You can probably leave this out
101
+ #### Developer note: You can probably leave out this `env` section
101
102
  env:
102
- ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || 'not @a11y' }}"
103
+ # Internal: docassemble changes sometimes break @a11y tests. That is
104
+ # a da problem, not ours. We will trigger these tests manually when we
105
+ # want to check up on this.
106
+ ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || '(not @a11y) and (not @error)' }}"
103
107
  with:
104
108
  #### Developer note: Required inputs. See
105
109
  #### https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#sandbox-inputs
106
110
  SERVER_URL: "${{ steps.github_server.outputs.SERVER_URL }}"
107
111
  DOCASSEMBLE_DEVELOPER_API_KEY: "${{ steps.github_server.outputs.DOCASSEMBLE_DEVELOPER_API_KEY }}"
108
112
  INSTALL_METHOD: "server"
109
- #### Developer note: See https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#optional-inputs
110
- #### You can probably leave this out
111
- # Internal: docassemble changes sometimes break @a11y tests. That is
112
- # a da problem, not ours. We will trigger these tests manually when we
113
- # want to check up on this.
114
- ALKILN_TAG_EXPRESSION: $ALKILN_TAG_EXPRESSION
115
- #### Developer note: You can probably leave this out
116
- # ALKILN_VERSION: url
113
+ #### Developer note: You can probably leave the rest out
114
+ #### To learn more, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#optional-inputs
115
+ ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
116
+ ALKILN_VERSION: output
117
+
118
+ #### Developer note: Example of making an issue when tests fail, part 1
119
+ - name: Get ALKiln output files
120
+ run: |
121
+ echo "REPORT=${{ steps.alkiln.outputs.REPORT }}" >> $GITHUB_ENV
122
+ echo "UNEXPECTED_RESULTS=${{ steps.alkiln.outputs.UNEXPECTED_RESULTS }}" >> $GITHUB_ENV
123
+ echo "DEBUG_LOG=${{ steps.alkiln.outputs.DEBUG_LOG }}" >> $GITHUB_ENV
124
+
125
+ #### Developer note: Example of making an issue when tests fail, part 2
126
+ #### You can do this with any type of GitHub test
127
+ #### Change the name, title, body, and labels
128
+ #### To learn more, search online for actions-ecosystem/action-create-issue
129
+ - name: "If tests failed, create an issue"
130
+ if: ${{ failure() && '$UNEXPECTED_RESULTS' != 'false' }}
131
+ uses: actions-ecosystem/action-create-issue@v1
132
+ with:
133
+ github_token: ${{ secrets.github_token }}
134
+ title: "ALKiln tests failed"
135
+ body: |
136
+ An ALKiln test run failed. See the action at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.
137
+
138
+ ### Report of unexpected results:
117
139
 
140
+ ${{ env.UNEXPECTED_RESULTS }}
141
+ labels: |
142
+ testing
@@ -55,18 +55,13 @@ jobs:
55
55
  # Place the root directory in this branch to access relative paths to action files
56
56
  - uses: actions/checkout@v4
57
57
 
58
- - name: Pause between triggers
59
- if: (github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork)
60
- shell: bash
61
- run: sleep 60
62
-
63
58
  #### Developer note: you'll need to replace `uses: ./`
64
59
  #### with the path to ALKiln’s repository name and branch name. For example,
65
60
  #### for version 5, use the branch name `v5`: `uses: suffolkLITLab/ALKiln@v5`
66
61
  - uses: ./
67
62
  #### Developer note: You can probably leave this out
68
63
  env:
69
- ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || 'not @a11y' }}"
64
+ ALKILN_TAG_EXPRESSION: "${{ (github.event.inputs.tags && format('{0}', github.event.inputs.tags)) || '(not @a11y) and (not @error)' }}"
70
65
  with:
71
66
  #### Developer note: Required ALKiln inputs. You can read more at
72
67
  #### https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing/#githubNyou-inputs
@@ -77,6 +72,6 @@ jobs:
77
72
  # Internal: docassemble changes sometimes break @a11y tests. That is
78
73
  # a da problem, not ours. We will trigger these tests manually when we
79
74
  # want to check up on this.
80
- ALKILN_TAG_EXPRESSION: $ALKILN_TAG_EXPRESSION
75
+ ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
81
76
  #### Developer note: You can probably leave this out
82
77
  # ALKILN_VERSION: url
package/CHANGELOG.md CHANGED
@@ -43,7 +43,44 @@ Format:
43
43
  -
44
44
  -->
45
45
 
46
- <!-- ## [Unreleased] -->
46
+ ## [Unreleased]
47
+
48
+ ### Added
49
+
50
+ - New outputs from the ALKiln action - `REPORT`, `UNEXPECTED_RESULTS_PATH`, and `DEBUG_LOG_PATH`. Authors can choose to use these as they wish. For example, they can put the contents of these in issues they create from their workflow. Closes [#701](https://github.com/SuffolkLITLab/ALKiln/issues/701).
51
+ - New file with just failure results, including cucumber results when possible. It accumulates Scenario info as the tests run. Even if a test run stops early, there will still be some results.
52
+
53
+ ### Changed
54
+
55
+ - report.txt should accumulate over time instead of being created all at once at the end. Even if a test run stops early, there will still be some results.
56
+ - report.txt includes the cucumber output at the bottom as well.
57
+
58
+ ### Fixed
59
+
60
+ - Tag expression not being parsed correctly. Surrounded `ALKILN_TAG_EXPRESSION` with `"`.
61
+
62
+ ### Internal
63
+
64
+ - Removed our own workflow's pause between triggers
65
+ - Improved our use of ALKIN_TAG_EXPRESSION env var
66
+ -
67
+
68
+ ## [5.11.2] - 2024-05-17
69
+
70
+ ### Added
71
+
72
+ - Added new debug log/code to reflect difference between a expected status mismatch and a cucumber status mismatch.
73
+
74
+ ### Changed
75
+
76
+ - Changed text of error to make it more clear.
77
+ - Changed logic of error finding to use expected status for users as well as internal tests. That needs discussion.
78
+ - Refactored code in that section.
79
+ - Wait till the end to throw some errors so the user can get as much info as possible about the failures.
80
+
81
+ ### Internal
82
+
83
+ - Exclude another file from code checking (cucumber-report.txt)
47
84
 
48
85
  ## [5.11.1] - 2024-04-06
49
86
 
package/action.yml CHANGED
@@ -34,6 +34,18 @@ inputs:
34
34
  required: false
35
35
  default: "0.0.17"
36
36
 
37
+
38
+ outputs:
39
+ REPORT:
40
+ description: 'report.txt contents or "false" if the file is missing'
41
+ value: ${{ steps.files.outputs.REPORT }}
42
+ UNEXPECTED_RESULTS:
43
+ description: 'unexpected_results.txt contents or "false" if the file is missing'
44
+ value: ${{ steps.files.outputs.UNEXPECTED_RESULTS }}
45
+ DEBUG_LOG:
46
+ description: 'debug_log.txt contents or "false" if the file is missing'
47
+ value: ${{ steps.files.outputs.DEBUG_LOG }}
48
+
37
49
  runs:
38
50
  using: "composite"
39
51
  steps:
@@ -121,7 +133,7 @@ runs:
121
133
  env:
122
134
  ALKILN_TAG_EXPRESSION: ${{ inputs.ALKILN_TAG_EXPRESSION || github.event.inputs.tags && format('{0}', github.event.inputs.tags) }}
123
135
  shell: bash
124
- run: alkiln-run $ALKILN_TAG_EXPRESSION
136
+ run: alkiln-run "$ALKILN_TAG_EXPRESSION"
125
137
 
126
138
  # on playground, delete project
127
139
  - name: "💡 ALK0032 INFO: Delete the Project from the Playground"
@@ -137,6 +149,43 @@ runs:
137
149
  name: ${{ env.ARTIFACT_NAME }}
138
150
  path: ./alkiln-*
139
151
 
152
+ # Download artifacts folder internally to create action output
153
+ - name: "💡 ALK0182 INFO: Get uploaded artifacts folder"
154
+ uses: actions/download-artifact@v4
155
+ with:
156
+ name: ${{ env.ARTIFACT_NAME }}
157
+
158
+ # Create output
159
+ - shell: bash
160
+ id: files
161
+ run: |
162
+ # 💡 ALK0183 INFO: Give file contents or 'false' to interview authors
163
+ REPORT_PATH="${ARTIFACT_NAME}/report.txt"
164
+ UNEXPECTED_RESULTS_PATH="${ARTIFACT_NAME}/unexpected_results.txt"
165
+ DEBUG_LOG_PATH="${ARTIFACT_NAME}/debug_log.txt"
166
+
167
+ # Get contents of files if they exist
168
+ if [ -f "$REPORT_PATH" ]; then
169
+ REPORT=$(cat "$REPORT_PATH")
170
+ else
171
+ REPORT="report.txt does not exist"
172
+ fi
173
+ if [ -f "$UNEXPECTED_RESULTS_PATH" ]; then
174
+ UNEXPECTED_RESULTS_PATH=$(cat "$UNEXPECTED_RESULTS_PATH")
175
+ else
176
+ UNEXPECTED_RESULTS_PATH="unexpected_results.txt does not exist"
177
+ fi
178
+ if [ -f "$DEBUG_LOG_PATH" ]; then
179
+ DEBUG_LOG_PATH=$(cat "$DEBUG_LOG_PATH")
180
+ else
181
+ DEBUG_LOG_PATH="debug_log.txt does not exist"
182
+ fi
183
+
184
+ # Create the outputs
185
+ echo "REPORT=$REPORT" >> "$GITHUB_OUTPUT"
186
+ echo "UNEXPECTED_RESULTS=$UNEXPECTED_RESULTS" >> "$GITHUB_OUTPUT"
187
+ echo "DEBUG_LOG=$DEBUG_LOG" >> "$GITHUB_OUTPUT"
188
+
140
189
  - shell: bash
141
190
  run: |
142
191
  echo "💡 ALK0034 INFO: ALkiln finished running tests"
@@ -3,9 +3,11 @@
3
3
  const fs = require('fs');
4
4
  const cucumber_api = require('@cucumber/cucumber/api');
5
5
 
6
- const log = require('./utils/log');
6
+ const log = require('./utils/log'); // These always log in the console
7
7
  const session_vars = require('./utils/session_vars');
8
8
 
9
+ const SUMMARY_FILENAME = `cucumber-report.txt`;
10
+
9
11
  function build_config( command_args ) {
10
12
  // Turn all the paths to the sources folders into paths to the feature files
11
13
  let sources_paths = session_vars.get_sources_paths();
@@ -17,13 +19,15 @@ function build_config( command_args ) {
17
19
  const config = {
18
20
  publishQuiet: true,
19
21
  require: `./lib/index.js`,
20
- format: [`progress`, `summary:cucumber-report.txt`],
22
+ format: [`progress`, `summary:${ SUMMARY_FILENAME }`],
21
23
  paths: feature_paths,
22
24
  retry: 1,
23
25
  }
24
26
 
25
27
  // Normalize tag expressions, if there are any
26
- let tags = command_args[`_`];
28
+
29
+ // Joining and splitting handles tags surrounded with `""` or those without `""`
30
+ let tags = command_args[`_`].join(` `).split(` `);
27
31
  if (tags.length > 0) {
28
32
  let joined_tags = '';
29
33
  // Cucumber expects tags in boolean expressions, like (@a0 or @a1) and @random.
@@ -105,19 +109,20 @@ function build_config( command_args ) {
105
109
 
106
110
  const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
107
111
  if ( success ) {
108
- log.info({ type: `run`, code: `ALK0054`, pre: `Cucumber executed correctly` });
112
+ log.success({ type: `run`, code: `ALK0054`, pre: `Tests succeeded!` });
109
113
  } else {
110
- log.error({ type: `run`, code: `ALK0055`, pre: `Cucumber ran into an error` });
114
+ log.error({ type: `run`, code: `ALK0055`, pre: `One or more tests got unexpected results` });
111
115
  }
112
- const exitCode = success ? 0 : 1;
116
+ // Code is our custom code for proper completion with failed tests
117
+ const exitCode = success ? 0 : 2;
113
118
  process.exitCode = exitCode;
114
119
 
115
120
  // This is hardcoded information from `provide_config` above.
116
121
  // This code isn't used for anything other than detecting and removing color codes.
117
- let summary_file_name = 'cucumber-report.txt';
122
+ let summary_file_name = SUMMARY_FILENAME;
118
123
  fs.readFile(summary_file_name, 'utf8', (err, text) => {
119
124
  if (err) {
120
- log.error({ type: `run`, code: `ALK0056`, pre: `cucumber report`, data: err });
125
+ log.error({ type: `internal run`, code: `ALK0056`, pre: `Failed to read cucumber report`, data: err });
121
126
  return;
122
127
  }
123
128
 
@@ -128,5 +133,16 @@ function build_config( command_args ) {
128
133
  // Put it in the correct file in the correct folder
129
134
  let artifacts_path = session_vars.get_artifacts_path_name();
130
135
  fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, `\n\n${text}`);
136
+ try {
137
+ // Add to a unexpected results file (that should exist already) if needed
138
+ if ( exitCode === 2 ) {
139
+ fs.appendFileSync(
140
+ `${ artifacts_path }/${ log.unexpected_filename }`,
141
+ `\n\n${ text }`
142
+ );
143
+ }
144
+ } catch ( error ) {
145
+ log.error({ type: `internal run`, code: `ALK0181`, pre: `This is sometimes fine. ALKiln was unable to read ${ log.unexpected_filename }, which might just mean there was an unrelated failure above.`, data: error });
146
+ }
131
147
  });
132
148
  })();
package/lib/scope.js CHANGED
@@ -3228,8 +3228,7 @@ module.exports = {
3228
3228
  *
3229
3229
  * @param expected {array} Arr of strs that should appear in the report.
3230
3230
  */
3231
- let current_report_obj = scope.report.get( scope.scenario_id );
3232
- let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
3231
+ let scenario = scope.report.get( scope.scenario_id );
3233
3232
  let report = reports.getPrintableScenario( scenario );
3234
3233
  let all_are_included = true;
3235
3234
  for ( let one_expectation of expected ) {
@@ -3248,8 +3247,7 @@ module.exports = {
3248
3247
  *
3249
3248
  * @param not_expected {array} Arr of strs that should not appear in the report.
3250
3249
  */
3251
- let current_report_obj = scope.report.get( scope.scenario_id );
3252
- let scenario = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
3250
+ let scenario = scope.report.get( scope.scenario_id );
3253
3251
  let report = reports.getPrintableScenario( scenario );
3254
3252
  let none_included = true;
3255
3253
  for ( let one_expectation of not_expected ) {
package/lib/steps.js CHANGED
@@ -101,8 +101,11 @@ BeforeAll(async () => {
101
101
  session_vars.save_artifacts_path_name( scope.paths.artifacts );
102
102
 
103
103
  scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
104
+ scope.paths.failures = `${ scope.paths.artifacts }/${ log.unexpected_filename }`;
104
105
  scope.paths.debug_log = `${ scope.paths.artifacts }/${ log.debug_log_file }`;
105
106
  fs.writeFileSync( scope.paths.debug_log, "" );
107
+
108
+ reports.create( scope );
106
109
  });
107
110
 
108
111
  Before(async (scenario) => {
@@ -1212,7 +1215,7 @@ After(async function(scenario) {
1212
1215
  }
1213
1216
 
1214
1217
  // Record status before internal report tests can interfere with values
1215
- reports.setReportScenarioStatus( scope, { status: changeable_test_status });
1218
+ reports.saveFinalScenarioData( scope, { status: changeable_test_status });
1216
1219
 
1217
1220
  if (changeable_test_status !== `PASSED`) {
1218
1221
  log.debug({
@@ -1256,13 +1259,13 @@ After(async function(scenario) {
1256
1259
  } // ends if not passed
1257
1260
 
1258
1261
  // Add report text to Scenario folder after everything has been added to the report
1259
- let current_report_obj = scope.report.get( scope.scenario_id );
1260
- let scenario_report_obj = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
1262
+ let scenario_report_obj = scope.report.get( scope.scenario_id );
1261
1263
  let report = reports.getPrintableScenario( scenario_report_obj );
1262
1264
  // Save the report as in the Scenario folder
1263
1265
  fs.writeFileSync( scope.paths.scenario_report, report );
1266
+ // TODO: Save any cucumber failure message for the Scenario here (instead of waiting till run_cucumber.js)
1264
1267
 
1265
- // ---------------- Check internal test results of this framework ----------------
1268
+ // ---------------- Check internal test results ----------------
1266
1269
 
1267
1270
  if ( scope.expected_in_report && scope.expected_in_report.length > 0) {
1268
1271
  let report_includes_all_expected_strings = await scope.reportIncludesAllExpected(
@@ -1284,74 +1287,120 @@ After(async function(scenario) {
1284
1287
  scope.expected_not_in_report = null;
1285
1288
  }
1286
1289
 
1287
- // The default is 'passed', which won't need to get in here
1288
- if ( scope.expected_status ) {
1289
- // When internal tests should fail (to test our failure conditions), passing _is_ failing
1290
- expect( changeable_test_status ).to.equal( scope.expected_status );
1291
- // If the status was as expected, pass this Scenario
1292
- changeable_test_status = `PASSED`;
1293
- scope.expected_status = null; // reset for next Scenario
1290
+ // ---------------- Ends internal results check ----------------
1291
+
1292
+ if ( scope.expected_status === null ) {
1293
+ scope.expected_status = `PASSED`; // default expected status
1294
1294
  }
1295
1295
 
1296
- // ---------------- Ends check internal test results of this framework ----------------
1296
+ let expected_status_was_met = true;
1297
+ let expected_status_err_msg = ``;
1298
+ // Avoid manipulating `UNDEFINED` and such
1299
+ if ( changeable_test_status === `PASSED`
1300
+ || changeable_test_status === `FAILED` ) {
1301
+
1302
+ if ( changeable_test_status === scope.expected_status ) {
1303
+ // If the status was as expected, pass this Scenario. E.g. an internal
1304
+ // test was supposed to fail and it did. Or a test was suppose to pass
1305
+ // and it passed.
1306
+ changeable_test_status = `PASSED`;
1307
+ } else {
1308
+ // If a test that was supposed to fail passed (e.g. internal
1309
+ // test), it failed. If is was supposed to pass and failed, it also
1310
+ // failed. E.g. a11y failures let the Scenario pass initially, but
1311
+ // should cause a failure here.
1312
+ changeable_test_status = `FAILED`;
1313
+ // Flag this failure to trigger an error later, so cucumber shows
1314
+ // it as failed too.
1315
+ expected_status_was_met = false;
1316
+ // Keep it general. Users may see this message, e.g. for a11y tests.
1317
+ let msg = `Test failed. Expected status: ${scope.expected_status}. `
1318
+ + `ALKiln's assessed status: ${ changeable_test_status }. `
1319
+ + `See the report for details.`;
1320
+ let code = `ALK0099`;
1321
+ log.debug({ type: `error`, code: code, pre: msg, })
1322
+ expected_status_err_msg = `🤕 ${ code } ERROR: ${ msg }`
1323
+ }
1324
+ }
1325
+ scope.expected_status = null; // reset for next Scenario
1326
+
1327
+ let need_to_force_failed_status = false;
1328
+ let force_failure_msg = `ALKiln assessed that the test failed. See the report for details.`;
1329
+ let force_failure_code = `ALK0180`;
1330
+ if ( changeable_test_status !== `FAILED` ) {
1331
+ // Other than `FAILED`, it's fine to set the scenario status to make
1332
+ // sure it matches up with ALKiln's assessed status.
1333
+ // WARNING: This may be impossible in future cucumber versions
1334
+ scenario.result.status = changeable_test_status;
1335
+ // TODO: Should we add (an info) debug log here too?
1297
1336
 
1337
+ // If assessed status is FAILED, but result.status is something else
1338
+ } else if ( scenario.result.status !== `FAILED` ) {
1339
+ // Make sure that tests which ALKiln has calculated as failed
1340
+ // cause a cucumber error later. Just changing the status to `FAILED`
1341
+ // causes unhelpful cucumber errors. TODO: Add to internal docs.
1342
+ log.debug({
1343
+ type: `error`,
1344
+ code: force_failure_code,
1345
+ pre: force_failure_msg
1346
+ });
1347
+ // TODO: Look into setDefinitionFunctionWrapper to catch errors
1348
+ // of failing tests that should indeed be failing:
1349
+ // https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/api_reference.md#setdefinitionfunctionwrapperwrapper
1350
+ need_to_force_failed_status = true;
1351
+ }
1352
+
1353
+ let signout_succeeded = true;
1354
+ let signout_error_msg = ``;
1298
1355
  // If there is a page open, then sign out and close it
1299
1356
  if ( scope.page ) {
1300
1357
  // Catch a possible reload error.
1301
-
1302
- // TODO: implement and use scope.handle_possible_timeout()
1303
1358
  try {
1304
1359
  // puppeteer will ensure proper timeout.
1305
1360
  await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
1306
1361
 
1307
1362
  } catch ( error ) {
1363
+ signout_succeeded = false;
1308
1364
 
1309
1365
  if ( error.name === `TimeoutError` ) {
1310
1366
  // TODO: Should a more specific message about signing out get into the report no matter what?
1311
1367
  // If so, what should the message be in order to not get confused with these other messages?
1312
1368
  let non_reload_report_msg = `It took too long to sign out in preparation for the next test.`;
1313
- await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
1314
- code: `ALK0097`,
1315
- message: non_reload_report_msg,
1316
- }, error });
1369
+ let code = `ALK0097`;
1370
+ try {
1371
+ await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
1372
+ code: code,
1373
+ message: non_reload_report_msg,
1374
+ }, error });
1375
+ } catch {
1376
+ signout_error_msg = `🤕 ${ code } ERROR: ${ non_reload_report_msg }`;
1377
+ }
1317
1378
  } else {
1318
1379
  // Throw any non-timeout error
1319
- let err_msg = `Error occurred when ALKiln tried to sign out in preparation for the next test.`
1320
- reports.addToReport( scope, { type: `error`, code: `ALK0098`, value: err_msg });
1321
- throw error;
1380
+ let other_err_msg = `Error occurred when ALKiln tried to sign out in preparation for the next test.`
1381
+ let code = `ALK0098`;
1382
+ reports.addToReport( scope, { type: `error`, code: code, value: other_err_msg });
1383
+ signout_error_msg = `🤕 ${ code } ERROR: ${ other_err_msg }`;
1322
1384
  } // ends if error is timeout error
1323
1385
 
1324
1386
  } // ends try/catch
1325
1387
 
1326
1388
  } // ends if scope.page
1327
1389
 
1328
- // INTERNAL WARNING: TODO: A console.log here prevents the try/catch from
1329
- // throwing an error. Why is that? Does it somehow make sure that scope.page
1330
- // gets destroyed?
1331
-
1332
- // console.log('This stops try/catch from throwing an error in some cases.');
1333
-
1334
- // More internal testing stuff
1335
- if ( changeable_test_status === `FAILED` ) {
1336
- // If the result object status wasn't failure and we still want
1337
- // the tests to fail, as with a11y tests, we can't mess with the
1338
- // cucumber result.status object because it will cause unwanted
1339
- // errors. We have to throw an error here instead.
1340
- if ( scenario.result.status !== `FAILED` ) {
1341
- let msg = `The test unexpectedly didn't fail. Scenario status: ${scenario.result.status}.`
1342
- reports.addToReport( scope, { type: `error`, code: `ALK0099`, value: msg });
1343
- throw new Error(msg);
1344
- }
1345
- } else {
1346
- // Some internal test failures should be ok
1347
- // TODO: Look into setDefinitionFunctionWrapper to catch errors
1348
- // of failing tests that should indeed be failing:
1349
- // https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/api_reference.md#setdefinitionfunctionwrapperwrapper
1350
- scenario.result.status = changeable_test_status;
1390
+ let errors = []
1391
+ if ( !expected_status_was_met ) { errors.push( expected_status_err_msg ); }
1392
+ if ( !signout_succeeded ) { errors.push( signout_error_msg ); }
1393
+ if ( !expected_status_was_met || !signout_succeeded ) {
1394
+ throw new Error( `${errors.join( errors, '\n')}` );
1351
1395
  }
1352
- // Ends more internal testing stuff
1353
1396
 
1354
- // WARNING: TODO: console.log can prevent a sign-out error. What race condition is going on?
1397
+ // Theoretically, we should never get here:
1398
+ // If everything else worked, if we need to force an internal test failure
1399
+ if ( need_to_force_failed_status ) {
1400
+ throw new Error( `🤕 ${force_failure_code} unreachable ERROR: ${force_failure_msg}` );
1401
+ }
1402
+
1403
+ // Find the race condition.
1355
1404
  log.debug({ type: `cucumber`, code: `ALK0100`, verbose: true, pre: `Scenario AfterStep() message`, data: scenario.result.message });
1356
1405
 
1357
1406
  });
@@ -1365,9 +1414,6 @@ AfterAll(async function() {
1365
1414
  // May need newest version of cucumberjs
1366
1415
  console.log( `\n\n${ report }` );
1367
1416
 
1368
- // Save/download the report as an artifact
1369
- fs.writeFileSync( scope.paths.report, report );
1370
-
1371
1417
  // If there is a browser open, then close it
1372
1418
  if (scope.browser) {
1373
1419
  await scope.browser.close();
package/lib/utils/log.js CHANGED
@@ -2,6 +2,9 @@ const fs = require('fs');
2
2
  const session_vars = require( './session_vars' );
3
3
 
4
4
  // ? 🌈 💡 🔎 🤕 🐞
5
+ // Alternatives: 🩹 ❤️‍🩹 🚑
6
+ // TODO: return full message so it can be passed to manually
7
+ // thrown errors if needed.
5
8
 
6
9
  let log = {};
7
10
  module.exports = log;
@@ -89,4 +92,5 @@ log.add_to_debug_log = function(value) {
89
92
  fs.appendFileSync(log.debug_log_file, value);
90
93
  };
91
94
 
92
- log.debug_log_file = 'debug_log.txt'
95
+ log.debug_log_file = `debug_log.txt`;
96
+ log.unexpected_filename = `unexpected_results.txt`;
@@ -7,6 +7,13 @@ const files = require(`./files` );
7
7
 
8
8
  module.exports = reports = {};
9
9
 
10
+ reports.title = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
11
+ reports.failures = [ `\n\n===============================\n===============================\n🤕 Failed scenarios:\n` ];
12
+ reports.successes = [ `\n\n===============================\n===============================\n🌈 Passed scenarios:\n` ];
13
+ // reports.warnings = [ `\n\n===============================\n===============================\n🔎 Scenarios to check:\n` ];
14
+ // reports.undefineds = [ `\n\n===============================\n===============================\n[langs icon] Scenarios with typos:\n` ];
15
+ reports.others = [ `\n\n===============================\n===============================\n🔎 Other scenarios that did not pass:\n` ];
16
+
10
17
  reports.addReportHeading = function (scope, { scenario }) {
11
18
  /** Return printable heading based on Scenario name and tags.
12
19
  * Allow characters from other languages.
@@ -46,6 +53,9 @@ reports.addToReport = function( scope, { type, code, value }) {
46
53
  * @params code {string} (Optional) Error or warning code. Default
47
54
  * is unspecified error or warning.
48
55
  * */
56
+ // TODO: return full message so it can be passed to manually
57
+ // thrown errors if needed.
58
+
49
59
  // Debugging logs
50
60
  let debug_line = getDebugLine({ type, code, value });
51
61
  if ( session_vars.get_debug() ) {
@@ -68,21 +78,30 @@ reports.addToReport = function( scope, { type, code, value }) {
68
78
  }
69
79
 
70
80
  // Report stuff
71
- let scenario = makeSureReportPartsExist( scope );
81
+ let scenario = reports.makeSureScenarioExists( scope );
72
82
  scenario.lines.push({ type, code, value });
73
83
 
74
84
  return scenario;
75
85
  }; // Ends reports.addToReport()
76
86
 
77
- let makeSureReportPartsExist = function( scope ) {
87
+ reports.create = function( scope ) {
88
+ reports.makeSureReportExists( scope );
89
+ }
90
+
91
+ reports.makeSureReportExists = function( scope ) {
78
92
  /** If a part of the report needs to exist right now, but doesn't exist,
79
93
  * create it. */
80
94
  if ( !scope.report ) { scope.report = new Map(); }
95
+ return this;
96
+ }; // Ends reports.makeSureReportExists()
97
+
98
+ reports.makeSureScenarioExists = function ( scope ) {
99
+ reports.makeSureReportExists( scope );
81
100
  if ( !scope.scenario_id ) {
82
101
  scope.scenario_id = `_report_${ Date.now() }`;
83
102
  }
84
103
  if ( !scope.report.get( scope.scenario_id )) {
85
- scope.report.set( scope.scenario_id, {} )
104
+ scope.report.set( scope.scenario_id, {} );
86
105
  scope.report.get( scope.scenario_id ).status = `in progress`;
87
106
  }
88
107
 
@@ -90,11 +109,36 @@ let makeSureReportPartsExist = function( scope ) {
90
109
  if ( !scenario.lines ) { scenario.lines = []; }
91
110
 
92
111
  return scenario;
93
- }; // Ends makeSureReportPartsExist()
112
+ }; // Ends reports.makeSureScenarioExists()
113
+
114
+ reports.saveFinalScenarioData = function( scope, { status=`in progress` }) {
115
+ this.setReportScenarioStatus( scope, { status });
116
+ let scenario = scope.report.get( scope.scenario_id );
117
+ let report = this.getPrintableScenario( scenario );
118
+ if ( scenario.status === `FAILED` ) { this.failures.push( report ); }
119
+ else if ( scenario.status === `PASSED` ) { this.successes.push( report ); }
120
+ // else if ( scenario.status === `WARNING` ) { passed_reports += report; this.warnings.push( report ); }
121
+ // else if ( scenario.status === `UNDEFINED` ) { passed_reports += report; this.undefineds.push( report ); }
122
+ else { this.others.push( report ); }
123
+
124
+ // Save to failures.txt (worry about optimization later)
125
+ if ( this.failures.length > 1 || this.others.length > 1 ) {
126
+ fs.writeFileSync(
127
+ scope.paths.failures,
128
+ this.title + this.getPrintableUnexpected( scope )
129
+ );
130
+ }
131
+ // Save to reports.txt (worry about optimization later)
132
+ fs.writeFileSync( scope.paths.report, this.getPrintableReport( scope ) );
133
+
134
+ return scenario;
135
+ }; // Ends reports.saveFinalScenarioData()
136
+
94
137
 
95
138
  reports.setReportScenarioStatus = function( scope, { status=`PASSED` }) {
96
139
  /** Add final status of scenario to scenario report object - passed, failed, etc. */
97
140
  scope.report.get( scope.scenario_id ).status = status;
141
+ return scope.report.get( scope.scenario_id );
98
142
  }; // Ends reports.setReportScenarioStatus()
99
143
 
100
144
  reports.convertToOriginalStoryTableRow = function( scope, { row_data }) {
@@ -121,34 +165,38 @@ let trimJSON = function( str=`` ) {
121
165
  }; // Ends trimJSON()
122
166
 
123
167
  reports.getPrintableReport = function( scope ) {
124
- /** Return a string generated from test report data. */
125
- let report = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
126
-
127
- // First list failed scenarios, then non-passed scenarios, then passed scenarios
128
- let failed_reports = ``;
129
- let passed_reports = ``;
130
- let other_reports = ``;
131
- for ( let scenario of scope.report ) {
132
-
133
- let report = reports.getPrintableScenario( scenario );
134
- let [ scenario_id, data ] = scenario;
135
- if ( data.status === `FAILED` ) { failed_reports += report; }
136
- else if ( data.status === `PASSED` ) { passed_reports += report; }
137
- else { other_reports += report; }
138
- }; // ends for every scenario
139
-
140
- if ( failed_reports !== `` ) { report += `\n\n===============================\n===============================\n🤕 Failed scenarios:\n${ failed_reports }`; }
141
- if ( other_reports !== `` ) { report += `\n\n===============================\n===============================\n🔎 Scenarios that did not pass:\n${ other_reports }`; }
142
- if ( passed_reports !== `` ) { report += `\n\n===============================\n===============================\n🌈 Passed scenarios:\n${ passed_reports }`; }
168
+ /** Return a string generated from test report data.
169
+ *
170
+ * First list failed scenarios, then non-passed scenarios, then passed scenarios. */
171
+ let report = this.getTitle( scope );
172
+
173
+ if ( this.failures.length > 1 ) { report += this.failures.join( `` ); }
174
+ if ( this.successes.length > 1 ) { report += this.successes.join( `` ); }
175
+ // if ( this.warnings.length > 1 ) {}
176
+ // if ( this.undefineds.length > 1 ) {}
177
+ if ( this.others.length > 1 ) { report += this.others.join( `` ); }
143
178
 
144
179
  return report;
145
180
  }; // Ends reports.getPrintableReport()
146
181
 
182
+ reports.getTitle = function( scope ) { return this.title; }
183
+
184
+ reports.getPrintableUnexpected = function( scope ) {
185
+ // Note about `> 1`: The first line of the report section always exists
186
+ let output = ``;
187
+ if ( this.failures.length > 1 ) {
188
+ output += this.failures.join(``);
189
+ }
190
+ if ( this.others.length > 1 ) {
191
+ output += this.others.join(``);
192
+ }
193
+ return output;
194
+ }; // Ends reports.getPrintableUnexpected()
195
+
147
196
  reports.getPrintableScenario = function( scenario ) {
148
197
  /** Return a string generated from scenario list of report items. */
149
- let [ scenario_id, data ] = scenario;
150
198
  let report = ``;
151
- for ( let line of data.lines ) {
199
+ for ( let line of scenario.lines ) {
152
200
  let { type, code, value } = line;
153
201
  report += `${ getLineText({ type, code, value }) }\n`;
154
202
  }
@@ -120,7 +120,7 @@ session_vars.save_project_name = function ( project_name ) {
120
120
  // Warn local developers if file already exists
121
121
  if ( fs.existsSync( runtime_config_path ) ) {
122
122
  let old_project_name = session_vars.get_project_name();
123
- let session_project_already_exists_msg = `🔎 ALK0035 WARNING: `
123
+ let session_project_already_exists_msg = `🔎 ALK0035 setup WARNING: `
124
124
  + `This session had a project name already: ${ old_project_name }. `
125
125
  + `A new project has now been created called ${ project_name }. `
126
126
  + `Remember to delete ${ old_project_name }`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.11.1",
3
+ "version": "5.11.2-feat-output-2",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -16,7 +16,7 @@ fi
16
16
  # The grepped files will include the list of all the codes that have been
17
17
  # removed and are no longer used. Syntax used works with GitHub cli ([[:digit:]])
18
18
  # https://stackoverflow.com/a/6901221
19
- lines=$(grep -roh --exclude="tests/log_codes/check_codes.sh" --exclude="debug_log.txt" --exclude-dir="node_modules" --exclude-dir='alkiln-*' --exclude-dir='_alkiln*' 'ALK[[:digit:]][[:digit:]][[:digit:]][[:digit:]]' "$directory")
19
+ lines=$(grep -roh --exclude="tests/log_codes/check_codes.sh" --exclude="debug_log.txt" --exclude="cucumber-report.txt" --exclude-dir="node_modules" --exclude-dir='alkiln-*' --exclude-dir='_alkiln*' 'ALK[[:digit:]][[:digit:]][[:digit:]][[:digit:]]' "$directory")
20
20
  sorted=$(echo "$lines" | sort -n)
21
21
 
22
22