craftdriver 1.4.0 → 1.5.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +10 -3
  2. package/README.md +4 -2
  3. package/dist/cli/parseArgs.d.ts +2 -2
  4. package/dist/cli/parseArgs.d.ts.map +1 -1
  5. package/dist/cli/parseArgs.js +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/bidi/storage.d.ts +13 -0
  11. package/dist/lib/bidi/storage.d.ts.map +1 -1
  12. package/dist/lib/bidi/storage.js +81 -32
  13. package/dist/lib/bidi/storage.js.map +1 -1
  14. package/dist/lib/browser.d.ts +27 -1
  15. package/dist/lib/browser.d.ts.map +1 -1
  16. package/dist/lib/browser.js +142 -74
  17. package/dist/lib/browser.js.map +1 -1
  18. package/dist/lib/browserContext.d.ts +2 -2
  19. package/dist/lib/browserContext.d.ts.map +1 -1
  20. package/dist/lib/browserContext.js +1 -1
  21. package/dist/lib/builder.d.ts +4 -1
  22. package/dist/lib/builder.d.ts.map +1 -1
  23. package/dist/lib/builder.js +20 -2
  24. package/dist/lib/builder.js.map +1 -1
  25. package/dist/lib/capabilities.d.ts +12 -2
  26. package/dist/lib/capabilities.d.ts.map +1 -1
  27. package/dist/lib/capabilities.js +84 -39
  28. package/dist/lib/capabilities.js.map +1 -1
  29. package/dist/lib/driver.d.ts +16 -1
  30. package/dist/lib/driver.d.ts.map +1 -1
  31. package/dist/lib/driver.js +64 -0
  32. package/dist/lib/driver.js.map +1 -1
  33. package/dist/lib/driverManager.d.ts +28 -0
  34. package/dist/lib/driverManager.d.ts.map +1 -1
  35. package/dist/lib/driverManager.js +60 -0
  36. package/dist/lib/driverManager.js.map +1 -1
  37. package/dist/lib/launchTarget.d.ts +1 -1
  38. package/dist/lib/launchTarget.d.ts.map +1 -1
  39. package/dist/lib/launchTarget.js +65 -3
  40. package/dist/lib/launchTarget.js.map +1 -1
  41. package/dist/lib/safari.d.ts +90 -0
  42. package/dist/lib/safari.d.ts.map +1 -0
  43. package/dist/lib/safari.js +136 -0
  44. package/dist/lib/safari.js.map +1 -0
  45. package/dist/lib/tracing.d.ts +1 -1
  46. package/dist/lib/tracing.d.ts.map +1 -1
  47. package/dist/lib/tracing.js.map +1 -1
  48. package/dist/lib/types.d.ts +41 -0
  49. package/dist/lib/types.d.ts.map +1 -1
  50. package/dist/lib/vibiumTrace.d.ts +1 -1
  51. package/dist/lib/vibiumTrace.d.ts.map +1 -1
  52. package/dist/lib/webelement.d.ts.map +1 -1
  53. package/dist/lib/webelement.js +118 -2
  54. package/dist/lib/webelement.js.map +1 -1
  55. package/docs/api-reference.md +3 -1
  56. package/docs/driver-configuration.md +54 -0
  57. package/docs/getting-started.md +30 -10
  58. package/docs/index.md +1 -1
  59. package/docs/public/examples/displayed.html +86 -0
  60. package/docs/safari.md +118 -0
  61. package/docs/standards.md +13 -0
  62. package/docs/why-craftdriver.md +1 -1
  63. package/docs/zero-config-drivers.md +12 -0
  64. package/package.json +3 -1
  65. package/skills/craftdriver/SKILL.md +4 -1
  66. package/skills/craftdriver/cheatsheet.md +4 -1
@@ -1,6 +1,106 @@
1
1
  import { HttpClient } from './http.js';
2
2
  export const W3C_ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf';
3
3
  export const LEGACY_ELEMENT_KEY = 'ELEMENT';
