dceky 1.0.0-beta-yuenler.1 → 1.0.0-beta-auto-import.1

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 (67) hide show
  1. package/.eslintrc.js +93 -0
  2. package/lib/commands/assertDoesNotHaveClass.d.ts +2 -1
  3. package/lib/commands/assertDoesNotHaveClass.js.map +1 -1
  4. package/lib/commands/assertHasClass.d.ts +2 -1
  5. package/lib/commands/assertHasClass.js.map +1 -1
  6. package/lib/commands/assertNumElements.d.ts +2 -1
  7. package/lib/commands/assertNumElements.js.map +1 -1
  8. package/lib/commands/clickWithRetry.d.ts +8 -4
  9. package/lib/commands/clickWithRetry.js +3 -3
  10. package/lib/commands/clickWithRetry.js.map +1 -1
  11. package/lib/commands/getNumElements.d.ts +4 -4
  12. package/lib/commands/getNumElements.js +1 -1
  13. package/lib/commands/getNumElements.js.map +1 -1
  14. package/lib/commands/handleHarvardKey.d.ts +9 -3
  15. package/lib/commands/handleHarvardKey.js +20 -24
  16. package/lib/commands/handleHarvardKey.js.map +1 -1
  17. package/lib/commands/handleHarvardKey2.d.ts +14 -0
  18. package/lib/commands/handleHarvardKey2.js +88 -0
  19. package/lib/commands/handleHarvardKey2.js.map +1 -0
  20. package/lib/commands/launchAs.d.ts +14 -12
  21. package/lib/commands/launchAs.js +24 -23
  22. package/lib/commands/launchAs.js.map +1 -1
  23. package/lib/commands/launchLTIUsingToken.d.ts +9 -4
  24. package/lib/commands/launchLTIUsingToken.js +25 -14
  25. package/lib/commands/launchLTIUsingToken.js.map +1 -1
  26. package/lib/commands/navigateToHref.d.ts +4 -9
  27. package/lib/commands/navigateToHref.js +10 -3
  28. package/lib/commands/navigateToHref.js.map +1 -1
  29. package/lib/commands/runScript.d.ts +3 -3
  30. package/lib/commands/runScript.js +2 -6
  31. package/lib/commands/runScript.js.map +1 -1
  32. package/lib/commands/typeInto.d.ts +5 -2
  33. package/lib/commands/typeInto.js +14 -4
  34. package/lib/commands/typeInto.js.map +1 -1
  35. package/lib/commands/visitCanvasGETEndpoint.d.ts +2 -2
  36. package/lib/commands/visitCanvasGETEndpoint.js +4 -4
  37. package/lib/commands/visitCanvasGETEndpoint.js.map +1 -1
  38. package/lib/commands/waitForElementVisible.d.ts +3 -2
  39. package/lib/commands/waitForElementVisible.js +1 -2
  40. package/lib/commands/waitForElementVisible.js.map +1 -1
  41. package/lib/genCommandPaths.d.ts +9 -0
  42. package/lib/genCommandPaths.js +86 -0
  43. package/lib/genCommandPaths.js.map +1 -0
  44. package/lib/index.d.ts +2 -1
  45. package/lib/index.js +3 -3
  46. package/lib/index.js.map +1 -1
  47. package/lib/init.d.ts +1 -5
  48. package/lib/init.js +71 -33
  49. package/lib/init.js.map +1 -1
  50. package/package.json +8 -2
  51. package/src/commands/assertDoesNotHaveClass.ts +20 -9
  52. package/src/commands/assertHasClass.ts +20 -9
  53. package/src/commands/assertNumElements.ts +20 -6
  54. package/src/commands/getNumElements.ts +17 -10
  55. package/src/commands/handleHarvardKey.ts +68 -53
  56. package/src/commands/launchAs.ts +79 -63
  57. package/src/commands/launchLTIUsingToken.ts +78 -47
  58. package/src/commands/navigateToHref.ts +28 -17
  59. package/src/commands/runScript.ts +16 -11
  60. package/src/commands/typeInto.ts +54 -26
  61. package/src/commands/visitCanvasGETEndpoint.ts +29 -16
  62. package/src/commands/waitForElementVisible.ts +19 -10
  63. package/src/genCommandPaths.ts +65 -0
  64. package/src/index.ts +2 -3
  65. package/src/init.ts +80 -34
  66. package/.eslintrc.json +0 -58
  67. package/src/commands/clickWithRetry.ts +0 -51
