@suffolklitlab/alkiln 5.13.4 → 5.14.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/.github/workflows/github_server.yml +1 -1
- package/.github/workflows/playground.yml +1 -1
- package/.github/workflows/unit_tests.yml +1 -1
- package/CHANGELOG.md +11 -0
- package/_alkiln_meta/gherkin_parsed_example.json +1546 -0
- package/_alkiln_meta/shift_logs.js +75 -0
- package/docs/constrained_random_tests.md +220 -289
- package/docs/decisions/move_runtime_config_to_sources_folder.md +3 -1
- package/docs/prioritize_constrained_random_tests.md +35 -76
- package/lib/scope.js +12 -1
- package/lib/steps.js +48 -23
- package/lib/utils/Log.js +15 -16
- package/lib/utils/_alkiln-constrained_random_tests_old.js +1776 -0
- package/lib/utils/reports.js +8 -6
- package/package.json +1 -1
- package/tests/unit_tests/example_of_chai_test_lifecycle.js +78 -0
- package/docs/seprate_file_generation_GitHub_action.md +0 -11
- package/lib/parse.js +0 -36
- package/lib/utils/get_sources_paths.js +0 -162
- package/lib/utils/parse.js +0 -1
- package/lib/utils/proccess_command_line_args.js +0 -267
|
@@ -1,101 +1,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Prioritize constrained random answers tests over pure random answers tests
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Context and Problem Statement
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
We could develop constrained randomness first or pure randomness first. Is there a reason to prioritize one over another?
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. They are worried they haven't covered all eventualities
|
|
7
|
+
## Considered Options
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
- Purely random answers. They fill in fields without any guidance.
|
|
10
|
+
- Constrained random answers tests. They make the test author list all the variable names and all the choices of answers they want to allow for those variables.
|
|
11
|
+
- Hybrid approach. Allow an author to specify some values while letting other values stay completely random.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Decision Outcome
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Prioritize constrained random answers tests over pure random answers tests. Strangely, they seem to be the more flexible of the two options.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
- **Some fields might require very specific answers, like a real lawyer's bar number or a specific file name for uploading exhibits.**
|
|
18
|
-
- Narrow down the paths at some points, like always choosing that you have minor children
|
|
19
|
-
- **Even if the user doesn't know it, this is the most flexible option**
|
|
17
|
+
## Pros and Cons of the Options
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
The discussion about the general pros and cons of random answers tests will be in the `constrained_random_tests` doc for now.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
### Purely random answers
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
Pros:
|
|
26
24
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- Or should we require a string (e.g. ALK_NO_ANSWER) for fields that should not get random inputs?
|
|
30
|
-
- (most easy purely random input method to implement without changing current code?) Or specify randomness (e.g. ALK_RANDOM_ANSWER)?
|
|
31
|
-
- **(backwards compatible because we can become more flexible later) Require both of the above?**
|
|
25
|
+
- Very little work for test authors to write.
|
|
26
|
+
- Because they're so free-form, they may do a better job finding unexpected behavior that authors didn't think to test for.
|
|
32
27
|
|
|
33
|
-
|
|
28
|
+
Cons:
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- Add Step to answer randomly for one page.
|
|
30
|
+
- Unless we can find better ways of generating purely random test files for the authors, copying a random answers test to keep permanently might be very challenging.
|
|
31
|
+
- Fields might get nonsensical answers. For example, a very large number for age or a very small number for income.
|
|
32
|
+
- Impossible to use for fields that need very specific answers, like a real lawyer's bar number or a specific file name for uploading exhibits.
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
### Constrained random answers
|
|
41
35
|
|
|
42
|
-
|
|
36
|
+
Pros:
|
|
43
37
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
- "ALKiln: max length 3" (of 5 checkbox answers)
|
|
48
|
-
- "ALKiln: name"
|
|
49
|
-
- "ALKiln: number" (and other types like "currency") (we can probably figure those out)
|
|
38
|
+
- Useful for fields that need very specific answers, like a real lawyer's bar number or a specific file name for uploading exhibits.
|
|
39
|
+
- An author can focus the tests' answers on important user stories. For example, they might want to focus some tests on users that have children while keeping other answers more random.
|
|
40
|
+
- An author might be able to make more efficient random tests. For example, a multiple choice question may have multiple answers that, behind the scenes, set the same value. Authors might do this to help users who are confused about their options give more accurate answers. A purely random test would pick that repeated value too often and waste testing time.
|
|
50
41
|
|
|
51
|
-
|
|
42
|
+
Cons:
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
- They take more time to write.
|
|
45
|
+
- They take more work to maintain.
|
|
54
46
|
|
|
55
|
-
|
|
47
|
+
<!-- TODO: Add to docs - each `possible_values` cell should contain 1 or more possible answers. -->
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
### Hybrid
|
|
58
50
|
|
|
59
|
-
|
|
51
|
+
In future, we believe we can figure out how to let users have partially constrained tests where unspecified fields get purely random answers while constrained fields choose from a set of answers. It would need to be its own Step.
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
"rerun" and then change the name at the end of the run.
|
|
63
|
-
retry the test somehow (if needed)...
|
|
64
|
-
present the test and results to the user
|
|
53
|
+
Pros:
|
|
65
54
|
|
|
66
|
-
|
|
55
|
+
- Offers the most flexibility and control.
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
Avoid repeating tests of users (which is much(?) harder)
|
|
57
|
+
Cons:
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Create a file with a certain # of Scenarios and run it
|
|
74
|
-
- Does cucumber decide the tests it'll run at runtime or before? I think the former
|
|
75
|
-
- Is it better to split tests up into separate files if there are a lot?
|
|
76
|
-
- Is it better or worse to help the developer feel the magnitude of the #
|
|
77
|
-
of tests they're running (using Example) [Answer: worse]
|
|
78
|
-
- How to handle separate constrained random tables?
|
|
79
|
-
|
|
80
|
-
## right now
|
|
81
|
-
[r1 opt 1, r1 opt 2, r1 opt 3, name input, date input, description input]
|
|
82
|
-
|
|
83
|
-
## need for random
|
|
84
|
-
|
|
85
|
-
detect r1 while looping through the fields
|
|
86
|
-
[r1 opt 1, r1 opt 2, r1 opt 3, name input, date input, description input]
|
|
87
|
-
Con: as we loop through, we don't know the number of options at each particular instance
|
|
88
|
-
|
|
89
|
-
or make grouped fields into groups. Basically group r1:
|
|
90
|
-
[[r1 opt 1, r1 opt 2, r1 opt 3], name input, date input, description input]
|
|
91
|
-
|
|
92
|
-
## why not generate a test that uses all the constraints and thus is fully random?
|
|
93
|
-
|
|
94
|
-
Because we'd have to parse docassembled *compiled* files, which is very hard
|
|
95
|
-
|
|
96
|
-
Alternative:
|
|
97
|
-
|
|
98
|
-
When the developer runs a test, they can use a Step to have it collect all possible fields that ALKiln sees during that test run.
|
|
99
|
-
Q: How effectively can we decode the fields? Or can we make a tool that will help a developer decode their fields? (Include an input that just decodes one encoded string (`atob()`))
|
|
100
|
-
Q: If the fields are multiple choice and the developer's test picks at least one choice from that group of fields, can we use that to properly decode all the names of the fields? Hesitant to do this because some values may be encoded differently from other values (I think).
|
|
101
|
-
Q: Is this more work than just refactoring how we group fields?
|
|
59
|
+
- Might be hard to avoid confusion about test behavior.
|
|
60
|
+
- Means developing both methods of testing. That's out of scope for one PR.
|
package/lib/scope.js
CHANGED
|
@@ -888,7 +888,7 @@ module.exports = {
|
|
|
888
888
|
}, // Ends scope.normalizeTable()
|
|
889
889
|
|
|
890
890
|
getAllFields: async function ( scope, { html }) {
|
|
891
|
-
|
|
891
|
+
/** Given HTML string of a page, returns a list of all the fields in it
|
|
892
892
|
* as some kind of sophisticated data? With their selectors, for example.
|
|
893
893
|
* We will choose selectors that will continue to identify the node, eve
|
|
894
894
|
* after a hidden field is revealed, etc.
|
|
@@ -1019,12 +1019,15 @@ module.exports = {
|
|
|
1019
1019
|
// button, radio, checkbox yesno, and maybe others need the value of the `value` attribute
|
|
1020
1020
|
let values = [];
|
|
1021
1021
|
if ( node.name === `select` ) {
|
|
1022
|
+
// Get possible values
|
|
1022
1023
|
values = await scope.getOptionValues( scope, { $select: $node });
|
|
1023
1024
|
}
|
|
1024
1025
|
if ( values.length === 0 && node.attribs.value ) {
|
|
1025
1026
|
// TODO: Add links to docs about da YAML values that devs never fill in themselves and
|
|
1026
1027
|
// so don't know what they are. E.g. yesnomabye -> True/False/None.
|
|
1027
1028
|
// TODO: Text inputs and textareas must ignore the value, etc. How?
|
|
1029
|
+
|
|
1030
|
+
// Get the current value for not `select` fields
|
|
1028
1031
|
values.push( node.attribs.value );
|
|
1029
1032
|
}
|
|
1030
1033
|
|
|
@@ -1387,6 +1390,14 @@ module.exports = {
|
|
|
1387
1390
|
* @param <field[n].guesses[n].trigger> {str} - var name that triggered this page
|
|
1388
1391
|
*
|
|
1389
1392
|
* @returns [ var_data[n] ]
|
|
1393
|
+
* @returns [{
|
|
1394
|
+
* table_row: var_data[n],
|
|
1395
|
+
* field: {
|
|
1396
|
+
* selector: str,
|
|
1397
|
+
* guesses: [{ values: [str] }]
|
|
1398
|
+
* }
|
|
1399
|
+
* }]
|
|
1400
|
+
* What does it return when there's no match?
|
|
1390
1401
|
*/
|
|
1391
1402
|
|
|
1392
1403
|
let rows_that_match_field_names = [];
|
package/lib/steps.js
CHANGED
|
@@ -707,39 +707,48 @@ Then(/the "([^"]+)" link opens in (a new window|the same window)/, async (linkTe
|
|
|
707
707
|
await scope.afterStep(scope);
|
|
708
708
|
});
|
|
709
709
|
|
|
710
|
-
Then(/(?:the )?text(?: in)?(?: the)?(?: JSON)?(?: var)?(?:iable)?(?: )?"([^"]+)" should be/i, async (var_name, expected_value) => {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
710
|
+
Then(/(?:the )?text(?: in)?(?: for)?(?: the)?(?: JSON)?(?: var)?(?:iable)?(?: )?"([^"]+)" should be/i, async (var_name, expected_value) => {
|
|
711
|
+
/** Check that the text string of the page's JSON var value matches
|
|
712
|
+
* the given text string. Does not currently accept nested values
|
|
713
|
+
* like "foo.bar". The word "text" is required.
|
|
714
|
+
*
|
|
715
|
+
* The format for this step is something like this:
|
|
716
|
+
* ```
|
|
717
|
+
* Then the text in the JSON variable "var_name" should be
|
|
718
|
+
* """
|
|
719
|
+
* A single or
|
|
720
|
+
* multiline string
|
|
721
|
+
* """
|
|
722
|
+
* ```
|
|
723
|
+
*
|
|
724
|
+
* Note: ALKiln removes `\r` from both the expected and actual values.
|
|
725
|
+
*
|
|
726
|
+
* Sentence combinations that will work:
|
|
727
|
+
* the text in the JSON variable "variable_with_text_value" should be
|
|
728
|
+
* the text in the JSON var "variable_with_text_value" should be
|
|
729
|
+
* the text for "variable_with_text_value" should be
|
|
730
|
+
* text "variable_with_text_value" should be
|
|
731
|
+
* */
|
|
730
732
|
|
|
731
733
|
// Get JSON variable value
|
|
732
734
|
let { json_path, json } = await scope.savePageJSONData( scope );
|
|
735
|
+
|
|
736
|
+
// docassemble (at least sometimes) makes new lines with `\r\n`
|
|
733
737
|
let actual_value = json.variables[ var_name ];
|
|
738
|
+
actual_value = actual_value.replace(/\r/, ``);
|
|
739
|
+
// Normalize `expected_value` as well for backwards compatibility in case any
|
|
740
|
+
// authors are already compensating for docassemble
|
|
741
|
+
expected_value = expected_value.replace(/\r/, ``);
|
|
734
742
|
|
|
735
743
|
// Add warning to report if value is not as expected
|
|
736
744
|
if ( actual_value !== expected_value ) {
|
|
737
745
|
reports.addToReport( scope, {
|
|
738
746
|
type: `error`,
|
|
739
747
|
code: `ALK0083`,
|
|
740
|
-
value: `The variable "${ var_name }" was
|
|
741
|
-
+ `value on the "${ scope.page_id }" screen
|
|
742
|
-
+
|
|
748
|
+
value: `The variable "${ var_name }" was different from the expected `
|
|
749
|
+
+ `value on the "${ scope.page_id }" screen.`
|
|
750
|
+
+ `The expected values was\n` + expected_value
|
|
751
|
+
+ `\nwhile the actual value was\n` + actual_value
|
|
743
752
|
});
|
|
744
753
|
}
|
|
745
754
|
|
|
@@ -1044,6 +1053,22 @@ When(/I set the var(?:iable)? "([^"]+)" to "([^"]+)"/, { timeout: -1 }, async (
|
|
|
1044
1053
|
|
|
1045
1054
|
});
|
|
1046
1055
|
|
|
1056
|
+
When(/I set the var(?:iable)? "([^"]+)" to\s*$/, { timeout: -1 }, async ( var_name, answer ) => {
|
|
1057
|
+
/** Set a non-story table variable (with or without a choice associated with
|
|
1058
|
+
* it) to a value that can have new lines in it. Most useful for
|
|
1059
|
+
* `textarea`s.
|
|
1060
|
+
*
|
|
1061
|
+
* Example:
|
|
1062
|
+
* I set the var "benefits['SSI']" to
|
|
1063
|
+
* """
|
|
1064
|
+
* Some multline
|
|
1065
|
+
* value
|
|
1066
|
+
* """
|
|
1067
|
+
* */
|
|
1068
|
+
// `page` timeout will deal with custom timeout
|
|
1069
|
+
await scope.steps.set_regular_var( scope, var_name, answer )
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1047
1072
|
When(/I set the var(?:iable)? "([^"]+)" to ?(?:the)? ?(?:GitHub)? secret "([^"]+)"/i, { timeout: -1 }, async ( var_name, set_to_env_name ) => {
|
|
1048
1073
|
/* Set a non-story table variable to a secrete value. Same as the above, but reads the value from `process.env` */
|
|
1049
1074
|
// This could theoretically tap an element, so
|
package/lib/utils/Log.js
CHANGED
|
@@ -491,31 +491,28 @@ class Log {
|
|
|
491
491
|
} // Ends Log._stringify_logs()
|
|
492
492
|
|
|
493
493
|
_stringify_inline({ prev_line_type, inline_log }) {
|
|
494
|
-
/** Formats the
|
|
495
|
-
*
|
|
494
|
+
/** Formats the log to add a space or a new line separator. May end up with
|
|
495
|
+
* a leading space for now.
|
|
496
496
|
*
|
|
497
497
|
* @param { "inline"|"block"|null } prev_line_type - Required. The type of
|
|
498
498
|
* the previous log.
|
|
499
499
|
* @param { null|non-obj } inline_log - Required. The log.
|
|
500
500
|
*
|
|
501
|
-
* @returns { string } The formatted
|
|
501
|
+
* @returns { string } The formatted line.
|
|
502
502
|
*
|
|
503
503
|
* @example
|
|
504
504
|
* // Backticks are to make the surrounding white space more clear
|
|
505
505
|
* log._stringify_inline({ prev_line_type: null, inline_log: "apple" });
|
|
506
|
-
* //
|
|
506
|
+
* // "apple "
|
|
507
507
|
* log._stringify_inline({ prev_line_type: 'inline', inline_log: "apple" });
|
|
508
|
-
* //
|
|
508
|
+
* // "apple "
|
|
509
509
|
* log._stringify_inline({ prev_line_type: 'block', inline_log: "apple" });
|
|
510
|
-
* //
|
|
511
|
-
* // apple
|
|
510
|
+
* // "
|
|
511
|
+
* // apple"
|
|
512
512
|
* */
|
|
513
|
-
// An inline log's string starts with nothing if it's the first string...
|
|
514
513
|
let start = ``;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
// ...or starts on a new line if it's after a block of text, like an error
|
|
518
|
-
// or object.
|
|
514
|
+
if ( prev_line_type === `inline` && !inline_log.startsWith(`\n`) ) { start = ` `; }
|
|
515
|
+
// This is actually the end of the block-type log
|
|
519
516
|
else if ( prev_line_type === `block` ) { start = `\n`; }
|
|
520
517
|
return `${ start }${ inline_log }`;
|
|
521
518
|
}
|
|
@@ -525,6 +522,8 @@ class Log {
|
|
|
525
522
|
* a "block"-type separator at the start. Can handle circular references.
|
|
526
523
|
* Catch ANY error.
|
|
527
524
|
*
|
|
525
|
+
* @param { "inline"|"block"|null } prev_line_type - Required. The type of
|
|
526
|
+
* the previous log.
|
|
528
527
|
* @param { Object } obj_log - Required. The object to format.
|
|
529
528
|
*
|
|
530
529
|
* @returns { string } The obj as a string plus a separator
|
|
@@ -536,15 +535,15 @@ class Log {
|
|
|
536
535
|
* // { fruit: 'bananas' }
|
|
537
536
|
* */
|
|
538
537
|
let start = ``;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
538
|
+
// Avoid adding a new line to the start of the first line
|
|
539
|
+
let is_first_line = prev_line_type === null;
|
|
540
|
+
if ( !is_first_line ) { start = `\n`; }
|
|
542
541
|
|
|
543
542
|
try {
|
|
544
543
|
// Try to stringify the object.
|
|
545
544
|
return `${ start }${ this._inspect({ value: obj_log }) }`;
|
|
546
545
|
|
|
547
|
-
// Discuss: record
|
|
546
|
+
// Discuss: record the outer error?
|
|
548
547
|
} catch ( outer_error ) {
|
|
549
548
|
// If that fails, see if it has its own way of stringifying
|
|
550
549
|
try {
|