flakiness 0.216.0 → 0.218.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.
package/lib/cli/cli.js CHANGED
@@ -1027,8 +1027,8 @@ import path7 from "path";
1027
1027
 
1028
1028
  // ../package.json
1029
1029
  var package_default = {
1030
- name: "flakiness",
1031
- version: "0.216.0",
1030
+ name: "@flakiness/monorepo",
1031
+ version: "0.218.0",
1032
1032
  type: "module",
1033
1033
  private: true,
1034
1034
  scripts: {
@@ -1036,7 +1036,7 @@ var package_default = {
1036
1036
  patch: "./version.mjs patch",
1037
1037
  dev: "pnpm kubik --env-file=.env.dev -w $(find . -name build.mts) ./app.mts ./github_webhooks.mts",
1038
1038
  "dev+billing": "pnpm kubik --env-file=.env.dev+billing -w $(find . -name build.mts) ./app.mts ./stripe_webhooks.mts ./github_webhooks.mts",
1039
- prod: "pnpm kubik --env-file=.env.prodlocal -w ./cli/build.mts ./server.mts ./web/build.mts ./experimental/build.mts ./landing/build.mts ./github_webhooks.mts",
1039
+ prod: "pnpm kubik --env-file=.env.prodlocal -w ./cli/build.mts ./server.mts ./docs/build.mts ./web/build.mts ./experimental/build.mts ./landing/build.mts ./github_webhooks.mts",
1040
1040
  build: "pnpm kubik $(find . -name build.mts)",
1041
1041
  perf: "node --max-old-space-size=10240 --enable-source-maps --env-file=.env.prodlocal experimental/lib/perf_filter.js"
1042
1042
  },
@@ -1046,6 +1046,7 @@ var package_default = {
1046
1046
  author: "Degu Labs, Inc",
1047
1047
  license: "Fair Source 100",
1048
1048
  devDependencies: {
1049
+ flakiness: "workspace:*",
1049
1050
  "@flakiness/playwright": "catalog:",
1050
1051
  "@playwright/test": "catalog:",
1051
1052
  "@types/node": "^22.19.3",
@@ -1511,6 +1512,7 @@ import fs3 from "fs/promises";
1511
1512
  import path3 from "path";
1512
1513
 
1513
1514
  // src/junit.ts
1515
+ import { FlakinessReport as FK } from "@flakiness/flakiness-report";
1514
1516
  import { ReportUtils } from "@flakiness/sdk";
1515
1517
  import { parseXml, XmlElement, XmlText } from "@rgrove/parse-xml";
1516
1518
  import assert from "assert";
@@ -1537,25 +1539,37 @@ function extractErrors(testcase) {
1537
1539
  const xmlErrors = testcase.children.filter((e2) => e2 instanceof XmlElement).filter((element) => element.name === "error" || element.name === "failure");
1538
1540
  if (!xmlErrors.length)
1539
1541
  return void 0;
1540
- const errors = [];
1541
- for (const xmlErr of xmlErrors) {
1542
- const message = [xmlErr.attributes["type"], xmlErr.attributes["message"]].filter((x) => !!x).join(" ");
1543
- const xmlStackNodes = xmlErr.children.filter((child) => child instanceof XmlText);
1544
- const stack = xmlStackNodes ? xmlStackNodes.map((node) => node.text).join("\n") : void 0;
1545
- errors.push({
1546
- message,
1547
- stack
1548
- });
1549
- }
1550
- return errors;
1542
+ return xmlErrors.map((xmlErr) => parseError(xmlErr));
1551
1543
  }
1552
- function extractStdout(testcase, stdio) {
1553
- const xmlStdio = testcase.children.filter((e2) => e2 instanceof XmlElement).filter((element) => element.name === stdio);
1554
- if (!xmlStdio.length)
1555
- return void 0;
1556
- return xmlStdio.map((node) => node.children.filter((node2) => node2 instanceof XmlText)).flat().map((txtNode) => ({
1557
- text: txtNode.text
1558
- }));
1544
+ function parseError(xmlErr, explicitStackTrace) {
1545
+ const stackTraceContainer = explicitStackTrace ? xmlErr.children.find((child) => child instanceof XmlElement && child.name === "stackTrace") : xmlErr;
1546
+ const xmlStackNodes = stackTraceContainer?.children.filter((child) => child instanceof XmlText);
1547
+ let stack = xmlStackNodes ? xmlStackNodes.map((node) => node.text).join("\n") : void 0;
1548
+ let message = "";
1549
+ let stackPrefix = "";
1550
+ for (const token of [xmlErr.attributes["type"], xmlErr.attributes["message"]]) {
1551
+ if (!token)
1552
+ continue;
1553
+ message = (message ? message + " " : "") + token;
1554
+ if (!stack?.includes(token))
1555
+ stackPrefix = (stackPrefix ? stackPrefix + " " : "") + token;
1556
+ }
1557
+ if (stack && stackPrefix)
1558
+ stack = stackPrefix + "\n" + stack;
1559
+ return {
1560
+ message,
1561
+ stack
1562
+ };
1563
+ }
1564
+ function extractStdIO(testcase) {
1565
+ const xmlStdio = testcase.children.filter((e2) => e2 instanceof XmlElement).filter((element) => element.name === "system-out" || element.name === "system-err");
1566
+ return xmlStdio.map((node) => {
1567
+ return node.children.filter((child) => child instanceof XmlText).map((txtNode) => ({
1568
+ stream: node.name === "system-out" ? FK.STREAM_STDOUT : FK.STREAM_STDERR,
1569
+ text: txtNode.text,
1570
+ dts: 0
1571
+ }));
1572
+ }).flat();
1559
1573
  }
1560
1574
  async function parseAttachment(value) {
1561
1575
  let absolutePath = path2.resolve(process.cwd(), value);
@@ -1567,7 +1581,7 @@ async function traverseJUnitReport(context, node) {
1567
1581
  const element = node;
1568
1582
  if (!(element instanceof XmlElement))
1569
1583
  return;
1570
- let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs, attachments } = context;
1584
+ let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs } = context;
1571
1585
  if (element.attributes["timestamp"])
1572
1586
  currentTimeMs = new Date(element.attributes["timestamp"]).getTime();
1573
1587
  if (element.name === "testsuite") {
@@ -1606,19 +1620,16 @@ async function traverseJUnitReport(context, node) {
1606
1620
  const file = element.attributes["file"];
1607
1621
  const name = element.attributes["name"];
1608
1622
  const line = parseInt(element.attributes["line"], 10);
1609
- const timeMs = parseFloat(element.attributes["time"]) * 1e3;
1610
- const startTimestamp = currentTimeMs;
1611
- const duration = timeMs;
1612
- currentTimeMs += timeMs;
1623
+ const duration = parseFloat(element.attributes["time"]) * 1e3;
1613
1624
  const annotations = [];
1614
- const attachments2 = [];
1625
+ const attachments = [];
1615
1626
  for (const [key, value] of getProperties(element)) {
1616
1627
  if (key.toLowerCase().startsWith("attachment")) {
1617
1628
  if (context.ignoreAttachments)
1618
1629
  continue;
1619
1630
  const attachment = await parseAttachment(value);
1620
1631
  context.attachments.set(attachment.id, attachment);
1621
- attachments2.push({
1632
+ attachments.push({
1622
1633
  id: attachment.id,
1623
1634
  contentType: attachment.contentType,
1624
1635
  //TODO: better default names for attachments?
@@ -1648,15 +1659,35 @@ async function traverseJUnitReport(context, node) {
1648
1659
  environmentIdx: currentEnvIndex,
1649
1660
  expectedStatus,
1650
1661
  annotations,
1651
- attachments: attachments2,
1652
- startTimestamp,
1662
+ attachments,
1663
+ startTimestamp: 0,
1653
1664
  duration,
1654
1665
  status: xmlSkippedAnnotation ? "skipped" : errors ? "failed" : "passed",
1655
1666
  errors,
1656
- stdout: extractStdout(element, "system-out"),
1657
- stderr: extractStdout(element, "system-err")
1667
+ stdio: extractStdIO(element)
1658
1668
  }]
1659
1669
  };
1670
+ for (const rerun of element.children.filter((child) => child instanceof XmlElement && ["rerunFailure", "rerunError", "flakyError", "flakyFailure"].includes(child.name))) {
1671
+ const duration2 = parseFloat(rerun.attributes["time"] || "0") * 1e3;
1672
+ const attempt = {
1673
+ environmentIdx: currentEnvIndex,
1674
+ expectedStatus,
1675
+ annotations,
1676
+ startTimestamp: 0,
1677
+ duration: duration2,
1678
+ status: "failed",
1679
+ errors: [parseError(rerun, "explicit-stack-trace")],
1680
+ stdio: extractStdIO(rerun)
1681
+ };
1682
+ if (rerun.name.startsWith("flaky"))
1683
+ test.attempts.splice(test.attempts.length - 1, 0, attempt);
1684
+ else
1685
+ test.attempts.push(attempt);
1686
+ }
1687
+ for (const attempt of test.attempts) {
1688
+ attempt.startTimestamp = currentTimeMs;
1689
+ currentTimeMs += attempt.duration;
1690
+ }
1660
1691
  currentSuite.tests ??= [];
1661
1692
  currentSuite.tests.push(test);
1662
1693
  }
package/lib/junit.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/junit.ts
2
+ import { FlakinessReport as FK } from "@flakiness/flakiness-report";
2
3
  import { ReportUtils } from "@flakiness/sdk";
3
4
  import { parseXml, XmlElement, XmlText } from "@rgrove/parse-xml";
4
5
  import assert from "assert";
@@ -25,25 +26,37 @@ function extractErrors(testcase) {
25
26
  const xmlErrors = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name === "error" || element.name === "failure");
26
27
  if (!xmlErrors.length)
27
28
  return void 0;
28
- const errors = [];
29
- for (const xmlErr of xmlErrors) {
30
- const message = [xmlErr.attributes["type"], xmlErr.attributes["message"]].filter((x) => !!x).join(" ");
31
- const xmlStackNodes = xmlErr.children.filter((child) => child instanceof XmlText);
32
- const stack = xmlStackNodes ? xmlStackNodes.map((node) => node.text).join("\n") : void 0;
33
- errors.push({
34
- message,
35
- stack
36
- });
29
+ return xmlErrors.map((xmlErr) => parseError(xmlErr));
30
+ }
31
+ function parseError(xmlErr, explicitStackTrace) {
32
+ const stackTraceContainer = explicitStackTrace ? xmlErr.children.find((child) => child instanceof XmlElement && child.name === "stackTrace") : xmlErr;
33
+ const xmlStackNodes = stackTraceContainer?.children.filter((child) => child instanceof XmlText);
34
+ let stack = xmlStackNodes ? xmlStackNodes.map((node) => node.text).join("\n") : void 0;
35
+ let message = "";
36
+ let stackPrefix = "";
37
+ for (const token of [xmlErr.attributes["type"], xmlErr.attributes["message"]]) {
38
+ if (!token)
39
+ continue;
40
+ message = (message ? message + " " : "") + token;
41
+ if (!stack?.includes(token))
42
+ stackPrefix = (stackPrefix ? stackPrefix + " " : "") + token;
37
43
  }
38
- return errors;
44
+ if (stack && stackPrefix)
45
+ stack = stackPrefix + "\n" + stack;
46
+ return {
47
+ message,
48
+ stack
49
+ };
39
50
  }
40
- function extractStdout(testcase, stdio) {
41
- const xmlStdio = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name === stdio);
42
- if (!xmlStdio.length)
43
- return void 0;
44
- return xmlStdio.map((node) => node.children.filter((node2) => node2 instanceof XmlText)).flat().map((txtNode) => ({
45
- text: txtNode.text
46
- }));
51
+ function extractStdIO(testcase) {
52
+ const xmlStdio = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name === "system-out" || element.name === "system-err");
53
+ return xmlStdio.map((node) => {
54
+ return node.children.filter((child) => child instanceof XmlText).map((txtNode) => ({
55
+ stream: node.name === "system-out" ? FK.STREAM_STDOUT : FK.STREAM_STDERR,
56
+ text: txtNode.text,
57
+ dts: 0
58
+ }));
59
+ }).flat();
47
60
  }
48
61
  async function parseAttachment(value) {
49
62
  let absolutePath = path.resolve(process.cwd(), value);
@@ -55,7 +68,7 @@ async function traverseJUnitReport(context, node) {
55
68
  const element = node;
56
69
  if (!(element instanceof XmlElement))
57
70
  return;
58
- let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs, attachments } = context;
71
+ let { currentEnv, currentEnvIndex, currentSuite, report, currentTimeMs } = context;
59
72
  if (element.attributes["timestamp"])
60
73
  currentTimeMs = new Date(element.attributes["timestamp"]).getTime();
61
74
  if (element.name === "testsuite") {
@@ -94,19 +107,16 @@ async function traverseJUnitReport(context, node) {
94
107
  const file = element.attributes["file"];
95
108
  const name = element.attributes["name"];
96
109
  const line = parseInt(element.attributes["line"], 10);
97
- const timeMs = parseFloat(element.attributes["time"]) * 1e3;
98
- const startTimestamp = currentTimeMs;
99
- const duration = timeMs;
100
- currentTimeMs += timeMs;
110
+ const duration = parseFloat(element.attributes["time"]) * 1e3;
101
111
  const annotations = [];
102
- const attachments2 = [];
112
+ const attachments = [];
103
113
  for (const [key, value] of getProperties(element)) {
104
114
  if (key.toLowerCase().startsWith("attachment")) {
105
115
  if (context.ignoreAttachments)
106
116
  continue;
107
117
  const attachment = await parseAttachment(value);
108
118
  context.attachments.set(attachment.id, attachment);
109
- attachments2.push({
119
+ attachments.push({
110
120
  id: attachment.id,
111
121
  contentType: attachment.contentType,
112
122
  //TODO: better default names for attachments?
@@ -136,15 +146,35 @@ async function traverseJUnitReport(context, node) {
136
146
  environmentIdx: currentEnvIndex,
137
147
  expectedStatus,
138
148
  annotations,
139
- attachments: attachments2,
140
- startTimestamp,
149
+ attachments,
150
+ startTimestamp: 0,
141
151
  duration,
142
152
  status: xmlSkippedAnnotation ? "skipped" : errors ? "failed" : "passed",
143
153
  errors,
144
- stdout: extractStdout(element, "system-out"),
145
- stderr: extractStdout(element, "system-err")
154
+ stdio: extractStdIO(element)
146
155
  }]
147
156
  };
157
+ for (const rerun of element.children.filter((child) => child instanceof XmlElement && ["rerunFailure", "rerunError", "flakyError", "flakyFailure"].includes(child.name))) {
158
+ const duration2 = parseFloat(rerun.attributes["time"] || "0") * 1e3;
159
+ const attempt = {
160
+ environmentIdx: currentEnvIndex,
161
+ expectedStatus,
162
+ annotations,
163
+ startTimestamp: 0,
164
+ duration: duration2,
165
+ status: "failed",
166
+ errors: [parseError(rerun, "explicit-stack-trace")],
167
+ stdio: extractStdIO(rerun)
168
+ };
169
+ if (rerun.name.startsWith("flaky"))
170
+ test.attempts.splice(test.attempts.length - 1, 0, attempt);
171
+ else
172
+ test.attempts.push(attempt);
173
+ }
174
+ for (const attempt of test.attempts) {
175
+ attempt.startTimestamp = currentTimeMs;
176
+ currentTimeMs += attempt.duration;
177
+ }
148
178
  currentSuite.tests ??= [];
149
179
  currentSuite.tests.push(test);
150
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flakiness",
3
- "version": "0.216.0",
3
+ "version": "0.218.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "flakiness": "./lib/cli/cli.js"
@@ -22,8 +22,8 @@
22
22
  "@playwright/test": "^1.57.0",
23
23
  "@types/debug": "^4.1.12",
24
24
  "@types/express": "^4.17.20",
25
- "@flakiness/server": "0.216.0",
26
- "@flakiness/shared": "0.216.0"
25
+ "@flakiness/shared": "0.218.0",
26
+ "@flakiness/server": "0.218.0"
27
27
  },
28
28
  "dependencies": {
29
29
  "@flakiness/flakiness-report": "^0.28.0",