@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/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
- await scope.addReportHeading(scope, {scenario});
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
- scope.addToReport( scope, { type: `error`, value: err_msg });
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 = `${ await scope.convertToOriginalStoryTableRow(scope, { row_data: row }) }`;
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
- await scope.addToReport( scope, { type: `note`, value: `\n${ ' '.repeat(2) }Rows that got set:` });
387
- await scope.addToReport( scope, { type: `step`, value: `${ ' '.repeat(4) }And I get to the question id "${ target_id }" with this data:` });
388
- await scope.addToReport(scope, { type: `row`, value: `${ ' '.repeat(6) }| var | value | trigger |`, });
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
- await scope.addToReport(scope, { type: `row`, value: row.report_str, });
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
- await scope.addToReport( scope, { type: `note`, value: `${ ' '.repeat(2) }Unused rows:` });
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
- await scope.addToReport(scope, { type: `row`, value: row.report_str, });
405
+ reports.addToReport(scope, { type: `row`, value: row.report_str, });
404
406
  }
405
407
  }
406
408
  } else {
407
- await scope.addToReport(scope, { type: `note`, value: `${ ' '.repeat(2) }All rows were used` });
409
+ reports.addToReport(scope, { type: `note`, value: `${ ' '.repeat(2) }All rows were used` });
408
410
  }
409
411
 
410
- await scope.addToReport(scope, { type: `note`, value: `` });
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
- await scope.addToReport( scope, { type: `error`, value: no_id_msg });
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
- await scope.addToReport( scope, { type: `error`, value: no_match_msg });
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 )) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 )) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: no_error_msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: not_user_error_msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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 ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
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
- await scope.addToReport( scope, {
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
- await scope.addToReport( scope, {
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
- await scope.addToReport( scope, {
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
- await scope.addToReport( scope, { type: `error`, value: timeout_message });
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
- await scope.addToReport( scope, {
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
- await scope.addToReport( scope, {
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
- await scope.addToReport( scope, { type: `error`, value: err_msg });
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
- scenario.result.status = `FAILED`;
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
- await scope.setReportScenarioStatus( scope, { status: scenario.result.status });
1137
+ reports.setReportScenarioStatus( scope, { status: changeable_test_status });
1109
1138
 
1110
- if (scenario.result.status !== `PASSED`) {
1139
+ if (changeable_test_status !== `PASSED`) {
1111
1140
  if ( session_vars.get_debug() ) {
1112
- console.log( `Non-passed status occurred while running test:`, scenario.result.status );
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
- scope.addToReport(scope, {
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 (scenario.result.status === `FAILED`) {
1143
- await scope.addToReport(scope, { type: `outcome`, value: `**-- Scenario Failed --**` });
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( scenario.result.status ).to.equal( expected );
1181
+ expect( changeable_test_status ).to.equal( expected );
1155
1182
  // If the status was as expected, pass this criteria
1156
- scenario.result.status = `PASSED`;
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 ) { scenario.result.status = `PASSED`; }
1166
- else { scenario.result.status = `FAILED`; }
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
- scenario.result.status = `PASSED`;
1203
+ changeable_test_status = `PASSED`;
1177
1204
  } else {
1178
- scenario.result.status = `FAILED`;
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
- scope.addToReport( scope, { type: `error`, value: err_msg });
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 = await scope.getPrintableScenario( scope, { scenario: scenario_report_obj });
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 = await scope.getPrintableReport( scope );
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()