Selenwright is a Docker-native browser automation grid built for first-class Playwright WebSocket sessions, with Selenium WebDriver compatibility when you need it.
It starts an isolated browser container for every session, exposes Playwright through a native /playwright/<browser>/<version> WebSocket endpoint, and gives teams the operational pieces missing from raw browser containers: queueing, VNC, video, logs, artifacts, authentication, browser discovery, and a web UI. Please refer to GitHub repository if you need source code.
1. Why Selenwright
-
Native Playwright in Docker - connect Playwright clients directly over WebSocket without Selenium compatibility mode.
-
One grid for Playwright and Selenium - run modern Playwright suites and existing WebDriver tests from the same service.
-
Ephemeral browser containers - every session gets an isolated Docker browser with predictable cleanup.
-
Debuggable CI browser automation - capture VNC, video, logs, downloads, metadata, and session artifacts.
-
Operator-friendly by default - use authentication, quotas, browser discovery, metrics, and Selenwright UI in production-style environments.
2. Getting Started
2.1. Quick Start Guide
2.1.1. Start Selenwright
-
Make sure you have recent Docker version installed.
-
Download Selenwright binary for your platform from releases page.
-
On Linux or Mac give execution permissions to binary:
$ chmod +x selenwright
-
Run Selenwright:
*nix$ ./selenwrightWindowsselenwright.exeRunning this command with
sudocan lead to broken installation. Recommended way is running Selenwright as regular user. On Linux to have permissions to access Docker you may need to add your user todockergroup:$ sudo usermod -aG docker $USER -
Optionally start Selenwright UI to see live browser sessions.
2.1.2. Connect Playwright
Connect Playwright to Selenwright’s native WebSocket endpoint:
ws://localhost:4444/playwright/chromium/1.56.1
const { chromium } = require("playwright");
const browser = await chromium.connect(
"ws://localhost:4444/playwright/chromium/1.56.1",
{
headers: {
Authorization: `Bearer ${process.env.SELENWRIGHT_TOKEN}`,
},
}
);
The Playwright client version and browser image version must match on major and minor version. For example, Playwright 1.56.x should connect to a Selenwright browser entry built for 1.56.x.
2.1.3. Connect Selenium WebDriver
Run existing Selenium tests against the WebDriver compatibility endpoint:
http://localhost:4444/wd/hub
2.1.4. Check Status and UI
If something does not work, you can check that Selenwright is running by opening the status URL:
http://localhost:4444/status
A successful request should return JSON with browser usage statistics.
To open Selenwright UI navigate to the following page in your browser:
http://localhost:4173/
2.2. Starting Selenwright Manually
|
2.2.1. Prepare Browsers
There are two ways to populate the browser catalog:
Option A: Browser Discovery (recommended)
Pull the browser images you need and let Selenwright discover them automatically:
$ docker pull selenwright/playwright-chromium:latest $ docker pull selenwright/playwright-firefox:latest $ docker pull selenwright/playwright-webkit:latest
Start Selenwright — it will detect the pulled images via Docker labels. Then open Selenwright UI and adopt the discovered images into the catalog. No configuration file needed.
Option B: Manual browsers.json
Create config/browsers.json configuration file:
{
"chromium": {
"default": "1.56.1",
"versions": {
"1.56.1": {
"image": "selenwright/playwright-chromium:latest",
"port": "3000",
"path": "/",
"protocol": "playwright"
}
}
},
"firefox": {
"default": "1.56.1",
"versions": {
"1.56.1": {
"image": "selenwright/playwright-firefox:latest",
"port": "3000",
"path": "/",
"protocol": "playwright"
}
}
}
}
Playwright images expose port 3000 at path / and require "protocol": "playwright". For classic Selenium images use "path": "/" for Chrome/Opera and "path": "/wd/hub" for Firefox. See Browsers Configuration File for all fields.
|
Pull the images:
$ docker pull selenwright/playwright-chromium:latest $ docker pull selenwright/playwright-firefox:latest
Selenwright falls back to this file when no adopted images are found.
Browser Images
Selenwright publishes Playwright-based browser images on Docker Hub:
Each image carries io.selenwright.* Docker labels so Browser Discovery picks them up without a configuration file. Dockerfiles live in aqa-alex/selenwright-browsers.
Classic Selenium-protocol images are not published under selenwright/ — bring your own (for example, upstream selenoid/chrome, selenoid/firefox).
|
Browser Discovery and
|
2.2.2. Start Selenwright
Option 1: start Selenwright binary
-
Download binary for your operating system from releases page and save it as
selenwright(orselenwright.exeon windows).Add execution permission in case of *nix os-type with chmod +x selenwright. -
Then run:
*nix./selenwright
Windowsselenwright.exe
-
It should write to console something like:
2017/11/26 21:23:43 Loading configuration files... 2017/11/26 21:23:43 Loaded configuration from [config/browsers.json] ... 2017/11/26 21:23:43 Listening on :4444
Option 2: start Selenwright container
If you have Docker installed you can omit downloading binary and run it inside container. Pull browser images, then run:
docker run -d \
--name selenwright \
-p 4444:4444 \
-v /var/run/docker.sock:/var/run/docker.sock \
selenwright/hub:latest-release
If you use a manual browsers.json (Option B above), mount it as a volume: -v /your/directory/config/:/etc/selenwright/:ro and add -conf /etc/selenwright/browsers.json to the command.
|
2.3. Frequently Asked Questions
2.3.1. Logs and Dirs
Where are Selenwright logs?
Selenwright outputs its logs to stdout. Selenwright launched as a binary should output logs to the screen. To see Selenwright logs launched as Docker container type:
$ docker logs selenwright
To follow the logs add one more flag:
$ docker logs -f selenwright
Where are recorded videos stored?
Videos are saved to the directory specified by -video-output-dir flag (default video relative to the working directory). When running in Docker, mount a host directory to this path — see Video Recording for details.
2.3.2. Limits and Timeouts
How can I limit overall browsers consumption?
You have to use -limit flag to specify total number of parallel sessions. Default value is 5. See Resources Consumption section on how to determine total number of parallel sessions.
Can I limit per-version browser consumption?
No, this is not supported. We consider the only reasonable limitation should be the overall browsers consumption. This is important to not overload the hardware.
How can I adjust Selenwright timeouts?
The main timeout flag is -timeout, specified as 60s or 2m or 1h. It means maximum amount of time between subsequent HTTP requests to Selenium API. When there are no requests during this time period - session is automatically closed. Selenwright also has more subtle timeouts like:
-
-service-startup-timeout- container or driver process startup timeout -
-session-attempt-timeout- new session HTTP request timeout, applied when container or driver has started -
-session-delete-timeout- container or process removal timeout, applied afterdriver.quit()call
2.3.3. Resources Consumption
How many resources browser containers consume?
This depends on your tests. We recommend to start with 1 CPU and 1 Gb of memory per container as a rough estimate and then increase -limit checking that your tests work stably.
Do VNC and non-VNC browser images memory and CPU consumption differ?
The only difference between these images - is a running VNC server (x11vnc) consuming approximately 20 Megabytes of RAM in idle state which is negligible compared to browser memory consumption.
2.3.4. Features not Working
Video feature not working
When running Selenwright as Docker container video feature can be not working (because of misconfiguration). If your video files are named like selenwright607667f7e1c7923779e35506b040300d.mp4 and you are seeing the following log message…
2018/03/20 21:06:37 [9] [VIDEO_ERROR] [Failed to rename /video/selenwright607667f7e1c7923779e35506b040300d.mp4 to /video/8019c4bc-9bec-4a8b-aa40-68d1db0cffd2.mp4: rename /video/selenwright607667f7e1c7923779e35506b040300d.mp4 /video/8019c4bc-9bec-4a8b-aa40-68d1db0cffd2.mp4: no such file or directory]
... then check that:
-
You are passing an
OVERRIDE_VIDEO_OUTPUT_DIRenvironment variable pointing to a directory on thehost machinewhere video files are actually stored -
When passing custom arguments to Selenwright container (such as
-limitor-timeout) you also have to pass-video-output-dir /opt/selenwright/videoand mount host machine video dir to/opt/selenwright/video
Can’t get VNC feature to work: Disconnected
Please check that you have enableVNC = true capability in your tests
Can Selenwright pull browser images automatically?
Selenwright does not auto-pull images during session creation to avoid unpredictable latency under load. However, you can use the Browser Discovery feature to scan images already present on the host and adopt them into the catalog. For stack-wide updates, see Docker Compose Stack Management.
3. Main Features
3.1. Native Playwright Support
Selenwright can proxy native Playwright WebSocket connections through a dedicated endpoint. This support is for direct Playwright clients and companion Playwright server images. It is not Selenium Grid compatibility mode.
3.1.1. What Selenwright Supports
-
One Playwright WebSocket connection maps to one Selenwright-managed browser session.
-
Selenwright starts the configured browser container, connects the client to the Playwright server running inside it, and tracks the session in the same queue and
/statusmodel as existing sessions. -
Idle timeout and disconnect cleanup stop the container and remove the session from Selenwright state.
3.1.2. Endpoint Format
Use the following endpoint:
ws://<host>:4444/playwright/<browser>/<playwright-version>
Where:
-
<browser>matches a top-level browser name in the browser catalog (e.g.chromium,firefox,webkit). -
<playwright-version>matches the configured browser version entry.
This route accepts WebSocket connections only.
3.1.3. Session Lifecycle And Cleanup
When a client connects to /playwright/<browser>/<playwright-version>, Selenwright resolves the matching browser entry, starts the container, and registers a session after the WebSocket tunnel is established.
Playwright sessions follow the same operational model as WebDriver sessions:
-
Queue admission and release behave the same way as for regular Selenwright sessions.
-
Active sessions are visible in
/status. -
If the client disconnects, the upstream Playwright server disconnects, or the idle timeout expires, Selenwright stops the container and removes the session.
3.1.4. Companion Image Contract
Companion Playwright images are external to this repository. To work with Selenwright they should follow this contract:
-
Pin the image tag to a Playwright version.
-
Run a Playwright server, not Selenium.
-
Expose one fixed port and declare that same port in the browser catalog.
-
Expose one fixed WebSocket path. The default path is
/. -
Run under
--initor an equivalent init process so child processes are reaped correctly. -
Define Docker
HEALTHCHECK. -
For Browser Discovery pickup, carry the
io.selenwright.*labels described below. Without them the image is invisible to discovery and must be registered viabrowsers.json.
| Label | Purpose |
|---|---|
|
Browser name ( |
|
Version entry used in the catalog and connection URL. Required. |
|
Port exposed by the image. Required unless |
|
Set to |
|
WebSocket path (default |
|
|
Optional labels mirror the per-field names in browsers.json: tmpfs, env, volumes, hosts, shmSize, mem, cpu, labels, sysctl. As an escape hatch, io.selenwright.config accepts a full Browser JSON blob; when present, per-field labels are ignored. See selenwright-browsers for a working example.
At startup Selenwright prefers Docker HEALTHCHECK when the image provides one. If health status is unavailable, Selenwright falls back to protocol-specific probing. For Playwright images that fallback is a TCP probe against the configured Playwright endpoint.
3.1.5. Version Matching Rule
Playwright client and server versions must match on major and minor version.
For example, a client from Playwright 1.56.x should connect to an image built for Playwright 1.56.x. Keep the image tag, the configured Selenwright version entry, and the client connection URL aligned.
3.1.6. Configuration Example
An example browser catalog entry for a native Playwright image (via browsers.json or Browser Discovery):
{
"chromium": {
"default": "1.56.1",
"versions": {
"1.56.1": {
"image": "selenwright/playwright-chromium:latest",
"port": "3000",
"path": "/",
"protocol": "playwright"
}
}
}
}
Selenwright publishes matching companion images — see Browser Images for the full list.
3.1.7. Usage Examples
Connect from Playwright with the dedicated Selenwright WebSocket endpoint:
const browser = await browserType.connect(
"ws://selenwright.example.com:4444/playwright/chromium/1.56.1"
);
Passing an API Token
When selenwright has authentication enabled (the default), pass the bearer token in the Authorization header:
const browser = await browserType.connect({
wsEndpoint: "wss://selenwright.example.com/playwright/chromium/1.56.1",
headers: { Authorization: `Bearer ${process.env.SELENWRIGHT_TOKEN}` },
});
For clients that cannot set custom WebSocket headers, selenwright also accepts a ?token=<plaintext> query fallback:
const browser = await browserType.connect(
`wss://selenwright.example.com/playwright/chromium/1.56.1?token=${process.env.SELENWRIGHT_TOKEN}`
);
| The query form puts the token in URL-shaped surfaces — shell history, CI logs, the browser’s reverse-proxy access log — even though selenwright strips the parameter before forwarding the request upstream. Prefer the header form whenever your client supports it. |
See API Tokens (for Machine Clients) in the Authentication chapter for how tokens are minted and revoked.
If your test bootstrap reads an environment variable to choose the remote endpoint, you can pass the same URL through it:
PW_TEST_CONNECT_WS_ENDPOINT=ws://selenwright.example.com:4444/playwright/chromium/1.56.1
PW_TEST_CONNECT_WS_ENDPOINT is only a user-side convention for your own Playwright config or test launcher. It is not a built-in Selenwright environment variable.
3.1.8. File Operations
File uploads (page.setInputFiles()) and downloads (page.download()) work natively through the Playwright WebSocket protocol. Selenwright proxies the connection transparently — no additional configuration is needed.
The Selenwright-specific /file and /download/ HTTP endpoints are WebDriver-only and are not used by Playwright sessions. Use native Playwright APIs for file operations.
|
3.1.9. Capabilities
A subset of Special Capabilities can be passed as query parameters on the WebSocket URL:
ws://host:4444/playwright/chromium/1.56.1?enableVNC=true&name=myTest&screenResolution=1280x1024
Supported: enableVNC, name, screenResolution.
Session logs (Playwright protocol messages) are captured automatically when artifact history is enabled.
3.1.10. External Session IDs (Router Integration)
Selenwright honors X-Selenwright-External-Session-ID on the Playwright upgrade request. When the header is present and valid, its value replaces the auto-generated session ID and is used as-is for:
-
the
app.sessionskey, -
the path segment in side endpoints (
/vnc/<id>,/video/<id>.mp4,/logs/<id>,/devtools/<id>,/clipboard/<id>), -
log file names and artifact history entries.
The header is intended for routers (e.g., gridlane) that need to encode routing metadata into the public session ID before the upgrade reaches selenwright — otherwise the router’s advertised ID would not match the one selenwright stored, and side endpoints would 404.
The value must match [a-zA-Z0-9_-]{1,128}; invalid values and collisions with existing sessions are rejected with 400 Bad Request before any browser container is started.
If the header is absent or blank, selenwright falls back to an internally generated 32-hex ID (the original behavior); standalone Playwright clients are unaffected.
3.2. Video Recording
|
Selenwright can capture browser screen and save it to MPEG-4 video file using H264 codec. Video recording works by attaching a separate container with video capturing software to running browser container.
To use this feature you should:
-
Pull video recorder image once:
$ docker pull selenwright-video-recorder:latest-release -
When running Selenwright in Docker container:
-
Mount a host directory to
/opt/selenwright/video/to persist recordings. -
Pass
OVERRIDE_VIDEO_OUTPUT_DIRwith the host-side absolute path to the same directory. The video recorder runs in a separate container and needs the host path to write files correctly.
-
-
When running Selenwright as a binary — videos are stored in the
-video-output-dirdirectory (defaultvideo) relative to the working directory.Example Docker Command$ docker run -d \ --name selenwright \ -p 4444:4444 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /data/video/:/opt/selenwright/video/ \ -e OVERRIDE_VIDEO_OUTPUT_DIR=/data/video/ \ selenwright/hub:latest-release
3.2.1. Downloading Video Files from Selenwright
You can access recorded video files using the following URL:
http://selenwright-host.example.com:4444/video/<filename>.mp4
Direct link will work only after session has finished because Selenwright renames temporary filename to <session-id>.mp4 (by default) at the session close.
|
To see all available files use:
http://selenwright-host.example.com:4444/video/
3.2.2. Deleting Video Files
When Artifact History is enabled, video files are automatically cleaned up by the retention janitor. Otherwise, to limit storage space consumption you have two alternatives:
-
Schedule scripts automatically removing files older than desired date and time. An example command under Unix operating systems can look like:
Shell Command to Remove Old Video Files$ find /path/to/video/dir -mindepth 1 -maxdepth 1 -mmin +120 -name '*.mp4' | xargs rm -rf
Notice
-mmin +120argument meaning to only process files older than 2 hours (120 minutes). -
Send video removal requests (e.g. from passed test cases). Just use
DELETEHTTP method and Selenwright video URL:Deleting Video File via HTTP API$ curl -X DELETE http://selenwright-host.example.com:4444/video/<filename>.mp4
3.3. Saving Session Logs
|
An additional |
Selenwright can save log files for every running session to a separate file. By default log files are saved as <session-id>.log but you can alter file name via logName capability.
To enable this feature you only need to add -log-output-dir </path/to/some/dir> flag to Selenwright:
$ ./selenwright -log-output-dir /path/to/some/dir
When running Selenwright in Docker container - don’t forget to mount logs directory from the host machine:
$ docker run -d \ --name selenwright \ -p 4444:4444 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /data/logs/:/opt/selenwright/logs/ \ selenwright/hub:latest-release -log-output-dir /opt/selenwright/logs
3.3.1. Downloading Log Files from Selenwright
You can access saved log files using the following URL:
http://selenwright-host.example.com:4444/logs/<filename>.log
Direct link will work only after session has finished because Selenwright renames temporary filename to <session-id>.log (by default) at the session close.
|
To see all available files use:
http://selenwright-host.example.com:4444/logs/
3.3.2. Deleting Log Files
When Artifact History is enabled, log files are automatically cleaned up by the retention janitor. Otherwise, either remove log files from the directory using a scheduled job or send log removal requests:
$ curl -X DELETE http://selenwright-host.example.com:4444/logs/<filename>.log
3.4. Uploading Files To Browser
Some tests require to upload files. This feature works out of the box in the majority of Selenium clients. A typical Java code snippet look like the following:
// Find file input element
WebElement input = driver.findElement(By.cssSelector("input[type='file']"));
// Make sure element is visible
((JavascriptExecutor) driver).executeScript("arguments[0].style.display = 'block';", input);
// Configure your client to upload local files to remote Selenium instance
driver.setFileDetector(new LocalFileDetector());
// Specify you local file path here (not path inside browser container!)
input.sendKeys("/path/to/file/on/machine/which/runs/tests");
from selenium.webdriver.remote.file_detector import LocalFileDetector
# ...
input = driver.find_element_by_css_selector("input[type='file']")
driver.execute_script("arguments[0].style.display = 'block';", input)
driver.file_detector = LocalFileDetector()
input.send_keys("/path/to/file/on/machine/which/runs/tests")
var filePath = path.join('/path/to/file/on/machine/which/runs/tests');
var remoteFilePath = browser.uploadFile(filePath);
$("input[type='file']").setValue(remoteFilePath);
Read the following note if you are using Selenwright without Docker.
This feature is supported in WebDriver protocol by sending zipped file contents to /file handle. However not all driver binaries support this feature (e.g. Geckodriver). When proxying requests directly to drivers (i.e. when not using Docker) you need to start Selenwright with -enable-file-upload flag. In that case Selenwright will provide the required API to tests. Firefox container images already include this parameter where needed.
|
3.4.1. HTTP Endpoint
When Selenwright is started with -enable-file-upload, an additional route is registered:
Method, path |
|
Content-Type |
|
Request body |
|
Response |
JSON |
Auth |
Same middleware as |
The zip archive must contain exactly one file. Selenwright extracts it into a per-session temp directory and returns that path.
If the flag is not set the route is not registered and the server replies 404 Not Found. Selenium clients that use LocalFileDetector will transparently fall back to the driver-level upload when that happens — you usually do not need to handle 404 yourself.
|
POST body cap, default 256 MiB. Affects the base64-wrapped payload size over the wire. |
|
Cap on the uncompressed size of the archive contents, default 1 GiB. Protects against zip-bomb style payloads. |
Both limits reject over-sized requests with 400 Invalid Argument and close the connection — the zip is never written to disk past the limit.
3.5. Downloading Files From Browser
The RemoteWebDriver examples below assume anonymous access for readability. When selenwright has authentication enabled (the default), supply an API token via ClientConfig.addHeader("Authorization", "Bearer " + token) in Java or the equivalent in your WebDriver binding, instead of embedding user:pass@ in the URL. See API Tokens (for Machine Clients).
|
3.5.1. Downloading Files in Different Browsers
Chrome
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setExperimentalOption("prefs", new HashMap<String, Object>(){
{
put("profile.default_content_settings.popups", 0);
put("download.default_directory", "/home/selenium/Downloads");
put("download.prompt_for_download", false);
put("download.directory_upgrade", true);
put("safebrowsing.enabled", false);
put("plugins.always_open_pdf_externally", true);
put("plugins.plugins_disabled", new ArrayList<String>(){
{
add("Chrome PDF Viewer");
}
});
}
});
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), chromeOptions);
driver.navigate().to("http://example.com/myfile.odt");
Firefox
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setCapability("moz:firefoxOptions", new HashMap<String, Object>(){
{
put("prefs", new HashMap<String, Object>(){
{
put("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream");
}
});
}
});
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), firefoxOptions);
driver.navigate().to("http://example.com/myfile.odt");
3.5.2. Accessing Files Downloaded with Browser
|
Your tests may need to download files with browsers. To analyze these files a common requirement is then to somehow extract downloaded files from browser containers. A possible solution can be dealing with container volumes. But Selenwright provides a /download API and dramatically simplifies downloading files. Having a running session f2bcd32b-d932-4cdc-a639-687ab8e4f840 you can access all downloaded files using an HTTP request:
GET http://selenwright-host.example.com:4444/download/f2bcd32b-d932-4cdc-a639-687ab8e4f840/myfile.txt
In order for this feature to work an HTTP file server should be listening inside browser container on port 8080. Download directory inside container to be used in tests is usually ~/Downloads.
Similarly to delete downloaded files replace GET request with DELETE:
DELETE http://selenwright-host.example.com:4444/download/f2bcd32b-d932-4cdc-a639-687ab8e4f840/myfile.txt
3.6. Accessing Clipboard
| Clipboard is accessible only when browser session is running. |
Sometimes you may need to interact with the clipboard to check that your application copy-paste feature works. Selenwright has a dedicated API to interact with the clipboard. To use it:
-
Start a new session, for example with ID
f2bcd32b-d932-4cdc-a639-687ab8e4f840. -
To get clipboard value send the following HTTP request:
$ curl http://selenwright-host.example.com:4444/clipboard/f2bcd32b-d932-4cdc-a639-687ab8e4f840 some-clipboard-value -
To update clipboard value:
$ curl -X POST --data 'some-clipboard-value' http://selenwright-host.example.com:4444/clipboard/f2bcd32b-d932-4cdc-a639-687ab8e4f840
3.7. Accessing Browser Developer Tools
|
Selenwright is proxying Chrome Developer Tools API to browser container. For every running Selenium session to access the API just use the following URL:
<ws or http>://selenwright.example.com:4444/devtools/<session-id>/<method>
Here <method> is one of the following:
Method |
Protocol |
Meaning |
/browser |
WebSocket |
Developer Tools browser websocket |
/ |
WebSocket |
An alias for /browser |
/page |
WebSocket |
Developer Tools current page (target) websocket - mainly useful when only one browser tab is open |
/page/<target-id> |
WebSocket |
Developer Tools specified page (target) websocket - allows to connect to concrete browser tab |
/json/protocol |
HTTP |
A list of supported Developer Tools protocol methods in JSON format (some client libraries are using it) |
For example an URL to connect to current page websocket would be:
ws://selenwright.example.com:4444/devtools/<session-id>/page
3.8. Special Capabilities
Selenwright can improve some automation aspects with custom capabilities. You can pass it in tests to enable/disable some features.
3.8.1. Live Browser Screen: enableVNC
Selenwright supports showing browser screen during test execution. This works with containers having VNC server installed (VNC column of [Browser Image information]). To see browser screen:
enableVNC: true
Then launch Selenwright UI to see the screen.
This works by proxying VNC port from started container to http://localhost:4444/vnc/<session-id> to WebSocket, where <session-id> is Selenium session ID.
3.8.2. Custom Screen Resolution: screenResolution
Selenwright allows you to set custom screen resolution in containers being run:
screenResolution: "1280x1024x24"
|
3.8.3. Video Recording: enableVideo, videoName, videoScreenSize, videoFrameRate, videoCodec
| This feature requires some preparation. Please refer to Video Recording section for more details. |
To enable video recording for session, add:
enableVideo: true
-
By default saved video files are named
<session-id>.mp4where<session-id>is a unique identifier of Selenium session. To provide custom video name specify:Type: stringvideoName: "my-cool-video.mp4"
It is important to add mp4file extension. -
By default the entire screen picture is being recorded. Specifying
screenResolutioncapability changes recorded video size (width and height) accordingly. You can override video screen size by passing a capability. In case ofvideoScreenSizeresolution is less than actual, screen on video will be trimmed starting from top-left corner:Type: stringvideoScreenSize: "1024x768"
-
Default video frame rate is
12frames per second. SpecifyingvideoFrameRatecapability changes this value:Type: intvideoFrameRate: 24
-
By default Selenwright is using
libx264codec for video output. If this codec is consuming too much CPU, you can change it usingvideoCodeccapability:Type: stringvideoCodec: "mpeg4"
To obtain the full list of supported values:
$ docker run -it --rm --entrypoint /usr/bin/ffmpeg selenwright-video-recorder:latest-release -codecs
3.8.4. Saving Session Logs: enableLog, logName
| This feature requires some preparation. Please refer to Saving Session Logs section for more details. |
To enable saving logs for a session, add:
enableLog: true
If you wish to automatically save logs for all sessions, you can enable this behavior globally with Selenwright -save-all-logs flag.
By default saved log files are named <session-id>.log where <session-id> is a unique identifier of Selenium session.
To provide custom log file name specify:
logName: "my-cool-log.log"
It is important to add log file extension.
|
3.8.5. Custom Test Name: name
For debugging purposes it is often useful to give a distinct name to every test case. When working with Selenwright you can set test case name by passing the following capability:
name: "myCoolTestName"
The main application of this capability - is debugging tests in the UI which is showing specified name for every running session.
3.8.6. Custom Session Timeout: sessionTimeout
Sometimes you may want to change idle timeout for selected browser session. To achieve this - pass the following capability:
sessionTimeout: 30m
Timeout is specified Golang duration format e.g. 30m or 10s or 1h5m and can be no more than the value set by -max-timeout flag.
3.8.7. Per-session Time Zone: timeZone
Some tests require particular time zone to be set in operating system. To achieve this with Selenwright use:
timeZone: "Europe/Berlin"
You can find most of available time zones here. Without this capability launched browser containers will have the same timezone as Selenwright one.
3.8.8. Per-session Container Hostname: containerHostname
Override the hostname of the browser container:
containerHostname: "my-browser-host"
3.8.9. Per-session Environment Variables: env
Sometimes you may want to set some environment variables for every test case (for example to test with different default locales). To achieve this pass one more capability:
env: ["LANG=ru_RU.UTF-8", "LANGUAGE=ru:en", "LC_ALL=ru_RU.UTF-8"]
Environment variables from this capability are appended to variables from configuration file.
Under -caps-policy=strict (default), the env, dnsServers, hostsEntries, additionalNetworks, and applicationContainers capabilities are restricted to admin users. See Authentication and Authorization for details.
|
3.8.10. Links to Application Containers: applicationContainers
Sometimes you may need to link browser container to application container running on the same host machine.
This allows you to use cool URLs like http://my-cool-app/ in tests.
To achieve this simply pass information about one or more desired links via capability:
applicationContainers: ["spring-application-main:my-cool-app", "spring-application-gateway"]
3.8.11. Hosts Entries: hostsEntries
Although you can configure a separate list of /etc/hosts entries for every browser image in Browsers Configuration File
sometimes you may need to add more entries for particular test cases. This can be easily achieved with:
hostsEntries: ["example.com:192.168.0.1", "test.com:192.168.0.2"]
Entries will be inserted to /etc/hosts before entries from browsers configuration file.
Thus entries from capabilities override entries from configuration file if some hosts are equal.
3.8.12. Custom DNS Servers: dnsServers
By default Selenwright browser containers are using global DNS settings of Docker daemon. Sometimes you may need to override used DNS servers list for particular test cases. This can be easily achieved with:
dnsServers: ["192.168.0.1", "192.168.0.2"]
3.8.13. More Docker Networks: additionalNetworks
By default Selenwright browser containers are started in Docker network specified by -container-network flag. If you tested application is running in another network you may need to connect browser container to this network:
additionalNetworks: ["my-custom-net-1", "my-custom-net-2"]
3.8.14. Container Labels: labels
In big clusters you may want to pass additional metadata to every browser session: environment, VCS revision, build number and so on. These labels can be then used to enrich session logs and send them to a centralized log storage. Later this metadata can be used for more efficient search through logs.
labels: {"environment": "testing", "build-number": "14353"}
Labels from this capability override labels from browsers configuration file. When name capability is specified - it is automatically added as a label to container.
3.8.15. S3 Key Pattern: s3KeyPattern
This capability allows to override S3 key pattern (specified by -s3-key-pattern flag) used when uploading files to S3.
s3KeyPattern: "$quota/$fileType$fileExtension"
The same key placeholders are supported. Please refer to Uploading Files To S3 section for more details.
3.8.16. Specifying Capabilities via Protocol Extensions
Some Selenium clients allow passing only a limited number of capabilities specified in WebDriver specification. For such cases Selenwright supports reading capabilities using WebDriver protocol extensions feature. The following two examples deliver the same result. Usually capabilities are passed like this:
{"browserName": "firefox", "version": "57.0", "screenResolution": "1280x1024x24"}
Selenwright reads the selenoid:options key for protocol extension capabilities (kept for backward compatibility with existing test suites):
{"browserName": "firefox", "version": "57.0", "selenoid:options": {"screenResolution": "1280x1024x24"}}
4. Advanced Features
4.1. Usage Statistics
Selenwright calculates usage statistics that can be accessed with HTTP request:
$ curl http://localhost:4444/status
{
"total": 80,
"used": 10,
"queued": 0,
"pending": 1,
"browsers": {
"chromium": {
"1.56.1": {
"alice": {
"count":1,
"sessions":[
{
"id": "a7a2b801-21db-4dae-a99b-4cbc0b81de96",
"vnc": false,
"screen": "1920x1080x24"
}
]
},
"jenkins-bot": {
"count":6,
"sessions":[
//...
]
}
}
},
"firefox": {
"1.56.1": {
"jenkins-bot": {
"count":3,
"sessions":[
//...
]
}
}
}
}
}
Users come from the authenticated identity — htpasswd or bearer token (embedded mode) or the X-Forwarded-User header (trusted-proxy mode). See Authentication and Authorization.
4.1.1. What Statistics Mean
-
A new session request arrives to Selenwright.
-
Selenwright
-limitflag specifies how many sessions can be created simultaneously. It is shown astotalin statistics. When requests reach the limit - subsequent requests are placed in queue.Queuedrequests just block and continue to wait. -
When there is a free slot for request Selenwright decides whether a Docker container or standalone driver process should be created. All requests during startup are marked as
pending. Before proceeding to next step Selenwright waits for required port to be open. -
When a container or driver is started Selenwright does a new session request just in the same way as standard Selenium client.
-
Depending on what is returned as response on the previous step session is marked as
createdorfailed. Created and running sessions are also included tousedvalue. -
When a session is created Selenwright just proxies the rest of session requests to the same container or driver.
-
New session request can fail because of Selenium errors or issues with container / driver startup. In that case an error is returned to user.
| For richer monitoring, enable the built-in Prometheus endpoint — see Metrics and Observability. |
4.2. Uploading Files To S3
|
This feature only becomes available when Selenwright is compiled with $ go build -tags s3 |
Selenwright can upload recorded video and log files for every running session to S3-compatible storage.
To enable this feature you need to specify S3 access information as follows:
$ ./selenwright -s3-endpoint https://s3.us-east-2.amazonaws.com -s3-region us-east-2 -s3-bucket-name my-bucket -s3-access-key <your-access-key> -s3-secret-key <your-secret-key>
If you omit -s3-access-key and -s3-secret-key flags then environment variables and shared credentials file are automatically tried as a fallback.
|
By default uploaded file name is preserved, i.e. S3 path is /<session-id>.log or /myCustomVideo.mp4. You can optionally provide your custom key pattern using -s3-key-pattern flag and a set of placeholders:
| Placeholder | Meaning |
|---|---|
$browserName |
Replaced by Selenium browser name capability value |
$browserVersion |
Replaced by Selenium browser version capability value |
$date |
Replaced by current date, e.g. |
$fileName |
Replaced by source file name |
$fileExtension |
Replaced by source file extension |
$fileType |
Replaced by |
$platformName |
Replaced by Selenium platform capability value |
$quota |
Replaced by the authenticated user/quota name |
$sessionId |
Replaced by Selenium session ID |
For example, when launching Selenwright with -s3-key-pattern $browserName/$sessionId/log.txt files will be accessible as firefox/0ee0b48b-e29b-6749-b4f1-2277b8f8d6c5/log.txt. You can also override key pattern for every session with s3KeyPattern capability.
Sometimes you may want to upload only video files or files matching some complicated pattern or to not upload some files. To achieve this use -s3-include-files and -s3-exclude-files flags. These flags accept globs such as *.mp4.
4.3. Artifact History
Selenwright can track and persist session artifacts (logs, videos, browser downloads) with configurable retention. This enables operators to browse and retrieve artifacts from past sessions through the UI or API.
4.3.1. How It Works
-
When a session ends, Selenwright creates a manifest containing session metadata (browser, version, protocol, user, timestamps).
-
If the session produced artifacts (log files, video recordings), they are linked in the manifest.
-
For Selenium sessions, browser downloads from
/home/selenium/Downloadsinside the container are captured viadocker cpand stored alongside other artifacts. If the path does not exist (e.g. in Playwright images), the capture step is silently skipped. -
For Playwright sessions, file downloads are handled natively by the Playwright protocol — the file content is streamed to the client via WebSocket (
page.download()), so there is nothing to capture from the container. -
A background janitor process runs every 3 hours and removes artifacts older than the configured retention period.
4.3.2. Configuration
| Flag | Description |
|---|---|
|
Directory to store artifact history manifests and persisted downloads (default |
|
JSON file storing artifact history settings (default |
4.3.3. Settings Endpoint
GET /history/settings
Returns the current artifact history configuration:
-
enabled— whether artifact history is active -
retentionDays— how many days to keep artifacts (1—365, default 7)
PUT /history/settings
Content-Type: application/json
{"enabled": true, "retentionDays": 14}
Updates artifact history settings. Changes take effect immediately.
This endpoint does not require authentication (open path).
4.3.4. Browsing Artifacts
Persisted downloads are served at:
GET /downloads/ GET /downloads/?json
The ?json parameter returns a JSON listing instead of the HTML directory view. Individual files can be downloaded directly by path.
Log files and videos are available at their respective endpoints (/logs/, /video/) as documented in Saving Session Logs and Video Recording.
4.4. Browser Discovery
Selenwright can automatically discover browser images available on the Docker host by scanning image labels. This allows operators to manage the browser catalog from the UI without manually editing browsers.json.
4.4.1. How It Works
-
Selenwright scans all local Docker images for well-known labels that identify browser name and version.
-
Images that are not yet in the catalog appear as "discovered" (unadopted).
-
An admin user can adopt a discovered image to add it to the live browser catalog, or dismiss it to hide it from the list.
-
Adoption state is persisted to
adopted.jsoninside the-state-dirdirectory (defaultstate/). -
After adoption or dismissal, Selenwright rebuilds the in-memory browser catalog automatically.
4.4.2. API Endpoints
All mutation endpoints require admin access (see Authentication and Authorization).
List Discovered Images
GET /browsers/discovered
Returns a JSON array of Docker images with browser labels that have not yet been adopted. Each entry contains:
-
name— browser name (e.g. "chrome", "firefox") -
version— browser version -
digest— image digest (used as identifier) -
repoTags— image repository tags
Adopt an Image
POST /browsers/adopt
Content-Type: application/json
{"digest": "<image-digest>"}
Adds the image to the browser catalog and triggers a catalog rebuild.
4.5. Docker Compose Stack Management
When Selenwright runs as part of a Docker Compose deployment, it can inspect, pull, and recreate the stack services through a set of API endpoints. This allows the operator UI to manage updates without SSH access to the host.
4.5.1. How It Works
Selenwright detects its own Compose deployment by reading Docker container labels (com.docker.compose.project, com.docker.compose.service, etc.) from its own container. When these labels are present, the stack management endpoints become available.
4.5.2. API Endpoints
Stack Status
GET /stack/status
Returns the current state of the Compose stack:
-
available— whether Selenwright is running inside a detected Compose stack -
reason— explanation when stack management is not available -
projectName— Docker Compose project name -
services— array of service objects, each containing:-
service— service name -
image— image reference -
imageId/imageIdShort— full and short image digest -
containerId— running container ID -
status— container status -
created— container creation timestamp
-
Pull Images
POST /stack/pull
Pulls the latest images for all stack services. Returns per-service results:
-
service— service name -
image— image reference -
previousId— image digest before pull -
currentId— image digest after pull -
updated—truewhen a new image was pulled
Recreate Stack
POST /stack/recreate
Recreates the Docker Compose stack using docker compose up -d. This applies any newly pulled images. Returns the output from the compose command.
The pull and recreate operations use a helper container (docker:27-cli) to run Docker Compose commands with access to the host Docker socket.
|
4.6. Metrics and Observability
4.6.1. Prometheus Metrics
Selenwright can expose a Prometheus-compatible metrics endpoint for monitoring session usage and server health.
./selenwright -enable-metrics
| Flag | Description |
|---|---|
|
Expose the metrics endpoint (disabled by default) |
|
Path for the metrics endpoint (default |
-
Queue depth and pending requests
-
Active session count
-
Session duration histogram
-
Authentication failure counter (by auth mode)
-
Capability policy rejection counter
The metrics endpoint is not gated by the configured authenticator. It is expected to live behind the same network boundary as the Prometheus scraper.
4.6.2. Structured Logging
./selenwright -log-json
When -log-json is set, Selenwright emits logs as one JSON object per line with fields: event, request_id, fields, level, time. Default is the legacy bracketed text format.
4.6.3. Event Workers
./selenwright -event-workers=16
The -event-workers flag (default 16) controls the number of goroutines that dispatch session-lifecycle events (FileCreated, SessionStopped) to registered listeners such as S3 upload handlers. Bounds fan-out so a single slow listener cannot leak goroutines.
4.7. Saving Session Metadata
This feature only becomes available when Selenwright is compiled with metadata build tag (missing by default).
|
Selenwright can save session metadata to a separate JSON file. Main use case is to somehow post-process session logs, e.g. send them to map-reduce cluster. No additional configuration is needed to enable this feature. When enabled and -log-output-dir flag is set - Selenwright will automatically save JSON files <log-output-dir>/<session-id>.json with the following content:
{
"id": "62a4d82d-edf6-43d5-886f-895b77ff23b7",
"capabilities": {
"browserName": "chrome",
"version": "70.0",
"name": "MyCoolTest",
"screenResolution": "1920x1080x24"
},
"started": "2018-11-15T16:23:12.440916+03:00",
"finished": "2018-11-15T16:23:12.480928+03:00"
}
5. Security
5.1. Authentication and Authorization
Selenwright controls who can create browser sessions and what they can do. There are two roles: regular user and admin. By default authentication is enabled and requires a password file.
5.1.1. Quick Start: Adding Users
Selenwright uses a standard htpasswd file with bcrypt passwords. Create it with Docker:
docker run --rm httpd:alpine htpasswd -nbB alice MyPassword123 >> users.htpasswd docker run --rm httpd:alpine htpasswd -nbB bob AnotherPass456 >> users.htpasswd
This creates a file users.htpasswd with two users: alice and bob. To add more users later, run the same command again — it appends to the file.
Pass the -htpasswd flag pointing to this file. As a binary:
./selenwright -htpasswd users.htpasswd
As a Docker container — mount the file and pass the flag:
docker run -d --name selenwright \ -p 4444:4444 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd)/users.htpasswd:/etc/selenwright/users.htpasswd:ro \ selenwright/hub:latest-release \ -htpasswd /etc/selenwright/users.htpasswd
Or in Docker Compose:
services:
selenwright:
image: selenwright/hub:latest-release
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./users.htpasswd:/etc/selenwright/users.htpasswd:ro
command: ["-htpasswd", "/etc/selenwright/users.htpasswd"]
ports:
- "4444:4444"
Now alice and bob can create sessions using HTTP Basic Auth:
curl -u alice:MyPassword123 http://localhost:4444/status
5.1.2. API Tokens (for Machine Clients)
htpasswd passwords are the right fit for humans logging into the UI. For Playwright/Selenium clients running in CI or on a developer’s laptop, selenwright supports bearer tokens so credentials never need to be pasted into a URL (where they leak into shell history, logs, and screenshots).
An admin mints a token on behalf of a user, the user drops it into SELENWRIGHT_TOKEN (or however the client reads secrets), and the client sends Authorization: Bearer <token> with every request. The token inherits the owner’s groups at creation time, so group-based session ACL still applies.
Tokens are stored hashed (bcrypt) in <state-dir>/auth/tokens.json. Plaintext is shown to the admin once — distribute it through a secret manager (1Password, Bitwarden, Keybase). Never paste tokens into Slack, tickets, or email.
Getting Your First Admin Token
The curl in the next section authenticates with Authorization: Bearer <admin-token>. The very first admin token is produced by selenwright itself — where exactly depends on how you started the process:
| Started with… | First admin token comes from… |
|---|---|
|
No token is auto-generated. Log into the UI with an htpasswd account listed in |
|
The env value itself is your admin token. On first boot (empty token store) selenwright hashes it and persists it as |
Neither |
Selenwright generates one on first boot and prints it to stdout (example below). Save it — it will not be shown again. Lost it? Delete |
Dev-fallback stdout:
./selenwright -listen :4444
# [-] [INIT] [No htpasswd configured — generated initial admin token]
# [-] [INIT] ┌──────────────────────────────────────────────────────────┐
# [-] [INIT] │ swr_live_4f8a9c2e7b1d3f5e8a6c9b4d7e2f1a3c
# [-] [INIT] │ id=tok_... owner=admin name=initial
# [-] [INIT] │ Save it — won't be shown again.
# [-] [INIT] │ Hash stored at state/auth/tokens.json
# [-] [INIT] └──────────────────────────────────────────────────────────┘
Minting Tokens
Once the admin has a bearer token (see above), mint tokens for users via REST:
curl -X POST https://selen.corp.com/api/admin/tokens \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"owner":"alice","name":"laptop"}'
# → {"id":"tok_...","token":"swr_live_..."}
The same operation is available in the UI: log in as an admin, open Settings → API Tokens, click Create, pick the owner and a human-readable name (e.g. laptop-alice, ci-runner). Copy the plaintext from the modal — it will not be shown again.
Using Tokens
Playwright:
const browser = await chromium.connect({
wsEndpoint: 'wss://selen.corp.com/playwright/chromium/1.56.1',
headers: { Authorization: `Bearer ${process.env.SELENWRIGHT_TOKEN}` },
});
Selenium (ClientConfig in Java; equivalents in every WebDriver binding):
ClientConfig config = ClientConfig.defaultConfig()
.baseUri(URI.create("https://selen.corp.com/wd/hub"))
.addHeader("Authorization", "Bearer " + System.getenv("SELENWRIGHT_TOKEN"));
For WebSocket clients that cannot set custom headers (wscat, <img src>, some browser DevTools frontends), selenwright accepts a ?token=<plaintext> query parameter on WebSocket-upgrade paths only: /playwright/, /wd/hub/, /devtools/, /vnc/, /logs/. The parameter is stripped from the URL before the request reaches upstream handlers, so it does not land in container logs. URL-embedded credentials still leak into shell history, reverse-proxy access logs, and terminal scrollback — prefer the header form whenever the client supports it.
Revoking Tokens
A single token:
curl -X DELETE https://selen.corp.com/api/admin/tokens/<id> \ -H "Authorization: Bearer <admin-token>"
Every token owned by a user (offboarding):
curl -X DELETE 'https://selen.corp.com/api/admin/tokens?owner=alice' \ -H "Authorization: Bearer <admin-token>"
The UI exposes both operations on the same page. Revocation is immediate — in-flight requests that already passed the auth check continue to their natural end, but no new request will clear.
5.1.3. Making a User an Admin
By default all users are regular (non-admin). To promote users to admin, list them in -admin-users:
./selenwright -htpasswd users.htpasswd -admin-users=alice
Now alice is admin, bob is a regular user. Admins can:
-
Access any user’s sessions (VNC, logs, devtools)
-
Use restricted capabilities (
env,dnsServers,hostsEntries,additionalNetworks,applicationContainers) -
Adopt and dismiss browsers in the Browser Discovery UI
5.1.4. Updating Users Without Restart
Edit the htpasswd file (add/remove/change passwords), then send SIGHUP:
docker kill -s HUP selenwright
Selenwright reloads the file without dropping running sessions.
5.1.5. Disabling Authentication
You can disable auth entirely with either spelling:
./selenwright -auth-mode=none -listen=127.0.0.1:4444 ./selenwright --no-auth -listen=127.0.0.1:4444
--no-auth is a convenience alias for -auth-mode=none. Both are accepted on any listen address, including :4444. When the listen address is not a loopback interface, selenwright logs a warning that the service is reachable without authentication — the operator is responsible for network-level protection (firewall, overlay network, bastion, reverse proxy).
5.1.6. What Is Reachable Anonymously
When authentication is enabled (-auth-mode=embedded or -auth-mode=trusted-proxy), these endpoints are deliberately left open and do not require a credential:
| Path | Purpose |
|---|---|
|
Liveness probe. Returns |
|
Selenium WebDriver invalid-session error JSON. Used as a proxy fall-through target |
|
Plain-text build banner (e.g. |
|
Returns the authenticated identity or |
|
Embedded-mode cookie login/logout |
|
Prometheus scrape target. Listed in a separate open-paths set so it tracks the flag and stays gated otherwise |
Everything else — /status, /config, /history/settings, /downloads/, /stack/status, /wd/hub/, /playwright/, /vnc/, /video/, /logs/, /download/, /clipboard/, /devtools/, the admin token REST surface — requires a valid BasicAuth header, bearer token, or session cookie. An anonymous scrape of /status was previously possible and leaked live session IDs, container IPs, and owner quotas; it now returns 401.
Human-readable Prometheus/Grafana setups still work: Prometheus scrapes /metrics (the open one), which carries lifecycle counters and queue depth gauges but none of the per-session detail that /status exposes.
5.1.7. Authentication Modes
Controlled by -auth-mode (default: embedded).
embedded — Built-in BasicAuth
The default mode described above. Uses an htpasswd file with bcrypt hashes.
| Flag | Description |
|---|---|
|
Path to the bcrypt htpasswd file |
|
Comma-separated list of admin usernames |
|
Path to JSON group file (see Team/Group Sharing). Hot-reloaded on |
trusted-proxy — Behind a Reverse Proxy
Use this when an upstream proxy (nginx, Envoy, OAuth2 Proxy) handles authentication and passes the user identity via HTTP headers.
./selenwright \ -auth-mode=trusted-proxy \ -user-header=X-Forwarded-User \ -admin-header=X-Admin
| Flag | Description |
|---|---|
|
Header containing the authenticated username |
|
Header whose value |
|
Header containing a comma-separated list of group names (see Team/Group Sharing). Set to empty to disable group reading |
Without source trust validation (see below), any client can forge these headers — including -groups-header, which would let a caller escalate into any team’s sessions. Always combine with at least one source trust check.
|
5.1.8. Source Trust (Protecting the Proxy)
When using trusted-proxy mode, you need to ensure that only your proxy can reach Selenwright — otherwise anyone can set the X-Forwarded-User header. Source trust adds that protection. When multiple checks are configured, all must pass.
| Flag | What it checks |
|---|---|
|
Request must have |
|
Request source IP must be in the listed CIDR range |
|
Request must present a client certificate signed by this CA |
./selenwright \ -auth-mode=trusted-proxy \ -user-header=X-Forwarded-User \ -admin-header=X-Admin \ -trusted-proxy-secret=s3cret \ -trusted-proxy-cidr=10.0.0.0/8
Source trust configuration is reloaded on SIGHUP.
5.1.9. Capability Policy
Controlled by -caps-policy (default: strict).
Some capabilities give the user control over the container environment. Under strict policy, these are admin-only:
-
env— environment variables -
dnsServers— custom DNS servers -
hostsEntries— custom /etc/hosts entries -
additionalNetworks— extra Docker networks -
applicationContainers— sidecar containers
Regular users requesting these get a 400 error. Set -caps-policy=permissive to allow all users to use all capabilities (legacy behavior).
5.1.10. Session Ownership
Every session is bound to the user who created it:
-
Owner — full access to their own sessions (VNC, logs, devtools, downloads, terminate)
-
Group-mate — when the owner and the caller belong to at least one common group, the caller gets the same full access as the owner (see Team/Group Sharing)
-
Admin — full access to all sessions
-
Other users — 403 Forbidden
Ownership is enforced uniformly for both Selenium (DELETE /wd/hub/session/{id}) and Playwright (DELETE /playwright/session/{id}) termination, as well as for VNC, devtools, downloads, clipboard and live log endpoints.
5.1.11. Team/Group Sharing
The default user == owner rule is too narrow when sessions are created by a shared service account — for example, jenkins-bot launching tests from CI. Without groups, no human user can terminate those sessions through the UI except the admin.
Groups solve this. Members of the same group can manage each other’s sessions (including sessions owned by service accounts in the group). Admin behavior is unchanged.
Configuring Groups (embedded mode)
Supply a JSON file mapping group name to list of member usernames:
{
"qa-payments": ["alice", "bob", "jenkins-bot"],
"qa-growth": ["carol"]
}
Point to it with -groups-file:
./selenwright \ -htpasswd users.htpasswd \ -admin-users=root \ -groups-file=groups.json
The file is hot-reloaded on SIGHUP alongside the htpasswd file. Parse or validation errors are logged and the previous membership is kept.
Configuring Groups (trusted-proxy mode)
The upstream proxy passes the user’s groups as a comma-separated list in the -groups-header header (default X-Groups):
./selenwright \ -auth-mode=trusted-proxy \ -user-header=X-Forwarded-User \ -admin-header=X-Admin \ -groups-header=X-Groups \ -trusted-proxy-secret=s3cret
Example request from a trusted proxy:
X-Forwarded-User: alice X-Groups: qa-payments,qa-growth
Set -groups-header="" to disable group reading entirely. X-Groups is always stripped from incoming requests before the application handlers run (same treatment as X-Forwarded-User and X-Admin), so downstream clients cannot forge it — provided at least one source trust check is configured.
Snapshot Semantics
The groups of a session’s owner are captured at session creation time and stored on the session itself. Revoking group membership (editing the JSON file or changing the upstream’s X-Groups output) does not retroactively change ACL for sessions that are already running. New sessions created after the change see the new membership immediately.
This avoids race conditions mid-operation: a group-mate who is in the middle of terminating a session cannot lose access half-way through.
6. Configuration
6.1. Docker Settings
6.1.1. Container Resource Limits
You may want to limit memory and CPU consumption for each browser container. Use -mem and -cpu flags:
./selenwright -mem 128m -cpu 1.0
Values are specified in Docker format. -mem accepts units like 128m or 1g. -cpu is a float representing the number of CPUs per container.
These can also be set per-browser in the browser catalog — see Browsers Configuration File.
6.2. Browsers Configuration File
Selenwright can be configured with an optional JSON file that defines available browsers. When Browser Discovery is used, this file is not required — the browser catalog is built from adopted Docker image labels. The file serves as a fallback when no adopted images are found.
{
"firefox": { (1)
"default": "46.0", (2)
"versions": { (3)
"46.0": { (4)
"image": "example.com/firefox:46.0", (5)
"port": "4444", (6)
"tmpfs": {"/tmp": "size=512m"}, (7)
"path" : "/wd/hub", (8)
"protocol" : "webdriver", (9)
"volumes" : ["/from:/to:ro"], (10)
"env" : ["TZ=Europe/Berlin"], (11)
"hosts" : ["example.com:192.168.0.1"], (12)
"shmSize" : 268435456, (13)
"cpu" : "1.0", (14)
"mem" : "512m", (15)
},
"50.0" :{
// ...
}
}
},
"chrome": {
// ...
}
}
| 1 | Browser name |
| 2 | Default browser version |
| 3 | A list of available browser versions |
| 4 | Version name |
| 5 | Image name or driver binary command |
| 6 | Containers only. Port to proxy connections to, see below |
| 7 | Optional. Containers only. Add in-memory filesystem (tmpfs) to container, see below |
| 8 | Optional. Path relative to / where we request a new session, see below |
| 9 | Optional. Upstream protocol contract. Omit this field for default WebDriver behavior or set it to playwright for native Playwright images. |
| 10 | Optional. Containers only. Mount path from host machine to created container |
| 11 | Optional. Environment variables to be passed to browser container or driver process |
| 12 | Optional. Custom /etc/hosts entries to be passed to browser container in hostname:ip format. |
| 13 | Optional. Shared memory size in bytes to be set for container. Some browsers (e.g. Chrome) may work unstable when having insufficient shmSize. Default value is 256 megabytes. |
| 14 | Optional. Containers only. Container CPU limit in Docker units. |
| 15 | Optional. Containers only. Container memory limit in Docker units. |
This file represents a mapping between a list of supported browser versions and Docker container images or driver binaries.
|
To use custom browsers configuration file - use $ ./selenwright -conf /path/to/browsers.json |
6.2.1. Browser Name and Version
Browser name and version are just strings that are matched against Selenium desired capabilities:
-
browserName -
version
For native Playwright connections the same entries are selected from the request path:
-
<browser>in/playwright/<browser>/<playwright-version> -
<playwright-version>in/playwright/<browser>/<playwright-version>
If no version capability is present default version is used. When there is no exact version match we also try to match by prefix. That means version string in JSON should start with version string from capabilities.
"versions": {
"46.0": {
Version capability that will match:
version = 46 (46.0 starts with 46)
Will not match:
version = 46.1 (46.0 does not start with 46.1)
6.2.2. Image
Image by default is a string with container specification in Docker format (hub.example.com/project/image:tag).
Image must be already pulled.
Pulled from internal docker registry:
my-internal-docker-hub.example.com/playwright-chromium:latest
Pulled from hub.docker.com
selenwright/playwright-chromium:latest
Standalone Binary
If you wish to use a standalone binary instead of Docker container, then image field should contain command specification in square brackets:
"46.0": {
"image": ["/usr/bin/mybinary", "-arg1", "foo", "-arg2", "bar", "-arg3"],
"port": "4444"
},
Selenwright proxies connections to either Selenium server or standalone driver binary. Depending on operating system both can be packaged inside Docker container.
6.2.3. Other Optional Fields
"46.0": {
//...
"port": "",
"tmpfs": {"/tmp": "size=512m", "/var": "size=128m"},
"path" : "",
"protocol" : "",
"volumes": ["/from:/to", "/another:/one:ro"],
"env" : ["TZ=Europe/Berlin", "ONE_MORE_VARIABLE=itsValue"],
"hosts" : ["one.example.com:192.168.0.1", "two.example.com:192.168.0.2"],
"labels" : {"component": "frontend", "project": "my-project"},
"sysctl" : {"net.ipv4.tcp_timestamps": "2", "kern.maxprocperuid": "1000"},
"shmSize" : 268435456,
},
-
port (only for containers) - You should use
portfield to specify the real port inside container that container process (Selenium server, Selenwright or driver) will listen on. For native Playwright images set this to the Playwright server listener port. -
tmpfs (optional) - You may probably know that moving browser cache to in-memory filesystem (Tmpfs) can dramatically improve its performance. Selenwright can automatically attach one or more in-memory filesystems as volumes to Docker container being run. To achieve this define one or more mount points and their respective sizes in optional
tmpfsfield. -
path (optional) -
pathfield is needed to specify relative path to the URL where a new session is created (default is/). Which value to specify in this field depends on container contents. For example, most of Firefox containers have Selenium server inside - thus you need to specify/wd/hub. Chrome and Opera containers use web driver binary as entrypoint application which is accepting requests at/. Playwright images also default to/when the field is omitted, and the selected WebSocket path should stay fixed for a given image. -
protocol (optional) -
protocolselects the upstream contract used by Selenwright. Two values are supported:-
webdriver(default when omitted) — standard Selenium WebDriver protocol. Used for Chrome, Firefox, Opera and other Selenium-compatible images. -
playwright— native Playwright WebSocket protocol. Set this when the image runs a Playwright server. Clients connect via/playwright/<browser>/<version>WebSocket endpoint instead of the Selenium HTTP API.
-
-
volumes (optional) - This field allows to mount volumes from host machine to browser container. Should be specified as an array of Docker volume expressions:
/host/dir:/container/dir[:mode]. -
env (optional) - This field allows to set any environment variables in running container. Specified as an array of
NAME=valuepairs. -
hosts (optional) - This field allows to add custom
/etc/hostsentries to running container. Specified as an array ofhostname:ippairs. -
labels (optional) - This field allows to add custom labels to running container. Specified as an object of
"key": "value"pairs. -
sysctl (optional) - This field allows to adjust kernel parameters of running container. Specified as an object of
"key": "value"pairs. -
shmSize (optional) - Use it to override shared memory size for browser container.
-
publishAllPorts (optional) - When set to
true, publishes all exposed container ports to the host. Useful for debugging or when browser containers need to be accessed directly.
6.3. Logging Configuration File
By default Docker container logs are saved to host machine hard drive. When using Selenwright for local development that’s ok. But in big Selenium cluster you may want to send logs to some centralized storage like Logstash or Graylog. Docker provides such functionality by so-called logging drivers. An optional Selenwright logging configuration file allows to specify which logging driver to use globally for all started Docker containers with browsers. Configuration file has the following format:
{
"Type" : "<driver-type>", (1)
"Config" : { (2)
"key1" : "value1",
"key2" : "value2"
}
}
| 1 | is a supported Docker logging driver type like syslog, journald or awslogs. |
| 2 | Config is a list of key-value pairs used to configure selected driver. |
For example these Docker logging parameters:
--log-driver=syslog --log-opt syslog-address=tcp://192.168.0.42:123 --log-opt syslog-facility=daemon
... are equivalent to the following Selenwright logging configuration:
{
"Type" : "syslog",
"Config" : {
"syslog-address" : "tcp://192.168.0.42:123",
"syslog-facility" : "daemon"
}
}
|
To specify custom logging configuration file - use $ ./selenwright -log-conf /path/to/logging.json ... |
6.4. Reloading Configuration
To reload configuration without restart send SIGHUP:
# kill -HUP <pid>
# docker kill -s HUP <container-id-or-name>
| Use only one of these commands. |
To check Selenwright instance health use /ping:
$ curl -s http://example.com:4444/ping
{
"uptime": "2m46.854829503s",
"lastReloadTime": "2026-04-16T12:33:06+03:00",
"numRequests": 42,
"version": "HEAD"
}
It returns 200 OK when Selenwright operates normally. Fields: server uptime, last configuration reload time (RFC3339), total session requests since startup, and build version.
6.5. Updating Browsers
6.5.1. Using Browser Discovery (recommended)
-
Pull new browser images on the host:
$ docker pull selenwright/playwright-chromium:latest
-
Open the Browser Discovery UI and adopt the new images.
-
Selenwright rebuilds the in-memory catalog automatically — no restart needed.
6.5.2. Using Stack Management
When running via Docker Compose, use the Docker Compose Stack Management endpoints (or the UI) to pull updated images and recreate the stack.
6.5.3. Manual browsers.json
-
Edit
browsers.jsonand pull the new images. -
Reload without restart:
$ docker kill -s HUP selenwright
See Reloading Configuration for details.
6.6. Setting Timezone
When used in Docker container Selenwright will have timezone set to UTC.
To set custom timezone pass TZ environment variable to Docker:
$ docker run -d --name selenwright \
-p 4444:4444 \
-e TZ=Europe/Berlin \
-v /var/run/docker.sock:/var/run/docker.sock \
selenwright/hub:latest-release
This sets the timezone for the Selenwright process itself (affects log timestamps, /ping response, etc.). To set timezone per browser session, use the timeZone capability — see Per-session Time Zone: timeZone.
|
6.7. Selenwright with Docker Compose
In example Docker Compose configurations below we assume that you created directories for video and log storage:
$ mkdir -p /data/selenwright/video /data/selenwright/logs
A browsers.json file is not required — Selenwright discovers browser images via Docker labels. See Browser Discovery. If you prefer a manual catalog, add -conf /etc/selenwright/browsers.json to the command and mount the config directory.
|
6.7.1. Option 1. Running Selenwright in default Docker network
In that case bridge network mode should be used for all services:
version: '3'
services:
selenwright:
network_mode: bridge
image: selenwright/hub:latest-release
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/data/selenwright/video:/opt/selenwright/video"
- "/data/selenwright/logs:/opt/selenwright/logs"
environment:
- OVERRIDE_VIDEO_OUTPUT_DIR=/data/selenwright/video
command: ["-video-output-dir", "/opt/selenwright/video", "-log-output-dir", "/opt/selenwright/logs"]
ports:
- "4444:4444"
6.7.2. Option 2. Running Selenwright in custom Docker network
In that case you have to add -container-network flag to Selenwright as follows:
-
Create custom Docker network:
$ docker network create selenwright
-
Start Selenwright:
version: '3'
networks:
selenwright:
external:
name: selenwright # This assumes network is already created
services:
selenwright:
networks:
selenwright: null
image: selenwright/hub:latest-release
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/data/selenwright/video:/opt/selenwright/video"
- "/data/selenwright/logs:/opt/selenwright/logs"
environment:
- OVERRIDE_VIDEO_OUTPUT_DIR=/data/selenwright/video
command: ["-video-output-dir", "/opt/selenwright/video", "-log-output-dir", "/opt/selenwright/logs", "-container-network", "selenwright"]
ports:
- "4444:4444"
6.8. Log Files
6.8.1. Log Format
By default Selenwright uses a bracketed text format:
2026/04/16 12:34:56 [41301] [SESSION_CREATED] [alice] [a7a2b801-...] [chrome:130.0] [1] [2.15s]
Every line contains:
| Field | Description |
|---|---|
Time |
Timestamp in server timezone |
Request counter |
|
Status |
|
User / quota |
Authenticated username |
Session ID |
Unique session identifier |
Browser |
Name and version |
Duration |
Time taken for the operation |
Not all fields are present in every line — it depends on the event type.
6.8.2. JSON Format
Pass -log-json to emit structured logs:
{"event":"SESSION_CREATED","request_id":41301,"fields":{"user":"alice","session":"a7a2b801-..."},"level":"info","time":"2026-04-16T12:34:56+02:00"}
6.8.3. Common Log Statuses
Session lifecycle: NEW_REQUEST, NEW_REQUEST_ACCEPTED, SESSION_ATTEMPTED, SESSION_CREATED, SESSION_DELETED, SESSION_TIMED_OUT, SESSION_FAILED, CLIENT_DISCONNECTED
Container management: CREATING_CONTAINER, STARTING_CONTAINER, CONTAINER_STARTED, CONTAINER_REMOVED, SERVICE_STARTED, SERVICE_STARTUP_FAILED
Playwright: PLAYWRIGHT_SESSION_CREATED, PLAYWRIGHT_SESSION_STOPPED, PLAYWRIGHT_CLIENT_DISCONNECTED, PLAYWRIGHT_UPSTREAM_DISCONNECTED
Features: VNC_ENABLED, DEVTOOLS_*, DOWNLOADING_FILE, UPLOADED_FILE, VIDEO_ERROR, LOG_ERROR
Discovery: DISCOVERY — browser image scanning, adopt, dismiss, catalog rebuild
Server: INIT, SIGHUP, SHUTTING_DOWN
6.9. Selenwright CLI Flags
The following flags are supported by selenwright command.
6.9.1. Server & Network
-listen string
Network address to accept connections (default ":4444")
-allowed-origins string
Comma-separated list of allowed Origin values for WebSocket upgrades
(devtools, playwright, vnc, logs). Empty (default) keeps permissive
behavior; '*' is explicit allow-all. Recommended: configure to your
CI/QA hosts to defend against Cross-Site WebSocket Hijacking
6.9.2. Authentication
-auth-mode string
Authentication mode: 'embedded', 'trusted-proxy', or 'none'
(default "embedded"). 'none' is allowed on any listen address;
operator owns network-level protection. See
<<Authentication and Authorization>> for details
--no-auth
Alias for -auth-mode=none. Allowed on any listen address; operator
owns network-level protection
-htpasswd string
Path to bcrypt-format htpasswd file used by -auth-mode=embedded.
Generate with: htpasswd -B users.htpasswd alice. When omitted and
auth is enabled, selenwright boots in dev mode and prints a single
admin bearer token to stdout; pin it across restarts with the
SELENWRIGHT_AUTH_TOKEN env var
-admin-users string
Comma-separated list of usernames treated as admin in
-auth-mode=embedded
-user-header string
Header to read for authenticated user identity in
-auth-mode=trusted-proxy (default "X-Forwarded-User")
-admin-header string
Header in -auth-mode=trusted-proxy whose value 'true' marks the
request as administrative (default "X-Admin")
-groups-file string
Path to JSON file mapping team/group name to list of member
usernames, e.g. {"qa-payments":["alice","jenkins-bot"]}. Members of
the same group can manage each other's sessions (useful for CI
service accounts). Hot-reloaded on SIGHUP. Used with
-auth-mode=embedded
-groups-header string
Header to read CSV group names from in -auth-mode=trusted-proxy
(e.g. "qa-payments,qa-growth"). Empty disables reading groups from
headers (default "X-Groups")
-caps-policy string
Capability policy: 'strict' rejects dangerous caps (env, dnsServers,
hostsEntries, additionalNetworks, applicationContainers) for
non-admin callers; 'permissive' preserves legacy behavior
(default "strict")
6.9.3. Source Trust (Upstream Proxy Validation)
When set, these checks are AND’d together — all configured conditions must pass.
-trusted-proxy-secret string
Shared secret expected in X-Router-Secret header. Every request must
present this value or it is rejected with 401
-trusted-proxy-cidr string
Comma-separated CIDR allow-list for the source IP (e.g.
"10.0.0.0/8,192.168.1.0/24")
-trusted-proxy-mtls-ca string
Path to PEM bundle of CAs that issued the trusted client certificate.
Requires a verified mTLS client certificate on every request
6.9.4. Session Management
-limit int
Simultaneous container runs (default 5)
-timeout duration
Session idle timeout in time.Duration format (default 1m0s)
-max-timeout duration
Maximum valid session idle timeout in time.Duration format
(default 1h0m0s)
-session-attempt-timeout duration
New session attempt timeout in time.Duration format (default 30s)
-session-delete-timeout duration
Session delete timeout in time.Duration format (default 30s)
-service-startup-timeout duration
Service startup timeout in time.Duration format (default 30s)
-retry-count int
New session attempts retry count (default 1)
-graceful-period duration
Graceful shutdown period in time.Duration format (default 5m0s)
-disable-queue
Disable wait queue
6.9.5. Browser Configuration
-conf string
Browsers configuration file (default "config/browsers.json")
-log-conf string
Container logging configuration file
-default-enable-vnc
Default value for the enableVNC capability when a client does not
pass enableVNC. Clients can still opt out per session
6.9.6. Container Runtime
-disable-docker
Disable docker support (driver-only mode)
-container-network string
Network to be used for containers (default "default")
-browser-network string
Dedicated Docker network for browser containers. Created on startup
with Internal=true (no external gateway) to limit blast radius of
sandbox escapes. Set empty to disable isolation and revert to
-container-network (default "selenwright-browsers")
-privileged
Run browser containers in privileged mode (default false)
-cap-add-sys-admin
Add the SYS_ADMIN Linux capability to browser containers without
full -privileged. Chrome's user-namespace sandbox requires it
-capture-driver-logs
Whether to add driver process logs to Selenwright output
-mem value
Containers memory limit e.g. 128m or 1g
-cpu value
Containers cpu limit as float e.g. 0.2 or 1.0
6.9.7. Video Recording
-video-output-dir string
Directory to save recorded video to (default "video")
-video-recorder-image string
Image to use as video recorder
(default "selenwright-video-recorder:latest-release")
6.9.8. Logs
-log-output-dir string
Directory to save session log to
-save-all-logs
Whether to save all logs without considering capabilities
-log-json
Emit logs as one JSON object per line (event, request_id, fields,
level, time). Default is the legacy bracketed text format
6.9.9. Artifact History
-artifact-history-dir string
Directory to store artifact history manifests and persisted downloads
(default "artifacts")
-artifact-history-settings string
JSON file storing artifact history settings (enabled, retentionDays)
(default "state/artifact-history.json")
-state-dir string
Directory for persistent state such as adopted browser set. Created
on startup if missing (default "state")
6.9.10. File Upload
-enable-file-upload
File upload support
See Uploading Files To Browser for the POST /file request format and size limits.
6.9.11. Request Limits
-max-create-body-bytes int
Maximum POST body size for /session create requests in bytes
(default 4194304 / 4 MiB)
-max-upload-body-bytes int
Maximum POST body size for /file upload requests in bytes
(default 268435456 / 256 MiB)
-max-upload-extracted-bytes int
Maximum total extracted size for /file uploaded zip archives in bytes
(default 1073741824 / 1 GiB)
-max-ws-message-bytes int
Maximum single WebSocket message size in bytes for Playwright,
DevTools, VNC and log streams. 0 disables the limit
(default 67108864 / 64 MiB)
6.9.12. Metrics & Observability
-enable-metrics
Expose a Prometheus-compatible /metrics endpoint (queue depth,
session counts, duration histogram, auth/caps rejection counters)
-metrics-path string
Path the Prometheus metrics endpoint is served on (default "/metrics")
-event-workers int
Number of worker goroutines that dispatch session-lifecycle events
(FileCreated, SessionStopped) (default 16)
6.9.14. Environment Variables
| Variable | Purpose |
|---|---|
|
Plaintext bearer token to seed on first boot. Applied only when |
6.9.15. Usage Example
As a binary:
$ ./selenwright -limit 10
As a Docker container:
$ docker run -d --name selenwright \
-p 4444:4444 \
-v /var/run/docker.sock:/var/run/docker.sock \
selenwright/hub:latest-release \
-limit 10
6.9.16. S3 CLI Flags
The following flags are supported by selenwright command when compiled with S3 support:
-s3-access-key string
S3 access key
-s3-bucket-name string
S3 bucket name
-s3-endpoint string
S3 endpoint URL
-s3-exclude-files string
Pattern used to match and exclude files
-s3-force-path-style
Force path-style addressing for file upload
-s3-include-files string
Pattern used to match and include files
-s3-keep-files
Do not remove uploaded files
-s3-key-pattern string
S3 bucket name (default "$fileName")
-s3-reduced-redundancy
Use reduced redundancy storage class
-s3-region string
S3 region
-s3-secret-key string
S3 secret key