dceky 1.0.0-beta.6 → 1.0.2

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 (199) 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 +46 -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 +58 -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 +66 -0
  17. package/lib/setup/genCommandImportFile.js.map +1 -0
  18. package/lib/setup/genConfiguration/helpers/resolveDependents.d.ts +13 -0
  19. package/lib/setup/genConfiguration/helpers/resolveDependents.js +50 -0
  20. package/lib/setup/genConfiguration/helpers/resolveDependents.js.map +1 -0
  21. package/lib/setup/genConfiguration/helpers/splitEnv.d.ts +10 -0
  22. package/lib/setup/genConfiguration/helpers/splitEnv.js +27 -0
  23. package/lib/setup/genConfiguration/helpers/splitEnv.js.map +1 -0
  24. package/lib/setup/genConfiguration/index.d.ts +15 -0
  25. package/lib/setup/genConfiguration/index.js +86 -0
  26. package/lib/setup/genConfiguration/index.js.map +1 -0
  27. package/lib/setup/genConfiguration/types/DependentValue.d.ts +11 -0
  28. package/lib/setup/genConfiguration/types/DependentValue.js +3 -0
  29. package/lib/setup/genConfiguration/types/DependentValue.js.map +1 -0
  30. package/lib/setup/genConfiguration/types/GlobalsOrProfile.d.ts +9 -0
  31. package/lib/setup/genConfiguration/types/GlobalsOrProfile.js +3 -0
  32. package/lib/setup/genConfiguration/types/GlobalsOrProfile.js.map +1 -0
  33. package/lib/setup/genConfiguration/types/SplitEnv.d.ts +13 -0
  34. package/lib/setup/genConfiguration/types/SplitEnv.js +3 -0
  35. package/lib/setup/genConfiguration/types/SplitEnv.js.map +1 -0
  36. package/lib/setup/genConfigurationFile.d.ts +2 -0
  37. package/lib/setup/genConfigurationFile.js +63 -0
  38. package/lib/setup/genConfigurationFile.js.map +1 -0
  39. package/lib/setup/genDynamicConfigFile.d.ts +5 -0
  40. package/lib/setup/genDynamicConfigFile.js +28 -0
  41. package/lib/setup/genDynamicConfigFile.js.map +1 -0
  42. package/lib/setup/index.d.ts +1 -0
  43. package/lib/setup/index.js +16 -0
  44. package/lib/setup/index.js.map +1 -0
  45. package/lib/setup/setupCypressDependencies.d.ts +6 -0
  46. package/lib/setup/setupCypressDependencies.js +40 -0
  47. package/lib/setup/setupCypressDependencies.js.map +1 -0
  48. package/lib/src/commands/assertDoesNotHaveClass.d.ts +20 -0
  49. package/lib/src/commands/assertDoesNotHaveClass.js +17 -0
  50. package/lib/src/commands/assertDoesNotHaveClass.js.map +1 -0
  51. package/lib/src/commands/assertHasClass.d.ts +20 -0
  52. package/lib/src/commands/assertHasClass.js +17 -0
  53. package/lib/src/commands/assertHasClass.js.map +1 -0
  54. package/lib/src/commands/assertNumElements.d.ts +19 -0
  55. package/lib/src/commands/assertNumElements.js +17 -0
  56. package/lib/src/commands/assertNumElements.js.map +1 -0
  57. package/lib/src/commands/extractDataFromClass.d.ts +18 -0
  58. package/lib/src/commands/extractDataFromClass.js +25 -0
  59. package/lib/src/commands/extractDataFromClass.js.map +1 -0
  60. package/lib/src/commands/extractDataFromClassByContents.d.ts +19 -0
  61. package/lib/src/commands/extractDataFromClassByContents.js +26 -0
  62. package/lib/src/commands/extractDataFromClassByContents.js.map +1 -0
  63. package/lib/src/commands/getJSON.d.ts +16 -0
  64. package/lib/src/commands/getJSON.js +21 -0
  65. package/lib/src/commands/getJSON.js.map +1 -0
  66. package/lib/src/commands/getNumElements.d.ts +15 -0
  67. package/lib/src/commands/getNumElements.js +19 -0
  68. package/lib/src/commands/getNumElements.js.map +1 -0
  69. package/lib/src/commands/handleHarvardKey.d.ts +15 -0
  70. package/lib/src/commands/handleHarvardKey.js +53 -0
  71. package/lib/src/commands/handleHarvardKey.js.map +1 -0
  72. package/lib/src/commands/launchAs.d.ts +25 -0
  73. package/lib/src/commands/launchAs.js +66 -0
  74. package/lib/src/commands/launchAs.js.map +1 -0
  75. package/lib/src/commands/launchLTIUsingToken.d.ts +21 -0
  76. package/lib/src/commands/launchLTIUsingToken.js +68 -0
  77. package/lib/src/commands/launchLTIUsingToken.js.map +1 -0
  78. package/lib/src/commands/navigateToHref.d.ts +15 -0
  79. package/lib/src/commands/navigateToHref.js +35 -0
  80. package/lib/src/commands/navigateToHref.js.map +1 -0
  81. package/lib/src/commands/runScript.d.ts +15 -0
  82. package/lib/src/commands/runScript.js +20 -0
  83. package/lib/src/commands/runScript.js.map +1 -0
  84. package/lib/src/commands/typeInto.d.ts +24 -0
  85. package/lib/src/commands/typeInto.js +41 -0
  86. package/lib/src/commands/typeInto.js.map +1 -0
  87. package/lib/src/commands/visitCanvasGETEndpoint.d.ts +20 -0
  88. package/lib/src/commands/visitCanvasGETEndpoint.js +26 -0
  89. package/lib/src/commands/visitCanvasGETEndpoint.js.map +1 -0
  90. package/lib/src/commands/waitForElementVisible.d.ts +16 -0
  91. package/lib/src/commands/waitForElementVisible.js +19 -0
  92. package/lib/src/commands/waitForElementVisible.js.map +1 -0
  93. package/lib/src/genConfiguration/helpers/resolveDependents.d.ts +13 -0
  94. package/lib/src/genConfiguration/helpers/resolveDependents.js +50 -0
  95. package/lib/src/genConfiguration/helpers/resolveDependents.js.map +1 -0
  96. package/lib/src/genConfiguration/helpers/splitEnv.d.ts +10 -0
  97. package/lib/src/genConfiguration/helpers/splitEnv.js +27 -0
  98. package/lib/src/genConfiguration/helpers/splitEnv.js.map +1 -0
  99. package/{src/genConfiguration.ts → lib/src/genConfiguration/index.d.ts} +3 -6
  100. package/lib/src/genConfiguration/index.js +88 -0
  101. package/lib/src/genConfiguration/index.js.map +1 -0
  102. package/lib/src/genConfiguration/types/DependentValue.d.ts +11 -0
  103. package/lib/src/genConfiguration/types/DependentValue.js +3 -0
  104. package/lib/src/genConfiguration/types/DependentValue.js.map +1 -0
  105. package/lib/src/genConfiguration/types/GlobalsOrProfile.d.ts +9 -0
  106. package/lib/src/genConfiguration/types/GlobalsOrProfile.js +3 -0
  107. package/lib/src/genConfiguration/types/GlobalsOrProfile.js.map +1 -0
  108. package/lib/src/genConfiguration/types/SplitEnv.d.ts +13 -0
  109. package/lib/src/genConfiguration/types/SplitEnv.js +3 -0
  110. package/lib/src/genConfiguration/types/SplitEnv.js.map +1 -0
  111. package/lib/{index.d.ts → src/index.d.ts} +1 -1
  112. package/lib/{index.js → src/index.js} +6 -3
  113. package/lib/src/index.js.map +1 -0
  114. package/lib/src/init.d.ts +6 -0
  115. package/lib/src/init.js +45 -0
  116. package/lib/src/init.js.map +1 -0
  117. package/lib/start/constants/AVAILABLE_BROWSERS.d.ts +9 -0
  118. package/lib/start/constants/AVAILABLE_BROWSERS.js +27 -0
  119. package/lib/start/constants/AVAILABLE_BROWSERS.js.map +1 -0
  120. package/lib/start/helpers/exec.d.ts +8 -0
  121. package/lib/start/helpers/exec.js +18 -0
  122. package/lib/start/helpers/exec.js.map +1 -0
  123. package/lib/start/helpers/extractArgValue.d.ts +10 -0
  124. package/lib/start/helpers/extractArgValue.js +39 -0
  125. package/lib/start/helpers/extractArgValue.js.map +1 -0
  126. package/lib/start/helpers/findProfilesByNames.d.ts +16 -0
  127. package/lib/start/helpers/findProfilesByNames.js +35 -0
  128. package/lib/start/helpers/findProfilesByNames.js.map +1 -0
  129. package/lib/start/helpers/parseCommaSeparated.d.ts +8 -0
  130. package/lib/start/helpers/parseCommaSeparated.js +23 -0
  131. package/lib/start/helpers/parseCommaSeparated.js.map +1 -0
  132. package/lib/start/helpers/print.d.ts +38 -0
  133. package/lib/start/helpers/print.js +145 -0
  134. package/lib/start/helpers/print.js.map +1 -0
  135. package/lib/start/helpers/prompt.d.ts +8 -0
  136. package/lib/start/helpers/prompt.js +25 -0
  137. package/lib/start/helpers/prompt.js.map +1 -0
  138. package/lib/start/helpers/showChooser.d.ts +21 -0
  139. package/lib/start/helpers/showChooser.js +116 -0
  140. package/lib/start/helpers/showChooser.js.map +1 -0
  141. package/lib/start/helpers/validateBrowsers.d.ts +8 -0
  142. package/lib/start/helpers/validateBrowsers.js +36 -0
  143. package/lib/start/helpers/validateBrowsers.js.map +1 -0
  144. package/lib/start/index.d.ts +7 -0
  145. package/lib/start/index.js +139 -0
  146. package/lib/start/index.js.map +1 -0
  147. package/lib/start/types/ChooserOption.d.ts +10 -0
  148. package/lib/start/types/ChooserOption.js +3 -0
  149. package/lib/start/types/ChooserOption.js.map +1 -0
  150. package/package.json +28 -8
  151. package/setup/addToGitIgnore.ts +47 -0
  152. package/setup/checkRequiredFiles.ts +62 -0
  153. package/setup/genCommandImportFile.ts +75 -0
  154. package/setup/genDynamicConfigFile.ts +28 -0
  155. package/setup/index.ts +11 -0
  156. package/setup/setupCypressDependencies.ts +38 -0
  157. package/src/commands/assertDoesNotHaveClass.ts +51 -0
  158. package/src/commands/assertHasClass.ts +51 -0
  159. package/src/commands/assertNumElements.ts +50 -0
  160. package/src/commands/extractDataFromClass.ts +52 -0
  161. package/src/commands/extractDataFromClassByContents.ts +55 -0
  162. package/src/commands/getJSON.ts +45 -0
  163. package/src/commands/getNumElements.ts +45 -0
  164. package/src/commands/handleHarvardKey.ts +91 -0
  165. package/src/commands/launchAs.ts +120 -0
  166. package/src/commands/launchLTIUsingToken.ts +115 -0
  167. package/src/commands/navigateToHref.ts +60 -0
  168. package/src/commands/runScript.ts +44 -0
  169. package/src/commands/typeInto.ts +88 -0
  170. package/src/commands/visitCanvasGETEndpoint.ts +61 -0
  171. package/src/commands/waitForElementVisible.ts +49 -0
  172. package/src/genConfiguration/helpers/resolveDependents.ts +47 -0
  173. package/src/genConfiguration/helpers/splitEnv.ts +30 -0
  174. package/src/genConfiguration/index.ts +94 -0
  175. package/src/genConfiguration/types/DependentValue.ts +14 -0
  176. package/src/genConfiguration/types/GlobalsOrProfile.ts +12 -0
  177. package/src/genConfiguration/types/SplitEnv.ts +18 -0
  178. package/src/index.ts +7 -4
  179. package/src/init.ts +36 -17
  180. package/start/constants/AVAILABLE_BROWSERS.ts +28 -0
  181. package/start/helpers/exec.ts +17 -0
  182. package/start/helpers/extractArgValue.ts +42 -0
  183. package/start/helpers/findProfilesByNames.ts +39 -0
  184. package/start/helpers/parseCommaSeparated.ts +23 -0
  185. package/start/helpers/print.ts +155 -0
  186. package/start/helpers/prompt.ts +23 -0
  187. package/start/helpers/showChooser.ts +140 -0
  188. package/start/helpers/validateBrowsers.ts +35 -0
  189. package/start/index.ts +163 -0
  190. package/start/types/ChooserOption.ts +11 -0
  191. package/tsconfig.json +3 -4
  192. package/.eslintrc.json +0 -58
  193. package/lib/genConfiguration.d.ts +0 -8
  194. package/lib/genConfiguration.js +0 -13
  195. package/lib/genConfiguration.js.map +0 -1
  196. package/lib/index.js.map +0 -1
  197. package/lib/init.d.ts +0 -13
  198. package/lib/init.js +0 -10
  199. package/lib/init.js.map +0 -1
