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