testTracing.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.TestTracing = void 0;
  6. exports.mergeTraceFiles = mergeTraceFiles;
  7. exports.testTraceEntryName = void 0;
  8. var _fs = _interopRequireDefault(require("fs"));
  9. var _path = _interopRequireDefault(require("path"));
  10. var _utils = require("playwright-core/lib/utils");
  11. var _zipBundle = require("playwright-core/lib/zipBundle");
  12. var _util = require("../util");
  13. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  14. /**
  15. * Copyright Microsoft Corporation. All rights reserved.
  16. *
  17. * Licensed under the Apache License, Version 2.0 (the "License");
  18. * you may not use this file except in compliance with the License.
  19. * You may obtain a copy of the License at
  20. *
  21. * http://www.apache.org/licenses/LICENSE-2.0
  22. *
  23. * Unless required by applicable law or agreed to in writing, software
  24. * distributed under the License is distributed on an "AS IS" BASIS,
  25. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  26. * See the License for the specific language governing permissions and
  27. * limitations under the License.
  28. */
  29. const testTraceEntryName = exports.testTraceEntryName = 'test.trace';
  30. class TestTracing {
  31. constructor() {
  32. this._liveTraceFile = void 0;
  33. this._traceEvents = [];
  34. this._options = void 0;
  35. }
  36. start(liveFileName, options) {
  37. this._options = options;
  38. if (!this._liveTraceFile && options._live) {
  39. this._liveTraceFile = liveFileName;
  40. _fs.default.mkdirSync(_path.default.dirname(this._liveTraceFile), {
  41. recursive: true
  42. });
  43. const data = this._traceEvents.map(e => JSON.stringify(e)).join('\n') + '\n';
  44. _fs.default.writeFileSync(this._liveTraceFile, data);
  45. }
  46. }
  47. async stop(fileName) {
  48. var _this$_options, _this$_options2;
  49. const zipFile = new _zipBundle.yazl.ZipFile();
  50. if (!((_this$_options = this._options) !== null && _this$_options !== void 0 && _this$_options.attachments)) {
  51. for (const event of this._traceEvents) {
  52. if (event.type === 'after') delete event.attachments;
  53. }
  54. }
  55. if ((_this$_options2 = this._options) !== null && _this$_options2 !== void 0 && _this$_options2.sources) {
  56. const sourceFiles = new Set();
  57. for (const event of this._traceEvents) {
  58. if (event.type === 'before') {
  59. for (const frame of event.stack || []) sourceFiles.add(frame.file);
  60. }
  61. }
  62. for (const sourceFile of sourceFiles) {
  63. await _fs.default.promises.readFile(sourceFile, 'utf8').then(source => {
  64. zipFile.addBuffer(Buffer.from(source), 'resources/src@' + (0, _utils.calculateSha1)(sourceFile) + '.txt');
  65. }).catch(() => {});
  66. }
  67. }
  68. const sha1s = new Set();
  69. for (const event of this._traceEvents.filter(e => e.type === 'after')) {
  70. for (const attachment of event.attachments || []) {
  71. let contentPromise;
  72. if (attachment.path) contentPromise = _fs.default.promises.readFile(attachment.path).catch(() => undefined);else if (attachment.base64) contentPromise = Promise.resolve(Buffer.from(attachment.base64, 'base64'));
  73. const content = await contentPromise;
  74. if (content === undefined) continue;
  75. const sha1 = (0, _utils.calculateSha1)(content);
  76. attachment.sha1 = sha1;
  77. delete attachment.path;
  78. delete attachment.base64;
  79. if (sha1s.has(sha1)) continue;
  80. sha1s.add(sha1);
  81. zipFile.addBuffer(content, 'resources/' + sha1);
  82. }
  83. }
  84. const traceContent = Buffer.from(this._traceEvents.map(e => JSON.stringify(e)).join('\n'));
  85. zipFile.addBuffer(traceContent, testTraceEntryName);
  86. await new Promise(f => {
  87. zipFile.end(undefined, () => {
  88. zipFile.outputStream.pipe(_fs.default.createWriteStream(fileName)).on('close', f);
  89. });
  90. });
  91. }
  92. appendForError(error) {
  93. var _error$stack;
  94. const rawStack = ((_error$stack = error.stack) === null || _error$stack === void 0 ? void 0 : _error$stack.split('\n')) || [];
  95. const stack = rawStack ? (0, _util.filteredStackTrace)(rawStack) : [];
  96. this._appendTraceEvent({
  97. type: 'error',
  98. message: error.message || String(error.value),
  99. stack
  100. });
  101. }
  102. appendStdioToTrace(type, chunk) {
  103. this._appendTraceEvent({
  104. type,
  105. timestamp: (0, _utils.monotonicTime)(),
  106. text: typeof chunk === 'string' ? chunk : undefined,
  107. base64: typeof chunk === 'string' ? undefined : chunk.toString('base64')
  108. });
  109. }
  110. appendBeforeActionForStep(callId, parentId, apiName, params, wallTime, stack) {
  111. this._appendTraceEvent({
  112. type: 'before',
  113. callId,
  114. parentId,
  115. wallTime,
  116. startTime: (0, _utils.monotonicTime)(),
  117. class: 'Test',
  118. method: 'step',
  119. apiName,
  120. params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])),
  121. stack
  122. });
  123. }
  124. appendAfterActionForStep(callId, error, attachments = []) {
  125. this._appendTraceEvent({
  126. type: 'after',
  127. callId,
  128. endTime: (0, _utils.monotonicTime)(),
  129. attachments: serializeAttachments(attachments),
  130. error
  131. });
  132. }
  133. _appendTraceEvent(event) {
  134. this._traceEvents.push(event);
  135. if (this._liveTraceFile) _fs.default.appendFileSync(this._liveTraceFile, JSON.stringify(event) + '\n');
  136. }
  137. }
  138. exports.TestTracing = TestTracing;
  139. function serializeAttachments(attachments) {
  140. return attachments.filter(a => a.name !== 'trace').map(a => {
  141. var _a$body;
  142. return {
  143. name: a.name,
  144. contentType: a.contentType,
  145. path: a.path,
  146. base64: (_a$body = a.body) === null || _a$body === void 0 ? void 0 : _a$body.toString('base64')
  147. };
  148. });
  149. }
  150. function generatePreview(value, visited = new Set()) {
  151. if (visited.has(value)) return '';
  152. visited.add(value);
  153. if (typeof value === 'string') return value;
  154. if (typeof value === 'number') return value.toString();
  155. if (typeof value === 'boolean') return value.toString();
  156. if (value === null) return 'null';
  157. if (value === undefined) return 'undefined';
  158. if (Array.isArray(value)) return '[' + value.map(v => generatePreview(v, visited)).join(', ') + ']';
  159. if (typeof value === 'object') return 'Object';
  160. return String(value);
  161. }
  162. async function mergeTraceFiles(fileName, temporaryTraceFiles) {
  163. if (temporaryTraceFiles.length === 1) {
  164. await _fs.default.promises.rename(temporaryTraceFiles[0], fileName);
  165. return;
  166. }
  167. const mergePromise = new _utils.ManualPromise();
  168. const zipFile = new _zipBundle.yazl.ZipFile();
  169. const entryNames = new Set();
  170. zipFile.on('error', error => mergePromise.reject(error));
  171. for (let i = temporaryTraceFiles.length - 1; i >= 0; --i) {
  172. const tempFile = temporaryTraceFiles[i];
  173. const promise = new _utils.ManualPromise();
  174. _zipBundle.yauzl.open(tempFile, (err, inZipFile) => {
  175. if (err) {
  176. promise.reject(err);
  177. return;
  178. }
  179. let pendingEntries = inZipFile.entryCount;
  180. inZipFile.on('entry', entry => {
  181. let entryName = entry.fileName;
  182. if (entry.fileName === testTraceEntryName) {
  183. // Keep the name for test traces so that the last test trace
  184. // that contains most of the information is kept in the trace.
  185. // Note the reverse order of the iteration (from new traces to old).
  186. } else if (entry.fileName.match(/[\d-]*trace\./)) {
  187. entryName = i + '-' + entry.fileName;
  188. }
  189. if (entryNames.has(entryName)) {
  190. if (--pendingEntries === 0) promise.resolve();
  191. return;
  192. }
  193. entryNames.add(entryName);
  194. inZipFile.openReadStream(entry, (err, readStream) => {
  195. if (err) {
  196. promise.reject(err);
  197. return;
  198. }
  199. zipFile.addReadStream(readStream, entryName);
  200. if (--pendingEntries === 0) promise.resolve();
  201. });
  202. });
  203. });
  204. await promise;
  205. }
  206. zipFile.end(undefined, () => {
  207. zipFile.outputStream.pipe(_fs.default.createWriteStream(fileName)).on('close', () => {
  208. void Promise.all(temporaryTraceFiles.map(tempFile => _fs.default.promises.unlink(tempFile))).then(() => {
  209. mergePromise.resolve();
  210. }).catch(error => mergePromise.reject(error));
  211. }).on('error', error => mergePromise.reject(error));
  212. });
  213. await mergePromise;
  214. }