4
+ /**
5
+ * W3C-compatible "is element displayed" algorithm, executed via
6
+ * `POST /execute/sync`. Faithfully reproduces the behavior of Selenium's
7
+ * `bot.dom.isShown_` (the atom chromedriver and geckodriver's native
8
+ * `is displayed` commands are compiled from), so results match the browsers
9
+ * that currently answer via the legacy `/element/{id}/displayed` endpoint —
10
+ * while also working on Safari, whose command table omits that endpoint.
11
+ *
12
+ * Written in ES5-compatible style (plain `function`s, `var`, `for` loops) to
13
+ * match the other scripts sent to `/execute/sync` in this codebase and to run
14
+ * safely across every supported Classic remote end. Receives the element as
15
+ * `arguments[0]` and returns a boolean.
16
+ *
17
+ * Rules, each mirroring the corresponding `bot.dom` behavior:
18
+ * - `<input type="hidden">` and `<noscript>` are never shown.
19
+ * - `display:none` on the element OR any ancestor hides it. `display` is not
20
+ * inherited, so the ancestor chain must be walked explicitly.
21
+ * - `visibility:hidden`/`collapse` hides it. `visibility` IS inherited, and a
22
+ * descendant may override an ancestor back to `visible`, so we check only the
23
+ * element's OWN computed value (getComputedStyle already resolves
24
+ * inheritance) — this both matches `bot.dom` and is more correct than walking
25
+ * the chain for visibility.
26
+ * - No positive-area box hides it, unless a child has one (zero-size wrappers)
27
+ * or it is a `<br>` (a legitimately zero-width line break). Mirrors
28
+ * `bot.dom.positiveSize_`.
29
+ * - Being clipped out of view by an `overflow:hidden` ancestor hides it,
30
+ * matching `bot.dom.OverflowState.HIDDEN`. An element scrolled out of a
31
+ * `scroll`/`auto` ancestor is still considered shown (it can be scrolled
32
+ * into view — `bot.dom.OverflowState.SCROLL`), so it is not treated as
33
+ * hidden here.
34
+ */
35
+ const IS_DISPLAYED_ATOM = `
36
+ var elem = arguments[0];
37
+ if (!elem || elem.nodeType !== 1) { return false; }
38
+
39
+ var win = elem.ownerDocument.defaultView || window;
40
+ function style(e, prop) {
41
+ var cs = win.getComputedStyle(e, null);
42
+ return cs ? cs.getPropertyValue(prop) : '';
43
+ }
44
+
45
+ // <input type="hidden"> is never shown (bot.dom.isShown_).
46
+ var tag = elem.tagName ? elem.tagName.toUpperCase() : '';
47
+ if (tag === 'INPUT' && (elem.type || '').toLowerCase() === 'hidden') { return false; }
48
+ // <noscript> is not rendered.
49
+ if (tag === 'NOSCRIPT') { return false; }
50
+
51
+ // display:none on the element or any ancestor hides it. display is NOT
52
+ // inherited, so the whole chain must be checked (bot.dom.displayed_).
53
+ var node = elem;
54
+ while (node && node.nodeType === 1) {
55
+ if (style(node, 'display') === 'none') { return false; }
56
+ node = node.parentElement;
57
+ }
58
+
59
+ // visibility:hidden/collapse hides it. visibility IS inherited but overridable
60
+ // by descendants, so checking the element's own computed value is correct.
61
+ var vis = style(elem, 'visibility');
62
+ if (vis === 'hidden' || vis === 'collapse') { return false; }
63
+
64
+ // An element with no positive-area box is not shown, unless a child has one
65
+ // (zero-size wrapper) or it is a <br> (bot.dom.positiveSize_).
66
+ function positiveSize(e) {
67
+ var r = e.getBoundingClientRect();
68
+ if (r.width > 0 && r.height > 0) { return true; }
69
+ if (e.tagName && e.tagName.toUpperCase() === 'BR') { return true; }
70
+ var kids = e.childNodes;
71
+ for (var i = 0; i < kids.length; i++) {
72
+ var k = kids[i];
73
+ if (k.nodeType === 1 && positiveSize(k)) { return true; }
74
+ }
75
+ return false;
76
+ }
77
+ if (!positiveSize(elem)) { return false; }
78
+
79
+ // Clipped out of view by an overflow:hidden ancestor => hidden
80
+ // (bot.dom.OverflowState.HIDDEN). scroll/auto ancestors are treated as shown
81
+ // (OverflowState.SCROLL — scrollable into view). position:fixed escapes
82
+ // ancestor clipping, so skip the check for fixed elements.
83
+ if (style(elem, 'position') !== 'fixed') {
84
+ var rect = elem.getBoundingClientRect();
85
+ var parent = elem.parentElement;
86
+ while (parent) {
87
+ var ox = style(parent, 'overflow-x');
88
+ var oy = style(parent, 'overflow-y');
89
+ if (ox === 'hidden' || oy === 'hidden' || ox === 'clip' || oy === 'clip') {
90
+ var pr = parent.getBoundingClientRect();
91
+ var clippedX = (ox === 'hidden' || ox === 'clip') &&
92
+ (rect.right <= pr.left || rect.left >= pr.right);
93
+ var clippedY = (oy === 'hidden' || oy === 'clip') &&
94
+ (rect.bottom <= pr.top || rect.top >= pr.bottom);
95
+ if (clippedX || clippedY) { return false; }
96
+ }
97
+ if (style(parent, 'position') === 'fixed') { break; }
98
+ parent = parent.parentElement;
99
+ }
100
+ }
101
+
102
+ return true;
103
+ `;
4
104
  export class WebElement {
5
105
  endpoint;
6
106
  sessionId;
@@ -43,10 +143,26 @@ export class WebElement {
43
143
  return { [W3C_ELEMENT_KEY]: this.elementId, [LEGACY_ELEMENT_KEY]: this.elementId };
44
144
  }
45
145
  async isDisplayed() {
146
+ // The legacy `GET /element/{id}/displayed` endpoint is a pre-W3C JSON Wire
147
+ // Protocol command. chromedriver/geckodriver still accept it for backward
148
+ // compatibility, but it was never part of W3C WebDriver Classic and is
149
+ // absent from Apple's Safari 12+ command table. We therefore compute
150
+ // displayedness the same way every real driver does under the hood: by
151
+ // executing the well-known "displayedness atom" via the standard
152
+ // `POST /execute/sync` command, which every Classic remote end supports.
153
+ //
154
+ // The script below mirrors Selenium's `bot.dom.isShown_`
155
+ // (selenium/javascript/atoms/dom.js) — the exact algorithm chromedriver's
156
+ // and geckodriver's `is displayed` implementations are compiled from, and
157
+ // the same one wdio/Playwright rely on. Keeping it byte-for-byte faithful
158
+ // in behavior is what preserves existing Chrome/Chromium/Firefox results;
159
+ // this is a protocol change, not a semantics change. Each rule is annotated
160
+ // with the `bot.dom` behavior it reproduces so a reviewer can verify it.
46
161
  const client = new HttpClient(this.endpoint);
47
162
  const res = await client.send({
48
- method: 'GET',
49
- path: `/session/${this.sessionId}/element/${this.elementId}/displayed`,
163
+ method: 'POST',
164
+ path: `/session/${this.sessionId}/execute/sync`,
165
+ body: { script: IS_DISPLAYED_ATOM, args: [this.toJSON()] },
50
166
  });
51
167
  const value = res?.value ?? res;
52
168
  return Boolean(value);
@@ -1 +1 @@
1
- {"version":3,"file":"webelement.js","sourceRoot":"","sources":["../../src/lib/webelement.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webelement.js","sourceRoot":"","sources":["../../src/lib/webelement.ts"],"names":[],"mappings":"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"}
@@ -153,6 +153,8 @@ Handle stable CraftDriver errors or customize browser driver services.
153
153
  | `ChromeServiceOptions` | type | — | [getting-started](getting-started.md) |
154
154
  | `FirefoxService` | class | — | [getting-started](getting-started.md) |
155
155
  | `FirefoxServiceOptions` | type | — | [getting-started](getting-started.md) |
156
+ | `SafariService` | class | — | [getting-started](getting-started.md) |
157
+ | `SafariServiceOptions` | type | — | [getting-started](getting-started.md) |
156
158
 
157
159
  ## Other Exports
158
160
 
@@ -166,4 +168,4 @@ Exports that are public but not yet assigned to a feature group.
166
168
  | `ElectronRemoteOptions` | type | Extra context ElectronRemote needs beyond the inspector endpoint. | [electron](electron.md) |
167
169
  | `PageMatcher` | type | Selects a top-level page by `url` and/or `title` for {@link Browser.waitForPage}. A string matches as a substring; a `RegExp` is tested. When both fields are given, both must match. | [browser-api](browser-api.md) |
168
170
 
169
- Total exports: **82**.
171
+ Total exports: **84**.
@@ -279,3 +279,57 @@ const browser = await Browser.launch({
279
279
  }),
280
280
  });
281
281
  ```
282
+
283
+ ## Safari (macOS): driver ships with the browser
284
+
285
+ Everything above (steps 1–9, the download chain, the TTL cache) resolves
286
+ **chromedriver**/**geckodriver** — binaries craftdriver may fetch and cache on
287
+ disk. `browserName: 'safari'` doesn't fit that model at all: Apple ships
288
+ `safaridriver` as part of macOS/Safari itself, so there is no download, no
289
+ version-matching problem, and no cache to warm. This page covers only driver
290
+ **resolution**; for setup (`safaridriver --enable`), capabilities, limitations,
291
+ and Safari Technology Preview, see the [**Safari guide**](./safari.md).
292
+
293
+ `SafariService` resolves the `safaridriver` binary through its own chain —
294
+ first match wins, and none of these steps ever triggers a network call:
295
+
296
+ | Step | Source |
297
+ |---|---|
298
+ | 1 | `binaryPath` passed to `new SafariService({ binaryPath })` |
299
+ | 2 | `CRAFTDRIVER_SAFARIDRIVER_PATH` env var |
300
+ | 3 | `CRAFTDRIVER_DRIVER_PATH` env var (generic fallback, shared with Chrome/Firefox) |
301
+ | 4 | `/usr/bin/safaridriver` (the fixed system location on macOS) |
302
+ | 5 | `safaridriver` on `PATH` |
303
+ | 6 | a clear, actionable error if nothing resolves — craftdriver never downloads a `safaridriver` |
304
+
305
+ To pin **Safari Technology Preview**, point `binaryPath` at its bundled driver
306
+ (`/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver`) —
307
+ see the [Safari guide](./safari.md#safari-technology-preview).
308
+
309
+ ### Extra `safaridriver` arguments
310
+
311
+ `SafariService` accepts `args`, passed through verbatim after `--port <port>`.
312
+ The most useful one is `--diagnose`, which turns on `safaridriver`'s
313
+ diagnostic logging (written under `~/Library/Logs/com.apple.WebDriver/`) —
314
+ handy when a Safari session fails to start and the driver's own stdout/stderr
315
+ isn't enough to tell why:
316
+
317
+ ```typescript
318
+ const browser = await Browser.launch({
319
+ browserName: 'safari',
320
+ safariService: new SafariService({ args: ['--diagnose'] }),
321
+ });
322
+ ```
323
+
324
+ ### What's different from Chrome/Firefox here
325
+
326
+ - No `CRAFTDRIVER_CACHE_DIR`, `CRAFTDRIVER_DRIVER_TTL`, or `CRAFTDRIVER_OFFLINE`
327
+ behavior applies to Safari — there's nothing cached or downloaded to
328
+ configure.
329
+ - No browser-binary resolution chain (`browserPath` / `CRAFTDRIVER_CHROME_PATH`-
330
+ style) exists for Safari — you select which Safari build's driver to use via
331
+ `SafariService.binaryPath` directly, as shown above, not via a separate
332
+ browser-path option.
333
+ - `enableBiDi: true` is rejected before launch — Safari has no documented
334
+ WebDriver BiDi endpoint to negotiate. See
335
+ [WebDriver Standards → Safari is Classic-only](./standards.md#safari-is-classic-only).
@@ -75,29 +75,49 @@ returns the same `Page` the shortcuts would target.
75
75
 
76
76
  ## Launch Options
77
77
 
78
+ The simplest launch runs Chrome:
79
+
80
+ ```typescript
81
+ const browser = await Browser.launch(); // Chrome by default
82
+ ```
83
+
84
+ Pick a browser with `browserName`:
85
+
78
86
  ```typescript
79
- // Chrome (default)
80
87
  const browser = await Browser.launch({
81
- browserName: 'chrome', // 'chrome' | 'chromium' | 'firefox'
88
+ browserName: 'firefox', // 'chrome' | 'chromium' | 'firefox' | 'safari'
82
89
  storageState: './session.json', // optional: pre-load saved session state
83
90
  });
91
+ ```
92
+
93
+ To switch browsers from an environment variable in your own tests, read it
94
+ yourself and pass it to `browserName` — craftdriver doesn't read any browser
95
+ env var for you:
84
96
 
85
- // Firefox
97
+ ```typescript
86
98
  const browser = await Browser.launch({
87
- browserName: 'firefox',
99
+ browserName: process.env.BROWSER_NAME ?? 'chrome',
88
100
  });
89
101
  ```
90
102
 
91
- ### Running tests on Firefox
103
+ ### Safari
92
104
 
93
- Set `BROWSER_NAME=firefox` before running your test command:
105
+ Safari runs on macOS with the same API, as a smaller Classic-only tier. Enable
106
+ automation once (`safaridriver --enable`) and launch:
94
107
 
95
- ```bash
96
- BROWSER_NAME=firefox npx vitest run
97
- # or use the built-in script if your package.json has it:
98
- npm run test:firefox
108
+ ```typescript
109
+ const browser = await Browser.launch({ browserName: 'safari' });
110
+ await browser.navigateTo('http://127.0.0.1:8080/login.html');
111
+ await browser.fill('#username', 'alice');
112
+ await browser.click('#submit');
113
+ await browser.quit();
99
114
  ```
100
115
 
116
+ Safari is macOS-only, headed, and serial (one session at a time), and doesn't
117
+ support BiDi-only features (network mocking, tracing, emulation, …). See the
118
+ [**Safari guide**](./safari.md) for the full picture — setup, what works, and
119
+ Safari Technology Preview.
120
+
101
121
  ### Custom WebDriver binary or port
102
122
 
103
123
  `Browser.launch()` resolves the driver binary automatically. To pin a
package/docs/index.md CHANGED
@@ -33,7 +33,7 @@ features:
33
33
  details: CLI, MCP server, and assistant rules are there when you want coding agents to drive the browser too.
34
34
  ---
35
35
 
36
- ## One Install
36
+ ## Quick Start
37
37
 
38
38
  ```bash
39
39
  npm install craftdriver --save-dev
@@ -0,0 +1,86 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Craftdriver Displayedness</title>
8
+ <style>
9
+ body {
10
+ font-family: system-ui, Arial, sans-serif;
11
+ margin: 1rem;
12
+ }
13
+
14
+ /* An ancestor hidden via display:none. Its descendant #hidden-by-ancestor
15
+ has no style of its own but must count as not displayed. */
16
+ #display-none-ancestor {
17
+ display: none;
18
+ }
19
+
20
+ /* Own visibility:hidden. */
21
+ #visibility-hidden {
22
+ visibility: hidden;
23
+ }
24
+
25
+ /* Zero-sized content: no rendered box, overflow clipped. */
26
+ #zero-size {
27
+ width: 0;
28
+ height: 0;
29
+ overflow: hidden;
30
+ }
31
+
32
+ /* A scrollable-but-CLIPPING (overflow:hidden) container. The child at the
33
+ bottom is scrolled out of the container's viewport, so it is clipped and
34
+ not displayed (bot.dom OverflowState.HIDDEN). */
35
+ #clip-container {
36
+ width: 120px;
37
+ height: 80px;
38
+ overflow: hidden;
39
+ border: 1px solid #ccc;
40
+ }
41
+
42
+ #clip-spacer {
43
+ height: 400px;
44
+ }
45
+ </style>
46
+ </head>
47
+
48
+ <body>
49
+ <div class="container">
50
+ <!-- Plainly visible baseline. -->
51
+ <div id="visible-el">I am visible</div>
52
+
53
+ <!-- Hidden by a display:none ancestor. -->
54
+ <div id="display-none-ancestor">
55
+ <div id="hidden-by-ancestor">Hidden because my ancestor is display:none</div>
56
+ </div>
57
+
58
+ <!-- Own display:none. -->
59
+ <div id="display-none-self" style="display: none">Hidden by my own display:none</div>
60
+
61
+ <!-- Own visibility:hidden. -->
62
+ <div id="visibility-hidden">Hidden by my own visibility:hidden</div>
63
+
64
+ <!-- Visibility hidden ancestor with a visible descendant override:
65
+ the child sets visibility:visible and MUST count as displayed. -->
66
+ <div style="visibility: hidden">
67
+ <div id="visibility-visible-override" style="visibility: visible">
68
+ Visible again despite hidden ancestor
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Zero-sized content. -->
73
+ <div id="zero-size">unrenderable</div>
74
+
75
+ <!-- A <br> is legitimately zero-width but should count as displayed. -->
76
+ <br id="line-break" />
77
+
78
+ <!-- Element scrolled out of an overflow:hidden container's viewport. -->
79
+ <div id="clip-container">
80
+ <div id="clip-spacer"></div>
81
+ <div id="scrolled-out-of-clip">I am below the clipped fold</div>
82
+ </div>
83
+ </div>
84
+ </body>
85
+
86
+ </html>
package/docs/safari.md ADDED
@@ -0,0 +1,118 @@
1
+ # Safari
2
+
3
+ CraftDriver drives **real desktop Safari on macOS** through W3C WebDriver
4
+ Classic.
5
+
6
+ ## One-time setup
7
+
8
+ Safari's driver (`safaridriver`) ships with macOS — **nothing to install or
9
+ download**. You only have to turn on automation **once per machine**:
10
+
11
+ ```bash
12
+ safaridriver --enable
13
+ ```
14
+
15
+ This is the single prerequisite.
16
+
17
+ CraftDriver never runs `--enable` for you. If automation isn't enabled, a
18
+ launch fails immediately with that exact remedy rather than hanging.
19
+
20
+ ## Quick start
21
+
22
+ ```typescript
23
+ import { Browser } from 'craftdriver';
24
+
25
+ const browser = await Browser.launch({ browserName: 'safari' });
26
+ await browser.navigateTo('http://127.0.0.1:8080/login.html');
27
+ await browser.getByLabel('Username').fill('alice');
28
+ await browser.getByLabel('Password').fill('secret');
29
+ await browser.getByRole('button', { name: 'Sign in' }).click();
30
+ await browser.expect('#welcome').toHaveText('Welcome back, alice!');
31
+ await browser.quit();
32
+ ```
33
+
34
+ Same API as every other browser — locators, actions, assertions, and
35
+ auto-waiting all work unchanged.
36
+
37
+ ## What works
38
+
39
+ Navigation, locators, and element queries · click / fill / clear / keyboard /
40
+ desktop mouse · `evaluate()` (sync & async JS) · frames and iframes · window &
41
+ popup enumeration · **imperative dialogs** (`getAlertText` / `acceptAlert` /
42
+ `dismissAlert` / `sendAlertText`) · viewport and element screenshots · cookies
43
+ and storage (via the standard Classic cookie endpoints) · accessibility checks.
44
+
45
+ ## What isn't supported
46
+
47
+ Safari exposes no WebDriver BiDi endpoint, so everything event- or
48
+ BiDi-driven is unavailable — and each of these throws a clear
49
+ [`UNSUPPORTED`](./error-codes.md) error immediately, never a silent hang:
50
+
51
+ - network mocking / interception and request/response waits
52
+ - console & JavaScript-error capture, tracing
53
+ - BiDi user contexts and emulation (permissions, geolocation,
54
+ locale/timezone, offline, color-scheme)
55
+ - init/preload scripts and the virtual clock
56
+ - event-driven dialogs (`onDialog` / `waitForDialog`) — use the imperative
57
+ methods above
58
+ - full-page screenshots and `openPage()`
59
+ - CraftDriver-managed downloads (`waitForDownload`)
60
+
61
+ Desktop Safari is also **not** iPhone/iPad Safari — there is no mobile or
62
+ touch emulation.
63
+
64
+ ## Limitations to plan around
65
+
66
+ - **macOS-only.** There is no Safari for Linux or Windows, and none is coming.
67
+ Trying to launch Safari off macOS throws `UNSUPPORTED`.
68
+ - **Headed only.** No headless mode — every launch opens a visible automation
69
+ window (with a purple banner). Don't interact with it while a test runs.
70
+ Setting `HEADLESS=true` with Safari is rejected up front.
71
+ - **Serial only.** Only one Safari WebDriver session can be active on a Mac at
72
+ a time. A second concurrent launch is refused with an actionable error
73
+ (never a hang) — run Safari tests serially.
74
+
75
+ ## Safari Technology Preview
76
+
77
+ STP ships its own `safaridriver`; point `SafariService` at it. This is also
78
+ the one way to get a **second concurrent session** on one Mac — stable Safari
79
+ and STP each run one session at the same time.
80
+
81
+ ```typescript
82
+ import { Browser, SafariService } from 'craftdriver';
83
+
84
+ const browser = await Browser.launch({
85
+ browserName: 'safari',
86
+ safariService: new SafariService({
87
+ binaryPath: '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver',
88
+ }),
89
+ });
90
+ ```
91
+
92
+ Enable automation once for STP too: `/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver --enable`.
93
+
94
+ ## Running Safari tests
95
+
96
+ Run Safari **serially** — one session at a time. With Vitest that's
97
+ `--maxWorkers=1`:
98
+
99
+ ```bash
100
+ vitest run --maxWorkers=1
101
+ ```
102
+
103
+ Keep it headed (don't set `HEADLESS`), on macOS with Remote Automation enabled.
104
+ In CI, use a macOS runner; parallelism across Safari needs **separate macOS
105
+ hosts**.
106
+
107
+ ## Troubleshooting
108
+
109
+ | Symptom | Fix |
110
+ | --- | --- |
111
+ | `You must enable 'Allow remote automation'…` | Run `safaridriver --enable` once (see [setup](#one-time-setup)). |
112
+ | Second launch fails while one session is open | Expected — Safari is serial. Serialize your tests, or use STP for a second session. |
113
+ | `Could not find 'safaridriver'` | You're not on macOS, or Safari is missing. For a non-standard install, set `CRAFTDRIVER_SAFARIDRIVER_PATH` or `SafariService({ binaryPath })`. |
114
+
115
+ ## See also
116
+
117
+ - [Driver Configuration → Safari](./driver-configuration.md#safari-macos-driver-ships-with-the-browser) — the no-download driver-resolution chain.
118
+ - [WebDriver Standards](./standards.md#safari-is-classic-only) — why Safari is Classic-only.
package/docs/standards.md CHANGED
@@ -16,6 +16,19 @@ That protocol choice is the center of the project. CraftDriver tries to provide
16
16
 
17
17
  CraftDriver picks the practical protocol path behind a single public API. You call `browser.click()`, `browser.navigateTo()`, `browser.network.mock()`, or `browser.startTrace()`; the library handles the transport details.
18
18
 
19
+ ### Safari is Classic-only
20
+
21
+ `browserName: 'safari'` always uses WebDriver Classic and never negotiates
22
+ `webSocketUrl`. This isn't a temporary gap CraftDriver plans to close — Apple
23
+ does not currently document a supported WebDriver BiDi endpoint for Safari or
24
+ Safari Technology Preview, so there's no BiDi transport to opt into. CraftDriver
25
+ rejects `enableBiDi: true` for Safari with a clear `UNSUPPORTED` error before
26
+ launch rather than silently downgrading, and every BiDi-only method (network,
27
+ logs, tracing, contexts, permissions/geolocation/emulation, full-page
28
+ screenshots, event-driven dialogs) fails the same way when called on a Safari
29
+ session. See the [Safari guide](./safari.md) for what remains available on
30
+ Classic.
31
+
19
32
  ## Why It Matters
20
33
 
21
34
  Standards-based automation gives projects a few useful properties:
@@ -5,7 +5,7 @@ CraftDriver is for writing boringly reliable automation against real browsers, w
5
5
  ## What It Cares About
6
6
 
7
7
  - 🍺 **Focused Node.js API** - browser automation without a giant framework around it.
8
- - 🧭 **Real browsers** - drives installed Chrome, Chromium, and Firefox instead of patched browser engine builds.
8
+ - 🧭 **Real browsers** - drives installed Chrome, Chromium, and Firefox instead of patched browser engine builds, plus real ([Safari on macOS](./safari.md)).
9
9
  - 🌐 **Standards that age well** - W3C WebDriver standards stay stable while browser-private protocols change.
10
10
  - 🚦 **Readable, auto-waited flows** - role, label, text, test id, CSS, XPath, click, fill, and expect.
11
11
  - 📡 **Network control** - mock, block, intercept, and wait for browser requests and responses.
@@ -62,3 +62,15 @@ Manual configuration is useful when:
62
62
  [Driver Configuration → Browser Binary Configuration](./driver-configuration.md#browser-binary-configuration)
63
63
 
64
64
  See [Driver Configuration](./driver-configuration.md) for the full reference.
65
+
66
+ ## Safari: nothing to resolve, nothing to download
67
+
68
+ The auto-download model above doesn't apply to `browserName: 'safari'`.
69
+ `safaridriver` ships as part of macOS/Safari itself — there's no version to
70
+ match, no binary to fetch, and no cache to warm. CraftDriver only ever
71
+ **locates** the driver Apple already installed. The single prerequisite is
72
+ turning automation on once per Mac with `safaridriver --enable`.
73
+
74
+ See the [**Safari guide**](./safari.md) for setup and capabilities, and
75
+ [Driver Configuration → Safari](./driver-configuration.md#safari-macos-driver-ships-with-the-browser)
76
+ for the driver-resolution chain.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craftdriver",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Modern WebDriver automation library for NodeJS",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "test:firefox": "BROWSER_NAME=firefox HEADLESS=true vitest run --maxWorkers=2",
18
18
  "test:chrome": "BROWSER_NAME=chrome vitest run",
19
19
  "test:chromium": "BROWSER_NAME=chromium vitest run",
20
+ "test:safari": "BROWSER_NAME=safari vitest run tests/safari.test.ts --maxWorkers=1",
20
21
  "test:recipes": "HEADLESS=true vitest run --config vitest.recipes.config.ts",
21
22
  "test:electron": "vitest run --config vitest.electron.config.ts",
22
23
  "bench": "HEADLESS=true vitest run --config vitest.perf.config.ts",
@@ -40,6 +41,7 @@
40
41
  "firefox",
41
42
  "chrome",
42
43
  "chromium",
44
+ "safari",
43
45
  "electron",
44
46
  "electron-testing",
45
47
  "desktop-app-testing",
@@ -36,7 +36,10 @@ break the moment markup shifts.
36
36
  4. BiDi features (`network`, `logs`, tracing, init scripts, true load
37
37
  events) work out of the box — `enableBiDi` defaults to `true`. They only
38
38
  break if you explicitly pass `enableBiDi: false`; the error code on the
39
- wrong transport is `UNSUPPORTED`.
39
+ wrong transport is `UNSUPPORTED`. Exception: `browserName: 'safari'`
40
+ defaults `enableBiDi` to `false` and rejects `true` — Safari is
41
+ Classic-only (macOS, headed, one session at a time). See
42
+ [docs/safari.md](../../docs/safari.md).
40
43
  5. Tests fetch from the example server. Start it in a separate
41
44
  terminal: `npm run examples:start` before `npm test`.
42
45
 
@@ -10,11 +10,14 @@ list.
10
10
  import { Browser } from 'craftdriver';
11
11
 
12
12
  const browser = await Browser.launch({
13
- browserName: 'chrome', // 'chrome' | 'chromium' | 'firefox'
13
+ browserName: 'chrome', // 'chrome' | 'chromium' | 'firefox' | 'safari'
14
14
  headless: true,
15
15
  // enableBiDi defaults to true — network / logs / tracing / init scripts
16
16
  // all need it, so only set enableBiDi: false if you must disable it.
17
17
  });
18
+ // 'safari' is macOS-only, headed only (no `headless`),
19
+ // one session at a time, WebDriver Classic only (enableBiDi is rejected).
20
+ // See docs/safari.md.
18
21
  try {
19
22
  await browser.navigateTo('https://example.com');
20
23
  // ...