@@ -9,15 +9,18 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Assert that an element does not have a specific className
12
+ * @author Yuen Ler Chow
12
13
  * @param opts object containing all arguments
13
14
  * @param opts.item the css selector for finding the element
14
15
  * @param opts.className the className to check for
15
- * @example cy.assertDoesNotHaveClass({ item: '.my-button', className: 'disabled' })
16
+ * @return Cypress chainable containing the element that was checked
16
17
  */
17
- assertDoesNotHaveClass(opts: {
18
- item: string;
19
- className: string;
20
- }): Chainable<JQuery<HTMLElement>>;
18
+ assertDoesNotHaveClass(
19
+ opts: {
20
+ item: string,
21
+ className: string,
22
+ },
23
+ ): Chainable<JQuery<HTMLElement>>;
21
24
  }
22
25
  }
23
26
  }
@@ -27,10 +30,18 @@ declare global {
27
30
  /*----------------------------------------*/
28
31
 
29
32
  const assertDoesNotHaveClass = () => {
30
- Cypress.Commands.add('assertDoesNotHaveClass', (opts: { item: string; className: string }) => {
31
- cy.log(`Assert ${opts.item} does not have class ${opts.className}`);
32
- return cy.get(opts.item).should('not.have.class', opts.className);
33
- });
33
+ Cypress.Commands.add(
34
+ 'assertDoesNotHaveClass',
35
+ (
36
+ opts: {
37
+ item: string,
38
+ className: string,
39
+ },
40
+ ) => {
41
+ cy.log(`Assert ${opts.item} does not have class ${opts.className}`);
42
+ return cy.get(opts.item).should('not.have.class', opts.className);
43
+ },
44
+ );
34
45
  };
35
46
 
36
47
  /*----------------------------------------*/
@@ -9,15 +9,18 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Assert that an element has a specific className
12
+ * @author Yuen Ler Chow
12
13
  * @param opts object containing all arguments
13
14
  * @param opts.item the css selector for finding the element
14
15
  * @param opts.className the className to check for
15
- * @example cy.assertHasClass({ item: '.my-button', className: 'active' })
16
+ * @return Cypress chainable containing the element that was checked
16
17
  */
17
- assertHasClass(opts: {
18
- item: string;
19
- className: string;
20
- }): Chainable<JQuery<HTMLElement>>;
18
+ assertHasClass(
19
+ opts: {
20
+ item: string,
21
+ className: string,
22
+ },
23
+ ): Chainable<JQuery<HTMLElement>>;
21
24
  }
22
25
  }
23
26
  }
@@ -27,10 +30,18 @@ declare global {
27
30
  /*----------------------------------------*/
28
31
 
29
32
  const assertHasClass = () => {
30
- Cypress.Commands.add('assertHasClass', (opts: { item: string; className: string }) => {
31
- cy.log(`Assert ${opts.item} has class ${opts.className}`);
32
- return cy.get(opts.item).should('have.class', opts.className);
33
- });
33
+ Cypress.Commands.add(
34
+ 'assertHasClass',
35
+ (
36
+ opts: {
37
+ item: string,
38
+ className: string,
39
+ },
40
+ ) => {
41
+ cy.log(`Assert ${opts.item} has class ${opts.className}`);
42
+ return cy.get(opts.item).should('have.class', opts.className);
43
+ },
44
+ );
34
45
  };
35
46
 
36
47
  /*----------------------------------------*/
@@ -9,12 +9,18 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Assert a certain number of elements
12
+ * @author Yuen Ler Chow
12
13
  * @param opts object containing all arguments
13
14
  * @param opts.item a CSS selector corresponding to the item
14
15
  * @param opts.num the precise number of elements expected
15
- * @example cy.assertNumElements({ item: '.list-item', num: 5 })
16
+ * @return Cypress chainable
16
17
  */
17
- assertNumElements(opts: { item: string; num: number }): Chainable<Element>;
18
+ assertNumElements(
19
+ opts: {
20
+ item: string,
21
+ num: number,
22
+ }
23
+ ): Chainable<Element>;
18
24
  }