@@ -0,0 +1,91 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Handle a HarvardKey login page for a user
12
+ * @author Yuen Ler Chow
13
+ * @param name the name of the user environment variable
14
+ * @return Cypress chainable (void) - performs authentication flow, no return value
15
+ */
16
+ handleHarvardKey(
17
+ name: string,
18
+ ): Chainable<void>;
19
+ }
20
+ }
21
+ }
22
+
23
+ /*----------------------------------------*/
24
+ /* --------------- Command -------------- */
25
+ /*----------------------------------------*/
26
+
27
+ const handleHarvardKey = () => {
28
+ Cypress.Commands.add(
29
+ 'handleHarvardKey',
30
+ (
31
+ name: string,
32
+ ) => {
33
+ cy.log('Handling HarvardKey authentication');
34
+
35
+ const userInfo = Cypress.env(name);
36
+ if (!userInfo) {
37
+ throw new Error(`Could not find ${name} in environment variables`);
38
+ }
39
+
40
+ // Destructure the user info object to get the username and password
41
+ const { username } = userInfo;
42
+ const { password } = userInfo;
43
+
44
+ // Get the Harvard login URL using originWithKaixa with auto-initialized functions
45
+ cy.origin('https://apps.cirrusidentity.com', () => {
46
+ Cypress.require('dceky');
47
+ // Clicking the button doesn't work for some reason, so we access the href attribute instead
48
+ return cy.navigateToHref('#idp_1001962798_button');
49
+ }).then((fullUrl: string) => {
50
+ // Navigate to the Harvard login page using originWithKaixa
51
+ cy.origin('https://harvard.idp.cirrusidentity.com', {
52
+ args: { username, password, fullUrl },
53
+ }, (args) => {
54
+ Cypress.require('dceky');
55
+ const {
56
+ username: harvardUsername,
57
+ password: harvardPassword,
58
+ fullUrl: harvardLoginUrl,
59
+ } = args;
60
+
61
+ cy.on('uncaught:exception', (e: Error) => {
62
+ // Ignore known HarvardKey errors
63
+ if (!e.message.includes('ready is not defined')) {
64
+ throw e;
65
+ }
66
+ return false;
67
+ });
68
+
69
+ // Visit the Harvard login page
70
+ cy.visit(harvardLoginUrl);
71
+
72
+ // Wait for and fill in the login form
73
+ cy.waitForElementVisible('#username');
74
+ cy.waitForElementVisible('#password');
75
+
76
+ cy.typeInto({ item: '#username', text: harvardUsername });
77
+ cy.typeInto({ item: '#password', text: harvardPassword });
78
+
79
+ // Submit the form
80
+ cy.get('input[type="submit"], button[type="submit"], .btn-primary').click();
81
+ });
82
+ });
83
+ },
84
+ );
85
+ };
86
+
87
+ /*----------------------------------------*/
88
+ /* --------------- Export --------------- */
89
+ /*----------------------------------------*/
90
+
91
+ export default handleHarvardKey;
@@ -0,0 +1,120 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Log into Canvas and launch an LTI app as a specific user from environment variables.
12
+ * The user should be defined as an environment variable with the following properties:
13
+ * - accessToken: Canvas access token for the user (required for production mode)
14
+ * - type: User type ('student', 'ta', 'teacher') for local mode (defaults to 'teacher')
15
+ * - simIndex: Simulator index for local mode (defaults to '0')
16
+ * @author Yuen Ler Chow
17
+ * @param name the name of the user environment variable
18
+ * @param opts object containing all arguments
19
+ * @param [opts.courseId] the Canvas ID of the course to launch from
20
+ * @param [opts.appName] the name of the app as it appears in the course's left-hand nav
21
+ * @return Cypress chainable (void) - launches the LTI app, no return value
22
+ */
23
+ launchAs(
24
+ name: string,
25
+ opts?: {
26
+ courseId?: number,
27
+ appName?: string,
28
+ }
29
+ ): Chainable<void>;
30
+ }
31
+ }
32
+ }
33
+
34
+ /*----------------------------------------*/
35
+ /* --------------- Command -------------- */
36
+ /*----------------------------------------*/
37
+
38
+ const launchAs = () => {
39
+ Cypress.Commands.add(
40
+ 'launchAs',
41
+ (
42
+ name: string,
43
+ opts: {
44
+ courseId?: number,
45
+ appName?: string,
46
+ } = {},
47
+ ) => {
48
+ let { courseId, appName } = opts;
49
+
50
+ // Get courseId from environment variables if not provided
51
+ if (!courseId) {
52
+ const envCourseId = Cypress.env('courseId');
53
+ if (!envCourseId) {
54
+ throw new Error('Could not find courseId in environment variables');
55
+ }
56
+ courseId = Number.parseInt(envCourseId, 10);
57
+ }
58
+
59
+ // Get appName from environment variables if not provided
60
+ appName = appName || Cypress.env('appName');
61
+ if (!appName) {
62
+ throw new Error('Could not find appName in environment variables');
63
+ }
64
+
65
+ // Get user info from environment variables
66
+ const userInfo = Cypress.env(name);
67
+ if (!userInfo) {
68
+ throw new Error(`Could not find ${name} in environment variables`);
69
+ }
70
+
71
+ cy.log(`🚀 Launch as ${name}`);
72
+
73
+ // Check if this is a local launch
74
+ const isLocal = Cypress.env('local') === 'true';
75
+
76
+ if (isLocal) {
77
+ // Handle local simulator launch
78
+ const userType = userInfo.type || 'teacher';
79
+ const simIndex = userInfo.simIndex || '0';
80
+ const simLaunchButtonId = `${userType}_${simIndex}-launch-button`;
81
+
82
+ cy.log('Local mode: launching simulator');
83
+ cy.visit('https://localhost:8088/simulator');
84
+
85
+ // Handle potential SSL certificate issues
86
+ cy.get('body').then(($body) => {
87
+ if ($body.find('.ssl').length > 0) {
88
+ cy.log('Handling SSL certificate issue');
89
+ cy.get('#details-button').click();
90
+ cy.get('#proceed-link').click();
91
+ }
92
+ });
93
+
94
+ // Launch the app
95
+ cy.get(`#${simLaunchButtonId}`).click();
96
+
97
+ // Click the authorize button
98
+ cy.get('.authorize-button', { timeout: 1000 }).click();
99
+
100
+ return;
101
+ }
102
+
103
+ // Handle production launch with access token
104
+ const { accessToken } = userInfo;
105
+
106
+ if (!accessToken) {
107
+ throw new Error(`Could not find accessToken for ${name} in environment variables`);
108
+ }
109
+
110
+ // Launch using the access token
111
+ cy.launchLTIUsingToken(accessToken, { courseId, appName });
112
+ },
113
+ );
114
+ };
115
+
116
+ /*----------------------------------------*/
117
+ /* --------------- Export --------------- */
118
+ /*----------------------------------------*/
119
+
120
+ export default launchAs;
@@ -0,0 +1,115 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Log into Canvas using an access token and launch an LTI app
12
+ * @author Yuen Ler Chow
13
+ * @param accessToken the user's Canvas access token
14
+ * @param opts object containing all arguments
15
+ * @param [opts.courseId] the Canvas ID of the course to launch from
16
+ * @param [opts.appName] the name of the app as it appears in the course's left-hand nav
17
+ * @return Cypress chainable (void) - launches the LTI app and navigates to it, no return value
18
+ */
19
+ launchLTIUsingToken(
20
+ accessToken: string,
21
+ opts?: {
22
+ courseId?: number,
23
+ appName?: string,
24
+ }
25
+ ): Chainable<void>;
26
+ }
27
+ }
28
+ }
29
+
30
+ /*----------------------------------------*/
31
+ /* --------------- Command -------------- */
32
+ /*----------------------------------------*/
33
+
34
+ const launchLTIUsingToken = () => {
35
+ Cypress.Commands.add(
36
+ 'launchLTIUsingToken',
37
+ (
38
+ accessToken: string,
39
+ opts: {
40
+ courseId?: number,
41
+ appName?: string,
42
+ } = {},
43
+ ) => {
44
+ let { courseId, appName } = opts;
45
+
46
+ if (!courseId) {
47
+ const envCourseId = Cypress.env('courseId');
48
+ if (!envCourseId) {
49
+ throw new Error('Could not find courseId in environment variables');
50
+ }
51
+ courseId = Number.parseInt(envCourseId, 10);
52
+ }
53
+
54
+ appName = appName || Cypress.env('appName');
55
+ if (!appName) {
56
+ throw new Error('Could not find appName in environment variables');
57
+ }
58
+
59
+ cy.log(`Launch LTI app "${appName}" in course ${courseId} using access token`);
60
+
61
+ // Get the external tools for the course
62
+ cy.visitCanvasGETEndpoint({
63
+ path: `/courses/${courseId}/external_tools`,
64
+ accessToken,
65
+ }).then((externalTools: { [key: string]: any }[]) => {
66
+ cy.log(`Found ${externalTools.length} external tools`);
67
+
68
+ // Find the external tool of interest
69
+ const matchingTool = externalTools.find((externalTool) => {
70
+ // Skip non-nav items
71
+ if (!externalTool.course_navigation || typeof externalTool.course_navigation !== 'object') {
72
+ return false;
73
+ }
74
+
75
+ // Skip non-labeled items
76
+ if (!externalTool.course_navigation.text) {
77
+ return false;
78
+ }
79
+
80
+ // Skip if the app name does not match the expected name
81
+ const thisAppName = externalTool.course_navigation.text.trim().toLowerCase();
82
+ return thisAppName === appName.trim().toLowerCase();
83
+ });
84
+
85
+ const toolId = matchingTool?.id || 0;
86
+ if (matchingTool) {
87
+ cy.log(`Found matching app: "${matchingTool.course_navigation.text}" with ID ${toolId}`);
88
+ }
89
+
90
+ // Make sure we found the app
91
+ if (toolId === 0) {
92
+ throw new Error(`Could not find any apps named "${appName}" in course ${courseId}`);
93
+ }
94
+
95
+ // Get a sessionless launch URL
96
+ cy.visitCanvasGETEndpoint({
97
+ path: `/courses/${courseId}/external_tools/sessionless_launch?id=${toolId}`,
98
+ accessToken,
99
+ }).then((sessionlessLaunchInfo) => {
100
+ const launchURL = sessionlessLaunchInfo.url;
101
+ cy.log(`Launching LTI app at: ${launchURL}`);
102
+
103
+ // Launch the tool
104
+ cy.visit(launchURL);
105
+ });
106
+ });
107
+ },
108
+ );
109
+ };
110
+
111
+ /*----------------------------------------*/
112
+ /* --------------- Export --------------- */
113
+ /*----------------------------------------*/
114
+
115
+ export default launchLTIUsingToken;
@@ -0,0 +1,60 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Navigate to href attribute of Harvard identity provider button
12
+ * @author Yuen Ler Chow
13
+ * @param item the CSS selector of interest
14
+ * @return Cypress chainable containing the full URL string (hostname + href attribute value)
15
+ */
16
+ navigateToHref(item: string): Chainable<string>;
17
+ }
18
+ }
19
+ }
20
+
21
+ /*----------------------------------------*/
22
+ /* --------------- Command -------------- */
23
+ /*----------------------------------------*/
24
+
25
+ const navigateToHref = () => {
26
+ Cypress.Commands.add(
27
+ 'navigateToHref',
28
+ (
29
+ item: string,
30
+ ) => {
31
+ cy.waitForElementVisible(item);
32
+
33
+ return cy
34
+ .url()
35
+ // get origin (protocol + '//' + host) from current URL
36
+ .then((url: string) => {
37
+ const { protocol, host } = new URL(url);
38
+ return `${protocol}//${host}`;
39
+ })
40
+ .then((origin: string) => {
41
+ return cy
42
+ .get(item)
43
+ .invoke('attr', 'href')
44
+ .then((href: string) => {
45
+ // If the href is a relative URL, prepend the hostname
46
+ if (href.startsWith('/')) {
47
+ return origin + href;
48
+ }
49
+ return href;
50
+ });
51
+ });
52
+ },
53
+ );
54
+ };
55
+
56
+ /*----------------------------------------*/
57
+ /* --------------- Export --------------- */
58
+ /*----------------------------------------*/
59
+
60
+ export default navigateToHref;
@@ -0,0 +1,44 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Run a script on the page
12
+ * @author Yuen Ler Chow
13
+ * @param script the script to run
14
+ * @return Cypress chainable containing the return value from executing the script
15
+ */
16
+ runScript(scriptLines: string): Chainable<any>;
17
+ }
18
+ }
19
+ }
20
+
21
+ /*----------------------------------------*/
22
+ /* --------------- Command -------------- */
23
+ /*----------------------------------------*/
24
+
25
+ const runScript = () => {
26
+ Cypress.Commands.add(
27
+ 'runScript',
28
+ (
29
+ script: string,
30
+ ) => {
31
+ cy.log('Running script');
32
+ return cy.window().then((win) => {
33
+ const result = win.eval(script);
34
+ return cy.wrap(result);
35
+ });
36
+ },
37
+ );
38
+ };
39
+
40
+ /*----------------------------------------*/
41
+ /* --------------- Export --------------- */
42
+ /*----------------------------------------*/
43
+
44
+ export default runScript;
@@ -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,47 @@
1
+
2
+ // Import shared types
3
+ import type GlobalsOrProfile from '../types/GlobalsOrProfile';
4
+ import type DependentValue from '../types/DependentValue';
5
+
6
+ /**
7
+ * Resolve all dependent values using already-merged basics.
8
+ * @author Gardenia Liu
9
+ * @param dependentMaps with dependencies on basics
10
+ * @param basics entries
11
+ * @returns env with all dependent values resolved
12
+ */
13
+ const resolveDependents = (
14
+ dependentMaps: { [k: string]: DependentValue }[],
15
+ basics: GlobalsOrProfile,
16
+ ): GlobalsOrProfile => {
17
+ const resolved: GlobalsOrProfile = {};
18
+
19
+ // Loop through all of the dependency maps
20
+ dependentMaps.forEach((depMap) => {
21
+ // Loop through all entries within dependency map
22
+ Object.entries(depMap).forEach(([k, depVal]) => {
23
+ // Get basic selector the value depends on
24
+ const selector = basics[depVal.dependsOn];
25
+
26
+ // Create table of values selector can be
27
+ const { dependsOn, ...table } = depVal as { [k: string]: unknown };
28
+
29
+ // Get the selector's picked value
30
+ const picked = table[selector];
31
+
32
+ if (picked !== undefined) {
33
+ resolved[k] = picked;
34
+ } else if ('default' in table) {
35
+ resolved[k] = table.default;
36
+ } else {
37
+ // eslint-disable-next-line max-len
38
+ console.warn(`[cypress.config] Could not resolve ${k}, dependsOn=${dependsOn}, selector=${String(selector)}`);
39
+ process.exit(1);
40
+ }
41
+ });
42
+ });
43
+
44
+ return resolved;
45
+ };
46
+
47
+ export default resolveDependents;