dceky 1.0.0-beta.6 → 1.0.3

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 (190) hide show
  1. package/.eslintrc.js +93 -0
  2. package/README.md +41 -0
  3. package/cypress/e2e/profile-test.cy.ts +10 -0
  4. package/cypress/fixtures/example.json +5 -0
  5. package/cypress/support/commands.ts +37 -0
  6. package/cypress/support/e2e.ts +17 -0
  7. package/cypress.config.ts +9 -0
  8. package/docs/GlobalsAndProfiles.md +23 -0
  9. package/lib/setup/addToGitIgnore.d.ts +6 -0
  10. package/lib/setup/addToGitIgnore.js +47 -0
  11. package/lib/setup/addToGitIgnore.js.map +1 -0
  12. package/lib/setup/checkRequiredFiles.d.ts +6 -0
  13. package/lib/setup/checkRequiredFiles.js +59 -0
  14. package/lib/setup/checkRequiredFiles.js.map +1 -0
  15. package/lib/setup/genCommandImportFile.d.ts +7 -0
  16. package/lib/setup/genCommandImportFile.js +67 -0
  17. package/lib/setup/genCommandImportFile.js.map +1 -0
  18. package/lib/setup/genDynamicConfigFile.d.ts +5 -0
  19. package/lib/setup/genDynamicConfigFile.js +29 -0
  20. package/lib/setup/genDynamicConfigFile.js.map +1 -0
  21. package/lib/setup/helpers/getRootPath.d.ts +7 -0
  22. package/lib/setup/helpers/getRootPath.js +17 -0
  23. package/lib/setup/helpers/getRootPath.js.map +1 -0
  24. package/lib/setup/index.d.ts +1 -0
  25. package/lib/setup/index.js +16 -0
  26. package/lib/setup/index.js.map +1 -0
  27. package/lib/setup/setupCypressDependencies.d.ts +6 -0
  28. package/lib/setup/setupCypressDependencies.js +40 -0
  29. package/lib/setup/setupCypressDependencies.js.map +1 -0
  30. package/lib/src/commands/assertDoesNotHaveClass.d.ts +20 -0
  31. package/lib/src/commands/assertDoesNotHaveClass.js +17 -0
  32. package/lib/src/commands/assertDoesNotHaveClass.js.map +1 -0
  33. package/lib/src/commands/assertHasClass.d.ts +20 -0
  34. package/lib/src/commands/assertHasClass.js +17 -0
  35. package/lib/src/commands/assertHasClass.js.map +1 -0
  36. package/lib/src/commands/assertNumElements.d.ts +19 -0
  37. package/lib/src/commands/assertNumElements.js +17 -0
  38. package/lib/src/commands/assertNumElements.js.map +1 -0
  39. package/lib/src/commands/extractDataFromClass.d.ts +18 -0
  40. package/lib/src/commands/extractDataFromClass.js +25 -0
  41. package/lib/src/commands/extractDataFromClass.js.map +1 -0
  42. package/lib/src/commands/extractDataFromClassByContents.d.ts +19 -0
  43. package/lib/src/commands/extractDataFromClassByContents.js +26 -0
  44. package/lib/src/commands/extractDataFromClassByContents.js.map +1 -0
  45. package/lib/src/commands/getJSON.d.ts +16 -0
  46. package/lib/src/commands/getJSON.js +21 -0
  47. package/lib/src/commands/getJSON.js.map +1 -0
  48. package/lib/src/commands/getNumElements.d.ts +15 -0
  49. package/lib/src/commands/getNumElements.js +19 -0
  50. package/lib/src/commands/getNumElements.js.map +1 -0
  51. package/lib/src/commands/handleHarvardKey.d.ts +15 -0
  52. package/lib/src/commands/handleHarvardKey.js +53 -0
  53. package/lib/src/commands/handleHarvardKey.js.map +1 -0
  54. package/lib/src/commands/launchAs.d.ts +25 -0
  55. package/lib/src/commands/launchAs.js +66 -0
  56. package/lib/src/commands/launchAs.js.map +1 -0
  57. package/lib/src/commands/launchLTIUsingToken.d.ts +21 -0
  58. package/lib/src/commands/launchLTIUsingToken.js +68 -0
  59. package/lib/src/commands/launchLTIUsingToken.js.map +1 -0
  60. package/lib/src/commands/navigateToHref.d.ts +15 -0
  61. package/lib/src/commands/navigateToHref.js +35 -0
  62. package/lib/src/commands/navigateToHref.js.map +1 -0
  63. package/lib/src/commands/runScript.d.ts +15 -0
  64. package/lib/src/commands/runScript.js +20 -0
  65. package/lib/src/commands/runScript.js.map +1 -0
  66. package/lib/src/commands/typeInto.d.ts +24 -0
  67. package/lib/src/commands/typeInto.js +41 -0
  68. package/lib/src/commands/typeInto.js.map +1 -0
  69. package/lib/src/commands/visitCanvasGETEndpoint.d.ts +20 -0
  70. package/lib/src/commands/visitCanvasGETEndpoint.js +26 -0
  71. package/lib/src/commands/visitCanvasGETEndpoint.js.map +1 -0
  72. package/lib/src/commands/waitForElementVisible.d.ts +16 -0
  73. package/lib/src/commands/waitForElementVisible.js +19 -0
  74. package/lib/src/commands/waitForElementVisible.js.map +1 -0
  75. package/lib/src/genConfiguration/helpers/getRootPath.d.ts +7 -0
  76. package/lib/src/genConfiguration/helpers/getRootPath.js +17 -0
  77. package/lib/src/genConfiguration/helpers/getRootPath.js.map +1 -0
  78. package/lib/src/genConfiguration/helpers/resolveDependents.d.ts +13 -0
  79. package/lib/src/genConfiguration/helpers/resolveDependents.js +50 -0
  80. package/lib/src/genConfiguration/helpers/resolveDependents.js.map +1 -0
  81. package/lib/src/genConfiguration/helpers/splitEnv.d.ts +10 -0
  82. package/lib/src/genConfiguration/helpers/splitEnv.js +27 -0
  83. package/lib/src/genConfiguration/helpers/splitEnv.js.map +1 -0
  84. package/{src/genConfiguration.ts → lib/src/genConfiguration/index.d.ts} +3 -6
  85. package/lib/src/genConfiguration/index.js +89 -0
  86. package/lib/src/genConfiguration/index.js.map +1 -0
  87. package/lib/src/genConfiguration/types/DependentValue.d.ts +11 -0
  88. package/lib/src/genConfiguration/types/DependentValue.js +3 -0
  89. package/lib/src/genConfiguration/types/DependentValue.js.map +1 -0
  90. package/lib/src/genConfiguration/types/GlobalsOrProfile.d.ts +9 -0
  91. package/lib/src/genConfiguration/types/GlobalsOrProfile.js +3 -0
  92. package/lib/src/genConfiguration/types/GlobalsOrProfile.js.map +1 -0
  93. package/lib/src/genConfiguration/types/SplitEnv.d.ts +13 -0
  94. package/lib/src/genConfiguration/types/SplitEnv.js +3 -0
  95. package/lib/src/genConfiguration/types/SplitEnv.js.map +1 -0
  96. package/lib/{index.d.ts → src/index.d.ts} +1 -1
  97. package/lib/{index.js → src/index.js} +6 -3
  98. package/lib/src/index.js.map +1 -0
  99. package/lib/src/init.d.ts +6 -0
  100. package/lib/src/init.js +45 -0
  101. package/lib/src/init.js.map +1 -0
  102. package/lib/start/constants/AVAILABLE_BROWSERS.d.ts +9 -0
  103. package/lib/start/constants/AVAILABLE_BROWSERS.js +27 -0
  104. package/lib/start/constants/AVAILABLE_BROWSERS.js.map +1 -0
  105. package/lib/start/helpers/exec.d.ts +8 -0
  106. package/lib/start/helpers/exec.js +18 -0
  107. package/lib/start/helpers/exec.js.map +1 -0
  108. package/lib/start/helpers/extractArgValue.d.ts +10 -0
  109. package/lib/start/helpers/extractArgValue.js +39 -0
  110. package/lib/start/helpers/extractArgValue.js.map +1 -0
  111. package/lib/start/helpers/findProfilesByNames.d.ts +16 -0
  112. package/lib/start/helpers/findProfilesByNames.js +35 -0
  113. package/lib/start/helpers/findProfilesByNames.js.map +1 -0
  114. package/lib/start/helpers/getRootPath.d.ts +7 -0
  115. package/lib/start/helpers/getRootPath.js +17 -0
  116. package/lib/start/helpers/getRootPath.js.map +1 -0
  117. package/lib/start/helpers/parseCommaSeparated.d.ts +8 -0
  118. package/lib/start/helpers/parseCommaSeparated.js +23 -0
  119. package/lib/start/helpers/parseCommaSeparated.js.map +1 -0
  120. package/lib/start/helpers/print.d.ts +38 -0
  121. package/lib/start/helpers/print.js +145 -0
  122. package/lib/start/helpers/print.js.map +1 -0
  123. package/lib/start/helpers/prompt.d.ts +8 -0
  124. package/lib/start/helpers/prompt.js +25 -0
  125. package/lib/start/helpers/prompt.js.map +1 -0
  126. package/lib/start/helpers/showChooser.d.ts +21 -0
  127. package/lib/start/helpers/showChooser.js +116 -0
  128. package/lib/start/helpers/showChooser.js.map +1 -0
  129. package/lib/start/helpers/validateBrowsers.d.ts +8 -0
  130. package/lib/start/helpers/validateBrowsers.js +36 -0
  131. package/lib/start/helpers/validateBrowsers.js.map +1 -0
  132. package/lib/start/index.d.ts +7 -0
  133. package/lib/start/index.js +140 -0
  134. package/lib/start/index.js.map +1 -0
  135. package/lib/start/types/ChooserOption.d.ts +10 -0
  136. package/lib/start/types/ChooserOption.js +3 -0
  137. package/lib/start/types/ChooserOption.js.map +1 -0
  138. package/package.json +28 -8
  139. package/setup/addToGitIgnore.ts +48 -0
  140. package/setup/checkRequiredFiles.ts +63 -0
  141. package/setup/genCommandImportFile.ts +76 -0
  142. package/setup/genDynamicConfigFile.ts +29 -0
  143. package/setup/helpers/getRootPath.ts +13 -0
  144. package/setup/index.ts +11 -0
  145. package/setup/setupCypressDependencies.ts +38 -0
  146. package/src/commands/assertDoesNotHaveClass.ts +51 -0
  147. package/src/commands/assertHasClass.ts +51 -0
  148. package/src/commands/assertNumElements.ts +50 -0
  149. package/src/commands/extractDataFromClass.ts +52 -0
  150. package/src/commands/extractDataFromClassByContents.ts +55 -0
  151. package/src/commands/getJSON.ts +45 -0
  152. package/src/commands/getNumElements.ts +45 -0
  153. package/src/commands/handleHarvardKey.ts +91 -0
  154. package/src/commands/launchAs.ts +120 -0
  155. package/src/commands/launchLTIUsingToken.ts +115 -0
  156. package/src/commands/navigateToHref.ts +60 -0
  157. package/src/commands/runScript.ts +44 -0
  158. package/src/commands/typeInto.ts +88 -0
  159. package/src/commands/visitCanvasGETEndpoint.ts +61 -0
  160. package/src/commands/waitForElementVisible.ts +49 -0
  161. package/src/genConfiguration/helpers/getRootPath.ts +13 -0
  162. package/src/genConfiguration/helpers/resolveDependents.ts +46 -0
  163. package/src/genConfiguration/helpers/splitEnv.ts +30 -0
  164. package/src/genConfiguration/index.ts +95 -0
  165. package/src/genConfiguration/types/DependentValue.ts +14 -0
  166. package/src/genConfiguration/types/GlobalsOrProfile.ts +12 -0
  167. package/src/genConfiguration/types/SplitEnv.ts +18 -0
  168. package/src/index.ts +7 -4
  169. package/src/init.ts +36 -17
  170. package/start/constants/AVAILABLE_BROWSERS.ts +28 -0
  171. package/start/helpers/exec.ts +17 -0
  172. package/start/helpers/extractArgValue.ts +42 -0
  173. package/start/helpers/findProfilesByNames.ts +39 -0
  174. package/start/helpers/getRootPath.ts +13 -0
  175. package/start/helpers/parseCommaSeparated.ts +23 -0
  176. package/start/helpers/print.ts +155 -0
  177. package/start/helpers/prompt.ts +23 -0
  178. package/start/helpers/showChooser.ts +140 -0
  179. package/start/helpers/validateBrowsers.ts +35 -0
  180. package/start/index.ts +164 -0
  181. package/start/types/ChooserOption.ts +11 -0
  182. package/tsconfig.json +3 -4
  183. package/.eslintrc.json +0 -58
  184. package/lib/genConfiguration.d.ts +0 -8
  185. package/lib/genConfiguration.js +0 -13
  186. package/lib/genConfiguration.js.map +0 -1
  187. package/lib/index.js.map +0 -1
  188. package/lib/init.d.ts +0 -13
  189. package/lib/init.js +0 -10
  190. package/lib/init.js.map +0 -1
