@wdio/browserstack-service 9.39.4 → 9.39.6

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.
@@ -1,3 +1,4 @@
1
+ import type { Frameworks } from '@wdio/types';
1
2
  import type TrackedContext from '../instances/trackedContext.js';
2
3
  import TrackedInstance from '../instances/trackedInstance.js';
3
4
  import type TestFrameworkInstance from '../instances/testFrameworkInstance.js';
@@ -53,6 +54,18 @@ export default class TestFramework {
53
54
  */
54
55
  trackEvent(testFrameworkState: State, hookState: State, args?: unknown): void;
55
56
  /**
57
+ * Pin a test the reporter's `fail` is about to report to the test run it belongs to, before
58
+ * that report is queued.
59
+ * @param {Frameworks.Test} _test
60
+ */
61
+ pinFailedAttempt(_test: Frameworks.Test): void;
62
+ /**
63
+ * Wait for test finishes that are still being reported. A module calls this before it reads the
64
+ * final results of the run (session status, the last deferred test finish).
65
+ * @returns {Promise<void>}
66
+ */
67
+ settleTestFinishes(): Promise<void>;
68
+ /**
56
69
  * run test hooks
57
70
  * @param {TestFrameworkInstance} instance
58
71
  * @param {TestFrameworkState} testFrameworkState
@@ -4,6 +4,40 @@ import type { Frameworks } from '@wdio/types';
4
4
  export default class WdioMochaTestFramework extends TestFramework {
5
5
  static KEY_HOOK_LAST_STARTED: string;
6
6
  static KEY_HOOK_LAST_FINISHED: string;
7
+ /**
8
+ * SDK-7843: mocha test attempts that started and have not finished, keyed per attempt.
9
+ *
10
+ * wdio runs `afterTest` inside the test's own runnable, after the body. When a test hits
11
+ * mocha's timeout, mocha fails it (and emits `fail` to reporters) while the body and its
12
+ * `afterTest` are still pending; mocha then moves on, and with `bail` (or on the worker's last
13
+ * test) wdio runs `after()` before that `afterTest`. So a test's finish can arrive from the
14
+ * reporter's `fail`, from a late `afterTest` while another test holds the tracked-instance
15
+ * slot, or not at all before the session status is marked. Each attempt keeps the instance it
16
+ * started on, so its finish is reported against its own test run, once.
17
+ */
18
+ private openAttempts;
19
+ /**
20
+ * The latest attempt started under each `attemptKey`. The key holds only the immediate parent's
21
+ * title, so two tests in a worker can share it; the later one's TEST/PRE replaces the entry.
22
+ */
23
+ private latestAttemptByKey;
24
+ /**
25
+ * Attempts by the test's body, then by `attemptKey`. wdio hands beforeTest and afterTest
26
+ * separate copies of the mocha test (`{ ...context.test }`, with no reference to the runnable),
27
+ * but both carry its `fn`, so afterTest finds its own attempt even when another test with the
28
+ * same key has started since. Tests can share one body (`it(name, sharedFn)`), hence the key.
29
+ */
30
+ private attemptsByFn;
31
+ /** The attempt a source's LOG_REPORT/POST resolved; its TEST/POST passes the same `test` object. */
32
+ private resolvedFinishes;
33
+ /** Bail cascades owed by failures reported after mocha moved on; settleTestFinishes() runs them. */
34
+ private owedBailCascades;
35
+ /** settleTestFinishes() has run its cascades; anything later cascades inline. */
36
+ private settled;
37
+ /** One attempt of a test: mocha retries a test as a new runnable with `_currentRetry` + 1. */
38
+ static attemptKey(test: Frameworks.Test): string;
39
+ private static currentRetry;
40
+ private static testBody;
7
41
  /**
8
42
  * Constructor for the TestFramework
9
43
  * @param {Array} testFrameworks - List of Test frameworks
@@ -18,6 +52,73 @@ export default class WdioMochaTestFramework extends TestFramework {
18
52
  * @param {*} args
19
53
  */
20
54
  trackEvent(testFrameworkState: State, hookState: State, args?: Record<string, unknown>): Promise<void>;
55
+ /**
56
+ * Whether this failure will be retried, in which case mocha has not dropped anything yet and
57
+ * the tests after it are still going to run.
58
+ *
59
+ * `results.retries` only tracks wdio's spec-file retries — `@wdio/utils` builds it as
60
+ * `{ attempts: 0, limit: repeatTest }` and `@wdio/mocha-framework` never feeds `mochaOpts.retries`
61
+ * into it, so under mocha-level retries it stays `{0, 0}` and tells us nothing. Read mocha's own
62
+ * runnable state for that case, otherwise the cascade fires on the first attempt and reports
63
+ * tests as skipped that the retry then actually runs. The runnable is the one captured at
64
+ * TEST/PRE: once mocha fails a timed-out test it moves on, and the suite's shared
65
+ * `ctx.test` then points at a hook, which inherits the suite's `retries` (SDK-7843).
66
+ */
67
+ private hasRetryPending;
68
+ /**
69
+ * mocha's `bail` aborts the run on the first failure, so every test the spec had not reached
70
+ * yet is dropped without emitting any event and never appears on the dashboard. Report them
71
+ * as skipped — same cascade the failed-hook path uses, from the spec's root suite so sibling
72
+ * describes are covered too (bail kills the whole spec, not just the failing describe).
73
+ *
74
+ * The root can span more than one file when specs are grouped — `MochaAdapter` adds every spec
75
+ * it is handed to one mocha instance. Cascading across them is still correct: bail aborts that
76
+ * whole runner, so those tests do not run either.
77
+ *
78
+ * Runs as part of the failed test's finish when that comes from afterTest while mocha waits
79
+ * for it. When mocha has already moved on (the reporter's `fail`, or a timed-out test's late
80
+ * afterTest), settleTestFinishes() runs it instead: bail has already aborted the spec, and that
81
+ * still lands before the session status is marked and the last test finish is flushed
82
+ * (SDK-7843).
83
+ */
84
+ private reportBailSkippedTests;
85
+ /** TEST/PRE: this attempt's finish is owed from here on, against this instance. */
86
+ private openTestAttempt;
87
+ /**
88
+ * The reporter's `fail`, synchronously in mocha's `fail` event (SDK-7843): pin the failed test
89
+ * to the attempt mocha just failed, the latest with its key. The finish itself is queued (see
90
+ * skipReporter's enqueueReport) and may run after mocha has started the next test, which can
91
+ * share the key.
92
+ */
93
+ pinFailedAttempt(test: Frameworks.Test): void;
94
+ /**
95
+ * Whether mocha already failed this attempt. A failure it retries is not marked: mocha clones
96
+ * the test, emits `retry` instead of `fail` and leaves the attempt's `state` unset. It only
97
+ * retries a failure, though, so a later attempt of the same test having started means this
98
+ * one failed.
99
+ */
100
+ private mochaFailed;
101
+ /**
102
+ * The attempt a finish belongs to. wdio's afterTest carries the test's body, which identifies
103
+ * it exactly. The reporter's `fail` has only the title and parent: it is pinned (see
104
+ * pinFailedAttempt) to the latest attempt with that key, which is the one mocha just failed,
105
+ * so its queued LOG_REPORT and TEST/POST cannot land on a later same-named test.
106
+ */
107
+ private findTestAttempt;
108
+ /**
109
+ * LOG_REPORT/POST and TEST/POST of a mocha test: the attempt to report against. Returns
110
+ * undefined for any other event, and for a test this framework never saw start (resolved as
111
+ * before); null to drop the event, when the attempt is already finished or another source is
112
+ * finishing it.
113
+ */
114
+ private resolveTestAttempt;
115
+ /**
116
+ * Before the session status is marked and the last test finish is flushed: finish every attempt
117
+ * mocha already failed (or retried) that nothing reported (no reporter is registered when Test
118
+ * Reporting, Accessibility and Percy are all off), then run the bail cascades owed. Runs after
119
+ * drainSkipReports(), which waits for the finishes the reporter queued.
120
+ */
121
+ settleTestFinishes(): Promise<void>;
21
122
  /**
22
123
  * Resolve instance for the test framework
23
124
  * @param {TestFrameworkState} testFrameworkState
@@ -5,6 +5,7 @@ export default class AutomateModule extends BaseModule {
5
5
  logger: typeof BStackLogger;
6
6
  browserStackConfig: Options.Testrunner;
7
7
  private sessionMap;
8
+ private mochaTestsRan;
8
9
  static readonly MODULE_NAME = "AutomateModule";
9
10
  /**
10
11
  * Create a new AutomateModule
@@ -25,11 +26,17 @@ export default class AutomateModule extends BaseModule {
25
26
  * `after()` marked the session failed; that whole accumulation is gated
26
27
  * `setSessionStatus && !BrowserstackCLI.isRunning()`, so it is dead while the binary is up.
27
28
  *
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.
29
+ * Cucumber only — mocha hooks go through onMochaHookEnd.
31
30
  */
32
31
  onBuildLevelHookEnd(hookKey: string, args: Record<string, unknown>): Promise<void>;
32
+ /**
33
+ * A failed mocha hook produces no test result — the tests it aborts are reported skipped,
34
+ * which counts as passed — so the session was marked PASSED even though the run failed.
35
+ * Legacy fails the session on any hook error unless ignoreHooksStatus is set and a test ran;
36
+ * `mochaTestsRan` is only final at teardown, so the verdict is decided in onAfterExecute.
37
+ */
38
+ onMochaHookEnd(hookKey: string, args: Record<string, unknown>): Promise<void>;
39
+ private isMochaInstance;
33
40
  private isCucumberInstance;
34
41
  /** The hook's reported name (`BEFORE_ALL for <feature>`), so the session reason names the hook. */
35
42
  private resolveHookName;
@@ -1,5 +1,11 @@
1
1
  import type { Frameworks } from '@wdio/types';
2
2
  import type TestFramework from './frameworks/testFramework.js';
3
+ /**
4
+ * Queue a report on the one chain every report wdio may not await goes through, so reports never
5
+ * interleave on the tracker's per-worker tracked-instance slot. service.after() waits for the chain
6
+ * through drainSkipReports(). Also used by the reporter's `fail` (SDK-7843).
7
+ */
8
+ export declare function enqueueReport(work: () => Promise<void>, what: string): Promise<void>;
3
9
  export declare function markTestStarted(identifier: string): void;
4
10
  export declare function drainSkipReports(): Promise<void>;
5
11
  export declare function reportSkippedTest(framework: TestFramework, identifier: string, test: Frameworks.Test, suiteTitle?: string, options?: {
@@ -11,7 +17,8 @@ export declare function reportSkippedTest(framework: TestFramework, identifier:
11
17
  * suite — report each state-undefined test as skipped, recursing into nested describes.
12
18
  *
13
19
  * Reports IMMEDIATELY (SDK-7493): every caller of this — the failed-hook cascade in
14
- * `afterHook` and the bail cascade in `afterTest` — is awaited by wdio, so these reports
20
+ * `afterHook` and the bail cascade in `afterTest` (or in `after()`, for a failure reported after
21
+ * mocha moved on, SDK-7843) — is awaited by wdio, so these reports
15
22
  * cannot interleave with a live test the way the un-awaited `onTestSkip` path could. They
16
23
  * belong to the hook/test being reported, so they must not slide to end-of-run.
17
24
  */