@suffolklitlab/alkiln 4.11.6 → 5.0.0-experimental2

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/lib/scope.js CHANGED
@@ -2,6 +2,7 @@ const { expect } = require('chai');
2
2
  const cheerio = require('cheerio');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
+ const { globSync } = require('glob');
5
6
  const { AxePuppeteer } = require('@axe-core/puppeteer');
6
7
  const safe_filename = require("sanitize-filename");
7
8
  // "en" is faster to import: https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
@@ -1528,7 +1529,10 @@ module.exports = {
1528
1529
  let row = matching_input_rows[ matching_input_rows.length - 1 ];
1529
1530
  let answer = row.value;
1530
1531
 
1531
- await scope.funnel_the_answer( scope, { handle, answer, field });
1532
+ // Will implement `was_used` in cases as they come along. For now, this is
1533
+ // necessary for file uploads to be as flexible as any other Story Table row.
1534
+ let was_used = await scope.funnel_the_answer( scope, { handle, answer, field });
1535
+ if ( was_used === false ) { row = null; }
1532
1536
 
1533
1537
  return row;
1534
1538
  }, // Ends scope.setVariable()
@@ -1590,16 +1594,18 @@ module.exports = {
1590
1594
  custom_datatype = await scope.get_custom_datatype( scope, { handle });
1591
1595
  }
1592
1596
 
1597
+ let row_was_used = true;
1593
1598
  // If there is a custom datatype function available, use it to set the field
1594
1599
  if ( scope.setCustomDatatype[ custom_datatype ] !== undefined ) {
1595
1600
  // Set da package custom fields. TODO: find `custom_handle` in the setter?
1596
1601
  let custom_handle = await handle.evaluateHandle(( elem )=> { return elem.closest( `.da-form-group` ) });
1597
- await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1602
+ row_was_used = await scope.setCustomDatatype[ custom_datatype ]( scope, { handle: custom_handle, answer });
1598
1603
  } else {
1599
1604
  // If no custom datatype, set a regular field
1600
- await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1605
+ row_was_used = await scope.enter_answer[ field.tag ]( scope, { handle, answer });
1601
1606
  }
1602
1607
 
1608
+ return row_was_used;
1603
1609
  }, // Ends scope.funnel_the_answer()
1604
1610
 
1605
1611
  // Handle setting values for da custom datatypes. E.g. `da-field-container-datatype-BirthDate`
@@ -1660,10 +1666,14 @@ module.exports = {
1660
1666
  }, // ends setCustomDatatype {}
1661
1667
 
