dceky 1.0.0-beta-profile.1 → 1.0.0-beta-yuenler.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 (75) hide show
  1. package/lib/commands/assertDoesNotHaveClass.d.ts +19 -0
  2. package/lib/commands/assertDoesNotHaveClass.js +17 -0
  3. package/lib/commands/assertDoesNotHaveClass.js.map +1 -0
  4. package/lib/commands/assertHasClass.d.ts +19 -0
  5. package/lib/commands/assertHasClass.js +17 -0
  6. package/lib/commands/assertHasClass.js.map +1 -0
  7. package/lib/commands/assertNumElements.d.ts +19 -0
  8. package/lib/commands/assertNumElements.js +17 -0
  9. package/lib/commands/assertNumElements.js.map +1 -0
  10. package/lib/commands/clickWithRetry.d.ts +16 -0
  11. package/lib/commands/clickWithRetry.js +29 -0
  12. package/lib/commands/clickWithRetry.js.map +1 -0
  13. package/lib/commands/getNumElements.d.ts +15 -0
  14. package/lib/commands/getNumElements.js +19 -0
  15. package/lib/commands/getNumElements.js.map +1 -0
  16. package/lib/commands/handleHarvardKey.d.ts +14 -0
  17. package/lib/commands/handleHarvardKey.js +59 -0
  18. package/lib/commands/handleHarvardKey.js.map +1 -0
  19. package/lib/commands/launchAs.d.ts +23 -0
  20. package/lib/commands/launchAs.js +65 -0
  21. package/lib/commands/launchAs.js.map +1 -0
  22. package/lib/commands/launchLTIUsingToken.d.ts +16 -0
  23. package/lib/commands/launchLTIUsingToken.js +57 -0
  24. package/lib/commands/launchLTIUsingToken.js.map +1 -0
  25. package/lib/commands/navigateToHref.d.ts +20 -0
  26. package/lib/commands/navigateToHref.js +23 -0
  27. package/lib/commands/navigateToHref.js.map +1 -0
  28. package/lib/commands/runScript.d.ts +17 -0
  29. package/lib/commands/runScript.js +25 -0
  30. package/lib/commands/runScript.js.map +1 -0
  31. package/lib/commands/typeInto.d.ts +21 -0
  32. package/lib/commands/typeInto.js +28 -0
  33. package/lib/commands/typeInto.js.map +1 -0
  34. package/lib/commands/visitCanvasGETEndpoint.d.ts +20 -0
  35. package/lib/commands/visitCanvasGETEndpoint.js +26 -0
  36. package/lib/commands/visitCanvasGETEndpoint.js.map +1 -0
  37. package/lib/commands/waitForElementVisible.d.ts +15 -0
  38. package/lib/commands/waitForElementVisible.js +20 -0
  39. package/lib/commands/waitForElementVisible.js.map +1 -0
  40. package/lib/index.js +1 -0
  41. package/lib/index.js.map +1 -1
  42. package/lib/init.d.ts +4 -11
  43. package/lib/init.js +35 -4
  44. package/lib/init.js.map +1 -1
  45. package/package.json +4 -20
  46. package/src/commands/assertDoesNotHaveClass.ts +40 -0
  47. package/src/commands/assertHasClass.ts +40 -0
  48. package/src/commands/assertNumElements.ts +37 -0
  49. package/src/commands/clickWithRetry.ts +51 -0
  50. package/src/commands/getNumElements.ts +38 -0
  51. package/src/commands/handleHarvardKey.ts +87 -0
  52. package/src/commands/launchAs.ts +104 -0
  53. package/src/commands/launchLTIUsingToken.ts +84 -0
  54. package/src/commands/navigateToHref.ts +45 -0
  55. package/src/commands/runScript.ts +42 -0
  56. package/src/commands/typeInto.ts +57 -0
  57. package/src/commands/visitCanvasGETEndpoint.ts +48 -0
  58. package/src/commands/waitForElementVisible.ts +40 -0
  59. package/src/genConfiguration.ts +11 -0
  60. package/src/index.ts +2 -7
  61. package/src/init.ts +26 -2
  62. package/tsconfig.json +0 -1
  63. package/docs/GlobalsAndProfiles.md +0 -23
  64. package/profileChooser/chooseProfile.ts +0 -35
  65. package/profileChooser/helpers/print.ts +0 -155
  66. package/profileChooser/helpers/prompt.ts +0 -23
  67. package/profileChooser/helpers/showChooser.ts +0 -95
  68. package/profileChooser/types/ChooserOption.ts +0 -11
  69. package/src/commands/clickSomething.ts +0 -33
  70. package/src/genConfiguration/helpers/resolveDependents.ts +0 -47
  71. package/src/genConfiguration/helpers/splitEnv.ts +0 -30
  72. package/src/genConfiguration/index.ts +0 -83
  73. package/src/genConfiguration/types/DependentValue.ts +0 -14
  74. package/src/genConfiguration/types/GlobalsOrProfile.ts +0 -12
  75. package/src/genConfiguration/types/SplitEnv.ts +0 -18
