@suffolklitlab/alkiln 5.11.2 → 5.12.0-delete2

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/lib/scope.js CHANGED
@@ -627,7 +627,7 @@ module.exports = {
627
627
  interview_url = `${ base_url }${ file_name }`;
628
628
  }
629
629
 
630
- reports.addToReport(scope,{
630
+ reports.addToReport(scope, {
631
631
  code: `ALK0113`,
632
632
  type: `row info`,
633
633
  value: `Trying to load the interview at "${ interview_url }"`
@@ -638,7 +638,17 @@ module.exports = {
638
638
  // TODO: implement and use scope.handle_possible_timeout()
639
639
  try {
640
640
 
641
- await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout })
641
+ await scope.page.goto(interview_url, { waitUntil: 'domcontentloaded', timeout: scope.timeout });
642
+ // Get interview session values, if possible, so we can delete the session later
643
+ let interview_vars = await scope.page.evaluate(async function() {
644
+ try {
645
+ // won't exist on non-da interview pages
646
+ return await get_interview_variables();
647
+ } catch (error) {
648
+ return null;
649
+ }
650
+ });
651
+ scope.set_interview_session_data( scope, { vars: interview_vars });
642
652
 
643
653
  } catch ( error ) {
644
654
 
@@ -682,6 +692,36 @@ module.exports = {
682
692
  return scope.page;
683
693
  }, // Ends scope.load()
684
694
 
695
+ set_interview_session_data: function ( scope, { vars = null }) {
696
+ /** Sets this interview's session data for this scenario if it doesn't
697
+ * already exist.
698
+ *
699
+ * @returns final_vars { object | null }
700
+ * @returns final_vars.i { str } File path of interview
701
+ * @returns final_vars.uid { str } UID of interview
702
+ * */
703
+ if ( vars && vars.i ) {
704
+ let scenario = scope.scenarios.get( scope.scenario_id );
705
+ if ( !scenario.interviews ) { scenario.interviews = {}; }
706
+ if ( !scenario.interviews[ vars.uid ] ) {
707
+ scenario.interviews[ vars.uid ] = vars;
708
+ log.debug({
709
+ code: `ALK0185`, type: `info`,
710
+ pre: `Saved new interview data. File path: ${ vars.i }. Session id: ${ vars.uid }.`
711
+ });
712
+ } else {
713
+ log.debug({
714
+ code: `ALK0186`, type: `info`,
715
+ pre: `Interview data already existed. File path: ${ vars.i }. Session id: ${ vars.uid }.`
716
+ });
717
+ }
718
+ return vars;
719
+ } else {
720
+ log.debug({ code: `ALK0187`, type: `info`, pre: `Found no interview data.` });
721
+ return null;
722
+ }
723
+ }, // Ends scope.set_interview_session_data()
724
+
685
725
  getLoadData: async function ( scope ) {
686
726
  /* Return elements that could load on the page, like #daMainQuestion
687
727
  * and #da-retry. If there's a system error on the page, return its main text.
@@ -771,7 +811,7 @@ module.exports = {
771
811
  if ( row_raw.length > 3 ) { more_or_less = `less`; }
772
812
  let bad_table_msg = `Your Story Table should have ${ more_or_less } than`
773
813
  + ` ${ row_raw.length } columns. Take another look at Story Table documentation at`
774
- + ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#story-tables.`
814
+ + ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#story-table.`
775
815
  reports.addToReport( scope, { type: `error`, code: `ALK0120`, value: bad_table_msg})
776
816
  throw Error(bad_table_msg);
777
817
  }
@@ -798,7 +838,7 @@ module.exports = {
798
838
  let not_in_env_msg = `ALKiln could not find the GitHub secret "${ row.flags.secret_name }".`
799
839
  + ` Check for a typo and check your workflow file.`
800
840
  + ` Read more about using GitHub secrets in your workflow file at`
801
- + ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#github-secrets.`
841
+ + ` https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/writing#secrets.`
802
842
  reports.addToReport( scope, { type: `error`, code: `ALK0121`, value: not_in_env_msg });
803
843
  throw ReferenceError(not_in_env_msg);
804
844
  }
@@ -1317,6 +1357,7 @@ module.exports = {
1317
1357
  * setting fields, and an object for a DOM field, returns list of the
1318
1358
  * rows that match the DOM field. That list of story table rows may be empty.
1319
1359
  *
1360
+ * Using a `trigger` column is deprecated, but still used in many tests in the wild.
1320
1361
  * For notes on trigger var, see https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger
1321
1362
  *
1322
1363
  * @param var_data {arr} - fields and the values they can be set to
@@ -1411,9 +1452,13 @@ module.exports = {
1411
1452
 
1412
1453
  // field/page trigger name is needed but is missing.
1413
1454
  if ( !page_trigger_name ) {
1414
- let page_trigger_warning = `A field on this page uses an index `
1415
- + `variable or a generic object, but the page is missing `
1416
- + `the ALKiln "trigger" variable's name. See `
1455
+ let page_trigger_warning = `You are using a 3-column Story Table. If possible, you should `
1456
+ + `think about switching to the newer, easier, 2-column Story Table. That will probably `
1457
+ + `get rid of this warning. Read more at `
1458
+ + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/trouble#deprecated-story-tables. `
1459
+ + `If you still want to use this 3-column format, here is more info about the warning: `
1460
+ + `A field on this page uses an index variable or a generic object in a 3-column `
1461
+ + `Story Table, but the page is missing the ALKiln "trigger" variable's name. See `
1417
1462
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#a-missing-trigger-variable.`;
1418
1463
  reports.addToReport(scope, { type: `warning`, code: `ALK0127`, value: page_trigger_warning });
1419
1464
  }
@@ -1430,7 +1475,7 @@ module.exports = {
1430
1475
 
1431
1476
  // First, make the row data more readable to humans.
1432
1477
  let row_as_story_table = reports.convertToOriginalStoryTableRow( scope, { row_data: row_that_matches_this_field });
1433
- let row_trigger_warning = `This Story Table row needs a value in the "trigger" `
1478
+ let row_trigger_warning = `This 3-column Story Table row needs a value in the "trigger" `
1434
1479
  + `column, but it is empty. The test might get unexpected results. See `
1435
1480
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
1436
1481
  + `\nThe rows:\n${ row_as_story_table }`;
@@ -1467,7 +1512,7 @@ module.exports = {
1467
1512
 
1468
1513
  let all_trigger_names_conflict_msg = `${ rows_that_match_field_names.length } `
1469
1514
  + `Story Table row(s) matched a field name, but didn't match a trigger `
1470
- + `variable. The Story Table might be missng a row or a trigger variable. `
1515
+ + `variable. This 3-column Story Table might be missing a row or a trigger variable. `
1471
1516
  + `The test will have to ignore this field. See `
1472
1517
  + `https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/#trigger.`
1473
1518
  + `\nThe row(s) data:\n${ conflicts_as_story_table_rows.join( `\n` )}`
@@ -2893,7 +2938,9 @@ module.exports = {
2893
2938
  await scope.afterStep(scope, {waitForShowIf: true});
2894
2939
  }, // Ends scope.steps.set_al_address()
2895
2940
 
2896
- sign_in: async function ( scope, { email_secret_name, password_secret_name }) {
2941
+ sign_in: async function ( scope, {
2942
+ email_secret_name, password_secret_name, api_key_secret_name
2943
+ }) {
2897
2944
  /** Allow the developer to log a user into their server using GitHub
2898
2945
  * secrets to authenticate.
2899
2946
  */
@@ -2946,6 +2993,16 @@ module.exports = {
2946
2993
  if ( password === undefined ) {
2947
2994
  reports.addToReport( scope, { type: `error`, code: `ALK0162`, value: password_msg })
2948
2995
  }
2996
+ // Save API key to clean up interviews later. Nice to have, not critical
2997
+ if ( api_key_secret_name ) {
2998
+ let api_key = process.env[ api_key_secret_name ];
2999
+ let api_key_msg = `The api_key GitHub SECRET "${ api_key_secret_name }" doesn't exist. Check for a typo and check your workflow file.`
3000
+ if ( api_key === undefined ) {
3001
+ reports.addToReport( scope, { type: `warning`, code: `ALK0199`, value: api_key_msg });
3002
+ } else {
3003
+ scope.scenarios.get( scope.scenario_id ).api_keys.push( api_key );
3004
+ }
3005
+ }
2949
3006
 
2950
3007
  expect( email, email_msg ).to.not.equal( undefined );
2951
3008
  expect( password, password_msg ).to.not.equal( undefined );
@@ -3228,8 +3285,7 @@ module.exports = {
3228
3285
  *
3229
3286
  * @param expected {array} Arr of strs that should appear in the report.
3230
3287
  */
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`
3288
+ let scenario = scope.report.get( scope.scenario_id );
3233
3289
  let report = reports.getPrintableScenario( scenario );
3234
3290
  let all_are_included = true;
3235
3291
  for ( let one_expectation of expected ) {
@@ -3248,8 +3304,7 @@ module.exports = {
3248
3304
  *
3249
3305
  * @param not_expected {array} Arr of strs that should not appear in the report.
3250
3306
  */
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`
3307
+ let scenario = scope.report.get( scope.scenario_id );
3253
3308
  let report = reports.getPrintableScenario( scenario );
3254
3309
  let none_included = true;
3255
3310
  for ( let one_expectation of not_expected ) {
@@ -3262,4 +3317,6 @@ module.exports = {
3262
3317
  return none_included;
3263
3318
  },
3264
3319
 
3320
+ // Test that all interviews have been deleted
3321
+
3265
3322
  };
package/lib/steps.js CHANGED
@@ -22,6 +22,7 @@ const log = require('./utils/log');
22
22
  const session_vars = require('./utils/session_vars');
23
23
  const files = require('./utils/files' );
24
24
  const reports = require(`./utils/reports`);
25
+ const da_api = require('./docassemble/docassemble_api_interface');
25
26
 
26
27
  /* Of Note:
27
28
  - We're using `*=` for selectors because sometimes da text has funny characters in it that are hard to anticipate
@@ -83,6 +84,9 @@ let click_with = {
83
84
 
84
85
  BeforeAll(async () => {
85
86
  scope.showif_timeout = 600;
87
+ // TODO: Do these really need to be maps? As sets, they may make the output
88
+ // more predictable to the user, but they add complexity.
89
+ scope.scenarios = new Map();
86
90
  scope.report = new Map();
87
91
 
88
92
  // Start tracking whether server is up and running
@@ -93,7 +97,7 @@ BeforeAll(async () => {
93
97
 
94
98
  // TODO: figure out how to test file and folder creation stuff,
95
99
  // especially when setup is excluded from the flow.
96
-
100
+
97
101
  // Make the folder to store all the files for this run of the tests
98
102
  scope.paths.artifacts = files.make_artifacts_folder();
99
103
  // Save its name in a file. `session_vars` can get it back later.
@@ -101,8 +105,11 @@ BeforeAll(async () => {
101
105
  session_vars.save_artifacts_path_name( scope.paths.artifacts );
102
106
 
103
107
  scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
108
+ scope.paths.failures = `${ scope.paths.artifacts }/${ log.unexpected_filename }`;
104
109
  scope.paths.debug_log = `${ scope.paths.artifacts }/${ log.debug_log_file }`;
105
110
  fs.writeFileSync( scope.paths.debug_log, "" );
111
+
112
+ reports.create( scope );
106
113
  });
107
114
 
108
115
  Before(async (scenario) => {
@@ -124,13 +131,16 @@ Before(async (scenario) => {
124
131
  }
125
132
  }
126
133
  // Clean up all previously existing pages
127
- for (const page of await scope.browser.pages()) {
134
+ for (const page of await scope.browser.pages()) {
128
135
  await page.close();
129
136
  }
130
137
  // Make a new page
131
138
  scope.page = await scope.browser.newPage()
132
139
 
133
140
  scope.scenario_id = uuidv4();
141
+ scope.scenarios.set( scope.scenario_id, {} );
142
+ scope.scenarios.get( scope.scenario_id ).interviews = {};
143
+ scope.scenarios.get( scope.scenario_id ).api_keys = [ session_vars.get_dev_api_key() ];
134
144
  scope.expected_status = null;
135
145
  scope.expected_in_report = null;
136
146
  scope.expected_not_in_report = null;
@@ -176,7 +186,7 @@ Given(/I start the interview at "([^"]+)"/, {timeout: -1}, async (file_name) =>
176
186
 
177
187
  // Load the right interview page
178
188
  await scope.load( scope, file_name );
179
-
189
+
180
190
  // The page timeout is set in here too in case someone set a custom timeout
181
191
  // just before a page was loaded for the Scenario. Move this to just after page creation?
182
192
  // The function itself won't really timeout here, so we don't need to wrap it in
@@ -271,7 +281,10 @@ Given(/I go to "([^"]+)"/i, {timeout: -1}, async ( url ) => {
271
281
  return result;
272
282
  });
273
283
 
274
- Given(/I (?:sign|log) ?(?:in)?(?:on)?(?:to the server)? with(?: the email)? "([^"]+)" and(?: the password)? "([^"]+)"(?: SECRETs)?/i, {timeout: -1}, async ( email, password ) => {
284
+ Given(
285
+ /I (?:sign|log) ?(?:in)?(?:on)?(?:to the server)? with(?: the email)? "([^"]+)",?(?: and)?(?: the password)? "([^"]+)"(?: SECRETs)?(?:,?(?: and)?(?: the API key)? "([^"]+)")?/i,
286
+ { timeout: -1 },
287
+ async ( email, password, api_key ) => {
275
288
  /** Uses the names of environment variables (most often GitHub SECRETs) to
276
289
  * log into an account on the user's server. Must be SECRETs to stay secure.
277
290
  *
@@ -279,13 +292,16 @@ Given(/I (?:sign|log) ?(?:in)?(?:on)?(?:to the server)? with(?: the email)? "([^
279
292
  * I sign into the server with the email "USER1_EMAIL" and the password "USER1_PASSWORD" SECRETs
280
293
  * I sign on with the email "USER1_EMAIL" and the password "USER1_PASSWORD" SECRETs
281
294
  * I log in with "USER1_EMAIL" and "USER1_PASSWORD"
295
+ * I sign in with the email "USER1_EMAIL", the password "USER1_PASSWORD", and the API key "USER1_API_KEY"
296
+ * I sign in with "USER1_EMAIL", "USER1_PASSWORD", "USER1_API_KEY"
282
297
  */
283
298
  // `page` timeout will deal with custom timeout
284
299
  // Couldn't test the timeout for tapping the button because there's not
285
300
  // enough of a pause between initial navigation and pressing button.
286
301
  await scope.steps.sign_in( scope, {
287
302
  email_secret_name: email,
288
- password_secret_name: password
303
+ password_secret_name: password,
304
+ api_key_secret_name: api_key
289
305
  });
290
306
  });
291
307
 
@@ -310,7 +326,7 @@ Given(/the max(?:imum)? sec(?:ond)?s for each step (?:in this scenario )?is (\d+
310
326
  /* At any time, set a custom timeout for the scenario. */
311
327
  // TODO: Add 1 second to this to allow other steps to not fail, like "I wait n seconds"
312
328
  scope.timeout = parseInt( seconds ) * 1000;
313
-
329
+
314
330
  // Cucumber timeouts are made custom for each step that needs it
315
331
 
316
332
  // If there is no page yet, we've ensured custom timeout will still be set
@@ -650,7 +666,7 @@ Then('I should see the link to {string}', async ( url ) => { // √
650
666
  let msg = `Cannot find a link to "${ url }".`;
651
667
  if ( !link ) { reports.addToReport(scope, { type: `error`, code: `ALK0080`, value: msg }); }
652
668
  expect(link, msg ).to.exist;
653
-
669
+
654
670
  await scope.afterStep(scope);
655
671
  });
656
672
 
@@ -659,7 +675,7 @@ Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) =>
659
675
  let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
660
676
  // TODO: Possibly add a check here for the link text, though note this
661
677
  // Step is not currently supported.
662
-
678
+
663
679
  let prop_obj = await link.getProperty('href');
664
680
  let actual_url = await prop_obj.jsonValue();
665
681
 
@@ -753,7 +769,7 @@ Then(/I expect the baseline PDF "([^"]+)" and the new PDF "([^"]+)" to be the sa
753
769
  * Variations:
754
770
  * Then I expect the baseline PDF "simple-doc-Baseline.pdf" and the new PDF "simple-doc.pdf" to be the same
755
771
  */
756
-
772
+
757
773
  return await wrapPromiseWithTimeout(
758
774
  scope.steps.compare_pdfs(scope, {existing_pdf_path, new_pdf_path}),
759
775
  scope.timeout
@@ -877,7 +893,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
877
893
 
878
894
  // Set up timeout to stop infinite loops
879
895
  let page_timeout_id = setTimeout(async function() {
880
-
896
+
881
897
  // Make sure to only throw once
882
898
  if ( has_thrown ) { return; }
883
899
  else { has_thrown = true; }
@@ -1175,6 +1191,72 @@ Given(/the final Scenario status should be "([^"]+)"/i, async ( expected_status
1175
1191
  scope.expected_status = expected_status.toUpperCase();
1176
1192
  });
1177
1193
 
1194
+ Then(/of two interviews, I delete one __internal__/i, async () => {
1195
+ /** Test that an interview does get successfully deleted. This tests whether
1196
+ * the API code is working correctly.
1197
+ */
1198
+ // Make sure we have two interviews in `scope.scenario.interviews` (two session ids)
1199
+ let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
1200
+ let interview_keys = Object.keys( interviews );
1201
+ expect(
1202
+ interview_keys.length,
1203
+ `Of 2 interview sessions, ALKiln only saved ${ interview_keys.length }`
1204
+ ).to.equal( 2 );
1205
+
1206
+ // Check that both interviews exist on the server. 1 √ 2 √
1207
+ let api_keys = scope.scenarios.get( scope.scenario_id ).api_keys;
1208
+ let existing_interviews = await da_api.get_interviews({ interviews, api_keys });
1209
+ let existing_keys = Object.keys( existing_interviews );
1210
+ expect(
1211
+ existing_keys.length,
1212
+ `2 interviews should be on the server. Instead: "${ existing_keys.join(', ') }"`
1213
+ ).to.equal( 2 );
1214
+
1215
+ // Delete one. 1 x 2 √
1216
+ let to_delete = interviews[ interview_keys[ 0 ]];
1217
+ await da_api.quietly_try_to_delete_interviews({
1218
+ interviews: { [interview_keys[ 0 ]]: to_delete },
1219
+ api_keys
1220
+ });
1221
+
1222
+ // Check that only one session exists on the server. This tests that
1223
+ // 1. An interview has been deleted and
1224
+ // 2. We are fetching interviews correctly because we are getting one
1225
+ // back (e.g. not passing null into the api function)
1226
+ // 1 x 2 √
1227
+ let after_deletion = await da_api.get_interviews({ interviews, api_keys });
1228
+ let keys_after_deletion = Object.keys( after_deletion );
1229
+ expect(
1230
+ keys_after_deletion.length,
1231
+ `1 interviews should be on the server: ${ keys_after_deletion.join(', ') }`
1232
+ ).to.equal( 1 )
1233
+ // In After(), the other session should get deleted
1234
+ });
1235
+
1236
+ Then(/as an anonymous user, I fail to delete an interview and get no error __internal__/i, async () => {
1237
+ /** Test that an interview passes even when unable to delete an interview.
1238
+ * An API test
1239
+ */
1240
+ // Make sure we started an interview
1241
+ let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
1242
+ expect(
1243
+ Object.keys( interviews ).length,
1244
+ `The test should have saved one interview session`
1245
+ ).to.equal( 1 );
1246
+
1247
+ // There should be no error about an unauthorized user
1248
+ let api_keys = scope.scenarios.get( scope.scenario_id ).api_keys;
1249
+ let existing_interviews = await da_api.get_interviews({ interviews, api_keys });
1250
+ let existing_keys = Object.keys( existing_interviews );
1251
+ expect(
1252
+ existing_keys.length,
1253
+ `Anonymous user should detect 0 interviews on the server. Instead: "${ existing_keys.join(', ') }"`
1254
+ ).to.equal( 0 );
1255
+
1256
+ // Deleting with an anonymous user should fail silently
1257
+ await da_api.quietly_try_to_delete_interviews({ interviews, api_keys });
1258
+ });
1259
+
1178
1260
 
1179
1261
  //#####################################
1180
1262
  //#####################################
@@ -1202,17 +1284,17 @@ After(async function(scenario) {
1202
1284
  }
1203
1285
 
1204
1286
  if ( scope.failed_pdf_compares.length > 0) {
1205
- let msg = scope.failed_pdf_compares.reduce((str, new_msg) => `${ str }\n---\n${ new_msg }`)
1287
+ let msg = scope.failed_pdf_compares.reduce((str, new_msg) => `${ str }\n―――\n${ new_msg }`)
1206
1288
  changeable_test_status = `FAILED`;
1207
1289
  reports.addToReport(scope, {
1208
1290
  type: `error`,
1209
1291
  code: `ALK0093`,
1210
- value: `PDF comparison failed ${ scope.failed_pdf_compares.length } time(s)\n---\n${ msg }\n---\n`
1292
+ value: `PDF comparison failed ${ scope.failed_pdf_compares.length } time(s)\n―――\n${ msg }\n―――\n`
1211
1293
  });
1212
1294
  }
1213
1295
 
1214
1296
  // Record status before internal report tests can interfere with values
1215
- reports.setReportScenarioStatus( scope, { status: changeable_test_status });
1297
+ reports.saveFinalScenarioData( scope, { status: changeable_test_status });
1216
1298
 
1217
1299
  if (changeable_test_status !== `PASSED`) {
1218
1300
  log.debug({
@@ -1256,11 +1338,11 @@ After(async function(scenario) {
1256
1338
  } // ends if not passed
1257
1339
 
1258
1340
  // 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`
1341
+ let scenario_report_obj = scope.report.get( scope.scenario_id );
1261
1342
  let report = reports.getPrintableScenario( scenario_report_obj );
1262
1343
  // Save the report as in the Scenario folder
1263
1344
  fs.writeFileSync( scope.paths.scenario_report, report );
1345
+ // TODO: Save any cucumber failure message for the Scenario here (instead of waiting till run_cucumber.js)
1264
1346
 
1265
1347
  // ---------------- Check internal test results ----------------
1266
1348
 
@@ -1273,7 +1355,7 @@ After(async function(scenario) {
1273
1355
  // Reset report values no matter what so they don't mess up future scenarios
1274
1356
  scope.expected_in_report = null;
1275
1357
  }
1276
-
1358
+
1277
1359
  if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
1278
1360
  let all_prohibited_strings_were_absent = await scope.reportDoesNotInclude(
1279
1361
  scope,
@@ -1311,8 +1393,8 @@ After(async function(scenario) {
1311
1393
  // it as failed too.
1312
1394
  expected_status_was_met = false;
1313
1395
  // Keep it general. Users may see this message, e.g. for a11y tests.
1314
- let msg = `Test failed. Expected status: ${scope.expected_status}. `
1315
- + `ALKiln's assessed status: ${ changeable_test_status }. `
1396
+ let msg = `Test ${ changeable_test_status }. `
1397
+ + `Expected status: ${scope.expected_status}. `
1316
1398
  + `See the report for details.`;
1317
1399
  let code = `ALK0099`;
1318
1400
  log.debug({ type: `error`, code: code, pre: msg, })
@@ -1354,7 +1436,7 @@ After(async function(scenario) {
1354
1436
  // Catch a possible reload error.
1355
1437
  try {
1356
1438
  // puppeteer will ensure proper timeout.
1357
- await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
1439
+ await scope.page.goto(`${ session_vars.get_da_server_url() }/user/sign-out`, {waitUntil: `domcontentloaded`});
1358
1440
 
1359
1441
  } catch ( error ) {
1360
1442
  signout_succeeded = false;
@@ -1369,7 +1451,7 @@ After(async function(scenario) {
1369
1451
  code: code,
1370
1452
  message: non_reload_report_msg,
1371
1453
  }, error });
1372
- } catch {
1454
+ } catch ( error ) {
1373
1455
  signout_error_msg = `🤕 ${ code } ERROR: ${ non_reload_report_msg }`;
1374
1456
  }
1375
1457
  } else {
@@ -1384,10 +1466,42 @@ After(async function(scenario) {
1384
1466
 
1385
1467
  } // ends if scope.page
1386
1468
 
1469
+ // Now that we're no longer on the interview page, one way or
1470
+ // another, try to delete the interviews created during the test
1471
+ // Authors running this manually get to choose what to do with
1472
+ // the interviews they're creating unless we hear otherwise.
1473
+ if ( session_vars.get_origin() !== `playground` ) {
1474
+ try {
1475
+ let interviews = scope.scenarios.get( scope.scenario_id ).interviews;
1476
+ let api_keys = scope.scenarios.get( scope.scenario_id ).api_keys;
1477
+ await da_api.quietly_try_to_delete_interviews({ interviews, api_keys });
1478
+ let existing_interviews = await da_api.get_interviews({ interviews, api_keys });
1479
+ let existing_keys = Object.keys( existing_interviews );
1480
+ // Deleting interviews is nice, but not non-critical. Don't bother the
1481
+ // user with it. Don't want to cover up a more meaningful failure (like
1482
+ // going to the wrong file). If it becomes a problem we can track it down later.
1483
+ if ( existing_keys.length > 0 ) {
1484
+ log.debug({ type: `internal warning`, code: `ALK0197`,
1485
+ pre: `${ existing_keys.length } interviews were not deleted: ${ existing_keys.join( ', ' ) }`
1486
+ });
1487
+ }
1488
+ } catch ( error ) {
1489
+ // Deleting interviews is nice, but not non-critical. Don't bother the
1490
+ // user with it. Don't want to cover up a more meaningful failure (like
1491
+ // going to the wrong file). Example of OK failure: the user owns the
1492
+ // interview is different than the API key that's trying to delete the
1493
+ // interview. If it becomes a problem we can track it down later.
1494
+ // Consider add "internal" to docs: "If you see this, make an
1495
+ // issue in ALKiln with your debug/verbose log"
1496
+ log.debug({ type: `internal warning`, code: `ALK0198`, pre: `Error while cleaning up interviews.`, data: error });
1497
+ }
1498
+ }
1499
+
1500
+ // Handle all the accumulated errors
1387
1501
  let errors = []
1388
1502
  if ( !expected_status_was_met ) { errors.push( expected_status_err_msg ); }
1389
1503
  if ( !signout_succeeded ) { errors.push( signout_error_msg ); }
1390
- if ( !expected_status_was_met || !signout_succeeded ) {
1504
+ if ( errors.length > 0 ) {
1391
1505
  throw new Error( `${errors.join( errors, '\n')}` );
1392
1506
  }
1393
1507
 
@@ -1405,17 +1519,14 @@ After(async function(scenario) {
1405
1519
  AfterAll(async function() {
1406
1520
  // Stop collecting server response statuses
1407
1521
  scope.stop_tracking_server_status = true;
1408
-
1522
+
1409
1523
  let report = reports.getPrintableReport( scope );
1410
1524
  // Log the report. Can't get this.attach() or this.log() to work in `After()` or here.
1411
1525
  // May need newest version of cucumberjs
1412
1526
  console.log( `\n\n${ report }` );
1413
1527
 
1414
- // Save/download the report as an artifact
1415
- fs.writeFileSync( scope.paths.report, report );
1416
-
1417
1528
  // If there is a browser open, then close it
1418
1529
  if (scope.browser) {
1419
1530
  await scope.browser.close();
1420
1531
  }
1421
- });
1532
+ });
package/lib/utils/log.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const session_vars = require( './session_vars' );
3
3
 
4
4
  // ? 🌈 💡 🔎 🤕 🐞
5
+ // Alternatives: 🩹 ❤️‍🩹 🚑
5
6
  // TODO: return full message so it can be passed to manually
6
7
  // thrown errors if needed.
7
8
 
@@ -91,4 +92,5 @@ log.add_to_debug_log = function(value) {
91
92
  fs.appendFileSync(log.debug_log_file, value);
92
93
  };
93
94
 
94
- log.debug_log_file = 'debug_log.txt'
95
+ log.debug_log_file = `debug_log.txt`;
96
+ log.unexpected_filename = `unexpected_results.txt`;