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,50 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Assert a certain number of elements
12
+ * @author Yuen Ler Chow
13
+ * @param opts object containing all arguments
14
+ * @param opts.item a CSS selector corresponding to the item
15
+ * @param opts.num the precise number of elements expected
16
+ */
17
+ assertNumElements(
18
+ opts: {
19
+ item: string,
20
+ num: number,
21
+ }
22
+ ): Chainable<Element>;
23
+ }
24
+ }
25
+ }
26
+
27
+ /*----------------------------------------*/
28
+ /* --------------- Command -------------- */
29
+ /*----------------------------------------*/
30
+
31
+ const assertNumElements = () => {
32
+ Cypress.Commands.add(
33
+ 'assertNumElements',
34
+ (
35
+ opts: {
36
+ item: string,
37
+ num: number,
38
+ },
39
+ ) => {
40
+ cy.log(`Assert ${opts.num} elements match ${opts.item}`);
41
+ cy.get(opts.item).should('have.length', opts.num);
42
+ },
43
+ );
44
+ };
45
+
46
+ /*----------------------------------------*/
47
+ /* --------------- Export --------------- */
48
+ /*----------------------------------------*/
49
+
50
+ export default assertNumElements;
@@ -0,0 +1,52 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Obtain data from a class attribute that starts with a specific prefix
12
+ * and return it
13
+ * @author Allison Zhang
14
+ * @param selector the css selector for finding the element
15
+ * @param classPrefix the prefix to look for in the class attribute
16
+ * @returns the extracted data
17
+ * @example cy.extractDataFromClass('.item', 'data-').then((data) => { ... })
18
+ */
19
+ extractDataFromClass(
20
+ selector: string,
21
+ classPrefix: string,
22
+ ): Chainable<string[]>;
23
+ }
24
+ }
25
+ }
26
+
27
+ /*----------------------------------------*/
28
+ /* --------------- Command -------------- */
29
+ /*----------------------------------------*/
30
+
31
+ const extractDataFromClass = () => {
32
+ Cypress.Commands.add('extractDataFromClass', (selector: string, classPrefix: string) => {
33
+ return (
34
+ cy
35
+ .get(selector)
36
+ .invoke('attr', 'class')
37
+ .then((classAttr: string) => {
38
+ const classList = classAttr.split(' ');
39
+ const data = classList
40
+ .filter((className) => { return className.startsWith(classPrefix); })
41
+ .map((className) => { return className.replace(classPrefix, ''); });
42
+ return data;
43
+ })
44
+ );
45
+ });
46
+ };
47
+
48
+ /*----------------------------------------*/
49
+ /* --------------- Export --------------- */
50
+ /*----------------------------------------*/
51
+
52
+ export default extractDataFromClass;
@@ -0,0 +1,55 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Obtain data from a class attribute that starts with a specific prefix
12
+ * and with specific contents, and return it
13
+ * @author Allison Zhang
14
+ * @param contents the contents of the element to find
15
+ * @param selector the css selector for finding the element
16
+ * @param classPrefix the prefix to look for in the class attribute
17
+ * @returns the extracted data
18
+ * @example cy.extractDataFromClassByContents('Submit', '.button', 'btn-').then((data) => { ... })
19
+ */
20
+ extractDataFromClassByContents(
21
+ contents: string,
22
+ selector: string,
23
+ classPrefix: string,
24
+ ): Chainable<string[]>;
25
+ }
26
+ }
27
+ }
28
+
29
+ /*----------------------------------------*/
30
+ /* --------------- Command -------------- */
31
+ /*----------------------------------------*/
32
+
33
+ const extractDataFromClassByContents = () => {
34
+ Cypress.Commands.add('extractDataFromClassByContents', (contents: string, selector: string, classPrefix: string) => {
35
+ return (
36
+ cy
37
+ .get(selector)
38
+ .contains(contents)
39
+ .invoke('attr', 'class')
40
+ .then((classAttr: string) => {
41
+ const classList = classAttr.split(' ');
42
+ const data = classList
43
+ .filter((className) => { return className.startsWith(classPrefix); })
44
+ .map((className) => { return className.replace(classPrefix, ''); });
45
+ return data;
46
+ })
47
+ );
48
+ });
49
+ };
50
+
51
+ /*----------------------------------------*/
52
+ /* --------------- Export --------------- */
53
+ /*----------------------------------------*/
54
+
55
+ export default extractDataFromClassByContents;
@@ -0,0 +1,45 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Obtain JSON data from a request and return it.
12
+ * @author Allison Zhang
13
+ * @param url the URL to fetch the JSON data from
14
+ * @returns the JSON data
15
+ * @example cy.getJSON('https://api.example.com/data').then((data) => { ... })
16
+ */
17
+ getJSON(
18
+ url: string,
19
+ ): Chainable<any>;
20
+ }
21
+ }
22
+ }
23
+
24
+ /*----------------------------------------*/
25
+ /* --------------- Command -------------- */
26
+ /*----------------------------------------*/
27
+
28
+ const getJSON = () => {
29
+ Cypress.Commands.add('getJSON', (url: string) => {
30
+ return (
31
+ cy
32
+ .request(url)
33
+ .its('body')
34
+ .then((body) => {
35
+ return body;
36
+ })
37
+ );
38
+ });
39
+ };
40
+
41
+ /*----------------------------------------*/
42
+ /* --------------- Export --------------- */
43
+ /*----------------------------------------*/
44
+
45
+ export default getJSON;
@@ -0,0 +1,45 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Get number of elements
12
+ * @author Yuen Ler Chow
13
+ * @param selector a CSS selector corresponding to the item to count
14
+ * @return Cypress chainable containing the number of elements matching the selector
15
+ */
16
+ getNumElements(
17
+ selector: string,
18
+ ): Chainable<number>;
19
+ }
20
+ }
21
+ }
22
+
23
+ /*----------------------------------------*/
24
+ /* --------------- Command -------------- */
25
+ /*----------------------------------------*/
26
+
27
+ const getNumElements = () => {
28
+ Cypress.Commands.add(
29
+ 'getNumElements',
30
+ (
31
+ selector: string,
32
+ ) => {
33
+ cy.log(`Count elements matching ${selector}`);
34
+ return cy.get(selector).then(($elements: JQuery<HTMLElement>) => {
35
+ return Cypress.$($elements).length;
36
+ });
37
+ },
38
+ );
39
+ };
40
+
41
+ /*----------------------------------------*/
42
+ /* --------------- Export --------------- */
43
+ /*----------------------------------------*/
44
+
45
+ export default getNumElements;
@@ -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;