19
25
  }
20
26
  }
@@ -24,10 +30,18 @@ declare global {
24
30
  /*----------------------------------------*/
25
31
 
26
32
  const assertNumElements = () => {
27
- Cypress.Commands.add('assertNumElements', (opts: { item: string; num: number }) => {
28
- cy.log(`Assert ${opts.num} elements match ${opts.item}`);
29
- cy.get(opts.item).should('have.length', opts.num);
30
- });
33
+ Cypress.Commands.add(
34
+ 'assertNumElements',
35
+ (
36
+ opts: {
37
+ item: string,
38
+ num: number,
39
+ },
40
+ ) => {
41
+ cy.log(`Assert ${opts.num} elements match ${opts.item}`);
42
+ cy.get(opts.item).should('have.length', opts.num);
43
+ },
44
+ );
31
45
  };
32
46
 
33
47
  /*----------------------------------------*/
@@ -9,11 +9,13 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Get number of elements
12
- * @param item a CSS selector corresponding to the item
13
- * @returns the number of elements on the page
14
- * @example cy.getNumElements('.list-item').then((count) => { ... })
12
+ * @author Yuen Ler Chow
13
+ * @param selector a CSS selector corresponding to the item
14
+ * @return Cypress chainable containing the number of elements matching the selector
15
15
  */
16
- getNumElements(item: string): Chainable<JQuery<any>>;
16
+ getNumElements(
17
+ selector: string,
18
+ ): Chainable<number>;
17
19
  }
18
20
  }
19
21
  }
@@ -23,12 +25,17 @@ declare global {
23
25
  /*----------------------------------------*/
24
26
 
25
27
  const getNumElements = () => {
26
- Cypress.Commands.add('getNumElements', (selector: string) => {
27
- cy.log(`Count elements matching ${selector}`);
28
- return cy.get(selector).then(($elements: any) => {
29
- return cy.wrap($elements.length);
30
- });
31
- });
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
+ );
32
39
  };
33
40
 
34
41
  /*----------------------------------------*/
@@ -9,10 +9,18 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Handle a HarvardKey login page for a user
12
- * @param url the URL to visit for HarvardKey authentication
13
- * @example cy.handleHarvardKey('https://canvas.harvard.edu/login/saml')
12
+ * @author Yuen Ler Chow
13
+ * @param opts object containing all arguments
14
+ * @param opts.url the URL to visit for HarvardKey authentication
15
+ * @param opts.name the name of the user environment variable
16
+ * @return Cypress chainable (void) - performs authentication flow, no return value
14
17
  */
15
- handleHarvardKey(url: string): Chainable<void>;
18
+ handleHarvardKey(
19
+ opts: {
20
+ url: string,
21
+ name: string,
22
+ }
23
+ ): Chainable<void>;
16
24
  }
17
25
  }
18
26
  }
