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.
Files changed (237) hide show
  1. package/README.md +45 -29
  2. package/lib/commands/assertDoesNotHaveClass.d.ts +18 -0
  3. package/lib/commands/assertDoesNotHaveClass.js +17 -0
  4. package/lib/commands/assertDoesNotHaveClass.js.map +1 -0
  5. package/lib/commands/assertHasClass.d.ts +18 -0
  6. package/lib/commands/assertHasClass.js +17 -0
  7. package/lib/commands/assertHasClass.js.map +1 -0
  8. package/lib/commands/assertNumElements.d.ts +18 -0
  9. package/lib/commands/assertNumElements.js +17 -0
  10. package/lib/commands/assertNumElements.js.map +1 -0
  11. package/lib/commands/clickWithRetry.d.ts +19 -0
  12. package/lib/commands/clickWithRetry.js +29 -0
  13. package/lib/commands/clickWithRetry.js.map +1 -0
  14. package/lib/commands/genTextOfLength.d.ts +20 -0
  15. package/lib/commands/genTextOfLength.js +20 -0
  16. package/lib/commands/genTextOfLength.js.map +1 -0
  17. package/lib/commands/getAttribute.d.ts +20 -0
  18. package/lib/commands/getAttribute.js +22 -0
  19. package/lib/commands/getAttribute.js.map +1 -0
  20. package/lib/commands/getClassName.d.ts +15 -0
  21. package/lib/commands/getClassName.js +21 -0
  22. package/lib/commands/getClassName.js.map +1 -0
  23. package/lib/commands/getCurrentDateInfo.d.ts +21 -0
  24. package/lib/commands/getCurrentDateInfo.js +25 -0
  25. package/lib/commands/getCurrentDateInfo.js.map +1 -0
  26. package/lib/commands/getId.d.ts +15 -0
  27. package/lib/commands/getId.js +21 -0
  28. package/lib/commands/getId.js.map +1 -0
  29. package/lib/commands/getNumElements.d.ts +14 -0
  30. package/lib/commands/getNumElements.js +19 -0
  31. package/lib/commands/getNumElements.js.map +1 -0
  32. package/lib/commands/getSpecialChars.d.ts +14 -0
  33. package/lib/commands/getSpecialChars.js +16 -0
  34. package/lib/commands/getSpecialChars.js.map +1 -0
  35. package/lib/commands/getTitle.d.ts +13 -0
  36. package/lib/commands/getTitle.js +20 -0
  37. package/lib/commands/getTitle.js.map +1 -0
  38. package/lib/commands/handleHarvardKey.d.ts +18 -0
  39. package/lib/commands/handleHarvardKey.js +56 -0
  40. package/lib/commands/handleHarvardKey.js.map +1 -0
  41. package/lib/commands/handleHarvardKey2.d.ts +14 -0
  42. package/lib/commands/handleHarvardKey2.js +88 -0
  43. package/lib/commands/handleHarvardKey2.js.map +1 -0
  44. package/lib/commands/launchAs.d.ts +20 -0
  45. package/lib/commands/launchAs.js +60 -0
  46. package/lib/commands/launchAs.js.map +1 -0
  47. package/lib/commands/launchLTIUsingToken.d.ts +19 -0
  48. package/lib/commands/launchLTIUsingToken.js +74 -0
  49. package/lib/commands/launchLTIUsingToken.js.map +1 -0
  50. package/lib/commands/listSelectLabels.d.ts +15 -0
  51. package/lib/commands/listSelectLabels.js +24 -0
  52. package/lib/commands/listSelectLabels.js.map +1 -0
  53. package/lib/commands/listSelectValues.d.ts +15 -0
  54. package/lib/commands/listSelectValues.js +26 -0
  55. package/lib/commands/listSelectValues.js.map +1 -0
  56. package/lib/commands/navigateToHref.d.ts +19 -0
  57. package/lib/commands/navigateToHref.js +23 -0
  58. package/lib/commands/navigateToHref.js.map +1 -0
  59. package/lib/commands/padWithZeros.d.ts +20 -0
  60. package/lib/commands/padWithZeros.js +24 -0
  61. package/lib/commands/padWithZeros.js.map +1 -0
  62. package/lib/commands/runScript.d.ts +16 -0
  63. package/lib/commands/runScript.js +25 -0
  64. package/lib/commands/runScript.js.map +1 -0
  65. package/lib/commands/typeInto.d.ts +20 -0
  66. package/lib/commands/typeInto.js +28 -0
  67. package/lib/commands/typeInto.js.map +1 -0
  68. package/lib/commands/uniquify.d.ts +15 -0
  69. package/lib/commands/uniquify.js +25 -0
  70. package/lib/commands/uniquify.js.map +1 -0
  71. package/lib/commands/visitCanvasEndpoint.d.ts +27 -0
  72. package/lib/commands/visitCanvasEndpoint.js +35 -0
  73. package/lib/commands/visitCanvasEndpoint.js.map +1 -0
  74. package/lib/commands/visitCanvasGETEndpoint.d.ts +20 -0
  75. package/lib/commands/visitCanvasGETEndpoint.js +26 -0
  76. package/lib/commands/visitCanvasGETEndpoint.js.map +1 -0
  77. package/lib/commands/waitForAtLeastOneElementPresent.d.ts +23 -0
  78. package/lib/commands/waitForAtLeastOneElementPresent.js +27 -0
  79. package/lib/commands/waitForAtLeastOneElementPresent.js.map +1 -0
  80. package/lib/commands/waitForElementVisible.d.ts +14 -0
  81. package/lib/commands/waitForElementVisible.js +20 -0
  82. package/lib/commands/waitForElementVisible.js.map +1 -0
  83. package/lib/index.js.map +1 -0
  84. package/lib/init.d.ts +6 -0
  85. package/lib/init.js +69 -0
  86. package/lib/init.js.map +1 -0
  87. package/lib/src/commands/extractDataFromClass.d.ts +7 -4
  88. package/lib/src/commands/extractDataFromClass.js +2 -1
  89. package/lib/src/commands/extractDataFromClass.js.map +1 -1
  90. package/lib/src/commands/extractDataFromClassByContents.d.ts +9 -5
  91. package/lib/src/commands/extractDataFromClassByContents.js +2 -1
  92. package/lib/src/commands/extractDataFromClassByContents.js.map +1 -1
  93. package/lib/src/commands/genTextOfLength.d.ts +20 -0
  94. package/lib/src/commands/genTextOfLength.js +20 -0
  95. package/lib/src/commands/genTextOfLength.js.map +1 -0
  96. package/lib/src/commands/getAttribute.d.ts +20 -0
  97. package/lib/src/commands/getAttribute.js +22 -0
  98. package/lib/src/commands/getAttribute.js.map +1 -0
  99. package/lib/src/commands/getClassName.d.ts +15 -0
  100. package/lib/src/commands/getClassName.js +21 -0
  101. package/lib/src/commands/getClassName.js.map +1 -0
  102. package/lib/src/commands/getCurrentDateInfo.d.ts +21 -0
  103. package/lib/src/commands/getCurrentDateInfo.js +25 -0
  104. package/lib/src/commands/getCurrentDateInfo.js.map +1 -0
  105. package/lib/src/commands/getId.d.ts +15 -0
  106. package/lib/src/commands/getId.js +21 -0
  107. package/lib/src/commands/getId.js.map +1 -0
  108. package/lib/src/commands/getJSON.d.ts +0 -1
  109. package/lib/src/commands/getJSON.js +1 -4
  110. package/lib/src/commands/getJSON.js.map +1 -1
  111. package/lib/src/commands/getSpecialChars.d.ts +14 -0
  112. package/lib/src/commands/getSpecialChars.js +16 -0
  113. package/lib/src/commands/getSpecialChars.js.map +1 -0
  114. package/lib/src/commands/getTitle.d.ts +13 -0
  115. package/lib/src/commands/getTitle.js +20 -0
  116. package/lib/src/commands/getTitle.js.map +1 -0
  117. package/lib/src/commands/index.js +29 -5
  118. package/lib/src/commands/index.js.map +1 -1
  119. package/lib/src/commands/launchLTIUsingToken.js +12 -4
  120. package/lib/src/commands/launchLTIUsingToken.js.map +1 -1
  121. package/lib/src/commands/listSelectLabels.d.ts +15 -0
  122. package/lib/src/commands/listSelectLabels.js +24 -0
  123. package/lib/src/commands/listSelectLabels.js.map +1 -0
  124. package/lib/src/commands/listSelectValues.d.ts +15 -0
  125. package/lib/src/commands/listSelectValues.js +27 -0
  126. package/lib/src/commands/listSelectValues.js.map +1 -0
  127. package/lib/src/commands/padWithZeros.d.ts +20 -0
  128. package/lib/src/commands/padWithZeros.js +24 -0
  129. package/lib/src/commands/padWithZeros.js.map +1 -0
  130. package/lib/src/commands/uniquify.d.ts +15 -0
  131. package/lib/src/commands/uniquify.js +26 -0
  132. package/lib/src/commands/uniquify.js.map +1 -0
  133. package/lib/src/commands/visitCanvasEndpoint.d.ts +26 -0
  134. package/lib/src/commands/visitCanvasEndpoint.js +36 -0
  135. package/lib/src/commands/visitCanvasEndpoint.js.map +1 -0
  136. package/lib/src/commands/waitForAtLeastOneElementPresent.d.ts +23 -0
  137. package/lib/src/commands/waitForAtLeastOneElementPresent.js +28 -0
  138. package/lib/src/commands/waitForAtLeastOneElementPresent.js.map +1 -0
  139. package/lib/src/init.js +28 -2
  140. package/lib/src/init.js.map +1 -1
  141. package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.d.ts +6 -0
  142. package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.js +9 -0
  143. package/lib/start/constants/DEFAULT_THREADS_PER_COMBO.js.map +1 -0
  144. package/lib/start/helpers/collectPngFiles.d.ts +9 -0
  145. package/lib/start/helpers/collectPngFiles.js +31 -0
  146. package/lib/start/helpers/collectPngFiles.js.map +1 -0
  147. package/lib/start/helpers/executeAllHeadlessCombinations.d.ts +15 -0
  148. package/lib/start/helpers/executeAllHeadlessCombinations.js +116 -0
  149. package/lib/start/helpers/executeAllHeadlessCombinations.js.map +1 -0
  150. package/lib/start/helpers/executeCypress.d.ts +17 -0
  151. package/lib/start/helpers/executeCypress.js +103 -0
  152. package/lib/start/helpers/executeCypress.js.map +1 -0
  153. package/lib/start/helpers/generateHtmlReport.d.ts +14 -0
  154. package/lib/start/helpers/generateHtmlReport.js +54 -0
  155. package/lib/start/helpers/generateHtmlReport.js.map +1 -0
  156. package/lib/start/helpers/generateReportHomepage.d.ts +9 -0
  157. package/lib/start/helpers/generateReportHomepage.js +142 -0
  158. package/lib/start/helpers/generateReportHomepage.js.map +1 -0
  159. package/lib/start/helpers/generateReporterConfig.d.ts +17 -0
  160. package/lib/start/helpers/generateReporterConfig.js +32 -0
  161. package/lib/start/helpers/generateReporterConfig.js.map +1 -0
  162. package/lib/start/helpers/getDateLabeledDir.d.ts +7 -0
  163. package/lib/start/helpers/getDateLabeledDir.js +38 -0
  164. package/lib/start/helpers/getDateLabeledDir.js.map +1 -0
  165. package/lib/start/helpers/mergeAllReportsAndGenerateHtml.d.ts +11 -0
  166. package/lib/start/helpers/mergeAllReportsAndGenerateHtml.js +121 -0
  167. package/lib/start/helpers/mergeAllReportsAndGenerateHtml.js.map +1 -0
  168. package/lib/start/helpers/mergeReports.d.ts +14 -0
  169. package/lib/start/helpers/mergeReports.js +63 -0
  170. package/lib/start/helpers/mergeReports.js.map +1 -0
  171. package/lib/start/helpers/reportHomepage.ejs +272 -0
  172. package/lib/start/helpers/runCypressHeadless.d.ts +18 -0
  173. package/lib/start/helpers/runCypressHeadless.js +138 -0
  174. package/lib/start/helpers/runCypressHeadless.js.map +1 -0
  175. package/lib/start/helpers/runCypressVisible.d.ts +8 -0
  176. package/lib/start/helpers/runCypressVisible.js +53 -0
  177. package/lib/start/helpers/runCypressVisible.js.map +1 -0
  178. package/lib/start/index.js +23 -5
  179. package/lib/start/index.js.map +1 -1
  180. package/lib/start/types/MochawesomeReporterConfig.d.ts +15 -0
  181. package/lib/start/types/MochawesomeReporterConfig.js +3 -0
  182. package/lib/start/types/MochawesomeReporterConfig.js.map +1 -0
  183. package/lib/start/types/Profile.d.ts +9 -0
  184. package/lib/start/types/Profile.js +3 -0
  185. package/lib/start/types/Profile.js.map +1 -0
  186. package/lib/start/types/ReportInfo.d.ts +14 -0
  187. package/lib/start/types/ReportInfo.js +3 -0
  188. package/lib/start/types/ReportInfo.js.map +1 -0
  189. package/lib/start/types/RunResult.d.ts +12 -0
  190. package/lib/start/types/RunResult.js +3 -0
  191. package/lib/start/types/RunResult.js.map +1 -0
  192. package/lib/start/types/ScreenshotInfo.d.ts +10 -0
  193. package/lib/start/types/ScreenshotInfo.js +3 -0
  194. package/lib/start/types/ScreenshotInfo.js.map +1 -0
  195. package/lib/start/types/TemplateReportInfo.d.ts +12 -0
  196. package/lib/start/types/TemplateReportInfo.js +3 -0
  197. package/lib/start/types/TemplateReportInfo.js.map +1 -0
  198. package/package.json +10 -8
  199. package/src/commands/extractDataFromClass.ts +17 -6
  200. package/src/commands/extractDataFromClassByContents.ts +21 -8
  201. package/src/commands/genTextOfLength.ts +54 -0
  202. package/src/commands/getAttribute.ts +58 -0
  203. package/src/commands/getClassName.ts +44 -0
  204. package/src/commands/getCurrentDateInfo.ts +57 -0
  205. package/src/commands/getId.ts +44 -0
  206. package/src/commands/getJSON.ts +0 -4
  207. package/src/commands/getSpecialChars.ts +34 -0
  208. package/src/commands/getTitle.ts +39 -0
  209. package/src/commands/index.ts +29 -5
  210. package/src/commands/launchLTIUsingToken.ts +12 -4
  211. package/src/commands/listSelectLabels.ts +47 -0
  212. package/src/commands/listSelectValues.ts +50 -0
  213. package/src/commands/padWithZeros.ts +53 -0
  214. package/src/commands/uniquify.ts +49 -0
  215. package/src/commands/visitCanvasEndpoint.ts +75 -0
  216. package/src/commands/waitForAtLeastOneElementPresent.ts +64 -0
  217. package/start/constants/DEFAULT_THREADS_PER_COMBO.ts +7 -0
  218. package/start/helpers/collectPngFiles.ts +25 -0
  219. package/start/helpers/executeAllHeadlessCombinations.ts +92 -0
  220. package/start/helpers/executeCypress.ts +66 -0
  221. package/start/helpers/generateHtmlReport.ts +71 -0
  222. package/start/helpers/generateReportHomepage.ts +148 -0
  223. package/start/helpers/generateReporterConfig.ts +39 -0
  224. package/start/helpers/getDateLabeledDir.ts +43 -0
  225. package/start/helpers/mergeAllReportsAndGenerateHtml.ts +150 -0
  226. package/start/helpers/mergeReports.ts +82 -0
  227. package/start/helpers/reportHomepage.ejs +272 -0
  228. package/start/helpers/runCypressHeadless.ts +164 -0
  229. package/start/helpers/runCypressVisible.ts +45 -0
  230. package/start/index.ts +23 -5
  231. package/start/types/MochawesomeReporterConfig.ts +23 -0
  232. package/start/types/Profile.ts +12 -0
  233. package/start/types/ReportInfo.ts +22 -0
  234. package/start/types/RunResult.ts +18 -0
  235. package/start/types/ScreenshotInfo.ts +13 -0
  236. package/start/types/TemplateReportInfo.ts +16 -0
  237. package/src/commands/visitCanvasGETEndpoint.ts +0 -61
