@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,63 @@
1
+ # Similarities and differences between basic field DOM structures
2
+
3
+ _Missing structure of buttons with `a` tags_
4
+
5
+ (SLIDEBAR) TEXT INPUT
6
+ <label
7
+ <input
8
+ <label (error text)
9
+
10
+ TEXTAREA
11
+ <label
12
+ <textarea
13
+ ===================================
14
+ RADIO/CHECKBOXES/YESNO RADIO
15
+ <label
16
+ <fieldset
17
+ <legend
18
+ <input
19
+ <label
20
+ <span
21
+ <span
22
+ <span
23
+ <span
24
+ <input
25
+ <label
26
+ <span
27
+ <span
28
+ <span
29
+ <span
30
+ ===================================
31
+ SELECT BOXES
32
+ <label
33
+ <select
34
+ <option
35
+ <option
36
+
37
+ (COMBOBOX) (choices are same)
38
+ <label
39
+ <input
40
+ <button
41
+ <select
42
+ <option
43
+ <option
44
+ ========================
45
+ BUTTON (missing the other button structure (with <a>))
46
+ <fieldset
47
+ <legend
48
+ <button
49
+ <span
50
+
51
+
52
+ ========================
53
+ NON-INPUTS BEFORE BOTTOM BUTTONS
54
+ <input hidden
55
+
56
+ NON-INPUTS AT THE VERY BOTTOM (hidden and such)
57
+ <input
58
+ <input
59
+ <input
60
+ <input
61
+ <input
62
+ <input
63
+ <input
@@ -0,0 +1,84 @@
1
+ const puppeteerutils = require('./puppeteer-utils');
2
+ const env_vars = require('../utils/env_vars');
3
+
4
+ const setup = async () => {
5
+ let {page, browser} = await puppeteerutils.login();
6
+ try {
7
+ await puppeteerutils.createProject(page);
8
+ await makeTestYML(page);
9
+ await puppeteerutils.installRepo(page);
10
+ await waitForPage(page); // Ensure server has really restarted
11
+ }
12
+ catch (e) {
13
+ try {
14
+ await page.screenshot({ path: `error-settingUp.jpg`, type: 'jpeg', fullPage: true });
15
+ } catch ( error ) {
16
+ console.log( `Could not take screenshot of repo setup error.\n` );
17
+ throw e;
18
+ }
19
+ }
20
+ finally {
21
+ browser.close();
22
+ }
23
+ };
24
+
25
+ const takedown = async () => {
26
+ let {page, browser} = await puppeteerutils.login();
27
+ try {
28
+ await puppeteerutils.deleteProject(page);
29
+ }
30
+ catch (e) {
31
+ console.log(e);
32
+ }
33
+ finally {
34
+ browser.close();
35
+ }
36
+ };
37
+
38
+ const makeTestYML = async (page) => {
39
+ // ONLY WORKS WITH A NEWLY CREATED PROJECT
40
+ // So that we can try to load our interview, we need this file to exist and work
41
+ // DEVELOPER: Always make sure your test.yml doesn't create an error page
42
+ let proj_name = env_vars.getProjectName();
43
+ await page.goto(`${env_vars.BASE_URL}/playground?project=${ proj_name }`, {waitUntil: 'domcontentloaded'});
44
+ }; // Ends makeTestYML
45
+
46
+ const waitForPage = async (page) => {
47
+ // Most reliable way we've found to be sure the server finishes restarting
48
+ // This counts on the fact that docassemble makes a `test.yml` file
49
+ // for every new project. This method doesn not care if the file errors.
50
+ const tries = 20;
51
+
52
+ // test.yml only exists if the 'developer' (this program) goes to the playground
53
+ const test_url = `${ env_vars.getBaseInterviewURL() }test.yml`;
54
+ console.log("\nTest file url:", test_url);
55
+ console.warn("This is meant to make sure the interview will load. Sometimes it doesn't work. If this test errors because the interview wouldn't load, you can try re-running this test.");
56
+
57
+ await page.goto(test_url, {waitUntil: 'domcontentloaded'});
58
+
59
+ // Wait till an interview can load
60
+ for (i=0; i < tries; i++) {
61
+ const element = await Promise.race([
62
+ page.$('#daMainQuestion'),
63
+ page.$x('//*[contains(text(), "History")]'),
64
+ ]);
65
+
66
+ if (element) {
67
+ console.log("Success! Repo was pulled and test.yml page loaded correctly.");
68
+ break;
69
+ } else {
70
+ let waitTime = 5 // 5 seconds
71
+ await page.screenshot({ path: './error-noInterview.jpg', type: 'jpeg', fullPage: true });
72
+ if ( i === tries ) {
73
+ console.log('Giving up.');
74
+ } else {
75
+ console.log(`Attempt ${i + 1} of ${ tries }. Will try again in ${ waitTime } seconds`);
76
+ }
77
+ await page.waitFor(waitTime * 1000); // seconds
78
+ await page.reload();
79
+ }
80
+ }
81
+ }; // Ends waitForPage()
82
+
83
+
84
+ module.exports = {setup, takedown};
@@ -0,0 +1,224 @@
1
+ const puppeteer = require('puppeteer');
2
+ const env_vars = require('../utils/env_vars');
3
+
4
+ const initPuppeteer = async () => {
5
+ const browser = await puppeteer.launch({headless: !env_vars.DEBUG});
6
+ const page = await browser.newPage();
7
+ page.setDefaultTimeout(120 * 1000);
8
+
9
+ return {'page': page, 'browser': browser};
10
+ }
11
+
12
+ const login = async () => {
13
+ const {page, browser} = await initPuppeteer();
14
+ // Login
15
+ await page.goto(`${env_vars.BASE_URL}/user/sign-in?`, {waitUntil: 'domcontentloaded'});
16
+ const emailElement = await page.$('#email');
17
+ await emailElement.type(env_vars.PLAYGROUND_EMAIL);
18
+ const passwordElement = await page.$('#password');
19
+ await passwordElement.type(env_vars.PLAYGROUND_PASSWORD);
20
+ await Promise.all([
21
+ passwordElement.press('Enter'),
22
+ page.waitForNavigation({waitUntil: 'domcontentloaded'}),
23
+ ]);
24
+ return {'page': page, 'browser': browser};
25
+ };
26
+
27
+ const navigateToManageProject = async (page) => {
28
+ // Go to manage projects page URL
29
+ await page.goto(`${env_vars.BASE_URL}/playgroundproject`, {waitUntil: 'domcontentloaded'});
30
+ };
31
+
32
+ const createProject = async (page) => {
33
+ /* Create a project with a unique name. Its name will be stored in an ignored file. */
34
+
35
+ // Check if a project with this name already exists
36
+ // If it does, come up with a new project name
37
+ await navigateToManageProject(page);
38
+ let projectExists = true;
39
+ let nameIncrementer = 0;
40
+ let base_project_name = env_vars.getProjectName(); // Will stay the same so just the number increments
41
+ let actual_project_name = base_project_name;
42
+ while ( projectExists ) {
43
+ if ( env_vars.DEBUG ) { console.log( `Trying the project name "${ actual_project_name }"` ); }
44
+ let projectLinkSelector = `[href="/playground?project=${ actual_project_name }"]`;
45
+ // This might be the 'Back to Playground' button because it remembers the last
46
+ // project you visited, even if it doesn't exist anymore
47
+ let projectButton = await page.$(projectLinkSelector);
48
+ if ( projectButton ) {
49
+ let text = await page.$eval(projectLinkSelector, elem => elem.innerText);
50
+ // If project already exists, try a new project name
51
+ if ( text === actual_project_name ) {
52
+ actual_project_name = base_project_name + nameIncrementer;
53
+ nameIncrementer++;
54
+ } else {
55
+ projectExists = false;
56
+ }
57
+ } else {
58
+ projectExists = false;
59
+ }
60
+ }
61
+
62
+ // Store the actual name of the project for later deletion
63
+ safe_project_name = env_vars.setProjectName( actual_project_name );
64
+
65
+ // Go to "Add a new project" page
66
+ await page.goto(`${env_vars.BASE_URL}/playgroundproject?new=1&project=default`, {waitUntil: 'domcontentloaded'});
67
+ // Enter new project name
68
+ const projectNameElement = await page.$('#name');
69
+ await projectNameElement.type( safe_project_name );
70
+ // Click Save
71
+ const saveButton = await page.$('[type="submit"]');
72
+ await Promise.all([
73
+ saveButton.click(),
74
+ page.waitForNavigation({waitUntil: 'domcontentloaded'}),
75
+ ]);
76
+
77
+ console.log( `Created the project "${ actual_project_name }" with no contents yet.` );
78
+ };
79
+
80
+ const deleteProject = async (page) => {
81
+ await navigateToManageProject(page);
82
+
83
+ let proj_name = env_vars.getProjectName();
84
+ console.log(`Trying to delete "${ proj_name }".`);
85
+
86
+ // Click Delete button
87
+ const deleteLink = `[href="/playgroundproject?delete=1&project=${ proj_name }"]`;
88
+ const deleteButton = await page.$(deleteLink);
89
+ if ( !deleteButton ) {
90
+ console.log(`The project "${ proj_name }" does not seem to exist.`);
91
+ return;
92
+ }
93
+ await Promise.all([
94
+ deleteButton.click(),
95
+ page.waitForNavigation({waitUntil: 'domcontentloaded'}),
96
+ ]);
97
+ // Click follow-up screen Delete confirmation button
98
+ const deleteButton2 = await page.$('[type="submit"]');
99
+ await Promise.all([
100
+ deleteButton2.click(),
101
+ page.waitForNavigation({waitUntil: 'domcontentloaded'}),
102
+ ]);
103
+
104
+ // Delete project name file if possible
105
+ env_vars.cleanUpProjectName();
106
+ console.log(`Deleted the project "${ proj_name }".`);
107
+ };
108
+
109
+ const installUrl = function () {
110
+ // Because of private repo issues (#417), can't use `github` url arg
111
+ let params_for_url = urlParams({ project: env_vars.getProjectName() });
112
+ return `${ env_vars.BASE_URL }/pullplaygroundpackage?${ params_for_url }`;
113
+ };
114
+
115
+ const urlParams = (params) => urlString = Object.keys(params).map(
116
+ (key) => `${key}=${params[key]}`
117
+ ).join('&')
118
+
119
+ const tap_pull = async ( page, elem_to_blur ) => {
120
+ await elem_to_blur.evaluate((elem) => { elem.blur(); });
121
+
122
+ try {
123
+ // Wait for the options to appear after the ajax call
124
+ let optSelector = '[value="' + env_vars.BRANCH_NAME + '"]';
125
+ console.log( `Looking for the ${ env_vars.BRANCH_NAME } branch of the repository at ${ env_vars.REPO_URL }.\n` );
126
+ await page.waitFor( optSelector ); // This is the key
127
+ console.log( `We found the ${ env_vars.BRANCH_NAME } branch of the repository at ${ env_vars.REPO_URL }. Next, we will try to pull the code.\n` );
128
+ let html = await page.$eval( optSelector, (elem) => { return elem.innerHTML }); // Why are we doing this?
129
+
130
+ // Tap 'Pull' and wait till the page has finished loading
131
+ const pullButton = await page.$('button[name=pull]');
132
+ await Promise.all([
133
+ pullButton.click(),
134
+ page.waitForNavigation({waitUntil: 'domcontentloaded'}),
135
+ ]);
136
+ } catch ( error ) {
137
+ return { succeeded: false, error: error };
138
+ }
139
+
140
+ return { succeeded: true, error: null };
141
+ }
142
+
143
+ const installRepo = async (page) => {
144
+ // await page.goto(installUrl(), {waitUntil: 'domcontentloaded'});
145
+ // `https://apps-dev.suffolklitlab.org/playgroundpackages?project=EmptyProject`
146
+ let package_url = `${ env_vars.BASE_URL }/playgroundpackages?project=${ env_vars.getProjectName() }`;
147
+ await page.goto(package_url, {waitUntil: 'domcontentloaded'});
148
+
149
+ // Without this temporary workaround, a current bug will not
150
+ // allow you to pull from a private repo if the Project is empty.
151
+ // For now, we'll make a package first so it's not empty.
152
+ let package_input = await page.$( `#file_name` );
153
+ await package_input.type( `TemporaryFix` );
154
+ let save_button = await page.$( `button[value="Save"]` );
155
+ Promise.all([
156
+ save_button.evaluate( (el) => { return el.click(); }),
157
+ page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
158
+ ]);
159
+ // Waiting for navigation isn't enough.
160
+ await page.waitFor( 1000 );
161
+ await page.waitForSelector(`#daDelete`, { visible: true });
162
+
163
+ await page.goto(installUrl(), {waitUntil: 'domcontentloaded'});
164
+ // For private repos especially, needs the format
165
+ // git@github.com:user/docassemble-package.git from original
166
+ // format https://github.com/user/docassemble-package
167
+ let user = env_vars.REPO_URL.split("/")[3];
168
+ let package = env_vars.REPO_URL.split("/")[4];
169
+ let repo_input = await page.$( `#github_url` );
170
+ await repo_input.type( `git@github.com:${ user }/${ package }.git` );
171
+
172
+ let result = await tap_pull( page, repo_input );
173
+
174
+ // if body contains "Could not read from remote repository"
175
+ let permissions_failed = await page.$eval(`body`, function (elem) {
176
+ return elem.innerText.includes("Could not read from remote repository");
177
+ });
178
+
179
+ // Try just the regular env_vars.REPO_URL
180
+ if ( permissions_failed ) {
181
+ let permissions_msg = `If this is a private repo, pay attention to the following: `
182
+ + `we tried to pull the package using the testing account's docassemble GitHub `
183
+ + `integration and the address git@github.com:${ user }/${ package }.git. It `
184
+ + `looks like the testing account is either not integrated with GitHub or the `
185
+ + `testing account GitHub account does not have permissions in this private `
186
+ + `GitHub repo. Try setting up the GitHub permissions for that docassemble `
187
+ + `testing account. If you're sure you have already done so, make an issue at `
188
+ + `https://github.com/SuffolkLITLab/ALKiln/issues.\n`
189
+ console.log( permissions_msg );
190
+ await page.goto(installUrl(), {waitUntil: 'domcontentloaded'});
191
+ repo_input = await page.$( `#github_url` ); // new page, new input element
192
+ await repo_input.type( env_vars.REPO_URL );
193
+ result = await tap_pull( page, repo_input );
194
+ }
195
+
196
+ if ( result.error ) {
197
+ // If there was a problem, either originally or with the private repo code, throw that error
198
+ throw result.error;
199
+ }
200
+ // What's a better way to handle this in the future?
201
+ // With the API, check if the user has GitHub integration set up.
202
+
203
+ // After all the pulling has been tried that needs to be tried, wait for loading to finish
204
+ try {
205
+ // server/module reload. See https://github.com/plocket/docassemble-cucumber/issues/367#issuecomment-892591770
206
+ // The selector find the 'Package Name' field on the 'Packages' page
207
+ await page.waitForSelector(`#file_name`, { visible: true })
208
+ } catch ( error ) {
209
+ console.log( `It looks like pulling the ${ env_vars.BRANCH_NAME } branch of the repository at ${ env_vars.REPO_URL } failed. Download the error screenshots in the GitHub Action artifacts to see if it tells you more.\n` );
210
+ throw error ;
211
+ }
212
+
213
+ console.log( `Pulled the GitHub branch "${ env_vars.BRANCH_NAME }" into the project "${ env_vars.getProjectName() }".\n` );
214
+ }
215
+
216
+
217
+
218
+ module.exports = {
219
+ login: login,
220
+ createProject: createProject,
221
+ deleteProject: deleteProject,
222
+ installRepo: installRepo,
223
+ initPuppeteer: initPuppeteer,
224
+ };
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ const install_branch = require('./install-branch.js');
4
+ install_branch.setup();
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ const install_branch = require('./install-branch.js');
4
+ install_branch.takedown();
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ require('./world');
2
+ require('./steps.js');