@qualflare/cucumberjs 0.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/LICENSE +190 -0
- package/README.md +104 -0
- package/dist/formatter/index.cjs +1441 -0
- package/dist/formatter/index.cjs.map +1 -0
- package/dist/formatter/index.d.cts +21 -0
- package/dist/formatter/index.d.ts +21 -0
- package/dist/formatter/index.js +1410 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/index.cjs +116 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +229 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Qualflare `/api/v1/collect` wire contract.
|
|
3
|
+
*
|
|
4
|
+
* These interfaces mirror `api-service/internal/core/domain/launch/launch.go`
|
|
5
|
+
* field-for-field (JSON key names, optionality, and string-union values) as
|
|
6
|
+
* verified against the live, deployed source. Do not add, rename, or change
|
|
7
|
+
* the optionality of a field here without re-verifying against that file —
|
|
8
|
+
* a mismatch either 400s the request or silently drops data server-side.
|
|
9
|
+
*
|
|
10
|
+
* Kept identical to `@qualflare/cypress`'s copy of this file — the two
|
|
11
|
+
* packages report to the same backend, so this file should stay in sync
|
|
12
|
+
* across both.
|
|
13
|
+
*/
|
|
14
|
+
/** Every `duration` field in this contract is a raw integer number of
|
|
15
|
+
* NANOSECONDS, with no unit marker on the wire. `@cucumber/messages` reports
|
|
16
|
+
* `{seconds, nanos}` — convert directly to nanoseconds (see `./duration.ts`)
|
|
17
|
+
* before assigning into one of these fields. */
|
|
18
|
+
type NanosecondDuration = number;
|
|
19
|
+
type Platform = 'android' | 'ios' | 'desktop' | 'web' | 'api';
|
|
20
|
+
type CaseStatus = 'passed' | 'failed' | 'skipped' | 'error' | 'timeout' | 'aborted' | 'pending';
|
|
21
|
+
type FrameworkCategory = 'unit' | 'bdd' | 'e2e' | 'api' | 'security' | 'generic';
|
|
22
|
+
type CasePriority = 'low' | 'medium' | 'high' | 'critical';
|
|
23
|
+
type LinkType = 'issue' | 'tms' | 'custom';
|
|
24
|
+
interface Metadata {
|
|
25
|
+
version: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
cliName: string;
|
|
28
|
+
}
|
|
29
|
+
interface Label {
|
|
30
|
+
/** Required, max 128 chars. Allure-style arbitrary label name — epic/feature/story/owner/severity
|
|
31
|
+
* are just conventional names, not separate fields. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Required, max 512 chars. */
|
|
34
|
+
value: string;
|
|
35
|
+
}
|
|
36
|
+
interface Link {
|
|
37
|
+
/** Required. A small, server-owned closed taxonomy — not open text. */
|
|
38
|
+
type: LinkType;
|
|
39
|
+
/** Optional, max 255 chars. */
|
|
40
|
+
name?: string;
|
|
41
|
+
/** Required, must be a valid URL, max 2048 chars. */
|
|
42
|
+
url: string;
|
|
43
|
+
}
|
|
44
|
+
interface Parameter {
|
|
45
|
+
name: string;
|
|
46
|
+
value?: string;
|
|
47
|
+
/** Display hint ONLY — the server does not redact this value. Do not treat
|
|
48
|
+
* this as real secret protection. */
|
|
49
|
+
masked?: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface Step {
|
|
52
|
+
name: string;
|
|
53
|
+
keyword?: string;
|
|
54
|
+
/** Reuses the Case status vocabulary. */
|
|
55
|
+
status: CaseStatus | string;
|
|
56
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
57
|
+
duration: NanosecondDuration;
|
|
58
|
+
error?: string;
|
|
59
|
+
location?: string;
|
|
60
|
+
/** 0-based index into the SAME Case's `steps[]` array identifying this
|
|
61
|
+
* step's parent, for nested/hierarchical steps. Omit for a root step.
|
|
62
|
+
* The server drops an out-of-range or cycle-forming value to root rather
|
|
63
|
+
* than rejecting the request — but a well-behaved client shouldn't rely
|
|
64
|
+
* on that. Server caps total steps per case at 1000. */
|
|
65
|
+
parentIndex?: number;
|
|
66
|
+
/** Server caps at 50 parameters per step. */
|
|
67
|
+
parameters?: Parameter[];
|
|
68
|
+
}
|
|
69
|
+
interface Attachment {
|
|
70
|
+
/** Required, max 255 chars. */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Optional, max 1024 chars — informational only, never fetched server-side. */
|
|
73
|
+
path?: string;
|
|
74
|
+
/** Optional, max 255 chars. */
|
|
75
|
+
mimeType?: string;
|
|
76
|
+
/** Base64-encoded content, max 2,097,152 characters (~1.5MB decoded binary).
|
|
77
|
+
* Inline only — there is no external/blob-URL attachment support yet. */
|
|
78
|
+
content?: string;
|
|
79
|
+
/** 0-based index into the Case's `steps[]` this attachment belongs to.
|
|
80
|
+
* Omit for a case-level (not step-level) attachment. */
|
|
81
|
+
stepIndex?: number;
|
|
82
|
+
}
|
|
83
|
+
interface Case {
|
|
84
|
+
/** Required. A stable per-test identifier used for flaky-history matching
|
|
85
|
+
* across separate runs — must stay the same for what a human would call
|
|
86
|
+
* "the same test" across renames you want tracked together. */
|
|
87
|
+
id: string;
|
|
88
|
+
/** Required, 1-255 chars. */
|
|
89
|
+
name: string;
|
|
90
|
+
/** Max 255 chars. */
|
|
91
|
+
className?: string;
|
|
92
|
+
/** Max 10000 chars. */
|
|
93
|
+
description?: string;
|
|
94
|
+
status: CaseStatus;
|
|
95
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
96
|
+
duration: NanosecondDuration;
|
|
97
|
+
retryCount?: number;
|
|
98
|
+
isFlaky?: boolean;
|
|
99
|
+
/** Truncated server-side at 65536 runes, never validation-rejected — send
|
|
100
|
+
* the full error/stack text, don't pre-truncate. */
|
|
101
|
+
error?: string;
|
|
102
|
+
/** Unrecognized values are silently normalized/dropped server-side, never
|
|
103
|
+
* rejects the request. */
|
|
104
|
+
priority?: CasePriority;
|
|
105
|
+
/** Max 64 items, each max 255 chars. */
|
|
106
|
+
tags?: string[];
|
|
107
|
+
properties?: Record<string, string>;
|
|
108
|
+
/** Max 50 items. */
|
|
109
|
+
attachments?: Attachment[];
|
|
110
|
+
/** Server caps at 1000 items and truncates individual fields — never
|
|
111
|
+
* validation-rejected, send freely. */
|
|
112
|
+
steps?: Step[];
|
|
113
|
+
/** Not validated; the server drops an out-of-range value rather than
|
|
114
|
+
* rejecting the request. */
|
|
115
|
+
shardIndex?: number;
|
|
116
|
+
/** RFC3339. */
|
|
117
|
+
startedAt?: string;
|
|
118
|
+
/** Max 100 items. */
|
|
119
|
+
labels?: Label[];
|
|
120
|
+
/** Max 20 items. */
|
|
121
|
+
links?: Link[];
|
|
122
|
+
}
|
|
123
|
+
interface Suite {
|
|
124
|
+
/** Required, 1-255 chars. One Suite per `.feature` file. */
|
|
125
|
+
name: string;
|
|
126
|
+
category?: FrameworkCategory;
|
|
127
|
+
assertions?: number;
|
|
128
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
129
|
+
duration: NanosecondDuration;
|
|
130
|
+
/** RFC3339. */
|
|
131
|
+
timestamp?: string;
|
|
132
|
+
/** Max 64 chars. Per-suite override of the launch-level `browser` — omit
|
|
133
|
+
* to inherit the launch value. */
|
|
134
|
+
browser?: string;
|
|
135
|
+
/** Max 64 chars. Per-suite override of the launch-level `os`. */
|
|
136
|
+
os?: string;
|
|
137
|
+
properties?: Record<string, string>;
|
|
138
|
+
/** Max 5000 items. */
|
|
139
|
+
cases: Case[];
|
|
140
|
+
}
|
|
141
|
+
interface Collect {
|
|
142
|
+
/** Required, 1-100 chars. */
|
|
143
|
+
framework: string;
|
|
144
|
+
platform: Platform;
|
|
145
|
+
/** Required, 1-100 chars. */
|
|
146
|
+
os: string;
|
|
147
|
+
/** Max 64 chars. */
|
|
148
|
+
browser: string;
|
|
149
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
150
|
+
branch: string | null;
|
|
151
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
152
|
+
commit: string | null;
|
|
153
|
+
/** Required, 1-100 chars. Must already exist server-side (404 if not) —
|
|
154
|
+
* every project seeds `development`/`staging`/`production`/`qa` by
|
|
155
|
+
* default. */
|
|
156
|
+
environment: string;
|
|
157
|
+
/** Required, BCP47 (e.g. "en-US"). */
|
|
158
|
+
language: string;
|
|
159
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
160
|
+
milestone: number | null;
|
|
161
|
+
metadata: Metadata | null;
|
|
162
|
+
properties?: Record<string, string>;
|
|
163
|
+
/** Max 2000 items. */
|
|
164
|
+
suites: Suite[];
|
|
165
|
+
/** Max 64 chars. Free text, no enum — an unrecognized CI provider must
|
|
166
|
+
* never reject the request. */
|
|
167
|
+
ciProvider?: string;
|
|
168
|
+
/** Max 128 chars. */
|
|
169
|
+
ciBuildNumber?: string;
|
|
170
|
+
/** Must be a valid URL, max 2048 chars. */
|
|
171
|
+
ciRunUrl?: string;
|
|
172
|
+
/** Must be >= 1. */
|
|
173
|
+
ciPrNumber?: number;
|
|
174
|
+
}
|
|
175
|
+
/** The server's success response body for `POST /api/v1/collect` (HTTP 201). */
|
|
176
|
+
interface CollectResult {
|
|
177
|
+
seq: number;
|
|
178
|
+
}
|
|
179
|
+
/** One entry in the server's validation-failure `fields[]` array. */
|
|
180
|
+
interface ApiFieldError {
|
|
181
|
+
field: string;
|
|
182
|
+
rule?: string;
|
|
183
|
+
message?: string;
|
|
184
|
+
}
|
|
185
|
+
/** The server's error envelope: `{code, message, fields, request_id}`
|
|
186
|
+
* (plus a legacy `error` key some older responses may still carry). */
|
|
187
|
+
interface ApiErrorResponse {
|
|
188
|
+
code?: string;
|
|
189
|
+
message?: string;
|
|
190
|
+
fields?: ApiFieldError[];
|
|
191
|
+
request_id?: string;
|
|
192
|
+
/** Legacy/back-compat key — mirrors the Go CLI's `ErrorResponse.Error` fallback. */
|
|
193
|
+
error?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
declare const qualflare: {
|
|
197
|
+
label(name: string, value: string): void;
|
|
198
|
+
link(url: string, opts?: {
|
|
199
|
+
type?: LinkType;
|
|
200
|
+
name?: string;
|
|
201
|
+
}): void;
|
|
202
|
+
tag(...tags: string[]): void;
|
|
203
|
+
description(text: string): void;
|
|
204
|
+
priority(value: CasePriority): void;
|
|
205
|
+
/** Placed on the currently-open `step()`, if any; otherwise on the Case
|
|
206
|
+
* itself (`Case.properties`). `masked` is a DISPLAY HINT ONLY — the
|
|
207
|
+
* server does not redact the value; see `docs/METADATA-API.md`. */
|
|
208
|
+
parameter(name: string, value?: string, opts?: {
|
|
209
|
+
masked?: boolean;
|
|
210
|
+
}): void;
|
|
211
|
+
attachment(name: string, content: string, opts?: {
|
|
212
|
+
encoding?: "utf8" | "base64";
|
|
213
|
+
mimeType?: string;
|
|
214
|
+
}): void;
|
|
215
|
+
attachmentFromFile(name: string, path: string, opts?: {
|
|
216
|
+
mimeType?: string;
|
|
217
|
+
}): void;
|
|
218
|
+
/** Wraps `fn` as a manually-declared step, nested under any currently-open
|
|
219
|
+
* `qualflare.step()` call. Unlike `@qualflare/cypress` (whose step
|
|
220
|
+
* definitions are Mocha test bodies executed synchronously ahead of
|
|
221
|
+
* Cypress's own deferred command queue, requiring a `Chainable`-detection
|
|
222
|
+
* trick to time the end of a step correctly), a cucumber-js step
|
|
223
|
+
* definition is already a plain `async function` — so this is a
|
|
224
|
+
* straightforward `try/finally`-wrapped call, with EXACT timing (real
|
|
225
|
+
* `Date.now()` deltas around the awaited body), not an approximation. */
|
|
226
|
+
step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export { type ApiErrorResponse, type ApiFieldError, type Attachment, type Case, type CasePriority, type CaseStatus, type Collect, type CollectResult, type FrameworkCategory, type Label, type Link, type LinkType, type Metadata, type NanosecondDuration, type Parameter, type Platform, type Step, type Suite, qualflare };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Qualflare `/api/v1/collect` wire contract.
|
|
3
|
+
*
|
|
4
|
+
* These interfaces mirror `api-service/internal/core/domain/launch/launch.go`
|
|
5
|
+
* field-for-field (JSON key names, optionality, and string-union values) as
|
|
6
|
+
* verified against the live, deployed source. Do not add, rename, or change
|
|
7
|
+
* the optionality of a field here without re-verifying against that file —
|
|
8
|
+
* a mismatch either 400s the request or silently drops data server-side.
|
|
9
|
+
*
|
|
10
|
+
* Kept identical to `@qualflare/cypress`'s copy of this file — the two
|
|
11
|
+
* packages report to the same backend, so this file should stay in sync
|
|
12
|
+
* across both.
|
|
13
|
+
*/
|
|
14
|
+
/** Every `duration` field in this contract is a raw integer number of
|
|
15
|
+
* NANOSECONDS, with no unit marker on the wire. `@cucumber/messages` reports
|
|
16
|
+
* `{seconds, nanos}` — convert directly to nanoseconds (see `./duration.ts`)
|
|
17
|
+
* before assigning into one of these fields. */
|
|
18
|
+
type NanosecondDuration = number;
|
|
19
|
+
type Platform = 'android' | 'ios' | 'desktop' | 'web' | 'api';
|
|
20
|
+
type CaseStatus = 'passed' | 'failed' | 'skipped' | 'error' | 'timeout' | 'aborted' | 'pending';
|
|
21
|
+
type FrameworkCategory = 'unit' | 'bdd' | 'e2e' | 'api' | 'security' | 'generic';
|
|
22
|
+
type CasePriority = 'low' | 'medium' | 'high' | 'critical';
|
|
23
|
+
type LinkType = 'issue' | 'tms' | 'custom';
|
|
24
|
+
interface Metadata {
|
|
25
|
+
version: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
cliName: string;
|
|
28
|
+
}
|
|
29
|
+
interface Label {
|
|
30
|
+
/** Required, max 128 chars. Allure-style arbitrary label name — epic/feature/story/owner/severity
|
|
31
|
+
* are just conventional names, not separate fields. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Required, max 512 chars. */
|
|
34
|
+
value: string;
|
|
35
|
+
}
|
|
36
|
+
interface Link {
|
|
37
|
+
/** Required. A small, server-owned closed taxonomy — not open text. */
|
|
38
|
+
type: LinkType;
|
|
39
|
+
/** Optional, max 255 chars. */
|
|
40
|
+
name?: string;
|
|
41
|
+
/** Required, must be a valid URL, max 2048 chars. */
|
|
42
|
+
url: string;
|
|
43
|
+
}
|
|
44
|
+
interface Parameter {
|
|
45
|
+
name: string;
|
|
46
|
+
value?: string;
|
|
47
|
+
/** Display hint ONLY — the server does not redact this value. Do not treat
|
|
48
|
+
* this as real secret protection. */
|
|
49
|
+
masked?: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface Step {
|
|
52
|
+
name: string;
|
|
53
|
+
keyword?: string;
|
|
54
|
+
/** Reuses the Case status vocabulary. */
|
|
55
|
+
status: CaseStatus | string;
|
|
56
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
57
|
+
duration: NanosecondDuration;
|
|
58
|
+
error?: string;
|
|
59
|
+
location?: string;
|
|
60
|
+
/** 0-based index into the SAME Case's `steps[]` array identifying this
|
|
61
|
+
* step's parent, for nested/hierarchical steps. Omit for a root step.
|
|
62
|
+
* The server drops an out-of-range or cycle-forming value to root rather
|
|
63
|
+
* than rejecting the request — but a well-behaved client shouldn't rely
|
|
64
|
+
* on that. Server caps total steps per case at 1000. */
|
|
65
|
+
parentIndex?: number;
|
|
66
|
+
/** Server caps at 50 parameters per step. */
|
|
67
|
+
parameters?: Parameter[];
|
|
68
|
+
}
|
|
69
|
+
interface Attachment {
|
|
70
|
+
/** Required, max 255 chars. */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Optional, max 1024 chars — informational only, never fetched server-side. */
|
|
73
|
+
path?: string;
|
|
74
|
+
/** Optional, max 255 chars. */
|
|
75
|
+
mimeType?: string;
|
|
76
|
+
/** Base64-encoded content, max 2,097,152 characters (~1.5MB decoded binary).
|
|
77
|
+
* Inline only — there is no external/blob-URL attachment support yet. */
|
|
78
|
+
content?: string;
|
|
79
|
+
/** 0-based index into the Case's `steps[]` this attachment belongs to.
|
|
80
|
+
* Omit for a case-level (not step-level) attachment. */
|
|
81
|
+
stepIndex?: number;
|
|
82
|
+
}
|
|
83
|
+
interface Case {
|
|
84
|
+
/** Required. A stable per-test identifier used for flaky-history matching
|
|
85
|
+
* across separate runs — must stay the same for what a human would call
|
|
86
|
+
* "the same test" across renames you want tracked together. */
|
|
87
|
+
id: string;
|
|
88
|
+
/** Required, 1-255 chars. */
|
|
89
|
+
name: string;
|
|
90
|
+
/** Max 255 chars. */
|
|
91
|
+
className?: string;
|
|
92
|
+
/** Max 10000 chars. */
|
|
93
|
+
description?: string;
|
|
94
|
+
status: CaseStatus;
|
|
95
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
96
|
+
duration: NanosecondDuration;
|
|
97
|
+
retryCount?: number;
|
|
98
|
+
isFlaky?: boolean;
|
|
99
|
+
/** Truncated server-side at 65536 runes, never validation-rejected — send
|
|
100
|
+
* the full error/stack text, don't pre-truncate. */
|
|
101
|
+
error?: string;
|
|
102
|
+
/** Unrecognized values are silently normalized/dropped server-side, never
|
|
103
|
+
* rejects the request. */
|
|
104
|
+
priority?: CasePriority;
|
|
105
|
+
/** Max 64 items, each max 255 chars. */
|
|
106
|
+
tags?: string[];
|
|
107
|
+
properties?: Record<string, string>;
|
|
108
|
+
/** Max 50 items. */
|
|
109
|
+
attachments?: Attachment[];
|
|
110
|
+
/** Server caps at 1000 items and truncates individual fields — never
|
|
111
|
+
* validation-rejected, send freely. */
|
|
112
|
+
steps?: Step[];
|
|
113
|
+
/** Not validated; the server drops an out-of-range value rather than
|
|
114
|
+
* rejecting the request. */
|
|
115
|
+
shardIndex?: number;
|
|
116
|
+
/** RFC3339. */
|
|
117
|
+
startedAt?: string;
|
|
118
|
+
/** Max 100 items. */
|
|
119
|
+
labels?: Label[];
|
|
120
|
+
/** Max 20 items. */
|
|
121
|
+
links?: Link[];
|
|
122
|
+
}
|
|
123
|
+
interface Suite {
|
|
124
|
+
/** Required, 1-255 chars. One Suite per `.feature` file. */
|
|
125
|
+
name: string;
|
|
126
|
+
category?: FrameworkCategory;
|
|
127
|
+
assertions?: number;
|
|
128
|
+
/** NANOSECONDS — see `NanosecondDuration`. */
|
|
129
|
+
duration: NanosecondDuration;
|
|
130
|
+
/** RFC3339. */
|
|
131
|
+
timestamp?: string;
|
|
132
|
+
/** Max 64 chars. Per-suite override of the launch-level `browser` — omit
|
|
133
|
+
* to inherit the launch value. */
|
|
134
|
+
browser?: string;
|
|
135
|
+
/** Max 64 chars. Per-suite override of the launch-level `os`. */
|
|
136
|
+
os?: string;
|
|
137
|
+
properties?: Record<string, string>;
|
|
138
|
+
/** Max 5000 items. */
|
|
139
|
+
cases: Case[];
|
|
140
|
+
}
|
|
141
|
+
interface Collect {
|
|
142
|
+
/** Required, 1-100 chars. */
|
|
143
|
+
framework: string;
|
|
144
|
+
platform: Platform;
|
|
145
|
+
/** Required, 1-100 chars. */
|
|
146
|
+
os: string;
|
|
147
|
+
/** Max 64 chars. */
|
|
148
|
+
browser: string;
|
|
149
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
150
|
+
branch: string | null;
|
|
151
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
152
|
+
commit: string | null;
|
|
153
|
+
/** Required, 1-100 chars. Must already exist server-side (404 if not) —
|
|
154
|
+
* every project seeds `development`/`staging`/`production`/`qa` by
|
|
155
|
+
* default. */
|
|
156
|
+
environment: string;
|
|
157
|
+
/** Required, BCP47 (e.g. "en-US"). */
|
|
158
|
+
language: string;
|
|
159
|
+
/** ALWAYS present as value-or-null on the wire — never omit this key. */
|
|
160
|
+
milestone: number | null;
|
|
161
|
+
metadata: Metadata | null;
|
|
162
|
+
properties?: Record<string, string>;
|
|
163
|
+
/** Max 2000 items. */
|
|
164
|
+
suites: Suite[];
|
|
165
|
+
/** Max 64 chars. Free text, no enum — an unrecognized CI provider must
|
|
166
|
+
* never reject the request. */
|
|
167
|
+
ciProvider?: string;
|
|
168
|
+
/** Max 128 chars. */
|
|
169
|
+
ciBuildNumber?: string;
|
|
170
|
+
/** Must be a valid URL, max 2048 chars. */
|
|
171
|
+
ciRunUrl?: string;
|
|
172
|
+
/** Must be >= 1. */
|
|
173
|
+
ciPrNumber?: number;
|
|
174
|
+
}
|
|
175
|
+
/** The server's success response body for `POST /api/v1/collect` (HTTP 201). */
|
|
176
|
+
interface CollectResult {
|
|
177
|
+
seq: number;
|
|
178
|
+
}
|
|
179
|
+
/** One entry in the server's validation-failure `fields[]` array. */
|
|
180
|
+
interface ApiFieldError {
|
|
181
|
+
field: string;
|
|
182
|
+
rule?: string;
|
|
183
|
+
message?: string;
|
|
184
|
+
}
|
|
185
|
+
/** The server's error envelope: `{code, message, fields, request_id}`
|
|
186
|
+
* (plus a legacy `error` key some older responses may still carry). */
|
|
187
|
+
interface ApiErrorResponse {
|
|
188
|
+
code?: string;
|
|
189
|
+
message?: string;
|
|
190
|
+
fields?: ApiFieldError[];
|
|
191
|
+
request_id?: string;
|
|
192
|
+
/** Legacy/back-compat key — mirrors the Go CLI's `ErrorResponse.Error` fallback. */
|
|
193
|
+
error?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
declare const qualflare: {
|
|
197
|
+
label(name: string, value: string): void;
|
|
198
|
+
link(url: string, opts?: {
|
|
199
|
+
type?: LinkType;
|
|
200
|
+
name?: string;
|
|
201
|
+
}): void;
|
|
202
|
+
tag(...tags: string[]): void;
|
|
203
|
+
description(text: string): void;
|
|
204
|
+
priority(value: CasePriority): void;
|
|
205
|
+
/** Placed on the currently-open `step()`, if any; otherwise on the Case
|
|
206
|
+
* itself (`Case.properties`). `masked` is a DISPLAY HINT ONLY — the
|
|
207
|
+
* server does not redact the value; see `docs/METADATA-API.md`. */
|
|
208
|
+
parameter(name: string, value?: string, opts?: {
|
|
209
|
+
masked?: boolean;
|
|
210
|
+
}): void;
|
|
211
|
+
attachment(name: string, content: string, opts?: {
|
|
212
|
+
encoding?: "utf8" | "base64";
|
|
213
|
+
mimeType?: string;
|
|
214
|
+
}): void;
|
|
215
|
+
attachmentFromFile(name: string, path: string, opts?: {
|
|
216
|
+
mimeType?: string;
|
|
217
|
+
}): void;
|
|
218
|
+
/** Wraps `fn` as a manually-declared step, nested under any currently-open
|
|
219
|
+
* `qualflare.step()` call. Unlike `@qualflare/cypress` (whose step
|
|
220
|
+
* definitions are Mocha test bodies executed synchronously ahead of
|
|
221
|
+
* Cypress's own deferred command queue, requiring a `Chainable`-detection
|
|
222
|
+
* trick to time the end of a step correctly), a cucumber-js step
|
|
223
|
+
* definition is already a plain `async function` — so this is a
|
|
224
|
+
* straightforward `try/finally`-wrapped call, with EXACT timing (real
|
|
225
|
+
* `Date.now()` deltas around the awaited body), not an approximation. */
|
|
226
|
+
step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export { type ApiErrorResponse, type ApiFieldError, type Attachment, type Case, type CasePriority, type CaseStatus, type Collect, type CollectResult, type FrameworkCategory, type Label, type Link, type LinkType, type Metadata, type NanosecondDuration, type Parameter, type Platform, type Step, type Suite, qualflare };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/runtime/qualflare-api.ts
|
|
2
|
+
import { world } from "@cucumber/cucumber";
|
|
3
|
+
|
|
4
|
+
// src/shared/constants.ts
|
|
5
|
+
var RESERVED_MESSAGE_MEDIA_TYPE = "application/vnd.qualflare.message+json";
|
|
6
|
+
|
|
7
|
+
// src/shared/logger.ts
|
|
8
|
+
var PREFIX = "[qualflare-cucumberjs]";
|
|
9
|
+
var logger = {
|
|
10
|
+
debug(...args) {
|
|
11
|
+
console.debug(PREFIX, ...args);
|
|
12
|
+
},
|
|
13
|
+
info(...args) {
|
|
14
|
+
console.log(PREFIX, ...args);
|
|
15
|
+
},
|
|
16
|
+
warn(...args) {
|
|
17
|
+
console.warn(PREFIX, ...args);
|
|
18
|
+
},
|
|
19
|
+
error(...args) {
|
|
20
|
+
console.error(PREFIX, ...args);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/runtime/qualflare-api.ts
|
|
25
|
+
function send(message) {
|
|
26
|
+
try {
|
|
27
|
+
world.attach(JSON.stringify(message), RESERVED_MESSAGE_MEDIA_TYPE);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
logger.warn(
|
|
30
|
+
`qualflare.* was called outside a running scenario (e.g. from a Before/After hook, BeforeAll/AfterAll, or at module-load time) \u2014 this call had no effect. (${err.message})`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function utf8ToBase64(text) {
|
|
35
|
+
return Buffer.from(text, "utf8").toString("base64");
|
|
36
|
+
}
|
|
37
|
+
var qualflare = {
|
|
38
|
+
label(name, value) {
|
|
39
|
+
send({ type: "label", name, value });
|
|
40
|
+
},
|
|
41
|
+
link(url, opts) {
|
|
42
|
+
send({ type: "link", url, linkType: opts?.type, name: opts?.name });
|
|
43
|
+
},
|
|
44
|
+
tag(...tags) {
|
|
45
|
+
send({ type: "tag", tags });
|
|
46
|
+
},
|
|
47
|
+
description(text) {
|
|
48
|
+
send({ type: "description", text });
|
|
49
|
+
},
|
|
50
|
+
priority(value) {
|
|
51
|
+
send({ type: "priority", value });
|
|
52
|
+
},
|
|
53
|
+
/** Placed on the currently-open `step()`, if any; otherwise on the Case
|
|
54
|
+
* itself (`Case.properties`). `masked` is a DISPLAY HINT ONLY — the
|
|
55
|
+
* server does not redact the value; see `docs/METADATA-API.md`. */
|
|
56
|
+
parameter(name, value, opts) {
|
|
57
|
+
send({ type: "parameter", name, value, masked: opts?.masked });
|
|
58
|
+
},
|
|
59
|
+
attachment(name, content, opts) {
|
|
60
|
+
const contentBase64 = opts?.encoding === "base64" ? content : utf8ToBase64(content);
|
|
61
|
+
send({ type: "attachment", name, contentBase64, mimeType: opts?.mimeType });
|
|
62
|
+
},
|
|
63
|
+
attachmentFromFile(name, path, opts) {
|
|
64
|
+
send({ type: "attachment_from_file", name, path, mimeType: opts?.mimeType });
|
|
65
|
+
},
|
|
66
|
+
/** Wraps `fn` as a manually-declared step, nested under any currently-open
|
|
67
|
+
* `qualflare.step()` call. Unlike `@qualflare/cypress` (whose step
|
|
68
|
+
* definitions are Mocha test bodies executed synchronously ahead of
|
|
69
|
+
* Cypress's own deferred command queue, requiring a `Chainable`-detection
|
|
70
|
+
* trick to time the end of a step correctly), a cucumber-js step
|
|
71
|
+
* definition is already a plain `async function` — so this is a
|
|
72
|
+
* straightforward `try/finally`-wrapped call, with EXACT timing (real
|
|
73
|
+
* `Date.now()` deltas around the awaited body), not an approximation. */
|
|
74
|
+
async step(name, fn) {
|
|
75
|
+
send({ type: "step_start", name, timestamp: Date.now() });
|
|
76
|
+
try {
|
|
77
|
+
const result = await fn();
|
|
78
|
+
send({ type: "step_stop", status: "passed", timestamp: Date.now() });
|
|
79
|
+
return result;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
send({ type: "step_stop", status: "failed", error: err.message, timestamp: Date.now() });
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
qualflare
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/qualflare-api.ts","../src/shared/constants.ts","../src/shared/logger.ts"],"sourcesContent":["import { world } from '@cucumber/cucumber';\n\nimport { RESERVED_MESSAGE_MEDIA_TYPE } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { CasePriority, LinkType } from '../shared/types.js';\nimport type { RuntimeMessage } from './message-types.js';\n\n/**\n * `@cucumber/cucumber` exports a `world` proxy (`AsyncLocalStorage`-backed,\n * added in cucumber-js 10.8.0 — this package's peer-dependency floor is\n * pinned exactly to that version because of this) that resolves to the\n * currently-executing scenario's World from anywhere — no `BeforeAll`\n * singleton registration needed. Calling it outside a step/hook body (e.g.\n * at module-load time, or from `BeforeAll`/`AfterAll`, which have no \"current\n * test case\") throws — caught here and logged once, mirroring\n * `@qualflare/cypress`'s \"warn, never throw, never abort the run\" philosophy\n * for a misplaced `qualflare.*()` call.\n */\nfunction send(message: RuntimeMessage): void {\n try {\n world.attach(JSON.stringify(message), RESERVED_MESSAGE_MEDIA_TYPE);\n } catch (err) {\n logger.warn(\n `qualflare.* was called outside a running scenario (e.g. from a Before/After hook, ` +\n `BeforeAll/AfterAll, or at module-load time) — this call had no effect. (${(err as Error).message})`,\n );\n }\n}\n\nfunction utf8ToBase64(text: string): string {\n return Buffer.from(text, 'utf8').toString('base64');\n}\n\nexport const qualflare = {\n label(name: string, value: string): void {\n send({ type: 'label', name, value });\n },\n\n link(url: string, opts?: { type?: LinkType; name?: string }): void {\n send({ type: 'link', url, linkType: opts?.type, name: opts?.name });\n },\n\n tag(...tags: string[]): void {\n send({ type: 'tag', tags });\n },\n\n description(text: string): void {\n send({ type: 'description', text });\n },\n\n priority(value: CasePriority): void {\n send({ type: 'priority', value });\n },\n\n /** Placed on the currently-open `step()`, if any; otherwise on the Case\n * itself (`Case.properties`). `masked` is a DISPLAY HINT ONLY — the\n * server does not redact the value; see `docs/METADATA-API.md`. */\n parameter(name: string, value?: string, opts?: { masked?: boolean }): void {\n send({ type: 'parameter', name, value, masked: opts?.masked });\n },\n\n attachment(name: string, content: string, opts?: { encoding?: 'utf8' | 'base64'; mimeType?: string }): void {\n const contentBase64 = opts?.encoding === 'base64' ? content : utf8ToBase64(content);\n send({ type: 'attachment', name, contentBase64, mimeType: opts?.mimeType });\n },\n\n attachmentFromFile(name: string, path: string, opts?: { mimeType?: string }): void {\n send({ type: 'attachment_from_file', name, path, mimeType: opts?.mimeType });\n },\n\n /** Wraps `fn` as a manually-declared step, nested under any currently-open\n * `qualflare.step()` call. Unlike `@qualflare/cypress` (whose step\n * definitions are Mocha test bodies executed synchronously ahead of\n * Cypress's own deferred command queue, requiring a `Chainable`-detection\n * trick to time the end of a step correctly), a cucumber-js step\n * definition is already a plain `async function` — so this is a\n * straightforward `try/finally`-wrapped call, with EXACT timing (real\n * `Date.now()` deltas around the awaited body), not an approximation. */\n async step<T>(name: string, fn: () => T | Promise<T>): Promise<T> {\n send({ type: 'step_start', name, timestamp: Date.now() });\n try {\n const result = await fn();\n send({ type: 'step_stop', status: 'passed', timestamp: Date.now() });\n return result;\n } catch (err) {\n send({ type: 'step_stop', status: 'failed', error: (err as Error).message, timestamp: Date.now() });\n throw err;\n }\n },\n};\n","/**\n * Shared constants used across the formatter and the author-facing runtime\n * API.\n */\n\n/** Reserved `World.attach()` media type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from step-definition and hook\n * code back to the formatter process — the only data channel CucumberJS\n * gives user code back to a running formatter. The formatter's attachment\n * handler recognizes this exact media type and replays the message as a\n * model mutation instead of rendering it as a literal attachment. */\nexport const RESERVED_MESSAGE_MEDIA_TYPE = 'application/vnd.qualflare.message+json';\n\n/** HTTP headers used against `/api/v1/collect`. */\nexport const HEADER_TOKEN = 'QF_TOKEN';\nexport const HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';\nexport const HEADER_CONTENT_TYPE = 'Content-Type';\nexport const HEADER_ACCEPT = 'Accept';\nexport const HEADER_USER_AGENT = 'User-Agent';\n\n/** Server-side caps this client should respect defensively (see\n * `api-service/internal/core/domain/launch/launch.go`). */\nexport const MAX_SUITES_PER_LAUNCH = 2000;\nexport const MAX_CASES_PER_SUITE = 5000;\nexport const MAX_STEPS_PER_CASE = 1000;\nexport const MAX_PARAMETERS_PER_STEP = 50;\nexport const MAX_ATTACHMENTS_PER_CASE = 50;\nexport const MAX_LABELS_PER_CASE = 100;\nexport const MAX_LINKS_PER_CASE = 20;\nexport const MAX_TAGS_PER_CASE = 64;\nexport const MAX_TAG_LENGTH = 255;\nexport const MAX_IDEMPOTENCY_KEY_CHARS = 255;\n\n/** Client-side SOFT cap on steps recorded per scenario attempt — well under\n * the server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). Once hit,\n * further steps within that attempt are dropped (with a one-time warning),\n * not queued and truncated later. */\nexport const MAX_STEPS_PER_TEST_ATTEMPT = 300;\n","/**\n * A minimal logger writing to stderr. Deliberately avoids stdout, since\n * that's typically `cucumber-js`'s own test-output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-cucumberjs]';\n\nexport const logger = {\n debug(...args: unknown[]): void {\n console.debug(PREFIX, ...args);\n },\n info(...args: unknown[]): void {\n console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]): void {\n console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]): void {\n console.error(PREFIX, ...args);\n },\n};\n"],"mappings":";AAAA,SAAS,aAAa;;;ACWf,IAAM,8BAA8B;;;ACL3C,IAAM,SAAS;AAER,IAAM,SAAS;AAAA,EACpB,SAAS,MAAuB;AAC9B,YAAQ,MAAM,QAAQ,GAAG,IAAI;AAAA,EAC/B;AAAA,EACA,QAAQ,MAAuB;AAC7B,YAAQ,IAAI,QAAQ,GAAG,IAAI;AAAA,EAC7B;AAAA,EACA,QAAQ,MAAuB;AAC7B,YAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,EAC9B;AAAA,EACA,SAAS,MAAuB;AAC9B,YAAQ,MAAM,QAAQ,GAAG,IAAI;AAAA,EAC/B;AACF;;;AFHA,SAAS,KAAK,SAA+B;AAC3C,MAAI;AACF,UAAM,OAAO,KAAK,UAAU,OAAO,GAAG,2BAA2B;AAAA,EACnE,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,kKAC8E,IAAc,OAAO;AAAA,IACrG;AAAA,EACF;AACF;AAEA,SAAS,aAAa,MAAsB;AAC1C,SAAO,OAAO,KAAK,MAAM,MAAM,EAAE,SAAS,QAAQ;AACpD;AAEO,IAAM,YAAY;AAAA,EACvB,MAAM,MAAc,OAAqB;AACvC,SAAK,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC;AAAA,EACrC;AAAA,EAEA,KAAK,KAAa,MAAiD;AACjE,SAAK,EAAE,MAAM,QAAQ,KAAK,UAAU,MAAM,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,EACpE;AAAA,EAEA,OAAO,MAAsB;AAC3B,SAAK,EAAE,MAAM,OAAO,KAAK,CAAC;AAAA,EAC5B;AAAA,EAEA,YAAY,MAAoB;AAC9B,SAAK,EAAE,MAAM,eAAe,KAAK,CAAC;AAAA,EACpC;AAAA,EAEA,SAAS,OAA2B;AAClC,SAAK,EAAE,MAAM,YAAY,MAAM,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,MAAc,OAAgB,MAAmC;AACzE,SAAK,EAAE,MAAM,aAAa,MAAM,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,EAC/D;AAAA,EAEA,WAAW,MAAc,SAAiB,MAAkE;AAC1G,UAAM,gBAAgB,MAAM,aAAa,WAAW,UAAU,aAAa,OAAO;AAClF,SAAK,EAAE,MAAM,cAAc,MAAM,eAAe,UAAU,MAAM,SAAS,CAAC;AAAA,EAC5E;AAAA,EAEA,mBAAmB,MAAc,MAAc,MAAoC;AACjF,SAAK,EAAE,MAAM,wBAAwB,MAAM,MAAM,UAAU,MAAM,SAAS,CAAC;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,KAAQ,MAAc,IAAsC;AAChE,SAAK,EAAE,MAAM,cAAc,MAAM,WAAW,KAAK,IAAI,EAAE,CAAC;AACxD,QAAI;AACF,YAAM,SAAS,MAAM,GAAG;AACxB,WAAK,EAAE,MAAM,aAAa,QAAQ,UAAU,WAAW,KAAK,IAAI,EAAE,CAAC;AACnE,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,EAAE,MAAM,aAAa,QAAQ,UAAU,OAAQ,IAAc,SAAS,WAAW,KAAK,IAAI,EAAE,CAAC;AAClG,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qualflare/cucumberjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Native CucumberJS reporter for the Qualflare test-management platform.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"qualflare",
|
|
7
|
+
"cucumber",
|
|
8
|
+
"cucumberjs",
|
|
9
|
+
"bdd",
|
|
10
|
+
"gherkin",
|
|
11
|
+
"test-reporter",
|
|
12
|
+
"test-results",
|
|
13
|
+
"flaky-tests",
|
|
14
|
+
"test-reporting"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://qualflare.com",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/Qualflare/qualflare-cucumberjs/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Qualflare/qualflare-cucumberjs.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"author": "Qualflare",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.cjs",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js",
|
|
34
|
+
"require": "./dist/index.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./formatter": {
|
|
37
|
+
"types": "./dist/formatter/index.d.ts",
|
|
38
|
+
"import": "./dist/formatter/index.js",
|
|
39
|
+
"require": "./dist/formatter/index.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"provenance": true
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@cucumber/cucumber": ">=10.8.0 <14"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@cucumber/messages": "^34.0.0",
|
|
60
|
+
"ci-info": "^4.0.0",
|
|
61
|
+
"undici": "^6.19.8"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@cucumber/cucumber": "^13.2.1",
|
|
65
|
+
"@types/node": "^18.19.50",
|
|
66
|
+
"eslint": "^9.9.1",
|
|
67
|
+
"execa": "^9.3.1",
|
|
68
|
+
"prettier": "^3.3.3",
|
|
69
|
+
"tsup": "^8.2.4",
|
|
70
|
+
"typescript": "^5.6.2",
|
|
71
|
+
"typescript-eslint": "^8.5.0",
|
|
72
|
+
"vitest": "^2.0.5"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"dev": "tsup --watch",
|
|
77
|
+
"typecheck": "tsc --noEmit",
|
|
78
|
+
"lint": "eslint .",
|
|
79
|
+
"test": "vitest run test/unit",
|
|
80
|
+
"test:integration": "vitest run test/integration --pool=forks --testTimeout=120000",
|
|
81
|
+
"test:all": "npm run test && npm run test:integration",
|
|
82
|
+
"prepublishOnly": "npm run build"
|
|
83
|
+
}
|
|
84
|
+
}
|