@wdio/browserstack-service 9.36.2 → 9.39.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.
@@ -0,0 +1,132 @@
1
+ import TestFramework from './testFramework.js';
2
+ import TestFrameworkInstance from '../instances/testFrameworkInstance.js';
3
+ import type { Frameworks } from '@wdio/types';
4
+ import type { CucumberHook, Feature } from '../../cucumber-types.js';
5
+ type CucumberHookType = 'BEFORE_ALL' | 'AFTER_ALL' | 'BEFORE_EACH' | 'AFTER_EACH';
6
+ /**
7
+ * CLI test framework for `framework: 'cucumber'` under WebdriverIO.
8
+ *
9
+ * Extends the BASE TestFramework, never WdioMochaTestFramework: WDIO does not call
10
+ * `beforeTest`/`afterTest`/titled hooks for cucumber at all, so mocha's INIT_TEST/TEST/hook
11
+ * boundary semantics have no source here and borrowing them would report cucumber through the
12
+ * wrong runner's event model.
13
+ *
14
+ * Cucumber's unit of work is the scenario, and every `cli/modules/*` observer subscribes to
15
+ * `TestFrameworkState.TEST` — so a scenario raises TEST/PRE at `beforeScenario` and TEST/POST at
16
+ * `afterScenario`, and the module set works unchanged.
17
+ */
18
+ export default class WdioCucumberTestFramework extends TestFramework {
19
+ static KEY_HOOK_LAST_STARTED: string;
20
+ static KEY_HOOK_LAST_FINISHED: string;
21
+ /**
22
+ * The bookkeeping `classifyHookType()` derives hook types from. A cucumber hook invocation
23
+ * carries no title, and `BeforeAll`/`AfterAll` pass no hook object at all, so classification
24
+ * is state-machine derived — `util.ts → getHookType()` is Mocha-title-shaped and can only
25
+ * return 'unknown' or throw here.
26
+ */
27
+ private cucumberData;
28
+ /**
29
+ * Steps accumulated for the scenario in flight. Re-allocated (never cleared in place) at each
30
+ * scenario start so a payload already built from the previous scenario can never observe the
31
+ * next one's steps.
32
+ */
33
+ private scenarioSteps;
34
+ /** The hook currently open on this worker — started and not yet finished. */
35
+ private openHook;
36
+ constructor(testFrameworks: string[], testFrameworkVersions: Record<string, string>, binSessionId: string);
37
+ /**
38
+ * Feature bookkeeping. Raises no framework state — cucumber has no feature-level wire event,
39
+ * and `beforeSuite`/`afterSuite` are not part of its WDIO surface.
40
+ */
41
+ onFeatureStart(uri: string, feature: Feature): void;
42
+ /**
43
+ * Step bookkeeping. Steps travel inside the scenario payload's BDD meta, never as their own
44
+ * event — `TestFrameworkState.STEP` has zero producers anywhere in this SDK.
45
+ */
46
+ onStepStart(step: Frameworks.PickleStep): void;
47
+ onStepEnd(step: Frameworks.PickleStep, result: Frameworks.PickleResult): void;
48
+ /**
49
+ * Classify a cucumber hook invocation from the bookkeeping state.
50
+ *
51
+ * Returns null for a step-scoped hook (`BeforeStep`/`AfterStep`), which is never reported —
52
+ * reporting it would change the dashboard hook count, which is a feature and not parity.
53
+ */
54
+ classifyHookType(test: CucumberHook | undefined): CucumberHookType | null;
55
+ /** The TestFrameworkState a cucumber hook invocation maps to, or null when unreported. */
56
+ classifyHookState(test: CucumberHook | undefined): State | null;
57
+ /**
58
+ * `<HOOK_TYPE> for <scenario|feature name>` — the separator is a literal ' ' + 'for' + ' '.
59
+ */
60
+ private hookName;
61
+ private featurePath;
62
+ /**
63
+ * The feature path as the bdd meta blob wants it — cwd-relative, matching legacy.
64
+ *
65
+ * `cucumberData.uri` cannot be forwarded raw: WDIO hands `beforeFeature` an ABSOLUTE uri, so
66
+ * the raw value carries the developer's home directory. Legacy never sees that value — it
67
+ * reads the cucumber world's `gherkinDocument.uri`, which is cwd-relative.
68
+ */
69
+ private featureUriForMeta;
70
+ trackEvent(testFrameworkState: State, hookState: State, args?: Record<string, unknown>): Promise<void>;
71
+ /**
72
+ * One instance per scenario, keyed by the worker (`pid:threadId`) — the single lookup every
73
+ * `cli/modules/*` reads, via `TestFramework.getTrackedInstance()`. WDIO forks one worker
74
+ * process per spec file, so the worker key is the scenario's execution context; a thread id
75
+ * alone would collide once threads are reused.
76
+ */
77
+ private resolveInstance;
78
+ private trackWdioCucumberInstance;
79
+ /** Scenario identity. Each field matches what the legacy flow sent; the asymmetries are deliberate. */
80
+ private loadScenarioData;
81
+ /**
82
+ * `feature.path` is cwd-relative, matching legacy (`insights-handler` builds
83
+ * `feature = { path: gherkinDocument.uri, … }` off the cucumber world). It is NOT the absolute
84
+ * path used for `test_file_path`: the binary re-bases that one, but never touches this blob, so
85
+ * an absolute value here reaches the dashboard as-is, home directory and all.
86
+ */
87
+ private buildBddMetaInfo;
88
+ /**
89
+ * Scenario result, loaded at the real "scenario ends" state.
90
+ *
91
+ * KEY_TEST_RESULT_AT is load-bearing, not decoration: `testHubModule → onAllTestEvents()`
92
+ * treats a TEST/POST without it as result-less, marks the test deferred, and then waits for a
93
+ * `LOG_REPORT` POST to recover it — a state cucumber never emits.
94
+ */
95
+ private loadScenarioResult;
96
+ /**
97
+ * One detached instance per scenario the feature never ran, for the BEFORE_ALL cascade
98
+ * (Rule-nested included). Detached is load-bearing: not registered via `setTrackedInstance`,
99
+ * so the live instance and `process.env[TEST_ANALYTICS_ID]` are untouched, and the caller
100
+ * sends each straight to TestHub — routing them through the observers would rename the
101
+ * session, stop accessibility and run a Percy teardown per row. No tags: legacy's cascade
102
+ * payload has no `world`.
103
+ */
104
+ buildSkippedScenarioInstances(): TestFrameworkInstance[];
105
+ private buildSkippedScenarioInstance;
106
+ /**
107
+ * Whether the scenario in flight failed in a STEP, as opposed to failing only in a hook.
108
+ * Public because `ignoreHooksStatus` has two surfaces: the o11y result below, and the Automate
109
+ * session status, which `service.afterScenario()` derives from the same answer.
110
+ */
111
+ hasStepFailures(): boolean;
112
+ /**
113
+ * Route a console log to the row it belongs on: the open hook's uuid while a hook is in
114
+ * flight and unfinished, otherwise the scenario's uuid.
115
+ *
116
+ * The record itself always goes into KEY_TEST_LOGS, because the send path
117
+ * (`testHubModule → onAllTestEvents`, unchanged) collects test logs plus the last FINISHED
118
+ * hook's logs — a record parked on a still-open hook's own array would never be picked up.
119
+ * The routing is carried by KEY_HOOK_ID on the record, which `sendLogCreatedEvent` reads in
120
+ * preference to the test uuid.
121
+ */
122
+ private loadLogEntry;
123
+ /**
124
+ * Hook lifecycle. Entries are keyed by the short state name, matching how the binary looks
125
+ * them up via `event.test_hooks_started[request.testFrameworkState]`.
126
+ *
127
+ * A finish with no recorded start is dropped rather than emitted — an unmatched
128
+ * HookRunFinished orphans a hook row the backend cannot pair.
129
+ */
130
+ private trackHookEvents;
131
+ }
132
+ export {};
@@ -51,6 +51,26 @@ export declare class GrpcClient {
51
51
  * @private
52
52
  */
53
53
  stopBinSession(): Promise<unknown>;
54
+ /**
55
+ * Render end-of-build customer-visible summary entries.
56
+ *
57
+ * Per the binary proto contract (CustomerVisibleSummaryEntry in
58
+ * sdk-messages.proto): iterate by `severity` + `body`, write `body` verbatim,
59
+ * and pick the stream from `severity`. Never branches on `entryType`, so new
60
+ * entry types need no SDK change.
61
+ * @private
62
+ */
63
+ private renderCustomerVisibleSummary;
64
+ /**
65
+ * Tint a summary block by severity — yellow for warn (an outdated SDK), red for
66
+ * error (a deprecated one), untouched otherwise.
67
+ *
68
+ * Each line is wrapped and reset on its own rather than the block as a whole, so
69
+ * a truncated or interleaved write cannot leave the customer's terminal stuck in
70
+ * colour. The first non-blank, non-border line is emphasised.
71
+ * @private
72
+ */
73
+ private colouriseSummaryBody;
54
74
  testSessionEvent(data: Omit<TestSessionEventRequest, 'binSessionId'>): Promise<TestSessionEventResponse | undefined>;
55
75
  /**
56
76
  *
@@ -4,6 +4,7 @@ import type { StartBinSessionResponse } from '../grpc/index.js';
4
4
  import type BaseModule from './modules/baseModule.js';
5
5
  import type { Options } from '@wdio/types';
6
6
  import WdioMochaTestFramework from './frameworks/wdioMochaTestFramework.js';
7
+ import WdioCucumberTestFramework from './frameworks/wdioCucumberTestFramework.js';
7
8
  import WdioAutomationFramework from './frameworks/wdioAutomationFramework.js';
8
9
  import type { BrowserstackConfig, BrowserstackOptions } from '../types.js';
9
10
  /**
@@ -26,7 +27,7 @@ export declare class BrowserstackCLI {
26
27
  modulesLoaded: boolean;
27
28
  binSessionId: string | null;
28
29
  modules: Record<string, BaseModule>;
29
- testFramework: WdioMochaTestFramework | null;
30
+ testFramework: WdioMochaTestFramework | WdioCucumberTestFramework | null;
30
31
  cliParams: Record<string, string> | null;
31
32
  automationFramework: WdioAutomationFramework | null;
32
33
  SDK_CLI_BIN_PATH: string | null;
@@ -152,7 +153,7 @@ export declare class BrowserstackCLI {
152
153
  * Get the test framework
153
154
  * @returns {Object} The test framework
154
155
  */
155
- getTestFramework(): WdioMochaTestFramework | null;
156
+ getTestFramework(): WdioMochaTestFramework | WdioCucumberTestFramework | null;
156
157
  /**
157
158
  * Get the automation framework
158
159
  * @returns {Object} The automation framework
@@ -18,8 +18,39 @@ export default class AutomateModule extends BaseModule {
18
18
  */
19
19
  private flushSessionName;
20
20
  onAfterTest(args: Record<string, unknown>): Promise<void>;
21
+ /**
22
+ * A `BeforeAll` / `AfterAll` failure produces no scenario result, so it can never enter the
23
+ * per-test `testResults` map that onAfterExecute aggregates — a run whose BeforeAll blew up
24
+ * reports its session as PASSED. Legacy pushed the hook error into `_failReasons` and
25
+ * `after()` marked the session failed; that whole accumulation is gated
26
+ * `setSessionStatus && !BrowserstackCLI.isRunning()`, so it is dead while the binary is up.
27
+ *
28
+ * Cucumber-gated deliberately. `wdio_mocha` has the identical latent shape on this flow, but
29
+ * legacy mocha behaved the same way, so repairing it here would be an unrequested behaviour
30
+ * change to the one framework already working on the CLI flow.
31
+ */
32
+ onBuildLevelHookEnd(hookKey: string, args: Record<string, unknown>): Promise<void>;
33
+ private isCucumberInstance;
34
+ /** The hook's reported name (`BEFORE_ALL for <feature>`), so the session reason names the hook. */
35
+ private resolveHookName;
21
36
  onAfterExecute(): Promise<void>;
22
37
  private isAppAutomate;
38
+ private isTurboScale;
39
+ /**
40
+ * Resolve the REST endpoint a session marker must hit.
41
+ *
42
+ * Turboscale is not a variant of Automate here — it is a different API on a different path
43
+ * with a different VERB (PATCH, not PUT). The legacy path expressed this through
44
+ * `_sessionBaseUrl` + `_update()`, both gated `!BrowserstackCLI.isRunning()`, so neither
45
+ * survives onto the CLI flow and nothing in the binary compensates.
46
+ *
47
+ * Precedence mirrors legacy's assignment order in `beforeSession()`: the turboscale base URL
48
+ * is assigned AFTER the app-automate one, so a turboscale grid wins even with an app cap set.
49
+ *
50
+ * Single resolver for both markers deliberately: naming and status previously duplicated the
51
+ * ternary, which is how the two can drift apart.
52
+ */
53
+ private resolveSessionApi;
23
54
  markSessionName(sessionId: string, sessionName: string, config: {
24
55
  user: string;
25
56
  key: string;
@@ -28,6 +28,7 @@ export default class CustomTagsModule extends BaseModule {
28
28
  private pendingTestLevelTags;
29
29
  constructor();
30
30
  getModuleName(): string;
31
+ private isMochaFramework;
31
32
  onBeforeExecute(): Promise<void>;
32
33
  /**
33
34
  * At each test start (TEST/PRE — after beforeTest tracks the instance), flush any
@@ -146,6 +146,27 @@ export interface StopBinSessionResponse {
146
146
  error?: string | undefined;
147
147
  automateBuildlink?: string | undefined;
148
148
  hashedId?: string | undefined;
149
+ /**
150
+ * End-of-build customer-visible summary entries. Populated on EVERY response
151
+ * shape — success, error, and clean-build alike (empty when nothing to
152
+ * surface). Iterate by `severity` + `body`; do not branch on `entry_type`,
153
+ * so new entry types need no SDK change.
154
+ */
155
+ entries: CustomerVisibleSummaryEntry[];
156
+ }
157
+ /**
158
+ * A single customer-visible summary entry surfaced at end of build via
159
+ * StopBinSessionResponse.entries. The binary owns the prose so all SDKs render
160
+ * consistent text; SDKs choose the output stream from `severity`.
161
+ */
162
+ export interface CustomerVisibleSummaryEntry {
163
+ /** Stable machine-readable identifier (e.g. "network_restrictions"). */
164
+ entryType: string;
165
+ /** One of: "info" | "warn" | "error". */
166
+ severity: string;
167
+ /** Pre-formatted block to display verbatim. May contain embedded newlines. */
168
+ body: string;
169
+ docLink?: string | undefined;
149
170
  }
150
171
  export interface ConnectBinSessionRequest {
151
172
  binSessionId: string;
@@ -265,6 +286,7 @@ export declare const TestOrchestrationRequest: MessageFns<TestOrchestrationReque
265
286
  export declare const TestOrchestrationResponse: MessageFns<TestOrchestrationResponse>;
266
287
  export declare const StopBinSessionRequest: MessageFns<StopBinSessionRequest>;
267
288
  export declare const StopBinSessionResponse: MessageFns<StopBinSessionResponse>;
289
+ export declare const CustomerVisibleSummaryEntry: MessageFns<CustomerVisibleSummaryEntry>;
268
290
  export declare const ConnectBinSessionRequest: MessageFns<ConnectBinSessionRequest>;
269
291
  export declare const TestFrameworkEventRequest: MessageFns<TestFrameworkEventRequest>;
270
292
  export declare const ExecutionContext: MessageFns<ExecutionContext>;