@flakiness/vitest 1.8.0 → 1.9.1
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/README.md +32 -5
- package/lib/reporter.js +277 -70
- package/package.json +18 -7
- package/types/src/reporter.d.ts +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A custom Vitest reporter that generates Flakiness Reports from your Vitest test
|
|
|
13
13
|
- [Viewing Reports](#viewing-reports)
|
|
14
14
|
- [Features](#features)
|
|
15
15
|
- [Test Location Tracking](#test-location-tracking)
|
|
16
|
+
- [Attachments](#attachments)
|
|
16
17
|
- [Handling Test Duplicates](#handling-test-duplicates)
|
|
17
18
|
- [Environment Detection](#environment-detection)
|
|
18
19
|
- [CI Integration](#ci-integration)
|
|
@@ -29,6 +30,7 @@ A custom Vitest reporter that generates Flakiness Reports from your Vitest test
|
|
|
29
30
|
|
|
30
31
|
## Requirements
|
|
31
32
|
|
|
33
|
+
- Node.js 20.17.0 or higher (22.9.0 or higher on Node 22)
|
|
32
34
|
- Vitest 4.0 or higher
|
|
33
35
|
- Node.js project with a git repository (for commit information)
|
|
34
36
|
- Valid Flakiness.io access token (for uploads)
|
|
@@ -77,14 +79,27 @@ npx flakiness show ./flakiness-report
|
|
|
77
79
|
|
|
78
80
|
## Uploading Reports
|
|
79
81
|
|
|
80
|
-
Reports are automatically uploaded to Flakiness.io after test completion. Authentication
|
|
82
|
+
Reports are automatically uploaded to Flakiness.io after test completion. Authentication is resolved in the following order, and the first available method wins:
|
|
81
83
|
|
|
82
84
|
- **Access token**: Provide a token via the `token` option or the `FLAKINESS_ACCESS_TOKEN` environment variable.
|
|
83
|
-
- **GitHub OIDC**: When running in GitHub Actions, the reporter can authenticate using GitHub's OIDC token
|
|
85
|
+
- **GitHub OIDC**: When running in GitHub Actions with no access token, the reporter can authenticate using GitHub's OIDC token. This requires three conditions:
|
|
84
86
|
1. The `flakinessProject` option must be set to your Flakiness.io project identifier (`org/project`).
|
|
85
87
|
2. The Flakiness.io project must be bound to the GitHub repository that runs the GitHub Actions workflow.
|
|
88
|
+
3. The workflow must grant the `id-token: write` permission.
|
|
89
|
+
- **GitLab OIDC**: When running in GitLab CI/CD with no access token, the reporter can authenticate using a GitLab ID token. GitLab mints ID tokens when the job starts, so the job must declare one named `FLAKINESS_ID_TOKEN` whose audience is your project identifier:
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
```yaml
|
|
92
|
+
test:
|
|
93
|
+
id_tokens:
|
|
94
|
+
FLAKINESS_ID_TOKEN:
|
|
95
|
+
aud: my-org/my-project # must match the `flakinessProject` option
|
|
96
|
+
script:
|
|
97
|
+
- npx vitest run
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `flakinessProject` option must be set, and the Flakiness.io project must be bound to the GitLab project running the pipeline.
|
|
101
|
+
|
|
102
|
+
If no method is available the upload is skipped, and if an upload fails the report is still available locally in the output folder. Either way the test run is unaffected.
|
|
88
103
|
|
|
89
104
|
## Viewing Reports
|
|
90
105
|
|
|
@@ -102,6 +117,17 @@ See [features.md](./features.md) for this reporter's status against the [Flakine
|
|
|
102
117
|
|
|
103
118
|
When `includeTaskLocation: true` is set in your Vitest config, the reporter records the exact file, line, and column for each test. This enables precise navigation from the Flakiness.io dashboard back to your source code.
|
|
104
119
|
|
|
120
|
+
### Attachments
|
|
121
|
+
|
|
122
|
+
[Browser Mode](https://vitest.dev/guide/browser/) records screenshots as Vitest artifacts. The reporter writes them into the local Flakiness report and uploads them along with it. Identical content is stored only once, no matter how many tests attach it.
|
|
123
|
+
|
|
124
|
+
Two kinds of screenshot are captured:
|
|
125
|
+
|
|
126
|
+
- **Failure screenshots.** Vitest screenshots every failing test when [`browser.screenshotFailures`](https://vitest.dev/config/browser/screenshotfailures) is enabled — which is the default outside of the Vitest UI. It is reported as `failure-screenshot.png`, and needs no changes to your tests.
|
|
127
|
+
- **Visual regressions.** Each failed `toMatchScreenshot()` comparison contributes `screenshot-1-expected.png`, `screenshot-1-actual.png` and `screenshot-1-diff.png`, numbered per comparison within the test. Vitest does not take a failure screenshot for these, since the comparison images already show the page.
|
|
128
|
+
|
|
129
|
+
Attachments of custom artifacts recorded through Vitest's `recordArtifact()` are named after the artifact type (`my-plugin:artifact-1`). Attachments of test annotations (`context.annotate()`) are not collected.
|
|
130
|
+
|
|
105
131
|
### Handling Test Duplicates
|
|
106
132
|
|
|
107
133
|
Vitest allows creating tests with identical names:
|
|
@@ -175,7 +201,7 @@ reporters: [
|
|
|
175
201
|
|
|
176
202
|
### `flakinessProject?: string`
|
|
177
203
|
|
|
178
|
-
The Flakiness.io project identifier in `org/project` format.
|
|
204
|
+
The Flakiness.io project identifier in `org/project` format. Required for CI OIDC authentication. When set, and when the Flakiness.io project is bound to the repository running the pipeline, the reporter authenticates uploads via a GitHub Actions or GitLab CI/CD OIDC token with no access token required. See [Uploading Reports](#uploading-reports) for the per-provider requirements.
|
|
179
205
|
|
|
180
206
|
```typescript
|
|
181
207
|
reporters: [
|
|
@@ -199,7 +225,7 @@ reporters: [
|
|
|
199
225
|
|
|
200
226
|
Access token for authenticating with Flakiness.io when uploading reports. Defaults to the `FLAKINESS_ACCESS_TOKEN` environment variable.
|
|
201
227
|
|
|
202
|
-
If no token is provided, reporter
|
|
228
|
+
If no token is provided, the reporter falls back to CI OIDC on GitHub Actions and GitLab CI/CD. If that is unavailable too, the report is still generated locally but won't be uploaded automatically.
|
|
203
229
|
|
|
204
230
|
```typescript
|
|
205
231
|
reporters: [
|
|
@@ -253,6 +279,7 @@ The reporter respects the following environment variables:
|
|
|
253
279
|
- **`FLAKINESS_ENDPOINT`**: Custom Flakiness.io endpoint URL (equivalent to `endpoint` option)
|
|
254
280
|
- **`FLAKINESS_OUTPUT_DIR`**: Output directory for reports (equivalent to `outputFolder` option)
|
|
255
281
|
- **`FLAKINESS_DISABLE_UPLOAD`**: When set, disables report uploads (equivalent to `disableUpload` option)
|
|
282
|
+
- **`FLAKINESS_ID_TOKEN`**: GitLab CI/CD ID token used for OIDC authentication. Set by GitLab for jobs that declare it under `id_tokens:` (see [Uploading Reports](#uploading-reports))
|
|
256
283
|
|
|
257
284
|
## Example Configuration
|
|
258
285
|
|
package/lib/reporter.js
CHANGED
|
@@ -12,7 +12,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
14
|
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
throw mod = 0, e;
|
|
19
|
+
}
|
|
16
20
|
};
|
|
17
21
|
var __export = (target, all) => {
|
|
18
22
|
for (var name in all)
|
|
@@ -807,9 +811,9 @@ var require_index_min = __commonJS({
|
|
|
807
811
|
}
|
|
808
812
|
});
|
|
809
813
|
|
|
810
|
-
// node_modules/.pnpm/which@
|
|
814
|
+
// node_modules/.pnpm/which@7.0.0/node_modules/which/lib/index.js
|
|
811
815
|
var require_lib = __commonJS({
|
|
812
|
-
"node_modules/.pnpm/which@
|
|
816
|
+
"node_modules/.pnpm/which@7.0.0/node_modules/which/lib/index.js"(exports, module) {
|
|
813
817
|
var { isexe, sync: isexeSync } = require_index_min();
|
|
814
818
|
var { join, delimiter, sep, posix } = __require("path");
|
|
815
819
|
var isWindows = process.platform === "win32";
|
|
@@ -895,14 +899,14 @@ var require_lib = __commonJS({
|
|
|
895
899
|
}
|
|
896
900
|
});
|
|
897
901
|
|
|
898
|
-
// node_modules/.pnpm/@flakiness+flakiness-report@0.
|
|
902
|
+
// node_modules/.pnpm/@flakiness+flakiness-report@0.36.0_zod@4.4.3/node_modules/@flakiness/flakiness-report/lib/flakinessReport.js
|
|
899
903
|
var FlakinessReport;
|
|
900
|
-
((
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
904
|
+
((FlakinessReport22) => {
|
|
905
|
+
FlakinessReport22.CATEGORY_PLAYWRIGHT = "playwright";
|
|
906
|
+
FlakinessReport22.CATEGORY_PYTEST = "pytest";
|
|
907
|
+
FlakinessReport22.CATEGORY_JUNIT = "junit";
|
|
908
|
+
FlakinessReport22.STREAM_STDOUT = 1;
|
|
909
|
+
FlakinessReport22.STREAM_STDERR = 2;
|
|
906
910
|
})(FlakinessReport || (FlakinessReport = {}));
|
|
907
911
|
|
|
908
912
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -15425,7 +15429,7 @@ var classic_default = external_exports;
|
|
|
15425
15429
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/index.js
|
|
15426
15430
|
var v4_default = classic_default;
|
|
15427
15431
|
|
|
15428
|
-
// node_modules/.pnpm/@flakiness+flakiness-report@0.
|
|
15432
|
+
// node_modules/.pnpm/@flakiness+flakiness-report@0.36.0_zod@4.4.3/node_modules/@flakiness/flakiness-report/lib/schema.js
|
|
15429
15433
|
var Schema;
|
|
15430
15434
|
((Schema2) => {
|
|
15431
15435
|
Schema2.CommitId = v4_default.string().min(40).max(40);
|
|
@@ -15498,7 +15502,7 @@ var Schema;
|
|
|
15498
15502
|
environmentIdx: v4_default.number().min(0).optional(),
|
|
15499
15503
|
expectedStatus: Schema2.TestStatus.optional(),
|
|
15500
15504
|
status: Schema2.TestStatus.optional(),
|
|
15501
|
-
startTimestamp: Schema2.UnixTimestampMS,
|
|
15505
|
+
startTimestamp: Schema2.UnixTimestampMS.optional(),
|
|
15502
15506
|
duration: Schema2.DurationMS.optional(),
|
|
15503
15507
|
timeout: Schema2.DurationMS.optional(),
|
|
15504
15508
|
annotations: v4_default.array(Schema2.Annotation).optional(),
|
|
@@ -15586,7 +15590,7 @@ var Schema;
|
|
|
15586
15590
|
});
|
|
15587
15591
|
})(Schema || (Schema = {}));
|
|
15588
15592
|
|
|
15589
|
-
// node_modules/.pnpm/@flakiness+sdk@3.
|
|
15593
|
+
// node_modules/.pnpm/@flakiness+sdk@3.6.0_@flakiness+flakiness-report@0.36.0_zod@4.4.3_/node_modules/@flakiness/sdk/lib/index.js
|
|
15590
15594
|
var import_debug = __toESM(require_src(), 1);
|
|
15591
15595
|
import os from "os";
|
|
15592
15596
|
import assert2 from "assert";
|
|
@@ -15629,7 +15633,7 @@ function stableHash(t) {
|
|
|
15629
15633
|
}
|
|
15630
15634
|
var dist_default = stableHash;
|
|
15631
15635
|
|
|
15632
|
-
// node_modules/.pnpm/@flakiness+sdk@3.
|
|
15636
|
+
// node_modules/.pnpm/@flakiness+sdk@3.6.0_@flakiness+flakiness-report@0.36.0_zod@4.4.3_/node_modules/@flakiness/sdk/lib/index.js
|
|
15633
15637
|
import assert22 from "assert";
|
|
15634
15638
|
import fs4 from "fs";
|
|
15635
15639
|
import { URL as URL2 } from "url";
|
|
@@ -15637,8 +15641,10 @@ var import_debug2 = __toESM(require_src(), 1);
|
|
|
15637
15641
|
var import_which = __toESM(require_lib(), 1);
|
|
15638
15642
|
import fs7 from "node:fs";
|
|
15639
15643
|
import path3 from "node:path";
|
|
15644
|
+
import path4 from "node:path";
|
|
15645
|
+
import { styleText as styleText2 } from "node:util";
|
|
15640
15646
|
import fs8 from "fs";
|
|
15641
|
-
import
|
|
15647
|
+
import path5 from "path";
|
|
15642
15648
|
var __defProp2 = Object.defineProperty;
|
|
15643
15649
|
var __export2 = (target, all) => {
|
|
15644
15650
|
for (var name in all)
|
|
@@ -15768,31 +15774,50 @@ async function compressTextAsync(text) {
|
|
|
15768
15774
|
}
|
|
15769
15775
|
});
|
|
15770
15776
|
}
|
|
15771
|
-
async function retryWithBackoff(job, backoff
|
|
15772
|
-
for (
|
|
15777
|
+
async function retryWithBackoff(job, backoff) {
|
|
15778
|
+
for (let i = 0; i < 100; ++i) {
|
|
15773
15779
|
try {
|
|
15774
15780
|
return await job();
|
|
15775
|
-
} catch (
|
|
15781
|
+
} catch (error51) {
|
|
15782
|
+
const timeout = backoff(error51, i);
|
|
15783
|
+
if (timeout === void 0)
|
|
15784
|
+
throw error51;
|
|
15776
15785
|
await new Promise((x) => setTimeout(x, timeout));
|
|
15777
15786
|
}
|
|
15778
15787
|
}
|
|
15779
15788
|
return await job();
|
|
15780
15789
|
}
|
|
15781
15790
|
var HTTP_BACKOFF = [100, 500, 1e3, 1e3, 1e3, 1e3];
|
|
15791
|
+
var HTTPResponseError = class extends Error {
|
|
15792
|
+
constructor(message, status) {
|
|
15793
|
+
super(message);
|
|
15794
|
+
this.status = status;
|
|
15795
|
+
}
|
|
15796
|
+
status;
|
|
15797
|
+
};
|
|
15798
|
+
function isRetryableHTTPError(error51, extraRetryableStatuses) {
|
|
15799
|
+
if (error51 instanceof HTTPResponseError) {
|
|
15800
|
+
return error51.status === 408 || error51.status === 425 || error51.status === 429 || error51.status >= 500 && error51.status <= 599 || extraRetryableStatuses.includes(error51.status);
|
|
15801
|
+
}
|
|
15802
|
+
return !(error51 instanceof Error && error51.name === "AbortError");
|
|
15803
|
+
}
|
|
15782
15804
|
async function fetchOk(input, init) {
|
|
15783
15805
|
const response = await fetch(input, init);
|
|
15784
15806
|
if (!response.ok) {
|
|
15785
|
-
const url2 = response.url || (input instanceof URL ? input.href : typeof input === "string" ? input : input.url);
|
|
15786
15807
|
const body = await response.text().catch(() => "");
|
|
15787
|
-
throw new
|
|
15808
|
+
throw new HTTPResponseError(response.status + " " + response.url + " " + body, response.status);
|
|
15788
15809
|
}
|
|
15789
15810
|
return response;
|
|
15790
15811
|
}
|
|
15791
|
-
async function getJSON(input, init, backoff = HTTP_BACKOFF) {
|
|
15812
|
+
async function getJSON(input, init, backoff = HTTP_BACKOFF, extraRetryableStatuses = []) {
|
|
15813
|
+
backoff = backoff.slice();
|
|
15792
15814
|
return await retryWithBackoff(async () => {
|
|
15793
15815
|
const response = await fetchOk(input, init);
|
|
15794
|
-
|
|
15795
|
-
|
|
15816
|
+
const text = await response.text();
|
|
15817
|
+
return text.trim() ? JSON.parse(text) : void 0;
|
|
15818
|
+
}, (error51, retryIdx) => {
|
|
15819
|
+
return retryIdx < backoff.length && isRetryableHTTPError(error51, extraRetryableStatuses) ? backoff[retryIdx] : void 0;
|
|
15820
|
+
});
|
|
15796
15821
|
}
|
|
15797
15822
|
async function putBuffer(input, body, headers, backoff = HTTP_BACKOFF) {
|
|
15798
15823
|
await retryWithBackoff(async () => {
|
|
@@ -15802,7 +15827,9 @@ async function putBuffer(input, body, headers, backoff = HTTP_BACKOFF) {
|
|
|
15802
15827
|
body: new Uint8Array(body)
|
|
15803
15828
|
});
|
|
15804
15829
|
await response.arrayBuffer();
|
|
15805
|
-
},
|
|
15830
|
+
}, (error51, retryIdx) => {
|
|
15831
|
+
return retryIdx < backoff.length && isRetryableHTTPError(error51, []) ? backoff[retryIdx] : void 0;
|
|
15832
|
+
});
|
|
15806
15833
|
}
|
|
15807
15834
|
function shell(command, args, options) {
|
|
15808
15835
|
try {
|
|
@@ -15836,6 +15863,10 @@ function sha1File(filePath) {
|
|
|
15836
15863
|
});
|
|
15837
15864
|
});
|
|
15838
15865
|
}
|
|
15866
|
+
function isCI() {
|
|
15867
|
+
const ci = (process.env.CI ?? "").toLowerCase();
|
|
15868
|
+
return ci !== "" && ci !== "false" && ci !== "0";
|
|
15869
|
+
}
|
|
15839
15870
|
var log = (0, import_debug.default)("fk:git");
|
|
15840
15871
|
var execAsync = promisify(exec);
|
|
15841
15872
|
var GIT_SAFE_ENV = {
|
|
@@ -15869,6 +15900,7 @@ var GitWorktree = class _GitWorktree {
|
|
|
15869
15900
|
this._gitRoot = _gitRoot;
|
|
15870
15901
|
this._posixGitRoot = toPosixAbsolutePath(this._gitRoot);
|
|
15871
15902
|
}
|
|
15903
|
+
_gitRoot;
|
|
15872
15904
|
/**
|
|
15873
15905
|
* Initializes a GitWorktree for any path inside a git repository and resolves the
|
|
15874
15906
|
* HEAD commit id in a single call.
|
|
@@ -16089,6 +16121,8 @@ var GithubOIDC = class _GithubOIDC {
|
|
|
16089
16121
|
this._requestUrl = _requestUrl;
|
|
16090
16122
|
this._requestToken = _requestToken;
|
|
16091
16123
|
}
|
|
16124
|
+
_requestUrl;
|
|
16125
|
+
_requestToken;
|
|
16092
16126
|
/**
|
|
16093
16127
|
* Creates a GithubOIDC instance from GitHub Actions environment variables.
|
|
16094
16128
|
*
|
|
@@ -16103,6 +16137,13 @@ var GithubOIDC = class _GithubOIDC {
|
|
|
16103
16137
|
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
16104
16138
|
return requestUrl && requestToken ? new _GithubOIDC(requestUrl, requestToken) : void 0;
|
|
16105
16139
|
}
|
|
16140
|
+
/**
|
|
16141
|
+
* Human-readable name of the CI provider, suitable for log messages.
|
|
16142
|
+
*
|
|
16143
|
+
* To branch on the provider, use `oidc instanceof GithubOIDC` instead — this string is meant
|
|
16144
|
+
* for humans and may be reworded.
|
|
16145
|
+
*/
|
|
16146
|
+
name = "GitHub Actions";
|
|
16106
16147
|
/**
|
|
16107
16148
|
* Mints a Flakiness access token for the specified project via GitHub OIDC.
|
|
16108
16149
|
*
|
|
@@ -16131,11 +16172,98 @@ var GithubOIDC = class _GithubOIDC {
|
|
|
16131
16172
|
} catch (error51) {
|
|
16132
16173
|
throw new Error(`Failed to request GitHub OIDC token: ${error51.message || String(error51)}`);
|
|
16133
16174
|
}
|
|
16134
|
-
if (!json2
|
|
16175
|
+
if (!json2?.value)
|
|
16135
16176
|
throw new Error("GitHub OIDC token response did not contain a token value.");
|
|
16136
16177
|
return json2.value;
|
|
16137
16178
|
}
|
|
16138
16179
|
};
|
|
16180
|
+
var GitlabOIDC = class _GitlabOIDC {
|
|
16181
|
+
constructor(_idToken) {
|
|
16182
|
+
this._idToken = _idToken;
|
|
16183
|
+
}
|
|
16184
|
+
_idToken;
|
|
16185
|
+
/**
|
|
16186
|
+
* Creates a GitlabOIDC instance from GitLab CI/CD environment variables.
|
|
16187
|
+
*
|
|
16188
|
+
* Reads the `FLAKINESS_ID_TOKEN` environment variable, which GitLab CI/CD sets for jobs
|
|
16189
|
+
* that declare an `id_tokens: FLAKINESS_ID_TOKEN:` entry in `.gitlab-ci.yml`.
|
|
16190
|
+
*
|
|
16191
|
+
* @returns {GitlabOIDC | undefined} A GitlabOIDC instance if the environment variable is
|
|
16192
|
+
* present, or `undefined` if not running in GitLab CI/CD with an ID token configured.
|
|
16193
|
+
*/
|
|
16194
|
+
static initializeFromEnv() {
|
|
16195
|
+
const idToken = process.env.FLAKINESS_ID_TOKEN;
|
|
16196
|
+
return idToken ? new _GitlabOIDC(idToken) : void 0;
|
|
16197
|
+
}
|
|
16198
|
+
/**
|
|
16199
|
+
* Human-readable name of the CI provider, suitable for log messages.
|
|
16200
|
+
*
|
|
16201
|
+
* To branch on the provider, use `oidc instanceof GitlabOIDC` instead — this string is meant
|
|
16202
|
+
* for humans and may be reworded.
|
|
16203
|
+
*/
|
|
16204
|
+
name = "GitLab CI/CD";
|
|
16205
|
+
/**
|
|
16206
|
+
* Returns the Flakiness access token for the specified project — the GitLab ID token itself.
|
|
16207
|
+
*
|
|
16208
|
+
* This method succeeds as long as the ID token names `flakinessProject` in its `aud` claim.
|
|
16209
|
+
* However, the returned token can only be used to upload reports if the Flakiness.io project
|
|
16210
|
+
* is bound to the GitLab project running the pipeline. If the project is not bound,
|
|
16211
|
+
* Flakiness.io will reject the token on upload.
|
|
16212
|
+
*
|
|
16213
|
+
* @param {string} flakinessProject - The flakiness project identifier in `"org/project"` format.
|
|
16214
|
+
*
|
|
16215
|
+
* @returns {Promise<string>} A Flakiness access token.
|
|
16216
|
+
*
|
|
16217
|
+
* @throws {Error} If the ID token is not a JWT, carries no `aud` claim, or its `aud` claim
|
|
16218
|
+
* does not include `flakinessProject`. GitLab mints the token when the job starts, so all
|
|
16219
|
+
* three can only be fixed in `.gitlab-ci.yml`.
|
|
16220
|
+
*/
|
|
16221
|
+
async createFlakinessAccessToken(flakinessProject) {
|
|
16222
|
+
const payload = jwtPayload(this._idToken);
|
|
16223
|
+
if (!payload) {
|
|
16224
|
+
throw new Error([
|
|
16225
|
+
`GitLab ID token is not a JWT.`,
|
|
16226
|
+
`Check that FLAKINESS_ID_TOKEN comes from an id_tokens entry with \`aud: ${flakinessProject}\` in .gitlab-ci.yml.`
|
|
16227
|
+
].join(" "));
|
|
16228
|
+
}
|
|
16229
|
+
const audience = audienceClaim(payload);
|
|
16230
|
+
if (!audience.length) {
|
|
16231
|
+
throw new Error([
|
|
16232
|
+
`GitLab ID token has no audience, so it cannot upload to "${flakinessProject}".`,
|
|
16233
|
+
`Declare the FLAKINESS_ID_TOKEN id_token with \`aud: ${flakinessProject}\` in .gitlab-ci.yml.`
|
|
16234
|
+
].join(" "));
|
|
16235
|
+
}
|
|
16236
|
+
if (!audience.includes(flakinessProject)) {
|
|
16237
|
+
throw new Error([
|
|
16238
|
+
`GitLab ID token audience is ${audience.map((aud) => JSON.stringify(aud)).join(", ")}, but the report uploads to "${flakinessProject}".`,
|
|
16239
|
+
`Set the audience of the FLAKINESS_ID_TOKEN id_token in .gitlab-ci.yml to "${flakinessProject}".`
|
|
16240
|
+
].join(" "));
|
|
16241
|
+
}
|
|
16242
|
+
return this._idToken;
|
|
16243
|
+
}
|
|
16244
|
+
};
|
|
16245
|
+
function jwtPayload(jwt2) {
|
|
16246
|
+
const payload = jwt2.split(".")[1];
|
|
16247
|
+
if (!payload)
|
|
16248
|
+
return void 0;
|
|
16249
|
+
try {
|
|
16250
|
+
const json2 = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
16251
|
+
return json2 && typeof json2 === "object" && !Array.isArray(json2) ? json2 : void 0;
|
|
16252
|
+
} catch {
|
|
16253
|
+
return void 0;
|
|
16254
|
+
}
|
|
16255
|
+
}
|
|
16256
|
+
function audienceClaim(payload) {
|
|
16257
|
+
const aud = payload["aud"];
|
|
16258
|
+
if (typeof aud === "string")
|
|
16259
|
+
return [aud];
|
|
16260
|
+
if (Array.isArray(aud))
|
|
16261
|
+
return aud.filter((entry) => typeof entry === "string");
|
|
16262
|
+
return [];
|
|
16263
|
+
}
|
|
16264
|
+
function initializeOIDCFromEnv() {
|
|
16265
|
+
return GithubOIDC.initializeFromEnv() ?? GitlabOIDC.initializeFromEnv();
|
|
16266
|
+
}
|
|
16139
16267
|
var reportUtils_exports = {};
|
|
16140
16268
|
__export2(reportUtils_exports, {
|
|
16141
16269
|
collectSources: () => collectSources,
|
|
@@ -16488,21 +16616,21 @@ async function createDataAttachment(contentType, data) {
|
|
|
16488
16616
|
async function uploadReport(report, attachments, options) {
|
|
16489
16617
|
let flakinessAccessToken = options?.flakinessAccessToken ?? process.env["FLAKINESS_ACCESS_TOKEN"];
|
|
16490
16618
|
const logger = options?.logger ?? console;
|
|
16491
|
-
const
|
|
16492
|
-
if (!flakinessAccessToken &&
|
|
16619
|
+
const oidc = initializeOIDCFromEnv();
|
|
16620
|
+
if (!flakinessAccessToken && oidc) {
|
|
16493
16621
|
if (!report.flakinessProject) {
|
|
16494
|
-
const reason =
|
|
16495
|
-
if (
|
|
16622
|
+
const reason = `\`flakinessProject\` is not configured to upload using ${oidc.name} OIDC.`;
|
|
16623
|
+
if (isCI())
|
|
16496
16624
|
logger.warn(`[flakiness.io] \u26A0 Skipping upload: ${reason}`);
|
|
16497
16625
|
return { status: "skipped", reason };
|
|
16498
16626
|
}
|
|
16499
16627
|
try {
|
|
16500
|
-
flakinessAccessToken = await
|
|
16628
|
+
flakinessAccessToken = await oidc.createFlakinessAccessToken(report.flakinessProject);
|
|
16501
16629
|
if (!flakinessAccessToken)
|
|
16502
16630
|
throw new Error("token is empty");
|
|
16503
16631
|
} catch (e) {
|
|
16504
16632
|
const errorMessage = e.message || String(e);
|
|
16505
|
-
logger.error(`[flakiness.io] \u2715 Unexpected error while fetching
|
|
16633
|
+
logger.error(`[flakiness.io] \u2715 Unexpected error while fetching ${oidc.name} OIDC token: ${errorMessage}`);
|
|
16506
16634
|
if (options?.throwOnFailure)
|
|
16507
16635
|
throw e;
|
|
16508
16636
|
return { status: "failed", error: errorMessage };
|
|
@@ -16510,7 +16638,7 @@ async function uploadReport(report, attachments, options) {
|
|
|
16510
16638
|
}
|
|
16511
16639
|
if (!flakinessAccessToken) {
|
|
16512
16640
|
const reason = "No FLAKINESS_ACCESS_TOKEN found";
|
|
16513
|
-
if (
|
|
16641
|
+
if (isCI())
|
|
16514
16642
|
logger.warn(`[flakiness.io] \u26A0 Skipping upload: ${reason}`);
|
|
16515
16643
|
return { status: "skipped", reason };
|
|
16516
16644
|
}
|
|
@@ -16582,12 +16710,16 @@ var ReportUpload = class {
|
|
|
16582
16710
|
if (response?.error || !response.result)
|
|
16583
16711
|
return { success: false, message: response.error };
|
|
16584
16712
|
const webUrl = new URL2(response.result.webUrl, this._options.flakinessEndpoint).toString();
|
|
16585
|
-
const
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16713
|
+
const attachments = /* @__PURE__ */ new Map();
|
|
16714
|
+
if (this._attachments.length) {
|
|
16715
|
+
const attachmentsPresignedUrls = await this._api("/api/upload/attachments", response.result.uploadToken, {
|
|
16716
|
+
attachmentIds: this._attachments.map((a) => a.id)
|
|
16717
|
+
});
|
|
16718
|
+
if (attachmentsPresignedUrls?.error || !attachmentsPresignedUrls.result)
|
|
16719
|
+
return { success: false, message: attachmentsPresignedUrls.error };
|
|
16720
|
+
for (const attachment of attachmentsPresignedUrls.result)
|
|
16721
|
+
attachments.set(attachment.attachmentId, attachment.presignedUrl);
|
|
16722
|
+
}
|
|
16591
16723
|
await Promise.all([
|
|
16592
16724
|
this._uploadReport(JSON.stringify(this._report), response.result.presignedReportUrl),
|
|
16593
16725
|
...this._attachments.map((attachment) => {
|
|
@@ -16597,7 +16729,9 @@ var ReportUpload = class {
|
|
|
16597
16729
|
return this._uploadAttachment(attachment, uploadURL);
|
|
16598
16730
|
})
|
|
16599
16731
|
]);
|
|
16600
|
-
await this._api("/api/upload/finish", response.result.uploadToken);
|
|
16732
|
+
const finishResponse = await this._api("/api/upload/finish", response.result.uploadToken);
|
|
16733
|
+
if (finishResponse.error)
|
|
16734
|
+
return { success: false, message: finishResponse.error };
|
|
16601
16735
|
return { success: true, reportUrl: webUrl };
|
|
16602
16736
|
}
|
|
16603
16737
|
async _uploadReport(data, uploadUrl) {
|
|
@@ -16745,9 +16879,17 @@ function quoteShellArgument(argument) {
|
|
|
16745
16879
|
return argument;
|
|
16746
16880
|
return JSON.stringify(argument);
|
|
16747
16881
|
}
|
|
16882
|
+
function showReportMessage(reportFolder) {
|
|
16883
|
+
if (isCI())
|
|
16884
|
+
return `Flakiness report written to ${path4.resolve(reportFolder)}`;
|
|
16885
|
+
const command = showReportCommand(reportFolder);
|
|
16886
|
+
return `To open last Flakiness report, run:
|
|
16887
|
+
|
|
16888
|
+
${styleText2("cyan", command)}`;
|
|
16889
|
+
}
|
|
16748
16890
|
async function writeReport(report, attachments, outputFolder) {
|
|
16749
|
-
const reportPath =
|
|
16750
|
-
const attachmentsFolder =
|
|
16891
|
+
const reportPath = path5.join(outputFolder, "report.json");
|
|
16892
|
+
const attachmentsFolder = path5.join(outputFolder, "attachments");
|
|
16751
16893
|
await fs8.promises.rm(outputFolder, { recursive: true, force: true });
|
|
16752
16894
|
await fs8.promises.mkdir(outputFolder, { recursive: true });
|
|
16753
16895
|
await fs8.promises.writeFile(reportPath, JSON.stringify(report), "utf-8");
|
|
@@ -16755,7 +16897,7 @@ async function writeReport(report, attachments, outputFolder) {
|
|
|
16755
16897
|
await fs8.promises.mkdir(attachmentsFolder);
|
|
16756
16898
|
const movedAttachments = [];
|
|
16757
16899
|
for (const attachment of attachments) {
|
|
16758
|
-
const attachmentPath =
|
|
16900
|
+
const attachmentPath = path5.join(attachmentsFolder, attachment.id);
|
|
16759
16901
|
if (attachment.type === "file")
|
|
16760
16902
|
await fs8.promises.cp(attachment.path, attachmentPath);
|
|
16761
16903
|
else if (attachment.type === "buffer")
|
|
@@ -16779,7 +16921,7 @@ import * as nodeUtil from "node:util";
|
|
|
16779
16921
|
// package.json
|
|
16780
16922
|
var package_default = {
|
|
16781
16923
|
name: "@flakiness/vitest",
|
|
16782
|
-
version: "1.
|
|
16924
|
+
version: "1.9.1",
|
|
16783
16925
|
packageManager: "pnpm@11.5.0",
|
|
16784
16926
|
description: "",
|
|
16785
16927
|
main: "lib/reporter.js",
|
|
@@ -16799,22 +16941,32 @@ var package_default = {
|
|
|
16799
16941
|
engines: {
|
|
16800
16942
|
node: "^20.17.0 || >=22.9.0"
|
|
16801
16943
|
},
|
|
16944
|
+
devEngines: {
|
|
16945
|
+
runtime: {
|
|
16946
|
+
name: "node",
|
|
16947
|
+
version: "^20.17.0",
|
|
16948
|
+
onFail: "download"
|
|
16949
|
+
}
|
|
16950
|
+
},
|
|
16802
16951
|
devDependencies: {
|
|
16803
|
-
"@flakiness/flakiness-report": "^0.
|
|
16804
|
-
"@flakiness/sdk": "^3.
|
|
16805
|
-
"@flakiness/vitest": "1.
|
|
16952
|
+
"@flakiness/flakiness-report": "^0.36.0",
|
|
16953
|
+
"@flakiness/sdk": "^3.6.0",
|
|
16954
|
+
"@flakiness/vitest": "1.8.0",
|
|
16806
16955
|
"@types/node": "^25.6.2",
|
|
16807
|
-
"@vitest/
|
|
16808
|
-
|
|
16956
|
+
"@vitest/browser": "^4.1.10",
|
|
16957
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
16958
|
+
"@vitest/utils": "^4.1.10",
|
|
16959
|
+
esbuild: "^0.28.1",
|
|
16809
16960
|
kubik: "^0.24.0",
|
|
16961
|
+
playwright: "^1.61.1",
|
|
16810
16962
|
tsx: "^4.21.0",
|
|
16811
16963
|
typescript: "^5.9.3",
|
|
16812
|
-
vitest: "^4.1.
|
|
16964
|
+
vitest: "^4.1.10"
|
|
16813
16965
|
}
|
|
16814
16966
|
};
|
|
16815
16967
|
|
|
16816
16968
|
// src/reporter.ts
|
|
16817
|
-
var
|
|
16969
|
+
var styleText3 = (format, text) => nodeUtil.styleText?.(format, text) ?? text;
|
|
16818
16970
|
var FKVitestReporter = class {
|
|
16819
16971
|
constructor(_options) {
|
|
16820
16972
|
this._options = _options;
|
|
@@ -16823,8 +16975,8 @@ var FKVitestReporter = class {
|
|
|
16823
16975
|
_impl;
|
|
16824
16976
|
_vitest;
|
|
16825
16977
|
_logger = {
|
|
16826
|
-
warn: (txt) => console.warn(
|
|
16827
|
-
error: (txt) => console.error(
|
|
16978
|
+
warn: (txt) => console.warn(styleText3("yellow", txt)),
|
|
16979
|
+
error: (txt) => console.error(styleText3("red", txt)),
|
|
16828
16980
|
log: (txt) => console.log(txt)
|
|
16829
16981
|
};
|
|
16830
16982
|
setLoggerForTest(logger) {
|
|
@@ -16870,6 +17022,7 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
16870
17022
|
_cpuUtilization = new CPUUtilization({ precision: 10 });
|
|
16871
17023
|
_ramUtilization = new RAMUtilization({ precision: 10 });
|
|
16872
17024
|
_startTimestamp = Date.now();
|
|
17025
|
+
_attachments = /* @__PURE__ */ new Map();
|
|
16873
17026
|
_stdio = /* @__PURE__ */ new Map();
|
|
16874
17027
|
_environments = [];
|
|
16875
17028
|
static create(rootPath, projects, options, logger, vitestVersion, config2) {
|
|
@@ -16907,7 +17060,7 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
16907
17060
|
column: frame.column
|
|
16908
17061
|
} : void 0;
|
|
16909
17062
|
}
|
|
16910
|
-
_collectSuite(fkParent, suite) {
|
|
17063
|
+
async _collectSuite(fkParent, suite) {
|
|
16911
17064
|
const fkSuite = {
|
|
16912
17065
|
type: "suite",
|
|
16913
17066
|
title: suite.name,
|
|
@@ -16920,9 +17073,42 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
16920
17073
|
fkParent.suites ??= [];
|
|
16921
17074
|
fkParent.suites.push(fkSuite);
|
|
16922
17075
|
for (const s of suite.children.suites())
|
|
16923
|
-
this._collectSuite(fkSuite, s);
|
|
17076
|
+
await this._collectSuite(fkSuite, s);
|
|
16924
17077
|
for (const t of suite.children.tests())
|
|
16925
|
-
this._collectTest(fkSuite, t);
|
|
17078
|
+
await this._collectTest(fkSuite, t);
|
|
17079
|
+
}
|
|
17080
|
+
async _collectAttachments(testCase) {
|
|
17081
|
+
const attachments = [];
|
|
17082
|
+
const add = async (attachment, name) => {
|
|
17083
|
+
const fkAttachment = await createFKAttachment(attachment);
|
|
17084
|
+
this._attachments.set(fkAttachment.id, fkAttachment);
|
|
17085
|
+
attachments.push({
|
|
17086
|
+
contentType: fkAttachment.contentType,
|
|
17087
|
+
id: fkAttachment.id,
|
|
17088
|
+
name: name + extension(attachment)
|
|
17089
|
+
});
|
|
17090
|
+
};
|
|
17091
|
+
let visualRegressionIndex = 0;
|
|
17092
|
+
let failureScreenshotIndex = 0;
|
|
17093
|
+
for (const artifact of testCase.artifacts?.() ?? []) {
|
|
17094
|
+
if (artifact.type === "internal:annotation")
|
|
17095
|
+
continue;
|
|
17096
|
+
if (artifact.type === "internal:toMatchScreenshot") {
|
|
17097
|
+
const prefix = `screenshot-${++visualRegressionIndex}`;
|
|
17098
|
+
for (const attachment of artifact.attachments)
|
|
17099
|
+
await add(attachment, `${prefix}-${VISUAL_REGRESSION_NAMES[attachment.name]}`);
|
|
17100
|
+
} else if (artifact.type === "internal:failureScreenshot") {
|
|
17101
|
+
for (const attachment of artifact.attachments) {
|
|
17102
|
+
++failureScreenshotIndex;
|
|
17103
|
+
await add(attachment, failureScreenshotIndex === 1 ? "failure-screenshot" : `failure-screenshot-${failureScreenshotIndex}`);
|
|
17104
|
+
}
|
|
17105
|
+
} else {
|
|
17106
|
+
const custom2 = artifact;
|
|
17107
|
+
for (const [index, attachment] of (custom2.attachments ?? []).entries())
|
|
17108
|
+
await add(attachment, `${custom2.type}-${index + 1}`);
|
|
17109
|
+
}
|
|
17110
|
+
}
|
|
17111
|
+
return attachments;
|
|
16926
17112
|
}
|
|
16927
17113
|
async _collectTest(fkParent, testCase) {
|
|
16928
17114
|
const environmentIdx = this._environments.findIndex((env) => env.name === this._envName(testCase.project));
|
|
@@ -16961,9 +17147,12 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
16961
17147
|
stdio.push({
|
|
16962
17148
|
text: entry.content,
|
|
16963
17149
|
stream: entry.type === "stdout" ? FlakinessReport.STREAM_STDOUT : FlakinessReport.STREAM_STDERR,
|
|
16964
|
-
|
|
17150
|
+
// Older Vitest versions can report a stale timestamp when output
|
|
17151
|
+
// switches between stdout and stderr. Preserve the emitted order, but
|
|
17152
|
+
// never serialize an invalid negative DurationMS.
|
|
17153
|
+
dts: Math.max(0, entry.time - ts)
|
|
16965
17154
|
});
|
|
16966
|
-
ts = entry.time;
|
|
17155
|
+
ts = Math.max(ts, entry.time);
|
|
16967
17156
|
}
|
|
16968
17157
|
this._stdio.delete(testCase.id);
|
|
16969
17158
|
const testFile = this._worktree.gitPath(testCase.module.moduleId);
|
|
@@ -16983,6 +17172,7 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
16983
17172
|
}));
|
|
16984
17173
|
if (testCase.options.fails)
|
|
16985
17174
|
annotations.push({ type: "fail" });
|
|
17175
|
+
const attachments = await this._collectAttachments(testCase);
|
|
16986
17176
|
const expectedStatus = testCase.options.fails ? "failed" : "passed";
|
|
16987
17177
|
const oppositeStatus = expectedStatus === "failed" ? "passed" : "failed";
|
|
16988
17178
|
for (let i = 0; i < retryCount; ++i) {
|
|
@@ -17001,7 +17191,8 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
17001
17191
|
// decides to push a new annotation, then they can do it per-attempt safely.
|
|
17002
17192
|
stdio: stdio.slice(),
|
|
17003
17193
|
errors: errors.slice(),
|
|
17004
|
-
annotations: annotations.slice()
|
|
17194
|
+
annotations: annotations.slice(),
|
|
17195
|
+
attachments: attachments.slice()
|
|
17005
17196
|
});
|
|
17006
17197
|
}
|
|
17007
17198
|
fkTest.attempts.push({
|
|
@@ -17015,7 +17206,8 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
17015
17206
|
// See https://github.com/vitest-dev/vitest/issues/10303
|
|
17016
17207
|
stdio,
|
|
17017
17208
|
errors,
|
|
17018
|
-
annotations
|
|
17209
|
+
annotations,
|
|
17210
|
+
attachments
|
|
17019
17211
|
});
|
|
17020
17212
|
}
|
|
17021
17213
|
_warnDuplicates(duplicates) {
|
|
@@ -17109,7 +17301,7 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
17109
17301
|
return { duplicates, testIdToTests };
|
|
17110
17302
|
}
|
|
17111
17303
|
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
17112
|
-
const fileSuites = testModules.map((file2) => {
|
|
17304
|
+
const fileSuites = await Promise.all(testModules.map(async (file2) => {
|
|
17113
17305
|
const fkFileSuite = {
|
|
17114
17306
|
type: "file",
|
|
17115
17307
|
title: file2.relativeModuleId,
|
|
@@ -17120,11 +17312,11 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
17120
17312
|
}
|
|
17121
17313
|
};
|
|
17122
17314
|
for (const suite of file2.children.suites())
|
|
17123
|
-
this._collectSuite(fkFileSuite, suite);
|
|
17315
|
+
await this._collectSuite(fkFileSuite, suite);
|
|
17124
17316
|
for (const test of file2.children.tests())
|
|
17125
|
-
this._collectTest(fkFileSuite, test);
|
|
17317
|
+
await this._collectTest(fkFileSuite, test);
|
|
17126
17318
|
return fkFileSuite;
|
|
17127
|
-
});
|
|
17319
|
+
}));
|
|
17128
17320
|
clearTimeout(this._telemetryTimer);
|
|
17129
17321
|
this._cpuUtilization.sample();
|
|
17130
17322
|
this._ramUtilization.sample();
|
|
@@ -17168,26 +17360,41 @@ var ReporterImpl = class _ReporterImpl {
|
|
|
17168
17360
|
process.cwd(),
|
|
17169
17361
|
process.env.FLAKINESS_OUTPUT_DIR ?? "flakiness-report"
|
|
17170
17362
|
);
|
|
17171
|
-
await writeReport(report,
|
|
17363
|
+
const attachments = await writeReport(report, Array.from(this._attachments.values()), outputFolder);
|
|
17172
17364
|
const disableUpload = !!this._options.disableUpload || !!process.env.FLAKINESS_DISABLE_UPLOAD;
|
|
17173
17365
|
if (!disableUpload) {
|
|
17174
|
-
await uploadReport(report,
|
|
17366
|
+
await uploadReport(report, attachments, {
|
|
17175
17367
|
flakinessAccessToken: this._options.token,
|
|
17176
17368
|
flakinessEndpoint: this._options.endpoint,
|
|
17177
17369
|
logger: this._logger
|
|
17178
17370
|
});
|
|
17179
17371
|
}
|
|
17180
|
-
const command = showReportCommand(outputFolder);
|
|
17181
17372
|
this._logger.log(`
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
${styleText2("cyan", command)}
|
|
17185
|
-
`);
|
|
17373
|
+
${showReportMessage(outputFolder)}
|
|
17374
|
+
`);
|
|
17186
17375
|
}
|
|
17187
17376
|
};
|
|
17188
17377
|
function dupeSuffix(dupeIndex) {
|
|
17189
17378
|
return ` \u2013 dupe #${dupeIndex}`;
|
|
17190
17379
|
}
|
|
17380
|
+
async function createFKAttachment(attachment) {
|
|
17381
|
+
const contentType = attachment.contentType ?? "application/octet-stream";
|
|
17382
|
+
if (attachment.path)
|
|
17383
|
+
return await reportUtils_exports.createFileAttachment(contentType, attachment.path);
|
|
17384
|
+
if (typeof attachment.body === "string")
|
|
17385
|
+
return reportUtils_exports.createDataAttachment(contentType, Buffer.from(attachment.body, attachment.bodyEncoding === "utf-8" ? "utf8" : "base64"));
|
|
17386
|
+
if (attachment.body)
|
|
17387
|
+
return reportUtils_exports.createDataAttachment(contentType, Buffer.from(attachment.body));
|
|
17388
|
+
throw new Error(`Failed to parse attachment: it has neither path to body. This is likely a bug; please report to https://github.com/flakiness/vitest/issues/new`);
|
|
17389
|
+
}
|
|
17390
|
+
var VISUAL_REGRESSION_NAMES = {
|
|
17391
|
+
reference: "expected",
|
|
17392
|
+
actual: "actual",
|
|
17393
|
+
diff: "diff"
|
|
17394
|
+
};
|
|
17395
|
+
function extension(attachment) {
|
|
17396
|
+
return attachment.path ? path.extname(attachment.path) : "";
|
|
17397
|
+
}
|
|
17191
17398
|
export {
|
|
17192
17399
|
FKVitestReporter as default
|
|
17193
17400
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flakiness/vitest",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/reporter.js",
|
|
6
6
|
"keywords": [],
|
|
@@ -14,17 +14,28 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": "^20.17.0 || >=22.9.0"
|
|
16
16
|
},
|
|
17
|
+
"devEngines": {
|
|
18
|
+
"runtime": {
|
|
19
|
+
"name": "node",
|
|
20
|
+
"version": "^20.17.0",
|
|
21
|
+
"onFail": "download"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
17
24
|
"devDependencies": {
|
|
18
|
-
"@flakiness/flakiness-report": "^0.
|
|
19
|
-
"@flakiness/sdk": "^3.
|
|
20
|
-
"@flakiness/vitest": "1.
|
|
25
|
+
"@flakiness/flakiness-report": "^0.36.0",
|
|
26
|
+
"@flakiness/sdk": "^3.6.0",
|
|
27
|
+
"@flakiness/vitest": "1.8.0",
|
|
21
28
|
"@types/node": "^25.6.2",
|
|
22
|
-
"@vitest/
|
|
23
|
-
"
|
|
29
|
+
"@vitest/browser": "^4.1.10",
|
|
30
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
31
|
+
"@vitest/utils": "^4.1.10",
|
|
32
|
+
"esbuild": "^0.28.1",
|
|
24
33
|
"kubik": "^0.24.0",
|
|
34
|
+
"playwright": "^1.61.1",
|
|
25
35
|
"tsx": "^4.21.0",
|
|
26
36
|
"typescript": "^5.9.3",
|
|
27
|
-
"vitest": "^4.1.
|
|
37
|
+
"vitest": "^4.1.10",
|
|
38
|
+
"node": "runtime:^20.17.0"
|
|
28
39
|
},
|
|
29
40
|
"scripts": {
|
|
30
41
|
"build": "kubik build.mts",
|
package/types/src/reporter.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { UserConsoleLog } from 'vitest';
|
|
1
2
|
import type { SerializedError, TestModule, TestRunEndReason, Vitest } from 'vitest/node';
|
|
2
3
|
import type { Reporter } from 'vitest/reporters';
|
|
3
|
-
import type { UserConsoleLog } from 'vitest';
|
|
4
4
|
export type FKVitestReporterOptions = {
|
|
5
5
|
disableUpload?: boolean;
|
|
6
6
|
flakinessProject?: string;
|