@@ -0,0 +1,20 @@
1
+ declare global {
2
+ namespace Cypress {
3
+ interface Chainable {
4
+ /**
5
+ * Makes a GET request to Canvas API endpoint
6
+ * @param opts object containing all arguments
7
+ * @param opts.path The API path (e.g., '/courses', '/users/self')
8
+ * @param opts.accessToken The Canvas access token
9
+ * @returns The API response body
10
+ * @example cy.visitCanvasGETEndpoint({ path: '/courses', accessToken: 'your_token' })
11
+ */
12
+ visitCanvasGETEndpoint(opts: {
13
+ path: string;
14
+ accessToken: string;
15
+ }): Chainable<any>;
16
+ }
17
+ }
18
+ }
19
+ declare const visitCanvasGETEndpoint: () => void;
20
+ export default visitCanvasGETEndpoint;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /// <reference types="cypress" />
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ /*----------------------------------------*/
5
+ /* --------------- Command -------------- */
6
+ /*----------------------------------------*/
7
+ var visitCanvasGETEndpoint = function () {
8
+ Cypress.Commands.add('visitCanvasGETEndpoint', function (opts) {
9
+ cy.log('Visiting Canvas GET endpoint');
10
+ var path = opts.path, accessToken = opts.accessToken;
11
+ cy.log('Canvas API: ' + path);
12
+ return cy.request({
13
+ method: 'GET',
14
+ url: 'https://canvas.harvard.edu/api/v1' + path,
15
+ qs: {
16
+ per_page: 200,
17
+ access_token: accessToken
18
+ }
19
+ }).its('body');
20
+ });
21
+ };
22
+ /*----------------------------------------*/
23
+ /* --------------- Export --------------- */
24
+ /*----------------------------------------*/
25
+ exports.default = visitCanvasGETEndpoint;
26
+ //# sourceMappingURL=visitCanvasGETEndpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitCanvasGETEndpoint.js","sourceRoot":"","sources":["../../src/commands/visitCanvasGETEndpoint.ts"],"names":[],"mappings":";AAAA,iCAAiC;;AAsBjC,4CAA4C;AAC5C,4CAA4C;AAC5C,4CAA4C;AAE5C,IAAM,sBAAsB,GAAG;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,wBAAwB,EAAE,UAAC,IAA2C;QACzF,EAAE,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC/B,IAAA,IAAI,GAAkB,IAAI,KAAtB,EAAE,WAAW,GAAK,IAAI,YAAT,CAAU;QAEnC,EAAE,CAAC,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;QAC9B,OAAO,EAAE,CAAC,OAAO,CAAC;YAChB,MAAM,EAAE,KAAK;YACb,GAAG,EAAE,mCAAmC,GAAG,IAAI;YAC/C,EAAE,EAAE;gBACF,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE,WAAW;aAC1B;SACF,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,4CAA4C;AAC5C,4CAA4C;AAC5C,4CAA4C;AAE5C,kBAAe,sBAAsB,CAAC"}
@@ -0,0 +1,15 @@
1
+ declare global {
2
+ namespace Cypress {
3
+ interface Chainable {
4
+ /**
5
+ * Wait for an element to be visible
6
+ * @param item the CSS selector of interest
7
+ * @param timeoutSec the number of seconds to wait before timing out (default: 10)
8
+ * @example cy.waitForElementVisible('#submit-button', 15)
9
+ */
10
+ waitForElementVisible(item: string, timeoutSec?: number): Chainable<JQuery<HTMLElement>>;
11
+ }
12
+ }
13
+ }
14
+ declare const waitForElementVisible: () => void;
15
+ export default waitForElementVisible;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /// <reference types="cypress" />
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ /*----------------------------------------*/
5
+ /* --------------- Command -------------- */
6
+ /*----------------------------------------*/
7
+ var waitForElementVisible = function () {
8
+ Cypress.Commands.add('waitForElementVisible', function (item, timeoutSec) {
9
+ cy.log('Waiting for element to be visible');
10
+ timeoutSec = timeoutSec || 10;
11
+ var timeoutMs = timeoutSec * 1000;
12
+ cy.log("Wait for ".concat(item, " to be visible"));
13
+ return cy.get(item, { timeout: timeoutMs }).should('be.visible');
14
+ });
15
+ };
16
+ /*----------------------------------------*/
17
+ /* --------------- Export --------------- */
18
+ /*----------------------------------------*/
19
+ exports.default = waitForElementVisible;
20
+ //# sourceMappingURL=waitForElementVisible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waitForElementVisible.js","sourceRoot":"","sources":["../../src/commands/waitForElementVisible.ts"],"names":[],"mappings":";AAAA,iCAAiC;;AAoBjC,4CAA4C;AAC5C,4CAA4C;AAC5C,4CAA4C;AAE5C,IAAM,qBAAqB,GAAG;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,uBAAuB,EAAE,UAAC,IAAY,EAAE,UAAmB;QAC9E,EAAE,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QAC5C,UAAU,GAAG,UAAU,IAAI,EAAE,CAAC;QAE9B,IAAM,SAAS,GAAG,UAAU,GAAG,IAAI,CAAC;QACpC,EAAE,CAAC,GAAG,CAAC,mBAAY,IAAI,mBAAgB,CAAC,CAAC;QACzC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,4CAA4C;AAC5C,4CAA4C;AAC5C,4CAA4C;AAE5C,kBAAe,qBAAqB,CAAC"}
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.init = exports.genConfiguration = void 0;
7
+ // Import helpers
7
8
  var init_1 = __importDefault(require("./init"));