@@ -0,0 +1,88 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Type text into an element. This function first removes the previous text in the element
12
+ * @author Yuen Ler Chow
13
+ * @param opts object containing all arguments
14
+ * @param opts.item the CSS selector of interest
15
+ * @param opts.text the text to type
16
+ * @param [opts.pressEnter] if true, after typing into the text field, simulate pressing enter
17
+ * @param [opts.append] if true, append the text to the end of the existing text in the element instead of replacing it
18
+ * @return Cypress chainable containing the input element that text was typed into
19
+ */
20
+ typeInto(
21
+ opts: {
22
+ item: string,
23
+ text: string,
24
+ pressEnter?: boolean,
25
+ append?: boolean,
26
+ },
27
+ ): Chainable<Element>;
28
+ }
29
+ }
30
+ }
31
+
32
+ /*----------------------------------------*/
33
+ /* --------------- Command -------------- */
34
+ /*----------------------------------------*/
35
+
36
+ const typeInto = () => {
37
+ Cypress.Commands.add(
38
+ 'typeInto',
39
+ (
40
+ opts: {
41
+ item: string,
42
+ text: string,
43
+ pressEnter?: boolean,
44
+ append?: boolean,
45
+ },
46
+ ) => {
47
+ const {
48
+ item, text, pressEnter, append,
49
+ } = opts;
50
+ // Check if the text contains an enter key press
51
+ const enterAtEndOfText = text.charAt(text.length - 1) === '\n';
52
+ const willPressEnter = pressEnter || enterAtEndOfText;
53
+
54
+ const action = append ? 'Append' : 'Type';
55
+ const enterSuffix = willPressEnter ? ' and then press enter' : '';
56
+ cy.log(`${action} "${text}" into ${item}${enterSuffix}`);
57
+
58
+ // Type the text without a trailing enter if there was one
59
+ const textWithoutTrailingEnter = (
60
+ enterAtEndOfText
61
+ ? text.substring(0, text.length - 1)
62
+ : text
63
+ );
64
+
65
+ if (append) {
66
+ cy
67
+ .get(item)
68
+ .type(textWithoutTrailingEnter);
69
+ } else {
70
+ cy
71
+ .get(item)
72
+ .clear()
73
+ .type(textWithoutTrailingEnter);
74
+ }
75
+
76
+ // Press enter if explicitly requested or if text ended with newline
77
+ if (willPressEnter) {
78
+ cy.get(item).type('{enter}');
79
+ }
80
+ },
81
+ );
82
+ };
83
+
84
+ /*----------------------------------------*/
85
+ /* --------------- Export --------------- */
86
+ /*----------------------------------------*/
87
+
88
+ export default typeInto;
@@ -0,0 +1,61 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Makes a GET request to Canvas API endpoint
12
+ * @author Yuen Ler Chow
13
+ * @param opts object containing all arguments
14
+ * @param opts.path The API path (e.g., '/courses', '/users/self')
15
+ * @param opts.accessToken The Canvas access token
16
+ * @return Cypress chainable containing the Canvas API response body
17
+ */
18
+ visitCanvasGETEndpoint(
19
+ opts: {
20
+ path: string,
21
+ accessToken: string,
22
+ }
23
+ ): Chainable<any>;
24
+ }
25
+ }
26
+ }
27
+
28
+ /*----------------------------------------*/
29
+ /* --------------- Command -------------- */
30
+ /*----------------------------------------*/
31
+
32
+ const visitCanvasGETEndpoint = () => {
33
+ Cypress.Commands.add(
34
+ 'visitCanvasGETEndpoint',
35
+ (
36
+ opts: {
37
+ path: string,
38
+ accessToken: string,
39
+ },
40
+ ) => {
41
+ cy.log('Visiting Canvas GET endpoint');
42
+ const { path, accessToken } = opts;
43
+
44
+ cy.log(`Canvas API: ${path}`);
45
+ return cy.request({
46
+ method: 'GET',
47
+ url: `https://canvas.harvard.edu/api/v1${path}`,
48
+ qs: {
49
+ per_page: 200,
50
+ access_token: accessToken,
51
+ },
52
+ }).its('body');
53
+ },
54
+ );
55
+ };
56
+
57
+ /*----------------------------------------*/
58
+ /* --------------- Export --------------- */
59
+ /*----------------------------------------*/
60
+
61
+ export default visitCanvasGETEndpoint;
@@ -0,0 +1,49 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Wait for an element to be visible
12
+ * @author Yuen Ler Chow
13
+ * @param item the CSS selector of interest
14
+ * @param [timeoutSec=10] the number of seconds to wait before timing out
15
+ * @return Cypress chainable containing the visible element
16
+ */
17
+ waitForElementVisible(
18
+ item: string,
19
+ timeoutSec?: number,
20
+ ): Chainable<JQuery<HTMLElement>>;
21
+ }
22
+ }
23
+ }
24
+
25
+ /*----------------------------------------*/
26
+ /* --------------- Command -------------- */
27
+ /*----------------------------------------*/
28
+
29
+ const waitForElementVisible = () => {
30
+ Cypress.Commands.add(
31
+ 'waitForElementVisible',
32
+ (
33
+ item: string,
34
+ timeoutSec?: number,
35
+ ) => {
36
+ cy.log('Waiting for element to be visible');
37
+
38
+ const timeoutMs = (timeoutSec || 10) * 1000;
39
+ cy.log(`Wait for ${item} to be visible`);
40
+ return cy.get(item, { timeout: timeoutMs }).should('be.visible');
41
+ },
42
+ );
43
+ };
44
+
45
+ /*----------------------------------------*/
46
+ /* --------------- Export --------------- */
47
+ /*----------------------------------------*/
48
+
49
+ export default waitForElementVisible;
@@ -0,0 +1,13 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * Get the root path of the project
5
+ * @author Yuen Ler Chow
6
+ * @returns the root path of the project
7
+ */
8
+ const getRootPath = () => {
9
+ const root = path.join(__dirname, '../../../../../../');
10
+ return root;
11
+ };
12
+
13
+ export default getRootPath;
@@ -0,0 +1,46 @@
1
+ // Import shared types
2
+ import type GlobalsOrProfile from '../types/GlobalsOrProfile';
3
+ import type DependentValue from '../types/DependentValue';
4
+
5
+ /**
6
+ * Resolve all dependent values using already-merged basics.
7
+ * @author Gardenia Liu
8
+ * @param dependentMaps with dependencies on basics
9
+ * @param basics entries
10
+ * @returns env with all dependent values resolved
11
+ */
12
+ const resolveDependents = (
13
+ dependentMaps: { [k: string]: DependentValue }[],
14
+ basics: GlobalsOrProfile,
15
+ ): GlobalsOrProfile => {
16
+ const resolved: GlobalsOrProfile = {};
17
+
18
+ // Loop through all of the dependency maps
19
+ dependentMaps.forEach((depMap) => {
20
+ // Loop through all entries within dependency map
21
+ Object.entries(depMap).forEach(([k, depVal]) => {
22
+ // Get basic selector the value depends on
23
+ const selector = basics[depVal.dependsOn];
24
+
25
+ // Create table of values selector can be
26
+ const { dependsOn, ...table } = depVal as { [k: string]: unknown };
27
+
28
+ // Get the selector's picked value
29
+ const picked = table[selector];
30
+
31
+ if (picked !== undefined) {
32
+ resolved[k] = picked;
33
+ } else if ('default' in table) {
34
+ resolved[k] = table.default;
35
+ } else {
36
+ // eslint-disable-next-line max-len
37
+ console.warn(`[cypress.config] Could not resolve ${k}, dependsOn=${dependsOn}, selector=${String(selector)}`);
38
+ process.exit(1);
39
+ }
40
+ });
41
+ });
42
+
43
+ return resolved;
44
+ };
45
+
46
+ export default resolveDependents;
@@ -0,0 +1,30 @@
1
+ // Import shared types
2
+ import type GlobalsOrProfile from '../types/GlobalsOrProfile';
3
+ import type DependentValue from '../types/DependentValue';
4
+ import type SplitEnv from '../types/SplitEnv';
5
+
6
+ /**
7
+ * Split one map into basic and dependent values
8
+ * @author Gardenia Liu
9
+ * @param map to split
10
+ * @returns split basic and dependent values map
11
+ */
12
+ const splitEnv = (map: GlobalsOrProfile): SplitEnv => {
13
+ const basic: GlobalsOrProfile = {};
14
+ const dependent: { [k: string]: DependentValue } = {};
15
+
16
+ Object.entries(map).forEach(([k, v]) => {
17
+ // If entry has dependsOn relationship, add it to dependent dictionary
18
+ if (typeof v === 'object' && 'dependsOn' in v) {
19
+ dependent[k] = v as DependentValue;
20
+ // Otherwise, add it to basic dictionary
21
+ } else {
22
+ basic[k] = v;
23
+ }
24
+ });
25
+
26
+ // Returns separated basic and dependent values
27
+ return { basic, dependent };
28
+ };
29
+
30
+ export default splitEnv;
@@ -0,0 +1,95 @@
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable global-require */
3
+ /* eslint-disable import/no-dynamic-require */
4
+ import path from 'path';
5
+
6
+ // Import cypress
7
+ import { defineConfig } from 'cypress';
8
+
9
+ // Import external libraries
10
+ import deepmerge from 'deepmerge';
11
+
12
+ // Import helpers
13
+ import splitEnv from './helpers/splitEnv';
14
+ import resolveDependents from './helpers/resolveDependents';
15
+ import getRootPath from './helpers/getRootPath';
16
+
17
+ // Import types
18
+ import type SplitEnv from './types/SplitEnv';
19
+ import type GlobalsOrProfile from './types/GlobalsOrProfile';
20
+
21
+ // Determine project directory
22
+ const CWD = getRootPath();
23
+
24
+ /**
25
+ * Generate Cypress configuration based on global credentials,
26
+ * variables, resources, and profiles
27
+ * @author Gardenia Liu
28
+ * @author Gabe Abrams
29
+ */
30
+ const genConfiguration = () => {
31
+ // Define paths
32
+ const GLOBALS_PATH = path.join(CWD, '/cypress/globals');
33
+ const PROFILES_PATH = path.join(CWD, '/cypress/profiles');
34
+
35
+ // Import global env var objects
36
+ const GlobalCredentials: GlobalsOrProfile = require(path.join(GLOBALS_PATH, '/GlobalCredentials'));
37
+ const GlobalResources: GlobalsOrProfile = require(path.join(GLOBALS_PATH, '/GlobalResources'));
38
+ const GlobalValues: GlobalsOrProfile = require(path.join(GLOBALS_PATH, '/GlobalValues'));
39
+
40
+ // Determine profile file name (default is stage)
41
+ const profileName = process.env.CYPRESS_PROFILE || 'stage';
42
+
43
+ // Load in chosen profile configuration
44
+ let profileModules: GlobalsOrProfile = {};
45
+ try {
46
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
47
+ const mod = require(path.join(PROFILES_PATH, `/${profileName}.Profile`));
48
+ profileModules = (mod?.default ?? mod ?? {}) as GlobalsOrProfile;
49
+ } catch (e) {
50
+ console.warn(`[cypress.config] Could not load profile '${profileName}.Profile'`, e);
51
+ process.exit(1);
52
+ }
53
+
54
+ // NOTE: Order matters (later overrides earlier)
55
+ const sources: GlobalsOrProfile[] = [GlobalCredentials, GlobalResources, GlobalValues, profileModules];
56
+
57
+ // Split all sources into basic and dependent
58
+ const splits: SplitEnv[] = sources.map((source) => {
59
+ return splitEnv(source);
60
+ });
61
+
62
+ // Extract basic and dependent values from splits
63
+ const basicValues = splits.map((s) => {
64
+ return s.basic;
65
+ });
66
+ const dependentValues = splits.map((s) => {
67
+ return s.dependent;
68
+ });
69
+
70
+ // Merge all of the basics
71
+ const basics: GlobalsOrProfile = deepmerge.all<GlobalsOrProfile>(basicValues);
72
+
73
+ // Resolve dependencies
74
+ const dependents: GlobalsOrProfile = resolveDependents(dependentValues, basics);
75
+
76
+ // Add basics, depends, and profileName into the final env
77
+ const finalEnv: GlobalsOrProfile = {
78
+ ...basics,
79
+ ...dependents,
80
+ profileName,
81
+ };
82
+
83
+ const baseUrl = finalEnv.baseURL;
84
+
85
+ // Return the configuration object
86
+ return defineConfig({
87
+ e2e: {
88
+ experimentalOriginDependencies: true,
89
+ baseUrl,
90
+ env: finalEnv,
91
+ },
92
+ });
93
+ };
94
+
95
+ export default genConfiguration;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Value for a dependent entry
3
+ * @author Gardenia Liu
4
+ */
5
+ type DependentValue = {
6
+ dependsOn: string,
7
+ } & {
8
+ // Environment, global variables, profile values, etc.
9
+ [k: string]: any,
10
+ // Default value if the dependsOn value is not found
11
+ default?: any,
12
+ };
13
+
14
+ export default DependentValue;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Object containing global variables, profile values, etc.
3
+ * @author Gardenia Liu
4
+ */
5
+ type GlobalsOrProfile = {
6
+ // Base URL to prefix all paths with
7
+ baseURL?: string,
8
+ // Environment, global variables, profile values, etc.
9
+ [k: string]: any,
10
+ };
11
+
12
+ export default GlobalsOrProfile;
@@ -0,0 +1,18 @@
1
+ // Import shared types
2
+ import type GlobalsOrProfile from './GlobalsOrProfile';
3
+ import type DependentValue from './DependentValue';
4
+
5
+ /**
6
+ * Env type where basic and dependent entries are separated
7
+ * @author Gardenia Liu
8
+ */
9
+ type SplitEnv = {
10
+ // Basic entries
11
+ basic: GlobalsOrProfile,
12
+ // Dependent entries
13
+ dependent: {
14
+ [k: string]: DependentValue,
15
+ },
16
+ };
17
+
18
+ export default SplitEnv;
package/src/index.ts CHANGED
@@ -1,11 +1,14 @@
1
+ // Import helpers
1
2
  import init from './init';
