dceky 1.0.5-beta.ky-declarations.13 ā 1.0.5
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/README.md +45 -29
- package/lib/commands/assertDoesNotHaveClass.d.ts +18 -0
- package/lib/commands/assertDoesNotHaveClass.js +17 -0
- package/lib/commands/assertDoesNotHaveClass.js.map +1 -0
- package/lib/commands/assertHasClass.d.ts +18 -0
- package/lib/commands/assertHasClass.js +17 -0
- package/lib/commands/assertHasClass.js.map +1 -0
- package/lib/commands/assertNumElements.d.ts +18 -0
- package/lib/commands/assertNumElements.js +17 -0
- package/lib/commands/assertNumElements.js.map +1 -0
- package/lib/commands/clickWithRetry.d.ts +19 -0
- package/lib/commands/clickWithRetry.js +29 -0
- package/lib/commands/clickWithRetry.js.map +1 -0
- package/lib/commands/genTextOfLength.d.ts +20 -0
- package/lib/commands/genTextOfLength.js +20 -0
- package/lib/commands/genTextOfLength.js.map +1 -0
- package/lib/commands/getAttribute.d.ts +20 -0
- package/lib/commands/getAttribute.js +22 -0
- package/lib/commands/getAttribute.js.map +1 -0
- package/lib/commands/getClassName.d.ts +15 -0
- package/lib/commands/getClassName.js +21 -0
- package/lib/commands/getClassName.js.map +1 -0
- package/lib/commands/getCurrentDateInfo.d.ts +21 -0
- package/lib/commands/getCurrentDateInfo.js +25 -0
- package/lib/commands/getCurrentDateInfo.js.map +1 -0
- package/lib/commands/getId.d.ts +15 -0
- package/lib/commands/getId.js +21 -0
- package/lib/commands/getId.js.map +1 -0
- package/lib/commands/getNumElements.d.ts +14 -0
- package/lib/commands/getNumElements.js +19 -0
- package/lib/commands/getNumElements.js.map +1 -0
- package/lib/commands/getSpecialChars.d.ts +14 -0
- package/lib/commands/getSpecialChars.js +16 -0
- package/lib/commands/getSpecialChars.js.map +1 -0
- package/lib/commands/getTitle.d.ts +13 -0
- package/lib/commands/getTitle.js +20 -0
- package/lib/commands/getTitle.js.map +1 -0
- package/lib/commands/handleHarvardKey.d.ts +18 -0
- package/lib/commands/handleHarvardKey.js +56 -0
- package/lib/commands/handleHarvardKey.js.map +1 -0
- package/lib/commands/handleHarvardKey2.d.ts +14 -0
- package/lib/commands/handleHarvardKey2.js +88 -0
- package/lib/commands/handleHarvardKey2.js.map +1 -0
- package/lib/commands/launchAs.d.ts +20 -0
- package/lib/commands/launchAs.js +60 -0
- package/lib/commands/launchAs.js.map +1 -0
- package/lib/commands/launchLTIUsingToken.d.ts +19 -0
- package/lib/commands/launchLTIUsingToken.js +74 -0
- package/lib/commands/launchLTIUsingToken.js.map +1 -0
- package/lib/commands/listSelectLabels.d.ts +15 -0
- package/lib/commands/listSelectLabels.js +24 -0
- package/lib/commands/listSelectLabels.js.map +1 -0
- package/lib/commands/listSelectValues.d.ts +15 -0
- package/lib/commands/listSelectValues.js +26 -0
- package/lib/commands/listSelectValues.js.map +1 -0
- package/lib/commands/navigateToHref.d.ts +19 -0
- package/lib/commands/navigateToHref.js +23 -0
- package/lib/commands/navigateToHref.js.map +1 -0
- package/lib/commands/padWithZeros.d.ts +20 -0
- package/lib/commands/padWithZeros.js +24 -0
- package/lib/commands/padWithZeros.js.map +1 -0
- package/lib/commands/runScript.d.ts +16 -0
- package/lib/commands/runScript.js +25 -0
- package/lib/commands/runScript.js.map +1 -0
- package/lib/commands/typeInto.d.ts +20 -0
- package/lib/commands/typeInto.js +28 -0
- package/lib/commands/typeInto.js.map +1 -0
- package/lib/commands/uniquify.d.ts +15 -0
- package/lib/commands/uniquify.js +25 -0
- package/lib/commands/uniquify.js.map +1 -0
- package/lib/commands/visitCanvasEndpoint.d.ts +27 -0
- package/lib/commands/visitCanvasEndpoint.js +35 -0
- package/lib/commands/visitCanvasEndpoint.js.map +1 -0
- package/lib/commands/visitCanvasGETEndpoint.d.ts +20 -0
- package/lib/commands/visitCanvasGETEndpoint.js +26 -0
- package/lib/commands/visitCanvasGETEndpoint.js.map +1 -0
- package/lib/commands/waitForAtLeastOneElementPresent.d.ts +23 -0
- package/lib/commands/waitForAtLeastOneElementPresent.js +27 -0
- package/lib/commands/waitForAtLeastOneElementPresent.js.map +1 -0
- package/lib/commands/waitForElementVisible.d.ts +14 -0
- package/lib/commands/waitForElementVisible.js +20 -0
- package/lib/commands/waitForElementVisible.js.map +1 -0
- package/lib/index.js.map +1 -0
- package/lib/init.d.ts +6 -0
- package/lib/init.js +69 -0
- package/lib/init.js.map +1 -0
- package/lib/src/commands/extractDataFromClass.d.ts +7 -4
- package/lib/src/commands/extractDataFromClass.js +2 -1
- package/lib/src/commands/extractDataFromClass.js.map +1 -1
- package/lib/src/commands/extractDataFromClassByContents.d.ts +9 -5
- package/lib/src/commands/extractDataFromClassByContents.js +2 -1
- package/lib/src/commands/extractDataFromClassByContents.js.map +1 -1
- package/lib/src/commands/genTextOfLength.d.ts +20 -0
- package/lib/src/commands/genTextOfLength.js +20 -0
- package/lib/src/commands/genTextOfLength.js.map +1 -0
- package/lib/src/commands/getAttribute.d.ts +20 -0
- package/lib/src/commands/getAttribute.js +22 -0
- package/lib/src/commands/getAttribute.js.map +1 -0
- package/lib/src/commands/getClassName.d.ts +15 -0
- package/lib/src/commands/getClassName.js +21 -0
- package/lib/src/commands/getClassName.js.map +1 -0
- package/lib/src/commands/getCurrentDateInfo.d.ts +21 -0
- package/lib/src/commands/getCurrentDateInfo.js +25 -0
- package/lib/src/commands/getCurrentDateInfo.js.map +1 -0
- package/lib/src/commands/getId.d.ts +15 -0
- package/lib/src/commands/getId.js +21 -0
- package/lib/src/commands/getId.js.map +1 -0
- package/lib/src/commands/getJSON.d.ts +0 -1
- package/lib/src/commands/getJSON.js +1 -4
- package/lib/src/commands/getJSON.js.map +1 -1
- package/lib/src/commands/getSpecialChars.d.ts +14 -0
- package/lib/src/commands/getSpecialChars.js +16 -0
- package/lib/src/commands/getSpecialChars.js.map +1 -0
- package/lib/src/commands/getTitle.d.ts +13 -0
- package/lib/src/commands/getTitle.js +20 -0
- package/lib/src/commands/getTitle.js.map +1 -0
- package/lib/src/commands/index.js +29 -5
- package/lib/src/commands/index.js.map +1 -1
- package/lib/src/commands/launchLTIUsingToken.js +12 -4
- package/lib/src/commands/launchLTIUsingToken.js.map +1 -1
- package/lib/src/commands/listSelectLabels.d.ts +15 -0
- package/lib/src/commands/listSelectLabels.js +24 -0
- package/lib/src/commands/listSelectLabels.js.map +1 -0
- package/lib/src/commands/listSelectValues.d.ts +15 -0
- package/lib/src/commands/listSelectValues.js +27 -0
- package/lib/src/commands/listSelectValues.js.map +1 -0
- package/lib/src/commands/padWithZeros.d.ts +20 -0
- package/lib/src/commands/padWithZeros.js +24 -0
- package/lib/src/commands/padWithZeros.js.map +1 -0
- package/lib/src/commands/uniquify.d.ts +15 -0
- package/lib/src/commands/uniquify.js +26 -0
- package/lib/src/commands/uniquify.js.map +1 -0
- package/lib/src/commands/visitCanvasEndpoint.d.ts +26 -0
- package/lib/src/commands/visitCanvasEndpoint.js +36 -0
- package/lib/src/commands/visitCanvasEndpoint.js.map +1 -0
- package/lib/src/commands/waitForAtLeastOneElementPresent.d.ts +23 -0
- package/lib/src/commands/waitForAtLeastOneElementPresent.js +28 -0
- package/lib/src/commands/waitForAtLeastOneElementPresent.js.map +1 -0
- package/lib/src/init.js +28 -2
- package/lib/src/init.js.map +1 -1
- package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.d.ts +6 -0
- package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.js +9 -0
- package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.js.map +1 -0
- package/lib/start/helpers/collectPngFiles.d.ts +9 -0
- package/lib/start/helpers/collectPngFiles.js +31 -0
- package/lib/start/helpers/collectPngFiles.js.map +1 -0
- package/lib/start/helpers/executeAllHeadlessCombinations.d.ts +15 -0
- package/lib/start/helpers/executeAllHeadlessCombinations.js +116 -0
- package/lib/start/helpers/executeAllHeadlessCombinations.js.map +1 -0
- package/lib/start/helpers/executeCypress.d.ts +17 -0
- package/lib/start/helpers/executeCypress.js +103 -0
- package/lib/start/helpers/executeCypress.js.map +1 -0
- package/lib/start/helpers/generateHtmlReport.d.ts +14 -0
- package/lib/start/helpers/generateHtmlReport.js +54 -0
- package/lib/start/helpers/generateHtmlReport.js.map +1 -0
- package/lib/start/helpers/generateReportHomepage.d.ts +9 -0
- package/lib/start/helpers/generateReportHomepage.js +142 -0
- package/lib/start/helpers/generateReportHomepage.js.map +1 -0
- package/lib/start/helpers/generateReporterConfig.d.ts +17 -0
- package/lib/start/helpers/generateReporterConfig.js +32 -0
- package/lib/start/helpers/generateReporterConfig.js.map +1 -0
- package/lib/start/helpers/getDateLabeledDir.d.ts +7 -0
- package/lib/start/helpers/getDateLabeledDir.js +38 -0
- package/lib/start/helpers/getDateLabeledDir.js.map +1 -0
- package/lib/start/helpers/mergeAllReportsAndGenerateHtml.d.ts +11 -0
- package/lib/start/helpers/mergeAllReportsAndGenerateHtml.js +121 -0
- package/lib/start/helpers/mergeAllReportsAndGenerateHtml.js.map +1 -0
- package/lib/start/helpers/mergeReports.d.ts +14 -0
- package/lib/start/helpers/mergeReports.js +63 -0
- package/lib/start/helpers/mergeReports.js.map +1 -0
- package/lib/start/helpers/reportHomepage.ejs +272 -0
- package/lib/start/helpers/runCypressHeadless.d.ts +18 -0
- package/lib/start/helpers/runCypressHeadless.js +138 -0
- package/lib/start/helpers/runCypressHeadless.js.map +1 -0
- package/lib/start/helpers/runCypressVisible.d.ts +8 -0
- package/lib/start/helpers/runCypressVisible.js +53 -0
- package/lib/start/helpers/runCypressVisible.js.map +1 -0
- package/lib/start/index.js +23 -5
- package/lib/start/index.js.map +1 -1
- package/lib/start/types/MochawesomeReporterConfig.d.ts +15 -0
- package/lib/start/types/MochawesomeReporterConfig.js +3 -0
- package/lib/start/types/MochawesomeReporterConfig.js.map +1 -0
- package/lib/start/types/Profile.d.ts +9 -0
- package/lib/start/types/Profile.js +3 -0
- package/lib/start/types/Profile.js.map +1 -0
- package/lib/start/types/ReportInfo.d.ts +14 -0
- package/lib/start/types/ReportInfo.js +3 -0
- package/lib/start/types/ReportInfo.js.map +1 -0
- package/lib/start/types/RunResult.d.ts +12 -0
- package/lib/start/types/RunResult.js +3 -0
- package/lib/start/types/RunResult.js.map +1 -0
- package/lib/start/types/ScreenshotInfo.d.ts +10 -0
- package/lib/start/types/ScreenshotInfo.js +3 -0
- package/lib/start/types/ScreenshotInfo.js.map +1 -0
- package/lib/start/types/TemplateReportInfo.d.ts +12 -0
- package/lib/start/types/TemplateReportInfo.js +3 -0
- package/lib/start/types/TemplateReportInfo.js.map +1 -0
- package/package.json +10 -8
- package/src/commands/extractDataFromClass.ts +17 -6
- package/src/commands/extractDataFromClassByContents.ts +21 -8
- package/src/commands/genTextOfLength.ts +54 -0
- package/src/commands/getAttribute.ts +58 -0
- package/src/commands/getClassName.ts +44 -0
- package/src/commands/getCurrentDateInfo.ts +57 -0
- package/src/commands/getId.ts +44 -0
- package/src/commands/getJSON.ts +0 -4
- package/src/commands/getSpecialChars.ts +34 -0
- package/src/commands/getTitle.ts +39 -0
- package/src/commands/index.ts +29 -5
- package/src/commands/launchLTIUsingToken.ts +12 -4
- package/src/commands/listSelectLabels.ts +47 -0
- package/src/commands/listSelectValues.ts +50 -0
- package/src/commands/padWithZeros.ts +53 -0
- package/src/commands/uniquify.ts +49 -0
- package/src/commands/visitCanvasEndpoint.ts +75 -0
- package/src/commands/waitForAtLeastOneElementPresent.ts +64 -0
- package/start/constants/DEFAULT_THREADS_PER_COMBO.ts +7 -0
- package/start/helpers/collectPngFiles.ts +25 -0
- package/start/helpers/executeAllHeadlessCombinations.ts +92 -0
- package/start/helpers/executeCypress.ts +66 -0
- package/start/helpers/generateHtmlReport.ts +71 -0
- package/start/helpers/generateReportHomepage.ts +148 -0
- package/start/helpers/generateReporterConfig.ts +39 -0
- package/start/helpers/getDateLabeledDir.ts +43 -0
- package/start/helpers/mergeAllReportsAndGenerateHtml.ts +150 -0
- package/start/helpers/mergeReports.ts +82 -0
- package/start/helpers/reportHomepage.ejs +272 -0
- package/start/helpers/runCypressHeadless.ts +164 -0
- package/start/helpers/runCypressVisible.ts +45 -0
- package/start/index.ts +23 -5
- package/start/types/MochawesomeReporterConfig.ts +23 -0
- package/start/types/Profile.ts +12 -0
- package/start/types/ReportInfo.ts +22 -0
- package/start/types/RunResult.ts +18 -0
- package/start/types/ScreenshotInfo.ts +13 -0
- package/start/types/TemplateReportInfo.ts +16 -0
- package/src/commands/visitCanvasGETEndpoint.ts +0 -61
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/*----------------------------------------*/
|
|
4
|
+
/* ---------------- Type ---------------- */
|
|
5
|
+
/*----------------------------------------*/
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
namespace Cypress {
|
|
9
|
+
interface Chainable {
|
|
10
|
+
/**
|
|
11
|
+
* Wait for any element in a list to be present (check every tenth
|
|
12
|
+
* of a second for status)
|
|
13
|
+
* @author Gardenia Liu
|
|
14
|
+
* @param opts object containing all arguments
|
|
15
|
+
* @param opts.items list of CSS selectors of interest
|
|
16
|
+
* @param [opts.timeoutSec] the number of seconds to wait before
|
|
17
|
+
* timing out
|
|
18
|
+
* @returns the selector of the item that was found first, ties
|
|
19
|
+
* broken by which item shows up in the list first
|
|
20
|
+
*/
|
|
21
|
+
waitForAtLeastOneElementPresent(
|
|
22
|
+
opts: {
|
|
23
|
+
items: string[],
|
|
24
|
+
timeoutSec?: number,
|
|
25
|
+
}
|
|
26
|
+
): Chainable<string>;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*----------------------------------------*/
|
|
32
|
+
/* --------------- Command -------------- */
|
|
33
|
+
/*----------------------------------------*/
|
|
34
|
+
|
|
35
|
+
const waitForAtLeastOneElementPresent = () => {
|
|
36
|
+
Cypress.Commands.add(
|
|
37
|
+
'waitForAtLeastOneElementPresent',
|
|
38
|
+
(opts: { items: string[], timeoutSec?: number }) => {
|
|
39
|
+
const { items, timeoutSec } = opts;
|
|
40
|
+
// Set timeout seconds to 10 if not specified
|
|
41
|
+
const timeoutMs = (timeoutSec || 10) * 1000;
|
|
42
|
+
|
|
43
|
+
// Combined selector for all items
|
|
44
|
+
const combinedSelector = items.join(', ');
|
|
45
|
+
|
|
46
|
+
// Wait for any of the elements to be visible
|
|
47
|
+
return (
|
|
48
|
+
cy
|
|
49
|
+
.get(combinedSelector, { timeout: timeoutMs })
|
|
50
|
+
.should(($els) => { return expect($els.is(':visible'), `at least one of [${combinedSelector}] visible`).to.be.true; })
|
|
51
|
+
.then(($els) => {
|
|
52
|
+
const $firstVisible = $els.filter(':visible').first();
|
|
53
|
+
return items.find((sel) => { return $firstVisible.is(sel); });
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/*----------------------------------------*/
|
|
61
|
+
/* --------------- Export --------------- */
|
|
62
|
+
/*----------------------------------------*/
|
|
63
|
+
|
|
64
|
+
export default waitForAtLeastOneElementPresent;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collect all PNG files recursively from a directory
|
|
6
|
+
* @author Yuen Ler Chow
|
|
7
|
+
* @param dir - Directory to search
|
|
8
|
+
* @param prefix - Path prefix for relative paths
|
|
9
|
+
* @returns Array of relative file paths
|
|
10
|
+
*/
|
|
11
|
+
const collectPngFiles = (dir: string, prefix = ''): string[] => {
|
|
12
|
+
const files: string[] = [];
|
|
13
|
+
const dirEntries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
dirEntries.forEach((entry) => {
|
|
15
|
+
const fullPath = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
files.push(...collectPngFiles(fullPath, path.join(prefix, entry.name)));
|
|
18
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith('.png')) {
|
|
19
|
+
files.push(path.join(prefix, entry.name));
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return files;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default collectPngFiles;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// Import helpers
|
|
6
|
+
import runCypressHeadless from './runCypressHeadless';
|
|
7
|
+
import getDateLabeledDir from './getDateLabeledDir';
|
|
8
|
+
import generateReportHomepage from './generateReportHomepage';
|
|
9
|
+
import mergeAllReportsAndGenerateHtml from './mergeAllReportsAndGenerateHtml';
|
|
10
|
+
|
|
11
|
+
// Import shared types
|
|
12
|
+
import Profile from '../types/Profile';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execute all headless Cypress test combinations in parallel
|
|
16
|
+
* @author Yuen Ler Chow
|
|
17
|
+
* @param opts - object of arguments
|
|
18
|
+
* @param opts.selectedProfiles - Array of selected profiles
|
|
19
|
+
* @param opts.selectedBrowsers - Array of selected browsers
|
|
20
|
+
* @param [opts.threadsPerCombo] - Number of parallel threads per combo (default: 2)
|
|
21
|
+
*/
|
|
22
|
+
const executeAllHeadlessCombinations = async (
|
|
23
|
+
opts: {
|
|
24
|
+
selectedProfiles: Profile[],
|
|
25
|
+
selectedBrowsers: string[],
|
|
26
|
+
threadsPerCombo?: number,
|
|
27
|
+
},
|
|
28
|
+
): Promise<void> => {
|
|
29
|
+
const { selectedProfiles, selectedBrowsers, threadsPerCombo } = opts;
|
|
30
|
+
|
|
31
|
+
// Create date-labeled output directory
|
|
32
|
+
const outputDir = getDateLabeledDir();
|
|
33
|
+
if (!fs.existsSync(outputDir)) {
|
|
34
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`š Results will be saved to: ${outputDir}\n`);
|
|
38
|
+
|
|
39
|
+
// Generate all Profile+Browser combinations
|
|
40
|
+
const combinations: { profileName: string; browser: string }[] = [];
|
|
41
|
+
selectedProfiles.forEach((profile) => {
|
|
42
|
+
selectedBrowsers.forEach((browser) => {
|
|
43
|
+
combinations.push({
|
|
44
|
+
profileName: profile.profileName,
|
|
45
|
+
browser,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(`š Running ${combinations.length} test combination(s) in parallel...\n`);
|
|
51
|
+
|
|
52
|
+
// Run all combinations in parallel
|
|
53
|
+
const runPromises = combinations.map((combo) => {
|
|
54
|
+
return runCypressHeadless({
|
|
55
|
+
profileName: combo.profileName,
|
|
56
|
+
browser: combo.browser,
|
|
57
|
+
outputDir,
|
|
58
|
+
numThreads: threadsPerCombo,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
const results = await Promise.all(runPromises);
|
|
62
|
+
|
|
63
|
+
// Generate combined HTML report across all profile+browser combinations
|
|
64
|
+
mergeAllReportsAndGenerateHtml(outputDir, results);
|
|
65
|
+
|
|
66
|
+
// Generate report homepage
|
|
67
|
+
const reportInfos = results.map((result) => {
|
|
68
|
+
return {
|
|
69
|
+
profileName: result.profileName,
|
|
70
|
+
browser: result.browser,
|
|
71
|
+
reportPath: result.reportPath || '',
|
|
72
|
+
success: result.success,
|
|
73
|
+
timestamp: new Date().toLocaleString(),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
generateReportHomepage(reportInfos, outputDir);
|
|
78
|
+
|
|
79
|
+
// Print summary
|
|
80
|
+
const passed = results.filter((r) => { return r.success; }).length;
|
|
81
|
+
const failed = results.filter((r) => { return !r.success; }).length;
|
|
82
|
+
|
|
83
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
84
|
+
console.log('š Execution Summary:');
|
|
85
|
+
console.log(` Total: ${results.length}`);
|
|
86
|
+
console.log(` ā
Passed: ${passed}`);
|
|
87
|
+
console.log(` ā Failed: ${failed}`);
|
|
88
|
+
console.log(` š Report homepage: file://${path.join(outputDir, 'index.html')}`);
|
|
89
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default executeAllHeadlessCombinations;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
// Import shared types
|
|
4
|
+
import Profile from '../types/Profile';
|
|
5
|
+
|
|
6
|
+
// Import helpers
|
|
7
|
+
import runCypressVisible from './runCypressVisible';
|
|
8
|
+
import executeAllHeadlessCombinations from './executeAllHeadlessCombinations';
|
|
9
|
+
|
|
10
|
+
// Import constants
|
|
11
|
+
import DEFAULT_THREADS_PER_COMBO from '../constants/DEFAULT_THREADS_PER_COMBO';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute Cypress tests in either headless or visible mode
|
|
15
|
+
* @author Yuen Ler Chow
|
|
16
|
+
* @param opts - object of arguments
|
|
17
|
+
* @param opts.selectedProfiles - Array of selected profiles
|
|
18
|
+
* @param opts.selectedBrowsers - Array of selected browsers
|
|
19
|
+
* @param opts.isHeadless - Whether to run in headless mode
|
|
20
|
+
* @param [opts.threadsPerCombo] - Number of parallel threads per combo (default: 2)
|
|
21
|
+
*/
|
|
22
|
+
const executeCypress = async (
|
|
23
|
+
opts: {
|
|
24
|
+
selectedProfiles: Profile[],
|
|
25
|
+
selectedBrowsers: string[],
|
|
26
|
+
isHeadless: boolean,
|
|
27
|
+
threadsPerCombo?: number,
|
|
28
|
+
},
|
|
29
|
+
): Promise<void> => {
|
|
30
|
+
const {
|
|
31
|
+
selectedProfiles,
|
|
32
|
+
selectedBrowsers,
|
|
33
|
+
isHeadless,
|
|
34
|
+
threadsPerCombo,
|
|
35
|
+
} = opts;
|
|
36
|
+
|
|
37
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
38
|
+
console.log('Starting Cypress with the following configuration:');
|
|
39
|
+
console.log(`Profile(s): ${selectedProfiles.map((p) => { return p.profileName; }).join(', ')}`);
|
|
40
|
+
console.log(`Browser(s): ${selectedBrowsers.join(', ')}`);
|
|
41
|
+
console.log(`Headless: ${isHeadless}`);
|
|
42
|
+
if (isHeadless) {
|
|
43
|
+
console.log(`Threads per combo: ${threadsPerCombo ?? DEFAULT_THREADS_PER_COMBO}`);
|
|
44
|
+
}
|
|
45
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (!isHeadless) {
|
|
49
|
+
// Visible mode: run with single profile
|
|
50
|
+
const { profileName } = selectedProfiles[0];
|
|
51
|
+
await runCypressVisible(profileName);
|
|
52
|
+
} else {
|
|
53
|
+
// Headless mode: run parallel instances for each Profile+Browser combination
|
|
54
|
+
await executeAllHeadlessCombinations({
|
|
55
|
+
selectedProfiles,
|
|
56
|
+
selectedBrowsers,
|
|
57
|
+
threadsPerCombo,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('\nā Error during execution:', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default executeCypress;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import getRootPath from './getRootPath';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate HTML report from merged JSON report using mochawesome-report-generator (marge)
|
|
9
|
+
* @author Yuen Ler Chow
|
|
10
|
+
* @param opts - object of arguments
|
|
11
|
+
* @param opts.resultsDir - Results directory
|
|
12
|
+
* @param opts.profileName - Profile name
|
|
13
|
+
* @param opts.browserName - Browser name
|
|
14
|
+
*/
|
|
15
|
+
const generateHtmlReport = (
|
|
16
|
+
opts: {
|
|
17
|
+
resultsDir: string;
|
|
18
|
+
profileName: string;
|
|
19
|
+
browserName: string;
|
|
20
|
+
},
|
|
21
|
+
): void => {
|
|
22
|
+
const { resultsDir, profileName, browserName } = opts;
|
|
23
|
+
|
|
24
|
+
const reportDataPath = path.join(
|
|
25
|
+
resultsDir,
|
|
26
|
+
`${profileName}-${browserName}`,
|
|
27
|
+
'report-data.json',
|
|
28
|
+
);
|
|
29
|
+
const reportDir = path.join(
|
|
30
|
+
resultsDir,
|
|
31
|
+
`${profileName}-${browserName}`,
|
|
32
|
+
'report',
|
|
33
|
+
);
|
|
34
|
+
const expectedHtmlPath = path.join(reportDir, 'report-data.html');
|
|
35
|
+
|
|
36
|
+
console.log(`\nš Generate HTML Report Debug for ${profileName} + ${browserName}:`);
|
|
37
|
+
console.log(` Report data path: ${reportDataPath}`);
|
|
38
|
+
console.log(` Report output dir: ${reportDir}`);
|
|
39
|
+
|
|
40
|
+
if (!existsSync(reportDataPath)) {
|
|
41
|
+
const errorMsg = `ā ERROR: Report data JSON does not exist: ${reportDataPath}`;
|
|
42
|
+
console.error(errorMsg);
|
|
43
|
+
throw new Error(errorMsg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const command = `npx marge "${reportDataPath}" --reportDir "${reportDir}"`;
|
|
48
|
+
|
|
49
|
+
console.log(` Running: ${command}`);
|
|
50
|
+
console.log(`Generating HTML report for ${profileName} + ${browserName}...`);
|
|
51
|
+
|
|
52
|
+
execSync(command, {
|
|
53
|
+
stdio: 'inherit',
|
|
54
|
+
cwd: getRootPath(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Verify the HTML report was created
|
|
58
|
+
if (!existsSync(expectedHtmlPath)) {
|
|
59
|
+
const errorMsg = `ā ERROR: HTML report was not created at: ${expectedHtmlPath}`;
|
|
60
|
+
console.error(errorMsg);
|
|
61
|
+
throw new Error(errorMsg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(` ā
HTML report generated successfully at: ${expectedHtmlPath}`);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error(`Error generating HTML report for ${profileName} + ${browserName}:`, e);
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default generateHtmlReport;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import ejs from 'ejs';
|
|
5
|
+
|
|
6
|
+
// Import helpers
|
|
7
|
+
import collectPngFiles from './collectPngFiles';
|
|
8
|
+
|
|
9
|
+
// Import types
|
|
10
|
+
import ReportInfo from '../types/ReportInfo';
|
|
11
|
+
import ScreenshotInfo from '../types/ScreenshotInfo';
|
|
12
|
+
import TemplateReportInfo from '../types/TemplateReportInfo';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate an HTML homepage that links to all reports
|
|
16
|
+
* @author Yuen Ler Chow
|
|
17
|
+
* @param reports - Array of report information
|
|
18
|
+
* @param outputDir - The directory where the homepage should be created
|
|
19
|
+
*/
|
|
20
|
+
const generateReportHomepage = (
|
|
21
|
+
reports: ReportInfo[],
|
|
22
|
+
outputDir: string,
|
|
23
|
+
): void => {
|
|
24
|
+
const homepagePath = path.join(outputDir, 'index.html');
|
|
25
|
+
const combinedReportPath = path.join(outputDir, 'all-runs.html');
|
|
26
|
+
const combinedJsonPath = path.join(outputDir, 'all-report-data.json');
|
|
27
|
+
|
|
28
|
+
const hasCombinedReport = fs.existsSync(combinedReportPath);
|
|
29
|
+
|
|
30
|
+
// Try to read combined stats from all-report-data.json
|
|
31
|
+
let combinedTotalTests: number | null = null;
|
|
32
|
+
let combinedPassedTests: number | null = null;
|
|
33
|
+
if (fs.existsSync(combinedJsonPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = fs.readFileSync(combinedJsonPath, 'utf-8');
|
|
36
|
+
const data = JSON.parse(raw);
|
|
37
|
+
const stats = (data && data.stats) || null;
|
|
38
|
+
if (stats && typeof stats.tests === 'number' && typeof stats.passes === 'number') {
|
|
39
|
+
combinedTotalTests = stats.tests;
|
|
40
|
+
combinedPassedTests = stats.passes;
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// File exists but couldn't be read/parsed - log warning
|
|
44
|
+
console.warn('ā ļø Could not read combined stats:', err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try to read per-combination stats from each combo's report-data.json
|
|
49
|
+
reports.forEach((report) => {
|
|
50
|
+
const comboJsonPath = path.join(
|
|
51
|
+
outputDir,
|
|
52
|
+
`${report.profileName}-${report.browser}`,
|
|
53
|
+
'report-data.json',
|
|
54
|
+
);
|
|
55
|
+
if (!fs.existsSync(comboJsonPath)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const raw = fs.readFileSync(comboJsonPath, 'utf-8');
|
|
60
|
+
const data = JSON.parse(raw);
|
|
61
|
+
const stats = (data && data.stats) || null;
|
|
62
|
+
if (stats && typeof stats.tests === 'number' && typeof stats.passes === 'number') {
|
|
63
|
+
// eslint-disable-next-line no-param-reassign
|
|
64
|
+
report.totalTests = stats.tests;
|
|
65
|
+
// eslint-disable-next-line no-param-reassign
|
|
66
|
+
report.passedTests = stats.passes;
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
// File exists but couldn't be read/parsed - log warning
|
|
70
|
+
console.warn(`ā ļø Could not read stats for ${report.profileName}-${report.browser}:`, err);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Sort reports by profile name, then browser
|
|
75
|
+
const sortedReports = [...reports].sort((a, b) => {
|
|
76
|
+
if (a.profileName !== b.profileName) {
|
|
77
|
+
return a.profileName.localeCompare(b.profileName);
|
|
78
|
+
}
|
|
79
|
+
return a.browser.localeCompare(b.browser);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Prepare reports with computed fields for the template
|
|
83
|
+
const templateReports: TemplateReportInfo[] = sortedReports.map((report) => {
|
|
84
|
+
// Compute relative report path
|
|
85
|
+
let relativeReportPath: string | null = null;
|
|
86
|
+
if (report.reportPath && fs.existsSync(report.reportPath)) {
|
|
87
|
+
relativeReportPath = path.relative(outputDir, report.reportPath).replace(/\\/g, '/');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Collect screenshots
|
|
91
|
+
const screenshots: ScreenshotInfo[] = [];
|
|
92
|
+
const comboScreenshotsDir = path.join(
|
|
93
|
+
outputDir,
|
|
94
|
+
`${report.profileName}-${report.browser}`,
|
|
95
|
+
'screenshots',
|
|
96
|
+
);
|
|
97
|
+
if (fs.existsSync(comboScreenshotsDir)) {
|
|
98
|
+
const pngFiles = collectPngFiles(comboScreenshotsDir);
|
|
99
|
+
pngFiles.forEach((rel) => {
|
|
100
|
+
const href = path
|
|
101
|
+
.relative(outputDir, path.join(comboScreenshotsDir, rel))
|
|
102
|
+
.replace(/\\/g, '/');
|
|
103
|
+
screenshots.push({ href, name: rel });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
...report,
|
|
109
|
+
relativeReportPath,
|
|
110
|
+
screenshots,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Group by profile
|
|
115
|
+
const reportsByProfile: { [key: string]: TemplateReportInfo[] } = {};
|
|
116
|
+
templateReports.forEach((report) => {
|
|
117
|
+
if (!reportsByProfile[report.profileName]) {
|
|
118
|
+
reportsByProfile[report.profileName] = [];
|
|
119
|
+
}
|
|
120
|
+
reportsByProfile[report.profileName].push(report);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Calculate summary stats
|
|
124
|
+
const totalRuns = reports.length;
|
|
125
|
+
const passedRuns = reports.filter((r) => { return r.success; }).length;
|
|
126
|
+
const failedRuns = reports.filter((r) => { return !r.success; }).length;
|
|
127
|
+
const profileCount = Object.keys(reportsByProfile).length;
|
|
128
|
+
|
|
129
|
+
// Load and render EJS template
|
|
130
|
+
const templatePath = path.join(__dirname, 'reportHomepage.ejs');
|
|
131
|
+
const templateFile = fs.readFileSync(templatePath, 'utf-8');
|
|
132
|
+
const html = ejs.render(templateFile, {
|
|
133
|
+
hasCombinedReport,
|
|
134
|
+
combinedTotalTests,
|
|
135
|
+
combinedPassedTests,
|
|
136
|
+
totalRuns,
|
|
137
|
+
passedRuns,
|
|
138
|
+
failedRuns,
|
|
139
|
+
profileCount,
|
|
140
|
+
reportsByProfile,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Write the HTML file
|
|
144
|
+
fs.writeFileSync(homepagePath, html, 'utf-8');
|
|
145
|
+
console.log(`\nš Report homepage generated: ${homepagePath}`);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default generateReportHomepage;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import MochawesomeReporterConfig from '../types/MochawesomeReporterConfig';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate inline reporter configuration for cypress-multi-reporters
|
|
6
|
+
* that produces granular mochawesome JSON per spec
|
|
7
|
+
* @author Yuen Ler Chow
|
|
8
|
+
* @param opts - object of arguments
|
|
9
|
+
* @param opts.resultsDir - Base directory for results
|
|
10
|
+
* @param opts.profileName - Profile name
|
|
11
|
+
* @param opts.browserName - Browser name
|
|
12
|
+
* @returns Reporter config object
|
|
13
|
+
*/
|
|
14
|
+
const generateReporterConfig = (opts: {
|
|
15
|
+
resultsDir: string;
|
|
16
|
+
profileName: string;
|
|
17
|
+
browserName: string;
|
|
18
|
+
}): MochawesomeReporterConfig => {
|
|
19
|
+
const { resultsDir, profileName, browserName } = opts;
|
|
20
|
+
|
|
21
|
+
const granularResultsDir = path.join(
|
|
22
|
+
resultsDir,
|
|
23
|
+
`${profileName}-${browserName}`,
|
|
24
|
+
'granular-results',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
reporterEnabled: 'mochawesome',
|
|
29
|
+
mochawesomeReporterOptions: {
|
|
30
|
+
reportDir: granularResultsDir,
|
|
31
|
+
reportFilename: 'test-[name]',
|
|
32
|
+
overwrite: false,
|
|
33
|
+
html: false,
|
|
34
|
+
json: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default generateReporterConfig;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import getRootPath from './getRootPath';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get a date-labeled directory path for results using Eastern Time
|
|
6
|
+
* @author Yuen Ler Chow
|
|
7
|
+
* @returns path to date-labeled directory (e.g., cypress-results/2024-1-15_3-05pm)
|
|
8
|
+
*/
|
|
9
|
+
const getDateLabeledDir = (): string => {
|
|
10
|
+
const root = getRootPath();
|
|
11
|
+
const now = new Date();
|
|
12
|
+
|
|
13
|
+
// Format date in Eastern Time
|
|
14
|
+
const str = now.toLocaleString(
|
|
15
|
+
'en-US', // Using US encoding (it's the only one installed on containers)
|
|
16
|
+
{ timeZone: 'America/New_York' }, // Force EST timezone
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Parse the string for the date/time info
|
|
20
|
+
const [dateStr, timeStr] = str.split(', '); // Format: MM/DD/YYYY, HH:MM:SS AM
|
|
21
|
+
const [monthStr, dayStr, yearStr] = dateStr.split('/'); // Format: MM/DD/YYYY
|
|
22
|
+
const [hourStr, minStr, ending] = timeStr.split(':'); // Format: HH:MM:SS AM
|
|
23
|
+
|
|
24
|
+
// Create all time numbers
|
|
25
|
+
const year = Number.parseInt(yearStr, 10);
|
|
26
|
+
const month = Number.parseInt(monthStr, 10);
|
|
27
|
+
const day = Number.parseInt(dayStr, 10);
|
|
28
|
+
const minute = Number.parseInt(minStr, 10);
|
|
29
|
+
const hour12 = Number.parseInt(hourStr, 10);
|
|
30
|
+
|
|
31
|
+
// Determine AM/PM
|
|
32
|
+
const isAM = ending.toLowerCase().includes('am');
|
|
33
|
+
|
|
34
|
+
// Pad minute with leading zero if needed
|
|
35
|
+
const minutePadded = minute.toString().padStart(2, '0');
|
|
36
|
+
|
|
37
|
+
// Build formatted date string: YYYY-M-D_H-MMam/pm (no spaces for shell compatibility)
|
|
38
|
+
const formattedDate = `${year}-${month}-${day}_${hour12}-${minutePadded}${isAM ? 'am' : 'pm'}`;
|
|
39
|
+
|
|
40
|
+
return path.join(root, 'cypress-results', formattedDate);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default getDateLabeledDir;
|