@qualflare/cucumberjs 0.1.0 → 0.3.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 +55 -14
- package/dist/formatter/index.cjs +230 -303
- package/dist/formatter/index.cjs.map +1 -1
- package/dist/formatter/index.d.cts +14 -1
- package/dist/formatter/index.d.ts +14 -1
- package/dist/formatter/index.js +230 -303
- package/dist/formatter/index.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var import_cucumber = require("@cucumber/cucumber");
|
|
|
29
29
|
|
|
30
30
|
// src/shared/constants.ts
|
|
31
31
|
var RESERVED_MESSAGE_MEDIA_TYPE = "application/vnd.qualflare.message+json";
|
|
32
|
+
var MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
32
33
|
|
|
33
34
|
// src/shared/logger.ts
|
|
34
35
|
var PREFIX = "[qualflare-cucumberjs]";
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/runtime/qualflare-api.ts","../src/shared/constants.ts","../src/shared/logger.ts"],"sourcesContent":["// Public entry point — safe to import from step-definition/support files.\nexport { qualflare } from './runtime/qualflare-api.js';\nexport type {\n ApiErrorResponse,\n ApiFieldError,\n Attachment,\n Case,\n CasePriority,\n CaseStatus,\n Collect,\n CollectResult,\n FrameworkCategory,\n Label,\n Link,\n LinkType,\n Metadata,\n NanosecondDuration,\n Parameter,\n Platform,\n Step,\n Suite,\n} from './shared/types.js';\n","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/**
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/runtime/qualflare-api.ts","../src/shared/constants.ts","../src/shared/logger.ts"],"sourcesContent":["// Public entry point — safe to import from step-definition/support files.\nexport { qualflare } from './runtime/qualflare-api.js';\nexport type {\n ApiErrorResponse,\n ApiFieldError,\n Attachment,\n Case,\n CasePriority,\n CaseStatus,\n Collect,\n CollectResult,\n FrameworkCategory,\n Label,\n Link,\n LinkType,\n Metadata,\n NanosecondDuration,\n Parameter,\n Platform,\n Step,\n Suite,\n} from './shared/types.js';\n","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/** 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;\n\n/** Mirrors `launch.MaxAttachmentUploadFileSize` — the server's hard cap on a\n * single `POST /api/v1/attachments/upload-url` request (video). */\nexport const MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;\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;AAAA;AAAA;AAAA;AAAA;;;ACAA,sBAAsB;;;ACWf,IAAM,8BAA8B;AAgBpC,IAAM,yBAAyB,KAAK,OAAO;;;ACrBlD,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,0BAAM,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/dist/index.d.cts
CHANGED
|
@@ -18,13 +18,30 @@
|
|
|
18
18
|
type NanosecondDuration = number;
|
|
19
19
|
type Platform = 'android' | 'ios' | 'desktop' | 'web' | 'api';
|
|
20
20
|
type CaseStatus = 'passed' | 'failed' | 'skipped' | 'error' | 'timeout' | 'aborted' | 'pending';
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The server's oneof accepts a value named after any of the ~23 frameworks it
|
|
23
|
+
* auto-detects (e.g. "cucumber"), not just the six coarse buckets below — a
|
|
24
|
+
* suite's category is meant to say exactly which tool produced it, which is
|
|
25
|
+
* what lets the UI show that tool's own logo. This reporter only ever emits
|
|
26
|
+
* 'cucumber' (see run-hook-tracker.ts / suite-builder.ts), so that's the only
|
|
27
|
+
* tool-specific value listed here; the six buckets remain for backward
|
|
28
|
+
* compatibility / servers that haven't been upgraded.
|
|
29
|
+
*/
|
|
30
|
+
type FrameworkCategory = 'cucumber' | 'unit' | 'bdd' | 'e2e' | 'api' | 'security' | 'generic';
|
|
22
31
|
type CasePriority = 'low' | 'medium' | 'high' | 'critical';
|
|
23
32
|
type LinkType = 'issue' | 'tms' | 'custom';
|
|
24
33
|
interface Metadata {
|
|
25
34
|
version: string;
|
|
26
35
|
timestamp: string;
|
|
27
36
|
cliName: string;
|
|
37
|
+
/** Identifier every shard of ONE run shares. `qualflare-cli collect` groups
|
|
38
|
+
* the report files in a directory by this and refuses to upload when more
|
|
39
|
+
* than one distinct run is present, so a file left over from an earlier run
|
|
40
|
+
* cannot be merged silently into this launch.
|
|
41
|
+
*
|
|
42
|
+
* Optional because reports written by earlier releases have none; the CLI
|
|
43
|
+
* treats those as "unknown run" and never blocks on them. */
|
|
44
|
+
runId?: string;
|
|
28
45
|
}
|
|
29
46
|
interface Label {
|
|
30
47
|
/** Required, max 128 chars. Allure-style arbitrary label name — epic/feature/story/owner/severity
|
|
@@ -74,8 +91,22 @@ interface Attachment {
|
|
|
74
91
|
/** Optional, max 255 chars. */
|
|
75
92
|
mimeType?: string;
|
|
76
93
|
/** Base64-encoded content, max 2,097,152 characters (~1.5MB decoded binary).
|
|
77
|
-
*
|
|
94
|
+
* Mutually exclusive with `storageKey` — used for small inline attachments. */
|
|
78
95
|
content?: string;
|
|
96
|
+
/** R2 object key from a prior `POST /api/v1/attachments/upload-url` call —
|
|
97
|
+
* mutually exclusive with `content`. Used for attachments too large to
|
|
98
|
+
* inline (video); if both are set, `storageKey` wins server-side. Max 1024
|
|
99
|
+
* chars. */
|
|
100
|
+
storageKey?: string;
|
|
101
|
+
/** Set when this is a video the reporter copied into the same output
|
|
102
|
+
* directory as the report file, rather than uploading it itself —
|
|
103
|
+
* `qualflare-cli` resolves this into a real `storageKey` at collect time.
|
|
104
|
+
* Relative to the report file's own directory. Never sent to `/collect`
|
|
105
|
+
* directly; mutually exclusive with `content`/`storageKey`. */
|
|
106
|
+
localVideoPath?: string;
|
|
107
|
+
/** Byte size of the object at `storageKey`. Ignored when `storageKey` is
|
|
108
|
+
* unset. */
|
|
109
|
+
fileSize?: number;
|
|
79
110
|
/** 0-based index into the Case's `steps[]` this attachment belongs to.
|
|
80
111
|
* Omit for a case-level (not step-level) attachment. */
|
|
81
112
|
stepIndex?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -18,13 +18,30 @@
|
|
|
18
18
|
type NanosecondDuration = number;
|
|
19
19
|
type Platform = 'android' | 'ios' | 'desktop' | 'web' | 'api';
|
|
20
20
|
type CaseStatus = 'passed' | 'failed' | 'skipped' | 'error' | 'timeout' | 'aborted' | 'pending';
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The server's oneof accepts a value named after any of the ~23 frameworks it
|
|
23
|
+
* auto-detects (e.g. "cucumber"), not just the six coarse buckets below — a
|
|
24
|
+
* suite's category is meant to say exactly which tool produced it, which is
|
|
25
|
+
* what lets the UI show that tool's own logo. This reporter only ever emits
|
|
26
|
+
* 'cucumber' (see run-hook-tracker.ts / suite-builder.ts), so that's the only
|
|
27
|
+
* tool-specific value listed here; the six buckets remain for backward
|
|
28
|
+
* compatibility / servers that haven't been upgraded.
|
|
29
|
+
*/
|
|
30
|
+
type FrameworkCategory = 'cucumber' | 'unit' | 'bdd' | 'e2e' | 'api' | 'security' | 'generic';
|
|
22
31
|
type CasePriority = 'low' | 'medium' | 'high' | 'critical';
|
|
23
32
|
type LinkType = 'issue' | 'tms' | 'custom';
|
|
24
33
|
interface Metadata {
|
|
25
34
|
version: string;
|
|
26
35
|
timestamp: string;
|
|
27
36
|
cliName: string;
|
|
37
|
+
/** Identifier every shard of ONE run shares. `qualflare-cli collect` groups
|
|
38
|
+
* the report files in a directory by this and refuses to upload when more
|
|
39
|
+
* than one distinct run is present, so a file left over from an earlier run
|
|
40
|
+
* cannot be merged silently into this launch.
|
|
41
|
+
*
|
|
42
|
+
* Optional because reports written by earlier releases have none; the CLI
|
|
43
|
+
* treats those as "unknown run" and never blocks on them. */
|
|
44
|
+
runId?: string;
|
|
28
45
|
}
|
|
29
46
|
interface Label {
|
|
30
47
|
/** Required, max 128 chars. Allure-style arbitrary label name — epic/feature/story/owner/severity
|
|
@@ -74,8 +91,22 @@ interface Attachment {
|
|
|
74
91
|
/** Optional, max 255 chars. */
|
|
75
92
|
mimeType?: string;
|
|
76
93
|
/** Base64-encoded content, max 2,097,152 characters (~1.5MB decoded binary).
|
|
77
|
-
*
|
|
94
|
+
* Mutually exclusive with `storageKey` — used for small inline attachments. */
|
|
78
95
|
content?: string;
|
|
96
|
+
/** R2 object key from a prior `POST /api/v1/attachments/upload-url` call —
|
|
97
|
+
* mutually exclusive with `content`. Used for attachments too large to
|
|
98
|
+
* inline (video); if both are set, `storageKey` wins server-side. Max 1024
|
|
99
|
+
* chars. */
|
|
100
|
+
storageKey?: string;
|
|
101
|
+
/** Set when this is a video the reporter copied into the same output
|
|
102
|
+
* directory as the report file, rather than uploading it itself —
|
|
103
|
+
* `qualflare-cli` resolves this into a real `storageKey` at collect time.
|
|
104
|
+
* Relative to the report file's own directory. Never sent to `/collect`
|
|
105
|
+
* directly; mutually exclusive with `content`/`storageKey`. */
|
|
106
|
+
localVideoPath?: string;
|
|
107
|
+
/** Byte size of the object at `storageKey`. Ignored when `storageKey` is
|
|
108
|
+
* unset. */
|
|
109
|
+
fileSize?: number;
|
|
79
110
|
/** 0-based index into the Case's `steps[]` this attachment belongs to.
|
|
80
111
|
* Omit for a case-level (not step-level) attachment. */
|
|
81
112
|
stepIndex?: number;
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { world } from "@cucumber/cucumber";
|
|
|
3
3
|
|
|
4
4
|
// src/shared/constants.ts
|
|
5
5
|
var RESERVED_MESSAGE_MEDIA_TYPE = "application/vnd.qualflare.message+json";
|
|
6
|
+
var MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
6
7
|
|
|
7
8
|
// src/shared/logger.ts
|
|
8
9
|
var PREFIX = "[qualflare-cucumberjs]";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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/**
|
|
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/** 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;\n\n/** Mirrors `launch.MaxAttachmentUploadFileSize` — the server's hard cap on a\n * single `POST /api/v1/attachments/upload-url` request (video). */\nexport const MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;\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;AAgBpC,IAAM,yBAAyB,KAAK,OAAO;;;ACrBlD,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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qualflare/cucumberjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Native CucumberJS reporter for the Qualflare test-management platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qualflare",
|
|
@@ -53,12 +53,11 @@
|
|
|
53
53
|
"provenance": true
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@cucumber/cucumber": ">=10.8.0
|
|
56
|
+
"@cucumber/cucumber": ">=10.8.0"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@cucumber/messages": "^34.0.0",
|
|
60
|
-
"ci-info": "^4.0.0"
|
|
61
|
-
"undici": "^6.19.8"
|
|
60
|
+
"ci-info": "^4.0.0"
|
|
62
61
|
},
|
|
63
62
|
"devDependencies": {
|
|
64
63
|
"@cucumber/cucumber": "^13.2.1",
|
|
@@ -69,6 +68,7 @@
|
|
|
69
68
|
"tsup": "^8.2.4",
|
|
70
69
|
"typescript": "^5.6.2",
|
|
71
70
|
"typescript-eslint": "^8.5.0",
|
|
71
|
+
"undici": "^6.19.8",
|
|
72
72
|
"vitest": "^2.0.5"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|