8
9
  exports.init = init_1.default;
9
10
  var genConfiguration_1 = __importDefault(require("./genConfiguration"));
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,gDAA0B;AASxB,eATK,cAAI,CASL;AARN,wEAAkD;AAOhD,2BAPK,0BAAgB,CAOL;AALlB,sDAAsD;AACtD,IAAA,cAAI,GAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,iBAAiB;AACjB,gDAA0B;AASxB,eATK,cAAI,CASL;AARN,wEAAkD;AAOhD,2BAPK,0BAAgB,CAOL;AALlB,sDAAsD;AACtD,IAAA,cAAI,GAAE,CAAC"}
package/lib/init.d.ts CHANGED
@@ -1,13 +1,6 @@
1
- declare global {
2
- namespace Cypress {
3
- interface Chainable {
4
- /**
5
- * Custom command to click an element by selector
6
- * @example cy.clickSomething('.my-button')
7
- */
8
- clickSomething(selector: string): Chainable<Element>;
9
- }
10
- }
11
- }
1
+ /**
2
+ * Initialize custom commands
3
+ * @author Gabe Abrams
4
+ */
12
5
  declare const init: () => void;
13
6
  export default init;
package/lib/init.js CHANGED
@@ -1,10 +1,41 @@
1
1
  "use strict";
