@flakiness/sdk 0.146.0 → 0.147.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/LICENSE +21 -45
- package/README.md +2 -27
- package/lib/browser/index.js +127 -0
- package/lib/createEnvironment.js +78 -0
- package/lib/createTestStepSnippets.js +142 -4
- package/lib/flakinessProjectConfig.js +22 -100
- package/lib/git.js +55 -0
- package/lib/{serverapi.js → httpUtils.js} +13 -25
- package/lib/{playwright-test.js → index.js} +746 -814
- package/lib/localGit.js +4 -2
- package/lib/localReportApi.js +155 -125
- package/lib/localReportServer.js +159 -129
- package/lib/pathutils.js +20 -0
- package/lib/reportUploader.js +77 -37
- package/lib/reportUtils.js +123 -0
- package/lib/showReport.js +195 -152
- package/lib/utils.js +0 -330
- package/package.json +12 -38
- package/types/tsconfig.tsbuildinfo +1 -1
package/LICENSE
CHANGED
|
@@ -1,45 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Software
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
meaning legal entities controlling, controlled by, or under common
|
|
23
|
-
control with you. If you exceed the Use Limitation, your use is
|
|
24
|
-
subject to payment of Licensor’s then-current list price for licenses.
|
|
25
|
-
|
|
26
|
-
Conditions. Redistribution in source code or other forms must include
|
|
27
|
-
a copy of this license document to be provided in a reasonable
|
|
28
|
-
manner. Any redistribution of the Software is only allowed subject to
|
|
29
|
-
this license.
|
|
30
|
-
|
|
31
|
-
Trademarks. This license does not grant you any right in the
|
|
32
|
-
trademarks, service marks, brand names or logos of Licensor.
|
|
33
|
-
|
|
34
|
-
DISCLAIMER. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OR
|
|
35
|
-
CONDITION, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES
|
|
36
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
37
|
-
NONINFRINGEMENT. LICENSORS HEREBY DISCLAIM ALL LIABILITY, WHETHER IN
|
|
38
|
-
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
39
|
-
CONNECTION WITH THE SOFTWARE.
|
|
40
|
-
|
|
41
|
-
Termination. If you violate the terms of this license, your rights
|
|
42
|
-
will terminate automatically and will not be reinstated without the
|
|
43
|
-
prior written consent of Licensor. Any such termination will not
|
|
44
|
-
affect the right of others who may have received copies of the
|
|
45
|
-
Software from you.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Degu Labs, Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,30 +1,5 @@
|
|
|
1
1
|
# Flakiness SDK
|
|
2
2
|
|
|
3
|
-
The package
|
|
4
|
-
- A CLI to interact with flakiness.io service
|
|
5
|
-
- A set of reporters for popular test runners to generate & upload Flakiness
|
|
6
|
-
report.
|
|
3
|
+
The package provides a set of tools to create Flakiness Reports in Node.js.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
To start using reporter with Playwright Test:
|
|
11
|
-
|
|
12
|
-
1. Install this package:
|
|
13
|
-
```bash
|
|
14
|
-
npm i @flakiness/sdk@latest
|
|
15
|
-
```
|
|
16
|
-
2. Add flakiness.io to the `playwright.config.ts` file:
|
|
17
|
-
```ts
|
|
18
|
-
import { defineConfig } from '@playwright/test';
|
|
19
|
-
|
|
20
|
-
export default defineConfig({
|
|
21
|
-
reporter: [
|
|
22
|
-
['list'],
|
|
23
|
-
['@flakiness/sdk/playwright-test', {
|
|
24
|
-
endpoint: 'https://flakiness.io', // custom endpoint
|
|
25
|
-
token: '...', // Flakiness access token
|
|
26
|
-
collectBrowserVersion: true, // collect browser versions
|
|
27
|
-
}]
|
|
28
|
-
],
|
|
29
|
-
});
|
|
30
|
-
```
|
|
5
|
+
Read docs at https://flakiness.io/docs/integrations/custom/
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/browser/index.ts
|
|
2
|
+
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
3
|
+
|
|
4
|
+
// src/reportUtils.ts
|
|
5
|
+
import { Multimap } from "@flakiness/shared/common/multimap.js";
|
|
6
|
+
import { xxHash, xxHashObject } from "@flakiness/shared/common/utils.js";
|
|
7
|
+
var ReportUtils;
|
|
8
|
+
((ReportUtils2) => {
|
|
9
|
+
function visitTests(report, testVisitor) {
|
|
10
|
+
function visitSuite(suite, parents) {
|
|
11
|
+
parents.push(suite);
|
|
12
|
+
for (const test of suite.tests ?? [])
|
|
13
|
+
testVisitor(test, parents);
|
|
14
|
+
for (const childSuite of suite.suites ?? [])
|
|
15
|
+
visitSuite(childSuite, parents);
|
|
16
|
+
parents.pop();
|
|
17
|
+
}
|
|
18
|
+
for (const test of report.tests ?? [])
|
|
19
|
+
testVisitor(test, []);
|
|
20
|
+
for (const suite of report.suites)
|
|
21
|
+
visitSuite(suite, []);
|
|
22
|
+
}
|
|
23
|
+
ReportUtils2.visitTests = visitTests;
|
|
24
|
+
function normalizeReport(report) {
|
|
25
|
+
const gEnvs = /* @__PURE__ */ new Map();
|
|
26
|
+
const gSuites = /* @__PURE__ */ new Map();
|
|
27
|
+
const gTests = new Multimap();
|
|
28
|
+
const gSuiteIds = /* @__PURE__ */ new Map();
|
|
29
|
+
const gTestIds = /* @__PURE__ */ new Map();
|
|
30
|
+
const gEnvIds = /* @__PURE__ */ new Map();
|
|
31
|
+
const gSuiteChildren = new Multimap();
|
|
32
|
+
const gSuiteTests = new Multimap();
|
|
33
|
+
for (const env of report.environments) {
|
|
34
|
+
const envId = computeEnvId(env);
|
|
35
|
+
gEnvs.set(envId, env);
|
|
36
|
+
gEnvIds.set(env, envId);
|
|
37
|
+
}
|
|
38
|
+
const usedEnvIds = /* @__PURE__ */ new Set();
|
|
39
|
+
function visitTests2(tests, suiteId) {
|
|
40
|
+
for (const test of tests ?? []) {
|
|
41
|
+
const testId = computeTestId(test, suiteId);
|
|
42
|
+
gTests.set(testId, test);
|
|
43
|
+
gTestIds.set(test, testId);
|
|
44
|
+
gSuiteTests.set(suiteId, test);
|
|
45
|
+
for (const attempt of test.attempts) {
|
|
46
|
+
const env = report.environments[attempt.environmentIdx];
|
|
47
|
+
const envId = gEnvIds.get(env);
|
|
48
|
+
usedEnvIds.add(envId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function visitSuite(suite, parentSuiteId) {
|
|
53
|
+
const suiteId = computeSuiteId(suite, parentSuiteId);
|
|
54
|
+
gSuites.set(suiteId, suite);
|
|
55
|
+
gSuiteIds.set(suite, suiteId);
|
|
56
|
+
for (const childSuite of suite.suites ?? []) {
|
|
57
|
+
visitSuite(childSuite, suiteId);
|
|
58
|
+
gSuiteChildren.set(suiteId, childSuite);
|
|
59
|
+
}
|
|
60
|
+
visitTests2(suite.tests ?? [], suiteId);
|
|
61
|
+
}
|
|
62
|
+
function transformTests(tests) {
|
|
63
|
+
const testIds = new Set(tests.map((test) => gTestIds.get(test)));
|
|
64
|
+
return [...testIds].map((testId) => {
|
|
65
|
+
const tests2 = gTests.getAll(testId);
|
|
66
|
+
const tags = tests2.map((test) => test.tags ?? []).flat();
|
|
67
|
+
return {
|
|
68
|
+
location: tests2[0].location,
|
|
69
|
+
title: tests2[0].title,
|
|
70
|
+
tags: tags.length ? tags : void 0,
|
|
71
|
+
attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
|
|
72
|
+
...attempt,
|
|
73
|
+
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function transformSuites(suites) {
|
|
79
|
+
const suiteIds = new Set(suites.map((suite) => gSuiteIds.get(suite)));
|
|
80
|
+
return [...suiteIds].map((suiteId) => {
|
|
81
|
+
const suite = gSuites.get(suiteId);
|
|
82
|
+
return {
|
|
83
|
+
location: suite.location,
|
|
84
|
+
title: suite.title,
|
|
85
|
+
type: suite.type,
|
|
86
|
+
suites: transformSuites(gSuiteChildren.getAll(suiteId)),
|
|
87
|
+
tests: transformTests(gSuiteTests.getAll(suiteId))
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
visitTests2(report.tests ?? [], "suiteless");
|
|
92
|
+
for (const suite of report.suites)
|
|
93
|
+
visitSuite(suite);
|
|
94
|
+
const newEnvironments = [...usedEnvIds];
|
|
95
|
+
const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
|
|
96
|
+
return {
|
|
97
|
+
...report,
|
|
98
|
+
environments: newEnvironments.map((envId) => gEnvs.get(envId)),
|
|
99
|
+
suites: transformSuites(report.suites),
|
|
100
|
+
tests: transformTests(report.tests ?? [])
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
ReportUtils2.normalizeReport = normalizeReport;
|
|
104
|
+
function computeEnvId(env) {
|
|
105
|
+
return xxHashObject(env);
|
|
106
|
+
}
|
|
107
|
+
function computeSuiteId(suite, parentSuiteId) {
|
|
108
|
+
return xxHash([
|
|
109
|
+
parentSuiteId ?? "",
|
|
110
|
+
suite.type,
|
|
111
|
+
suite.location?.file ?? "",
|
|
112
|
+
suite.title
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
function computeTestId(test, suiteId) {
|
|
116
|
+
return xxHash([
|
|
117
|
+
suiteId,
|
|
118
|
+
test.location?.file ?? "",
|
|
119
|
+
test.title
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
})(ReportUtils || (ReportUtils = {}));
|
|
123
|
+
export {
|
|
124
|
+
FlakinessReport,
|
|
125
|
+
ReportUtils
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/createEnvironment.ts
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
function shell(command, args, options) {
|
|
6
|
+
try {
|
|
7
|
+
const result = spawnSync(command, args, { encoding: "utf-8", ...options });
|
|
8
|
+
if (result.status !== 0) {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
return result.stdout.trim();
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.error(e);
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function readLinuxOSRelease() {
|
|
18
|
+
const osReleaseText = fs.readFileSync("/etc/os-release", "utf-8");
|
|
19
|
+
return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
20
|
+
line = line.trim();
|
|
21
|
+
let [key, value] = line.split("=");
|
|
22
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
23
|
+
value = value.substring(1, value.length - 1);
|
|
24
|
+
return [key, value];
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function osLinuxInfo() {
|
|
28
|
+
const arch = shell(`uname`, [`-m`]);
|
|
29
|
+
const osReleaseMap = readLinuxOSRelease();
|
|
30
|
+
const name = osReleaseMap.get("name") ?? shell(`uname`);
|
|
31
|
+
const version = osReleaseMap.get("version_id");
|
|
32
|
+
return { name, arch, version };
|
|
33
|
+
}
|
|
34
|
+
function osDarwinInfo() {
|
|
35
|
+
const name = "macos";
|
|
36
|
+
const arch = shell(`uname`, [`-m`]);
|
|
37
|
+
const version = shell(`sw_vers`, [`-productVersion`]);
|
|
38
|
+
return { name, arch, version };
|
|
39
|
+
}
|
|
40
|
+
function osWinInfo() {
|
|
41
|
+
const name = "win";
|
|
42
|
+
const arch = process.arch;
|
|
43
|
+
const version = os.release();
|
|
44
|
+
return { name, arch, version };
|
|
45
|
+
}
|
|
46
|
+
function getOSInfo() {
|
|
47
|
+
if (process.platform === "darwin")
|
|
48
|
+
return osDarwinInfo();
|
|
49
|
+
if (process.platform === "win32")
|
|
50
|
+
return osWinInfo();
|
|
51
|
+
return osLinuxInfo();
|
|
52
|
+
}
|
|
53
|
+
function extractEnvConfiguration() {
|
|
54
|
+
const ENV_PREFIX = "FK_ENV_";
|
|
55
|
+
return Object.fromEntries(
|
|
56
|
+
Object.entries(process.env).filter(([key]) => key.toUpperCase().startsWith(ENV_PREFIX.toUpperCase())).map(([key, value]) => [key.substring(ENV_PREFIX.length).toLowerCase(), (value ?? "").trim().toLowerCase()])
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function createEnvironment(options) {
|
|
60
|
+
const osInfo = getOSInfo();
|
|
61
|
+
return {
|
|
62
|
+
name: options.name,
|
|
63
|
+
systemData: {
|
|
64
|
+
osArch: osInfo.arch,
|
|
65
|
+
osName: osInfo.name,
|
|
66
|
+
osVersion: osInfo.version
|
|
67
|
+
},
|
|
68
|
+
userSuppliedData: {
|
|
69
|
+
...extractEnvConfiguration(),
|
|
70
|
+
...options.userSuppliedData ?? {}
|
|
71
|
+
},
|
|
72
|
+
opaqueData: options.opaqueData
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
createEnvironment
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=createEnvironment.js.map
|
|
@@ -1,11 +1,149 @@
|
|
|
1
1
|
// src/createTestStepSnippets.ts
|
|
2
2
|
import { codeFrameColumns } from "@babel/code-frame";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { posix as posixPath } from "path";
|
|
5
|
+
|
|
6
|
+
// src/reportUtils.ts
|
|
7
|
+
import { Multimap } from "@flakiness/shared/common/multimap.js";
|
|
8
|
+
import { xxHash, xxHashObject } from "@flakiness/shared/common/utils.js";
|
|
9
|
+
var ReportUtils;
|
|
10
|
+
((ReportUtils2) => {
|
|
11
|
+
function visitTests(report, testVisitor) {
|
|
12
|
+
function visitSuite(suite, parents) {
|
|
13
|
+
parents.push(suite);
|
|
14
|
+
for (const test of suite.tests ?? [])
|
|
15
|
+
testVisitor(test, parents);
|
|
16
|
+
for (const childSuite of suite.suites ?? [])
|
|
17
|
+
visitSuite(childSuite, parents);
|
|
18
|
+
parents.pop();
|
|
19
|
+
}
|
|
20
|
+
for (const test of report.tests ?? [])
|
|
21
|
+
testVisitor(test, []);
|
|
22
|
+
for (const suite of report.suites)
|
|
23
|
+
visitSuite(suite, []);
|
|
24
|
+
}
|
|
25
|
+
ReportUtils2.visitTests = visitTests;
|
|
26
|
+
function normalizeReport(report) {
|
|
27
|
+
const gEnvs = /* @__PURE__ */ new Map();
|
|
28
|
+
const gSuites = /* @__PURE__ */ new Map();
|
|
29
|
+
const gTests = new Multimap();
|
|
30
|
+
const gSuiteIds = /* @__PURE__ */ new Map();
|
|
31
|
+
const gTestIds = /* @__PURE__ */ new Map();
|
|
32
|
+
const gEnvIds = /* @__PURE__ */ new Map();
|
|
33
|
+
const gSuiteChildren = new Multimap();
|
|
34
|
+
const gSuiteTests = new Multimap();
|
|
35
|
+
for (const env of report.environments) {
|
|
36
|
+
const envId = computeEnvId(env);
|
|
37
|
+
gEnvs.set(envId, env);
|
|
38
|
+
gEnvIds.set(env, envId);
|
|
39
|
+
}
|
|
40
|
+
const usedEnvIds = /* @__PURE__ */ new Set();
|
|
41
|
+
function visitTests2(tests, suiteId) {
|
|
42
|
+
for (const test of tests ?? []) {
|
|
43
|
+
const testId = computeTestId(test, suiteId);
|
|
44
|
+
gTests.set(testId, test);
|
|
45
|
+
gTestIds.set(test, testId);
|
|
46
|
+
gSuiteTests.set(suiteId, test);
|
|
47
|
+
for (const attempt of test.attempts) {
|
|
48
|
+
const env = report.environments[attempt.environmentIdx];
|
|
49
|
+
const envId = gEnvIds.get(env);
|
|
50
|
+
usedEnvIds.add(envId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function visitSuite(suite, parentSuiteId) {
|
|
55
|
+
const suiteId = computeSuiteId(suite, parentSuiteId);
|
|
56
|
+
gSuites.set(suiteId, suite);
|
|
57
|
+
gSuiteIds.set(suite, suiteId);
|
|
58
|
+
for (const childSuite of suite.suites ?? []) {
|
|
59
|
+
visitSuite(childSuite, suiteId);
|
|
60
|
+
gSuiteChildren.set(suiteId, childSuite);
|
|
61
|
+
}
|
|
62
|
+
visitTests2(suite.tests ?? [], suiteId);
|
|
63
|
+
}
|
|
64
|
+
function transformTests(tests) {
|
|
65
|
+
const testIds = new Set(tests.map((test) => gTestIds.get(test)));
|
|
66
|
+
return [...testIds].map((testId) => {
|
|
67
|
+
const tests2 = gTests.getAll(testId);
|
|
68
|
+
const tags = tests2.map((test) => test.tags ?? []).flat();
|
|
69
|
+
return {
|
|
70
|
+
location: tests2[0].location,
|
|
71
|
+
title: tests2[0].title,
|
|
72
|
+
tags: tags.length ? tags : void 0,
|
|
73
|
+
attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
|
|
74
|
+
...attempt,
|
|
75
|
+
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
|
|
76
|
+
}))
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function transformSuites(suites) {
|
|
81
|
+
const suiteIds = new Set(suites.map((suite) => gSuiteIds.get(suite)));
|
|
82
|
+
return [...suiteIds].map((suiteId) => {
|
|
83
|
+
const suite = gSuites.get(suiteId);
|
|
84
|
+
return {
|
|
85
|
+
location: suite.location,
|
|
86
|
+
title: suite.title,
|
|
87
|
+
type: suite.type,
|
|
88
|
+
suites: transformSuites(gSuiteChildren.getAll(suiteId)),
|
|
89
|
+
tests: transformTests(gSuiteTests.getAll(suiteId))
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
visitTests2(report.tests ?? [], "suiteless");
|
|
94
|
+
for (const suite of report.suites)
|
|
95
|
+
visitSuite(suite);
|
|
96
|
+
const newEnvironments = [...usedEnvIds];
|
|
97
|
+
const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
|
|
98
|
+
return {
|
|
99
|
+
...report,
|
|
100
|
+
environments: newEnvironments.map((envId) => gEnvs.get(envId)),
|
|
101
|
+
suites: transformSuites(report.suites),
|
|
102
|
+
tests: transformTests(report.tests ?? [])
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
ReportUtils2.normalizeReport = normalizeReport;
|
|
106
|
+
function computeEnvId(env) {
|
|
107
|
+
return xxHashObject(env);
|
|
108
|
+
}
|
|
109
|
+
function computeSuiteId(suite, parentSuiteId) {
|
|
110
|
+
return xxHash([
|
|
111
|
+
parentSuiteId ?? "",
|
|
112
|
+
suite.type,
|
|
113
|
+
suite.location?.file ?? "",
|
|
114
|
+
suite.title
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
function computeTestId(test, suiteId) {
|
|
118
|
+
return xxHash([
|
|
119
|
+
suiteId,
|
|
120
|
+
test.location?.file ?? "",
|
|
121
|
+
test.title
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
})(ReportUtils || (ReportUtils = {}));
|
|
125
|
+
|
|
126
|
+
// src/createTestStepSnippets.ts
|
|
127
|
+
function createTestStepSnippetsInplace(report, gitRoot) {
|
|
128
|
+
const allSteps = /* @__PURE__ */ new Map();
|
|
129
|
+
ReportUtils.visitTests(report, (test) => {
|
|
130
|
+
for (const attempt of test.attempts) {
|
|
131
|
+
for (const step of attempt.steps ?? []) {
|
|
132
|
+
if (!step.location)
|
|
133
|
+
continue;
|
|
134
|
+
let fileSteps = allSteps.get(step.location.file);
|
|
135
|
+
if (!fileSteps) {
|
|
136
|
+
fileSteps = /* @__PURE__ */ new Set();
|
|
137
|
+
allSteps.set(step.location.file, fileSteps);
|
|
138
|
+
}
|
|
139
|
+
fileSteps.add(step);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
for (const [gitFilePath, steps] of allSteps) {
|
|
6
144
|
let source;
|
|
7
145
|
try {
|
|
8
|
-
source = fs.readFileSync(
|
|
146
|
+
source = fs.readFileSync(posixPath.join(gitRoot, gitFilePath), "utf-8");
|
|
9
147
|
} catch (e) {
|
|
10
148
|
continue;
|
|
11
149
|
}
|
|
@@ -27,6 +165,6 @@ function createTestStepSnippets(filepathToSteps) {
|
|
|
27
165
|
}
|
|
28
166
|
}
|
|
29
167
|
export {
|
|
30
|
-
|
|
168
|
+
createTestStepSnippetsInplace
|
|
31
169
|
};
|
|
32
170
|
//# sourceMappingURL=createTestStepSnippets.js.map
|
|
@@ -1,96 +1,26 @@
|
|
|
1
1
|
// src/flakinessProjectConfig.ts
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
import
|
|
3
|
+
import path from "path";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
import { ReportUtils } from "@flakiness/report";
|
|
5
|
+
// src/git.ts
|
|
7
6
|
import assert from "assert";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
var
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async function retryWithBackoff(job, backoff = []) {
|
|
17
|
-
for (const timeout of backoff) {
|
|
18
|
-
try {
|
|
19
|
-
return await job();
|
|
20
|
-
} catch (e) {
|
|
21
|
-
if (e instanceof AggregateError)
|
|
22
|
-
console.error(`[flakiness.io err]`, errorText(e.errors[0]));
|
|
23
|
-
else if (e instanceof Error)
|
|
24
|
-
console.error(`[flakiness.io err]`, errorText(e));
|
|
25
|
-
else
|
|
26
|
-
console.error(`[flakiness.io err]`, e);
|
|
27
|
-
await new Promise((x) => setTimeout(x, timeout));
|
|
28
|
-
}
|
|
7
|
+
|
|
8
|
+
// src/pathutils.ts
|
|
9
|
+
import { posix as posixPath, win32 as win32Path } from "path";
|
|
10
|
+
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
11
|
+
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
12
|
+
function normalizePath(aPath) {
|
|
13
|
+
if (IS_WIN32_PATH.test(aPath)) {
|
|
14
|
+
aPath = aPath.split(win32Path.sep).join(posixPath.sep);
|
|
29
15
|
}
|
|
30
|
-
|
|
16
|
+
if (IS_ALMOST_POSIX_PATH.test(aPath))
|
|
17
|
+
return "/" + aPath[0] + aPath.substring(2);
|
|
18
|
+
return aPath;
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let reject;
|
|
37
|
-
const responseDataPromise = new Promise((a, b) => {
|
|
38
|
-
resolve = a;
|
|
39
|
-
reject = b;
|
|
40
|
-
});
|
|
41
|
-
const protocol = url.startsWith("https") ? https : http;
|
|
42
|
-
headers = Object.fromEntries(Object.entries(headers).filter(([key, value]) => value !== void 0));
|
|
43
|
-
const request = protocol.request(url, { method, headers }, (res) => {
|
|
44
|
-
const chunks = [];
|
|
45
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
46
|
-
res.on("end", () => {
|
|
47
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
|
|
48
|
-
resolve(Buffer.concat(chunks));
|
|
49
|
-
else
|
|
50
|
-
reject(new Error(`Request to ${url} failed with ${res.statusCode}`));
|
|
51
|
-
});
|
|
52
|
-
res.on("error", (error) => reject(error));
|
|
53
|
-
});
|
|
54
|
-
request.on("error", reject);
|
|
55
|
-
return { request, responseDataPromise };
|
|
56
|
-
}
|
|
57
|
-
httpUtils2.createRequest = createRequest;
|
|
58
|
-
async function getBuffer(url, backoff) {
|
|
59
|
-
return await retryWithBackoff(async () => {
|
|
60
|
-
const { request, responseDataPromise } = createRequest({ url });
|
|
61
|
-
request.end();
|
|
62
|
-
return await responseDataPromise;
|
|
63
|
-
}, backoff);
|
|
64
|
-
}
|
|
65
|
-
httpUtils2.getBuffer = getBuffer;
|
|
66
|
-
async function getText(url, backoff) {
|
|
67
|
-
const buffer = await getBuffer(url, backoff);
|
|
68
|
-
return buffer.toString("utf-8");
|
|
69
|
-
}
|
|
70
|
-
httpUtils2.getText = getText;
|
|
71
|
-
async function getJSON(url) {
|
|
72
|
-
return JSON.parse(await getText(url));
|
|
73
|
-
}
|
|
74
|
-
httpUtils2.getJSON = getJSON;
|
|
75
|
-
async function postText(url, text, backoff) {
|
|
76
|
-
const headers = {
|
|
77
|
-
"Content-Type": "application/json",
|
|
78
|
-
"Content-Length": Buffer.byteLength(text) + ""
|
|
79
|
-
};
|
|
80
|
-
return await retryWithBackoff(async () => {
|
|
81
|
-
const { request, responseDataPromise } = createRequest({ url, headers, method: "post" });
|
|
82
|
-
request.write(text);
|
|
83
|
-
request.end();
|
|
84
|
-
return await responseDataPromise;
|
|
85
|
-
}, backoff);
|
|
86
|
-
}
|
|
87
|
-
httpUtils2.postText = postText;
|
|
88
|
-
async function postJSON(url, json, backoff) {
|
|
89
|
-
const buffer = await postText(url, JSON.stringify(json), backoff);
|
|
90
|
-
return JSON.parse(buffer.toString("utf-8"));
|
|
91
|
-
}
|
|
92
|
-
httpUtils2.postJSON = postJSON;
|
|
93
|
-
})(httpUtils || (httpUtils = {}));
|
|
20
|
+
|
|
21
|
+
// src/utils.ts
|
|
22
|
+
import { spawnSync } from "child_process";
|
|
23
|
+
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
94
24
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
95
25
|
function shell(command, args, options) {
|
|
96
26
|
try {
|
|
@@ -104,6 +34,8 @@ function shell(command, args, options) {
|
|
|
104
34
|
return void 0;
|
|
105
35
|
}
|
|
106
36
|
}
|
|
37
|
+
|
|
38
|
+
// src/git.ts
|
|
107
39
|
function computeGitRoot(somePathInsideGitRepo) {
|
|
108
40
|
const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
|
|
109
41
|
cwd: somePathInsideGitRepo,
|
|
@@ -112,20 +44,10 @@ function computeGitRoot(somePathInsideGitRepo) {
|
|
|
112
44
|
assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
|
|
113
45
|
return normalizePath(root);
|
|
114
46
|
}
|
|
115
|
-
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
116
|
-
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
117
|
-
function normalizePath(aPath) {
|
|
118
|
-
if (IS_WIN32_PATH.test(aPath)) {
|
|
119
|
-
aPath = aPath.split(win32Path.sep).join(posixPath.sep);
|
|
120
|
-
}
|
|
121
|
-
if (IS_ALMOST_POSIX_PATH.test(aPath))
|
|
122
|
-
return "/" + aPath[0] + aPath.substring(2);
|
|
123
|
-
return aPath;
|
|
124
|
-
}
|
|
125
47
|
|
|
126
48
|
// src/flakinessProjectConfig.ts
|
|
127
49
|
function createConfigPath(dir) {
|
|
128
|
-
return
|
|
50
|
+
return path.join(dir, ".flakiness", "config.json");
|
|
129
51
|
}
|
|
130
52
|
var gConfigPath;
|
|
131
53
|
function ensureConfigPath() {
|
|
@@ -134,7 +56,7 @@ function ensureConfigPath() {
|
|
|
134
56
|
return gConfigPath;
|
|
135
57
|
}
|
|
136
58
|
function computeConfigPath() {
|
|
137
|
-
for (let p = process.cwd(); p !==
|
|
59
|
+
for (let p = process.cwd(); p !== path.resolve(p, ".."); p = path.resolve(p, "..")) {
|
|
138
60
|
const configPath = createConfigPath(p);
|
|
139
61
|
if (fs.existsSync(configPath))
|
|
140
62
|
return configPath;
|
|
@@ -173,7 +95,7 @@ var FlakinessProjectConfig = class _FlakinessProjectConfig {
|
|
|
173
95
|
this._config.projectPublicId = projectId;
|
|
174
96
|
}
|
|
175
97
|
async save() {
|
|
176
|
-
await fs.promises.mkdir(
|
|
98
|
+
await fs.promises.mkdir(path.dirname(this._configPath), { recursive: true });
|
|
177
99
|
await fs.promises.writeFile(this._configPath, JSON.stringify(this._config, null, 2));
|
|
178
100
|
}
|
|
179
101
|
};
|
package/lib/git.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/git.ts
|
|
2
|
+
import assert from "assert";
|
|
3
|
+
|
|
4
|
+
// src/pathutils.ts
|
|
5
|
+
import { posix as posixPath, win32 as win32Path } from "path";
|
|
6
|
+
var IS_WIN32_PATH = new RegExp("^[a-zA-Z]:\\\\", "i");
|
|
7
|
+
var IS_ALMOST_POSIX_PATH = new RegExp("^[a-zA-Z]:/", "i");
|
|
8
|
+
function normalizePath(aPath) {
|
|
9
|
+
if (IS_WIN32_PATH.test(aPath)) {
|
|
10
|
+
aPath = aPath.split(win32Path.sep).join(posixPath.sep);
|
|
11
|
+
}
|
|
12
|
+
if (IS_ALMOST_POSIX_PATH.test(aPath))
|
|
13
|
+
return "/" + aPath[0] + aPath.substring(2);
|
|
14
|
+
return aPath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/utils.ts
|
|
18
|
+
import { spawnSync } from "child_process";
|
|
19
|
+
var FLAKINESS_DBG = !!process.env.FLAKINESS_DBG;
|
|
20
|
+
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
21
|
+
function shell(command, args, options) {
|
|
22
|
+
try {
|
|
23
|
+
const result = spawnSync(command, args, { encoding: "utf-8", ...options });
|
|
24
|
+
if (result.status !== 0) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
return result.stdout.trim();
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(e);
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/git.ts
|
|
35
|
+
function gitCommitInfo(gitRepo) {
|
|
36
|
+
const sha = shell(`git`, ["rev-parse", "HEAD"], {
|
|
37
|
+
cwd: gitRepo,
|
|
38
|
+
encoding: "utf-8"
|
|
39
|
+
});
|
|
40
|
+
assert(sha, `FAILED: git rev-parse HEAD @ ${gitRepo}`);
|
|
41
|
+
return sha.trim();
|
|
42
|
+
}
|
|
43
|
+
function computeGitRoot(somePathInsideGitRepo) {
|
|
44
|
+
const root = shell(`git`, ["rev-parse", "--show-toplevel"], {
|
|
45
|
+
cwd: somePathInsideGitRepo,
|
|
46
|
+
encoding: "utf-8"
|
|
47
|
+
});
|
|
48
|
+
assert(root, `FAILED: git rev-parse --show-toplevel HEAD @ ${somePathInsideGitRepo}`);
|
|
49
|
+
return normalizePath(root);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
computeGitRoot,
|
|
53
|
+
gitCommitInfo
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=git.js.map
|