@flakiness/cucumberjs 1.0.0-alpha.1 → 1.1.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/README.md +201 -0
- package/lib/formatter.js +13 -3
- package/package.json +7 -2
- package/types/src/formatter.d.ts.map +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
[](https://flakiness.io/flakiness/cucumberjs)
|
|
2
|
+
|
|
3
|
+
# Flakiness.io CucumberJS Formatter
|
|
4
|
+
|
|
5
|
+
A custom CucumberJS formatter that generates Flakiness Reports from your Cucumber test runs. The formatter automatically converts CucumberJS test results into the standardized [Flakiness JSON format](https://github.com/flakiness/flakiness-report), preserving complete Gherkin structure, test outcomes, and environment information.
|
|
6
|
+
|
|
7
|
+
## Supported Gherkin Features
|
|
8
|
+
|
|
9
|
+
- Scenarios & Scenario Outlines (with multiple Examples blocks)
|
|
10
|
+
- Rules
|
|
11
|
+
- Tags & tag inheritance (Feature → Rule → Scenario → Examples)
|
|
12
|
+
- Steps (Given/When/Then with keyword prefix)
|
|
13
|
+
- Data Tables
|
|
14
|
+
- Before & After Hooks (named and unnamed)
|
|
15
|
+
- Feature, Rule, and Scenario descriptions
|
|
16
|
+
- Attachments (`this.attach()` and `this.log()`)
|
|
17
|
+
- Retries (`--retry`)
|
|
18
|
+
- Parallel execution (`--parallel`)
|
|
19
|
+
- All statuses: passed, failed, pending, undefined, ambiguous, skipped
|
|
20
|
+
|
|
21
|
+
## Table of Contents
|
|
22
|
+
|
|
23
|
+
- [Requirements](#requirements)
|
|
24
|
+
- [Installation](#installation)
|
|
25
|
+
- [Quick Start](#quick-start)
|
|
26
|
+
- [Uploading Reports](#uploading-reports)
|
|
27
|
+
- [Viewing Reports](#viewing-reports)
|
|
28
|
+
- [Features](#features)
|
|
29
|
+
- [Environment Detection](#environment-detection)
|
|
30
|
+
- [CI Integration](#ci-integration)
|
|
31
|
+
- [Configuration Options](#configuration-options)
|
|
32
|
+
- [`flakinessProject?: string`](#flakinessproject-string)
|
|
33
|
+
- [`endpoint?: string`](#endpoint-string)
|
|
34
|
+
- [`token?: string`](#token-string)
|
|
35
|
+
- [`outputFolder?: string`](#outputfolder-string)
|
|
36
|
+
- [`disableUpload?: boolean`](#disableupload-boolean)
|
|
37
|
+
- [Example Configuration](#example-configuration)
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- `@cucumber/cucumber` 12.0 or higher
|
|
42
|
+
- Node.js project with a git repository (for commit information)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -D @flakiness/cucumberjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
Add the formatter to your `cucumber.mjs`:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
export default {
|
|
56
|
+
paths: ['features/**/*.feature'],
|
|
57
|
+
import: ['features/support/**/*.ts'],
|
|
58
|
+
format: ['@flakiness/cucumberjs', 'progress'],
|
|
59
|
+
formatOptions: {
|
|
60
|
+
flakinessProject: 'my-org/my-project',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Run your tests. The report will be automatically generated in the `./flakiness-report` folder:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx cucumber-js
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
View the interactive report:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx flakiness show ./flakiness-report
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Uploading Reports
|
|
78
|
+
|
|
79
|
+
Reports are automatically uploaded to Flakiness.io after test completion. Authentication can be done in two ways:
|
|
80
|
+
|
|
81
|
+
- **Access token**: Provide a token via the `token` format option or the `FLAKINESS_ACCESS_TOKEN` environment variable.
|
|
82
|
+
- **GitHub OIDC**: When running in GitHub Actions, the formatter can authenticate using GitHub's OIDC token — no access token needed. See [GitHub Actions integration](https://docs.flakiness.io/ci/github-actions/) for setup instructions.
|
|
83
|
+
|
|
84
|
+
If upload fails, the report is still available locally in the output folder.
|
|
85
|
+
|
|
86
|
+
## Viewing Reports
|
|
87
|
+
|
|
88
|
+
After test execution, you can view the report using:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx flakiness show ./flakiness-report
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
### Environment Detection
|
|
97
|
+
|
|
98
|
+
Environment variables prefixed with `FK_ENV_` are automatically included in the environment metadata. The prefix is stripped and the key is converted to lowercase.
|
|
99
|
+
|
|
100
|
+
**Example:**
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export FK_ENV_DEPLOYMENT=staging
|
|
104
|
+
export FK_ENV_REGION=us-east-1
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This will result in the environment containing:
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"metadata": {
|
|
111
|
+
"deployment": "staging",
|
|
112
|
+
"region": "us-east-1"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Flakiness.io will create a dedicated history for tests executed in each unique environment. This means tests run with `FK_ENV_DEPLOYMENT=staging` will have a separate timeline from tests run with `FK_ENV_DEPLOYMENT=production`, allowing you to track flakiness patterns specific to each deployment environment.
|
|
118
|
+
|
|
119
|
+
### CI Integration
|
|
120
|
+
|
|
121
|
+
The formatter automatically detects CI environments and includes:
|
|
122
|
+
- CI run URLs (GitHub Actions, Azure DevOps, Jenkins, GitLab CI)
|
|
123
|
+
- Git commit information
|
|
124
|
+
- System environment data
|
|
125
|
+
|
|
126
|
+
## Configuration Options
|
|
127
|
+
|
|
128
|
+
All options are passed via CucumberJS's `formatOptions` in your configuration file.
|
|
129
|
+
|
|
130
|
+
### `flakinessProject?: string`
|
|
131
|
+
|
|
132
|
+
The Flakiness.io project identifier in `org/project` format. Used for GitHub OIDC authentication — when set, and the Flakiness.io project is bound to the GitHub repository running the workflow, the formatter authenticates uploads via GitHub Actions OIDC token with no access token required.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
formatOptions: {
|
|
136
|
+
flakinessProject: 'my-org/my-project',
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `endpoint?: string`
|
|
141
|
+
|
|
142
|
+
Custom Flakiness.io endpoint URL for uploading reports. Defaults to the `FLAKINESS_ENDPOINT` environment variable, or `https://flakiness.io` if not set.
|
|
143
|
+
|
|
144
|
+
Use this option to point to a custom or self-hosted Flakiness.io instance.
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
formatOptions: {
|
|
148
|
+
endpoint: 'https://custom.flakiness.io',
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `token?: string`
|
|
153
|
+
|
|
154
|
+
Access token for authenticating with Flakiness.io when uploading reports. Defaults to the `FLAKINESS_ACCESS_TOKEN` environment variable.
|
|
155
|
+
|
|
156
|
+
If no token is provided, the formatter will attempt to authenticate using GitHub OIDC.
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
formatOptions: {
|
|
160
|
+
token: 'your-access-token',
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### `outputFolder?: string`
|
|
165
|
+
|
|
166
|
+
Directory path where the Flakiness report will be written. Defaults to `flakiness-report` in the current working directory, or the `FLAKINESS_OUTPUT_DIR` environment variable if set.
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
formatOptions: {
|
|
170
|
+
outputFolder: './test-results/flakiness',
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `disableUpload?: boolean`
|
|
175
|
+
|
|
176
|
+
When set to `true`, prevents uploading the report to Flakiness.io. The report is still generated locally. Can also be controlled via the `FLAKINESS_DISABLE_UPLOAD` environment variable.
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
formatOptions: {
|
|
180
|
+
disableUpload: true,
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Example Configuration
|
|
185
|
+
|
|
186
|
+
Here's a complete example with all options:
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
export default {
|
|
190
|
+
paths: ['features/**/*.feature'],
|
|
191
|
+
import: ['features/support/**/*.ts'],
|
|
192
|
+
format: ['@flakiness/cucumberjs', 'progress'],
|
|
193
|
+
formatOptions: {
|
|
194
|
+
flakinessProject: 'my-org/my-project',
|
|
195
|
+
endpoint: process.env.FLAKINESS_ENDPOINT,
|
|
196
|
+
token: process.env.FLAKINESS_ACCESS_TOKEN,
|
|
197
|
+
outputFolder: './flakiness-report',
|
|
198
|
+
disableUpload: false,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
```
|
package/lib/formatter.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
GitWorktree,
|
|
7
7
|
RAMUtilization,
|
|
8
8
|
ReportUtils,
|
|
9
|
+
showReportCommand,
|
|
9
10
|
uploadReport,
|
|
10
11
|
writeReport
|
|
11
12
|
} from "@flakiness/sdk";
|
|
@@ -104,18 +105,18 @@ class FlakinessCucumberFormatter extends Formatter {
|
|
|
104
105
|
flakinessEndpoint: this._config.endpoint
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
|
-
const
|
|
108
|
-
const folder = defaultOutputFolder === this._outputFolder ? "" : path.relative(this.cwd, this._outputFolder);
|
|
108
|
+
const command = showReportCommand(this._outputFolder);
|
|
109
109
|
this.log(`
|
|
110
110
|
To open last Flakiness report, run:
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
${command}
|
|
113
113
|
`);
|
|
114
114
|
}
|
|
115
115
|
async _collectSuites(worktree) {
|
|
116
116
|
const suitesByKey = /* @__PURE__ */ new Map();
|
|
117
117
|
const testsById = /* @__PURE__ */ new Map();
|
|
118
118
|
const attachments = /* @__PURE__ */ new Map();
|
|
119
|
+
const parallelIndexByWorkerId = /* @__PURE__ */ new Map();
|
|
119
120
|
for (const [testCaseStartedId, testCaseStarted] of this._testCaseStartedById) {
|
|
120
121
|
const attemptData = this.eventDataCollector.getTestCaseAttempt(testCaseStartedId);
|
|
121
122
|
const parsedAttempt = formatterHelpers.parseTestCaseAttempt({
|
|
@@ -151,11 +152,20 @@ To open last Flakiness report, run:
|
|
|
151
152
|
const finishTimestamp = testCaseFinished ? toUnixTimestampMS(testCaseFinished.timestamp) : startTimestamp;
|
|
152
153
|
const errors = parsedAttempt.testSteps.map((step) => extractErrorFromStep(worktree, this.cwd, step)).filter((error) => !!error);
|
|
153
154
|
const stdio = extractSTDIOFromTestSteps(parsedAttempt.testSteps, startTimestamp);
|
|
155
|
+
let parallelIndex;
|
|
156
|
+
if (testCaseStarted.workerId) {
|
|
157
|
+
parallelIndex = parallelIndexByWorkerId.get(testCaseStarted.workerId);
|
|
158
|
+
if (parallelIndex === void 0) {
|
|
159
|
+
parallelIndex = parallelIndexByWorkerId.size;
|
|
160
|
+
parallelIndexByWorkerId.set(testCaseStarted.workerId, parallelIndex);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
154
163
|
test.attempts.push({
|
|
155
164
|
environmentIdx: 0,
|
|
156
165
|
startTimestamp,
|
|
157
166
|
duration: Math.max(0, finishTimestamp - startTimestamp),
|
|
158
167
|
status: toFKStatus(attemptData.worstTestStepResult.status),
|
|
168
|
+
parallelIndex,
|
|
159
169
|
annotations: extractAttemptAnnotations(worktree, this.cwd, featureUri, attemptData.gherkinDocument, attemptData.pickle),
|
|
160
170
|
errors: errors.length ? errors : void 0,
|
|
161
171
|
attachments: await extractAttachmentsFromTestSteps(parsedAttempt.testSteps, attachments),
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flakiness/cucumberjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/flakiness/cucumberjs.git"
|
|
7
|
+
},
|
|
4
8
|
"description": "",
|
|
5
9
|
"exports": {
|
|
6
10
|
".": {
|
|
@@ -17,11 +21,12 @@
|
|
|
17
21
|
"type": "module",
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@flakiness/flakiness-report": "^0.28.0",
|
|
20
|
-
"@flakiness/sdk": "^2.
|
|
24
|
+
"@flakiness/sdk": "^2.4.0"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
27
|
"@cucumber/cucumber": "^12.7.0",
|
|
24
28
|
"@cucumber/messages": "^32.2.0",
|
|
29
|
+
"@flakiness/cucumberjs": "1.0.1",
|
|
25
30
|
"@types/node": "^25.5.0",
|
|
26
31
|
"c8": "^11.0.0",
|
|
27
32
|
"esbuild": "^0.27.4",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;AAkDjE,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,SAAS;IAC/D,MAAM,CAAC,aAAa,SAAwD;IAE5E,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAiB;IAEzC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,oBAAoB,CAAsC;IAClE,OAAO,CAAC,qBAAqB,CAAuC;gBAExD,OAAO,EAAE,iBAAiB;IA0BtC,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,mBAAmB;IAIZ,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAaxC,OAAO,CAAC,aAAa;YAMP,kBAAkB;YAqDlB,cAAc;CA4F7B"}
|