@@ -22,62 +30,69 @@ declare global {
22
30
  /*----------------------------------------*/
23
31
 
24
32
  const handleHarvardKey = () => {
25
- Cypress.Commands.add('handleHarvardKey', (url: string) => {
26
- cy.log('Handling HarvardKey authentication');
27
-
28
- // Get credentials from environment variables
29
- const username = (window as any).Cypress.env('HARVARD_USERNAME');
30
- const password = (window as any).Cypress.env('HARVARD_PASSWORD');
31
-
32
- if (!username) {
33
- throw new Error(`HARVARD_USERNAME environment variable is not set`);
34
- }
35
-
36
- if (!password) {
37
- throw new Error(`HARVARD_PASSWORD environment variable is not set`);
38
- }
33
+ Cypress.Commands.add(
34
+ 'handleHarvardKey',
35
+ (
36
+ opts: {
37
+ url: string,
38
+ name: string,
39
+ },
40
+ ) => {
41
+ const { url, name } = opts;
42
+ cy.log('Handling HarvardKey authentication');
39
43
 
40
- cy.visit(url);
44
+ const userInfo = Cypress.env(name);
45
+ if (!userInfo) {
46
+ throw new Error(`Could not find ${name} in environment variables`);
47
+ }
41
48
 
42
- // Get the Harvard login URL using originWithKaixa with auto-initialized functions
43
- cy.origin('https://apps.cirrusidentity.com', () => {
44
- Cypress.require('dceky');
49
+ // Destructure the user info object to get the username and password
50
+ const { username } = userInfo;
51
+ const { password } = userInfo;
45
52
 
46
- return cy.navigateToHref({
47
- item: '#idp_1001962798_button',
48
- domain: 'https://apps.cirrusidentity.com'
49
- });
50
- }).then((fullUrl: any) => {
51
- // Navigate to the Harvard login page using originWithKaixa
52
- cy.origin('https://harvard.idp.cirrusidentity.com', {
53
- args: { username, password, fullUrl }
54
- }, ({ args }: any) => {
53
+ cy.visit(url);
54
+
55
+ // Get the Harvard login URL using originWithKaixa with auto-initialized functions
56
+ cy.origin('https://apps.cirrusidentity.com', () => {
55
57
  Cypress.require('dceky');
56
- const { username, password, fullUrl } = args;
57
-
58
- cy.on('uncaught:exception', (e: any) => {
59
- // Ignore known HarvardKey errors
60
- if (!e.message.includes('ready is not defined')) {
61
- throw e;
62
- }
63
- return false;
58
+ // Clicking the button doesn't work for some reason, so we access the href attribute instead
59
+ return cy.navigateToHref('#idp_1001962798_button');
60
+ }).then((fullUrl: string) => {
61
+ // Navigate to the Harvard login page using originWithKaixa
62
+ cy.origin('https://harvard.idp.cirrusidentity.com', {
63
+ args: { username, password, fullUrl },
64
+ }, (args) => {
65
+ Cypress.require('dceky');
66
+ const {
67
+ username: harvardUsername,
68
+ password: harvardPassword,
69
+ fullUrl: harvardLoginUrl,
70
+ } = args;
71
+
72
+ cy.on('uncaught:exception', (e: Error) => {
73
+ // Ignore known HarvardKey errors
74
+ if (!e.message.includes('ready is not defined')) {
75
+ throw e;
76
+ }
77
+ return false;
78
+ });
79
+
80
+ // Visit the Harvard login page
81
+ cy.visit(harvardLoginUrl);
82
+
83
+ // Wait for and fill in the login form
84
+ cy.waitForElementVisible('#username');
85
+ cy.waitForElementVisible('#password');
86
+
87
+ cy.typeInto({ item: '#username', text: harvardUsername });
88
+ cy.typeInto({ item: '#password', text: harvardPassword });
89
+
90
+ // Submit the form
91
+ cy.get('input[type="submit"], button[type="submit"], .btn-primary').click();
64
92
  });
65
-
66
- cy.visit(fullUrl.toString());
67
-
68
- // Wait for and fill in the login form using enhanced functions
69
- cy.waitForElementVisible('#username');
70
- cy.waitForElementVisible('#password');
71
-
72
- // Add credentials using enhanced functions
73
- cy.typeInto({ item: '#username', text: username });
74
- cy.typeInto({ item: '#password', text: password });
75
-
76
- // Submit the form using clickWithRetry
77
- cy.clickWithRetry('input[type="submit"], button[type="submit"], .btn-primary');
78
93
  });
79
- });
80
- });
94
+ },
95
+ );
81
96
  };
82
97
 
83
98
  /*----------------------------------------*/
@@ -9,19 +9,24 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Log into Canvas and launch an LTI app as a specific user from environment variables.
12
- * The user should be defined as environment variables with the following properties:
13
- * - {name}_ACCESS_TOKEN: Canvas access token for the user
14
- * - {name}_USERNAME: Username (if no access token)
15
- * - {name}_PASSWORD: Password (if using username)
16
- * - {name}_IS_XID: Set to 'true' if using XID login
17
- * - {name}_TYPE: User type ('student', 'ta', 'teacher') for local mode
18
- * - {name}_SIM_INDEX: Simulator index for local mode
19
- * @param name the name of the user (used as prefix for environment variables)
20
- * @param courseId the Canvas ID of the course to launch from (uses COURSE_ID env var if not provided)
21
- * @param appName the name of the app as it appears in the course's left-hand nav (uses APP_NAME env var if not provided)
22
- * @example cy.launchAs('student1', 12345, 'My LTI App')
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
23
22
  */
24
- launchAs(name: string, courseId?: number, appName?: string): Chainable<void>;
23
+ launchAs(
24
+ name: string,
25
+ opts?: {
26
+ courseId?: number,
27
+ appName?: string,
28
+ }
29
+ ): Chainable<void>;
25
30
  }
26
31
  }
27
32
  }
@@ -31,70 +36,81 @@ declare global {
31
36
  /*----------------------------------------*/
32
37
 
33
38
  const launchAs = () => {
34
- Cypress.Commands.add('launchAs', (name: string, courseId?: number, appName?: string) => {
35
- cy.log(`🚀 Launch as ${name}`);
36
-
37
- // Get default values from environment variables if not provided
38
- if (!courseId) {
39
- const envCourseId = Cypress.env('COURSE_ID');
40
- if (!envCourseId) {
41
- throw new Error('courseId parameter is required or COURSE_ID environment variable must be set');
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);
42
57
  }
43
- courseId = parseInt(envCourseId, 10);
44
- }
45
58
 
46
- if (!appName) {
47
- appName = Cypress.env('APP_NAME');
59
+ // Get appName from environment variables if not provided
60
+ appName = appName || Cypress.env('appName');
48
61
  if (!appName) {
49
- throw new Error('appName parameter is required or APP_NAME environment variable must be set');
62
+ throw new Error('Could not find appName in environment variables');
50
63
  }
51
- }
52
64
 
53
- // Check if this is a local launch
54
- const isLocal = Cypress.env('LOCAL') === 'true';
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
+ }
55
70
 
56
- if (isLocal) {
57
- // Handle local simulator launch
58
- const userType = Cypress.env(`${name}_TYPE`) || 'teacher';
59
- const simIndex = Cypress.env(`${name}_SIM_INDEX`) || '0';
60
- const simLaunchButtonId = `${userType}_${simIndex}-launch-button`;
71
+ cy.log(`🚀 Launch as ${name}`);
61
72
 
62
- cy.log('Local mode: launching simulator');
63
- cy.visit('https://localhost:8088/simulator');
73
+ // Check if this is a local launch
74
+ const isLocal = Cypress.env('local') === 'true';
64
75
 
65
- // Handle potential SSL certificate issues
66
- cy.get('body').then(($body) => {
67
- if ($body.find('.ssl').length > 0) {
68
- cy.log('Handling SSL certificate issue');
69
- cy.get('#details-button').click();
70
- cy.get('#proceed-link').click();
71
- }
72
- });
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`;
73
81
 
74
- // Launch the app
75
- cy.get(`#${simLaunchButtonId}`).click();
82
+ cy.log('Local mode: launching simulator');
83
+ cy.visit('https://localhost:8088/simulator');
76
84
 
77
- // Check for authorization screen
78
- cy.wait(1000);
79
- cy.get('body').then(($body) => {
80
- if ($body.find('.authorize-button').length > 0) {
81
- cy.get('.authorize-button').click();
82
- }
83
- });
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
+ });
84
93
 
85
- return;
86
- }
94
+ // Launch the app
95
+ cy.get(`#${simLaunchButtonId}`).click();
87
96
 
88
- // Handle production launch with access token
89
- const accessToken = Cypress.env(`${name}_ACCESS_TOKEN`);
90
-
91
- if (!accessToken) {
92
- throw new Error(`${name}_ACCESS_TOKEN environment variable is required for user "${name}"`);
93
- }
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
+ }
94
109
 
95
- // Launch using the access token
96
- cy.launchLTIUsingToken(accessToken, courseId, appName);
97
- });
110
+ // Launch using the access token
111
+ cy.launchLTIUsingToken(accessToken, { courseId, appName });
112
+ },
113
+ );
98
114
  };
