@wdio/xvfb 9.19.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenJS Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # @wdio/xvfb
2
+
3
+ A standalone utility to manage Xvfb (X Virtual Framebuffer) for headless testing on Linux systems.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @wdio/xvfb
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Automatic Integration
14
+
15
+ **Most users don't need to use this package directly.** It's automatically integrated into:
16
+
17
+ - **`@wdio/local-runner`** - Automatically ensures xvfb is available for headless browser testing
18
+ - **`wdio-electron-service`** - Provides headless Electron testing capabilities
19
+
20
+ Simply run your WebDriverIO tests normally, and xvfb will be managed automatically when needed.
21
+
22
+ ### Manual Usage (Advanced)
23
+
24
+ For custom integrations or non-WDIO environments:
25
+
26
+ ```js
27
+ import { xvfb } from "@wdio/xvfb";
28
+
29
+ // Initialize xvfb-run (only works on Linux in headless environments)
30
+ const ready = await xvfb.init();
31
+
32
+ if (ready) {
33
+ console.log("xvfb-run is ready for use");
34
+ // Use xvfb-run to execute commands that need a display
35
+ // e.g., xvfb-run npm test
36
+ }
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### XvfbManager Class
42
+
43
+ #### Constructor Options
44
+
45
+ ```ts
46
+ interface XvfbOptions {
47
+ force?: boolean; // Force Xvfb even on non-Linux systems (for testing)
48
+ xvfbMaxRetries?: number; // Number of retry attempts for xvfb failures (default: 3)
49
+ xvfbRetryDelay?: number; // Base delay between retries in milliseconds (default: 1000)
50
+ }
51
+ ```
52
+
53
+ #### Methods
54
+
55
+ - **`shouldRun(capabilities?): boolean`** - Check if Xvfb should run on this system
56
+ - **`init(capabilities?): Promise<boolean>`** - Initialize xvfb-run, returns true if ready, false if not needed
57
+ - **`executeWithRetry<T>(commandFn, context?): Promise<T>`** - Execute a function with automatic retry on xvfb-related errors
58
+
59
+ ### Advanced Usage Examples
60
+
61
+ #### Force Mode (Testing Only)
62
+
63
+ For testing on non-Linux systems:
64
+
65
+ ```js
66
+ import { XvfbManager } from "@wdio/xvfb";
67
+
68
+ const manager = new XvfbManager({
69
+ force: true,
70
+ xvfbMaxRetries: 3,
71
+ xvfbRetryDelay: 1000
72
+ });
73
+ const ready = await manager.init();
74
+ ```
75
+
76
+ #### Custom Retry Configuration
77
+
78
+ ```js
79
+ import { XvfbManager } from "@wdio/xvfb";
80
+
81
+ // High-reliability setup for CI environments
82
+ const manager = new XvfbManager({
83
+ xvfbMaxRetries: 10, // More retries for flaky CI
84
+ xvfbRetryDelay: 500 // Faster retries
85
+ });
86
+
87
+ // Execute operation with automatic retry
88
+ const result = await manager.executeWithRetry(async () => {
89
+ // Your operation that might fail due to xvfb issues
90
+ return await runHeadlessTests();
91
+ }, 'Running headless tests');
92
+ ```
93
+
94
+ ## When does it run?
95
+
96
+ The utility automatically detects when Xvfb is needed:
97
+
98
+ - ✅ Linux systems without a DISPLAY environment variable
99
+ - ✅ Linux systems in CI environments (uses `is-ci` package for detection)
100
+ - ✅ Linux systems when headless browser flags are detected:
101
+ - **Chrome/Chromium**: `--headless`, `--headless=new`, `--headless=old`
102
+ - **Firefox**: `--headless`, `-headless`
103
+ - **Edge** (Chromium-based): `--headless`, `--headless=new`, `--headless=old`
104
+ - ❌ Non-Linux systems (unless `force: true`)
105
+ - ❌ Linux systems with existing DISPLAY (unless in CI or headless flags detected)
106
+
107
+ ## Features
108
+
109
+ - **Conditional execution**: Only runs on Linux systems in headless environments
110
+ - **Smart headless detection**: Automatically detects browser headless flags (`--headless`, `--headless=new`, etc.) and forces XVFB usage for consistent behavior
111
+ - **Automatic retry mechanism**: Built-in retry logic with progressive delays for handling xvfb startup failures
112
+ - **Configurable retry behavior**: Customizable retry count and delay settings via WDIO configuration
113
+ - **Universal package manager support**: Automatically detects and uses the system's package manager (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `xbps`)
114
+ - **Cross-distro compatibility**: Works across hundreds of Linux distributions by supporting their underlying package managers
115
+ - **Command execution**: Execute commands under Xvfb using `xvfb-run`
116
+ - **Graceful cleanup**: Automatic cleanup when commands complete
117
+ - **State tracking**: Prevents duplicate starts and handles multiple calls safely
118
+ - **Flexible logging**: Configurable logger support
119
+
120
+ ## Retry Mechanism
121
+
122
+ The package includes automatic retry functionality for handling xvfb-related failures:
123
+
124
+ ### Automatic Retry Detection
125
+
126
+ The retry mechanism automatically detects xvfb-specific errors:
127
+ - `xvfb-run: error: Xvfb failed to start`
128
+ - `Xvfb failed to start`
129
+ - `xvfb-run: error:`
130
+ - `X server died`
131
+
132
+ ### Progressive Delay Strategy
133
+
134
+ Retry delays increase progressively to avoid overwhelming the system:
135
+ - Attempt 1: Immediate execution
136
+ - Attempt 2: Wait `xvfbRetryDelay × 1` ms (default: 1000ms)
137
+ - Attempt 3: Wait `xvfbRetryDelay × 2` ms (default: 2000ms)
138
+ - Attempt N: Wait `xvfbRetryDelay × (N-1)` ms
139
+
140
+ ### Usage Example
141
+
142
+ ```js
143
+ import { XvfbManager } from '@wdio/xvfb';
144
+
145
+ const manager = new XvfbManager({
146
+ xvfbMaxRetries: 5,
147
+ xvfbRetryDelay: 1500
148
+ });
149
+
150
+ // Automatic retry on xvfb failures
151
+ const result = await manager.executeWithRetry(async () => {
152
+ // Your xvfb-dependent operation
153
+ return await someXvfbDependentTask();
154
+ }, 'Custom operation name');
155
+ ```
156
+
157
+ ## Implementation Details
158
+
159
+ This package uses `xvfb-run` to manage Xvfb sessions, which:
160
+
161
+ - Automatically selects available display numbers with `--auto-servernum`
162
+ - Configures screen resolution, DPI, and other display settings
163
+ - Handles process cleanup automatically when commands complete
164
+ - Supports custom arguments for advanced Xvfb configuration
165
+
166
+ ## Supported Package Managers
167
+
168
+ The utility automatically detects the system's package manager and installs xvfb accordingly:
169
+
170
+ | Package Manager | Command | Distributions | Package Name |
171
+ |----------------|---------|---------------|--------------|
172
+ | **`apt`** | `apt-get` | Ubuntu, Debian, Pop!_OS, Mint, Elementary, Zorin, etc. | `xvfb` |
173
+ | **`dnf`** | `dnf` | Fedora, Rocky Linux, AlmaLinux, Nobara, Bazzite, etc. | `xorg-x11-server-Xvfb` |
174
+ | **`yum`** | `yum` | CentOS, RHEL (legacy) | `xorg-x11-server-Xvfb` |
175
+ | **`zypper`** | `zypper` | openSUSE, SUSE Linux Enterprise | `xorg-x11-server-Xvfb xvfb-run` |
176
+ | **`pacman`** | `pacman` | Arch Linux, Manjaro, EndeavourOS, CachyOS, etc. | `xorg-server-xvfb` |
177
+ | **`apk`** | `apk` | Alpine Linux, PostmarketOS | `xvfb-run` |
178
+ | **`xbps`** | `xbps-install` | Void Linux | `xvfb` |
179
+
180
+ ### Future-Proof Design
181
+
182
+ This package manager-focused approach means **new Linux distributions automatically work** without code changes, as long as they use one of the supported package managers. For example:
183
+ - New Ubuntu derivatives automatically use `apt`
184
+ - New Arch derivatives automatically use `pacman`
185
+ - New Fedora derivatives automatically use `dnf`
186
+
187
+ The 7 supported package managers cover **95%+ of all Linux users** and hundreds of distributions, making this solution both comprehensive and maintainable.
188
+
189
+ ## Examples
190
+
191
+ ### Basic Setup for Custom Tools
192
+
193
+ ```js
194
+ import { xvfb } from "@wdio/xvfb";
195
+ import { exec } from "child_process";
196
+ import { promisify } from "util";
197
+
198
+ const execAsync = promisify(exec);
199
+
200
+ async function runCustomTests() {
201
+ const ready = await xvfb.init();
202
+
203
+ const command = ready
204
+ ? "xvfb-run your-custom-test-command"
205
+ : "your-custom-test-command";
206
+
207
+ await execAsync(command);
208
+ }
209
+ ```
210
+
211
+ ### WebDriverIO Configuration
212
+
213
+ For most users, no configuration is needed. WDIO automatically handles xvfb:
214
+
215
+ ```js
216
+ // wdio.conf.js - Minimal configuration
217
+ export const config = {
218
+ // No xvfb configuration needed - handled automatically
219
+ capabilities: [{
220
+ browserName: 'chrome',
221
+ 'goog:chromeOptions': {
222
+ args: ['--headless', '--no-sandbox'] // Automatically detected - will force XVFB
223
+ }
224
+ }],
225
+ services: [
226
+ 'chromedriver',
227
+ // xvfb is automatically managed by local-runner
228
+ ]
229
+ };
230
+ ```
231
+
232
+ #### Advanced WDIO Configuration
233
+
234
+ You can customize xvfb behavior and retry settings:
235
+
236
+ ```js
237
+ // wdio.conf.js - Custom xvfb configuration
238
+ export const config = {
239
+ // Xvfb configuration options (all optional)
240
+ autoXvfb: true, // Enable automatic xvfb (default: true)
241
+ xvfbMaxRetries: 5, // Max retry attempts for xvfb failures (default: 3)
242
+ xvfbRetryDelay: 2000, // Base delay between retries in ms (default: 1000)
243
+
244
+ capabilities: [{
245
+ browserName: 'chrome',
246
+ 'goog:chromeOptions': {
247
+ args: ['--headless', '--no-sandbox']
248
+ }
249
+ }]
250
+ };
251
+ ```
252
+
253
+ **Configuration Options:**
254
+
255
+ - **`autoXvfb`** *(boolean, default: true)*: Enable/disable automatic xvfb initialization
256
+ - **`xvfbMaxRetries`** *(number, default: 3)*: Number of retry attempts when xvfb process fails
257
+ - **`xvfbRetryDelay`** *(number, default: 1000)*: Base delay between retries in milliseconds. Uses progressive delay (delay × attempt number)
258
+
259
+ ### Headless Flag Detection
260
+
261
+ The utility automatically detects browser headless flags and forces XVFB usage even when a DISPLAY is available:
262
+
263
+ ```js
264
+ // These configurations will automatically trigger XVFB on Linux:
265
+
266
+ // Chrome/Chromium
267
+ capabilities: [{
268
+ 'goog:chromeOptions': {
269
+ args: ['--headless'] // Detected
270
+ }
271
+ }]
272
+
273
+ // Firefox
274
+ capabilities: [{
275
+ 'moz:firefoxOptions': {
276
+ args: ['--headless'] // Detected
277
+ }
278
+ }]
279
+
280
+ // Edge (Chromium-based)
281
+ capabilities: [{
282
+ 'ms:edgeOptions': {
283
+ args: ['--headless=new'] // Detected
284
+ }
285
+ }]
286
+
287
+ // Legacy formats also supported
288
+ capabilities: [{
289
+ chromeOptions: { args: ['--headless'] }, // Legacy Chrome
290
+ edgeOptions: { args: ['--headless'] } // Legacy Edge
291
+ }]
292
+
293
+ // Multiremote scenarios are also supported
294
+ capabilities: {
295
+ browserA: { 'goog:chromeOptions': { args: ['--headless'] }},
296
+ browserB: { 'ms:edgeOptions': { args: ['--no-sandbox'] }}
297
+ // XVFB will be used because browserA has headless flag
298
+ }
299
+ ```
300
+
301
+ This ensures consistent headless behavior regardless of host environment setup.
302
+
303
+ ## Logging
304
+
305
+ The utility uses `@wdio/logger` with the namespace `@wdio/xvfb`. Enable debug logging:
306
+
307
+ ```bash
308
+ DEBUG=@wdio/xvfb npm run test
309
+ ```
310
+
311
+ The logger is automatically created with the namespace `@wdio/xvfb` and cannot be customized in the current interface.
312
+
313
+ ## License
314
+
315
+ MIT
@@ -0,0 +1,17 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ import { XvfbManager } from './XvfbManager.js';
3
+ export interface ProcessCreator {
4
+ createWorkerProcess(scriptPath: string, args: string[], options: ProcessCreationOptions): Promise<ChildProcess>;
5
+ }
6
+ export interface ProcessCreationOptions {
7
+ cwd?: string;
8
+ env?: Record<string, string>;
9
+ execArgv?: string[];
10
+ stdio?: ('inherit' | 'pipe' | 'ignore' | 'ipc')[];
11
+ }
12
+ export declare class ProcessFactory implements ProcessCreator {
13
+ #private;
14
+ constructor(xvfbManager?: XvfbManager);
15
+ createWorkerProcess(scriptPath: string, args: string[], options: ProcessCreationOptions): Promise<ChildProcess>;
16
+ }
17
+ //# sourceMappingURL=ProcessFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProcessFactory.d.ts","sourceRoot":"","sources":["../src/ProcessFactory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,YAAY,EAGf,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,MAAM,WAAW,cAAc;IAC3B,mBAAmB,CACf,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,sBAAsB,GAChC,OAAO,CAAC,YAAY,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC;CACrD;AAED,qBAAa,cAAe,YAAW,cAAc;;gBAIrC,WAAW,CAAC,EAAE,WAAW;IAI/B,mBAAmB,CACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,sBAAsB,GAChC,OAAO,CAAC,YAAY,CAAC;CAkG3B"}
@@ -0,0 +1,81 @@
1
+ import type { Capabilities } from '@wdio/types';
2
+ export interface XvfbOptions {
3
+ /**
4
+ * Force Xvfb to run even on non-Linux systems (for testing)
5
+ */
6
+ force?: boolean;
7
+ /**
8
+ * Override package manager detection (for testing)
9
+ */
10
+ packageManager?: string;
11
+ /**
12
+ * Skip xvfb-run availability check and force installation (for testing)
13
+ */
14
+ forceInstall?: boolean;
15
+ /**
16
+ * Number of retry attempts for xvfb process failures (default: 3)
17
+ */
18
+ xvfbMaxRetries?: number;
19
+ /**
20
+ * Base delay between retries in milliseconds (default: 1000)
21
+ * Progressive delay will be: baseDelay * attemptNumber
22
+ */
23
+ xvfbRetryDelay?: number;
24
+ }
25
+ export declare class XvfbManager {
26
+ private force;
27
+ private packageManagerOverride?;
28
+ private forceInstall;
29
+ private maxRetries;
30
+ private retryDelay;
31
+ private log;
32
+ constructor(options?: XvfbOptions);
33
+ /**
34
+ * Check if Xvfb should run on this system
35
+ */
36
+ shouldRun(capabilities?: Capabilities.ResolvedTestrunnerCapabilities): boolean;
37
+ /**
38
+ * Initialize xvfb-run for use
39
+ * @returns Promise<boolean> - true if xvfb-run is ready, false if not needed
40
+ */
41
+ init(capabilities?: Capabilities.ResolvedTestrunnerCapabilities): Promise<boolean>;
42
+ /**
43
+ * Ensure xvfb-run is available, installing if necessary
44
+ */
45
+ private ensureXvfbRunAvailable;
46
+ /**
47
+ * Detect if headless mode is enabled in Chrome/Chromium capabilities
48
+ */
49
+ private detectHeadlessMode;
50
+ /**
51
+ * Check if the capabilities object is a single capability (not multiremote)
52
+ */
53
+ private isSingleCapability;
54
+ /**
55
+ * Check if the capabilities object is multiremote
56
+ */
57
+ private isMultiRemoteCapability;
58
+ /**
59
+ * Extract capabilities from browser config (handles both nested and direct formats)
60
+ */
61
+ private extractCapabilitiesFromBrowserConfig;
62
+ /**
63
+ * Check a single capability object for headless flags
64
+ */
65
+ private checkCapabilityForHeadless;
66
+ /**
67
+ * Check if browser options contain headless flags
68
+ */
69
+ private hasHeadlessFlag;
70
+ protected detectPackageManager(): Promise<string>;
71
+ private installXvfbPackages;
72
+ /**
73
+ * Execute a command with retry logic for xvfb failures
74
+ */
75
+ executeWithRetry<T>(commandFn: () => Promise<T>, context?: string): Promise<T>;
76
+ /**
77
+ * Check if an error is related to xvfb failures
78
+ */
79
+ private isXvfbError;
80
+ }
81
+ //# sourceMappingURL=XvfbManager.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,5 @@
1
+ import { XvfbManager } from './XvfbManager.js';
2
+ export { XvfbManager, type XvfbOptions } from './XvfbManager.js';
3
+ export { ProcessFactory, type ProcessCreator, type ProcessCreationOptions } from './ProcessFactory.js';
4
+ export declare const xvfb: XvfbManager;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAGtG,eAAO,MAAM,IAAI,aAAoB,CAAA"}