@wdio/xvfb 9.19.0 → 9.19.1

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.
package/README.md CHANGED
@@ -65,7 +65,7 @@ For testing on non-Linux systems:
65
65
  ```js
66
66
  import { XvfbManager } from "@wdio/xvfb";
67
67
 
68
- const manager = new XvfbManager({
68
+ const manager = new XvfbManager({
69
69
  force: true,
70
70
  xvfbMaxRetries: 3,
71
71
  xvfbRetryDelay: 1000
@@ -238,9 +238,10 @@ You can customize xvfb behavior and retry settings:
238
238
  export const config = {
239
239
  // Xvfb configuration options (all optional)
240
240
  autoXvfb: true, // Enable automatic xvfb (default: true)
241
+ xvfbAutoInstall: false, // Enable auto-install of xvfb if missing (default: false)
241
242
  xvfbMaxRetries: 5, // Max retry attempts for xvfb failures (default: 3)
242
243
  xvfbRetryDelay: 2000, // Base delay between retries in ms (default: 1000)
243
-
244
+
244
245
  capabilities: [{
245
246
  browserName: 'chrome',
246
247
  'goog:chromeOptions': {
@@ -253,6 +254,7 @@ export const config = {
253
254
  **Configuration Options:**
254
255
 
255
256
  - **`autoXvfb`** *(boolean, default: true)*: Enable/disable automatic xvfb initialization
257
+ - **`xvfbAutoInstall`** *(boolean, default: false)*: Install `xvfb` packages automatically if `xvfb-run` is missing
256
258
  - **`xvfbMaxRetries`** *(number, default: 3)*: Number of retry attempts when xvfb process fails
257
259
  - **`xvfbRetryDelay`** *(number, default: 1000)*: Base delay between retries in milliseconds. Uses progressive delay (delay × attempt number)
258
260
 
@@ -12,6 +12,10 @@ export interface XvfbOptions {
12
12
  * Skip xvfb-run availability check and force installation (for testing)
13
13
  */
14
14
  forceInstall?: boolean;
15
+ /**
16
+ * Enable automatic installation of Xvfb packages if `xvfb-run` is missing (default: false)
17
+ */
18
+ autoInstall?: boolean;
15
19
  /**
16
20
  * Number of retry attempts for xvfb process failures (default: 3)
17
21
  */
@@ -26,6 +30,7 @@ export declare class XvfbManager {
26
30
  private force;
27
31
  private packageManagerOverride?;
28
32
  private forceInstall;
33
+ private autoInstall;
29
34
  private maxRetries;
30
35
  private retryDelay;
31
36
  private log;
@@ -1 +1 @@
1
- {"version":3,"file":"XvfbManager.d.ts","sourceRoot":"","sources":["../src/XvfbManager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,WAAW;IACxB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,qBAAa,WAAW;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,GAAG,CAA2B;gBAE1B,OAAO,GAAE,WAAgB;IASrC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,8BAA8B,GAAG,OAAO;IAoB9E;;;OAGG;IACU,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB/F;;OAEG;YACW,sBAAsB;IAsCpC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAI/B;;OAEG;IACH,OAAO,CAAC,oCAAoC;IAO5C;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA0BlC;;OAEG;IACH,OAAO,CAAC,eAAe;cAeP,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;YA4BzC,mBAAmB;IAoCjC;;OAEG;IACU,gBAAgB,CAAC,CAAC,EAC3B,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,MAAyB,GACnC,OAAO,CAAC,CAAC,CAAC;IA2Cb;;OAEG;IACH,OAAO,CAAC,WAAW;CAYtB"}
1
+ {"version":3,"file":"XvfbManager.d.ts","sourceRoot":"","sources":["../src/XvfbManager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,WAAW;IACxB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,qBAAa,WAAW;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,GAAG,CAA2B;gBAE1B,OAAO,GAAE,WAAgB;IAUrC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,8BAA8B,GAAG,OAAO;IAoB9E;;;OAGG;IACU,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB/F;;OAEG;YACW,sBAAsB;IAiDpC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAI/B;;OAEG;IACH,OAAO,CAAC,oCAAoC;IAO5C;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA0BlC;;OAEG;IACH,OAAO,CAAC,eAAe;cAeP,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;YA4BzC,mBAAmB;IAoCjC;;OAEG;IACU,gBAAgB,CAAC,CAAC,EAC3B,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,MAAyB,GACnC,OAAO,CAAC,CAAC,CAAC;IA2Cb;;OAEG;IACH,OAAO,CAAC,WAAW;CAYtB"}
package/build/index.js CHANGED
@@ -9,6 +9,7 @@ var XvfbManager = class {
9
9
  force;
10
10
  packageManagerOverride;
11
11
  forceInstall;
12
+ autoInstall;
12
13
  maxRetries;
13
14
  retryDelay;
14
15
  log;
@@ -16,6 +17,7 @@ var XvfbManager = class {
16
17
  this.force = options.force ?? false;
17
18
  this.packageManagerOverride = options.packageManager;
18
19
  this.forceInstall = options.forceInstall ?? false;
20
+ this.autoInstall = options.autoInstall ?? false;
19
21
  this.maxRetries = options.xvfbMaxRetries ?? 3;
20
22
  this.retryDelay = options.xvfbRetryDelay ?? 1e3;
21
23
  this.log = logger("@wdio/xvfb");
@@ -47,9 +49,13 @@ var XvfbManager = class {
47
49
  }
48
50
  this.log.info("Xvfb should run, checking if setup is needed");
49
51
  try {
50
- await this.ensureXvfbRunAvailable();
51
- this.log.info("xvfb-run is ready for use");
52
- return true;
52
+ const isReady = await this.ensureXvfbRunAvailable();
53
+ if (isReady) {
54
+ this.log.info("xvfb-run is ready for use");
55
+ return true;
56
+ }
57
+ this.log.warn("xvfb-run not available; continuing without virtual display");
58
+ return false;
53
59
  } catch (error) {
54
60
  this.log.error("Failed to setup xvfb-run:", error);
55
61
  throw error;
@@ -64,9 +70,18 @@ var XvfbManager = class {
64
70
  try {
65
71
  await execAsync("which xvfb-run");
66
72
  this.log.info("xvfb-run found in PATH");
67
- return;
73
+ return true;
68
74
  } catch {
69
- this.log.info("xvfb-run not found, installing xvfb packages...");
75
+ if (!this.autoInstall) {
76
+ this.log.warn(
77
+ "xvfb-run not found. Skipping automatic installation. To enable auto-install, set 'xvfbAutoInstall: true' in your WDIO config."
78
+ );
79
+ this.log.warn(
80
+ "Hint: you can also install it manually via your distro's package manager (e.g., 'sudo apt-get install xvfb', 'sudo dnf install xorg-x11-server-Xvfb')."
81
+ );
82
+ return false;
83
+ }
84
+ this.log.info("xvfb-run not found, installing xvfb packages (xvfbAutoInstall enabled)...");
70
85
  }
71
86
  } else {
72
87
  this.log.info("Force install enabled, skipping availability check");
@@ -81,6 +96,7 @@ var XvfbManager = class {
81
96
  this.log.info(
82
97
  `Successfully installed xvfb-run at: ${stdout.trim()}`
83
98
  );
99
+ return true;
84
100
  } catch (error) {
85
101
  this.log.error("Failed to install xvfb-run:", error);
86
102
  throw new Error(
@@ -88,6 +104,7 @@ var XvfbManager = class {
88
104
  );
89
105
  }
90
106
  }
107
+ return true;
91
108
  }
92
109
  /**
93
110
  * Detect if headless mode is enabled in Chrome/Chromium capabilities
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/xvfb",
3
- "version": "9.19.0",
3
+ "version": "9.19.1",
4
4
  "description": "A standalone utility to manage Xvfb (X Virtual Framebuffer) for headless testing",
5
5
  "author": "WebdriverIO Team",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-xvfb",
@@ -44,10 +44,10 @@
44
44
  "devDependencies": {
45
45
  "@types/is-ci": "^3.0.4",
46
46
  "@types/node": "^20.0.0",
47
- "@wdio/types": "9.19.0"
47
+ "@wdio/types": "9.19.1"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
51
51
  },
52
- "gitHead": "be9a42358a6646d48c3ce5a341761f55aa605b24"
52
+ "gitHead": "df3ec33741d11d196adad148f4d066a3fcbcd51b"
53
53
  }
@@ -18,6 +18,10 @@ export interface XvfbOptions {
18
18
  * Skip xvfb-run availability check and force installation (for testing)
19
19
  */
20
20
  forceInstall?: boolean;
21
+ /**
22
+ * Enable automatic installation of Xvfb packages if `xvfb-run` is missing (default: false)
23
+ */
24
+ autoInstall?: boolean;
21
25
  /**
22
26
  * Number of retry attempts for xvfb process failures (default: 3)
23
27
  */
@@ -35,6 +39,7 @@ export class XvfbManager {
35
39
  private force: boolean
36
40
  private packageManagerOverride?: string
37
41
  private forceInstall: boolean
42
+ private autoInstall: boolean
38
43
  private maxRetries: number
39
44
  private retryDelay: number
40
45
  private log: ReturnType<typeof logger>
@@ -43,6 +48,7 @@ export class XvfbManager {
43
48
  this.force = options.force ?? false
44
49
  this.packageManagerOverride = options.packageManager
45
50
  this.forceInstall = options.forceInstall ?? false
51
+ this.autoInstall = options.autoInstall ?? false
46
52
  this.maxRetries = options.xvfbMaxRetries ?? 3
47
53
  this.retryDelay = options.xvfbRetryDelay ?? 1000
48
54
  this.log = logger('@wdio/xvfb')
@@ -85,9 +91,14 @@ export class XvfbManager {
85
91
  this.log.info('Xvfb should run, checking if setup is needed')
86
92
 
87
93
  try {
88
- await this.ensureXvfbRunAvailable()
89
- this.log.info('xvfb-run is ready for use')
90
- return true
94
+ const isReady = await this.ensureXvfbRunAvailable()
95
+
96
+ if (isReady) {
97
+ this.log.info('xvfb-run is ready for use')
98
+ return true
99
+ }
100
+ this.log.warn('xvfb-run not available; continuing without virtual display')
101
+ return false
91
102
  } catch (error) {
92
103
  this.log.error('Failed to setup xvfb-run:', error)
93
104
  throw error
@@ -97,7 +108,7 @@ export class XvfbManager {
97
108
  /**
98
109
  * Ensure xvfb-run is available, installing if necessary
99
110
  */
100
- private async ensureXvfbRunAvailable(): Promise<void> {
111
+ private async ensureXvfbRunAvailable(): Promise<boolean> {
101
112
  this.log.info('Checking if xvfb-run is available...')
102
113
 
103
114
  if (!this.forceInstall) {
@@ -105,9 +116,18 @@ export class XvfbManager {
105
116
  // Check if xvfb-run is already available
106
117
  await execAsync('which xvfb-run')
107
118
  this.log.info('xvfb-run found in PATH')
108
- return
119
+ return true
109
120
  } catch {
110
- this.log.info('xvfb-run not found, installing xvfb packages...')
121
+ if (!this.autoInstall) {
122
+ this.log.warn(
123
+ "xvfb-run not found. Skipping automatic installation. To enable auto-install, set 'xvfbAutoInstall: true' in your WDIO config."
124
+ )
125
+ this.log.warn(
126
+ "Hint: you can also install it manually via your distro's package manager (e.g., 'sudo apt-get install xvfb', 'sudo dnf install xorg-x11-server-Xvfb')."
127
+ )
128
+ return false
129
+ }
130
+ this.log.info('xvfb-run not found, installing xvfb packages (xvfbAutoInstall enabled)...')
111
131
  }
112
132
  } else {
113
133
  this.log.info('Force install enabled, skipping availability check')
@@ -126,6 +146,7 @@ export class XvfbManager {
126
146
  this.log.info(
127
147
  `Successfully installed xvfb-run at: ${stdout.trim()}`
128
148
  )
149
+ return true
129
150
  } catch (error) {
130
151
  this.log.error('Failed to install xvfb-run:', error)
131
152
  throw new Error(
@@ -133,6 +154,7 @@ export class XvfbManager {
133
154
  )
134
155
  }
135
156
  }
157
+ return true
136
158
  }
137
159
 
138
160
  /**
@@ -298,133 +298,161 @@ describe('XvfbManager', () => {
298
298
  expect(result).toBe(true)
299
299
  })
300
300
 
301
- it('should install xvfb when xvfb-run is not available', async () => {
302
- // Mock xvfb-run not found first, then found after installation
303
- mockExecAsync
304
- .mockRejectedValueOnce(new Error('Command not found'))
305
- .mockResolvedValueOnce({ stdout: '', stderr: '' })
306
- .mockResolvedValueOnce({ stdout: '/usr/bin/which', stderr: '' })
307
- .mockResolvedValueOnce({ stdout: 'apt-get install success', stderr: '' })
308
- .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
309
-
310
- const manager = new XvfbManager()
311
-
312
- // Mock platform and environment
313
- mockPlatform.mockReturnValue('linux')
314
- delete process.env.DISPLAY
315
-
316
- const result = await manager.init()
317
-
318
- expect(result).toBe(true)
319
- expect(mockExecAsync).toHaveBeenCalledWith('which xvfb-run')
320
- expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
321
- expect(mockExecAsync).toHaveBeenCalledWith(
322
- 'sudo apt-get update -qq && sudo apt-get install -y xvfb',
323
- { timeout: 240000 }
324
- )
325
- })
326
- })
327
-
328
- describe('cross-distribution support', () => {
329
- beforeEach(() => {
330
- mockPlatform.mockReturnValue('linux')
331
- })
332
-
333
- it('should detect Ubuntu distribution', async () => {
334
- // Mock xvfb-run not found, then package manager detection
335
- mockExecAsync
336
- .mockRejectedValueOnce(new Error('Command not found'))
337
- .mockResolvedValueOnce({ stdout: '/usr/bin/apt-get', stderr: '' })
338
- .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
339
- .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
340
-
341
- const manager = new XvfbManager()
342
- delete process.env.DISPLAY
343
-
344
- await manager.init()
345
-
346
- expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
347
- expect(mockExecAsync).toHaveBeenCalledWith(
348
- 'sudo apt-get update -qq && sudo apt-get install -y xvfb',
349
- { timeout: 240000 }
350
- )
351
- })
352
-
353
- it('should detect dnf package manager', async () => {
354
- // Mock xvfb-run not found, then package manager detection
355
- mockExecAsync
356
- .mockRejectedValueOnce(new Error('Command not found'))
357
- .mockRejectedValueOnce(new Error('apt-get not found'))
358
- .mockResolvedValueOnce({ stdout: '/usr/bin/dnf', stderr: '' })
359
- .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
360
- .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
361
-
362
- const manager = new XvfbManager()
363
- delete process.env.DISPLAY
364
-
365
- await manager.init()
366
-
367
- expect(mockExecAsync).toHaveBeenCalledWith('which dnf')
368
- expect(mockExecAsync).toHaveBeenCalledWith(
369
- 'sudo dnf makecache && sudo dnf install -y xorg-x11-server-Xvfb',
370
- { timeout: 240000 }
371
- )
372
- })
373
-
374
- it('should detect pacman package manager', async () => {
375
- // Mock xvfb-run not found, then package manager detection
376
- mockExecAsync
377
- .mockRejectedValueOnce(new Error('Command not found'))
378
- .mockRejectedValueOnce(new Error('apt-get not found'))
379
- .mockRejectedValueOnce(new Error('dnf not found'))
380
- .mockRejectedValueOnce(new Error('yum not found'))
381
- .mockRejectedValueOnce(new Error('zypper not found'))
382
- .mockResolvedValueOnce({ stdout: '/usr/bin/pacman', stderr: '' })
383
- .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
384
- .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
385
-
386
- const manager = new XvfbManager()
387
- delete process.env.DISPLAY
388
-
389
- await manager.init()
390
-
391
- expect(mockExecAsync).toHaveBeenCalledWith('which pacman')
392
- expect(mockExecAsync).toHaveBeenCalledWith(
393
- 'sudo pacman -Sy --noconfirm xorg-server-xvfb',
394
- { timeout: 240000 }
395
- )
396
- })
397
-
398
- it('should detect dnf when apt-get is not available', async () => {
399
- // Mock xvfb-run not found, then package manager detection
400
- mockExecAsync
401
- .mockRejectedValueOnce(new Error('Command not found'))
402
- .mockRejectedValueOnce(new Error('apt-get not found'))
403
- .mockResolvedValueOnce({ stdout: '/usr/bin/dnf', stderr: '' })
404
- .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
405
- .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
406
-
407
- const manager = new XvfbManager()
408
- delete process.env.DISPLAY
409
-
410
- await manager.init()
411
-
412
- expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
413
- expect(mockExecAsync).toHaveBeenCalledWith('which dnf')
414
- })
415
-
416
- it('should handle unsupported package managers gracefully', async () => {
417
- // Mock all package managers as not found
418
- mockExecAsync
419
- .mockRejectedValueOnce(new Error('Command not found'))
420
- .mockRejectedValue(new Error('Package manager not found'))
301
+ describe('autoInstall', () => {
302
+ it('should install xvfb when xvfb-run is not available and autoInstall is enabled', async () => {
303
+ // Mock xvfb-run not found first, then found after installation
304
+ mockExecAsync
305
+ .mockRejectedValueOnce(new Error('Command not found'))
306
+ .mockResolvedValueOnce({ stdout: '', stderr: '' })
307
+ .mockResolvedValueOnce({ stdout: '/usr/bin/which', stderr: '' })
308
+ .mockResolvedValueOnce({ stdout: 'apt-get install success', stderr: '' })
309
+ .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
310
+
311
+ const manager = new XvfbManager({ autoInstall: true })
312
+
313
+ // Mock platform and environment
314
+ mockPlatform.mockReturnValue('linux')
315
+ delete process.env.DISPLAY
316
+
317
+ const result = await manager.init()
318
+
319
+ expect(result).toBe(true)
320
+ expect(mockExecAsync).toHaveBeenCalledWith('which xvfb-run')
321
+ expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
322
+ expect(mockExecAsync).toHaveBeenCalledWith(
323
+ 'sudo apt-get update -qq && sudo apt-get install -y xvfb',
324
+ { timeout: 240000 }
325
+ )
326
+ })
421
327
 
422
- const manager = new XvfbManager()
423
- delete process.env.DISPLAY
328
+ it('should not install and return false when xvfb-run is not available and autoInstall is disabled', async () => {
329
+ // Mock xvfb-run not found
330
+ mockExecAsync
331
+ .mockRejectedValueOnce(new Error('Command not found'))
332
+
333
+ const manager = new XvfbManager()
334
+
335
+ // Mock platform and environment
336
+ mockPlatform.mockReturnValue('linux')
337
+ delete process.env.DISPLAY
338
+
339
+ const result = await manager.init()
340
+
341
+ expect(result).toBe(false)
342
+ // Should only check for xvfb-run
343
+ expect(mockExecAsync).toHaveBeenCalledWith('which xvfb-run')
344
+ // And should not attempt any package manager detection or install
345
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which apt-get')
346
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which dnf')
347
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which yum')
348
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which zypper')
349
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which pacman')
350
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which apk')
351
+ expect(mockExecAsync).not.toHaveBeenCalledWith('which xbps-install')
352
+ })
424
353
 
425
- await expect(manager.init()).rejects.toThrow(
426
- 'Unsupported package manager: unknown. Please install Xvfb manually.'
427
- )
354
+ describe('cross-distribution support', () => {
355
+ beforeEach(() => {
356
+ mockPlatform.mockReturnValue('linux')
357
+ })
358
+
359
+ it('should detect Ubuntu distribution', async () => {
360
+ // Mock xvfb-run not found, then package manager detection
361
+ mockExecAsync
362
+ .mockRejectedValueOnce(new Error('Command not found'))
363
+ .mockResolvedValueOnce({ stdout: '/usr/bin/apt-get', stderr: '' })
364
+ .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
365
+ .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
366
+
367
+ const manager = new XvfbManager({ autoInstall: true })
368
+ delete process.env.DISPLAY
369
+
370
+ await manager.init()
371
+
372
+ expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
373
+ expect(mockExecAsync).toHaveBeenCalledWith(
374
+ 'sudo apt-get update -qq && sudo apt-get install -y xvfb',
375
+ { timeout: 240000 }
376
+ )
377
+ })
378
+
379
+ it('should detect dnf package manager', async () => {
380
+ // Mock xvfb-run not found, then package manager detection
381
+ mockExecAsync
382
+ .mockRejectedValueOnce(new Error('Command not found'))
383
+ .mockRejectedValueOnce(new Error('apt-get not found'))
384
+ .mockResolvedValueOnce({ stdout: '/usr/bin/dnf', stderr: '' })
385
+ .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
386
+ .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
387
+
388
+ const manager = new XvfbManager({ autoInstall: true })
389
+ delete process.env.DISPLAY
390
+
391
+ await manager.init()
392
+
393
+ expect(mockExecAsync).toHaveBeenCalledWith('which dnf')
394
+ expect(mockExecAsync).toHaveBeenCalledWith(
395
+ 'sudo dnf makecache && sudo dnf install -y xorg-x11-server-Xvfb',
396
+ { timeout: 240000 }
397
+ )
398
+ })
399
+
400
+ it('should detect pacman package manager', async () => {
401
+ // Mock xvfb-run not found, then package manager detection
402
+ mockExecAsync
403
+ .mockRejectedValueOnce(new Error('Command not found'))
404
+ .mockRejectedValueOnce(new Error('apt-get not found'))
405
+ .mockRejectedValueOnce(new Error('dnf not found'))
406
+ .mockRejectedValueOnce(new Error('yum not found'))
407
+ .mockRejectedValueOnce(new Error('zypper not found'))
408
+ .mockResolvedValueOnce({ stdout: '/usr/bin/pacman', stderr: '' })
409
+ .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
410
+ .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
411
+
412
+ const manager = new XvfbManager({ autoInstall: true })
413
+ delete process.env.DISPLAY
414
+
415
+ await manager.init()
416
+
417
+ expect(mockExecAsync).toHaveBeenCalledWith('which pacman')
418
+ expect(mockExecAsync).toHaveBeenCalledWith(
419
+ 'sudo pacman -Sy --noconfirm xorg-server-xvfb',
420
+ { timeout: 240000 }
421
+ )
422
+ })
423
+
424
+ it('should detect dnf when apt-get is not available', async () => {
425
+ // Mock xvfb-run not found, then package manager detection
426
+ mockExecAsync
427
+ .mockRejectedValueOnce(new Error('Command not found'))
428
+ .mockRejectedValueOnce(new Error('apt-get not found'))
429
+ .mockResolvedValueOnce({ stdout: '/usr/bin/dnf', stderr: '' })
430
+ .mockResolvedValueOnce({ stdout: 'installation success', stderr: '' })
431
+ .mockResolvedValueOnce({ stdout: '/usr/bin/xvfb-run\n', stderr: '' })
432
+
433
+ const manager = new XvfbManager({ autoInstall: true })
434
+ delete process.env.DISPLAY
435
+
436
+ await manager.init()
437
+
438
+ expect(mockExecAsync).toHaveBeenCalledWith('which apt-get')
439
+ expect(mockExecAsync).toHaveBeenCalledWith('which dnf')
440
+ })
441
+
442
+ it('should handle unsupported package managers gracefully', async () => {
443
+ // Mock all package managers as not found
444
+ mockExecAsync
445
+ .mockRejectedValueOnce(new Error('Command not found'))
446
+ .mockRejectedValue(new Error('Package manager not found'))
447
+
448
+ const manager = new XvfbManager({ autoInstall: true })
449
+ delete process.env.DISPLAY
450
+
451
+ await expect(manager.init()).rejects.toThrow(
452
+ 'Unsupported package manager: unknown. Please install Xvfb manually.'
453
+ )
454
+ })
455
+ })
428
456
  })
429
457
  })
430
458