99
115
 
100
116
  /*----------------------------------------*/
@@ -9,12 +9,20 @@ declare global {
9
9
  interface Chainable {
10
10
  /**
11
11
  * Log into Canvas using an access token and launch an LTI app
12
+ * @author Yuen Ler Chow
12
13
  * @param accessToken the user's Canvas access token
13
- * @param courseId the Canvas ID of the course to launch from
14
- * @param appName the name of the app as it appears in the course's left-hand nav
15
- * @example cy.launchLTIUsingToken('your_access_token', 12345, 'My LTI App')
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
16
18
  */
17
- launchLTIUsingToken(accessToken: string, courseId: number, appName: string): Chainable<void>;
19
+ launchLTIUsingToken(
20
+ accessToken: string,
21
+ opts?: {
22
+ courseId?: number,
23
+ appName?: string,
24
+ }
25
+ ): Chainable<void>;
18
26
  }
19
27
  }
20
28
  }
@@ -24,57 +32,80 @@ declare global {
24
32
  /*----------------------------------------*/
25
33
 
26
34
  const launchLTIUsingToken = () => {
27
- Cypress.Commands.add('launchLTIUsingToken', (accessToken: string, courseId: number, appName: string) => {
28
- cy.log(`Launch LTI app "${appName}" in course ${courseId} using access token`);
29
-
30
- // Get the external tools for the course
31
- cy.visitCanvasGETEndpoint({
32
- path: `/courses/${courseId}/external_tools`,
33
- accessToken: accessToken
34
- }).then((externalTools: any[]) => {
35
- cy.log(`Found ${externalTools.length} external tools`);
36
-
37
- // Find the external tool of interest
38
- let toolId = 0;
39
-
40
- for (const externalTool of externalTools) {
41
- // Skip non-nav items
42
- if (!externalTool.course_navigation || typeof externalTool.course_navigation !== 'object') {
43
- continue;
44
- }
45
-
46
- // Skip non-labeled items
47
- if (!externalTool.course_navigation.text) {
48
- continue;
49
- }
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;
50
45
 
51
- // Check if this app matches the name
52
- const thisAppName = externalTool.course_navigation.text.trim().toLowerCase();
53
- if (thisAppName === appName.trim().toLowerCase()) {
54
- toolId = externalTool.id;
55
- cy.log(`Found matching app: "${externalTool.course_navigation.text}" with ID ${toolId}`);
56
- break;
46
+ if (!courseId) {
47
+ const envCourseId = Cypress.env('courseId');
48
+ if (!envCourseId) {
49
+ throw new Error('Could not find courseId in environment variables');
57
50
  }
51
+ courseId = Number.parseInt(envCourseId, 10);
58
52
  }
59
53
 
60
- // Make sure we found the app
61
- if (toolId === 0) {
62
- throw new Error(`Could not find any apps named "${appName}" in course ${courseId}`);
54
+ appName = appName || Cypress.env('appName');
55
+ if (!appName) {
56
+ throw new Error('Could not find appName in environment variables');
63
57
  }
64
58
 
65
- // Get a sessionless launch URL
59
+ cy.log(`Launch LTI app "${appName}" in course ${courseId} using access token`);
60
+
61
+ // Get the external tools for the course
66
62
  cy.visitCanvasGETEndpoint({
67
- path: `/courses/${courseId}/external_tools/sessionless_launch?id=${toolId}`,
68
- accessToken: accessToken
69
- }).then((sessionlessLaunchInfo: any) => {
70
- const launchURL = sessionlessLaunchInfo.url;
71
- cy.log(`Launching LTI app at: ${launchURL}`);
72
-
73
- // Launch the tool
74
- cy.visit(launchURL);
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
+ });
75
106
  });
76
- });
77
- });
107
+ },
108
+ );
78
109
  };
79
110
 
80
111
  /*----------------------------------------*/