json.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. exports.serializePatterns = serializePatterns;
  7. exports.toPosixPath = toPosixPath;
  8. var _fs = _interopRequireDefault(require("fs"));
  9. var _path = _interopRequireDefault(require("path"));
  10. var _base = require("./base");
  11. var _utils = require("playwright-core/lib/utils");
  12. var _config = require("../common/config");
  13. var _empty = _interopRequireDefault(require("./empty"));
  14. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  15. /**
  16. * Copyright (c) Microsoft Corporation.
  17. *
  18. * Licensed under the Apache License, Version 2.0 (the "License");
  19. * you may not use this file except in compliance with the License.
  20. * You may obtain a copy of the License at
  21. *
  22. * http://www.apache.org/licenses/LICENSE-2.0
  23. *
  24. * Unless required by applicable law or agreed to in writing, software
  25. * distributed under the License is distributed on an "AS IS" BASIS,
  26. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27. * See the License for the specific language governing permissions and
  28. * limitations under the License.
  29. */
  30. function toPosixPath(aPath) {
  31. return aPath.split(_path.default.sep).join(_path.default.posix.sep);
  32. }
  33. class JSONReporter extends _empty.default {
  34. constructor(options = {}) {
  35. super();
  36. this.config = void 0;
  37. this.suite = void 0;
  38. this._errors = [];
  39. this._outputFile = void 0;
  40. this._outputFile = options.outputFile || reportOutputNameFromEnv();
  41. }
  42. printsToStdio() {
  43. return !this._outputFile;
  44. }
  45. onConfigure(config) {
  46. this.config = config;
  47. }
  48. onBegin(suite) {
  49. this.suite = suite;
  50. }
  51. onError(error) {
  52. this._errors.push(error);
  53. }
  54. async onEnd(result) {
  55. await outputReport(this._serializeReport(result), this.config, this._outputFile);
  56. }
  57. _serializeReport(result) {
  58. const report = {
  59. config: {
  60. ...removePrivateFields(this.config),
  61. rootDir: toPosixPath(this.config.rootDir),
  62. projects: this.config.projects.map(project => {
  63. return {
  64. outputDir: toPosixPath(project.outputDir),
  65. repeatEach: project.repeatEach,
  66. retries: project.retries,
  67. metadata: project.metadata,
  68. id: (0, _config.getProjectId)(project),
  69. name: project.name,
  70. testDir: toPosixPath(project.testDir),
  71. testIgnore: serializePatterns(project.testIgnore),
  72. testMatch: serializePatterns(project.testMatch),
  73. timeout: project.timeout
  74. };
  75. })
  76. },
  77. suites: this._mergeSuites(this.suite.suites),
  78. errors: this._errors,
  79. stats: {
  80. startTime: result.startTime.toISOString(),
  81. duration: result.duration,
  82. expected: 0,
  83. skipped: 0,
  84. unexpected: 0,
  85. flaky: 0
  86. }
  87. };
  88. for (const test of this.suite.allTests()) ++report.stats[test.outcome()];
  89. return report;
  90. }
  91. _mergeSuites(suites) {
  92. const fileSuites = new _utils.MultiMap();
  93. for (const projectSuite of suites) {
  94. const projectId = (0, _config.getProjectId)(projectSuite.project());
  95. const projectName = projectSuite.project().name;
  96. for (const fileSuite of projectSuite.suites) {
  97. const file = fileSuite.location.file;
  98. const serialized = this._serializeSuite(projectId, projectName, fileSuite);
  99. if (serialized) fileSuites.set(file, serialized);
  100. }
  101. }
  102. const results = [];
  103. for (const [, suites] of fileSuites) {
  104. const result = {
  105. title: suites[0].title,
  106. file: suites[0].file,
  107. column: 0,
  108. line: 0,
  109. specs: []
  110. };
  111. for (const suite of suites) this._mergeTestsFromSuite(result, suite);
  112. results.push(result);
  113. }
  114. return results;
  115. }
  116. _relativeLocation(location) {
  117. if (!location) return {
  118. file: '',
  119. line: 0,
  120. column: 0
  121. };
  122. return {
  123. file: toPosixPath(_path.default.relative(this.config.rootDir, location.file)),
  124. line: location.line,
  125. column: location.column
  126. };
  127. }
  128. _locationMatches(s1, s2) {
  129. return s1.file === s2.file && s1.line === s2.line && s1.column === s2.column;
  130. }
  131. _mergeTestsFromSuite(to, from) {
  132. for (const fromSuite of from.suites || []) {
  133. const toSuite = (to.suites || []).find(s => s.title === fromSuite.title && this._locationMatches(s, fromSuite));
  134. if (toSuite) {
  135. this._mergeTestsFromSuite(toSuite, fromSuite);
  136. } else {
  137. if (!to.suites) to.suites = [];
  138. to.suites.push(fromSuite);
  139. }
  140. }
  141. for (const spec of from.specs || []) {
  142. const toSpec = to.specs.find(s => s.title === spec.title && s.file === toPosixPath(_path.default.relative(this.config.rootDir, spec.file)) && s.line === spec.line && s.column === spec.column);
  143. if (toSpec) toSpec.tests.push(...spec.tests);else to.specs.push(spec);
  144. }
  145. }
  146. _serializeSuite(projectId, projectName, suite) {
  147. if (!suite.allTests().length) return null;
  148. const suites = suite.suites.map(suite => this._serializeSuite(projectId, projectName, suite)).filter(s => s);
  149. return {
  150. title: suite.title,
  151. ...this._relativeLocation(suite.location),
  152. specs: suite.tests.map(test => this._serializeTestSpec(projectId, projectName, test)),
  153. suites: suites.length ? suites : undefined
  154. };
  155. }
  156. _serializeTestSpec(projectId, projectName, test) {
  157. return {
  158. title: test.title,
  159. ok: test.ok(),
  160. tags: (test.title.match(/@[\S]+/g) || []).map(t => t.substring(1)),
  161. tests: [this._serializeTest(projectId, projectName, test)],
  162. id: test.id,
  163. ...this._relativeLocation(test.location)
  164. };
  165. }
  166. _serializeTest(projectId, projectName, test) {
  167. return {
  168. timeout: test.timeout,
  169. annotations: test.annotations,
  170. expectedStatus: test.expectedStatus,
  171. projectId,
  172. projectName,
  173. results: test.results.map(r => this._serializeTestResult(r, test)),
  174. status: test.outcome()
  175. };
  176. }
  177. _serializeTestResult(result, test) {
  178. var _result$error;
  179. const steps = result.steps.filter(s => s.category === 'test.step');
  180. const jsonResult = {
  181. workerIndex: result.workerIndex,
  182. status: result.status,
  183. duration: result.duration,
  184. error: result.error,
  185. errors: result.errors.map(e => this._serializeError(e)),
  186. stdout: result.stdout.map(s => stdioEntry(s)),
  187. stderr: result.stderr.map(s => stdioEntry(s)),
  188. retry: result.retry,
  189. steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined,
  190. startTime: result.startTime.toISOString(),
  191. attachments: result.attachments.map(a => {
  192. var _a$body;
  193. return {
  194. name: a.name,
  195. contentType: a.contentType,
  196. path: a.path,
  197. body: (_a$body = a.body) === null || _a$body === void 0 ? void 0 : _a$body.toString('base64')
  198. };
  199. })
  200. };
  201. if ((_result$error = result.error) !== null && _result$error !== void 0 && _result$error.stack) jsonResult.errorLocation = (0, _base.prepareErrorStack)(result.error.stack).location;
  202. return jsonResult;
  203. }
  204. _serializeError(error) {
  205. return (0, _base.formatError)(error, true);
  206. }
  207. _serializeTestStep(step) {
  208. const steps = step.steps.filter(s => s.category === 'test.step');
  209. return {
  210. title: step.title,
  211. duration: step.duration,
  212. error: step.error,
  213. steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined
  214. };
  215. }
  216. }
  217. async function outputReport(report, config, outputFile) {
  218. const reportString = JSON.stringify(report, undefined, 2);
  219. if (outputFile) {
  220. (0, _utils.assert)(config.configFile || _path.default.isAbsolute(outputFile), 'Expected fully resolved path if not using config file.');
  221. outputFile = config.configFile ? _path.default.resolve(_path.default.dirname(config.configFile), outputFile) : outputFile;
  222. await _fs.default.promises.mkdir(_path.default.dirname(outputFile), {
  223. recursive: true
  224. });
  225. await _fs.default.promises.writeFile(outputFile, reportString);
  226. } else {
  227. console.log(reportString);
  228. }
  229. }
  230. function stdioEntry(s) {
  231. if (typeof s === 'string') return {
  232. text: s
  233. };
  234. return {
  235. buffer: s.toString('base64')
  236. };
  237. }
  238. function removePrivateFields(config) {
  239. return Object.fromEntries(Object.entries(config).filter(([name, value]) => !name.startsWith('_')));
  240. }
  241. function reportOutputNameFromEnv() {
  242. if (process.env[`PLAYWRIGHT_JSON_OUTPUT_NAME`]) return _path.default.resolve(process.cwd(), process.env[`PLAYWRIGHT_JSON_OUTPUT_NAME`]);
  243. return undefined;
  244. }
  245. function serializePatterns(patterns) {
  246. if (!Array.isArray(patterns)) patterns = [patterns];
  247. return patterns.map(s => s.toString());
  248. }
  249. var _default = exports.default = JSONReporter;