@suffolklitlab/alkiln 3.0.9

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.
Files changed (40) hide show
  1. package/.github/workflows/test_tests.yml +77 -0
  2. package/CHANGELOG.md +381 -0
  3. package/README.md +18 -0
  4. package/action.yml +158 -0
  5. package/lib/DOM.md.html +63 -0
  6. package/lib/github_integration/install-branch.js +84 -0
  7. package/lib/github_integration/puppeteer-utils.js +224 -0
  8. package/lib/github_integration/setup.js +4 -0
  9. package/lib/github_integration/takedown.js +4 -0
  10. package/lib/index.js +2 -0
  11. package/lib/scope.js +1632 -0
  12. package/lib/steps.js +878 -0
  13. package/lib/utils/env_vars.js +173 -0
  14. package/lib/utils/langs.js +51 -0
  15. package/lib/world.js +10 -0
  16. package/package.json +53 -0
  17. package/tests/features/assembly_line.feature +43 -0
  18. package/tests/features/establishing_steps.feature +22 -0
  19. package/tests/features/example_test.feature +11 -0
  20. package/tests/features/interactive_steps.feature +85 -0
  21. package/tests/features/observation_steps.feature +97 -0
  22. package/tests/features/reports.feature +482 -0
  23. package/tests/features/some_png_1.png +0 -0
  24. package/tests/features/some_png_2.png +0 -0
  25. package/tests/features/story_table_formats.feature +77 -0
  26. package/tests/features/story_tables.feature +88 -0
  27. package/tests/unit_tests/ensureSpecialRows.test.js +34 -0
  28. package/tests/unit_tests/env_vars.test.js +53 -0
  29. package/tests/unit_tests/fields.fixtures.js +904 -0
  30. package/tests/unit_tests/getAllFields.test.js +142 -0
  31. package/tests/unit_tests/getMatchingRows.test.js +261 -0
  32. package/tests/unit_tests/getSafeScenarioBaseFilename.fixtures.js +12 -0
  33. package/tests/unit_tests/getSafeScenarioBaseFilename.test.js +62 -0
  34. package/tests/unit_tests/getSafeScenarioFilename.test.js +83 -0
  35. package/tests/unit_tests/html.fixtures.js +861 -0
  36. package/tests/unit_tests/matches.fixtures.js +168 -0
  37. package/tests/unit_tests/names.fixtures.js +35 -0
  38. package/tests/unit_tests/normalizeTable.test.js +22 -0
  39. package/tests/unit_tests/special_rows.fixtures.js +101 -0
  40. package/tests/unit_tests/tables.fixtures.js +428 -0