1662
1668
  enter_answer: {
1663
- button: async function ( scope, { handle, answer }) { await scope.tapElement( scope, handle ); },
1669
+ button: async function ( scope, { handle, answer }) {
1670
+ await scope.tapElement( scope, handle );
1671
+ return true;
1672
+ },
1664
1673
  textarea: async function ( scope, { handle, answer }) {
1665
1674
  await scope.setText( scope, { handle, answer });
1666
1675
  await scope.afterStep(scope); // No showifs for this one?
1676
+ return true;
1667
1677
  },
1668
1678
  select: async function ( scope, { handle, answer }) {
1669
1679
  // Note: I don't remember what's really going on in here.
@@ -1692,13 +1702,16 @@ module.exports = {
1692
1702
  }
1693
1703
 
1694
1704
  await scope.afterStep(scope, { waitForShowIf: true });
1705
+ return true;
1695
1706
  },
1696
1707
  canvas: async function ( scope, { handle, answer }) {
1697
1708
  await scope.draw_signature( scope );
1698
1709
  await scope.afterStep( scope );
1710
+ return true;
1699
1711
  },
1700
1712
  input: async function ( scope, { handle, answer }) {
1701
1713
  /* Set value of some `input` element to the given value. */
1714
+ let row_was_used = true;
1702
1715
 
1703
1716
  let type = await handle.evaluate( elem => { return elem.getAttribute('type'); });
1704
1717
  if ( type === `radio` || type === `checkbox` ) {
@@ -1717,7 +1730,7 @@ module.exports = {
1717
1730
  await scope.afterStep(scope, { waitForShowIf: true });
1718
1731
 
1719
1732
  } else if ( type === `file` ) {
1720
- await scope.uploadFiles( scope, { handle, answer });
1733
+ row_was_used = await scope.uploadFiles( scope, { handle, answer });
1721
1734
  await scope.afterStep(scope, { waitForShowIf: true });
1722
1735
 
1723
1736
  } else if (type == `date`) {
@@ -1746,6 +1759,8 @@ module.exports = {
1746
1759
  await scope.setText( scope, { handle, answer });
1747
1760
  await scope.afterStep(scope, { waitForShowIf: true });
1748
1761
  }
1762
+
1763
+ return row_was_used;
1749
1764
  }, // Ends scope.enter_answer.input()
1750
1765
  hidden: async function ( scope, { handle, answer }) {
1751
1766
  // Do nothing. The only way the code should get here is if
@@ -1762,80 +1777,57 @@ module.exports = {
1762
1777
  uploadFiles: async function ( scope, { handle, answer }) {
1763
1778
  /* Try to upload one or more files to a file-type input field.
1764
1779
  * Does not submit, so no danger of timeout with server reload.
1780
+ *
1781
+ * Returns whether the row was used or not.
1765
1782
  */
1783
+ console.log( process.cwd() );
1766
1784
 
1767
- // Tests should be one place: docassemble/*/data/sources, where * is the only folder in `docassemble`.
1768
- // Makes an absolute path from the folder in which `npm run` was used plus our internal folders
1769
- let docassemble_pkg_path = `${ process.cwd() }/docassemble`;
1770
- let subpackage_name = session_vars.get_package_name().substring(12);
1771
- let pkg_paths = [];
1772
- try {
1773
- fs.accessSync(docassemble_pkg_path);
1774
- // https://stackoverflow.com/a/24594123/11416267
1775
- let dirs = fs.readdirSync(docassemble_pkg_path, {withFileTypes: true}).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
1776
- if (dirs.length < 1) {
1777
- let msg = `You can store files to upload in 'docassemble/${ subpackage_name }/data/source'. These tests could not find a folder like that starting at ${ process.cwd() }.`;
1778
- await scope.addToReport( scope, { type: `warning`, value: msg});
1779
- throw Error(msg); // Most people will want to know the sources folder path;
1780
- }
1781
- for (let dir of dirs) {
1782
- pkg_paths.push(`${docassemble_pkg_path}/${dir}`);
1783
- }
1784
- } catch ( error_da ) {
1785
- let msg = `You can store files to upload in 'docassemble/${ subpackage_name }/data/source'. ` +
1786
- `These tests could not find a folder like that. The current directory is ` +
1787
- ` ${ process.cwd() }; is that in the top level folder of your docassemble package? (${ session_vars.get_package_name() })?`;
1788
- await scope.addToReport( scope, { type: `warning`, value: msg});
1789
- throw error_da; // Most people will want to know the sources folder path;
1790
- }
1791
-
1792
- let chosen_dir = null;
1793
- let err_paths = [];
1794
- for (let pkg_path of pkg_paths) {
1795
- let dir = `${ pkg_path }/data/sources`;
1796
- try {
1797
- fs.accessSync(dir);
1798
- chosen_dir = dir;
1799
- break;
1800
- } catch ( error_full ) {
1801
- err_paths.push(`'${dir}'`);
1802
- }
1803
- }
1804
- if (chosen_dir === null) {
1805
- let msg = `You can store files to upload in ${err_paths.join(", or ")}. These tests could not find any of those folders.`;
1806
- await scope.addToReport( scope, { type: `warning`, value: msg });
1807
- throw Error(msg); // Most people will want to know the sources folder path;
1785
+ // First make sure the list of files is of the correct format
1786
+ // Allow a typo where someone forgot to include a space after a comma
1787
+ let uploads_names = answer.split(`,`);
1788
+ if ( uploads_names.length === 0 ) {
1789
+ await scope.addToReport( scope, {
1790
+ type: `error`,
1791
+ value: `Your list of files to upload is empty.`
1792
+ });
1793
+ return false;
1808
1794
  }
1809
1795
 
1810
- if (pkg_paths.length > 1 && pkg_paths.length - err_paths.length > 1) {
1811
- await scope.addToReport( scope, { type: `warning`, value: `You have multiple folders in the 'docassemble' folder (${ pkg_paths }). These tests will use the first folder with a "data/sources" subfolder (${chosen_dir}).` });
1796
+ // Make sure sources directory exists
1797
+ let dirs = globSync( `**/data/sources` );
1798
+ if ( dirs.length === 0 ) {
1799
+ await scope.addToReport( scope, {
1800
+ type: `warning`,
1801
+ value: `ALKiln couldn't find a "sources" directory in "${ process.cwd() }" where it could get files to upload.`
1802
+ });
1803
+ return false;
1812
1804
  }
1805
+ let dir = dirs[0];
1813
1806
 
1814
- // Allow a typo where someone forgot to include a space after a comma
1815
- let file_names = answer.split(`,`);
1816
-
1817
- // If a file exist, add it to a list of files to be uploaded. If it doesn't, add a warning.
1818
1807
  let paths = [];
1819
- for ( let name of file_names ) {
1808
+ let missing_files = [];
1809
+ for ( let name of uploads_names ) {
1810
+ let trimmed_name = name.trim();
1811
+ let matching_paths = globSync( `${ dir }/${ trimmed_name }` );
1812
+ if ( matching_paths.length === 0 ) {
1813
+ missing_files.push( trimmed_name );
1814
+ } else {
1815
+ paths.push( matching_paths[0] );
1816
+ }
1817
+ }
1820
1818
 
1821
- // If the developer correctly used a ", ", there will be a space at the
1822
- // start of the string. Remove it. If they forgot the space, that's fine too.
1823
- name = name.replace(/^\s/, '');
1824
- let file_path = path.join( chosen_dir, name );
1819
+ if ( missing_files.length > 0 ) {
1820
+ await scope.addToReport( scope, {
1821
+ type: `warning`,
1822
+ value: `ALKiln could not find "${ missing_files.join('", "') }" in "${ dir }".`
1823
+ });
1824
+ }
1825
1825
 
1826
- try {
1827
- fs.accessSync( file_path );
1828
- paths.push( file_path );
1829
- } catch ( err ) {
1830
- // TODO: Should this throw an error?
1831
- await scope.addToReport( scope, { type: `warning`, value: `Could not find "${ name }" in "${ chosen_dir }"` });
1832
- }
1826
+ // If all files missing, return that the row wasn't used
1827
+ if ( missing_files.length === uploads_names.length ) {
1828
+ return false;
1833
1829
  }
1834
1830
 
1835
- if ( paths.length === 0 ) {
1836
- await scope.addToReport( scope, { type: `error`, value: `Could not find "${answer}".` });
1837
- throw ReferenceError(`Could not find "${answer}".`)
1838
- }
1839
1831
  // Prepare to wait for upload to complete
1840
1832
  let name = await handle.evaluate( elem => { return elem.getAttribute(`name`); });
1841
1833
  let selector = `*[name="${ name }"]`
@@ -1855,8 +1847,9 @@ module.exports = {
1855
1847
  await wait_for_disable;
1856
1848
  // When it's again enabled, the files have finished loading. Or so I have observed.
1857
1849
  await scope.page.waitForSelector( enabled_selector );
1858
-
1859
1850
 
1851
+ // Return that the row was used
1852
+ return true;
1860
1853
  }, // Ends scope.uploadFiles()
1861
1854
 
1862
1855
  setCheckbox: async function ( scope, { label, answer }) {
@@ -1877,16 +1870,26 @@ module.exports = {
1877
1870
  }, 500)
1878
1871
  },
1879
1872
 
1880
- draw_signature: async function ( scope ) {
1873
+ draw_signature: async function ( scope, name ) {
1881
1874
  /* On a signature page, there should only be a single signature canvas
1882
1875
  * and a continue button. Draw in the #dasigcanvas element. `<canvas>`
1883
1876
  * takes a while to load. */
1884
- let handle = await scope.page.waitForSelector( `#dasigcanvas` );
1877
+ let canvas = await scope.page.waitForSelector( `#dasigcanvas` );
1885
1878
  let msg = `Could not find a signature field.`;
1886
- if ( !handle ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
1887
- expect( handle, msg ).to.exist;
1888
-
1889
- let bounding_box = await handle.boundingBox();
1879
+ if ( !canvas ) { await scope.addToReport(scope, { type: `error`, value: msg }); }
1880
+ expect( canvas, msg ).to.exist;
1881
+
1882
+ // Provides functionality to draw name arg on canvas
1883
+ if (name !== undefined) {
1884
+ await scope.page.$eval(`#dasigcanvas`, (canvas, name) => {
1885
+ const ctx = canvas.getContext("2d");
1886
+ ctx.font = "24px serif";
1887
+ ctx.fillText(name, 10, 50);
1888
+ }, name);
1889
+ }
1890
+ let bounding_box = await canvas.boundingBox();
1891
+ // We draw the dot because it allows the developer to tap continue
1892
+ // Docassemble does not allow to tap continue unless the user interacts with the canvas
1890
1893
  await scope.page.mouse.move(bounding_box.x + bounding_box.width / 2, bounding_box.y + bounding_box.height / 2);
1891
1894
  await scope.page.mouse.down();
1892
1895
  // await scope.page.mouse.move(1, 1); // Too big? Wait a while before drawing?
@@ -2415,9 +2418,9 @@ module.exports = {
2415
2418
  await scope.afterStep(scope, {waitForShowIf: true});
2416
2419
  }, // Ends scope.steps.tap_term()
2417
2420
 
2418
- sign: async ( scope ) => {
2421
+ sign: async ( scope, name ) => {
2419
2422
  /** Sign on a signature page. */
2420
- await scope.draw_signature( scope );
2423
+ await scope.draw_signature( scope, name );
2421
2424
  await scope.afterStep(scope);
2422
2425
  }, // Ends scope.steps.sign()
2423
2426
 
package/lib/steps.js CHANGED
@@ -92,7 +92,7 @@ BeforeAll(async () => {
92
92
  // Make the folder to store all the files for this run of the tests
93
93
  scope.paths.artifacts = files.make_artifacts_folder();
94
94
  // Save its name in a file. `session_vars` can get it back later.
95
- // This is used by at least clean_reports.js.
95
+ // The value we are saving is used by at least run_cucumber.js.
96
96
  session_vars.save_artifacts_path_name( scope.paths.artifacts );
97
97
 
98
98
  scope.paths.report = `${ scope.paths.artifacts }/report.txt`;
@@ -103,7 +103,14 @@ BeforeAll(async () => {
103
103
  Before(async (scenario) => {
104
104
  // Create browser, which can't be created in BeforeAll() where .driver doesn't exist for some reason
105
105
  if (!scope.browser) {
106
- scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
106
+ if (session_vars.get_origin() === 'playground') {
107
+ // Will only run in the Playground outside of a sandbox. TODO: There's a
108
+ // better way to do this, though it's more complicated. See comments in
109
+ // https://github.com/SuffolkLITLab/ALKiln/issues/661
110
+ scope.browser = await scope.driver.launch({ args: ['--no-sandbox'] });
111
+ } else {
112
+ scope.browser = await scope.driver.launch({ headless: !session_vars.get_debug(), devtools: session_vars.get_debug() });
113
+ }
107
114
  }
108
115
  // Clean up all previously existing pages
109
116
  for (const page of await scope.browser.pages()) {
@@ -973,13 +980,21 @@ When('I tap the defined text link {string}', { timeout: -1 }, async ( phrase ) =
973
980
  await scope.steps.tap_term( scope, phrase );
974
981
  });
975
982
 
976
- Then('I sign', { timeout: -1 }, async () => {
983
+ Then(/^I sign$/, { timeout: -1 }, async () => {
977
984
  return wrapPromiseWithTimeout(
978
985
  scope.steps.sign( scope ),
979
986
  scope.timeout
980
987
  );
981
988
  });
982
989
 
990
+ Then('I sign with the name {string}', { timeout: -1 }, async ( name ) => {
991
+ /* Signs with the string argument */
992
+ return wrapPromiseWithTimeout(
993
+ scope.steps.sign( scope, name ),
994
+ scope.timeout
995
+ );
996
+ });
997
+
983
998
  When(/I download "([^"]+)"$/, { timeout: -1 }, async ( filename ) => {
984
999
  /* Taps the link that leads to the given filename to trigger downloading.
985
1000
  * and waits till the file has been downloaded before allowing the tests to continue.
@@ -23,9 +23,27 @@ module.exports = session_vars;
23
23
  // These default to null
24
24
  // TODO: How to isolate debug elsewhere so this file can use the logger?
25
25
  session_vars.get_debug = function () { return process.env.DEBUG || null; };
26
+ session_vars.is_dev_env = function () { return process.env.DEV || null; }
27
+
26
28
  session_vars.get_dev_api_key = function () { return process.env.DOCASSEMBLE_DEVELOPER_API_KEY || null; };
27
29
  session_vars.get_repo_url = function () { return process.env.REPO_URL || null; };
28
- session_vars.is_dev_env = function () { return process.env.DEV || null; }
30
+
31
+ session_vars.get_origin = function () {
32
+ /** The location from which the tests are being run. Possible
33
+ * valid return values: 'playground', 'github', or 'local'.
34
+ * Default is 'local' for ease of our own onboarding process.
35
+ */
36
+ return process.env._ORIGIN || 'local';
37
+ };
38
+ session_vars.get_user_project_name = function () { return process.env._PROJECT_NAME || null; };
39
+ session_vars.get_user_id = function () { return process.env._USER_ID || null; };
40
+ session_vars.get_tags = function () {
41
+ if ( process.env._TAGS ) {
42
+ return process.env._TAGS.split(` `);
43
+ } else {
44
+ return [];
45
+ }
46
+ };
29
47
 
30
48
  // More complex logic
31
49
  session_vars.get_server_reload_timeout = function () {
@@ -130,7 +148,10 @@ session_vars.save_project_name = function ( project_name ) {
130
148
  }; // Ends save_project_name()
131
149
 
132
150
  session_vars.get_project_name = function () {
133
- /* Get the name of the current docassemble Project */
151
+ /* Get the name of the current docassemble Project */
152
+ if ( session_vars.get_origin() === 'playground' ) {
153
+ return session_vars.get_user_project_name();
154
+ }
134
155
  let json = JSON.parse( fs.readFileSync( runtime_config_path ));
135
156
  let project_name = json[ project_name_key ] || null;
136
157
  if ( session_vars.get_debug() ) { console.log( `ALKiln debug: Project name from file is "${ project_name }".` ); }
@@ -174,7 +195,7 @@ session_vars.save_artifacts_path_name = function ( path_name ) {
174
195
  console.log( `ALKiln debug: Stored name of artifacts directory "${ path_name }" locally.` );
175
196
  }
176
197
  return file;
177
- }; // Ends get_project_name()
198
+ }; // Ends save_artifacts_path_name()
178
199
 
179
200
  session_vars.get_artifacts_path_name = function () {
180
201
  /* Get the name of the current artifacts path. */
@@ -187,48 +208,69 @@ session_vars.get_artifacts_path_name = function () {
187
208
  } catch ( error ) {
188
209
  return null;
189
210
  }
190
- }; // Ends get_project_name()
191
-
192
- /**
193
- * Ensure all required environment variables exist. Add each missing variable
194
- * to an error that will be thrown at the end.
195
- *
196
- * @throws ReferenceError with a message for all missing variables.
197
- */
198
- session_vars.validateEnvironment = function() {
199
- /** Throw a useful error for the developer if any of the required env vars
200
- * are missing. For most devs, these will be GitHub secrets. */
201
-
202
- // Note: We are checking for BASE_URL, but we are referring it as SERVER_URL as that
203
- // is the name used in GitHub secrets, which is what most devs will be using.
211
+ }; // Ends get_artifacts_path_name()
204
212
 
213
+ session_vars.validateEnvironment = function() {
214
+ /** Throw a useful error for the developer if any of the env vars
215
+ * required by absolutely all tests are missing. Gather all
216
+ * missing var names into one message to ease dev process.
217
+ *
218
+ * Rational for doing it all in here:
219
+ * 1. Easier to test
220
+ * 2. Will avoid piecemeal errors in separate locations
221
+ *
222
+ * TODO: Also validate format of variables?
223
+ *
224
+ * @throws ReferenceError with a message for all missing variables.
225
+ */
205
226
  let errorMessage = '';
206
227
 
207
- // 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
208
- // will have to run their pipeline an incredible number of times to discover all the missing variables.
209
- if ( !session_vars.get_dev_api_key() )
210
- {
211
- errorMessage += `\nThe DOCASSEMBLE_DEVELOPER_API_KEY GitHub secret must be defined. The DOCASSEMBLE_DEVELOPER_API_KEY is a docassemble API key you created for your server's testing account. It should have developer permissions.`
212
- }
213
- if ( !session_vars.get_da_server_url() )
214
- {
228
+ // Env vars required for any tests
229
+ if ( !session_vars.get_da_server_url() ) {
215
230
  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.`;
216
231
  }
217
- if ( !session_vars.get_repo_url() )
218
- {
219
- errorMessage += `\nThe REPO_URL GitHub secret must be defined. The REPO_URL is the URL to the Git repo whose Actions are being run.`;
220
- }
221
- // The branch name is always automatically defined in Github, it'll be missing only if you're running locally and missing an env var.
222
- if ( !session_vars.get_branch_name() )
223
- {
224
- errorMessage += `\nThe BRANCH_NAME environment variable must be defined. The BRANCH_NAME is the name of the repository branch that should be pulled into the docassemble project.`;
225
- }
232
+
233
+ let origin = session_vars.get_origin();
234
+ if ( origin !== 'github' && origin !== 'local' && origin !== 'playground' ) {
235
+ errorMessage += `\nThe _ORIGIN environment variable should be set to a valid value automatically. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It must be defined as either 'playground', 'github', or 'local', but its value was ${origin}`;
236
+
237
+ // If origin has one of the allowed values, we can do the rest of the validations
238
+ } else {
239
+
240
+ // Env vars required for github or local runs, as both create Projects
241
+ // in the Playground and pull the repo from GitHub
242
+ if ( origin === 'github' || origin === 'local' ) {
243
+ if ( !session_vars.get_dev_api_key() ) {
244
+ errorMessage += `\nThe DOCASSEMBLE_DEVELOPER_API_KEY GitHub secret must be defined. The DOCASSEMBLE_DEVELOPER_API_KEY is a docassemble API key you created for your server's testing account. It should have developer permissions.`
245
+ }
246
+ // Github automatically gives this, but may be missing locally.
247
+ if ( !session_vars.get_repo_url() ) {
248
+ errorMessage += `\nThe REPO_URL environment variable must be defined. The REPO_URL is the URL to the GitHub repo whose Actions are being run.`;
249
+ }
250
+ // Github automatically gives this, but may be missing locally.
251
+ if ( !session_vars.get_branch_name() ) {
252
+ errorMessage += `\nThe BRANCH_NAME environment variable must be defined. The BRANCH_NAME is the name of the repository branch that should be pulled into the docassemble project.`;
253
+ }
254
+ } // ends github and local env vars
255
+
256
+ // We're the ones ensuring these exist for Playground runs
257
+ if ( origin === 'playground' ) {
258
+ if ( !session_vars.get_user_project_name() ) {
259
+ errorMessage += `\nThe _PROJECT_NAME environment variable should automatically exist. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It is the name of the Project the developer chose to test.`
260
+ }
261
+ if ( !session_vars.get_user_id() ) {
262
+ errorMessage += `\nThe _USER_ID environment variable should automatically exist. If you see this error, file an issue at https://github.com/suffolkLITLab/docassemble-ALKilnInThePlayground. It is the user ID of the developer on the server.`
263
+ }
264
+ // Tags are optional
265
+ } // ends Playground env vars
266
+
267
+ } // ends validation based on origin
226
268
 
227
269
  errorMessage = errorMessage.trim();
228
270
 
229
271
  if (errorMessage !== '') {
230
- throw new ReferenceError(errorMessage);
272
+ throw new ReferenceError(`ALKiln error: ${errorMessage}`);
231
273
  }
232
274
  };
233
-
275
+
234
276
  session_vars.validateEnvironment();