@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/.github/workflows/github_server.yml +40 -16
- package/.github/workflows/playground.yml +5 -10
- package/CHANGELOG.md +39 -1
- package/README.md +1 -1
- package/action.yml +75 -2
- package/action_for_github_server/action.yml +5 -3
- package/docs/story_tables.md +1 -1
- package/lib/docassemble/docassemble_api_REST.js +46 -1
- package/lib/docassemble/docassemble_api_interface.js +130 -1
- package/lib/run_cucumber.js +31 -10
- package/lib/scope.js +71 -14
- package/lib/steps.js +137 -26
- package/lib/utils/log.js +3 -1
- package/lib/utils/reports.js +72 -27
- package/package.json +4 -2
- package/setup.py +1 -1
- package/tests/log_codes/check_codes.sh +2 -1
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
|
|
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
|
|
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 = `
|
|
1415
|
-
+ `
|
|
1416
|
-
+ `
|
|
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.
|
|
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, {
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
1315
|
-
+ `
|
|
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 (
|
|
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 =
|
|
95
|
+
log.debug_log_file = `debug_log.txt`;
|
|
96
|
+
log.unexpected_filename = `unexpected_results.txt`;
|