@@ -0,0 +1,150 @@
1
+ /* eslint-disable no-console */
2
+
3
+ // Import libs
4
+ import { execSync } from 'child_process';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+
8
+ // Import helpers
9
+ import getRootPath from './getRootPath';
10
+ import RunResult from '../types/RunResult';
11
+
12
+ /**
13
+ * Recursively annotate mochawesome suites (not individual tests) with a label
14
+ * so the combined report shows profile+browser for each suite
15
+ * @author Yuen Ler Chow
16
+ * @param node - The mochawesome node to annotate
17
+ * @param label - The label to prepend to suite titles
18
+ */
19
+ const annotateMochawesomeSuites = (node: any, label: string): void => {
20
+ if (!node || typeof node !== 'object') {
21
+ return;
22
+ }
23
+
24
+ // If this is an array of nodes, annotate each element
25
+ if (Array.isArray(node)) {
26
+ node.forEach((child: any) => {
27
+ annotateMochawesomeSuites(child, label);
28
+ });
29
+ return;
30
+ }
31
+
32
+ // Only annotate if this is a suite (has suites or tests arrays)
33
+ const isSuite = Array.isArray(node.suites) || Array.isArray(node.tests);
34
+ if (isSuite) {
35
+ if (typeof node.title === 'string') {
36
+ // eslint-disable-next-line no-param-reassign
37
+ node.title = `${label}${node.title}`;
38
+ }
39
+ if (typeof node.fullTitle === 'string') {
40
+ // eslint-disable-next-line no-param-reassign
41
+ node.fullTitle = `${label}${node.fullTitle}`;
42
+ }
43
+ }
44
+
45
+ // Recurse into child suites (but not tests)
46
+ if (Array.isArray(node.suites)) {
47
+ node.suites.forEach((suite: any) => {
48
+ annotateMochawesomeSuites(suite, label);
49
+ });
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Merge all per-combination mochawesome JSON reports into a single JSON
55
+ * and generate one combined HTML report for all runs.
56
+ * Each suite/test is annotated with [profile][browser] in its title.
57
+ * @author Yuen Ler Chow
58
+ * @param outputDir - Date-labeled results directory
59
+ * @param results - Array of run results
60
+ */
61
+ const mergeAllReportsAndGenerateHtml = (
62
+ outputDir: string,
63
+ results: RunResult[],
64
+ ): void => {
65
+ // Collect annotated copies of report-data.json files from successful runs
66
+ const annotatedJsonPaths: string[] = [];
67
+
68
+ results.forEach((result) => {
69
+ const jsonPath = path.join(
70
+ outputDir,
71
+ `${result.profileName}-${result.browser}`,
72
+ 'report-data.json',
73
+ );
74
+ if (!fs.existsSync(jsonPath)) {
75
+ return;
76
+ }
77
+
78
+ try {
79
+ // Import the json report
80
+ const raw = fs.readFileSync(jsonPath, 'utf-8');
81
+ const data = JSON.parse(raw);
82
+ const label = `[${result.profileName}][${result.browser}] `;
83
+
84
+ if (Array.isArray(data.results)) {
85
+ data.results.forEach((res: any) => {
86
+ if (res && res.suites) {
87
+ annotateMochawesomeSuites(res.suites, label);
88
+ }
89
+ });
90
+ }
91
+
92
+ // Annotate tests with their profile+browser
93
+ const annotatedPath = path.join(
94
+ outputDir,
95
+ `${result.profileName}-${result.browser}-labeled.json`,
96
+ );
97
+ fs.writeFileSync(annotatedPath, JSON.stringify(data, null, 2), 'utf-8');
98
+ annotatedJsonPaths.push(annotatedPath);
99
+ } catch (e) {
100
+ console.error(`Error annotating report for ${result.profileName} + ${result.browser}:`, e);
101
+ }
102
+ });
103
+
104
+ if (annotatedJsonPaths.length === 0) {
105
+ console.warn('No annotated report-data.json files found. Skipping global merge.');
106
+ return;
107
+ }
108
+
109
+ const allReportDataPath = path.join(outputDir, 'all-report-data.json');
110
+ const allReportHtmlDir = outputDir;
111
+
112
+ try {
113
+ // Build mochawesome-merge command with all annotated JSON paths
114
+ const mergedArgs = (
115
+ annotatedJsonPaths
116
+ .map((p) => {
117
+ return `"${p}"`;
118
+ })
119
+ .join(' ')
120
+ );
121
+ const mergeCommand = `npx mochawesome-merge ${mergedArgs} > "${allReportDataPath}"`;
122
+
123
+ console.log('Merging all annotated report-data.json files into a single JSON for all runs...');
124
+
125
+ execSync(mergeCommand, {
126
+ stdio: 'inherit',
127
+ cwd: getRootPath(),
128
+ shell: '/bin/bash',
129
+ });
130
+
131
+ // Generate combined HTML using marge
132
+ const margeCommand = `npx marge "${allReportDataPath}" --reportDir "${allReportHtmlDir}" --reportFilename "all-runs"`;
133
+
134
+ console.log('Generating combined HTML report for all runs...');
135
+
136
+ execSync(
137
+ margeCommand,
138
+ {
139
+ stdio: 'inherit',
140
+ cwd: getRootPath(),
141
+ },
142
+ );
143
+
144
+ console.log(`Combined HTML report generated at: ${path.join(allReportHtmlDir, 'all-runs.html')}`);
145
+ } catch (e) {
146
+ console.error('Error generating combined HTML report for all runs:', e);
147
+ }
148
+ };
149
+
150
+ export default mergeAllReportsAndGenerateHtml;
@@ -0,0 +1,82 @@
1
+ /* eslint-disable no-console */
2
+ import { execSync } from 'child_process';
3
+ import path from 'path';
4
+ import { existsSync, readdirSync } from 'fs';
5
+ import getRootPath from './getRootPath';
6
+
7
+ /**
8
+ * Merge mochawesome JSON reports into a single JSON file using mochawesome-merge
9
+ * @author Yuen Ler Chow
10
+ * @param opts - object of arguments
11
+ * @param opts.resultsDir - Results directory containing the reports
12
+ * @param opts.profileName - Profile name
13
+ * @param opts.browserName - Browser name
14
+ */
15
+ const mergeReports = (
16
+ opts: {
17
+ resultsDir: string;
18
+ profileName: string;
19
+ browserName: string;
20
+ },
21
+ ): void => {
22
+ const { resultsDir, profileName, browserName } = opts;
23
+
24
+ const granularResultsDir = path.join(
25
+ resultsDir,
26
+ `${profileName}-${browserName}`,
27
+ 'granular-results',
28
+ );
29
+ const reportDataPath = path.join(
30
+ resultsDir,
31
+ `${profileName}-${browserName}`,
32
+ 'report-data.json',
33
+ );
34
+
35
+ console.log(`\n📊 Merge Reports Debug for ${profileName} + ${browserName}:`);
36
+ console.log(` Granular results dir: ${granularResultsDir}`);
37
+ console.log(` Report data path: ${reportDataPath}`);
38
+
39
+ if (!existsSync(granularResultsDir)) {
40
+ const errorMsg = `❌ ERROR: Granular results directory does not exist: ${granularResultsDir}`;
41
+ console.error(errorMsg);
42
+ throw new Error(errorMsg);
43
+ }
44
+
45
+ // List files in granular results directory
46
+ const files = readdirSync(granularResultsDir);
47
+ const jsonFiles = files.filter((f) => { return f.endsWith('.json'); });
48
+ console.log(` Found ${jsonFiles.length} JSON files: ${jsonFiles.join(', ') || '(none)'}`);
49
+
50
+ if (jsonFiles.length === 0) {
51
+ const errorMsg = `❌ ERROR: No JSON files found in granular results directory: ${granularResultsDir}`;
52
+ console.error(errorMsg);
53
+ throw new Error(errorMsg);
54
+ }
55
+
56
+ try {
57
+ const command = `npx mochawesome-merge "${path.join(granularResultsDir, '*.json')}" > "${reportDataPath}"`;
58
+
59
+ console.log(` Running: ${command}`);
60
+ console.log(`Merging reports for ${profileName} + ${browserName}...`);
61
+
62
+ execSync(command, {
63
+ stdio: 'inherit',
64
+ cwd: getRootPath(),
65
+ shell: '/bin/bash',
66
+ });
67
+
68
+ // Verify the report was created
69
+ if (!existsSync(reportDataPath)) {
70
+ const errorMsg = `❌ ERROR: Report data was not created at: ${reportDataPath}`;
71
+ console.error(errorMsg);
72
+ throw new Error(errorMsg);
73
+ }
74
+
75
+ console.log(` ✅ Report data created successfully at: ${reportDataPath}`);
76
+ } catch (e) {
77
+ console.error(`Error merging reports for ${profileName} + ${browserName}:`, e);
78
+ throw e;
79
+ }
80
+ };
81
+
82
+ export default mergeReports;
@@ -0,0 +1,272 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cypress Test Reports</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
15
+ background: #f5f5f5;
16
+ padding: 2rem 1rem;
17
+ color: #222;
18
+ }
19
+ .container {
20
+ max-width: 75rem;
21
+ margin: 0 auto;
22
+ background: #ffffff;
23
+ border-radius: 0.5rem;
24
+ border: 0.0625rem solid #e0e0e0;
25
+ box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.04);
26
+ }
27
+ header {
28
+ border-bottom: 0.0625rem solid #e0e0e0;
29
+ padding: 1.25rem 1.5rem;
30
+ text-align: center;
31
+ }
32
+ h1 {
33
+ font-size: 1.75rem;
34
+ margin-bottom: 0.625rem;
35
+ }
36
+ .subtitle {
37
+ font-size: 0.95rem;
38
+ color: #666666;
39
+ }
40
+ .content {
41
+ padding: 1.5rem;
42
+ }
43
+ .profile-section {
44
+ margin-bottom: 2.5rem;
45
+ }
46
+ .profile-title {
47
+ font-size: 1.1rem;
48
+ color: #111111;
49
+ margin-bottom: 0.75rem;
50
+ border-bottom: 0.0625rem solid #e0e0e0;
51
+ padding-bottom: 0.375rem;
52
+ }
53
+ .report-grid {
54
+ display: grid;
55
+ grid-template-columns: repeat(auto-fill, minmax(15.625rem, 1fr));
56
+ gap: 1.25rem;
57
+ margin-bottom: 1.875rem;
58
+ }
59
+ .report-card {
60
+ border: 0.0625rem solid #e0e0e0;
61
+ border-radius: 0.5rem;
62
+ padding: 1rem;
63
+ background: #fafafa;
64
+ }
65
+ .report-card.success {
66
+ border-color: #c8e6c9;
67
+ background: #f4f9f4;
68
+ }
69
+ .report-card.failed {
70
+ border-color: #ffcdd2;
71
+ background: #fdf5f6;
72
+ }
73
+ .browser-name {
74
+ font-size: 1rem;
75
+ font-weight: bold;
76
+ color: #222222;
77
+ margin-bottom: 0.625rem;
78
+ text-transform: capitalize;
79
+ }
80
+ .status {
81
+ display: inline-block;
82
+ padding: 0.25rem 0.75rem;
83
+ border-radius: 0.75rem;
84
+ font-size: 0.85em;
85
+ font-weight: bold;
86
+ margin-bottom: 0.625rem;
87
+ }
88
+ .status.success {
89
+ background: #2e7d32;
90
+ color: white;
91
+ }
92
+ .status.failed {
93
+ background: #c62828;
94
+ color: white;
95
+ }
96
+ .report-link {
97
+ display: inline-block;
98
+ margin-top: 0.625rem;
99
+ padding: 0.5rem 1rem;
100
+ background: #1976d2;
101
+ color: white;
102
+ text-decoration: none;
103
+ border-radius: 0.375rem;
104
+ transition: background 0.3s ease;
105
+ }
106
+ .report-link:hover {
107
+ background: #115293;
108
+ }
109
+ .timestamp {
110
+ font-size: 0.85em;
111
+ color: #777777;
112
+ margin-top: 0.5rem;
113
+ }
114
+ .summary {
115
+ background: #fafafa;
116
+ padding: 1rem 1.125rem;
117
+ border-radius: 0.375rem;
118
+ margin-bottom: 1.875rem;
119
+ border: 0.0625rem solid #e0e0e0;
120
+ }
121
+ .summary h2 {
122
+ color: #111111;
123
+ margin-bottom: 0.625rem;
124
+ font-size: 1rem;
125
+ }
126
+ .summary-stats {
127
+ display: flex;
128
+ gap: 1.25rem;
129
+ flex-wrap: wrap;
130
+ }
131
+ .stat {
132
+ display: flex;
133
+ flex-direction: column;
134
+ }
135
+ .stat-value {
136
+ font-size: 1.4rem;
137
+ font-weight: bold;
138
+ color: #1976d2;
139
+ }
140
+ .stat-label {
141
+ font-size: 0.9em;
142
+ color: #777777;
143
+ text-transform: uppercase;
144
+ letter-spacing: 0.0625rem;
145
+ }
146
+ .combined-report {
147
+ margin-bottom: 1.5rem;
148
+ padding: 0.875rem 1rem;
149
+ border-radius: 0.375rem;
150
+ border: 0.0625rem solid #d0d0d0;
151
+ background: #f9fafb;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: space-between;
155
+ gap: 0.75rem;
156
+ }
157
+ .combined-report-title {
158
+ font-size: 0.95rem;
159
+ font-weight: 500;
160
+ color: #111111;
161
+ }
162
+ .combined-report-subtitle {
163
+ font-size: 0.85rem;
164
+ color: #777777;
165
+ margin-top: 0.125rem;
166
+ }
167
+ .combined-report-button {
168
+ padding: 0.5rem 1rem;
169
+ border-radius: 0.25rem;
170
+ border: 0.0625rem solid #1976d2;
171
+ background: #1976d2;
172
+ color: #ffffff;
173
+ text-decoration: none;
174
+ font-size: 0.9rem;
175
+ font-weight: 500;
176
+ white-space: nowrap;
177
+ }
178
+ .combined-report-button:hover {
179
+ background: #115293;
180
+ border-color: #115293;
181
+ }
182
+ .screenshot-list {
183
+ list-style: none;
184
+ padding-left: 0;
185
+ margin-top: 0.5rem;
186
+ }
187
+ .no-report {
188
+ color: #999;
189
+ font-size: 0.9em;
190
+ }
191
+ </style>
192
+ </head>
193
+ <body>
194
+ <div class="container">
195
+ <header>
196
+ <h1>Cypress Test Reports</h1>
197
+ <p class="subtitle">Profile and browser run summary</p>
198
+ </header>
199
+ <div class="content">
200
+ <% if (hasCombinedReport) { %>
201
+ <div class="combined-report">
202
+ <div>
203
+ <div class="combined-report-title">Combined report for all profiles and browsers</div>
204
+ <div class="combined-report-subtitle">
205
+ <% if (combinedTotalTests !== null && combinedPassedTests !== null) { %>
206
+ <%= combinedPassedTests %>/<%= combinedTotalTests %> tests passing in this run.
207
+ <% } else { %>
208
+ View a single consolidated Mochawesome report for this run.
209
+ <% } %>
210
+ </div>
211
+ </div>
212
+ <a href="all-runs.html" class="combined-report-button" target="_blank">Open combined report</a>
213
+ </div>
214
+ <% } %>
215
+
216
+ <div class="summary">
217
+ <h2>Summary</h2>
218
+ <div class="summary-stats">
219
+ <div class="stat">
220
+ <div class="stat-value"><%= totalRuns %></div>
221
+ <div class="stat-label">Total Runs</div>
222
+ </div>
223
+ <div class="stat">
224
+ <div class="stat-value"><%= passedRuns %></div>
225
+ <div class="stat-label">Passed</div>
226
+ </div>
227
+ <div class="stat">
228
+ <div class="stat-value"><%= failedRuns %></div>
229
+ <div class="stat-label">Failed</div>
230
+ </div>
231
+ <div class="stat">
232
+ <div class="stat-value"><%= profileCount %></div>
233
+ <div class="stat-label">Profiles</div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <% Object.entries(reportsByProfile).forEach(([profileName, profileReports]) => { %>
239
+ <div class="profile-section">
240
+ <h2 class="profile-title">📋 Profile: <%= profileName %></h2>
241
+ <div class="report-grid">
242
+ <% profileReports.forEach((report) => { %>
243
+ <div class="report-card <%= report.success ? 'success' : 'failed' %>">
244
+ <div class="browser-name">🌐 <%= report.browser %></div>
245
+ <span class="status <%= report.success ? 'success' : 'failed' %>">
246
+ <%= report.success ? '✅ Passed' : '❌ Failed' %>
247
+ </span>
248
+ <% if (report.totalTests !== undefined && report.passedTests !== undefined) { %>
249
+ <div class="timestamp"><%= report.passedTests %>/<%= report.totalTests %> tests passing</div>
250
+ <% } %>
251
+ <div class="timestamp"><%= report.timestamp %></div>
252
+ <% if (report.relativeReportPath) { %>
253
+ <a href="<%= report.relativeReportPath %>" class="report-link" target="_blank">View Report</a>
254
+ <% } else { %>
255
+ <p class="no-report">Report not available</p>
256
+ <% } %>
257
+ <% if (report.screenshots && report.screenshots.length > 0) { %>
258
+ <ul class="screenshot-list">
259
+ <% report.screenshots.forEach((screenshot) => { %>
260
+ <li><a href="<%= screenshot.href %>" class="report-link" target="_blank"><%= screenshot.name %></a></li>
261
+ <% }) %>
262
+ </ul>
263
+ <% } %>
264
+ </div>
265
+ <% }) %>
266
+ </div>
267
+ </div>
268
+ <% }) %>
269
+ </div>
270
+ </div>
271
+ </body>
272
+ </html>
@@ -0,0 +1,164 @@
1
+ /* eslint-disable no-console */
2
+
3
+ // Import libs
4
+ import { spawn } from 'child_process';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+
8
+ // Import helpers
9
+ import getRootPath from './getRootPath';
10
+ import generateReporterConfig from './generateReporterConfig';
11
+ import mergeReports from './mergeReports';
12
+ import generateHtmlReport from './generateHtmlReport';
13
+
14
+ // Import shared types
15
+ import RunResult from '../types/RunResult';
16
+
17
+ // Import constants
18
+ import DEFAULT_THREADS_PER_COMBO from '../constants/DEFAULT_THREADS_PER_COMBO';
19
+
20
+ /**
21
+ * Run Cypress in headless mode for a single profile and browser using cypress-parallel
22
+ * @author Yuen Ler Chow
23
+ * @param opts - object of arguments
24
+ * @param opts.profileName - The profile name to use
25
+ * @param opts.browser - The browser to use
26
+ * @param opts.outputDir - The output directory for results
27
+ * @param [opts.numThreads] - The number of parallel threads to use (default: 2)
28
+ * @returns Promise that resolves with run result
29
+ */
30
+ const runCypressHeadless = (opts: {
31
+ profileName: string,
32
+ browser: string,
33
+ outputDir: string,
34
+ numThreads?: number,
35
+ }): Promise<RunResult> => {
36
+ const {
37
+ profileName,
38
+ browser,
39
+ outputDir,
40
+ numThreads = DEFAULT_THREADS_PER_COMBO,
41
+ } = opts;
42
+
43
+ return new Promise((resolve, reject) => {
44
+ const root = getRootPath();
45
+
46
+ // Ensure output directory exists
47
+ if (!fs.existsSync(outputDir)) {
48
+ fs.mkdirSync(outputDir, { recursive: true });
49
+ }
50
+
51
+ // Create subdirectory for this profile+browser combination
52
+ const comboDir = path.join(outputDir, `${profileName}-${browser}`);
53
+ if (!fs.existsSync(comboDir)) {
54
+ fs.mkdirSync(comboDir, { recursive: true });
55
+ }
56
+
57
+ // Create per-combo screenshots directory
58
+ const comboScreenshotsDir = path.join(comboDir, 'screenshots');
59
+ if (!fs.existsSync(comboScreenshotsDir)) {
60
+ fs.mkdirSync(comboScreenshotsDir, { recursive: true });
61
+ }
62
+
63
+ const env = {
64
+ ...process.env,
65
+ CYPRESS_PROFILE: profileName,
66
+ BROWSER: browser,
67
+ };
68
+
69
+ console.log(`\n🚀 Starting Cypress headless: ${profileName} + ${browser}`);
70
+ console.log(`Output: ${comboDir}\n`);
71
+
72
+ // Create reporter config for cypress-multi-reporters
73
+ const reporterConfigPath = path.join(comboDir, 'reporter-config.json');
74
+ const reporterConfig = generateReporterConfig({
75
+ resultsDir: outputDir,
76
+ profileName,
77
+ browserName: browser,
78
+ });
79
+ fs.writeFileSync(reporterConfigPath, JSON.stringify(reporterConfig, null, 2));
80
+
81
+ // Cypress parallel run command with browser, threads, and reporter config
82
+ const cypressArgsString = `--headless --browser ${browser} --config screenshotsFolder=${comboScreenshotsDir} --reporter cypress-multi-reporters --reporter-options configFile=${reporterConfigPath}`;
83
+
84
+ console.log(`Reporter config path: ${reporterConfigPath}`);
85
+ console.log(`Granular results dir: ${path.join(comboDir, 'granular-results')}`);
86
+
87
+ const args = [
88
+ 'cypress-parallel',
89
+ '-s',
90
+ 'cy:run',
91
+ '--threads',
92
+ numThreads.toString(),
93
+ '-d',
94
+ 'cypress/e2e/**/*',
95
+ '-a',
96
+ // eslint-disable-next-line quotes
97
+ `'"${cypressArgsString}"'`,
98
+ ];
99
+
100
+ const cypressProcess = spawn('npx', args, {
101
+ cwd: root,
102
+ env,
103
+ stdio: 'inherit',
104
+ shell: true,
105
+ });
106
+
107
+ cypressProcess.on('close', (code) => {
108
+ const success = code === 0;
109
+
110
+ // Merge granular mochawesome JSON and generate a single HTML report
111
+ let reportGenerated = true;
112
+ try {
113
+ // Merge all json test suites (test files) into one report
114
+ mergeReports({
115
+ resultsDir: outputDir,
116
+ profileName,
117
+ browserName: browser,
118
+ });
119
+
120
+ // Generate combined HTML report for all test suites for this single browser+profile combo
121
+ generateHtmlReport({
122
+ resultsDir: outputDir,
123
+ profileName,
124
+ browserName: browser,
125
+ });
126
+ } catch (reportError) {
127
+ console.error(`\n⚠️ Report generation failed for ${profileName} + ${browser}:`, reportError);
128
+ reportGenerated = false;
129
+ }
130
+
131
+ // By default, marge names the HTML after the JSON basename: report-data.html
132
+ const reportPath = path.join(
133
+ outputDir,
134
+ `${profileName}-${browser}`,
135
+ 'report',
136
+ 'report-data.html',
137
+ );
138
+
139
+ const result: RunResult = {
140
+ profileName,
141
+ browser,
142
+ success: success && reportGenerated,
143
+ outputDir: comboDir,
144
+ reportPath: reportGenerated ? reportPath : undefined,
145
+ };
146
+
147
+ if (success && reportGenerated) {
148
+ console.log(`\n✅ Completed: ${profileName} + ${browser}`);
149
+ } else if (!reportGenerated) {
150
+ console.log(`\n⚠️ Tests ran but report generation failed: ${profileName} + ${browser}`);
151
+ } else {
152
+ console.log(`\n❌ Failed: ${profileName} + ${browser} (exit code: ${code})`);
153
+ }
154
+
155
+ resolve(result);
156
+ });
157
+
158
+ cypressProcess.on('error', (error) => {
159
+ reject(error);
160
+ });
161
+ });
162
+ };
163
+
164
+ export default runCypressHeadless;