@wdio/browserstack-service 9.36.2 → 9.38.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 {};
@@ -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