2
3
  import genConfiguration from './genConfiguration';
3
4
 
4
- // Automatically initialize upon importing the library
5
- init();
5
+ // Automatically initialize upon importing the library, only run in Cypress context
6
+ if (typeof Cypress !== 'undefined') {
7
+ init();
8
+ }
6
9
 
7
- // Exports
8
10
  export {
9
- genConfiguration,
11
+ // Helpers
10
12
  init,
13
+ genConfiguration,
11
14
  };
package/src/init.ts CHANGED
@@ -1,22 +1,41 @@
1
- /// <reference types="cypress" />
2
-
3
- // Add type declarations to Cypress namespace
4
- declare global {
5
- namespace Cypress {
6
- interface Chainable {
7
- /**
8
- * Custom command to click an element by selector
9
- * @example cy.clickSomething('.my-button')
10
- */
11
- clickSomething(selector: string): Chainable<Element>;
12
- }
13
- }
14
- }
1
+ // Import all commands
2
+ import assertDoesNotHaveClass from './commands/assertDoesNotHaveClass';
3
+ import assertHasClass from './commands/assertHasClass';
4
+ import assertNumElements from './commands/assertNumElements';
5
+ import extractDataFromClass from './commands/extractDataFromClass';
6
+ import extractDataFromClassByContents from './commands/extractDataFromClassByContents';
7
+ import getJSON from './commands/getJSON';
8
+ import getNumElements from './commands/getNumElements';
9
+ import handleHarvardKey from './commands/handleHarvardKey';
10
+ import launchAs from './commands/launchAs';
11
+ import launchLTIUsingToken from './commands/launchLTIUsingToken';
12
+ import navigateToHref from './commands/navigateToHref';
13
+ import runScript from './commands/runScript';
14
+ import typeInto from './commands/typeInto';
15
+ import visitCanvasGETEndpoint from './commands/visitCanvasGETEndpoint';
16
+ import waitForElementVisible from './commands/waitForElementVisible';
15
17
 