2
- /// <reference types="cypress" />
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
3
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ // Import all commands
7
+ var assertDoesNotHaveClass_1 = __importDefault(require("./commands/assertDoesNotHaveClass"));
8
+ var assertHasClass_1 = __importDefault(require("./commands/assertHasClass"));
9
+ var assertNumElements_1 = __importDefault(require("./commands/assertNumElements"));
10
+ var clickWithRetry_1 = __importDefault(require("./commands/clickWithRetry"));
11
+ var getNumElements_1 = __importDefault(require("./commands/getNumElements"));
12
+ var handleHarvardKey_1 = __importDefault(require("./commands/handleHarvardKey"));
13
+ var launchAs_1 = __importDefault(require("./commands/launchAs"));
14
+ var launchLTIUsingToken_1 = __importDefault(require("./commands/launchLTIUsingToken"));
15
+ var navigateToHref_1 = __importDefault(require("./commands/navigateToHref"));
16
+ var runScript_1 = __importDefault(require("./commands/runScript"));
17
+ var typeInto_1 = __importDefault(require("./commands/typeInto"));
18
+ var visitCanvasGETEndpoint_1 = __importDefault(require("./commands/visitCanvasGETEndpoint"));
19
+ var waitForElementVisible_1 = __importDefault(require("./commands/waitForElementVisible"));
20
+ /**
21
+ * Initialize custom commands
22
+ * @author Gabe Abrams
23
+ */
4
24
  var init = function () {
5
- Cypress.Commands.add('clickSomething', function (selector) {
6
- cy.get(selector).click();
7
- });
25
+ // Execute each command adder
26
+ (0, assertDoesNotHaveClass_1.default)();
27
+ (0, assertHasClass_1.default)();
28
+ (0, assertNumElements_1.default)();
29
+ (0, clickWithRetry_1.default)();
30
+ (0, getNumElements_1.default)();
31
+ (0, handleHarvardKey_1.default)();
32
+ (0, launchAs_1.default)();
33
+ (0, launchLTIUsingToken_1.default)();
34
+ (0, navigateToHref_1.default)();
35
+ (0, runScript_1.default)();
36
+ (0, typeInto_1.default)();
37
+ (0, visitCanvasGETEndpoint_1.default)();
38
+ (0, waitForElementVisible_1.default)();
8
39
  };
9
40
  exports.default = init;
10
41
  //# sourceMappingURL=init.js.map
