@flakiness/playwright 1.6.0 → 1.8.0

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.
@@ -1,11 +1,14 @@
1
+ import {
2
+ FlakinessReport as FK
3
+ } from "@flakiness/flakiness-report";
1
4
  import {
2
5
  CIUtils,
6
+ CPUUtilization,
3
7
  GitWorktree,
8
+ RAMUtilization,
4
9
  ReportUtils,
5
10
  showReport,
6
11
  showReportCommand,
7
- CPUUtilization,
8
- RAMUtilization,
9
12
  uploadReport,
10
13
  writeReport
11
14
  } from "@flakiness/sdk";
@@ -36,6 +39,7 @@ class FlakinessReporter {
36
39
  _config;
37
40
  _rootSuite;
38
41
  _results = /* @__PURE__ */ new Map();
42
+ _stdioEntries = /* @__PURE__ */ new Map();
39
43
  _unattributedErrors = [];
40
44
  _cpuUtilization = new CPUUtilization({ precision: 10 });
41
45
  _ramUtilization = new RAMUtilization({ precision: 10 });
@@ -61,6 +65,25 @@ class FlakinessReporter {
61
65
  }
62
66
  onTestBegin(test) {
63
67
  }
68
+ onStdOut(chunk, test, result) {
69
+ this._onStdio(chunk, "stdout", result);
70
+ }
71
+ onStdErr(chunk, test, result) {
72
+ this._onStdio(chunk, "stderr", result);
73
+ }
74
+ _onStdio(chunk, stream, result) {
75
+ if (!result) return;
76
+ let entries = this._stdioEntries.get(result);
77
+ if (!entries) {
78
+ entries = [];
79
+ this._stdioEntries.set(result, entries);
80
+ }
81
+ entries.push({
82
+ data: chunk,
83
+ stream: stream === "stderr" ? FK.STREAM_STDERR : FK.STREAM_STDOUT,
84
+ time: Date.now()
85
+ });
86
+ }
64
87
  onTestEnd(test, result) {
65
88
  const results = this._results.get(test) ?? /* @__PURE__ */ new Set();
66
89
  results.add(result);
@@ -94,7 +117,6 @@ class FlakinessReporter {
94
117
  };
95
118
  }
96
119
  async _toFKRunAttempt(context, pwTest, result) {
97
- const attachments = [];
98
120
  const attempt = {
99
121
  timeout: parseDurationMS(pwTest.timeout),
100
122
  annotations: pwTest.annotations.map((annotation) => ({
@@ -107,48 +129,74 @@ class FlakinessReporter {
107
129
  parallelIndex: result.parallelIndex,
108
130
  status: result.status,
109
131
  errors: result.errors && result.errors.length ? result.errors.map((error) => this._toFKTestError(context, error)) : void 0,
110
- stdout: result.stdout ? result.stdout.map(toSTDIOEntry) : void 0,
111
- stderr: result.stderr ? result.stderr.map(toSTDIOEntry) : void 0,
112
- steps: result.steps ? result.steps.map((jsonTestStep) => this._toFKTestStep(context, jsonTestStep)) : void 0,
132
+ stdio: this._buildStdio(result),
133
+ steps: result.steps ? await Promise.all(result.steps.map((jsonTestStep) => this._toFKTestStep(context, jsonTestStep))) : void 0,
113
134
  startTimestamp: +result.startTime,
114
135
  duration: +result.duration,
115
- attachments
136
+ attachments: await this._toFKAttachments(context, result.attachments)
116
137
  };
117
- await Promise.all((result.attachments ?? []).map(async (jsonAttachment) => {
118
- if (jsonAttachment.path && !await existsAsync(jsonAttachment.path)) {
119
- context.unaccessibleAttachmentPaths.push(jsonAttachment.path);
120
- return;
121
- }
122
- let attachment;
123
- if (jsonAttachment.path)
124
- attachment = await ReportUtils.createFileAttachment(jsonAttachment.contentType, jsonAttachment.path);
125
- else if (jsonAttachment.body)
126
- attachment = await ReportUtils.createDataAttachment(jsonAttachment.contentType, jsonAttachment.body);
127
- else
128
- return;
129
- context.attachments.set(attachment.id, attachment);
130
- attachments.push({
131
- id: attachment.id,
132
- name: jsonAttachment.name,
133
- contentType: jsonAttachment.contentType
134
- });
135
- }));
136
138
  return attempt;
137
139
  }
138
- _toFKTestStep(context, pwStep) {
140
+ _buildStdio(result) {
141
+ const rawEntries = this._stdioEntries.get(result);
142
+ if (!rawEntries?.length)
143
+ return void 0;
144
+ const stdio = [];
145
+ let ts = +result.startTime;
146
+ for (const entry of rawEntries) {
147
+ const dts = Math.max(0, entry.time - ts);
148
+ ts = entry.time;
149
+ if (Buffer.isBuffer(entry.data))
150
+ stdio.push({ buffer: entry.data.toString("base64"), dts, stream: entry.stream });
151
+ else
152
+ stdio.push({ text: entry.data, dts, stream: entry.stream });
153
+ }
154
+ this._stdioEntries.delete(result);
155
+ return stdio;
156
+ }
157
+ async _toFKAttachments(context, pwAttachments) {
158
+ const all = await Promise.all(pwAttachments.map((psAttachment) => this._toFKAttachment(context, psAttachment)));
159
+ const filtered = all.filter((attachment) => attachment !== void 0);
160
+ return filtered.length ? filtered : void 0;
161
+ }
162
+ async _toFKAttachment(context, pwAttachment) {
163
+ let result = context.attachmentsCache.get(pwAttachment);
164
+ if (!result) {
165
+ result = (async () => {
166
+ if (pwAttachment.path && !await existsAsync(pwAttachment.path)) {
167
+ context.unaccessibleAttachmentPaths.push(pwAttachment.path);
168
+ return;
169
+ }
170
+ let attachment;
171
+ if (pwAttachment.path)
172
+ attachment = await ReportUtils.createFileAttachment(pwAttachment.contentType, pwAttachment.path);
173
+ else if (pwAttachment.body)
174
+ attachment = await ReportUtils.createDataAttachment(pwAttachment.contentType, pwAttachment.body);
175
+ else
176
+ return;
177
+ context.attachments.set(attachment.id, attachment);
178
+ return {
179
+ id: attachment.id,
180
+ name: pwAttachment.name,
181
+ contentType: pwAttachment.contentType
182
+ };
183
+ })();
184
+ context.attachmentsCache.set(pwAttachment, result);
185
+ }
186
+ return await result;
187
+ }
188
+ async _toFKTestStep(context, pwStep) {
139
189
  const step = {
140
190
  // NOTE: jsonStep.duration was -1 in some playwright versions
141
191
  duration: parseDurationMS(Math.max(pwStep.duration, 0)),
142
192
  title: pwStep.title,
143
- location: pwStep.location ? this._createLocation(context, pwStep.location) : void 0
193
+ location: pwStep.location ? this._createLocation(context, pwStep.location) : void 0,
194
+ attachments: await this._toFKAttachments(context, pwStep.attachments)
144
195
  };
145
- if (pwStep.location) {
146
- const resolvedPath = path.resolve(pwStep.location.file);
147
- }
148
196
  if (pwStep.error)
149
197
  step.error = this._toFKTestError(context, pwStep.error);
150
198
  if (pwStep.steps)
151
- step.steps = pwStep.steps.map((childJSONStep) => this._toFKTestStep(context, childJSONStep));
199
+ step.steps = await Promise.all(pwStep.steps.map((childJSONStep) => this._toFKTestStep(context, childJSONStep)));
152
200
  return step;
153
201
  }
154
202
  _createLocation(context, pwLocation) {
@@ -173,28 +221,23 @@ class FlakinessReporter {
173
221
  this._ramUtilization.sample();
174
222
  if (!this._config || !this._rootSuite)
175
223
  throw new Error("ERROR: failed to resolve config");
176
- let commitId;
177
- let worktree;
178
- try {
179
- worktree = GitWorktree.create(this._config.rootDir);
180
- commitId = worktree.headCommitId();
181
- } catch (e) {
182
- warn(`Failed to fetch commit info - is this a git repo?`);
224
+ const worktreeResult = GitWorktree.initialize(this._config.rootDir);
225
+ if (!worktreeResult.ok) {
226
+ warn(`Failed to fetch commit info - is this a git repo? (${worktreeResult.error})`);
183
227
  err(`Report is NOT generated.`);
184
228
  return;
185
229
  }
230
+ const { commitId, worktree } = worktreeResult;
186
231
  const configPath = this._config.configFile ? worktree.gitPath(this._config.configFile) : void 0;
187
232
  const context = {
188
233
  project2environmentIdx: /* @__PURE__ */ new Map(),
189
234
  worktree,
190
235
  attachments: /* @__PURE__ */ new Map(),
236
+ attachmentsCache: /* @__PURE__ */ new Map(),
191
237
  unaccessibleAttachmentPaths: []
192
238
  };
193
- const environmentsMap = createEnvironments(this._config.projects);
194
- if (!environmentsMap.size) {
195
- warn("Report is NOT generated since no Playwright project was executed.");
196
- return;
197
- }
239
+ const projects = this._rootSuite.suites.map((s) => s.project()).filter((p) => !!p);
240
+ const environmentsMap = createEnvironments(projects);
198
241
  if (this._options.collectBrowserVersions) {
199
242
  try {
200
243
  let playwrightPath = fs.realpathSync(process.argv[1]);
@@ -220,16 +263,17 @@ class FlakinessReporter {
220
263
  const browser = await browserType.launch({ channel, headless });
221
264
  const version = browser.version();
222
265
  await browser.close();
223
- env.userSuppliedData ??= {};
224
- env.userSuppliedData["browser"] = (channel ?? browserName).toLowerCase().trim() + " " + version;
266
+ env.metadata ??= {};
267
+ env.metadata["browser"] = (channel ?? browserName).toLowerCase().trim() + " " + version;
225
268
  }
226
269
  } catch (e) {
227
270
  err(`Failed to resolve browser version: ${e}`);
228
271
  }
229
272
  }
230
273
  const environments = [...environmentsMap.values()];
231
- for (let envIdx = 0; envIdx < environments.length; ++envIdx)
232
- context.project2environmentIdx.set(this._config.projects[envIdx], envIdx);
274
+ Array.from(environmentsMap.keys()).forEach((project, envIdx) => {
275
+ context.project2environmentIdx.set(project, envIdx);
276
+ });
233
277
  const report = ReportUtils.normalizeReport({
234
278
  flakinessProject: this._options.flakinessProject,
235
279
  title: this._options.title ?? process.env.FLAKINESS_TITLE,
@@ -280,11 +324,6 @@ To open last Flakiness report, run:
280
324
  function envBool(name) {
281
325
  return ["1", "true"].includes(process.env[name]?.toLowerCase() ?? "");
282
326
  }
283
- function toSTDIOEntry(data) {
284
- if (Buffer.isBuffer(data))
285
- return { buffer: data.toString("base64") };
286
- return { text: data };
287
- }
288
327
  function createEnvironments(projects) {
289
328
  let uniqueNames = /* @__PURE__ */ new Set();
290
329
  const result = /* @__PURE__ */ new Map();
@@ -296,12 +335,7 @@ function createEnvironments(projects) {
296
335
  for (let i = 2; uniqueNames.has(name); ++i)
297
336
  name = `${defaultName}-${i}`;
298
337
  uniqueNames.add(defaultName);
299
- const metadata = structuredClone(project.metadata);
300
- delete metadata.gitDiff;
301
- result.set(project, ReportUtils.createEnvironment({
302
- name,
303
- metadata
304
- }));
338
+ result.set(project, ReportUtils.createEnvironment({ name }));
305
339
  }
306
340
  return result;
307
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flakiness/playwright",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "author": "Degu Labs, Inc",
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
- "@flakiness/playwright": "^1.5.0",
31
+ "@flakiness/playwright": "^1.6.0",
32
32
  "@playwright/test": "^1.57.0",
33
33
  "@types/node": "^25.0.3",
34
34
  "esbuild": "^0.27.2",
@@ -37,8 +37,8 @@
37
37
  "typescript": "^5.9.3"
38
38
  },
39
39
  "dependencies": {
40
- "@flakiness/flakiness-report": "^0.29.0",
41
- "@flakiness/sdk": "^2.6.0"
40
+ "@flakiness/flakiness-report": "^0.31.0",
41
+ "@flakiness/sdk": "^3.1.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "kubik build.mts",
@@ -5,6 +5,7 @@ export default class FlakinessReporter implements Reporter {
5
5
  private _config?;
6
6
  private _rootSuite?;
7
7
  private _results;
8
+ private _stdioEntries;
8
9
  private _unattributedErrors;
9
10
  private _cpuUtilization;
10
11
  private _ramUtilization;
@@ -28,10 +29,16 @@ export default class FlakinessReporter implements Reporter {
28
29
  onBegin(config: FullConfig, suite: Suite): void;
29
30
  onError(error: TestError): void;
30
31
  onTestBegin(test: TestCase): void;
32
+ onStdOut(chunk: string | Buffer, test: TestCase | void, result: TestResult | void): void;
33
+ onStdErr(chunk: string | Buffer, test: TestCase | void, result: TestResult | void): void;
34
+ private _onStdio;
31
35
  onTestEnd(test: TestCase, result: TestResult): void;
32
36
  private _toFKSuites;
33
37
  private _toFKTest;
34
38
  private _toFKRunAttempt;
39
+ private _buildStdio;
40
+ private _toFKAttachments;
41
+ private _toFKAttachment;
35
42
  private _toFKTestStep;
36
43
  private _createLocation;
37
44
  private _toFKTestError;
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-test.d.ts","sourceRoot":"","sources":["../../src/playwright-test.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EAEV,QAAQ,EACR,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAEvC,MAAM,2BAA2B,CAAC;AAgCnC,KAAK,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ;IAgB5C,OAAO,CAAC,QAAQ;IAf5B,OAAO,CAAC,OAAO,CAAC,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,mBAAmB,CAAmB;IAE9C,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,OAAO,CAAC,CAAa;IAE7B,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAErB,QAAQ,GAAE;QAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,QAAQ,CAAC;QAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;QACjC,aAAa,CAAC,EAAE,OAAO,CAAC;KACpB;IAON,OAAO,CAAC,aAAa;IAMrB,aAAa,IAAI,OAAO;IAIxB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK;IAKxC,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAI/B,WAAW,CAAC,IAAI,EAAE,QAAQ;IAG1B,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;YAM9B,WAAW;YAsBX,SAAS;YAWT,eAAe;IAmD7B,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,cAAc;IAUhB,KAAK,CAAC,MAAM,EAAE,UAAU;IAmGxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CA4B9B"}
1
+ {"version":3,"file":"playwright-test.d.ts","sourceRoot":"","sources":["../../src/playwright-test.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EAEV,QAAQ,EACR,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAEvC,MAAM,2BAA2B,CAAC;AAwCnC,KAAK,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;AAQlD,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ;IAiB5C,OAAO,CAAC,QAAQ;IAhB5B,OAAO,CAAC,OAAO,CAAC,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,mBAAmB,CAAmB;IAE9C,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,OAAO,CAAC,CAAa;IAE7B,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAErB,QAAQ,GAAE;QAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,QAAQ,CAAC;QAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;QACjC,aAAa,CAAC,EAAE,OAAO,CAAC;KACpB;IAON,OAAO,CAAC,aAAa;IAMrB,aAAa,IAAI,OAAO;IAIxB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK;IAKxC,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAI/B,WAAW,CAAC,IAAI,EAAE,QAAQ;IAG1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAIjF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAIjF,OAAO,CAAC,QAAQ;IAchB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;YAM9B,WAAW;YAsBX,SAAS;YAWT,eAAe;IA0B7B,OAAO,CAAC,WAAW;YAkBL,gBAAgB;YAMhB,eAAe;YA4Bf,aAAa;IAgB3B,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,cAAc;IAUhB,KAAK,CAAC,MAAM,EAAE,UAAU;IAgGxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CA4B9B"}