craftdriver 1.4.0 → 1.6.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/CHANGELOG.md +17 -3
- package/README.md +6 -2
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +4 -0
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +4 -0
- package/dist/cli/mcp/server.js.map +1 -1
- package/dist/cli/parseArgs.d.ts +2 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +13 -0
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +81 -32
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/browser.d.ts +48 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +212 -86
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +2 -2
- package/dist/lib/browserContext.d.ts.map +1 -1
- package/dist/lib/browserContext.js +1 -1
- package/dist/lib/builder.d.ts +16 -2
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +46 -2
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/capabilities.d.ts +20 -2
- package/dist/lib/capabilities.d.ts.map +1 -1
- package/dist/lib/capabilities.js +109 -48
- package/dist/lib/capabilities.js.map +1 -1
- package/dist/lib/driver.d.ts +33 -2
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +91 -2
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +28 -0
- package/dist/lib/driverManager.d.ts.map +1 -1
- package/dist/lib/driverManager.js +60 -0
- package/dist/lib/driverManager.js.map +1 -1
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +34 -0
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +31 -10
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/launchTarget.d.ts +22 -3
- package/dist/lib/launchTarget.d.ts.map +1 -1
- package/dist/lib/launchTarget.js +159 -9
- package/dist/lib/launchTarget.js.map +1 -1
- package/dist/lib/remote.d.ts +55 -0
- package/dist/lib/remote.d.ts.map +1 -0
- package/dist/lib/remote.js +127 -0
- package/dist/lib/remote.js.map +1 -0
- package/dist/lib/safari.d.ts +90 -0
- package/dist/lib/safari.d.ts.map +1 -0
- package/dist/lib/safari.js +136 -0
- package/dist/lib/safari.js.map +1 -0
- package/dist/lib/tracing.d.ts +1 -1
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js.map +1 -1
- package/dist/lib/types.d.ts +63 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/vibiumTrace.d.ts +1 -1
- package/dist/lib/vibiumTrace.d.ts.map +1 -1
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +118 -2
- package/dist/lib/webelement.js.map +1 -1
- package/docs/api-reference.md +6 -2
- package/docs/browser-api.md +5 -4
- package/docs/driver-configuration.md +60 -0
- package/docs/getting-started.md +30 -10
- package/docs/index.md +2 -1
- package/docs/keyboard-mouse.md +7 -5
- package/docs/public/examples/displayed.html +86 -0
- package/docs/recipes/run-on-browserstack.md +265 -0
- package/docs/recipes.md +15 -1
- package/docs/remote-webdriver.md +308 -0
- package/docs/safari.md +118 -0
- package/docs/standards.md +18 -0
- package/docs/why-craftdriver.md +3 -1
- package/docs/zero-config-drivers.md +12 -0
- package/package.json +5 -1
- package/skills/craftdriver/SKILL.md +4 -1
- package/skills/craftdriver/cheatsheet.md +4 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Run On BrowserStack
|
|
2
|
+
|
|
3
|
+
A provider-specific walkthrough of [Remote WebDriver](../remote-webdriver.md)
|
|
4
|
+
against [BrowserStack](https://www.browserstack.com). craftdriver's core has
|
|
5
|
+
no BrowserStack-specific code — everything here is the generic `remote`
|
|
6
|
+
launch option plus BrowserStack's own `bstack:options` vendor capabilities,
|
|
7
|
+
which pass straight through untouched. If you're targeting a different
|
|
8
|
+
provider or a self-hosted Grid, start with the
|
|
9
|
+
[Remote WebDriver](../remote-webdriver.md) page instead; this recipe assumes
|
|
10
|
+
you've read it.
|
|
11
|
+
|
|
12
|
+
The desktop and real-device examples use the hosted
|
|
13
|
+
[login example](https://dtopuzov.github.io/craftdriver/examples/login.html).
|
|
14
|
+
The BrowserStack Local example uses a URL reachable through your own tunnel.
|
|
15
|
+
|
|
16
|
+
## Credentials
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
export BROWSERSTACK_USERNAME=your_username
|
|
20
|
+
export BROWSERSTACK_ACCESS_KEY=your_access_key
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Get these from BrowserStack's Automate dashboard. Never commit them — read
|
|
24
|
+
them from the environment, as shown throughout this page.
|
|
25
|
+
|
|
26
|
+
## Desktop Chrome (primary example)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Browser } from 'craftdriver';
|
|
30
|
+
|
|
31
|
+
const browser = await Browser.launch({
|
|
32
|
+
browserName: 'chrome',
|
|
33
|
+
enableBiDi: true,
|
|
34
|
+
remote: {
|
|
35
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
36
|
+
auth: {
|
|
37
|
+
username: process.env.BROWSERSTACK_USERNAME!,
|
|
38
|
+
password: process.env.BROWSERSTACK_ACCESS_KEY!,
|
|
39
|
+
},
|
|
40
|
+
// Allow time for BrowserStack to provision the session.
|
|
41
|
+
sessionTimeoutMs: 120_000,
|
|
42
|
+
commandTimeoutMs: 120_000,
|
|
43
|
+
capabilities: {
|
|
44
|
+
browserVersion: 'latest',
|
|
45
|
+
'bstack:options': {
|
|
46
|
+
os: 'Windows',
|
|
47
|
+
osVersion: '11',
|
|
48
|
+
projectName: 'CraftDriver',
|
|
49
|
+
buildName: 'Remote smoke',
|
|
50
|
+
sessionName: 'Chrome login flow',
|
|
51
|
+
seleniumBidi: true,
|
|
52
|
+
seleniumVersion: '4.20.0',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.html');
|
|
60
|
+
await browser.getByLabel('Username').fill('alice');
|
|
61
|
+
await browser.getByLabel('Password').fill('secret');
|
|
62
|
+
await browser.getByRole('button', { name: 'Sign in' }).click();
|
|
63
|
+
await browser.expect('#welcome').toContainText('Welcome back, alice!');
|
|
64
|
+
} finally {
|
|
65
|
+
await browser.quit();
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
BiDi requires both craftdriver's `enableBiDi: true` and BrowserStack's
|
|
70
|
+
`seleniumBidi: true`. The Selenium version above follows BrowserStack's BiDi
|
|
71
|
+
setup example; use the version in BrowserStack's current
|
|
72
|
+
[BiDi guide](https://www.browserstack.com/docs/automate/selenium/bidi-event-driven-testing) or
|
|
73
|
+
Capability Generator when they differ. After launch,
|
|
74
|
+
`browser.isBiDiEnabled()` tells you whether the provider returned a usable
|
|
75
|
+
BiDi endpoint.
|
|
76
|
+
|
|
77
|
+
**Timeouts matter more remotely.** `sessionTimeoutMs` bounds only the initial
|
|
78
|
+
`POST /session` request (30 seconds when omitted). `commandTimeoutMs` sets the
|
|
79
|
+
default for subsequent Classic WebDriver HTTP commands. BiDi commands use
|
|
80
|
+
their own WebSocket timeouts and are not bounded by `commandTimeoutMs`.
|
|
81
|
+
|
|
82
|
+
## Concise variants
|
|
83
|
+
|
|
84
|
+
### Desktop Safari (Classic only)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const browser = await Browser.launch({
|
|
88
|
+
browserName: 'safari',
|
|
89
|
+
// Remote Safari defaults to Classic.
|
|
90
|
+
remote: {
|
|
91
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
92
|
+
auth: { username: process.env.BROWSERSTACK_USERNAME!, password: process.env.BROWSERSTACK_ACCESS_KEY! },
|
|
93
|
+
capabilities: {
|
|
94
|
+
'bstack:options': { os: 'OS X', osVersion: 'Sonoma', projectName: 'CraftDriver' },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Android Chrome (real device)
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const browser = await Browser.launch({
|
|
104
|
+
browserName: 'chrome',
|
|
105
|
+
remote: {
|
|
106
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
107
|
+
auth: { username: process.env.BROWSERSTACK_USERNAME!, password: process.env.BROWSERSTACK_ACCESS_KEY! },
|
|
108
|
+
capabilities: {
|
|
109
|
+
'bstack:options': { deviceName: 'Samsung Galaxy S23', osVersion: '13.0', realMobile: true },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### iPhone Safari (real device)
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const browser = await Browser.launch({
|
|
119
|
+
browserName: 'safari',
|
|
120
|
+
remote: {
|
|
121
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
122
|
+
auth: { username: process.env.BROWSERSTACK_USERNAME!, password: process.env.BROWSERSTACK_ACCESS_KEY! },
|
|
123
|
+
capabilities: {
|
|
124
|
+
'bstack:options': { deviceName: 'iPhone 15', osVersion: '17', realMobile: true },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
craftdriver doesn't model devices at all — **BrowserStack owns the real-device
|
|
131
|
+
catalog, and you pick from it.** Set `deviceName`/`osVersion`/`realMobile` in
|
|
132
|
+
`bstack:options` and craftdriver forwards them untouched; there is no
|
|
133
|
+
craftdriver device list to keep in sync. Pick valid values from BrowserStack's
|
|
134
|
+
own sources, not from memory:
|
|
135
|
+
|
|
136
|
+
- [Devices & platform grid](https://www.browserstack.com/list-of-browsers-and-platforms/automate)
|
|
137
|
+
— the live catalog of every real device, OS version, and browser available
|
|
138
|
+
for Automate.
|
|
139
|
+
- [Capabilities reference](https://www.browserstack.com/docs/automate/capabilities)
|
|
140
|
+
— every `bstack:options` key, plus an interactive Capability Generator that
|
|
141
|
+
emits the exact capability object for a device you choose.
|
|
142
|
+
|
|
143
|
+
A fleet's exact devices and OS versions change over time, so verify current
|
|
144
|
+
names against those pages before pinning them in CI.
|
|
145
|
+
|
|
146
|
+
`gesture.swipe()` and `gesture.pinch()` send W3C touch-pointer actions.
|
|
147
|
+
Whether those actions are supported is determined by the selected real
|
|
148
|
+
device, browser, and BrowserStack. Derive gesture coordinates from
|
|
149
|
+
`window.innerWidth` and `window.innerHeight`; a remote driver can reject
|
|
150
|
+
coordinates outside the device viewport with `move target out of bounds`.
|
|
151
|
+
Assert the page behavior caused by each gesture instead of treating a
|
|
152
|
+
no-error command as proof that the page received it.
|
|
153
|
+
|
|
154
|
+
## Running a platform list sequentially and in parallel
|
|
155
|
+
|
|
156
|
+
The three variants above are just different `capabilities`; running a list of
|
|
157
|
+
them is a loop over that list plus [Remote WebDriver](../remote-webdriver.md#parallel-remote-sessions)'s
|
|
158
|
+
same concurrency guarantee — each session gets its own connection pool, so
|
|
159
|
+
quitting one never disrupts another running against the same hub.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const platforms = [
|
|
163
|
+
{ browserName: 'chrome', 'bstack:options': { os: 'Windows', osVersion: '11' } },
|
|
164
|
+
{ browserName: 'safari', 'bstack:options': { os: 'OS X', osVersion: 'Sonoma' } },
|
|
165
|
+
{ browserName: 'chrome', 'bstack:options': { deviceName: 'Samsung Galaxy S23', realMobile: true } },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
// Sequentially — one BrowserStack session (and its metered minute) at a time.
|
|
169
|
+
for (const { browserName, ...capabilities } of platforms) {
|
|
170
|
+
const browser = await Browser.launch({
|
|
171
|
+
browserName,
|
|
172
|
+
remote: { url: hubUrl, auth, capabilities },
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
await runSmokeFlow(browser);
|
|
176
|
+
} finally {
|
|
177
|
+
await browser.quit();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// In parallel — respect your BrowserStack plan's concurrent-session limit.
|
|
182
|
+
await Promise.all(
|
|
183
|
+
platforms.map(async ({ browserName, ...capabilities }) => {
|
|
184
|
+
const browser = await Browser.launch({
|
|
185
|
+
browserName,
|
|
186
|
+
remote: { url: hubUrl, auth, capabilities },
|
|
187
|
+
});
|
|
188
|
+
try {
|
|
189
|
+
await runSmokeFlow(browser);
|
|
190
|
+
} finally {
|
|
191
|
+
await browser.quit();
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Running in parallel is useful only up to your plan's concurrent-session
|
|
198
|
+
limit. BrowserStack may queue additional requests within your plan's queue
|
|
199
|
+
allowance and reject requests beyond that allowance, so bound application
|
|
200
|
+
concurrency instead of relying on an unlimited provider queue.
|
|
201
|
+
|
|
202
|
+
## BrowserStack Local for internal/private sites
|
|
203
|
+
|
|
204
|
+
craftdriver does not manage the BrowserStack Local process — provider
|
|
205
|
+
infrastructure stays outside the generic library API, the same way
|
|
206
|
+
craftdriver doesn't manage BrowserStack's Grid itself. You run the tunnel;
|
|
207
|
+
craftdriver just launches a session that's told to use it.
|
|
208
|
+
|
|
209
|
+
1. Run `BrowserStackLocal` yourself, on a machine/CI runner that can reach
|
|
210
|
+
your internal site, with a unique identifier:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
./BrowserStackLocal --key $BROWSERSTACK_ACCESS_KEY --local-identifier ci-run-42
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
2. Wait for it to report ready (its own stdout, or the
|
|
217
|
+
[`browserstack-local` npm package](https://www.npmjs.com/package/browserstack-local)'s
|
|
218
|
+
callback if you're launching it programmatically) before starting the
|
|
219
|
+
session below — a session that requests `local: true` before the tunnel is
|
|
220
|
+
up simply can't reach the internal site.
|
|
221
|
+
|
|
222
|
+
3. Set `local: true` in `bstack:options`, matching `localIdentifier` if you
|
|
223
|
+
have multiple tunnels running (e.g. parallel CI jobs):
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const browser = await Browser.launch({
|
|
227
|
+
browserName: 'chrome',
|
|
228
|
+
remote: {
|
|
229
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
230
|
+
auth: { username: process.env.BROWSERSTACK_USERNAME!, password: process.env.BROWSERSTACK_ACCESS_KEY! },
|
|
231
|
+
capabilities: {
|
|
232
|
+
acceptInsecureCerts: true, // only if the internal site requires it
|
|
233
|
+
'bstack:options': { local: true, localIdentifier: 'ci-run-42' },
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await browser.navigateTo('http://localhost:3000/internal-dashboard');
|
|
240
|
+
// ...
|
|
241
|
+
} finally {
|
|
242
|
+
await browser.quit();
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
4. Configure tunnel behavior such as `--force-local` and proxy settings on
|
|
247
|
+
the `BrowserStackLocal` binary. `acceptInsecureCerts` is a standard
|
|
248
|
+
top-level WebDriver capability, as shown above. Follow BrowserStack's
|
|
249
|
+
[Local testing docs](https://www.browserstack.com/docs/automate/selenium/local-testing-introduction)
|
|
250
|
+
for the current tunnel flags.
|
|
251
|
+
|
|
252
|
+
**Honest caveat:** the remote browser does not automatically inherit your
|
|
253
|
+
test runner's VPN connection just because the tunnel is running somewhere —
|
|
254
|
+
BrowserStack Local specifically proxies traffic through the machine running
|
|
255
|
+
`BrowserStackLocal`, so that machine (not the machine running your test
|
|
256
|
+
script) is what needs network access to the internal site.
|
|
257
|
+
|
|
258
|
+
## See also
|
|
259
|
+
|
|
260
|
+
- [Remote WebDriver](../remote-webdriver.md) — the generic, provider-neutral
|
|
261
|
+
page this recipe builds on.
|
|
262
|
+
- [File Uploads And Downloads](./file-upload-download.md) — desktop remote
|
|
263
|
+
sessions can upload through Selenium's `se/file` extension; see
|
|
264
|
+
[Remote WebDriver → Remote file uploads](../remote-webdriver.md#remote-file-uploads)
|
|
265
|
+
for what's different under the hood.
|
package/docs/recipes.md
CHANGED
|
@@ -18,7 +18,10 @@ setup.
|
|
|
18
18
|
|
|
19
19
|
The [Electron recipes](#electron) are different: they show the project shape for
|
|
20
20
|
driving a packaged desktop app, so they use paths you adapt to your app repo
|
|
21
|
-
rather than the hosted browser examples.
|
|
21
|
+
rather than the hosted browser examples. The
|
|
22
|
+
[BrowserStack recipe](#external-providers) is also different: it needs real
|
|
23
|
+
provider credentials. Its live test is credential-gated, runs only for trusted
|
|
24
|
+
repository events, and is skipped for forks.
|
|
22
25
|
|
|
23
26
|
For exact signatures, use the linked feature docs and the
|
|
24
27
|
[API reference](./api-reference.md).
|
|
@@ -55,6 +58,17 @@ For exact signatures, use the linked feature docs and the
|
|
|
55
58
|
| Console and JavaScript errors | Tests should fail if the browser reports unexpected client-side errors. | [Fail On Console And JavaScript Errors](./recipes/console-error-gate.md) |
|
|
56
59
|
| Debug failing tests | You need the actions, screenshots, logs, and network activity behind a failure. | [Use Traces To Debug Failing Tests](./recipes/debug-failing-tests-with-traces.md) |
|
|
57
60
|
|
|
61
|
+
## External Providers
|
|
62
|
+
|
|
63
|
+
Running against a remote Grid or cloud provider instead of a local browser.
|
|
64
|
+
This recipe needs provider credentials. Its live test is credential-gated,
|
|
65
|
+
runs on trusted pushes and pull requests, and is skipped for forks — see
|
|
66
|
+
[`tests/recipes/README.md`](https://github.com/dtopuzov/craftdriver/blob/main/tests/recipes/README.md).
|
|
67
|
+
|
|
68
|
+
| Scenario | Use when | Recipe |
|
|
69
|
+
| -------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
|
70
|
+
| Run on BrowserStack | You want to run against real browsers/devices in the cloud. | [Run On BrowserStack](./recipes/run-on-browserstack.md) |
|
|
71
|
+
|
|
58
72
|
## Electron
|
|
59
73
|
|
|
60
74
|
Driving a packaged Electron desktop app. These use paths you adapt to your app
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Remote WebDriver
|
|
2
|
+
|
|
3
|
+
`Browser.launch({ remote })` connects to a W3C-compatible remote WebDriver
|
|
4
|
+
endpoint — a self-hosted Selenium Grid, BrowserStack, or another cloud
|
|
5
|
+
provider — instead of starting a local browser/driver process. You use the
|
|
6
|
+
same `Browser` API after launch. Feature availability still depends on the
|
|
7
|
+
browser, protocol, Grid configuration, and provider; BiDi-only features such
|
|
8
|
+
as network interception require the endpoint to return a working
|
|
9
|
+
`webSocketUrl`.
|
|
10
|
+
|
|
11
|
+
craftdriver's core has **zero provider-specific code**. `remote` speaks plain
|
|
12
|
+
W3C `POST /session` + Basic auth + opaque capability passthrough; a provider's
|
|
13
|
+
own vendor capabilities (BrowserStack's `bstack:options`, or another
|
|
14
|
+
provider's equivalent) pass straight through `remote.capabilities` untouched.
|
|
15
|
+
See the [BrowserStack recipe](./recipes/run-on-browserstack.md) for a
|
|
16
|
+
provider-specific walkthrough — this page stays generic.
|
|
17
|
+
|
|
18
|
+
> **Not available from the CLI or MCP server.** Both are local dev tools —
|
|
19
|
+
> they help you write and debug tests against a browser on your own machine,
|
|
20
|
+
> not execute against a Grid or cloud. Passing `remote` to either fails fast
|
|
21
|
+
> with a clear error at startup. Use the `Browser.launch({ remote })` library
|
|
22
|
+
> API directly from your own script/test runner instead.
|
|
23
|
+
|
|
24
|
+
## Local vs. remote session lifecycle
|
|
25
|
+
|
|
26
|
+
A local launch starts a driver process (`chromedriver`/`geckodriver`/
|
|
27
|
+
`safaridriver`) on your machine, then creates a WebDriver session against it.
|
|
28
|
+
A remote launch skips that local process entirely — no driver download, no
|
|
29
|
+
downloads directory, no browser-binary resolution — and creates the session
|
|
30
|
+
directly against the endpoint you give it:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Browser } from 'craftdriver';
|
|
34
|
+
|
|
35
|
+
const browser = await Browser.launch({
|
|
36
|
+
browserName: 'chrome',
|
|
37
|
+
remote: {
|
|
38
|
+
url: 'http://localhost:4444', // Selenium Grid 4 default
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await browser.navigateTo('https://example.com');
|
|
43
|
+
// ...
|
|
44
|
+
await browser.quit(); // DELETEs the remote session
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`browser.quit()` closes an active BiDi connection and deletes the WebDriver
|
|
48
|
+
session. There is no local driver process for craftdriver to stop remotely.
|
|
49
|
+
|
|
50
|
+
## A plain Selenium Grid, no provider-specific capabilities
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const browser = await Browser.launch({
|
|
54
|
+
browserName: 'firefox',
|
|
55
|
+
remote: {
|
|
56
|
+
url: 'http://grid.internal:4444',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Nothing above is BrowserStack-specific. Selenium Grid 4 uses the root URL by
|
|
62
|
+
default. If your Grid is configured with a non-root base path, include that
|
|
63
|
+
path in `remote.url`; craftdriver preserves it.
|
|
64
|
+
|
|
65
|
+
## Authentication
|
|
66
|
+
|
|
67
|
+
Pass credentials via `remote.auth`, or embed them in `remote.url` for
|
|
68
|
+
Selenium-migration compatibility (`https://user:pass@host/...`) — not both;
|
|
69
|
+
craftdriver rejects the combination rather than silently picking one.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const browser = await Browser.launch({
|
|
73
|
+
browserName: 'chrome',
|
|
74
|
+
remote: {
|
|
75
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
76
|
+
auth: {
|
|
77
|
+
username: process.env.BROWSERSTACK_USERNAME!,
|
|
78
|
+
password: process.env.BROWSERSTACK_ACCESS_KEY!,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Credentials are sent as a standard `Authorization: Basic ...` header on every
|
|
85
|
+
request to the remote endpoint. craftdriver never logs `remote.auth`, never
|
|
86
|
+
includes it in a thrown error's message or `detail`, and never forwards it to
|
|
87
|
+
a BiDi WebSocket connection (some providers proxy `webSocketUrl` through a
|
|
88
|
+
different host than the REST endpoint — Basic auth is tied to the REST
|
|
89
|
+
origin and is never reused there).
|
|
90
|
+
|
|
91
|
+
**Secret-handling guidance:** treat `remote.auth`/URL-embedded credentials
|
|
92
|
+
like any other secret — read them from environment variables, never commit
|
|
93
|
+
them, and never log the `remote` object itself (its `auth` field and any
|
|
94
|
+
credential embedded in `url` are exactly what you don't want in a CI log).
|
|
95
|
+
|
|
96
|
+
## Capabilities: standard and vendor-namespaced
|
|
97
|
+
|
|
98
|
+
`remote.capabilities` is opaque W3C JSON. craftdriver validates that it is an
|
|
99
|
+
object and merges it under `alwaysMatch`; vendor namespaces such as
|
|
100
|
+
`bstack:options` pass through without schema conversion. craftdriver fills in
|
|
101
|
+
`browserName` and its BiDi request defaults only when those values are absent.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const browser = await Browser.launch({
|
|
105
|
+
browserName: 'chrome',
|
|
106
|
+
remote: {
|
|
107
|
+
url: 'https://hub.browserstack.com/wd/hub',
|
|
108
|
+
auth: { username: '...', password: '...' },
|
|
109
|
+
capabilities: {
|
|
110
|
+
browserVersion: 'latest',
|
|
111
|
+
'bstack:options': {
|
|
112
|
+
os: 'Windows',
|
|
113
|
+
osVersion: '11',
|
|
114
|
+
projectName: 'CraftDriver',
|
|
115
|
+
buildName: 'Remote smoke',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If `remote.capabilities` also sets `browserName`, it must agree
|
|
123
|
+
(case-insensitively) with the top-level `browserName` — craftdriver rejects a
|
|
124
|
+
genuine conflict rather than silently picking one.
|
|
125
|
+
|
|
126
|
+
### Headless and other browser flags on a remote session
|
|
127
|
+
|
|
128
|
+
A local launch honors the `HEADLESS` env var and injects `--headless=new` for
|
|
129
|
+
you. A **remote** launch deliberately does not — the remote path is opaque
|
|
130
|
+
passthrough and never adds browser flags itself (it can't assume which browser
|
|
131
|
+
or vendor-option shape the endpoint runs). Pass headless (and any other flag)
|
|
132
|
+
through the browser's own vendor capability:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const browser = await Browser.launch({
|
|
136
|
+
browserName: 'chrome',
|
|
137
|
+
remote: {
|
|
138
|
+
url: 'http://grid.internal:4444',
|
|
139
|
+
capabilities: {
|
|
140
|
+
'goog:chromeOptions': { args: ['--headless=new', '--no-sandbox', '--disable-dev-shm-usage'] },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Use `moz:firefoxOptions.args` (`-headless`) for Firefox and `ms:edgeOptions`
|
|
147
|
+
for Edge. This is how a Grid runs browsers headless on a CI runner with no
|
|
148
|
+
display — no Xvfb required.
|
|
149
|
+
|
|
150
|
+
## Browser names: not limited to craftdriver's local whitelist
|
|
151
|
+
|
|
152
|
+
Locally, craftdriver only knows how to launch `chrome`, `chromium`,
|
|
153
|
+
`firefox`, and `safari` — those are the only browsers craftdriver itself
|
|
154
|
+
knows how to spawn a driver process for. Remotely, a Grid or provider may
|
|
155
|
+
offer Edge, older browser versions, or anything else it supports, so
|
|
156
|
+
`browserName` accepts any non-empty string for a remote launch and defaults
|
|
157
|
+
to `'chrome'` when omitted.
|
|
158
|
+
|
|
159
|
+
## Classic/BiDi negotiation for remote
|
|
160
|
+
|
|
161
|
+
The default mirrors local policy: `chrome`, `chromium`, `firefox`, and
|
|
162
|
+
`edge`/`microsoftedge` default to BiDi on (`enableBiDi !== false`); `safari`
|
|
163
|
+
and any browser name craftdriver doesn't recognize default to Classic,
|
|
164
|
+
requiring an explicit `enableBiDi: true` to opt in. Unlike local Safari
|
|
165
|
+
(where `enableBiDi: true` is rejected outright), an unrecognized remote
|
|
166
|
+
browser name is allowed to opt in — craftdriver just won't assume BiDi works
|
|
167
|
+
for a name it doesn't know, but it also won't refuse the attempt.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const browser = await Browser.launch({
|
|
171
|
+
browserName: 'MicrosoftEdge',
|
|
172
|
+
remote: { url: 'https://hub.example.com/wd/hub' },
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If the remote endpoint doesn't actually return a `webSocketUrl` in its
|
|
177
|
+
session-create response, craftdriver simply never attempts a BiDi connection
|
|
178
|
+
— no error, no retry, the session runs Classic. If a BiDi connection is
|
|
179
|
+
attempted and fails after retries, craftdriver falls back to Classic and logs
|
|
180
|
+
a warning with the WebSocket URL's query string, fragment, and any embedded
|
|
181
|
+
credentials stripped before logging.
|
|
182
|
+
|
|
183
|
+
Whether a remote endpoint returns a `webSocketUrl` is up to that endpoint.
|
|
184
|
+
Selenium Grid can proxy WebDriver BiDi when Grid BiDi proxying is enabled and
|
|
185
|
+
the selected node/browser supports it. This project verifies that path with
|
|
186
|
+
Selenium Grid 4.46.0 and Chrome. Administrators can disable the proxy, and a
|
|
187
|
+
provider may require additional capabilities, so check
|
|
188
|
+
`browser.isBiDiEnabled()` before depending on BiDi-only APIs.
|
|
189
|
+
|
|
190
|
+
## Session and command timeouts
|
|
191
|
+
|
|
192
|
+
Real remote sessions run over a network, not a loopback socket to a process
|
|
193
|
+
craftdriver just spawned — set generous timeouts:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const browser = await Browser.launch({
|
|
197
|
+
remote: {
|
|
198
|
+
url: 'https://hub.example.com/wd/hub',
|
|
199
|
+
sessionTimeoutMs: 120_000, // POST /session — session creation only
|
|
200
|
+
commandTimeoutMs: 120_000, // applied as the default for every command after
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`sessionTimeoutMs` bounds only the initial session-creation request.
|
|
206
|
+
`commandTimeoutMs` becomes the default for every WebDriver command sent over
|
|
207
|
+
that session afterward (navigate, find, click, evaluate, ...), unless a
|
|
208
|
+
specific call already passes its own timeout.
|
|
209
|
+
|
|
210
|
+
A timed-out `POST /session` is **not retried** — a client-side timeout on
|
|
211
|
+
session creation doesn't mean the remote end failed; it may have already
|
|
212
|
+
created the session. Blind retry risks creating a second, paid, orphaned
|
|
213
|
+
session on a metered provider, so craftdriver creates a remote session
|
|
214
|
+
exactly once and surfaces the failure if it doesn't succeed. (Local sessions
|
|
215
|
+
still retry — that logic exists for local driver-process readiness lag, which
|
|
216
|
+
doesn't apply here.)
|
|
217
|
+
|
|
218
|
+
## Parallel remote sessions
|
|
219
|
+
|
|
220
|
+
Many concurrent sessions against one hub/host is the normal case for a
|
|
221
|
+
Grid or cloud provider — unlike a local launch, where every `DriverService`
|
|
222
|
+
picks its own port. craftdriver gives each remote session its own
|
|
223
|
+
keep-alive connection pool, so quitting one session never disrupts another
|
|
224
|
+
session's in-flight requests, even when they share the same host:port.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const [a, b] = await Promise.all([
|
|
228
|
+
Browser.launch({ browserName: 'chrome', remote: { url: hubUrl } }),
|
|
229
|
+
Browser.launch({ browserName: 'firefox', remote: { url: hubUrl } }),
|
|
230
|
+
]);
|
|
231
|
+
try {
|
|
232
|
+
await Promise.all([a.navigateTo(url1), b.navigateTo(url2)]);
|
|
233
|
+
} finally {
|
|
234
|
+
await a.quit();
|
|
235
|
+
await b.quit(); // unaffected by a.quit() completing first
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Supported / unsupported feature matrix
|
|
240
|
+
|
|
241
|
+
The Classic/BiDi split still applies — see
|
|
242
|
+
[WebDriver Standards](./standards.md). Remote endpoints add these constraints:
|
|
243
|
+
|
|
244
|
+
| Feature | Remote behavior |
|
|
245
|
+
|---|---|
|
|
246
|
+
| File upload (`setInputFiles()`) | Supported when the endpoint implements Selenium's `se/file` extension; see below. Mobile/cloud sessions may require a provider-specific upload mechanism. |
|
|
247
|
+
| Downloads (`waitForDownload()`) | **Unsupported.** Throws `UNSUPPORTED` immediately (not a timeout) — a remote session has no client-visible downloads directory. Use your provider's own download/artifact API if it has one. |
|
|
248
|
+
| Desktop browsers, Edge, unrecognized names | Can be requested; the endpoint decides whether the browser/version is available. See [Browser names](#browser-names-not-limited-to-craftdrivers-local-whitelist). |
|
|
249
|
+
| Real mobile browsers (Android Chrome, iOS Safari) | Device selection is provider-controlled. Pass documented device capabilities through `remote.capabilities`. `gesture.swipe()` and `gesture.pinch()` use W3C touch-pointer actions, but support is determined by the selected device/browser/provider; verify the gestures your suite relies on. |
|
|
250
|
+
|
|
251
|
+
## Remote file uploads
|
|
252
|
+
|
|
253
|
+
`setInputFiles()` works transparently on a remote session — you still pass a
|
|
254
|
+
local file path:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
await browser.find('input[type=file]').setInputFiles('/local/path/report.csv');
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Under the hood, a local path cannot be used directly by a remote node.
|
|
261
|
+
craftdriver zips the file, calls Selenium's
|
|
262
|
+
`POST /session/{id}/se/file` extension, and sends the returned remote path to
|
|
263
|
+
the input. This works for desktop Grid/provider sessions that implement that
|
|
264
|
+
extension. Check your provider's documentation for mobile sessions or
|
|
265
|
+
endpoints that do not implement `se/file`.
|
|
266
|
+
|
|
267
|
+
## Provider tunnels for private/internal sites
|
|
268
|
+
|
|
269
|
+
craftdriver doesn't manage a provider's tunnel process (e.g. BrowserStack
|
|
270
|
+
Local) — that stays outside the generic library API, the same way craftdriver
|
|
271
|
+
doesn't manage the Grid/hub itself. Start the tunnel yourself (or in CI)
|
|
272
|
+
before launching, then pass whatever capability the provider documents for
|
|
273
|
+
routing through it (e.g. `local: true` in BrowserStack's `bstack:options`).
|
|
274
|
+
See the [BrowserStack recipe](./recipes/run-on-browserstack.md#browserstack-local-for-internalprivate-sites)
|
|
275
|
+
for a worked example.
|
|
276
|
+
|
|
277
|
+
## Troubleshooting
|
|
278
|
+
|
|
279
|
+
**Authentication failures** — double check `remote.auth` isn't also set
|
|
280
|
+
alongside URL-embedded credentials (craftdriver rejects that combination at
|
|
281
|
+
launch, before any network call); confirm the credential env vars are
|
|
282
|
+
actually set in the process running your tests, not just your shell.
|
|
283
|
+
|
|
284
|
+
**Hub base paths** — Selenium Grid 4 uses its root URL by default, for example
|
|
285
|
+
`http://grid.internal:4444`. If your deployment configures a base path (or
|
|
286
|
+
uses a legacy `/wd/hub` path), include it in `remote.url`; craftdriver
|
|
287
|
+
preserves the path you pass.
|
|
288
|
+
|
|
289
|
+
**Capability mismatch** — for a `session not created` response, inspect the
|
|
290
|
+
provider's error and validate the browser, OS, version, and vendor capability
|
|
291
|
+
names against its current capability generator or platform catalog.
|
|
292
|
+
|
|
293
|
+
**BiDi connectivity** — if you expect BiDi but `browser.isBiDiEnabled()` is
|
|
294
|
+
`false`, confirm the provider returned a `webSocketUrl` for the session. Some
|
|
295
|
+
providers require their own opt-in capability in addition to craftdriver's
|
|
296
|
+
`enableBiDi`; BrowserStack currently documents `seleniumBidi: true`. Also
|
|
297
|
+
confirm that your network permits outbound WebSocket connections to the
|
|
298
|
+
returned host.
|
|
299
|
+
|
|
300
|
+
## See also
|
|
301
|
+
|
|
302
|
+
- [BrowserStack recipe](./recipes/run-on-browserstack.md) — a full,
|
|
303
|
+
provider-specific walkthrough with desktop, mobile, and BrowserStack Local
|
|
304
|
+
examples.
|
|
305
|
+
- [Driver Configuration](./driver-configuration.md) — local-only; this page
|
|
306
|
+
is the remote counterpart.
|
|
307
|
+
- [WebDriver Standards](./standards.md) — the Classic/BiDi split this page
|
|
308
|
+
builds on.
|