@suffolklitlab/alkiln 4.4.0 → 4.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,11 @@ on:
5
5
  workflow_dispatch:
6
6
  inputs:
7
7
  extra_languages:
8
+ required: False
8
9
  description: 'Optional. A list of comma separated language names visible on buttons or links that change the language of the interview. Overrides the EXTRA_LANGUAGES repo secret.'
9
10
  default: ''
10
11
  tags:
12
+ required: False
11
13
  description: 'Optional. Use a "tag expression" specify which tagged tests to run (https://cucumber.io/docs/cucumber/api/#tag-expressions)'
12
14
  default: ''
13
15
 
package/CHANGELOG.md CHANGED
@@ -40,6 +40,21 @@ Format:
40
40
  -->
41
41
  <!-- ## [Unreleased] -->
42
42
 
43
+ ## [4.5.2] - 2022-06-10
44
+ ### Fixed
45
+ - (internal) Expected in report is reset properly
46
+ - if a test used "the Scenario report should include ...", then all tests that were
47
+ run after that would pass even if the test itself failed but wasn't expected to fail
48
+
49
+ ## [4.5.1] - 2022-06-10
50
+ ### Fixed
51
+ - Corrected some schema errors in the `action.yml` (the `run` key was specified twice, should only have been once, and `required: False` is needed
52
+ for optional inputs)
53
+
54
+ ## [4.5.0] - 2022-05-23
55
+ ### Added
56
+ - Steps that tap tabs (made using the `tabbed_templates_html` function from ALToolbox) and other arbitrary elements on the page.
57
+
43
58
  ## [4.4.0] - 2022-05-23
44
59
  ### Added
