@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.
- package/.github/workflows/test_tests.yml +2 -0
- package/CHANGELOG.md +15 -0
- package/action.yml +3 -2
- package/lib/scope.js +78 -19
- package/lib/steps.js +26 -4
- package/package.json +1 -1
- package/tests/features/interactive_steps.feature +27 -0
- package/tests/features/observation_steps.feature +2 -0
- package/tests/features/reports.feature +13 -0
- package/tests/features/story_tables.feature +2 -0
|
@@ -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:
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
339
|
-
|
|
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
|
-
|
|
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
|
@@ -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 | |
|