@rstest/browser 0.11.6 → 0.11.8

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.
Files changed (88) hide show
  1. package/dist/browser-container/container-static/css/index.43daaf6c93.css +1 -0
  2. package/dist/browser-container/container-static/js/260.3739fe815c.js +194 -0
  3. package/dist/browser-container/container-static/js/index.c0f87bfb3e.js +1 -0
  4. package/dist/browser-container/index.html +1 -1
  5. package/dist/browserRsbuild.d.ts +24 -4
  6. package/dist/browserV8Coverage.d.ts +15 -0
  7. package/dist/client/240~1.js +150 -0
  8. package/dist/{augmentExpect.d.ts → client/client/augmentExpect.d.ts} +1 -1
  9. package/dist/client/{browserRpc.d.ts → client/browserRpc.d.ts} +1 -1
  10. package/dist/client/{dispatchTransport.d.ts → client/dispatchTransport.d.ts} +8 -4
  11. package/dist/client/client/index.d.ts +2 -0
  12. package/dist/client/client/runIdentity.d.ts +12 -0
  13. package/dist/client/client/runner.d.ts +4 -0
  14. package/dist/client/client/snapshot.d.ts +27 -0
  15. package/dist/client/client/sourceMapSupport.d.ts +36 -0
  16. package/dist/{browser.js → client/index.js} +3 -102
  17. package/dist/client/protocol.d.ts +225 -0
  18. package/dist/client/rpcProtocol.d.ts +138 -0
  19. package/dist/client/runner.js +675 -0
  20. package/dist/client/sourceMap/sourceMapLoader.d.ts +25 -0
  21. package/dist/containerRpc.d.ts +27 -19
  22. package/dist/headedRunRegistry.d.ts +51 -0
  23. package/dist/headedScheduler.d.ts +10 -10
  24. package/dist/headlessScheduler.d.ts +5 -4
  25. package/dist/headlessTransport.d.ts +4 -2
  26. package/dist/hostPayloads.d.ts +1 -7
  27. package/dist/index.js +840 -330
  28. package/dist/protocol.d.ts +48 -8
  29. package/dist/providers/index.d.ts +21 -0
  30. package/dist/providers/playwright/v8Coverage.d.ts +2 -0
  31. package/dist/rpcProtocol.d.ts +1 -2
  32. package/dist/schedulerSeam.d.ts +5 -0
  33. package/dist/sourceMap/sourceMapLoader.d.ts +12 -1
  34. package/dist/watchRerunPlanner.d.ts +20 -9
  35. package/dist/watchSignals.d.ts +8 -1
  36. package/package.json +11 -11
  37. package/dist/33.js +0 -7
  38. package/dist/browser-container/container-static/css/index.0521ff95b1.css +0 -1
  39. package/dist/browser-container/container-static/js/683.4f821ce8f1.js +0 -194
  40. package/dist/browser-container/container-static/js/index.568aa7cb35.js +0 -1
  41. package/dist/browser.d.ts +0 -2
  42. package/src/augmentExpect.ts +0 -62
  43. package/src/browser.ts +0 -3
  44. package/src/browserExecutor.ts +0 -222
  45. package/src/browserRpcRegistry.ts +0 -57
  46. package/src/browserRsbuild.ts +0 -1927
  47. package/src/client/api.ts +0 -213
  48. package/src/client/browserRpc.ts +0 -86
  49. package/src/client/dispatchTransport.ts +0 -240
  50. package/src/client/entry.ts +0 -807
  51. package/src/client/locator.ts +0 -448
  52. package/src/client/snapshot.ts +0 -113
  53. package/src/client/sourceMapSupport.ts +0 -159
  54. package/src/concurrency.ts +0 -65
  55. package/src/configValidation.ts +0 -263
  56. package/src/containerRpc.ts +0 -206
  57. package/src/dispatchCapabilities.ts +0 -177
  58. package/src/dispatchRouter.ts +0 -82
  59. package/src/env.d.ts +0 -54
  60. package/src/headedScheduler.ts +0 -664
  61. package/src/headedSerialTaskQueue.ts +0 -23
  62. package/src/headlessScheduler.ts +0 -566
  63. package/src/headlessTransport.ts +0 -28
  64. package/src/hostController.ts +0 -1365
  65. package/src/hostPayloads.ts +0 -62
  66. package/src/index.ts +0 -51
  67. package/src/manifest.d.ts +0 -41
  68. package/src/protocol.ts +0 -225
  69. package/src/providers/index.ts +0 -126
  70. package/src/providers/playwright/compileLocator.ts +0 -130
  71. package/src/providers/playwright/dispatchBrowserRpc.ts +0 -372
  72. package/src/providers/playwright/expectUtils.ts +0 -57
  73. package/src/providers/playwright/implementation.ts +0 -35
  74. package/src/providers/playwright/index.ts +0 -1
  75. package/src/providers/playwright/runtime.ts +0 -159
  76. package/src/providers/playwright/textMatcher.ts +0 -10
  77. package/src/rpcProtocol.ts +0 -218
  78. package/src/runSession.ts +0 -110
  79. package/src/schedulerSeam.ts +0 -49
  80. package/src/sessionRegistry.ts +0 -89
  81. package/src/sourceMap/sourceMapLoader.ts +0 -96
  82. package/src/viewportPresets.ts +0 -69
  83. package/src/watchRerunPlanner.ts +0 -88
  84. package/src/watchRuntime.ts +0 -83
  85. package/src/watchSignals.ts +0 -93
  86. /package/dist/{rslib-runtime.js → 612~0.js} +0 -0
  87. /package/dist/client/{api.d.ts → client/api.d.ts} +0 -0
  88. /package/dist/client/{locator.d.ts → client/locator.d.ts} +0 -0
