flakiness 0.216.0 → 0.217.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 +63 -32
- package/lib/junit.js +58 -28
- package/package.json +3 -3
- package/types/tsconfig.tsbuildinfo +1 -1
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.
|
|
1030
|
+
name: "@flakiness/monorepo",
|
|
1031
|
+
version: "0.217.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
|
-
|
|
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
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
1652
|
-
startTimestamp,
|
|
1662
|
+
attachments,
|
|
1663
|
+
startTimestamp: 0,
|
|
1653
1664
|
duration,
|
|
1654
1665
|
status: xmlSkippedAnnotation ? "skipped" : errors ? "failed" : "passed",
|
|
1655
1666
|
errors,
|
|
1656
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
44
|
+
if (stack && stackPrefix)
|
|
45
|
+
stack = stackPrefix + "\n" + stack;
|
|
46
|
+
return {
|
|
47
|
+
message,
|
|
48
|
+
stack
|
|
49
|
+
};
|
|
39
50
|
}
|
|
40
|
-
function
|
|
41
|
-
const xmlStdio = testcase.children.filter((e) => e instanceof XmlElement).filter((element) => element.name ===
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
140
|
-
startTimestamp,
|
|
149
|
+
attachments,
|
|
150
|
+
startTimestamp: 0,
|
|
141
151
|
duration,
|
|
142
152
|
status: xmlSkippedAnnotation ? "skipped" : errors ? "failed" : "passed",
|
|
143
153
|
errors,
|
|
144
|
-
|
|
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.
|
|
3
|
+
"version": "0.217.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.
|
|
26
|
-
"@flakiness/shared": "0.
|
|
25
|
+
"@flakiness/server": "0.217.0",
|
|
26
|
+
"@flakiness/shared": "0.217.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@flakiness/flakiness-report": "^0.28.0",
|