@rstest/browser 0.9.2 → 0.9.3

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.
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import type { IncomingMessage, ServerResponse } from 'node:http';
4
4
  import type { AddressInfo } from 'node:net';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { isDeepStrictEqual } from 'node:util';
6
7
  import type { Rspack } from '@rstest/core';
7
8
  import {
8
9
  type BrowserTestRunOptions,
@@ -30,7 +31,7 @@ import {
30
31
  import { type BirpcReturn, createBirpc } from 'birpc';
31
32
  import openEditor from 'open-editor';
32
33
  import { basename, dirname, join, normalize, relative, resolve } from 'pathe';
33
- import * as picomatch from 'picomatch';
34
+ import picomatch from 'picomatch';
34
35
  import sirv from 'sirv';
35
36
  import { type WebSocket, WebSocketServer } from 'ws';
36
37
  import { getHeadlessConcurrency } from './concurrency';
@@ -123,10 +124,24 @@ type BrowserProviderProject = {
123
124
  provider: BrowserProvider;
124
125
  };
125
126
 
126
- type BrowserLaunchOptions = Pick<
127
- ProjectContext['normalizedConfig']['browser'],
128
- 'provider' | 'browser' | 'headless' | 'port' | 'strictPort'
129
- >;
127
+ type BrowserLaunchOptions = {
128
+ provider: BrowserProvider;
129
+ browser: ProjectContext['normalizedConfig']['browser']['browser'];
130
+ headless: ProjectContext['normalizedConfig']['browser']['headless'];
131
+ port: ProjectContext['normalizedConfig']['browser']['port'];
132
+ strictPort: ProjectContext['normalizedConfig']['browser']['strictPort'];
133
+ providerOptions: Record<string, unknown>;
134
+ };
135
+
136
+ const getBrowserProviderOptions = (
137
+ project: ProjectContext,
138
+ ): Record<string, unknown> => {
139
+ const browserConfig = project.normalizedConfig.browser as {
140
+ providerOptions?: Record<string, unknown>;
141
+ };
142
+
143
+ return browserConfig.providerOptions ?? {};
144
+ };
130
145
 
131
146
  /** Payload for test file start event */
132
147
  type TestFileStartPayload = {
@@ -157,6 +172,33 @@ type TestFileReadyPayload = ReporterHookArg<'onTestFileReady'>;
157
172
  type TestSuiteStartPayload = ReporterHookArg<'onTestSuiteStart'>;
158
173
  type TestSuiteResultPayload = ReporterHookArg<'onTestSuiteResult'>;
159
174
  type TestCaseStartPayload = ReporterHookArg<'onTestCaseStart'>;
175
+ type ReloadTestFileAck = {
176
+ runId: string;
177
+ };
178
+ type HeadedTestFileCompletePayload = TestFileResult & {
179
+ runId?: string;
180
+ };
181
+
182
+ type DeferredPromise<T> = {
183
+ promise: Promise<T>;
184
+ resolve: (value: T | PromiseLike<T>) => void;
185
+ reject: (reason?: unknown) => void;
186
+ };
187
+
188
+ const createDeferredPromise = <T>(): DeferredPromise<T> => {
189
+ let resolve!: DeferredPromise<T>['resolve'];
190
+ let reject!: DeferredPromise<T>['reject'];
191
+ const promise = new Promise<T>((res, rej) => {
192
+ resolve = res;
193
+ reject = rej;
194
+ });
195
+
196
+ return {
197
+ promise,
198
+ resolve,
199
+ reject,
200
+ };
201
+ };
160
202
 
161
203
  /** RPC methods exposed by the host (server) to the container (client) */
162
204
  type HostRpcMethods = {
@@ -166,7 +208,7 @@ type HostRpcMethods = {
166
208
  // Test result callbacks from container
167
209
  onTestFileStart: (payload: TestFileStartPayload) => Promise<void>;
168
210
  onTestCaseResult: (payload: TestResult) => Promise<void>;
169
- onTestFileComplete: (payload: TestFileResult) => Promise<void>;
211
+ onTestFileComplete: (payload: HeadedTestFileCompletePayload) => Promise<void>;
170
212
  onLog: (payload: LogPayload) => Promise<void>;
171
213
  onFatal: (payload: FatalPayload) => Promise<void>;
172
214
  // Generic dispatch endpoint used by runner RPC requests.
@@ -178,7 +220,10 @@ type HostRpcMethods = {
178
220
  /** RPC methods exposed by the container (client) to the host (server) */
179
221
  type ContainerRpcMethods = {
180
222
  onTestFileUpdate: (testFiles: TestFileInfo[]) => Promise<void>;
181
- reloadTestFile: (testFile: string, testNamePattern?: string) => Promise<void>;
223
+ reloadTestFile: (
224
+ testFile: string,
225
+ testNamePattern?: string,
226
+ ) => Promise<ReloadTestFileAck>;
182
227
  };
183
228
 
184
229
  type ContainerRpc = BirpcReturn<ContainerRpcMethods, HostRpcMethods>;
@@ -196,16 +241,27 @@ class ContainerRpcManager {
196
241
  private ws: WebSocket | null = null;
197
242
  private rpc: ContainerRpc | null = null;
198
243
  private methods: HostRpcMethods;
244
+ private onDisconnect?: (error: Error) => void;
245
+ private detachActiveSocketListeners: (() => void) | null = null;
199
246
 
200
- constructor(wss: WebSocketServer, methods: HostRpcMethods) {
247
+ constructor(
248
+ wss: WebSocketServer,
249
+ methods: HostRpcMethods,
250
+ onDisconnect?: (error: Error) => void,
251
+ ) {
201
252
  this.wss = wss;
202
253
  this.methods = methods;
254
+ this.onDisconnect = onDisconnect;
203
255
  this.setupConnectionHandler();
204
256
  }
205
257
 
206
258
  /** Update the RPC methods (used when starting a new test run) */
207
- updateMethods(methods: HostRpcMethods): void {
259
+ updateMethods(
260
+ methods: HostRpcMethods,
261
+ onDisconnect?: (error: Error) => void,
262
+ ): void {
208
263
  this.methods = methods;
264
+ this.onDisconnect = onDisconnect;
209
265
  // Re-create birpc with new methods if already connected
210
266
  if (this.ws && this.ws.readyState === this.ws.OPEN) {
211
267
  this.attachWebSocket(this.ws);
@@ -223,35 +279,68 @@ class ContainerRpcManager {
223
279
  }
224
280
 
225
281
  private attachWebSocket(ws: WebSocket): void {
282
+ this.detachActiveSocketListeners?.();
283
+ if (this.rpc && !this.rpc.$closed) {
284
+ this.rpc.$close(new Error('Container RPC transport reattached'));
285
+ }
226
286
  this.ws = ws;
287
+ const messageHandlers = new WeakMap<
288
+ (data: any) => void,
289
+ (message: any) => void
290
+ >();
227
291
 
228
292
  this.rpc = createBirpc<ContainerRpcMethods, HostRpcMethods>(this.methods, {
293
+ timeout: -1,
229
294
  post: (data) => {
230
295
  if (ws.readyState === ws.OPEN) {
231
296
  ws.send(JSON.stringify(data));
232
297
  }
233
298
  },
234
299
  on: (fn) => {
235
- ws.on('message', (message) => {
300
+ const handler = (message: any) => {
236
301
  try {
237
302
  const data = JSON.parse(message.toString());
238
303
  fn(data);
239
304
  } catch {
240
305
  // ignore invalid messages
241
306
  }
242
- });
307
+ };
308
+ messageHandlers.set(fn, handler);
309
+ ws.on('message', handler);
310
+ },
311
+ off: (fn) => {
312
+ const handler = messageHandlers.get(fn);
313
+ if (!handler) {
314
+ return;
315
+ }
316
+ ws.off('message', handler);
317
+ messageHandlers.delete(fn);
243
318
  },
244
319
  });
245
320
 
246
- ws.on('close', () => {
321
+ const handleClose = () => {
247
322
  // Only clear if this is still the active connection
248
323
  // This prevents a race condition when a new connection is established
249
324
  // before the old one's close event fires
250
325
  if (this.ws === ws) {
251
326
  this.ws = null;
252
- this.rpc = null;
253
327
  }
254
- });
328
+ this.detachActiveSocketListeners?.();
329
+ this.detachActiveSocketListeners = null;
330
+ if (this.rpc && !this.rpc.$closed) {
331
+ const disconnectError = new Error(
332
+ 'Browser UI WebSocket disconnected before reload completed',
333
+ );
334
+ this.rpc.$close(disconnectError);
335
+ this.onDisconnect?.(disconnectError);
336
+ }
337
+ this.rpc = null;
338
+ };
339
+
340
+ ws.on('close', handleClose);
341
+ this.detachActiveSocketListeners = () => {
342
+ ws.off('close', handleClose);
343
+ };
255
344
  }
256
345
 
257
346
  /** Check if a container is currently connected */
@@ -278,16 +367,15 @@ class ContainerRpcManager {
278
367
  async reloadTestFile(
279
368
  testFile: string,
280
369
  testNamePattern?: string,
281
- ): Promise<void> {
370
+ ): Promise<ReloadTestFileAck> {
282
371
  logger.debug(
283
372
  `[Browser UI] reloadTestFile called, rpc: ${this.rpc ? 'exists' : 'null'}, ws: ${this.ws ? 'exists' : 'null'}`,
284
373
  );
285
374
  if (!this.rpc) {
286
- logger.debug('[Browser UI] RPC not available, skipping reloadTestFile');
287
- return;
375
+ throw new Error('Browser UI RPC not available for reloadTestFile');
288
376
  }
289
377
  logger.debug(`[Browser UI] Calling reloadTestFile: ${testFile}`);
290
- await this.rpc.reloadTestFile(testFile, testNamePattern);
378
+ return this.rpc.reloadTestFile(testFile, testNamePattern);
291
379
  }
292
380
  }
293
381
 
@@ -299,6 +387,7 @@ type BrowserRuntime = {
299
387
  rsbuildInstance: RsbuildInstance;
300
388
  devServer: RsbuildDevServer;
301
389
  browser: BrowserProviderBrowser;
390
+ browserLaunchOptions: BrowserLaunchOptions;
302
391
  port: number;
303
392
  wsPort: number;
304
393
  manifestPath: string;
@@ -724,6 +813,7 @@ const getBrowserLaunchOptions = (
724
813
  headless: project.normalizedConfig.browser.headless,
725
814
  port: project.normalizedConfig.browser.port,
726
815
  strictPort: project.normalizedConfig.browser.strictPort,
816
+ providerOptions: getBrowserProviderOptions(project),
727
817
  });
728
818
 
729
819
  const ensureConsistentBrowserLaunchOptions = (
@@ -743,11 +833,12 @@ const ensureConsistentBrowserLaunchOptions = (
743
833
  options.browser !== firstOptions.browser ||
744
834
  options.headless !== firstOptions.headless ||
745
835
  options.port !== firstOptions.port ||
746
- options.strictPort !== firstOptions.strictPort
836
+ options.strictPort !== firstOptions.strictPort ||
837
+ !isDeepStrictEqual(options.providerOptions, firstOptions.providerOptions)
747
838
  ) {
748
839
  throw new Error(
749
840
  `Browser launch config mismatch between projects "${firstProject.name}" and "${project.name}". ` +
750
- 'All browser-enabled projects in one run must share provider/browser/headless/port/strictPort.',
841
+ 'All browser-enabled projects in one run must share provider/browser/headless/port/strictPort/providerOptions.',
751
842
  );
752
843
  }
753
844
  }
@@ -1469,11 +1560,13 @@ const createBrowserRuntime = async ({
1469
1560
  const runtime = await providerImplementation.launchRuntime({
1470
1561
  browserName,
1471
1562
  headless: forceHeadless ?? browserLaunchOptions.headless,
1563
+ providerOptions: browserLaunchOptions.providerOptions,
1472
1564
  });
1473
1565
  return {
1474
1566
  rsbuildInstance,
1475
1567
  devServer,
1476
1568
  browser: runtime.browser,
1569
+ browserLaunchOptions,
1477
1570
  port,
1478
1571
  wsPort,
1479
1572
  manifestPath,
@@ -1802,7 +1895,7 @@ export const runBrowserController = async (
1802
1895
  }
1803
1896
  }
1804
1897
 
1805
- const { browser, port, wsPort, wss } = runtime;
1898
+ const { browser, browserLaunchOptions, port, wsPort, wss } = runtime;
1806
1899
  const buildTime = Date.now() - buildStart;
1807
1900
 
1808
1901
  // Collect all test files from project entries with project info
@@ -2208,6 +2301,7 @@ export const runBrowserController = async (
2208
2301
 
2209
2302
  const viewport = viewportByProject.get(file.projectName);
2210
2303
  const browserContext = await browser.newContext({
2304
+ providerOptions: browserLaunchOptions.providerOptions,
2211
2305
  viewport: viewport ?? null,
2212
2306
  });
2213
2307
  run.contexts.add(browserContext);
@@ -2673,6 +2767,7 @@ export const runBrowserController = async (
2673
2767
  } else {
2674
2768
  isNewPage = true;
2675
2769
  containerContext = await browser.newContext({
2770
+ providerOptions: browserLaunchOptions.providerOptions,
2676
2771
  viewport: null,
2677
2772
  });
2678
2773
  containerPage = await containerContext.newPage();
@@ -2706,6 +2801,13 @@ export const runBrowserController = async (
2706
2801
 
2707
2802
  const dispatchRouter = createDispatchRouter();
2708
2803
  const headedReloadQueue = createHeadedSerialTaskQueue();
2804
+ const pendingHeadedReloads = new Map<
2805
+ string,
2806
+ {
2807
+ runId: string;
2808
+ deferred: DeferredPromise<void>;
2809
+ }
2810
+ >();
2709
2811
  let enqueueHeadedReload = async (
2710
2812
  _file: TestFileInfo,
2711
2813
  _testNamePattern?: string,
@@ -2713,16 +2815,89 @@ export const runBrowserController = async (
2713
2815
  throw new Error('Headed reload queue is not initialized');
2714
2816
  };
2715
2817
 
2818
+ const rejectPendingHeadedReload = (
2819
+ testPath: string,
2820
+ error: Error,
2821
+ runId?: string,
2822
+ ): void => {
2823
+ const pending = pendingHeadedReloads.get(testPath);
2824
+ if (!pending) {
2825
+ return;
2826
+ }
2827
+ if (runId && pending.runId !== runId) {
2828
+ return;
2829
+ }
2830
+ pendingHeadedReloads.delete(testPath);
2831
+ pending.deferred.reject(error);
2832
+ };
2833
+
2834
+ const rejectAllPendingHeadedReloads = (error: Error): void => {
2835
+ for (const [testPath, pending] of pendingHeadedReloads) {
2836
+ pendingHeadedReloads.delete(testPath);
2837
+ pending.deferred.reject(error);
2838
+ }
2839
+ };
2840
+
2841
+ const registerPendingHeadedReload = (
2842
+ testPath: string,
2843
+ runId: string,
2844
+ ): Promise<void> => {
2845
+ const previousPending = pendingHeadedReloads.get(testPath);
2846
+ if (previousPending) {
2847
+ previousPending.deferred.reject(
2848
+ new Error(
2849
+ `Reload for "${testPath}" was superseded by a newer request.`,
2850
+ ),
2851
+ );
2852
+ pendingHeadedReloads.delete(testPath);
2853
+ }
2854
+
2855
+ const deferred = createDeferredPromise<void>();
2856
+ pendingHeadedReloads.set(testPath, {
2857
+ runId,
2858
+ deferred,
2859
+ });
2860
+
2861
+ return deferred.promise;
2862
+ };
2863
+
2864
+ const resolvePendingHeadedReload = (
2865
+ testPath: string,
2866
+ runId?: string,
2867
+ ): void => {
2868
+ const pending = pendingHeadedReloads.get(testPath);
2869
+ if (!pending) {
2870
+ return;
2871
+ }
2872
+ if (runId && pending.runId !== runId) {
2873
+ logger.debug(
2874
+ `[Browser UI] Ignoring stale file-complete for ${testPath}. current=${pending.runId}, incoming=${runId}`,
2875
+ );
2876
+ return;
2877
+ }
2878
+ pendingHeadedReloads.delete(testPath);
2879
+ pending.deferred.resolve();
2880
+ };
2881
+
2716
2882
  const reloadTestFileWithTimeout = async (
2717
2883
  file: TestFileInfo,
2718
2884
  testNamePattern?: string,
2719
2885
  ): Promise<void> => {
2720
2886
  const timeoutMs = getHeadedPerFileTimeoutMs(file);
2721
2887
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
2888
+ let reloadAck: ReloadTestFileAck | undefined;
2722
2889
 
2723
2890
  try {
2891
+ reloadAck = await rpcManager.reloadTestFile(
2892
+ file.testPath,
2893
+ testNamePattern,
2894
+ );
2895
+ const completionPromise = registerPendingHeadedReload(
2896
+ file.testPath,
2897
+ reloadAck.runId,
2898
+ );
2724
2899
  await Promise.race([
2725
- rpcManager.reloadTestFile(file.testPath, testNamePattern),
2900
+ completionPromise,
2726
2901
  new Promise<never>((_, reject) => {
2727
2902
  timeoutId = setTimeout(() => {
2728
2903
  reject(
@@ -2733,6 +2908,15 @@ export const runBrowserController = async (
2733
2908
  }, timeoutMs);
2734
2909
  }),
2735
2910
  ]);
2911
+ } catch (error) {
2912
+ if (reloadAck?.runId) {
2913
+ rejectPendingHeadedReload(
2914
+ file.testPath,
2915
+ toError(error),
2916
+ reloadAck.runId,
2917
+ );
2918
+ }
2919
+ throw error;
2736
2920
  } finally {
2737
2921
  if (timeoutId) {
2738
2922
  clearTimeout(timeoutId);
@@ -2765,13 +2949,26 @@ export const runBrowserController = async (
2765
2949
  async onTestCaseResult(payload: TestResult) {
2766
2950
  await handleTestCaseResult(payload);
2767
2951
  },
2768
- async onTestFileComplete(payload: TestFileResult) {
2769
- await handleTestFileComplete(payload);
2952
+ async onTestFileComplete(payload: HeadedTestFileCompletePayload) {
2953
+ try {
2954
+ await handleTestFileComplete(payload);
2955
+ resolvePendingHeadedReload(payload.testPath, payload.runId);
2956
+ } catch (error) {
2957
+ rejectPendingHeadedReload(
2958
+ payload.testPath,
2959
+ toError(error),
2960
+ payload.runId,
2961
+ );
2962
+ throw error;
2963
+ }
2770
2964
  },
2771
2965
  async onLog(payload: LogPayload) {
2772
2966
  await handleLog(payload);
2773
2967
  },
2774
2968
  async onFatal(payload: FatalPayload) {
2969
+ const error = new Error(payload.message);
2970
+ error.stack = payload.stack;
2971
+ rejectAllPendingHeadedReloads(error);
2775
2972
  await handleFatal(payload);
2776
2973
  },
2777
2974
  async dispatch(request: BrowserDispatchRequest) {
@@ -2786,14 +2983,18 @@ export const runBrowserController = async (
2786
2983
  if (isWatchMode && runtime.rpcManager) {
2787
2984
  rpcManager = runtime.rpcManager;
2788
2985
  // Update methods with new test state (caseResults, completedTests, etc.)
2789
- rpcManager.updateMethods(createRpcMethods());
2986
+ rpcManager.updateMethods(createRpcMethods(), rejectAllPendingHeadedReloads);
2790
2987
  // Reattach if we have an existing WebSocket
2791
2988
  const existingWs = rpcManager.currentWebSocket;
2792
2989
  if (existingWs) {
2793
2990
  rpcManager.reattach(existingWs);
2794
2991
  }
2795
2992
  } else {
2796
- rpcManager = new ContainerRpcManager(wss, createRpcMethods());
2993
+ rpcManager = new ContainerRpcManager(
2994
+ wss,
2995
+ createRpcMethods(),
2996
+ rejectAllPendingHeadedReloads,
2997
+ );
2797
2998
 
2798
2999
  if (isWatchMode) {
2799
3000
  runtime.rpcManager = rpcManager;
@@ -3066,7 +3267,7 @@ export const listBrowserTests = async (
3066
3267
  throw error;
3067
3268
  }
3068
3269
 
3069
- const { browser, port } = runtime;
3270
+ const { browser, browserLaunchOptions, port } = runtime;
3070
3271
 
3071
3272
  // Get browser projects for runtime config
3072
3273
  // Normalize projectRoot to posix format for cross-platform compatibility
@@ -3110,7 +3311,10 @@ export const listBrowserTests = async (
3110
3311
  });
3111
3312
 
3112
3313
  // Create a headless page to run collection
3113
- const browserContext = await browser.newContext({ viewport: null });
3314
+ const browserContext = await browser.newContext({
3315
+ providerOptions: browserLaunchOptions.providerOptions,
3316
+ viewport: null,
3317
+ });
3114
3318
  const page = await browserContext.newPage();
3115
3319
 
3116
3320
  // Expose dispatch function for browser client to send messages
package/src/index.ts CHANGED
@@ -10,7 +10,6 @@ import {
10
10
  } from './hostController';
11
11
 
12
12
  export { validateBrowserConfig } from './configValidation';
13
-
14
13
  export {
15
14
  BROWSER_VIEWPORT_PRESET_DIMENSIONS,
16
15
  BROWSER_VIEWPORT_PRESET_IDS,
@@ -6,6 +6,14 @@ import { playwrightProviderImplementation } from './playwright';
6
6
  *
7
7
  * When adding a new built-in provider, implement `BrowserProviderImplementation`
8
8
  * and register it in `providerImplementations` below.
9
+ *
10
+ * Provider-agnostic policy:
11
+ * - keep shared contracts and behavior provider-neutral
12
+ * - `browser.providerOptions` stays opaque at the framework boundary
13
+ * - do not export provider-owned config types from `@rstest/browser`
14
+ * - do not reference optional peer provider types from public declarations
15
+ * - keep provider-specific behavior and config decoding inside provider implementations
16
+ * - prefer direct passthrough to provider APIs over provider-specific translation
9
17
  */
10
18
  export type BrowserProvider = 'playwright';
11
19
 
@@ -50,6 +58,7 @@ export type BrowserProviderBrowser = {
50
58
  close: () => Promise<void>;
51
59
  newContext: (options: {
52
60
  viewport: { width: number; height: number } | null;
61
+ providerOptions?: Record<string, unknown>;
53
62
  }) => Promise<BrowserProviderContext>;
54
63
  };
55
64
 
@@ -62,6 +71,7 @@ export type BrowserProviderRuntime = {
62
71
  export type LaunchBrowserInput = {
63
72
  browserName: 'chromium' | 'firefox' | 'webkit';
64
73
  headless: boolean | undefined;
74
+ providerOptions: Record<string, unknown>;
65
75
  };
66
76
 
67
77
  /** Input contract for provider-side browser RPC dispatch. */
@@ -11,10 +11,12 @@ export const playwrightProviderImplementation: BrowserProviderImplementation = {
11
11
  async launchRuntime({
12
12
  browserName,
13
13
  headless,
14
+ providerOptions,
14
15
  }): Promise<BrowserProviderRuntime> {
15
16
  return launchPlaywrightBrowser({
16
17
  browserName,
17
18
  headless,
19
+ providerOptions,
18
20
  });
19
21
  },
20
22
  async dispatchRpc({
@@ -1,32 +1,54 @@
1
- import type { BrowserProviderRuntime } from '../index';
2
-
3
- type PlaywrightModule = typeof import('playwright');
4
- type PlaywrightBrowserType = PlaywrightModule['chromium'];
1
+ import type { BrowserProviderContext, BrowserProviderRuntime } from '../index';
5
2
 
6
3
  export async function launchPlaywrightBrowser({
7
4
  browserName,
8
5
  headless,
6
+ providerOptions,
9
7
  }: {
10
8
  browserName: 'chromium' | 'firefox' | 'webkit';
11
9
  headless: boolean | undefined;
10
+ providerOptions: Record<string, unknown>;
12
11
  }): Promise<BrowserProviderRuntime> {
13
12
  const playwright = await import('playwright');
14
- const browserType = playwright[browserName] as PlaywrightBrowserType;
13
+ const browserType = playwright[browserName];
14
+ const launchOptions = providerOptions.launch as
15
+ | Record<string, unknown>
16
+ | undefined;
17
+ const launchArgs = Array.isArray(launchOptions?.args)
18
+ ? launchOptions.args
19
+ : browserName === 'chromium'
20
+ ? [
21
+ '--disable-popup-blocking',
22
+ '--no-first-run',
23
+ '--no-default-browser-check',
24
+ ]
25
+ : undefined;
15
26
 
16
27
  const browser = await browserType.launch({
28
+ ...launchOptions,
17
29
  headless,
18
- // Chromium-specific args (ignored by other browsers)
19
- args:
20
- browserName === 'chromium'
21
- ? [
22
- '--disable-popup-blocking',
23
- '--no-first-run',
24
- '--no-default-browser-check',
25
- ]
26
- : undefined,
30
+ args: launchArgs,
27
31
  });
28
32
 
33
+ const wrappedBrowser: BrowserProviderRuntime['browser'] = {
34
+ close: async () => browser.close(),
35
+ newContext: async ({
36
+ providerOptions: contextProviderOptions,
37
+ viewport,
38
+ }) => {
39
+ const contextOptions = contextProviderOptions?.context as
40
+ | Record<string, unknown>
41
+ | undefined;
42
+ const context = await browser.newContext({
43
+ ...contextOptions,
44
+ viewport,
45
+ });
46
+
47
+ return context as unknown as BrowserProviderContext;
48
+ },
49
+ };
50
+
29
51
  return {
30
- browser: browser as unknown as BrowserProviderRuntime['browser'],
52
+ browser: wrappedBrowser,
31
53
  };
32
54
  }
@@ -1 +0,0 @@
1
- /*! LICENSE: 101.82cdbbe145.js.LICENSE.txt */
@@ -1 +0,0 @@
1
- /*! LICENSE: lib-react.ce60b6aea5.js.LICENSE.txt */