@suffolklitlab/alkiln 5.2.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/lib/scope.js +141 -220
- package/lib/steps.js +89 -49
- package/lib/utils/pdf_reader.js +113 -0
- package/lib/utils/reports.js +147 -0
- package/npm-shrinkwrap.json +757 -8
- package/package.json +4 -2
package/lib/steps.js
CHANGED
|
@@ -21,6 +21,7 @@ const scope = require('./scope');
|
|
|
21
21
|
const log = require('./utils/log');
|
|
22
22
|
const session_vars = require('./utils/session_vars');
|
|
23
23
|
const files = require('./utils/files' );
|
|
24
|
+
const reports = require(`./utils/reports`);
|
|
24
25
|
|
|
25
26
|
/* Of Note:
|
|
26
27
|
- We're using `*=` for selectors because sometimes da text has funny characters in it that are hard to anticipate
|
|
@@ -125,7 +126,7 @@ Before(async (scenario) => {
|
|
|
125
126
|
scope.expected_not_in_report = null;
|
|
126
127
|
scope.server_reload_promise = null;
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
reports.addReportHeading(scope, {scenario});
|
|
129
130
|
// Make folder for this Scenario in the all-tests artifacts folder
|
|
130
131
|
scope.base_filename = await scope.getSafeScenarioBaseFilename(scope, {scenario});
|
|
131
132
|
// Add a date for uniqueness in case dev has accidentally copied a Scenario description
|
|
@@ -146,6 +147,7 @@ Before(async (scenario) => {
|
|
|
146
147
|
scope.page_id = null;
|
|
147
148
|
scope.check_all_for_a11y = false;
|
|
148
149
|
scope.passed_all_for_a11y = true;
|
|
150
|
+
scope.failed_pdf_compares = [];
|
|
149
151
|
|
|
150
152
|
// Reset default timeout
|
|
151
153
|
scope.timeout = default_timeout;
|
|
@@ -353,7 +355,7 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
353
355
|
resolve();
|
|
354
356
|
} catch ( err ) {
|
|
355
357
|
let err_msg = `Error occurred when tried to answer fields on question "${ id }".`
|
|
356
|
-
|
|
358
|
+
reports.addToReport( scope, { type: `error`, value: err_msg });
|
|
357
359
|
// Throw error instead?
|
|
358
360
|
reject( err );
|
|
359
361
|
} finally {
|
|
@@ -372,7 +374,7 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
372
374
|
|
|
373
375
|
// Add report strings from supported table, not an altered one
|
|
374
376
|
for ( let row of supported_table ) {
|
|
375
|
-
row.report_str = `${
|
|
377
|
+
row.report_str = `${ reports.convertToOriginalStoryTableRow(scope, { row_data: row }) }`;
|
|
376
378
|
}
|
|
377
379
|
// Sort into alphabetical order, as is recommended for writing story tables
|
|
378
380
|
let sorted_rows = supported_table.sort( function ( row1, row2 ) {
|
|
@@ -383,13 +385,13 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
383
385
|
|
|
384
386
|
// Add full used table to report so it can be copied if needed.
|
|
385
387
|
// TODO: Should we/How do we get this in the final scenario report instead of here?
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
388
|
+
reports.addToReport( scope, { type: `note`, value: `\n${ ' '.repeat(2) }Rows that got set:` });
|
|
389
|
+
reports.addToReport( scope, { type: `step`, value: `${ ' '.repeat(4) }And I get to the question id "${ target_id }" with this data:` });
|
|
390
|
+
reports.addToReport(scope, { type: `row`, value: `${ ' '.repeat(6) }| var | value | trigger |`, });
|
|
389
391
|
let at_least_one_row_was_NOT_used = false;
|
|
390
392
|
for ( let row of sorted_rows ) {
|
|
391
393
|
if ( row.times_used > 0 ) {
|
|
392
|
-
|
|
394
|
+
reports.addToReport(scope, { type: `row`, value: row.report_str, });
|
|
393
395
|
} else {
|
|
394
396
|
at_least_one_row_was_NOT_used = true;
|
|
395
397
|
}
|
|
@@ -397,17 +399,17 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
|
|
|
397
399
|
|
|
398
400
|
// Add unused rows if needed
|
|
399
401
|
if ( at_least_one_row_was_NOT_used ) {
|
|
400
|
-
|
|
402
|
+
reports.addToReport( scope, { type: `note`, value: `${ ' '.repeat(2) }Unused rows:` });
|
|
401
403
|
for ( let row of sorted_rows ) {
|
|
402
404
|
if ( row.times_used === 0 ) {
|
|
403
|
-
|
|
405
|
+
reports.addToReport(scope, { type: `row`, value: row.report_str, });
|
|
404
406
|
}
|
|
405
407
|
}
|
|
406
408
|
} else {
|
|
407
|
-
|
|
409
|
+
reports.addToReport(scope, { type: `note`, value: `${ ' '.repeat(2) }All rows were used` });
|
|
408
410
|
}
|
|
409
411
|
|
|
410
|
-
|
|
412
|
+
reports.addToReport(scope, { type: `note`, value: `` });
|
|
411
413
|
});
|
|
412
414
|
|
|
413
415
|
//#####################################
|
|
@@ -457,7 +459,7 @@ Then(/the (?:question|page|screen) id should be "([^"]+)"/i, { timeout: -1 }, as
|
|
|
457
459
|
// No question id exists
|
|
458
460
|
let no_id_msg = `Did not find any question id.`;
|
|
459
461
|
if ( !question_has_id ) {
|
|
460
|
-
|
|
462
|
+
reports.addToReport( scope, { type: `error`, value: no_id_msg });
|
|
461
463
|
}
|
|
462
464
|
expect( question_has_id , no_id_msg ).to.be.true;
|
|
463
465
|
|
|
@@ -465,7 +467,7 @@ Then(/the (?:question|page|screen) id should be "([^"]+)"/i, { timeout: -1 }, as
|
|
|
465
467
|
let no_match_msg = `The question id was supposed to be "${ question_id }"`
|
|
466
468
|
+ `, but it's actually "${ id }".`;
|
|
467
469
|
if ( !id_matches ) {
|
|
468
|
-
|
|
470
|
+
reports.addToReport( scope, { type: `error`, value: no_match_msg });
|
|
469
471
|
}
|
|
470
472
|
expect( id_matches, no_match_msg ).to.be.true;
|
|
471
473
|
|
|
@@ -478,7 +480,7 @@ Then(/I ?(?:should)? see the phrase "([^"]+)"/i, async ( phrase ) => { // √
|
|
|
478
480
|
const bodyText = await scope.page.$eval('body', elem => elem.innerText);
|
|
479
481
|
|
|
480
482
|
let msg = `The text "${ phrase }" SHOULD be on this page, but it's NOT.`;
|
|
481
|
-
if ( !bodyText.includes( phrase )) {
|
|
483
|
+
if ( !bodyText.includes( phrase )) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
482
484
|
expect( bodyText, msg ).to.contain( phrase );
|
|
483
485
|
|
|
484
486
|
await scope.afterStep(scope);
|
|
@@ -492,7 +494,7 @@ Then(/I should not see the phrase "([^"]+)"/i, async ( phrase ) => { // √
|
|
|
492
494
|
const bodyText = await scope.page.$eval('body', elem => elem.innerText);
|
|
493
495
|
|
|
494
496
|
let msg = `The text "${ phrase }" should NOT be on this page, but it IS here.`;
|
|
495
|
-
if ( bodyText.includes( phrase )) {
|
|
497
|
+
if ( bodyText.includes( phrase )) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
496
498
|
expect( bodyText, msg ).not.to.contain( phrase );
|
|
497
499
|
|
|
498
500
|
await scope.afterStep(scope);
|
|
@@ -502,7 +504,7 @@ Then('an element should have the id {string}', async ( id ) => { // √
|
|
|
502
504
|
const element = await scope.page.$('#' + id);
|
|
503
505
|
|
|
504
506
|
let msg = `No element on this page has the ID "${ id }".`;
|
|
505
|
-
if ( !element ) {
|
|
507
|
+
if ( !element ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
506
508
|
expect( element, msg ).to.exist;
|
|
507
509
|
|
|
508
510
|
await scope.afterStep(scope);
|
|
@@ -557,7 +559,7 @@ Then(/I (don't|can't|don’t|can’t|cannot|do not) continue/, async (unused) =>
|
|
|
557
559
|
* Can other things trigger navigation? Re: other inputs things
|
|
558
560
|
* like 'Enter' acting like a click is a test for docassemble */
|
|
559
561
|
let msg = `The page should have stopped the user from continuing, but the user was able to continue.`;
|
|
560
|
-
if ( scope.navigated ) {
|
|
562
|
+
if ( scope.navigated ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
561
563
|
expect( scope.navigated, msg ).to.be.false;
|
|
562
564
|
|
|
563
565
|
await scope.afterStep(scope);
|
|
@@ -568,12 +570,12 @@ Then('I will be told an answer is invalid', async () => { // √
|
|
|
568
570
|
let { was_found, error_handlers } = await scope.checkForError( scope );
|
|
569
571
|
|
|
570
572
|
let no_error_msg = `No error message was found on the page`;
|
|
571
|
-
if ( !was_found ) {
|
|
573
|
+
if ( !was_found ) { reports.addToReport(scope, { type: `error`, value: no_error_msg }); }
|
|
572
574
|
expect( was_found, no_error_msg ).to.be.true;
|
|
573
575
|
|
|
574
576
|
let was_user_error = error_handlers.complex_user_error !== undefined || error_handlers.simple_user_error !== undefined;
|
|
575
577
|
let not_user_error_msg = `The error was a system error, not an invalid user answer. Check for the picture of the page's error if the step did not use any secret variables.`;
|
|
576
|
-
if ( !was_user_error ) {
|
|
578
|
+
if ( !was_user_error ) { reports.addToReport(scope, { type: `error`, value: not_user_error_msg }); }
|
|
577
579
|
expect( was_user_error, not_user_error_msg ).to.be.true;
|
|
578
580
|
|
|
579
581
|
await scope.afterStep(scope);
|
|
@@ -584,7 +586,7 @@ Then('I arrive at the next page', async () => { // √
|
|
|
584
586
|
* Can other things trigger navigation? Re: other inputs things
|
|
585
587
|
* like 'Enter' acting like a click is a test for docassemble */
|
|
586
588
|
let msg = `User did not arrive at the next page.`
|
|
587
|
-
if ( !scope.navigated ) {
|
|
589
|
+
if ( !scope.navigated ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
588
590
|
expect( scope.navigated, msg ).to.be.true;
|
|
589
591
|
await scope.afterStep(scope);
|
|
590
592
|
});
|
|
@@ -595,7 +597,7 @@ Then('I should see the link {string}', async ( linkText ) => { // √
|
|
|
595
597
|
let [link] = await scope.page.$x(`//a[contains(text(), "${linkText}")]`);
|
|
596
598
|
|
|
597
599
|
let msg = `Cannot find a link with the text "${ linkText }".`;
|
|
598
|
-
if ( !link ) {
|
|
600
|
+
if ( !link ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
599
601
|
expect( link, msg ).to.exist;
|
|
600
602
|
|
|
601
603
|
await scope.afterStep(scope);
|
|
@@ -606,7 +608,7 @@ Then('I should see the link to {string}', async ( url ) => { // √
|
|
|
606
608
|
let link = await scope.page.$(`*[href="${ url }"]`);
|
|
607
609
|
|
|
608
610
|
let msg = `Cannot find a link to "${ url }".`;
|
|
609
|
-
if ( !link ) {
|
|
611
|
+
if ( !link ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
610
612
|
expect(link, msg ).to.exist;
|
|
611
613
|
|
|
612
614
|
await scope.afterStep(scope);
|
|
@@ -622,7 +624,7 @@ Then(/the "([^"]+)" link leads to "([^"]+)"/, async (linkText, expected_url) =>
|
|
|
622
624
|
let actual_url = await prop_obj.jsonValue();
|
|
623
625
|
|
|
624
626
|
let msg = `Cannot find a link with the text "${ linkText }" leading to ${ expected_url }.`;
|
|
625
|
-
if ( actual_url !== expected_url ) {
|
|
627
|
+
if ( actual_url !== expected_url ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
626
628
|
expect( actual_url ).to.equal( expected_url );
|
|
627
629
|
|
|
628
630
|
await scope.afterStep(scope);
|
|
@@ -641,7 +643,7 @@ Then(/the "([^"]+)" link opens in (a new window|the same window)/, async (linkTe
|
|
|
641
643
|
|| ( !should_open_a_new_window && !opens_a_new_window );
|
|
642
644
|
|
|
643
645
|
let msg = `The link "${ linkText }" does NOT open in ${ which_window }.`;
|
|
644
|
-
if ( !hasCorrectWindowTarget ) {
|
|
646
|
+
if ( !hasCorrectWindowTarget ) { reports.addToReport(scope, { type: `error`, value: msg }); }
|
|
645
647
|
expect( hasCorrectWindowTarget, msg ).to.be.true;
|
|
646
648
|
|
|
647
649
|
await scope.afterStep(scope);
|
|
@@ -674,7 +676,7 @@ Then(/(?:the )?text(?: in)?(?: the)?(?: JSON)?(?: var)?(?:iable)?(?: )?"([^"]+)"
|
|
|
674
676
|
|
|
675
677
|
// Add warning to report if value is not as expected
|
|
676
678
|
if ( actual_value !== expected_value ) {
|
|
677
|
-
|
|
679
|
+
reports.addToReport( scope, {
|
|
678
680
|
type: `error`,
|
|
679
681
|
value: `The variable "${ var_name }" was not equal to the expected `
|
|
680
682
|
+ `value on the "${ scope.page_id }" screen. Check `
|
|
@@ -704,6 +706,19 @@ Then(/I get the(?: page's)?(?: JSON)? var(?:iable)?s and val(?:ue)?s/i, { timeou
|
|
|
704
706
|
);
|
|
705
707
|
});
|
|
706
708
|
|
|
709
|
+
Then(/I expect the baseline PDF "([^"]+)" and the new PDF "([^"]+)" to be the same/i, async (existing_pdf_path, new_pdf_path) => {
|
|
710
|
+
/** Compare the text or fields of a baseline PDF and the newly downloaded PDF.
|
|
711
|
+
*
|
|
712
|
+
* Variations:
|
|
713
|
+
* Then I expect the baseline PDF "simple-doc-Baseline.pdf" and the new PDF "simple-doc.pdf" to be the same
|
|
714
|
+
*/
|
|
715
|
+
|
|
716
|
+
return await wrapPromiseWithTimeout(
|
|
717
|
+
scope.steps.compare_pdfs(scope, {existing_pdf_path, new_pdf_path}),
|
|
718
|
+
scope.timeout
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
|
|
707
722
|
//#####################################
|
|
708
723
|
//#####################################
|
|
709
724
|
// Actions
|
|
@@ -781,7 +796,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
781
796
|
let random_input_path = `${ scope.paths.scenario }/random${ timestamp }`
|
|
782
797
|
scope.paths.random_screenshots = random_input_path;
|
|
783
798
|
fs.mkdirSync( scope.paths.random_screenshots );
|
|
784
|
-
|
|
799
|
+
reports.addToReport( scope, {
|
|
785
800
|
type: 'row',
|
|
786
801
|
value: `Testing interview with random input (in ${ random_input_path })`
|
|
787
802
|
});
|
|
@@ -816,7 +831,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
816
831
|
let has_content = await scope.has_interview_content( scope );
|
|
817
832
|
|
|
818
833
|
if ( !has_content ) {
|
|
819
|
-
|
|
834
|
+
reports.addToReport( scope, {
|
|
820
835
|
type: `error`,
|
|
821
836
|
value: no_content_err
|
|
822
837
|
});
|
|
@@ -825,7 +840,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
825
840
|
} else {
|
|
826
841
|
let timeout_message = `The page with an id of "${ id }" took too long`
|
|
827
842
|
+ ` to fill with random answers (over ${ scope.timeout/1000/60 } min).`
|
|
828
|
-
|
|
843
|
+
reports.addToReport( scope, { type: `error`, value: timeout_message });
|
|
829
844
|
reject( timeout_message );
|
|
830
845
|
}
|
|
831
846
|
|
|
@@ -866,7 +881,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
866
881
|
// Make sure to only throw once
|
|
867
882
|
if ( !has_thrown ) {
|
|
868
883
|
has_thrown = true;
|
|
869
|
-
|
|
884
|
+
reports.addToReport( scope, {
|
|
870
885
|
type: `error`,
|
|
871
886
|
value: no_content_err
|
|
872
887
|
});
|
|
@@ -890,7 +905,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
890
905
|
// Throw a specific error if there's no interview content
|
|
891
906
|
let has_content = await scope.has_interview_content( scope );
|
|
892
907
|
if ( !has_content ) {
|
|
893
|
-
|
|
908
|
+
reports.addToReport( scope, {
|
|
894
909
|
type: `error`,
|
|
895
910
|
value: no_content_err
|
|
896
911
|
});
|
|
@@ -900,7 +915,7 @@ When(/I answer randomly for at most ([0-9]+) (?:page(?:s)?|screen(?:s)?)/, { tim
|
|
|
900
915
|
} else {
|
|
901
916
|
let err_msg = `The page with the question id "${ id }" errored when trying to fill`
|
|
902
917
|
+ ` it with random answers: ${ error.name }`
|
|
903
|
-
|
|
918
|
+
reports.addToReport( scope, { type: `error`, value: err_msg });
|
|
904
919
|
reject( error );
|
|
905
920
|
} // ends if !has_content
|
|
906
921
|
|
|
@@ -1098,24 +1113,38 @@ Given(/the final Scenario status should be "(passed|failed)"/i, async ( expected
|
|
|
1098
1113
|
|
|
1099
1114
|
After(async function(scenario) {
|
|
1100
1115
|
|
|
1116
|
+
let changeable_test_status = scenario.result.status;
|
|
1101
1117
|
// The accessibility failures don't happen til the end, so the rest of the test can continue
|
|
1102
1118
|
if ( scope.check_all_for_a11y && !scope.passed_all_for_a11y) {
|
|
1103
1119
|
// The fact that the scenario failed has already been noted earlier in the report
|
|
1104
|
-
|
|
1120
|
+
changeable_test_status = `FAILED`;
|
|
1121
|
+
reports.addToReport(scope, {
|
|
1122
|
+
type: `error`,
|
|
1123
|
+
value: `Accessibility standards weren't met. See information above.`
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
if ( scope.failed_pdf_compares.length > 0) {
|
|
1128
|
+
let msg = scope.failed_pdf_compares.reduce((str, new_msg) => `${ str }\n---\n${ new_msg }`)
|
|
1129
|
+
changeable_test_status = `FAILED`;
|
|
1130
|
+
reports.addToReport(scope, {
|
|
1131
|
+
type: `error`,
|
|
1132
|
+
value: `PDF comparison failed ${ scope.failed_pdf_compares.length } time(s)\n---\n${ msg }\n---\n`
|
|
1133
|
+
});
|
|
1105
1134
|
}
|
|
1106
1135
|
|
|
1107
1136
|
// Record status before internal report tests can interfere with values
|
|
1108
|
-
|
|
1137
|
+
reports.setReportScenarioStatus( scope, { status: changeable_test_status });
|
|
1109
1138
|
|
|
1110
|
-
if (
|
|
1139
|
+
if (changeable_test_status !== `PASSED`) {
|
|
1111
1140
|
if ( session_vars.get_debug() ) {
|
|
1112
|
-
console.log( `Non-passed status occurred while running test:`,
|
|
1141
|
+
console.log( `Non-passed status occurred while running test:`, changeable_test_status );
|
|
1113
1142
|
}
|
|
1114
1143
|
|
|
1115
1144
|
if ( scope.page ) {
|
|
1116
1145
|
|
|
1117
1146
|
if ( !!scope.disable_error_screenshot ) {
|
|
1118
|
-
|
|
1147
|
+
reports.addToReport(scope, {
|
|
1119
1148
|
type: `row`,
|
|
1120
1149
|
value: `For security, ALKiln will avoid creating a picture of the page for this error. It's possible a secret is being used on this screen.`
|
|
1121
1150
|
});
|
|
@@ -1139,21 +1168,19 @@ After(async function(scenario) {
|
|
|
1139
1168
|
} // ends if scope.page exists
|
|
1140
1169
|
|
|
1141
1170
|
// This has to come after the security message or security message doesn't print. Not sure why.
|
|
1142
|
-
if (
|
|
1143
|
-
|
|
1171
|
+
if (changeable_test_status === `FAILED`) {
|
|
1172
|
+
reports.addToReport(scope, { type: `outcome`, value: `**-- Scenario Failed --**` });
|
|
1144
1173
|
}
|
|
1145
1174
|
|
|
1146
1175
|
} // ends if not passed
|
|
1147
1176
|
|
|
1148
|
-
let original_scenario_status = scenario.result.status;
|
|
1149
|
-
|
|
1150
1177
|
// Internal tests of this framework
|
|
1151
1178
|
if ( scope.expected_status ) {
|
|
1152
1179
|
let expected = scope.expected_status;
|
|
1153
1180
|
scope.expected_status = null; // reset for next Scenario
|
|
1154
|
-
expect(
|
|
1181
|
+
expect( changeable_test_status ).to.equal( expected );
|
|
1155
1182
|
// If the status was as expected, pass this criteria
|
|
1156
|
-
|
|
1183
|
+
changeable_test_status = `PASSED`;
|
|
1157
1184
|
}
|
|
1158
1185
|
|
|
1159
1186
|
if ( scope.expected_in_report && scope.expected_in_report.length > 0) {
|
|
@@ -1162,8 +1189,8 @@ After(async function(scenario) {
|
|
|
1162
1189
|
scope.expected_in_report = null;
|
|
1163
1190
|
let all_were_included = await scope.reportIncludesAllExpected( scope, { expected });
|
|
1164
1191
|
|
|
1165
|
-
if ( all_were_included ) {
|
|
1166
|
-
else {
|
|
1192
|
+
if ( all_were_included ) { changeable_test_status = `PASSED`; }
|
|
1193
|
+
else { changeable_test_status = `FAILED`; }
|
|
1167
1194
|
}
|
|
1168
1195
|
|
|
1169
1196
|
if (scope.expected_not_in_report && scope.expected_not_in_report.length > 0) {
|
|
@@ -1173,9 +1200,9 @@ After(async function(scenario) {
|
|
|
1173
1200
|
let all_were_not_there = await scope.reportDoesNotInclude( scope, { not_expected });
|
|
1174
1201
|
|
|
1175
1202
|
if ( all_were_not_there ) {
|
|
1176
|
-
|
|
1203
|
+
changeable_test_status = `PASSED`;
|
|
1177
1204
|
} else {
|
|
1178
|
-
|
|
1205
|
+
changeable_test_status = `FAILED`;
|
|
1179
1206
|
}
|
|
1180
1207
|
|
|
1181
1208
|
}
|
|
@@ -1199,7 +1226,7 @@ After(async function(scenario) {
|
|
|
1199
1226
|
} else {
|
|
1200
1227
|
// Throw any non-timeout error
|
|
1201
1228
|
let err_msg = `Error occurred when ALKiln tried to sign out in preparation for the next test.`
|
|
1202
|
-
|
|
1229
|
+
reports.addToReport( scope, { type: `error`, value: err_msg });
|
|
1203
1230
|
throw error;
|
|
1204
1231
|
} // ends if error is timeout error
|
|
1205
1232
|
|
|
@@ -1210,16 +1237,29 @@ After(async function(scenario) {
|
|
|
1210
1237
|
// Add report text to Scenario folder after everything has been added to the report
|
|
1211
1238
|
let current_report_obj = scope.report.get( scope.scenario_id );
|
|
1212
1239
|
let scenario_report_obj = [ scope.scenario_id, current_report_obj ]; // Mimic how loop works with `Map`
|
|
1213
|
-
let report =
|
|
1240
|
+
let report = reports.getPrintableScenario( scenario_report_obj );
|
|
1214
1241
|
// Save the report as in the Scenario folder
|
|
1215
1242
|
fs.writeFileSync( scope.paths.scenario_report, report );
|
|
1243
|
+
|
|
1244
|
+
if ( changeable_test_status === `FAILED` ) {
|
|
1245
|
+
// If the result object status wasn't failure and we still want
|
|
1246
|
+
// the tests to fail, as with a11y tests, we can't mess with the
|
|
1247
|
+
// cucumber result.status object because it will cause unwanted
|
|
1248
|
+
// errors. We have to throw an error here instead.
|
|
1249
|
+
if ( scenario.result.status !== `FAILED` ) {
|
|
1250
|
+
throw new Error(`The test failed. See the report for more information.`);
|
|
1251
|
+
}
|
|
1252
|
+
} else {
|
|
1253
|
+
// Other types of errors should be ok
|
|
1254
|
+
scenario.result.status = changeable_test_status;
|
|
1255
|
+
}
|
|
1216
1256
|
});
|
|
1217
1257
|
|
|
1218
1258
|
AfterAll(async function() {
|
|
1219
1259
|
// Stop collecting server response statuses
|
|
1220
1260
|
scope.stop_tracking_server_status = true;
|
|
1221
1261
|
|
|
1222
|
-
let report =
|
|
1262
|
+
let report = reports.getPrintableReport( scope );
|
|
1223
1263
|
// Log the report. Can't get this.attach() or this.log() to work in `After()` or here.
|
|
1224
1264
|
// May need newest version of cucumberjs
|
|
1225
1265
|
console.log( `\n\n${ report }` );
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// All functions dealing with getting information from PDFs
|
|
2
|
+
|
|
3
|
+
const pdfjs_lib = require("pdfjs-dist/legacy/build/pdf.js");
|
|
4
|
+
const diff_lib = require("diff");
|
|
5
|
+
|
|
6
|
+
let _load_pdf = async function(pdf_path) {
|
|
7
|
+
// Simple wrapper to get the document
|
|
8
|
+
return await pdfjs_lib.getDocument({
|
|
9
|
+
url: pdf_path,
|
|
10
|
+
// https://github.com/mozilla/pdf.js/issues/4244#issuecomment-1479534301
|
|
11
|
+
useSystemFonts: true
|
|
12
|
+
}).promise;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let _get_content = async function(pdf_doc) {
|
|
16
|
+
let num_pages = pdf_doc.numPages;
|
|
17
|
+
let contents = "";
|
|
18
|
+
for (var i = 1; i <= num_pages; i++) {
|
|
19
|
+
const page_contents = await (await pdf_doc.getPage(i)).getTextContent();
|
|
20
|
+
contents += "\n" + page_contents.items.map((item) => item.str).join(" ");
|
|
21
|
+
}
|
|
22
|
+
return contents;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let pdf_reader = {};
|
|
26
|
+
|
|
27
|
+
pdf_reader.get_content = async function(pdf_path) {
|
|
28
|
+
/* Gets the full text content from the PDF.
|
|
29
|
+
*
|
|
30
|
+
* @returns str
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
let doc = await _load_pdf(pdf_path);
|
|
34
|
+
return await _get_content(doc);
|
|
35
|
+
}; // Ends get_content()
|
|
36
|
+
|
|
37
|
+
pdf_reader.get_fields = async function(pdf_path) {
|
|
38
|
+
/* Gets all of the fields in the PDF
|
|
39
|
+
*
|
|
40
|
+
* Example:
|
|
41
|
+
* {
|
|
42
|
+
* "users1_name_first": [
|
|
43
|
+
* {
|
|
44
|
+
* "id": "6R",
|
|
45
|
+
* "value": "aeu",
|
|
46
|
+
* "defaultValue": "",
|
|
47
|
+
* "multiline": false,
|
|
48
|
+
* "password": false,
|
|
49
|
+
* "charLimit": 0,
|
|
50
|
+
* "comb": false,
|
|
51
|
+
* "editable": true,
|
|
52
|
+
* "hidden": false,
|
|
53
|
+
* "name": "users1_name_first",
|
|
54
|
+
* "rect": [
|
|
55
|
+
* 143.52,
|
|
56
|
+
* 660.72,
|
|
57
|
+
* 315.11,
|
|
58
|
+
* 674.88
|
|
59
|
+
* ],
|
|
60
|
+
* "actions": null,
|
|
61
|
+
* "page": 0,
|
|
62
|
+
* "strokeColor": null,
|
|
63
|
+
* "fillColor": null,
|
|
64
|
+
* "rotation": 0,
|
|
65
|
+
* "type": "text"
|
|
66
|
+
* }
|
|
67
|
+
* ],
|
|
68
|
+
*/
|
|
69
|
+
let doc = await _load_pdf(pdf_path);
|
|
70
|
+
const field_objects = await doc.getFieldObjects();
|
|
71
|
+
return field_objects;
|
|
72
|
+
}; // Ends get_fields
|
|
73
|
+
|
|
74
|
+
pdf_reader.get_field_value = async function(pdf_path, field_name) {
|
|
75
|
+
let field_objects = await pdf_reader.get_fields(pdf_path);
|
|
76
|
+
if (field_name in field_objects) {
|
|
77
|
+
for (let inst of field_objects[field_name]) {
|
|
78
|
+
if ("value" in inst) {
|
|
79
|
+
return inst["value"];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}; // Ends get_field
|
|
85
|
+
|
|
86
|
+
pdf_reader.compare_pdfs = async function(existing_pdf_path, new_pdf_path) {
|
|
87
|
+
/**
|
|
88
|
+
* Returns the change objects between the PDF fields, if there's any diff
|
|
89
|
+
*/
|
|
90
|
+
let existing_doc = await _load_pdf(existing_pdf_path);
|
|
91
|
+
let new_doc = await _load_pdf(new_pdf_path);
|
|
92
|
+
let existing_content = await _get_content(existing_doc);
|
|
93
|
+
let new_content = await _get_content(new_doc);
|
|
94
|
+
|
|
95
|
+
// Assumption: authors won't be changing their PDF templates programmatically;
|
|
96
|
+
// so if the text of the PDFs are different, then it's actually the fields that are different,
|
|
97
|
+
// but they aren't editable / are flattened into the PDF.
|
|
98
|
+
let change = diff_lib.diffWords(existing_content, new_content);
|
|
99
|
+
if (change.some(diff => diff.added || diff.removed)) {
|
|
100
|
+
return change;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const existing_fields = await existing_doc.getFieldObjects();
|
|
104
|
+
const new_fields = await new_doc.getFieldObjects();
|
|
105
|
+
|
|
106
|
+
let changeObjs = diff_lib.diffJson(existing_fields, new_fields);
|
|
107
|
+
if (changeObjs.some(diff => diff.added || diff.removed)) {
|
|
108
|
+
return changeObjs;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}; // End compare_pdfs
|
|
112
|
+
|
|
113
|
+
module.exports = pdf_reader;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const fs = require(`fs`);
|
|
2
|
+
|
|
3
|
+
// Ours
|
|
4
|
+
const session_vars = require(`./session_vars`);
|
|
5
|
+
const files = require(`./files` );
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module.exports = reports = {};
|
|
9
|
+
|
|
10
|
+
reports.addReportHeading = function (scope, { scenario }) {
|
|
11
|
+
/** Return printable heading based on Scenario name and tags.
|
|
12
|
+
* Allow characters from other languages.
|
|
13
|
+
*/
|
|
14
|
+
if ( session_vars.get_debug() ) { console.log(`reports.addReportHeading(): ${scenario.pickle.name} \n`); }
|
|
15
|
+
|
|
16
|
+
// Collect all tag names
|
|
17
|
+
let tag_names = [];
|
|
18
|
+
for ( let tag of scenario.pickle.tags ) {
|
|
19
|
+
tag_names.push( tag.name );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let heading = `\n---------------\n`
|
|
23
|
+
+ `Scenario: ${ scenario.pickle.name }`;
|
|
24
|
+
|
|
25
|
+
if ( tag_names.length > 0 ) {
|
|
26
|
+
heading += `\nTags: ${ tag_names.join(' ') }`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
heading += `\n---------------`;
|
|
30
|
+
|
|
31
|
+
reports.addToReport( scope, {
|
|
32
|
+
type: `heading`,
|
|
33
|
+
value: heading,
|
|
34
|
+
});
|
|
35
|
+
}; // Ends reports.addReportHeading()
|
|
36
|
+
|
|
37
|
+
reports.addToReport = function( scope, { type, value }) {
|
|
38
|
+
/** Add an item to a specific list in a scenario's report.
|
|
39
|
+
* Create report if necessary.
|
|
40
|
+
*
|
|
41
|
+
* The report has a key for each scenario and each of those
|
|
42
|
+
* keys should correspond to a list containing the relevant data. */
|
|
43
|
+
if ( session_vars.get_debug() ) { console.log(`reports.addToReport():\ntype: ${ type }, value: ${ value }\n`) }
|
|
44
|
+
let scenario = makeSureReportPartsExist( scope );
|
|
45
|
+
scenario.lines.push({ type, value });
|
|
46
|
+
|
|
47
|
+
const MISC_ARTIFACTS_DIR = `_alkiln-misc_artifacts`;
|
|
48
|
+
// Ensure filepath for saving local unit test logs
|
|
49
|
+
if ( !scope.paths ) {
|
|
50
|
+
// TODO: would it be useful to put in existing artifacts folder if possible.
|
|
51
|
+
// Can this be usefully abstracted?
|
|
52
|
+
scope.paths = { debug_log: `${ MISC_ARTIFACTS_DIR }/unit_tests_logs-${ files.readable_date() }.txt` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
fs.appendFileSync(scope.paths.debug_log, `\nReport:\ntype: ${ type }, value: ${ value }`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
fs.mkdirSync( MISC_ARTIFACTS_DIR );
|
|
59
|
+
fs.appendFileSync(scope.paths.debug_log, `\nReport:\ntype: ${ type }, value: ${ value }`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return scenario;
|
|
63
|
+
}; // Ends reports.addToReport()
|
|
64
|
+
|
|
65
|
+
let makeSureReportPartsExist = function( scope ) {
|
|
66
|
+
/** If a part of the report needs to exist right now, but doesn't exist,
|
|
67
|
+
* create it. */
|
|
68
|
+
if ( !scope.report ) { scope.report = new Map(); }
|
|
69
|
+
if ( !scope.scenario_id ) {
|
|
70
|
+
scope.scenario_id = `_report_${ Date.now() }`;
|
|
71
|
+
}
|
|
72
|
+
if ( !scope.report.get( scope.scenario_id )) {
|
|
73
|
+
scope.report.set( scope.scenario_id, {} )
|
|
74
|
+
scope.report.get( scope.scenario_id ).status = `in progress`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let scenario = scope.report.get( scope.scenario_id );
|
|
78
|
+
if ( !scenario.lines ) { scenario.lines = []; }
|
|
79
|
+
|
|
80
|
+
return scenario;
|
|
81
|
+
}; // Ends makeSureReportPartsExist()
|
|
82
|
+
|
|
83
|
+
reports.setReportScenarioStatus = function( scope, { status=`PASSED` }) {
|
|
84
|
+
/** Add final status of scenario to scenario report object - passed, failed, etc. */
|
|
85
|
+
scope.report.get( scope.scenario_id ).status = status;
|
|
86
|
+
}; // Ends reports.setReportScenarioStatus()
|
|
87
|
+
|
|
88
|
+
reports.convertToOriginalStoryTableRow = function( scope, { row_data }) {
|
|
89
|
+
/** Returns the original data of row into a string formatted as a story table row.
|
|
90
|
+
* Should this show the actual row data that was used instead? That could
|
|
91
|
+
* be pretty confusing to most devs. */
|
|
92
|
+
let source_row = row_data.source || row_data;
|
|
93
|
+
let original = source_row.original;
|
|
94
|
+
let var_str = trimJSON( original.var );
|
|
95
|
+
let value_str = trimJSON( original.value );
|
|
96
|
+
let trigger_str = trimJSON( original.trigger );
|
|
97
|
+
if ( trigger_str === scope.trigger_not_needed_flag ) {
|
|
98
|
+
// This text was added by ALKiln itself. It's not part of the user's data
|
|
99
|
+
trigger_str = ``;
|
|
100
|
+
}
|
|
101
|
+
return `${ ' '.repeat(6) }| ${ var_str } | ${ value_str } | ${ trigger_str } |`;
|
|
102
|
+
}; // Ends reports.convertToOriginalStoryTableRows()
|
|
103
|
+
|
|
104
|
+
let trimJSON = function( str=`` ) {
|
|
105
|
+
/** Turn into JSON string then trim quotes from the start and end. */
|
|
106
|
+
let json = JSON.stringify( str );
|
|
107
|
+
let trimmed = json.replace( /^"/, `` ).replace( /"$/, `` ).replace( /\\\\/g, `\\`);
|
|
108
|
+
return trimmed;
|
|
109
|
+
}; // Ends trimJSON()
|
|
110
|
+
|
|
111
|
+
reports.getPrintableReport = function( scope ) {
|
|
112
|
+
/** Return a string generated from test report data. */
|
|
113
|
+
let report = `Assembly Line Kiln Automated Testing Report - ${ (new Date()).toUTCString() }\n`;
|
|
114
|
+
|
|
115
|
+
// First list failed scenarios, then non-passed scenarios, then passed scenarios
|
|
116
|
+
let failed_reports = ``;
|
|
117
|
+
let passed_reports = ``;
|
|
118
|
+
let other_reports = ``;
|
|
119
|
+
for ( let scenario of scope.report ) {
|
|
120
|
+
|
|
121
|
+
let report = reports.getPrintableScenario( scenario );
|
|
122
|
+
let [ scenario_id, data ] = scenario;
|
|
123
|
+
if ( data.status === `FAILED` ) { failed_reports += report; }
|
|
124
|
+
else if ( data.status === `PASSED` ) { passed_reports += report; }
|
|
125
|
+
else { other_reports += report; }
|
|
126
|
+
}; // ends for every scenario
|
|
127
|
+
|
|
128
|
+
if ( failed_reports !== `` ) { report += `\n\n===============================\n===============================\nFailed scenarios:\n${ failed_reports }`; }
|
|
129
|
+
if ( other_reports !== `` ) { report += `\n\n===============================\n===============================\nScenarios that did not pass:\n${ other_reports }`; }
|
|
130
|
+
if ( passed_reports !== `` ) { report += `\n\n===============================\n===============================\nPassed scenarios:\n${ passed_reports }`; }
|
|
131
|
+
|
|
132
|
+
return report;
|
|
133
|
+
}; // Ends reports.getPrintableReport()
|
|
134
|
+
|
|
135
|
+
reports.getPrintableScenario = function( scenario ) {
|
|
136
|
+
/** Return a string generated from scenario list of report items. */
|
|
137
|
+
let [ scenario_id, data ] = scenario;
|
|
138
|
+
let report = ``;
|
|
139
|
+
for ( let line of data.lines ) {
|
|
140
|
+
if ( line.type === `page_id` ) { report += `screen id: `; }
|
|
141
|
+
else if ( line.type === `outcome` ) { report += `\n`; }
|
|
142
|
+
else if ( line.type === `warning` ) { report += `WARNING: `; }
|
|
143
|
+
else if ( line.type === `error` ) { report += `\nERROR: ` }
|
|
144
|
+
report += `${ line.value }\n`;
|
|
145
|
+
}
|
|
146
|
+
return report;
|
|
147
|
+
}; // Ends reports.getPrintableScenario()
|