45
60
  - Appends the results of the cucumber [summary formatter](https://github.com/cucumber/cucumber-js/blob/main/docs/formatters.md#summary) to the `debug_log.txt`, which includes useful stack traces into the Kiln code when tests fail.
package/action.yml CHANGED
@@ -61,8 +61,9 @@ runs:
61
61
  echo "MAX_SECONDS_FOR_SETUP=${{ inputs.MAX_SECONDS_FOR_SETUP }}" >> $GITHUB_ENV
62
62
  shell: bash
63
63
  - name: "ALKiln: confirm info"
64
- run: echo -e "\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nManually added languages are $EXTRA_LANGUAGES\nAll other data is secret\n"
65
- run: echo -e "\nALKiln version is $ALKILN_VERSION\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nMAX_SECONDS_FOR_SETUP is $MAX_SECONDS_FOR_SETUP\nManually added languages are $EXTRA_LANGUAGES\n"
64
+ run: |
65
+ echo -e "\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nManually added languages are $EXTRA_LANGUAGES\nAll other data is secret\n"
66
+ echo -e "\nALKiln version is $ALKILN_VERSION\nRepo is $REPO_URL\nBranch ref is $BRANCH_PATH\nMAX_SECONDS_FOR_SETUP is $MAX_SECONDS_FOR_SETUP\nManually added languages are $EXTRA_LANGUAGES\n"
66
67
  shell: bash
67
68
 
68
69
  # This doesn't use an already established package-lock.json and that
package/lib/scope.js CHANGED
@@ -244,10 +244,37 @@ module.exports = {
244
244
  if ( showif ) { await scope.page.waitForTimeout( scope.showif_timeout ); }
245
245
  },
246
246
 
247
- afterStep: async function afterStep(scope, options = {waitForShowIf: false, navigated: false, waitForTimeout: 0}) {
247
+ waitForTab: async function waitForTab(scope, tab_to_show) {
248
+ /**
249
+ * @param {tab_to_show} the aria-labeledby text of the tab panel, which
250
+ * we use to query and wait for that tab panel to be shown.
251
+ */
252
+ // Implementation linked to https://github.com/SuffolkLITLab/docassemble-ALToolbox/blob/4aa1069389d2ca9cadb586f309dd73d791f081d1/docassemble/ALToolbox/misc.py#L127
253
+ // I think `.$()` does not use a timeout
254
+ if ( tab_to_show.length == 0) { return }
255
+ // waiting for the transitionend event: https://stackoverflow.com/a/57955251/11416267
256
+ await scope.page.evaluate((element_query) => {
257
+ return new Promise((resolve) => {
258
+ const elem = document.querySelector(element_query);
259
+ // Taken from jQuery: https://stackoverflow.com/a/33456469/11416267
260
+ const is_visible = !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
261
+ if (!is_visible) {
262
+ const onEnd = function() {
263
+ elem.removeEventListener('transitionend', onEnd);
264
+ resolve();
265
+ };
266
+ elem.addEventListener('transitionend', onEnd);
267
+ } else {
268
+ resolve();
269
+ }
270
+ });
271
+ }, `div[aria-labelledby="${tab_to_show}"]`);
272
+ },
273
+
274
+ afterStep: async function afterStep(scope, options = {waitForShowIf: false, tabToWaitFor: '', navigated: false, waitForTimeout: 0}) {
248
275
  /* Common things to do after a step, including take care of errors.
249
276
  * TODO: discuss splitting into `afterField` and `afterStep` or something. Or just change name
250
- * TODO: Update to latest cucumberjs, which as `AfterStep` */
277
+ * TODO: Update to latest cucumberjs, which has `AfterStep` */
251
278
 
252
279
  if ( scope.page ) {
253
280
  // Use a while loop until the below does not error with `Error: Execution context was destroyed`
@@ -274,6 +301,7 @@ module.exports = {
274
301
 
275
302
  // Wait for elements that might get revealed
276
303
  if ( options.waitForShowIf ) { await scope.waitForShowIf( scope ); }
304
+ if ( options.tabToWaitFor ) { await scope.waitForTab( scope, options.tabToWaitFor); }
277
305
  } // ends if scope.page
278
306
 
279
307
  if ( options.navigated ) {
@@ -290,7 +318,7 @@ module.exports = {
290
318
  if ( options.waitForTimeout ) { await time.waitForTimeout( options.waitForTimeout ); }
291
319
  }, // Ends afterStep
292
320
 
293
- tapElement: async function tapElement(scope, elem) {
321
+ tapElement: async function tapElement(scope, elem, tabToWaitFor='', waitForTimeout=0) {
294
322
  /* Tap a given element, detect navigation, and do appropriate related things. */
295
323
 
296
324
  // This check might never be needed now as we try to match fields on the page
@@ -303,25 +331,32 @@ module.exports = {
303
331
 
304
332
  let start_url = await scope.page.url(); // so we can later check for navigation
305
333
 
306
- let winner;
307
- winner = await Promise.race([
308
- Promise.all([ // Error loads page, so no need to detect to keep things short
309
- // Click with no navigation will end immediately
310
- // Can't use elem[ scope.activate ](). It works locally, but errors on GitHub
311
- // with "Node is not visible or not an HTMLElement". We're not using a custom Chromium version
312
- // Unfortunately, this won't catch an invisible element. Hopefully we'd catch it before now...?
313
- // "Tap" on mobile is more complex to implement ourselves.
314
- // See https://stackoverflow.com/a/56547605/14144258
315
- // and other convos around it. We haven't made it public that the device
316
- // can customized. Until we do that, we'll just use "click" all the time.
334
+ let clickWait;
335
+ // Can't use elem[ scope.activate ](). It works locally, but errors on GitHub
336
+ // with "Node is not visible or not an HTMLElement". We're not using a custom Chromium version
337
+ // Unfortunately, this won't catch an invisible element. Hopefully we'd catch it before now...?
338
+ // "Tap" on mobile is more complex to implement ourselves.
339
+ // See https://stackoverflow.com/a/56547605/14144258
340
+ // and other convos around it. We haven't made it public that the device
341
+ // can customized. Until we do that, we'll just use "click" all the time.
342
+ if (!tabToWaitFor) {
343
+ // Click with no navigation will end immediately
344
+ clickWait = Promise.all([
317
345
  elem.evaluate( (el) => { return el.click(); }),
318
346
  scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
319
- ]),
347
+ ]);
348
+ } else {
349
+ // Same thing as above, but the promise doesn't wait for navigation.
350
+ clickWait = elem.evaluate( (el) => {return el.click(); });
351
+ }
352
+ // Error loads page, so no need to detect to keep things short
353
+ let winner = await Promise.race([
354
+ clickWait,
320
355
  // This is meant to detect user-visible alerts/errors,
321
356
  // not breaking errors in the testing code.
322
357
  scope.page.waitForSelector('.alert-danger', { visible: true }),
323
358
  scope.page.waitForSelector('.da-has-error', { visible: true }),
324
- ]);
359
+ ])
325
360
 
326
361
  // TODO: Not sure how to detect navigation landing at same page.
327
362
  // Maybe url change + trigger var change? Maybe just trigger var?
@@ -335,8 +370,12 @@ module.exports = {
335
370
  // If navigation, wait for 200ms to let things settle down
336
371
  await scope.afterStep(scope, {waitForTimeout: 200, navigated: navigated});
337
372
  } else {
338
- // If stay on page, wait for `show if` or feedback elements to be move around
339
- await scope.afterStep(scope, {waitForShowIf: true, navigated: navigated});
373
+ // If stay on page, wait for `show if`, the new tab to be loaded, or feedback elements to be move around
374
+ options = {waitForShowIf: !tabToWaitFor, tabToWaitFor: tabToWaitFor, navigated: navigated};
375
+ if (waitForTimeout > 0) {
376
+ options = {waitForTimeout: waitForTimeout, navigated: navigated};
377
+ }
378
+ await scope.afterStep(scope, options);
340
379
  }
341
380
 
342
381
  return winner;
@@ -426,13 +465,33 @@ module.exports = {
426
465
  await scope.tapElement( scope, elem );
427
466
  },
428
467
 
468
+ tapTab: async function ( scope, tab_id ) {
469
+ let elem = await scope.page.$(`#${tab_id}`);
470
+ let error_msg = `Couldn't find the tab with id "#${tab_id}" on the page, is there a typo?`;
471
+ if ( !elem ) {
472
+ await scope.addToReport(scope, { type: `error`, value: error_msg });
473
+ }
474
+ expect( elem, error_msg ).to.exist;
475
+ await scope.tapElement( scope, elem, tab_id );
476
+ },
477
+
478
+ tapElementBySelector: async function ( scope, selector, waitForTimeout ) {
479
+ let elem = await scope.page.$(`${selector}`);
480
+ let error_msg = `Couldn't find "${selector}" on the page, is there a typo?`;
481
+ if ( !elem ) {
482
+ await scope.addToReport(scope, { type: `error`, value: error_msg });
483
+ }
484
+ expect( elem, error_msg ).to.exist;
485
+ await scope.tapElement( scope, elem, '', waitForTimeout );
486
+ },
487
+
429
488
  load: async function ( scope, file_name ) {
430
489
  /* Try to load the page. Should we also pass a `timeout` arg or just
431
490
  * use scope.timeout? */
432
491
 
433
492
  // If there is no browser open, start a new one
434
493
  if (!scope.browser) {
435
- scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug() });
494
+ scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
436
495
  }
437
496
  if ( !scope.page ) { scope.page = await scope.browser.newPage(); }
438
497
 
package/lib/steps.js CHANGED
@@ -73,6 +73,8 @@ BeforeAll(async () => {
73
73
 
74
74
  Before(async (scenario) => {
75
75
  scope.scenario_id = uuidv4();
76
+ scope.expected_status = null;
77
+ scope.expected_in_report = null;
76
78
 
77
79
  await scope.addReportHeading(scope, {scenario});
78
80
  scope.base_filename = await scope.getSafeScenarioBaseFilename(scope, {scenario});
@@ -391,7 +393,7 @@ When(/^(?:the user gets)?(?:I get)? to ?(?:the)?(?: ?question)?(?: id)? "([^"]+)
391
393
  //#####################################
392
394
  //#####################################
393
395
 
394
- When(/I wait (\d*\.?\d+) seconds?/, { timeout: -1 }, async ( seconds ) => {
396
+ When(/I wait ?(?:for)? (\d*\.?\d+) seconds?/, { timeout: -1 }, async ( seconds ) => {
395
397
  /** Wait for a specified amount of time. Can help with some race conditions.
396
398
  * We're working on reducing race conditions, but it'll never be perfect.
397
399
  *
@@ -707,6 +709,27 @@ When('I tap to continue', { timeout: -1 }, async () => {
707
709
  );
708
710
  });
709
711
 
712
+ When(/I tap the "([^"]+)" tab/i, { timeout: -1 }, async ( tabId ) => {
713
+ return wrapPromiseWithTimeout(
714
+ scope.tapTab( scope, tabId ),
715
+ scope.timeout
716
+ )
717
+ });
718
+
719
+ When(/I tap the "([^"]+)" element$/i, { timeout: -1 }, async ( elemSelector ) => {
720
+ return wrapPromiseWithTimeout(
721
+ scope.tapElementBySelector( scope, elemSelector, 2000 ),
722
+ scope.timeout
723
+ )
724
+ });
725
+
726
+ When(/I tap the "([^"]+)" element and wait ([0-9]+) second(?:s)?/i, { timeout: -1 }, async ( elemSelector, waitTime ) => {
727
+ return wrapPromiseWithTimeout(
728
+ scope.tapElementBySelector( scope, elemSelector, waitTime * 1000 ),
729
+ scope.timeout
730
+ )
731
+ });
732
+
710
733
 
711
734
  //#####################################
712
735
  // UI element interaction
@@ -909,11 +932,10 @@ After(async function(scenario) {
909
932
  scope.expected_status = null; // reset for next Scenario
910
933
  expect( scenario.result.status ).to.equal( expected );
911
934
  }
912
- if ( scope.expected_in_report ) {
935
+ if ( scope.expected_in_report && scope.expected_in_report.length > 0) {
913
936
  let expected = scope.expected_in_report;
914
937
  // Reset report values no matter what so they don't mess up future scenarios
915
- // Maybe this should be in `Before`, but it would be so out of context there...
916
- scope.expected_in_report = [];
938
+ scope.expected_in_report = null;
917
939
  let all_were_included = await scope.reportIncludesAllExpected( scope, { expected });
918
940
  if ( all_were_included ) { scenario.result.status = `PASSED`; }
919
941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "4.4.0",
3
+ "version": "4.5.2",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -41,6 +41,11 @@ Scenario: I set text-type values
41
41
  And I set the var "showif_dropdown" to "showif_dropdown_1"
42
42
  When I tap to continue
43
43
  # Next page
44
+ Then the question id should be "object checkboxes"
45
+ And I set the var "object_checkboxes_test['obj_chkbx_opt_1']" to "true"
46
+ And I set the var "object_dropdown" to "obj_opt_2"
47
+ When I tap to continue
48
+ # Next page
44
49
  Then the question id should be "buttons yesnomaybe"
45
50
  When I set the var "buttons_yesnomaybe" to "None"
46
51
  # Next page
@@ -101,3 +106,25 @@ Scenario: handles settings from Github secrets
101
106
  Then I see the phrase "secret-var2-value"
102
107
  When I tap to continue
103
108
  And I set the variable "third_text_entry" to secret "SECRET_NOT_THERE"
109
+
110
+ @i3 @tap-elements @tabs
111
+ Scenario: tap elements
112
+ Given I start the interview at "test_taps"
113
+ And I tap the "Tests-first_template-tab" tab
114
+ Then I see the phrase "Mechanics"
115
+ And I tap the "Tests-second_template-tab" tab
116
+ Then I see the phrase "villify"
117
+ And I tap the "Tests-third_template-tab" tab
118
+ Then I see the phrase "museum"
119
+ And I tap the "#special_event" element and wait 1 second
120
+ Then I see the phrase "Portishead"
121
+
122
+ @i4 @tap-elements @tabs @error
123
+ Scenario: tap element with an error
124
+ Given the final Scenario status should be "failed"
125
+ And the Scenario report should include:
126
+ """
127
+ is there a typo?
128
+ """
129
+ Given I start the interview at "test_taps"
130
+ And I tap the "Tests-not_there-tab" tab
@@ -46,6 +46,8 @@ Scenario: I check navigation
46
46
  Then I arrive at the next page
47
47
    Then I get to "screen features" with this data:
48
48
  | var | value | trigger |
49
+ | object_checkboxes_test["obj_chkbx_opt_1"] | True | |
50
+ | object_dropdown | obj_opt_2 | |
49
51
  | button_continue | True | |
50
52
  | buttons_other | button_2 | |
51
53
  | buttons_yesnomaybe | True | |
@@ -160,6 +160,8 @@ Scenario: Fail with link with given text does not lead to correct url
160
160
  Then I arrive at the next page
161
161
    Then I get to "screen features" with this data:
162
162
  | var | value | trigger |
163
+ | object_checkboxes_test["obj_chkbx_opt_1"] | true | |
164
+ | object_dropdown | obj_opt_2 | |
163
165
  | button_continue | True | |
164
166
  | buttons_other | button_2 | |
165
167
  | buttons_yesnomaybe | True | |
@@ -188,6 +190,8 @@ Scenario: Fail with link unexpectedly opens in same window
188
190
  Then I arrive at the next page
189
191
    Then I get to "screen features" with this data:
190
192
  | var | value | trigger |
193
+ | object_checkboxes_test["obj_chkbx_opt_1"] | true | |
194
+ | object_dropdown | obj_opt_2 | |
191
195
  | button_continue | True | |
192
196
  | buttons_other | button_2 | |
193
197
  | buttons_yesnomaybe | True | |
@@ -216,6 +220,8 @@ Scenario: Fail with link unexpectedly opens in a new window
216
220
  Then I arrive at the next page
217
221
    Then I get to "screen features" with this data:
218
222
  | var | value | trigger |
223
+ | object_checkboxes_test["obj_chkbx_opt_1"] | true | |
224
+ | object_dropdown | obj_opt_2 | |
219
225
  | button_continue | True | |
220
226
  | buttons_other | button_2 | |
221
227
  | buttons_yesnomaybe | True | |
@@ -472,6 +478,11 @@ Scenario: Report still shows page id when I tap to continue without setting any
472
478
  # Next page
473
479
  Then the question id should be "showifs"
474
480
  When I tap to continue
481
+ # Next page
482
+ Then the question id should be "object checkboxes"
483
+ And I set the var "object_checkboxes_test['obj_chkbx_opt_1']" to "true"
484
+ And I set the var "object_dropdown" to "obj_opt_2"
485
+ When I tap to continue
475
486
  # Next page (showifs ID SHOULD BE SHOWN IN REPORT)
476
487
  Then the question id should be "buttons yesnomaybe"
477
488
 
@@ -517,6 +528,8 @@ Scenario: Report lists unused table rows
517
528
  | extra_5 | extra 5 | |
518
529
    Then I get to "screen features" with this data:
519
530
  | var | value | trigger |
531
+ | object_checkboxes_test["obj_chkbx_opt_1"] | true | |
532
+ | object_dropdown | obj_opt_2 | |
520
533
  | extra_6 | extra 6 | |
521
534
  | extra_7 | extra 7 | |
522
535
  | button_continue | True | |
@@ -47,6 +47,8 @@ Covers story table tests for:
47
47
  | checkboxes_yesno | True | |
48
48
  | direct_standard_fields | True | |
49
49
  | dropdown_test | dropdown_opt_2 | |
50
+ | object_checkboxes_test["obj_chkbx_opt_1"] | true | |
51
+ | object_dropdown | obj_opt_2 | |
50
52
  | radio_other | radio_other_opt_3 | |
51
53
  | radio_yesno | False | |
52
54
  | screen_features | True | |