package/lib/init.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":";AAAA,iCAAiC;;AAejC,IAAM,IAAI,GAAG;IACX,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,EAAE,UAAC,QAAgB;QACtD,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,kBAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":";;;;;AAAA,sBAAsB;AACtB,6FAAuE;AACvE,6EAAuD;AACvD,mFAA6D;AAC7D,6EAAuD;AACvD,6EAAuD;AACvD,iFAA2D;AAC3D,iEAA2C;AAC3C,uFAAiE;AACjE,6EAAuD;AACvD,mEAA6C;AAC7C,iEAA2C;AAC3C,6FAAuE;AACvE,2FAAqE;AAErE;;;GAGG;AACH,IAAM,IAAI,GAAG;IACX,6BAA6B;IAC7B,IAAA,gCAAsB,GAAE,CAAC;IACzB,IAAA,wBAAc,GAAE,CAAC;IACjB,IAAA,2BAAiB,GAAE,CAAC;IACpB,IAAA,wBAAc,GAAE,CAAC;IACjB,IAAA,wBAAc,GAAE,CAAC;IACjB,IAAA,0BAAgB,GAAE,CAAC;IACnB,IAAA,kBAAQ,GAAE,CAAC;IACX,IAAA,6BAAmB,GAAE,CAAC;IACtB,IAAA,wBAAc,GAAE,CAAC;IACjB,IAAA,mBAAS,GAAE,CAAC;IACZ,IAAA,kBAAQ,GAAE,CAAC;IACX,IAAA,gCAAsB,GAAE,CAAC;IACzB,IAAA,+BAAqB,GAAE,CAAC;AAC1B,CAAC,CAAC;AAEF,kBAAe,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,17 +1,11 @@
1
1
  {
2
2
  "name": "dceky",
3
- "version": "1.0.0-beta-profile.1",
3
+ "version": "1.0.0-beta-yuenler.1",
4
4
  "description": "Cypress toolkit for Harvard DCE",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
7
7
  "scripts": {
8
- "build": "tsc --project ./tsconfig.json",
9
- "cypress": "npx cypress open",
10
- "cy:open:stage": "cross-env CYPRESS_PROFILE=stage npx cypress open",
11
- "cy:open:prod": "cross-env CYPRESS_PROFILE=prod npx cypress open",
12
- "cy:run:stage": "cross-env CYPRESS_PROFILE=stage npx cypress run",
13
- "cy:run:prod": "cross-env CYPRESS_PROFILE=prod npx cypress run",
14
- "launch-cypress": "npx tsx ./profileChooser/chooseProfile.ts"
8
+ "build": "tsc --project ./tsconfig.json"
15
9
  },
16
10
  "repository": {
17
11
  "type": "git",
@@ -28,21 +22,11 @@
28
22
  "url": "https://github.com/harvard-edtech/dceky/issues"
29
23
  },
30
24
  "homepage": "https://github.com/harvard-edtech/dceky#readme",
31
- "dependencies": {
32
- "deepmerge": "4.3.1",
33
- "clear": "^0.1.0",
34
- "prompt-sync": "^4.2.0"
25
+ "peerDependencies": {
26
+ "cypress": "x.x.x"
35
27
  },
36
28
  "devDependencies": {
37
- "@types/deepmerge": "2.1.0",
38
- "@types/node": "^24.6.1",
39
- "typescript": "5.9.3"
40
- },
41
- "peerDependencies": {
42
- "cypress": "14.5.4",
43
- "@types/clear": "^0.1.4",
44
29
  "@types/node": "^22.7.0",
45
- "@types/prompt-sync": "^4.2.3",
46
30
  "typescript": "^5.9.3"
47
31
  }
48
32
  }
@@ -0,0 +1,40 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Assert that an element does not have a specific className
12
+ * @param opts object containing all arguments
13
+ * @param opts.item the css selector for finding the element
14
+ * @param opts.className the className to check for
15
+ * @example cy.assertDoesNotHaveClass({ item: '.my-button', className: 'disabled' })
16
+ */
17
+ assertDoesNotHaveClass(opts: {
18
+ item: string;
19
+ className: string;
20
+ }): Chainable<JQuery<HTMLElement>>;
21
+ }
22
+ }
23
+ }
24
+
25
+ /*----------------------------------------*/
26
+ /* --------------- Command -------------- */
27
+ /*----------------------------------------*/
28
+
29
+ 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
+ });
34
+ };
35
+
36
+ /*----------------------------------------*/
37
+ /* --------------- Export --------------- */
38
+ /*----------------------------------------*/
39
+
40
+ export default assertDoesNotHaveClass;
@@ -0,0 +1,40 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Assert that an element has a specific className
12
+ * @param opts object containing all arguments
13
+ * @param opts.item the css selector for finding the element
14
+ * @param opts.className the className to check for
15
+ * @example cy.assertHasClass({ item: '.my-button', className: 'active' })
16
+ */
17
+ assertHasClass(opts: {
18
+ item: string;
19
+ className: string;
20
+ }): Chainable<JQuery<HTMLElement>>;
21
+ }
22
+ }
23
+ }
24
+
25
+ /*----------------------------------------*/
26
+ /* --------------- Command -------------- */
27
+ /*----------------------------------------*/
28
+
29
+ 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
+ });
34
+ };
35
+
36
+ /*----------------------------------------*/
37
+ /* --------------- Export --------------- */
38
+ /*----------------------------------------*/
39
+
40
+ export default assertHasClass;
@@ -0,0 +1,37 @@
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
+ * @param opts object containing all arguments
13
+ * @param opts.item a CSS selector corresponding to the item
14
+ * @param opts.num the precise number of elements expected
15
+ * @example cy.assertNumElements({ item: '.list-item', num: 5 })
16
+ */
17
+ assertNumElements(opts: { item: string; num: number }): Chainable<Element>;
18
+ }
19
+ }
20
+ }
21
+
22
+ /*----------------------------------------*/
23
+ /* --------------- Command -------------- */
24
+ /*----------------------------------------*/
25
+
26
+ 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
+ });
31
+ };
32
+
33
+ /*----------------------------------------*/
34
+ /* --------------- Export --------------- */
35
+ /*----------------------------------------*/
36
+
37
+ export default assertNumElements;
@@ -0,0 +1,51 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /*----------------------------------------*/
4
+ /* ---------------- Type ---------------- */
5
+ /*----------------------------------------*/
6
+
7
+ declare global {
8
+ namespace Cypress {
9
+ interface Chainable {
10
+ /**
11
+ * Click an element with visibility checking, scrolling, and timeout handling
12
+ * @param item the CSS selector of interest
13
+ * @param timeoutSec the number of seconds to wait before timing out (default: 10)
14
+ * @param dontScrollTo if true, do not scroll to the element (default: false)
15
+ * @example cy.clickWithRetry('.submit-button', 15, false)
16
+ */
17
+ clickWithRetry(item: string, timeoutSec?: number, dontScrollTo?: boolean): Chainable<JQuery<HTMLElement>>;
18
+ }
19
+ }
20
+ }
21
+
22
+ /*----------------------------------------*/
23
+ /* --------------- Command -------------- */
24
+ /*----------------------------------------*/
25
+
26
+ const clickWithRetry = () => {
27
+ Cypress.Commands.add('clickWithRetry', (item: string, timeoutSec?: number, dontScrollTo?: boolean) => {
28
+ timeoutSec = timeoutSec || 10;
29
+ dontScrollTo = dontScrollTo || false;
30
+
31
+ cy.log(`Click ${item}`);
32
+
33
+ // Wait for element to be visible first using our own function
34
+ cy.waitForElementVisible(item, timeoutSec);
35
+
36
+ // If scrolling is disabled, try to click directly
37
+ if (dontScrollTo) {
38
+ return cy.get(item).click();
39
+ } else {
40
+ // Scroll to element and then click using our own function
41
+ cy.get(item).scrollIntoView();
42
+ return cy.get(item).click();
43
+ }
44
+ });
45
+ };
46
+
47
+ /*----------------------------------------*/
48
+ /* --------------- Export --------------- */
49
+ /*----------------------------------------*/
50
+
51
+ export default clickWithRetry;
@@ -0,0 +1,38 @@
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
+ * @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) => { ... })
15
+ */
16
+ getNumElements(item: string): Chainable<JQuery<any>>;
17
+ }
18
+ }
19
+ }
20
+
21
+ /*----------------------------------------*/
22
+ /* --------------- Command -------------- */
23
+ /*----------------------------------------*/
24
+
25
+ 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
+ });
32
+ };
33
+
34
+ /*----------------------------------------*/
35
+ /* --------------- Export --------------- */
36
+ /*----------------------------------------*/
37
+
38
+ export default getNumElements;
@@ -0,0 +1,87 @@
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
+ * @param url the URL to visit for HarvardKey authentication
13
+ * @example cy.handleHarvardKey('https://canvas.harvard.edu/login/saml')
14
+ */
15
+ handleHarvardKey(url: string): Chainable<void>;
16
+ }
17
+ }
18
+ }
19
+
20
+ /*----------------------------------------*/
21
+ /* --------------- Command -------------- */
22
+ /*----------------------------------------*/
23
+
24
+ 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
+ }
39
+
40
+ cy.visit(url);
41
+
42
+ // Get the Harvard login URL using originWithKaixa with auto-initialized functions
43
+ cy.origin('https://apps.cirrusidentity.com', () => {
44
+ Cypress.require('dceky');
45
+
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) => {
55
+ 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;
64
+ });
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
+ });
79
+ });
80
+ });
81
+ };
82
+
83
+ /*----------------------------------------*/
84
+ /* --------------- Export --------------- */
85
+ /*----------------------------------------*/
86
+
87
+ export default handleHarvardKey;
@@ -0,0 +1,104 @@
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 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')
23
+ */
24
+ launchAs(name: string, courseId?: number, appName?: string): Chainable<void>;
25
+ }
26
+ }
27
+ }
28
+
29
+ /*----------------------------------------*/
30
+ /* --------------- Command -------------- */
31
+ /*----------------------------------------*/
32
+
33
+ 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');
42
+ }
43
+ courseId = parseInt(envCourseId, 10);
44
+ }
45
+
46
+ if (!appName) {
47
+ appName = Cypress.env('APP_NAME');
48
+ if (!appName) {
49
+ throw new Error('appName parameter is required or APP_NAME environment variable must be set');
50
+ }
51
+ }
52
+
53
+ // Check if this is a local launch
54
+ const isLocal = Cypress.env('LOCAL') === 'true';
55
+
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`;
61
+
62
+ cy.log('Local mode: launching simulator');
63
+ cy.visit('https://localhost:8088/simulator');
64
+
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
+ });
73
+
74
+ // Launch the app
75
+ cy.get(`#${simLaunchButtonId}`).click();
76
+
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
+ });
84
+
85
+ return;
86
+ }
87
+
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
+ }
94
+
95
+ // Launch using the access token
96
+ cy.launchLTIUsingToken(accessToken, courseId, appName);
97
+ });
98
+ };
99
+
100
+ /*----------------------------------------*/
101
+ /* --------------- Export --------------- */
102
+ /*----------------------------------------*/
103
+
104
+ export default launchAs;