@@ -0,0 +1,173 @@
1
+ const fs = require('fs');
2
+
3
+ require('dotenv').config();
4
+
5
+ /**
6
+ * This code assumes that this module is run as part of a GitHub Action. If the
7
+ * customer is running it as a CLI tool, then they are on their own.
8
+ */
9
+
10
+ /* This file provides both `process.env` variables and other variables
11
+ * that are needed in a similar way, but are not `process.env` variables themselves,
12
+ * though they are based on the `process.env` variables.
13
+ *
14
+ * The alternative seems to be some `process.env` type variables and some `foo`
15
+ * variables without great ways to differentiate between them. That or duplicate
16
+ * fiddly work in multiple files.
17
+ *
18
+ * The need to get and set project name, etc. makes me wonder if we should create
19
+ * a class with getters and setters, but general wisdom is that it's a bad idea and
20
+ * maybe it comes to mind because I'm used to asking for `env_vars.CAPS_CASE`. Another
21
+ * alternative is `env_vars.get( 'CAPS_CASE' )` and `env_vars.set( 'CAPS_CASE', 'value' )`
22
+ */
23
+
24
+ // DEBUG is not required.
25
+
26
+ const env_vars = {
27
+ PLAYGROUND_ID: process.env.PLAYGROUND_ID,
28
+ PLAYGROUND_EMAIL: process.env.PLAYGROUND_EMAIL,
29
+ PLAYGROUND_PASSWORD: process.env.PLAYGROUND_PASSWORD,
30
+ BASE_URL: process.env.BASE_URL,
31
+ REPO_URL: process.env.REPO_URL,
32
+ DEBUG: process.env.DEBUG,
33
+ };
34
+
35
+ /**
36
+ * For each field in env_vars, check if the value is defined in an environment
37
+ * variable, and throw an exception if it is not.
38
+ *
39
+ * @throws ReferenceError with a message for all missing variables.
40
+ */
41
+ env_vars.validateEnvironment = function() {
42
+
43
+ // We are checking for BASE_URL, but we are referring it as SERVER_URL as that
44
+ // is the name used in GitHub secrets, which is what most devs will be using.
45
+
46
+ let errorMessage = '';
47
+
48
+ // Since this code will most often be run in GitHub, I'm going to build a single error message that outlines all missing variables. Otherwise, a developer
49
+ // will have to run their pipeline an incredible number of times to discover all the missing variables.
50
+ if (!process.env.PLAYGROUND_ID)
51
+ {
52
+ errorMessage += '\nThe PLAYGROUND_ID GitHub secret must be defined. The PLAYGROUND_ID is the ID of the Docassemble suite to run.'
53
+ }
54
+ if (!process.env.PLAYGROUND_EMAIL)
55
+ {
56
+ errorMessage += '\nThe PLAYGROUND_EMAIL GitHub secret must be defined. The PLAYGROUND_EMAIL is the e-mail address of the test user to run the Docassemble suite.'
57
+ }
58
+ if (!process.env.PLAYGROUND_PASSWORD)
59
+ {
60
+ errorMessage += '\nThe PLAYGROUND_PASSWORD GitHub secret must be defined. The PLAYGROUND_PASSWORD is your test user\'s password for the test suite.'
61
+ }
62
+ if (!process.env.BASE_URL)
63
+ {
64
+ errorMessage += `\nThe SERVER_URL GitHub secret must be defined. The SERVER_URL is the URL of the Docassemble Server where the tests should be run.`;
65
+ }
66
+ if (!process.env.REPO_URL)
67
+ {
68
+ errorMessage += `\nThe REPO_URL GitHub secret must be defined. The REPO_URL is the URL to the Git repo whose Actions are being run.`;
69
+ }
70
+
71
+ errorMessage = errorMessage.trim();
72
+
73
+ if (errorMessage !== '')
74
+ throw new ReferenceError(errorMessage);
75
+ };
76
+
77
+ env_vars.validateEnvironment();
78
+
79
+ // Path could be 'refs/branch_name' or 'refs/head/branch_name' and maybe other things
80
+ // Assume branch name is last part of branch path
81
+ if ( process.env.BRANCH_PATH ) {
82
+ let split = process.env.BRANCH_PATH.split('/');
83
+ env_vars.BRANCH_NAME = split[ split.length - 1 ];
84
+ } else {
85
+ env_vars.BRANCH_NAME = 'test'
86
+ }
87
+
88
+ // ---- Project name ----
89
+ let no_starting_digit = ( 'testing' + env_vars.BRANCH_NAME ).replace( /^[0-9]+/i, '' );
90
+ env_vars.PROJECT_BASE_NAME = no_starting_digit.replace( /[^A-Za-z0-9]+/gi, '' );
91
+
92
+ const project_name_file_path = '_al_test_project_name.json';
93
+ env_vars.setProjectName = function ( value ) {
94
+ // As long as it's a string, it can be made safe for a project name
95
+ if ( typeof value !== 'string' ) { throw new TypeError(`The project's name must start as a string. It was ${ value }.`); }
96
+ // Make the name safe for a project name
97
+ let no_starting_digit = value.replace( /^[0-9]+/i, '' );
98
+ let alphanumeric = no_starting_digit.replace( /[^A-Za-z0-9]+/gi, '' );
99
+ // Create or modify a file to store the actual name of the project
100
+ // for later deletion as internal values and context get destroyed before
101
+ // the takedown command that comes later. It's a string, not a list, to avoid
102
+ // the list getting out of date and deleting another test's project.
103
+ fs.writeFileSync( project_name_file_path, JSON.stringify( alphanumeric ));
104
+ console.log( `Saved new project name: ${ alphanumeric }` );
105
+
106
+ return alphanumeric;
107
+ }
108
+
109
+ env_vars.getProjectName = function () {
110
+ /* Get the stored name of the project that was created on the
111
+ * server - the base project name might have been adjusted to
112
+ * avoid overwriting existing project. */
113
+ if ( env_vars.DEBUG ) { console.trace( `Getting project name` ); }
114
+ let actual_proj_name = null;
115
+ // Avoid race coditions possibly caused by checking for existence first
116
+ try {
117
+ actual_proj_name = JSON.parse( fs.readFileSync( project_name_file_path ));
118
+ if ( env_vars.DEBUG ) { console.log( `Project name from file is ${ actual_proj_name }.` ); }
119
+ } catch ( error ) {} // Do nothing
120
+
121
+ // If no such file existed or value was invalid, set up the file with a basic name
122
+ if ( !actual_proj_name ) {
123
+ actual_proj_name = env_vars.setProjectName( env_vars.PROJECT_BASE_NAME );
124
+ }
125
+
126
+ if ( env_vars.DEBUG ) { console.log( `Processed project name is "${ actual_proj_name }"` ); }
127
+ return actual_proj_name;
128
+ };
129
+
130
+ env_vars.cleanUpProjectName = function () {
131
+ /* Delete stored name of project */
132
+ // Avoid race coditions possibly caused by checking for existence first
133
+ // Delete file if possible
134
+ try { fs.unlinkSync( project_name_file_path ); }
135
+ catch ( error ) {
136
+ console.warn(`After trying to delete the project created for the test, the file containing the name of project did not exist. ${ error }`)
137
+ }
138
+ };
139
+
140
+ // Notes: We were using `new_session=1` in the url args, but it wasn't
141
+ // always working. The idea was to save each session so we could look
142
+ // back on it if needed. Aside from the fact that you usually need to log in
143
+ // to make the sessions retrievable (though if you're using `interview_list()`
144
+ // you can get them anyway), it also has inconsistent behavior when
145
+ // `sessions are unique`` or `temporary session` or use `interview_list()`
146
+ // and/or respond immediately with a `command()`. Three ways to avoid this:
147
+ // 1. x Start a new browser instead of just a new page each time. Lengthens the tests.
148
+ // 1. √ Visit `/user/sign-out` (needed even if the user is not logged in)
149
+ // 1. x (seems to be causing a 'Unable to locate interview session' error
150
+ // instead) Use `reset=2` instead , which will not save the previous session at all. It
151
+ // Will not just restart any existing interview sessions, it will also
152
+ // clear out the browser session's memory of what other interviews the user has
153
+ // visited in the same browser session.
154
+ env_vars.getBaseInterviewURL = function () {
155
+ /* Return the part of the interview URL that comes before the filename.
156
+ * Depends on the project name, which may have been changed. */
157
+ let new_session = `/interview?new_session=1&i=docassemble.playground`;
158
+ return `${ env_vars.BASE_URL }${ new_session }${ env_vars.PLAYGROUND_ID }${ env_vars.getProjectName() }%3A`;
159
+ };
160
+ env_vars.BASE_INTERVIEW_URL = env_vars.getBaseInterviewURL();
161
+
162
+
163
+ // These are actually the words that are on the buttons or links
164
+ // that change the language of the interview. Have not yet detected
165
+ // another distinguishing feature
166
+ if ( process.env.EXTRA_LANGUAGES && process.env.EXTRA_LANGUAGES !== 'default' ) {
167
+ env_vars.EXTRA_LANGUAGES = process.env.EXTRA_LANGUAGES.split(', ');
168
+ } else {
169
+ env_vars.EXTRA_LANGUAGES = [];
170
+ }
171
+
172
+
173
+ module.exports = env_vars;
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ let _path = require('path');
4
+ let fs = require('fs');
5
+ let glob = require('glob');
6
+
7
+ let env_vars = require('./env_vars');
8
+
9
+ // Get the glob filepaths to find all the test files
10
+ let dir_root = process.env.INIT_CWD; // Path to root of project
11
+ let test_glob = process.argv[2]; // String of glob (regex for filepaths)
12
+ let filepaths = glob.sync( _path.resolve( dir_root, test_glob ) );
13
+ let flag = '_lang_';
14
+
15
+ let langs = env_vars.EXTRA_LANGUAGES;
16
+
17
+ // Generate new language files
18
+ for ( let full_path of filepaths ) { // Handle old language files before creating new ones.
19
+
20
+ // Don't apply this to the example test that this package always provides
21
+ if ( full_path.includes( 'example_test.feature' )) { continue; }
22
+
23
+ // Delete alredy existing non-default language files and skip the rest of this. For example,
24
+ // if 3 languages were tested previously and now only two are being tested.
25
+ if ( full_path.includes( flag )) {
26
+ fs.unlinkSync( full_path );
27
+ continue;
28
+ }
29
+
30
+ for ( let lang of langs ) { // Create new language files
31
+ // Get and change the text from the file to add the language to it
32
+ let data = fs.readFileSync( full_path, 'utf8' );
33
+ let result = data.replace(/(Given I start the interview.*$)/gm, `$1 in lang "${ lang }"`);
34
+
35
+ // Unicode category for NOT language characters \P{L}
36
+ const lang_escaped = lang.replace(/(\P{L})+/gu, '_');
37
+
38
+ // append generated language tags to existing Feature tags if they exist
39
+ const tags_regex = /(?:(?<tags>@.*)\n)?(?=Feature:)/;
40
+ result = result.replace(tags_regex, (_, tags) => `${tags ? tags + ' ' : ''}@generated @lang @${ lang_escaped }\n`);
41
+
42
+ // Save the file to the same folder with a new name indicating the language
43
+ let test_dir = _path.dirname( full_path );
44
+ // Put it alphabetically next to its matching tests by starting with the same name (discuss)
45
+ let orig_filename = _path.basename( full_path ).replace('.feature', '');
46
+ let filename = `${ orig_filename }${ flag }${ lang_escaped }.feature`;
47
+ let fullPath = _path.resolve( test_dir, filename );
48
+ // Always overwrite previous versions of the file if they exist
49
+ fs.writeFileSync(fullPath, result, { encoding:'utf8', flag:'w' });
50
+ }
51
+ }
package/lib/world.js ADDED
@@ -0,0 +1,10 @@
1
+ const { setWorldConstructor } = require('cucumber');
2
+ const puppeteer = require('puppeteer');
3
+ const scope = require('./scope');
4
+
5
+ const World = function() {
6
+ scope.driver = puppeteer;
7
+ scope.context = {};
8
+ };
9
+
10
+ setWorldConstructor(World);
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@suffolklitlab/alkiln",
3
+ "version": "3.0.9",
4
+ "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
+ "main": "lib/index.js",
6
+ "scripts": {
7
+ "test": "npm run unit && npm run langs_setup && npm run cucumber -- $@",
8
+ "cucumber_base": "./node_modules/.bin/cucumber-js --require ./lib/index.js",
9
+ "cucumber": "run_cucumber(){ npm run cucumber_base -- \"$@\" tests/features/*.feature; }; run_cucumber",
10
+ "langs": "npm run langs_setup && ./node_modules/.bin/cucumber-js --require ./lib/index.js tests/features/*_lang*.feature",
11
+ "langs_setup": "./lib/utils/langs.js 'tests/features/*.feature'",
12
+ "langs_local": "npm run langs_setup && npm run local",
13
+ "local": "npm run setup && npm run cucumber; npm run takedown",
14
+ "setup": "./lib/github_integration/setup.js",
15
+ "takedown": "./lib/github_integration/takedown.js",
16
+ "unit": "mocha tests/unit_tests/*.test.*"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/SuffolkLITLab/alkiln.git"
21
+ },
22
+ "keywords": [
23
+ "docassemble",
24
+ "cucumber",
25
+ "puppeteer",
26
+ "automated testing",
27
+ "integrated testing",
28
+ "chai"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "bugs": {
33
+ "url": "https://github.com/SuffolkLITLab/alkiln/issues"
34
+ },
35
+ "homepage": "https://github.com/SuffolkLITLab/alkiln#readme",
36
+ "dependencies": {
37
+ "chai": "^4.2.0",
38
+ "cheerio": "^1.0.0-rc.5",
39
+ "colors": "1.4.0",
40
+ "cucumber": "^6.0.5",
41
+ "dotenv": "^8.2.0",
42
+ "mocha": "^7.2.0",
43
+ "puppeteer": "^3.1.0",
44
+ "sanitize-filename": "^1.6.3",
45
+ "uuid": "^8.3.2"
46
+ },
47
+ "bin": {
48
+ "da-setup": "./lib/github_integration/setup.js",
49
+ "da-takedown": "./lib/github_integration/takedown.js",
50
+ "da-cucumber": "./lib/index.js",
51
+ "da-langs": "./lib/utils/langs.js"
52
+ }
53
+ }
@@ -0,0 +1,43 @@
1
+ @assembly_line
2
+ Feature: Assembly Line package-specific Steps
3
+
4
+ # In tag names, 'al' is for 'assembly line'
5
+
6
+ # Note: the first test for this suite is the first test in this file and we're having timeout issues, so we're trying to give the server longer to load.
7
+
8
+ @fast @al1
9
+ Scenario: I have two name parts
10
+ Given the max secs for each step is 120
11
+   Given I start the interview at "AL_tests"
12
+ And I set the name of "users[0]" to "Uli User"
13
+ And I tap to continue
14
+ And I set the address of "users[0]" to "120 Tremont Street, Unit 2, Boston, MA 02108"
15
+ And I tap to continue
16
+ And I set the name of "users[1]" to "Uli2 User2"
17
+ And I set the var "users[i].proxy_var" to "Mixed proxy var step test"
18
+ And I tap to continue
19
+ Then the question id should be "end"
20
+
21
+ @fast @al2
22
+ Scenario: I have three name parts
23
+   Given I start the interview at "AL_tests"
24
+ And I set the name of "users[0]" to "Uli Udo User"
25
+ And I tap to continue
26
+ And I set the address of "users[0]" to "120 Tremont Street, Unit 2, Boston, MA 02108"
27
+ And I tap to continue
28
+ And I set the name of "users[1]" to "Uli2 User2"
29
+ And I set the var "users[i].proxy_var" to "Mixed proxy var step test"
30
+ And I tap to continue
31
+ Then the question id should be "end"
32
+
33
+ @fast @al3
34
+ Scenario: I have four name parts
35
+   Given I start the interview at "AL_tests"
36
+ And I set the name of "users[0]" to "Uli Udo User Sr"
37
+ And I tap to continue
38
+ And I set the address of "users[0]" to "120 Tremont Street, Unit 2, Boston, MA 02108"
39
+ And I tap to continue
40
+ And I set the name of "users[1]" to "Uli2 User2"
41
+ And I set the var "users[i].proxy_var" to "Mixed proxy var step test"
42
+ And I tap to continue
43
+ Then the question id should be "end"
@@ -0,0 +1,22 @@
1
+ @establishing
2
+ Feature: Establishing steps
3
+
4
+ Steps that establish things about the state of the test
5
+ # In tag names, 'e' is for 'establishing'
6
+
7
+ @slow @e1
8
+ Scenario: I am able to set a custom wait time that allows steps to run longer than the half minute default
9
+ Given I start the interview at "all_tests"
10
+ And the max seconds for each step in this scenario is 40
11
+ And I wait 35 seconds
12
+
13
+ @slow @e2
14
+ Scenario: I am able to set a custom wait time before an interview has been loaded
15
+ Given the max seconds for each step in this scenario is 40
16
+ And I start the interview at "all_tests"
17
+ And I wait 35 seconds
18
+
19
+ @fast @e3 @urlargs
20
+ Scenario: Interview name includes url args
21
+   Given I start the interview at "url_args.yml&from=theinternets&random=zoo"
22
+   Then I should see the phrase "zoo"
@@ -0,0 +1,11 @@
1
+ @example_feature_tag
2
+ Feature: An example test
3
+
4
+ The tests in this file should cover
5
+ - [x] Provide a template people can use to make their own tests.
6
+ - [ ] Direct people to more documentation.
7
+ - [ ] Find the ultimate question to life, the universe, and everything.
8
+
9
+ @example_scenario_tag
10
+ Scenario: Server finishes reloading
11
+ Given I start the interview at "test"
@@ -0,0 +1,85 @@
1
+ @interactive
2
+ Feature: Interactive steps
3
+
4
+ # In tag names, 'i' is for 'interactive'
5
+
6
+ @slow @i1
7
+ Scenario: I set text-type values
8
+   Given I start the interview at "all_tests"
9
+ Then the question id should be "upload files"
10
+ When I set the var "upload_files_visible" to "some_png_1.png, some_png_2.png"
11
+ And I set the var "show_upload" to "True"
12
+ And I upload "some_png_2.png,some_png_1.png" to "upload_files_hidden"
13
+ And I tap to continue
14
+ # Next page
15
+ Then the question id should be "group of complex fields"
16
+ And I set the var "double_quote_dict['double_quote_key']['dq_two']" to "true"
17
+ And I set the var "single_quote_dict['single_quote_key']['sq_two']" to "true"
18
+ And I tap to continue
19
+ Then the question id should be "direct standard fields"
20
+ # Next page
21
+ When I set the var "checkboxes_yesno" to "True"
22
+ And I set the var "checkboxes_other['checkbox_other_opt_1']" to "true"
23
+ And I set the var "dropdown_test" to "dropdown_opt_2"
24
+ And I set the var "radio_yesno" to "False"
25
+ And I set the var "radio_other" to "radio_other_opt_3"
26
+ And I set the var "text_input" to "Regular text input field value"
27
+ And I set the var "textarea" to "Multiline text\narea value"
28
+ And I set the var "date_input" to "today - 1"
29
+ When I set the var "direct_standard_fields" to "True"
30
+ # Next page
31
+ Then the question id should be "showifs"
32
+ When I set the var "show_2" to "True"
33
+ And I set the var "show_3" to "True"
34
+ And I set the var "showif_checkbox_yesno" to "true"
35
+ And I set the var "showif_checkboxes_other['showif_checkboxes_nota_2']" to "true"
36
+ And I set the var "showif_checkboxes_other['showif_checkboxes_nota_3']" to "true"
37
+ And I set the var "showif_yesnoradio" to "True"
38
+ And I set the var "showif_radio_other" to "showif_radio_multi_2"
39
+ And I set the var "showif_text_input" to "Show if text input value"
40
+ And I set the var "showif_textarea" to "Show if\nmultiline text\narea value"
41
+ And I set the var "showif_dropdown" to "showif_dropdown_1"
42
+ When I tap to continue
43
+ # Next page
44
+ Then the question id should be "buttons yesnomaybe"
45
+ When I set the var "buttons_yesnomaybe" to "None"
46
+ # Next page
47
+ Then the question id should be "buttons other"
48
+ When I set the var "buttons_other" to "button_3"
49
+ # Next page
50
+ Then the question id should be "button continue"
51
+ When I set the var "button_continue" to "True"
52
+ # Next page
53
+ Then the question id should be "screen features"
54
+ When I tap to continue
55
+ # Next page
56
+ Then the question id should be "proxy vars"
57
+ When I set the var "x[i].name.first" to "Proxyname1"
58
+ And I tap to continue
59
+ # Next page
60
+ Then the question id should be "is there another generic"
61
+ When I set the var "x.there_is_another" to "True"
62
+ # Next page
63
+ Then the question id should be "proxy vars"
64
+ When I set the var "x[i].name.first" to "Proxyname2"
65
+ And I tap to continue
66
+ # Next page
67
+ Then the question id should be "is there another generic"
68
+ When I set the var "x.there_is_another" to "False"
69
+ # Next page
70
+ Then the question id should be "first signature"
71
+ When I sign
72
+ And I tap to continue
73
+ # Next page
74
+ Then the question id should be "second signature"
75
+ When I sign
76
+ And I tap to continue
77
+ # Next page
78
+ Then the question id should be "simple doc"
79
+ When I download "simple-doc.pdf"
80
+ And I tap to continue
81
+ # Next page
82
+ Then the question id should be "button event action"
83
+ And I tap to continue
84
+ # Next page
85
+ Then the question id should be "the end"
@@ -0,0 +1,97 @@
1
+ @observation
2
+ Feature: Observational steps
3
+
4
+ # In tag names, 'o' is for 'observational'
5
+
6
+ @fast @o1
7
+ Scenario: I observe things on a single page when I arrive
8
+   Given I start the interview at "all_tests"
9
+ And I tap to continue
10
+ Then the question id should be "group of complex fields"
11
+ And I should see the phrase "Some complex fields"
12
+ And I SHOULD see the phrase "Some complex fields"
13
+ And I should not see the phrase "zzzzzzz"
14
+ And I should NOT see the phrase "zzzzzzz"
15
+ And an element should have the id "daform"
16
+
17
+ @fast @o2
18
+ Scenario: I see user errors.
19
+ # They're everywhere. Some don't even know they are errors.
20
+   Given I start the interview at "all_tests"
21
+ And I tap to continue
22
+ And I tap to continue
23
+ And I tap to continue
24
+ Then I can't continue
25
+ And I will be told an answer is invalid
26
+
27
+ @fast @o3
28
+ Scenario: I can include .yml in the filename
29
+   Given I start the interview at "all_tests.yml"
30
+
31
+ @slow @o4
32
+ Scenario: I check navigation
33
+   Given I start the interview at "all_tests"
34
+ And I tap to continue
35
+   And I get to "showifs" with this data:
36
+ | var | value | trigger |
37
+ | double_quote_dict["double_quote_key"]['dq_two'] | true | |
38
+ | checkboxes_other['checkbox_other_opt_1'] | true | |
39
+ | dropdown_test | dropdown_opt_2 | |
40
+ | radio_yesno | False | false |
41
+ | radio_other | radio_other_opt_3 | |
42
+ | single_quote_dict['single_quote_key']['sq_two'] | true | |
43
+ | text_input | Regular text input field value | |
44
+ | textarea | Multiline text\narea value | |
45
+ | date_input | today | |
46
+ Then I arrive at the next page
47
+   Then I get to "screen features" with this data:
48
+ | var | value | trigger |
49
+ | button_continue | True | |
50
+ | buttons_other | button_2 | |
51
+ | buttons_yesnomaybe | True | |
52
+ And I should see the link to "http://ecosia.org/"
53
+
54
+ @fast @o4
55
+ Scenario: Test "Then I don't continue" with a single quote
56
+   Given I start the interview at "all_tests"
57
+ And I tap to continue
58
+ And I tap to continue
59
+ And I tap to continue
60
+ Then I don't continue
61
+ And I will be told an answer is invalid
62
+
63
+ @fast @o5
64
+ Scenario: Test "Then I cannot continue"
65
+   Given I start the interview at "all_tests"
66
+ And I tap to continue
67
+ And I tap to continue
68
+ And I tap to continue
69
+ Then I cannot continue
70
+ And I will be told an answer is invalid
71
+
72
+ @fast @o6
73
+ Scenario: Test "Then I do not continue"
74
+   Given I start the interview at "all_tests"
75
+ And I tap to continue
76
+ And I tap to continue
77
+ And I tap to continue
78
+ Then I do not continue
79
+ And I will be told an answer is invalid
80
+
81
+ @fast @o7
82
+ Scenario: Test "Then I can’t continue" with an apostrophe
83
+   Given I start the interview at "all_tests"
84
+ And I tap to continue
85
+ And I tap to continue
86
+ And I tap to continue
87
+ Then I can’t continue
88
+ And I will be told an answer is invalid
89
+
90
+ @fast @o8
91
+ Scenario: Test "Then I don’t continue" with an apostrophe
92
+   Given I start the interview at "all_tests"
93
+ And I tap to continue
94
+ And I tap to continue
95
+ And I tap to continue
96
+ Then I don’t continue
97
+ And I will be told an answer is invalid