18
+ /**
19
+ * Initialize custom commands
20
+ * @author Gabe Abrams
21
+ */
16
22
  const init = () => {
17
- Cypress.Commands.add('clickSomething', (selector: string) => {
18
- cy.get(selector).click();
19
- });
23
+ // Execute each command adder
24
+ assertDoesNotHaveClass();
25
+ assertHasClass();
26
+ assertNumElements();
27
+ getNumElements();
28
+ handleHarvardKey();
29
+ launchAs();
30
+ launchLTIUsingToken();
31
+ navigateToHref();
32
+ runScript();
33
+ typeInto();
34
+ visitCanvasGETEndpoint();
35
+ waitForElementVisible();
36
+ extractDataFromClass();
37
+ extractDataFromClassByContents();
38
+ getJSON();
20
39
  };
21
40
 
22
41
  export default init;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Available browsers for Cypress tests
3
+ * @author Yuen Ler Chow
4
+ */
5
+ const AVAILABLE_BROWSERS: {
6
+ name: string,
7
+ tag: string,
8
+ }[] = [
9
+ {
10
+ name: 'chrome',
11
+ tag: 'C',
12
+ },
13
+ {
14
+ name: 'firefox',
15
+ tag: 'F',
16
+ },
17
+ {
18
+ name: 'edge',
19
+ tag: 'E',
20
+ },
21
+ {
22
+ name: 'safari',
23
+ tag: 'S',
24
+ },
25
+ ];
26
+
27
+ // Export only the browser names in lowercase
28
+ export default AVAILABLE_BROWSERS;
@@ -0,0 +1,17 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ /**
4
+ * Execute a command
5
+ * @author Gabe Abrams
6
+ * @param command the command to execute
7
+ * @param printToStdOut if true, print to standard out instead of returning
8
+ */
9
+ const exec = (command: string, printToStdOut?: boolean): string => {
10
+ if (printToStdOut) {
11
+ execSync(command, { stdio: 'inherit' });
12
+ return '';
13
+ }
14
+ return execSync(command).toString();
15
+ };
16
+
17
+ export default exec;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Extract CLI argument value or check if flag exists
3
+ * @author Gardenia Liu
4
+ * @author Yuen Ler Chow
5
+ * @param args array of CLI arguments
6
+ * @param argName the argument name to look for (e.g., '--profile' or '--headless')
7
+ * @returns the argument value if found, empty string if flag exists without value, undefined otherwise
8
+ */
9
+ const extractArgValue = (args: string[], argName: string): string | undefined => {
10
+ let matchedValue: string | undefined;
11
+ args.some((rawArg, index) => {
12
+ const normalizedArg = rawArg.toLowerCase();
13
+
14
+ // Case 1: flag and value combined, e.g. "--profile=stage"
15
+ if (normalizedArg.startsWith(`${argName}=`)) {
16
+ const [, value] = rawArg.split('=');
17
+ matchedValue = value;
18
+ return true; // stop scanning
19
+ }
20
+
21
+ // Case 2: flag by itself, possibly followed by a separate value
22
+ if (normalizedArg === argName) {
23
+ const nextArg = args[index + 1];
24
+
25
+ // If the next token exists and isn't another flag, treat it as the value
26
+ if (nextArg && !nextArg.startsWith('--')) {
27
+ matchedValue = nextArg;
28
+ } else {
29
+ // Flag exists but no value given (boolean-style flag) (e.g. "--headless")
30
+ matchedValue = '';
31
+ }
32
+
33
+ return true; // stop scanning
34
+ }
35
+
36
+ return false;
37
+ });
38
+
39
+ return matchedValue;
40
+ };
41
+
42
+ export default extractArgValue;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Find profiles by names
3
+ * @author Gardenia Liu
4
+ * @author Yuen Ler Chow
5
+ * @param names array of profile names to find
6
+ * @param profiles array of available profiles
7
+ * @returns array of found profiles
8
+ */
9
+ const findProfilesByNames = (
10
+ names: string[],
11
+ profiles: Array<{ file: string; profileName: string }>,
12
+ ): Array<{ file: string; profileName: string }> => {
13
+ const found: Array<{ file: string; profileName: string }> = [];
14
+ const notFound: string[] = [];
15
+
16
+ names.forEach((name) => {
17
+ const target = name.toLowerCase();
18
+
19
+ // Find matching profile (case-insensitive)
20
+ const match = profiles.find((p) => {
21
+ return p.profileName.toLowerCase() === target;
22
+ });
23
+ if (match) {
24
+ found.push(match);
25
+ } else {
26
+ notFound.push(name);
27
+ }
28
+ });
29
+
30
+ if (notFound.length > 0) {
31
+ // eslint-disable-next-line no-console
32
+ console.error(`Profile(s) not found: ${notFound.join(', ')}. Available profiles: ${profiles.map((p) => { return p.profileName; }).join(', ')}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ return found;
37
+ };
38
+
39
+ export default findProfilesByNames;
@@ -0,0 +1,13 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * Get the root path of the project
5
+ * @author Yuen Ler Chow
6
+ * @returns the root path of the project
7
+ */
8
+ const getRootPath = () => {
9
+ const root = path.join(__dirname, '../../../../');
10
+ return root;
11
+ };
12
+
13
+ export default getRootPath;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Parse comma-separated values into an array
3
+ * @author Yuen Ler Chow
4
+ * @param commaSeparatedString the comma-separated string to parse
5
+ * @returns array of trimmed, non-empty values
6
+ */
7
+ const parseCommaSeparated = (commaSeparatedString?: string): string[] => {
8
+ if (!commaSeparatedString) {
9
+ return [];
10
+ }
11
+ return (
12
+ commaSeparatedString
13
+ .split(',')
14
+ .map((value) => {
15
+ return value.trim();
16
+ })
17
+ .filter((value) => {
18
+ return value.length > 0;
19
+ })
20
+ );
21
+ };
22
+
23
+ export default parseCommaSeparated;