@@ -1,65 +0,0 @@
1
- import {
2
- getNumCpus,
3
- parseWorkers,
4
- resolveWorkerCount,
5
- } from '@rstest/core/internal/browser';
6
- import type { Rstest } from '@rstest/core/internal/browser';
7
-
8
- // Re-export the shared worker primitives so existing consumers (and unit tests
9
- // importing from `../src/concurrency`) keep a stable import surface.
10
- export { getNumCpus, parseWorkers };
11
-
12
- // The browser headless path caps CPU-derived workers at 12 and halves that
13
- // capped base in watch; the shared `resolveWorkerCount` helper owns the
14
- // `maxWorkers` override and workload clamp.
15
- const DEFAULT_MAX_HEADLESS_WORKERS = 12;
16
-
17
- type HeadlessConcurrencyContext = Pick<Rstest, 'command'> & {
18
- normalizedConfig: {
19
- pool: {
20
- maxWorkers?: string | number;
21
- };
22
- };
23
- };
24
-
25
- const resolveHeadlessWorkerCount = ({
26
- command,
27
- maxWorkers,
28
- totalTasks,
29
- numCpus = getNumCpus(),
30
- }: {
31
- command: HeadlessConcurrencyContext['command'];
32
- maxWorkers?: string | number;
33
- totalTasks: number;
34
- numCpus?: number;
35
- }): number => {
36
- const base = Math.max(Math.min(DEFAULT_MAX_HEADLESS_WORKERS, numCpus - 1), 1);
37
- return resolveWorkerCount({
38
- command,
39
- maxWorkers,
40
- totalTasks,
41
- recommended: base,
42
- watchRecommended: Math.max(Math.floor(base / 2), 1),
43
- numCpus,
44
- });
45
- };
46
-
47
- export const resolveDefaultHeadlessWorkers = (
48
- command: HeadlessConcurrencyContext['command'],
49
- numCpus: number = getNumCpus(),
50
- ): number =>
51
- resolveHeadlessWorkerCount({
52
- command,
53
- totalTasks: Number.POSITIVE_INFINITY,
54
- numCpus,
55
- });
56
-
57
- export const getHeadlessConcurrency = (
58
- context: HeadlessConcurrencyContext,
59
- totalTests: number,
60
- ): number =>
61
- resolveHeadlessWorkerCount({
62
- command: context.command,
63
- maxWorkers: context.normalizedConfig.pool.maxWorkers,
64
- totalTasks: totalTests,
65
- });
@@ -1,263 +0,0 @@
1
- import {
2
- browserIgnoredRuntimeConfigKeys,
3
- color,
4
- logger,
5
- type RstestContext,
6
- type RuntimeConfig,
7
- } from '@rstest/core/internal/browser';
8
- import { resolveBrowserViewportPreset } from './viewportPresets';
9
-
10
- type BrowserProjectConfig =
11
- RstestContext['projects'][number]['normalizedConfig'];
12
-
13
- /**
14
- * Per-key warning for a browser-ignored RuntimeConfig field, keyed by the exact
15
- * RuntimeConfig keys the `executorCapabilities` table marks 'ignored-warn' /
16
- * 'stripped'. {@link reportUnsupportedBrowserOptions} iterates the table's
17
- * {@link browserIgnoredRuntimeConfigKeys} and looks each key up here, so the
18
- * warnings are genuinely table-driven.
19
- */
20
- const ignoredKeyWarnings: Partial<
21
- Record<
22
- keyof RuntimeConfig,
23
- {
24
- /** True when the project's value is non-default (worth warning about). */
25
- isNonDefault: (config: BrowserProjectConfig) => boolean;
26
- message: (config: BrowserProjectConfig) => string;
27
- /**
28
- * Warn only on browser-only runs. Global-only config (copied onto every
29
- * project) that a mixed repo may legitimately set for its node projects.
30
- */
31
- browserOnly?: boolean;
32
- }
33
- >
34
- > = {
35
- testEnvironment: {
36
- isNonDefault: (config) => config.testEnvironment.name !== 'node',
37
- message: (config) =>
38
- `Ignoring testEnvironment '${config.testEnvironment.name}' in browser ` +
39
- 'mode: the browser itself is the test environment.',
40
- },
41
- isolate: {
42
- isNonDefault: (config) => config.isolate === false,
43
- message: () =>
44
- 'Ignoring isolate: false in browser mode: each test file still runs in ' +
45
- 'a fresh context.',
46
- browserOnly: true,
47
- },
48
- federation: {
49
- isNonDefault: (config) => config.federation === true,
50
- message: () =>
51
- 'Ignoring federation in browser mode: it only applies to the Node runner.',
52
- browserOnly: true,
53
- },
54
- detectAsyncLeaks: {
55
- isNonDefault: (config) => config.detectAsyncLeaks === true,
56
- message: () =>
57
- 'Ignoring detectAsyncLeaks in browser mode: it relies on node ' +
58
- 'async_hooks.',
59
- },
60
- logHeapUsage: {
61
- isNonDefault: (config) => config.logHeapUsage === true,
62
- message: () => 'Ignoring logHeapUsage in browser mode.',
63
- },
64
- };
65
-
66
- /**
67
- * Browser-ignored keys handled outside the generic warn loop: `coverage` is
68
- * 'stripped' but gets the dedicated provider check in
69
- * {@link reportUnsupportedBrowserOptions} (a v8 hard error on browser-only
70
- * runs, a warning in mixed runs), not a plain "set → warn".
71
- */
72
- const speciallyHandledIgnoredKeys: (keyof RuntimeConfig)[] = ['coverage'];
73
-
74
- /**
75
- * Every browser-ignored RuntimeConfig key this module checks — the union of the
76
- * warn descriptors and the specially-handled keys. Exported for the lockstep
77
- * test that asserts it covers {@link browserIgnoredRuntimeConfigKeys}.
78
- */
79
- export const browserValidatedIgnoredKeys: (keyof RuntimeConfig)[] = [
80
- ...(Object.keys(ignoredKeyWarnings) as (keyof RuntimeConfig)[]),
81
- ...speciallyHandledIgnoredKeys,
82
- ];
83
-
84
- // Anti-#1389 lockstep (runs at module load): fail loudly if the
85
- // `executorCapabilities` table gains an 'ignored-warn' / 'stripped' browser key
86
- // that this validation does not cover, instead of it becoming a silent no-op.
87
- const assertIgnoredKeysCovered = (): void => {
88
- const covered = new Set<string>(browserValidatedIgnoredKeys);
89
- const uncovered = browserIgnoredRuntimeConfigKeys.filter(
90
- (key) => !covered.has(key),
91
- );
92
- if (uncovered.length > 0) {
93
- throw new Error(
94
- 'Browser config validation is out of sync with executorCapabilities: ' +
95
- `no check for ignored RuntimeConfig field(s): ${uncovered.join(', ')}. ` +
96
- 'Add a descriptor to `ignoredKeyWarnings` or `speciallyHandledIgnoredKeys`.',
97
- );
98
- }
99
- };
100
- assertIgnoredKeysCovered();
101
-
102
- const SUPPORTED_PROVIDERS = ['playwright'] as const;
103
-
104
- const isPlainObject = (value: unknown): value is Record<string, unknown> => {
105
- return Object.prototype.toString.call(value) === '[object Object]';
106
- };
107
-
108
- const validateViewport = (viewport: unknown): void => {
109
- if (viewport == null) {
110
- return;
111
- }
112
-
113
- if (typeof viewport === 'string') {
114
- const presetId = viewport.trim();
115
- if (!presetId) {
116
- throw new Error('browser.viewport must be a non-empty preset id.');
117
- }
118
- if (!resolveBrowserViewportPreset(presetId)) {
119
- throw new Error(
120
- `browser.viewport must be a valid preset id. Received: ${viewport}`,
121
- );
122
- }
123
- return;
124
- }
125
-
126
- if (isPlainObject(viewport)) {
127
- const width = (viewport as any).width;
128
- const height = (viewport as any).height;
129
- if (!Number.isFinite(width) || width <= 0) {
130
- throw new Error('browser.viewport.width must be a positive number.');
131
- }
132
- if (!Number.isFinite(height) || height <= 0) {
133
- throw new Error('browser.viewport.height must be a positive number.');
134
- }
135
- return;
136
- }
137
-
138
- throw new Error(
139
- 'browser.viewport must be either a preset id or { width, height }.',
140
- );
141
- };
142
-
143
- /**
144
- * Warn (or hard-error) on node-only config that is silently ignored under
145
- * `browser.enabled`. Prevents the #1389 class of silent no-ops. The per-key
146
- * warnings are driven by the `executorCapabilities` table: the loop iterates
147
- * {@link browserIgnoredRuntimeConfigKeys} and looks each key up in
148
- * {@link ignoredKeyWarnings}; `coverage` (and `pool`, which is not a
149
- * RuntimeConfig field) keep bespoke handling below.
150
- *
151
- * Scoping rules: `coverage` and `isolate` are global-only config copied onto
152
- * every project, so a v8 hard-error / isolate warning is gated to browser-only
153
- * runs — a mixed repo legitimately sets them for its node projects and must not
154
- * get unsilenceable noise on a correct configuration.
155
- */
156
- const reportUnsupportedBrowserOptions = (context: RstestContext): void => {
157
- const browserProjects = context.projects.filter(
158
- (project) => project.normalizedConfig.browser.enabled,
159
- );
160
- if (browserProjects.length === 0) {
161
- return;
162
- }
163
- const isBrowserOnlyRun = browserProjects.length === context.projects.length;
164
-
165
- // `coverage`, `pool` and `isolate` are global-only config (not settable per
166
- // project), read from the root normalized config.
167
- const globalConfig = context.normalizedConfig;
168
-
169
- const { coverage } = globalConfig;
170
- // `list` never collects coverage, so the v8 guard must not block listing.
171
- if (
172
- context.command !== 'list' &&
173
- coverage.enabled &&
174
- coverage.provider === 'v8'
175
- ) {
176
- if (isBrowserOnlyRun) {
177
- throw new Error(
178
- "Coverage provider 'v8' is not supported in browser mode: browser " +
179
- 'projects produce no v8 coverage. Use the default istanbul provider ' +
180
- "(coverage.provider: 'istanbul') for browser coverage.",
181
- );
182
- }
183
- logger.warn(
184
- color.yellow(
185
- "Coverage provider 'v8' produces no coverage for browser project " +
186
- "files; use the 'istanbul' provider to collect browser coverage. " +
187
- 'Node projects will still use v8.',
188
- ),
189
- );
190
- }
191
-
192
- // Node-only options silently ignored under browser.enabled. Collected into a
193
- // Set so each distinct warning is emitted once, not per project.
194
- const warnings = new Set<string>();
195
-
196
- if (globalConfig.pool.type !== 'forks') {
197
- warnings.add(
198
- `Ignoring pool.type '${globalConfig.pool.type}' in browser mode.`,
199
- );
200
- }
201
- if (globalConfig.pool.execArgv && globalConfig.pool.execArgv.length > 0) {
202
- warnings.add('Ignoring pool.execArgv in browser mode.');
203
- }
204
- // Table-driven warnings: for every browser-ignored RuntimeConfig key, warn
205
- // when a browser project sets it to a non-default value. `isolate` (and any
206
- // other global-only key) is copied onto every project, so reading it per
207
- // project matches the old global read; the Set dedupes the repeats.
208
- for (const project of browserProjects) {
209
- const config = project.normalizedConfig;
210
- for (const key of browserIgnoredRuntimeConfigKeys) {
211
- const descriptor = ignoredKeyWarnings[key];
212
- if (!descriptor) {
213
- // Specially handled elsewhere (e.g. `coverage`).
214
- continue;
215
- }
216
- if (descriptor.browserOnly && !isBrowserOnlyRun) {
217
- continue;
218
- }
219
- if (descriptor.isNonDefault(config)) {
220
- warnings.add(descriptor.message(config));
221
- }
222
- }
223
- }
224
-
225
- for (const message of warnings) {
226
- logger.warn(color.yellow(message));
227
- }
228
- };
229
-
230
- export const validateBrowserConfig = (context: RstestContext): void => {
231
- for (const project of context.projects) {
232
- const { browser, output } = project.normalizedConfig;
233
- if (!browser.enabled) {
234
- continue;
235
- }
236
-
237
- if (!browser.provider) {
238
- throw new Error(
239
- 'browser.provider is required when browser.enabled is true.',
240
- );
241
- }
242
-
243
- if (!SUPPORTED_PROVIDERS.includes(browser.provider)) {
244
- throw new Error(
245
- `browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`,
246
- );
247
- }
248
-
249
- validateViewport(browser.viewport);
250
-
251
- if (!isPlainObject(browser.providerOptions)) {
252
- throw new Error('browser.providerOptions must be a plain object.');
253
- }
254
-
255
- if (output?.bundleDependencies === false) {
256
- throw new Error(
257
- 'output.bundleDependencies false is not supported in browser mode.',
258
- );
259
- }
260
- }
261
-
262
- reportUnsupportedBrowserOptions(context);
263
- };
@@ -1,206 +0,0 @@
1
- import { logger, type TestResult } from '@rstest/core/internal/browser';
2
- import { type BirpcReturn, createBirpc } from 'birpc';
3
- import { type WebSocket, WebSocketServer } from 'ws';
4
- import type {
5
- BrowserDispatchRequest,
6
- BrowserDispatchResponse,
7
- BrowserHostConfig,
8
- TestFileInfo,
9
- } from './protocol';
10
- import type {
11
- FatalPayload,
12
- HeadedTestFileCompletePayload,
13
- LogPayload,
14
- ReloadTestFileAck,
15
- TestFileStartPayload,
16
- } from './hostPayloads';
17
-
18
- /** RPC methods exposed by the host (server) to the container (client) */
19
- export type HostRpcMethods = {
20
- rerunTest: (testFile: string, testNamePattern?: string) => Promise<void>;
21
- getTestFiles: () => Promise<TestFileInfo[]>;
22
- onRunnerFramesReady: (testFiles: string[]) => Promise<void>;
23
- // Test result callbacks from container
24
- onTestFileStart: (payload: TestFileStartPayload) => Promise<void>;
25
- onTestCaseResult: (payload: TestResult) => Promise<void>;
26
- onTestFileComplete: (payload: HeadedTestFileCompletePayload) => Promise<void>;
27
- onLog: (payload: LogPayload) => Promise<void>;
28
- onFatal: (payload: FatalPayload) => Promise<void>;
29
- // Generic dispatch endpoint used by runner RPC requests.
30
- dispatch: (
31
- request: BrowserDispatchRequest,
32
- ) => Promise<BrowserDispatchResponse>;
33
- };
34
-
35
- /** RPC methods exposed by the container (client) to the host (server) */
36
- export type ContainerRpcMethods = {
37
- onTestFileUpdate: (testFiles: TestFileInfo[]) => Promise<void>;
38
- reloadTestFile: (
39
- testFile: string,
40
- testNamePattern?: string,
41
- ) => Promise<ReloadTestFileAck>;
42
- /**
43
- * Replace the container's copy of the host config so runner iframes loaded
44
- * from now on receive fresh values (e.g. the 'u' shortcut flipping
45
- * `snapshot.updateSnapshot` between watch reruns).
46
- */
47
- onHostConfigUpdate: (config: BrowserHostConfig) => Promise<void>;
48
- };
49
-
50
- export type ContainerRpc = BirpcReturn<ContainerRpcMethods, HostRpcMethods>;
51
-
52
- // ============================================================================
53
- // RPC Manager - Encapsulates WebSocket and birpc management
54
- // ============================================================================
55
-
56
- /**
57
- * Manages the WebSocket connection and birpc communication with the container UI.
58
- * Provides a clean interface for sending RPC calls and handling connections.
59
- */
60
- export class ContainerRpcManager {
61
- private wss: WebSocketServer;
62
- private ws: WebSocket | null = null;
63
- private rpc: ContainerRpc | null = null;
64
- private methods: HostRpcMethods;
65
- private onDisconnect?: (error: Error) => void;
66
- private detachActiveSocketListeners: (() => void) | null = null;
67
-
68
- constructor(
69
- wss: WebSocketServer,
70
- methods: HostRpcMethods,
71
- onDisconnect?: (error: Error) => void,
72
- ) {
73
- this.wss = wss;
74
- this.methods = methods;
75
- this.onDisconnect = onDisconnect;
76
- this.setupConnectionHandler();
77
- }
78
-
79
- /** Update the RPC methods (used when starting a new test run) */
80
- updateMethods(
81
- methods: HostRpcMethods,
82
- onDisconnect?: (error: Error) => void,
83
- ): void {
84
- this.methods = methods;
85
- this.onDisconnect = onDisconnect;
86
- // Re-create birpc with new methods if already connected
87
- if (this.ws && this.ws.readyState === this.ws.OPEN) {
88
- this.attachWebSocket(this.ws);
89
- }
90
- }
91
-
92
- private setupConnectionHandler(): void {
93
- this.wss.on('connection', (ws: WebSocket) => {
94
- logger.debug('[Browser UI] Container WebSocket connected');
95
- logger.debug(
96
- `[Browser UI] Current ws: ${this.ws ? 'exists' : 'null'}, new ws: ${ws ? 'exists' : 'null'}`,
97
- );
98
- this.attachWebSocket(ws);
99
- });
100
- }
101
-
102
- private attachWebSocket(ws: WebSocket): void {
103
- this.detachActiveSocketListeners?.();
104
- if (this.rpc && !this.rpc.$closed) {
105
- this.rpc.$close(new Error('Container RPC transport reattached'));
106
- }
107
- this.ws = ws;
108
- const messageHandlers = new WeakMap<
109
- (data: any) => void,
110
- (message: any) => void
111
- >();
112
-
113
- this.rpc = createBirpc<ContainerRpcMethods, HostRpcMethods>(this.methods, {
114
- timeout: -1,
115
- post: (data) => {
116
- if (ws.readyState === ws.OPEN) {
117
- ws.send(JSON.stringify(data));
118
- }
119
- },
120
- on: (fn) => {
121
- const handler = (message: any) => {
122
- try {
123
- const data = JSON.parse(message.toString());
124
- fn(data);
125
- } catch {
126
- // ignore invalid messages
127
- }
128
- };
129
- messageHandlers.set(fn, handler);
130
- ws.on('message', handler);
131
- },
132
- off: (fn) => {
133
- const handler = messageHandlers.get(fn);
134
- if (!handler) {
135
- return;
136
- }
137
- ws.off('message', handler);
138
- messageHandlers.delete(fn);
139
- },
140
- });
141
-
142
- const handleClose = () => {
143
- // Only clear if this is still the active connection
144
- // This prevents a race condition when a new connection is established
145
- // before the old one's close event fires
146
- if (this.ws === ws) {
147
- this.ws = null;
148
- }
149
- this.detachActiveSocketListeners?.();
150
- this.detachActiveSocketListeners = null;
151
- if (this.rpc && !this.rpc.$closed) {
152
- const disconnectError = new Error(
153
- 'Browser UI WebSocket disconnected before reload completed',
154
- );
155
- this.rpc.$close(disconnectError);
156
- this.onDisconnect?.(disconnectError);
157
- }
158
- this.rpc = null;
159
- };
160
-
161
- ws.on('close', handleClose);
162
- this.detachActiveSocketListeners = () => {
163
- ws.off('close', handleClose);
164
- };
165
- }
166
-
167
- /** Check if a container is currently connected */
168
- get isConnected(): boolean {
169
- return this.ws !== null && this.ws.readyState === this.ws.OPEN;
170
- }
171
-
172
- /** Get the current WebSocket instance (for reuse in watch mode) */
173
- get currentWebSocket(): WebSocket | null {
174
- return this.ws;
175
- }
176
-
177
- /** Reattach an existing WebSocket (for watch mode reuse) */
178
- reattach(ws: WebSocket): void {
179
- this.attachWebSocket(ws);
180
- }
181
-
182
- /** Notify container of test file changes */
183
- async notifyTestFileUpdate(files: TestFileInfo[]): Promise<void> {
184
- await this.rpc?.onTestFileUpdate(files);
185
- }
186
-
187
- /** Push a refreshed host config to the container (watch reruns) */
188
- async updateHostConfig(config: BrowserHostConfig): Promise<void> {
189
- await this.rpc?.onHostConfigUpdate(config);
190
- }
191
-
192
- /** Request container to reload a specific test file */
193
- async reloadTestFile(
194
- testFile: string,
195
- testNamePattern?: string,
196
- ): Promise<ReloadTestFileAck> {
197
- logger.debug(
198
- `[Browser UI] reloadTestFile called, rpc: ${this.rpc ? 'exists' : 'null'}, ws: ${this.ws ? 'exists' : 'null'}`,
199
- );
200
- if (!this.rpc) {
201
- throw new Error('Browser UI RPC not available for reloadTestFile');
202
- }
203
- logger.debug(`[Browser UI] Calling reloadTestFile: ${testFile}`);
204
- return this.rpc.reloadTestFile(testFile, testNamePattern);
205
- }
206
- }