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.
- package/CHANGELOG.md +10 -3
- package/README.md +4 -2
- 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 +1 -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 +27 -1
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +142 -74
- 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 +4 -1
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +20 -2
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/capabilities.d.ts +12 -2
- package/dist/lib/capabilities.d.ts.map +1 -1
- package/dist/lib/capabilities.js +84 -39
- package/dist/lib/capabilities.js.map +1 -1
- package/dist/lib/driver.d.ts +16 -1
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +64 -0
- 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/launchTarget.d.ts +1 -1
- package/dist/lib/launchTarget.d.ts.map +1 -1
- package/dist/lib/launchTarget.js +65 -3
- package/dist/lib/launchTarget.js.map +1 -1
- 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 +41 -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 +3 -1
- package/docs/driver-configuration.md +54 -0
- package/docs/getting-started.md +30 -10
- package/docs/index.md +1 -1
- package/docs/public/examples/displayed.html +86 -0
- package/docs/safari.md +118 -0
- package/docs/standards.md +13 -0
- package/docs/why-craftdriver.md +1 -1
- package/docs/zero-config-drivers.md +12 -0
- package/package.json +3 -1
- package/skills/craftdriver/SKILL.md +4 -1
- package/skills/craftdriver/cheatsheet.md +4 -1
package/dist/lib/browser.js
CHANGED
|
@@ -5,6 +5,7 @@ import { diagnoseElectronLaunchFailure } from './electronDiagnostics.js';
|
|
|
5
5
|
import { ElectronRemote } from './electronRemote.js';
|
|
6
6
|
import { findFreePort } from './service.js';
|
|
7
7
|
import { FirefoxService } from './firefox.js';
|
|
8
|
+
import { SafariService } from './safari.js';
|
|
8
9
|
import { resolveBrowserBinaryPath } from './driverManager.js';
|
|
9
10
|
import { buildLaunchCapabilities } from './capabilities.js';
|
|
10
11
|
import { resolveLaunchTarget } from './launchTarget.js';
|
|
@@ -351,9 +352,18 @@ export class Browser {
|
|
|
351
352
|
// it's the optional custom-binary resolution chain — but see
|
|
352
353
|
// resolveBrowserBinaryPath's doc: some of the env vars in that chain aren't
|
|
353
354
|
// craftdriver-opt-in, they're ambient conventions other tools may have set.
|
|
355
|
+
// Safari has no custom-binary concept: browserPath/args are rejected
|
|
356
|
+
// upstream in resolveLaunchTarget(), so target.browserPath is
|
|
357
|
+
// always undefined here. Skip resolveBrowserBinaryPath() for Safari
|
|
358
|
+
// entirely rather than calling it with 'firefox' as a stand-in — it would
|
|
359
|
+
// still probe FIREFOX_BIN/SE_FIREFOX_PATH-style env vars that have
|
|
360
|
+
// nothing to do with Safari, which is misleading even though it happens
|
|
361
|
+
// to return undefined/harmless today.
|
|
354
362
|
const browserBinary = target.kind === 'electron'
|
|
355
363
|
? electronBinary
|
|
356
|
-
:
|
|
364
|
+
: name === 'safari'
|
|
365
|
+
? undefined
|
|
366
|
+
: resolveBrowserBinaryPath(isChromeFamily ? 'chrome' : 'firefox', target.browserPath);
|
|
357
367
|
// Main-process access (opt-in): launch the Electron app with a Node inspector
|
|
358
368
|
// on a free local port. chromedriver forwards this arg to the app, enabling
|
|
359
369
|
// its main-process inspector; browser.electron connects below for log capture
|
|
@@ -399,6 +409,13 @@ export class Browser {
|
|
|
399
409
|
driverService = options.chromeService ?? new ChromeService({ browserPath: browserBinary });
|
|
400
410
|
builder.setChromeService(driverService);
|
|
401
411
|
}
|
|
412
|
+
else if (name === 'safari') {
|
|
413
|
+
// No browserPath threading (Safari has none — see the browserBinary
|
|
414
|
+
// resolution above) and no headless/args to pass through; SafariService
|
|
415
|
+
// resolves its own driver binary (never auto-downloaded).
|
|
416
|
+
driverService = options.safariService ?? new SafariService();
|
|
417
|
+
builder.setSafariService(driverService);
|
|
418
|
+
}
|
|
402
419
|
else {
|
|
403
420
|
driverService = options.firefoxService ?? new FirefoxService();
|
|
404
421
|
builder.setFirefoxService(driverService);
|
|
@@ -498,6 +515,26 @@ export class Browser {
|
|
|
498
515
|
isBiDiEnabled() {
|
|
499
516
|
return this.bidiSession?.isConnected() ?? false;
|
|
500
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Guard for the BiDi-only surface below. On Safari (which never
|
|
520
|
+
* negotiates BiDi) this throws `CraftdriverError`/`UNSUPPORTED` with a
|
|
521
|
+
* stable `{ browserName, feature }` detail so callers can branch on `code`
|
|
522
|
+
* instead of parsing prose. On every other browser the behavior is
|
|
523
|
+
* unchanged: the exact plain-`Error` message passed by the call site.
|
|
524
|
+
*
|
|
525
|
+
* Deliberately scoped to Safari's guards only — the pre-existing
|
|
526
|
+
* plain-`Error` guards elsewhere are a separate follow-up, not silently
|
|
527
|
+
* folded into this helper.
|
|
528
|
+
*/
|
|
529
|
+
requireBiDi(feature, chromeMessage) {
|
|
530
|
+
if (this.bidiSession?.isConnected())
|
|
531
|
+
return;
|
|
532
|
+
if (this._browserName === 'safari') {
|
|
533
|
+
throw new CraftdriverError(ErrorCode.UNSUPPORTED, `${feature} is not supported on Safari (no WebDriver BiDi). ` +
|
|
534
|
+
'Apple does not document a WebDriver BiDi endpoint for Safari.', { detail: { browserName: 'safari', feature } });
|
|
535
|
+
}
|
|
536
|
+
throw new Error(chromeMessage);
|
|
537
|
+
}
|
|
501
538
|
// === BiDi Feature Accessors ===
|
|
502
539
|
/**
|
|
503
540
|
* Network interception API (BiDi)
|
|
@@ -505,10 +542,8 @@ export class Browser {
|
|
|
505
542
|
*/
|
|
506
543
|
get network() {
|
|
507
544
|
if (!this._network) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
'BiDi negotiation may have failed at launch — check browser logs for WebSocket errors.');
|
|
511
|
-
}
|
|
545
|
+
this.requireBiDi('network', 'Network interception requires BiDi. ' +
|
|
546
|
+
'BiDi negotiation may have failed at launch — check browser logs for WebSocket errors.');
|
|
512
547
|
this._network = this.bidiSession.network;
|
|
513
548
|
}
|
|
514
549
|
return this._network;
|
|
@@ -518,10 +553,8 @@ export class Browser {
|
|
|
518
553
|
*/
|
|
519
554
|
get logs() {
|
|
520
555
|
if (!this._logs) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
'BiDi negotiation may have failed at launch — check browser logs for WebSocket errors.');
|
|
524
|
-
}
|
|
556
|
+
this.requireBiDi('logs', 'Log monitoring requires BiDi. ' +
|
|
557
|
+
'BiDi negotiation may have failed at launch — check browser logs for WebSocket errors.');
|
|
525
558
|
this._logs = this.bidiSession.logs;
|
|
526
559
|
}
|
|
527
560
|
return this._logs;
|
|
@@ -738,11 +771,9 @@ export class Browser {
|
|
|
738
771
|
* await browser.grantPermissions(['clipboard-read'], { origin: 'https://example.com' });
|
|
739
772
|
*/
|
|
740
773
|
async grantPermissions(permissions, opts) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
'which has no Classic-WebDriver equivalent.');
|
|
745
|
-
}
|
|
774
|
+
this.requireBiDi('grantPermissions()', 'grantPermissions() requires BiDi (enableBiDi: true). ' +
|
|
775
|
+
'Permission overrides use the W3C BiDi `permissions.setPermission` command, ' +
|
|
776
|
+
'which has no Classic-WebDriver equivalent.');
|
|
746
777
|
if (!Array.isArray(permissions) || permissions.length === 0) {
|
|
747
778
|
throw new Error('grantPermissions: pass a non-empty array of permission names.');
|
|
748
779
|
}
|
|
@@ -781,11 +812,9 @@ export class Browser {
|
|
|
781
812
|
* await browser.setGeolocation(null); // clear
|
|
782
813
|
*/
|
|
783
814
|
async setGeolocation(coords) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
'command, which has no Classic-WebDriver equivalent.');
|
|
788
|
-
}
|
|
815
|
+
this.requireBiDi('setGeolocation()', 'setGeolocation() requires BiDi (enableBiDi: true). ' +
|
|
816
|
+
'Geolocation overrides use the W3C BiDi `emulation.setGeolocationOverride` ' +
|
|
817
|
+
'command, which has no Classic-WebDriver equivalent.');
|
|
789
818
|
const conn = this.bidiSession.getConnection();
|
|
790
819
|
// Apply across all top-level contexts in the default user context.
|
|
791
820
|
const tree = await conn.send('browsingContext.getTree', { maxDepth: 0 });
|
|
@@ -851,12 +880,10 @@ export class Browser {
|
|
|
851
880
|
async emulate(options) {
|
|
852
881
|
if (!options || Object.keys(options).length === 0)
|
|
853
882
|
return;
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
'which have no Classic-WebDriver equivalent.');
|
|
859
|
-
}
|
|
883
|
+
this.requireBiDi('emulate()', 'emulate() requires BiDi (enableBiDi: true). ' +
|
|
884
|
+
'Emulation overrides use the W3C BiDi `emulation.*` commands ' +
|
|
885
|
+
'(and the BiDi+CDP bridge for media features and offline), ' +
|
|
886
|
+
'which have no Classic-WebDriver equivalent.');
|
|
860
887
|
// Validate Chromium-only fields up front so we fail before mutating state.
|
|
861
888
|
const chromiumOnly = ['colorScheme', 'reducedMotion', 'forcedColors', 'offline'];
|
|
862
889
|
const isFirefox = this._browserName === 'firefox';
|
|
@@ -1096,7 +1123,7 @@ export class Browser {
|
|
|
1096
1123
|
async startTrace(opts) {
|
|
1097
1124
|
if (!this.bidiSession?.isConnected()) {
|
|
1098
1125
|
throw new CraftdriverError(ErrorCode.UNSUPPORTED, 'startTrace() requires BiDi (enableBiDi: true). ' +
|
|
1099
|
-
'Tracing relies on BiDi events; Classic WebDriver does not expose them.', { detail: { feature: 'startTrace' } });
|
|
1126
|
+
'Tracing relies on BiDi events; Classic WebDriver does not expose them.', { detail: { browserName: this._browserName, feature: 'startTrace' } });
|
|
1100
1127
|
}
|
|
1101
1128
|
if (!this._tracer) {
|
|
1102
1129
|
this._tracer = new Tracer(this, this.bidiSession.getConnection(), this._browserName);
|
|
@@ -1193,10 +1220,8 @@ export class Browser {
|
|
|
1193
1220
|
* ```
|
|
1194
1221
|
*/
|
|
1195
1222
|
async addInitScript(fnOrSrc) {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
'BiDi is enabled by default — check that your browser supports it.');
|
|
1199
|
-
}
|
|
1223
|
+
this.requireBiDi('addInitScript()', 'addInitScript() requires BiDi. ' +
|
|
1224
|
+
'BiDi is enabled by default — check that your browser supports it.');
|
|
1200
1225
|
const conn = this.bidiSession.getConnection();
|
|
1201
1226
|
const fnSrc = typeof fnOrSrc === 'function' ? fnOrSrc.toString() : `() => { ${fnOrSrc} }`;
|
|
1202
1227
|
const result = await conn.send('script.addPreloadScript', {
|
|
@@ -1218,9 +1243,7 @@ export class Browser {
|
|
|
1218
1243
|
* Register **before** the action that triggers the request.
|
|
1219
1244
|
*/
|
|
1220
1245
|
waitForRequest(pattern, opts) {
|
|
1221
|
-
|
|
1222
|
-
throw new Error('waitForRequest() requires BiDi. BiDi is enabled by default — check your browser supports it.');
|
|
1223
|
-
}
|
|
1246
|
+
this.requireBiDi('waitForRequest()', 'waitForRequest() requires BiDi. BiDi is enabled by default — check your browser supports it.');
|
|
1224
1247
|
return this.network.waitForRequest(pattern, {
|
|
1225
1248
|
timeout: opts?.timeout ?? this.defaults.navigationTimeout,
|
|
1226
1249
|
});
|
|
@@ -1239,18 +1262,14 @@ export class Browser {
|
|
|
1239
1262
|
* ```
|
|
1240
1263
|
*/
|
|
1241
1264
|
waitForResponse(pattern, opts) {
|
|
1242
|
-
|
|
1243
|
-
throw new Error('waitForResponse() requires BiDi. BiDi is enabled by default \u2014 check your browser supports it.');
|
|
1244
|
-
}
|
|
1265
|
+
this.requireBiDi('waitForResponse()', 'waitForResponse() requires BiDi. BiDi is enabled by default \u2014 check your browser supports it.');
|
|
1245
1266
|
return this.network.waitForResponse(pattern, {
|
|
1246
1267
|
timeout: opts?.timeout ?? this.defaults.navigationTimeout,
|
|
1247
1268
|
});
|
|
1248
1269
|
}
|
|
1249
1270
|
on(event, listener) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
'Network event listeners use the W3C BiDi `network` module, which has no Classic-WebDriver equivalent.');
|
|
1253
|
-
}
|
|
1271
|
+
this.requireBiDi(`browser.on('${event}')`, `browser.on('${event}') requires BiDi (enableBiDi: true). ` +
|
|
1272
|
+
'Network event listeners use the W3C BiDi `network` module, which has no Classic-WebDriver equivalent.');
|
|
1254
1273
|
if (event === 'request') {
|
|
1255
1274
|
return this.network.on('request', listener);
|
|
1256
1275
|
}
|
|
@@ -1266,6 +1285,17 @@ export class Browser {
|
|
|
1266
1285
|
* await dl.saveAs('/tmp/report.csv');
|
|
1267
1286
|
*/
|
|
1268
1287
|
async waitForDownload(action, opts) {
|
|
1288
|
+
if (this._browserName === 'safari') {
|
|
1289
|
+
// safaridriver exposes no download-directory configuration craftdriver
|
|
1290
|
+
// can manage, so a browser download never lands in _downloadsDir — it
|
|
1291
|
+
// goes to the user's ~/Downloads. Without this guard, waitForDownload()
|
|
1292
|
+
// would watch an empty temp dir and fail with an opaque timeout instead
|
|
1293
|
+
// of a clear "unsupported" error. Craftdriver-managed downloads are out
|
|
1294
|
+
// of scope for Safari.
|
|
1295
|
+
throw new CraftdriverError(ErrorCode.UNSUPPORTED, 'waitForDownload() is not supported on Safari: safaridriver exposes no ' +
|
|
1296
|
+
'download-directory configuration craftdriver can manage, so downloads ' +
|
|
1297
|
+
'cannot be routed to or observed in a controlled directory.', { detail: { browserName: 'safari', feature: 'waitForDownload()' } });
|
|
1298
|
+
}
|
|
1269
1299
|
const dir = this._downloadsDir;
|
|
1270
1300
|
if (!dir) {
|
|
1271
1301
|
throw new Error('waitForDownload() requires a downloads directory. Browser was not launched correctly.');
|
|
@@ -1317,7 +1347,23 @@ export class Browser {
|
|
|
1317
1347
|
await handler(dialog);
|
|
1318
1348
|
});
|
|
1319
1349
|
}
|
|
1320
|
-
|
|
1350
|
+
if (this._browserName === 'safari') {
|
|
1351
|
+
// Safari is Classic-only and has no WebDriver BiDi push events, so there
|
|
1352
|
+
// is no event-driven way to detect a dialog opening. A polling fallback
|
|
1353
|
+
// (repeatedly calling getAlertText()/driver "no such alert" errors to
|
|
1354
|
+
// detect a dialog) was considered, but would need to be *proven*
|
|
1355
|
+
// reliable on real Safari before shipping. Rather than silently no-op (today's
|
|
1356
|
+
// Classic behavior for other browsers) or let waitForDialog() hang
|
|
1357
|
+
// until its own generic timeout — which would look like a missed
|
|
1358
|
+
// dialog rather than "this API isn't supported here" — fail
|
|
1359
|
+
// immediately with a clear, actionable error. Revisit if a polling
|
|
1360
|
+
// form is later measured reliable on real Safari.
|
|
1361
|
+
throw new CraftdriverError(ErrorCode.UNSUPPORTED, 'onDialog() / waitForDialog() are not supported on Safari: Safari has no ' +
|
|
1362
|
+
'WebDriver BiDi, so there is no push-event mechanism for dialogs. ' +
|
|
1363
|
+
'Use the imperative dialog API instead — acceptDialog(), dismissDialog(), ' +
|
|
1364
|
+
'getDialogMessage() — which work in Classic mode.', { detail: { browserName: 'safari', feature: 'onDialog()' } });
|
|
1365
|
+
}
|
|
1366
|
+
// Classic (non-Safari): no push events — callers must use the imperative API below
|
|
1321
1367
|
// Return a no-op unsubscribe
|
|
1322
1368
|
return () => {
|
|
1323
1369
|
/* no-op */
|
|
@@ -1378,18 +1424,31 @@ export class Browser {
|
|
|
1378
1424
|
waitForDialog(opts) {
|
|
1379
1425
|
const timeout = opts?.timeout ?? this.defaults.timeout;
|
|
1380
1426
|
return new Promise((resolve, reject) => {
|
|
1381
|
-
|
|
1382
|
-
|
|
1427
|
+
let tid;
|
|
1428
|
+
let off;
|
|
1429
|
+
// onDialog() throws synchronously on Safari (no BiDi push events for
|
|
1430
|
+
// dialogs — see onDialog()'s comment). Register the handler before
|
|
1431
|
+
// scheduling the timeout so that throw rejects this promise
|
|
1432
|
+
// immediately instead of leaving a dangling timer whose callback would
|
|
1433
|
+
// later reference `off` before it was ever assigned.
|
|
1434
|
+
try {
|
|
1435
|
+
off = this.onDialog((dialog) => {
|
|
1436
|
+
if (tid !== undefined)
|
|
1437
|
+
clearTimeout(tid);
|
|
1438
|
+
off();
|
|
1439
|
+
resolve(dialog);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
catch (err) {
|
|
1443
|
+
reject(err);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (timeout > 0) {
|
|
1447
|
+
tid = setTimeout(() => {
|
|
1383
1448
|
off();
|
|
1384
1449
|
reject(new Error(`waitForDialog timed out after ${timeout}ms`));
|
|
1385
|
-
}, timeout)
|
|
1386
|
-
|
|
1387
|
-
const off = this.onDialog((dialog) => {
|
|
1388
|
-
if (tid !== undefined)
|
|
1389
|
-
clearTimeout(tid);
|
|
1390
|
-
off();
|
|
1391
|
-
resolve(dialog);
|
|
1392
|
-
});
|
|
1450
|
+
}, timeout);
|
|
1451
|
+
}
|
|
1393
1452
|
});
|
|
1394
1453
|
}
|
|
1395
1454
|
_buildDialog(params) {
|
|
@@ -1512,10 +1571,8 @@ export class Browser {
|
|
|
1512
1571
|
* await page.waitForLoadState();
|
|
1513
1572
|
*/
|
|
1514
1573
|
async openPage(opts) {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
'WebDriver Classic cannot create top-level browsing contexts.');
|
|
1518
|
-
}
|
|
1574
|
+
this.requireBiDi('openPage()', 'openPage() requires BiDi (enableBiDi: true). ' +
|
|
1575
|
+
'WebDriver Classic cannot create top-level browsing contexts.');
|
|
1519
1576
|
const conn = this.bidiSession.getConnection();
|
|
1520
1577
|
const created = await conn.send('browsingContext.create', {
|
|
1521
1578
|
type: opts?.type ?? 'tab',
|
|
@@ -1679,10 +1736,8 @@ export class Browser {
|
|
|
1679
1736
|
return ctx;
|
|
1680
1737
|
}
|
|
1681
1738
|
async newContext(opts) {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
'WebDriver Classic has no concept of user contexts.');
|
|
1685
|
-
}
|
|
1739
|
+
this.requireBiDi('newContext()', 'newContext() requires BiDi (enableBiDi: true). ' +
|
|
1740
|
+
'WebDriver Classic has no concept of user contexts.');
|
|
1686
1741
|
const conn = this.bidiSession.getConnection();
|
|
1687
1742
|
const created = await conn.send('browser.createUserContext', {});
|
|
1688
1743
|
const ctx = this._wrapContext(created.userContext, {
|
|
@@ -1692,7 +1747,7 @@ export class Browser {
|
|
|
1692
1747
|
if (opts?.storageState !== undefined) {
|
|
1693
1748
|
await ctx.loadStorageState(opts.storageState);
|
|
1694
1749
|
}
|
|
1695
|
-
// Apply
|
|
1750
|
+
// Apply identity & device emulation options. Each setter is `userContexts`-scoped,
|
|
1696
1751
|
// so future pages in this context inherit automatically.
|
|
1697
1752
|
if (opts?.locale !== undefined)
|
|
1698
1753
|
await ctx.setLocale(opts.locale);
|
|
@@ -1708,11 +1763,9 @@ export class Browser {
|
|
|
1708
1763
|
* Maps to BiDi `browser.getUserContexts`. **BiDi-only.**
|
|
1709
1764
|
*/
|
|
1710
1765
|
async contexts() {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
'Use browser.pages() to list tabs instead.');
|
|
1715
|
-
}
|
|
1766
|
+
this.requireBiDi('contexts()', 'contexts() requires BiDi (enableBiDi: true). ' +
|
|
1767
|
+
'WebDriver Classic has no concept of user contexts. ' +
|
|
1768
|
+
'Use browser.pages() to list tabs instead.');
|
|
1716
1769
|
const conn = this.bidiSession.getConnection();
|
|
1717
1770
|
const result = await conn.send('browser.getUserContexts', {});
|
|
1718
1771
|
return (result.userContexts ?? []).map((uc) => this._wrapContext(uc.userContext));
|
|
@@ -1723,10 +1776,8 @@ export class Browser {
|
|
|
1723
1776
|
* to this context. **BiDi-only.**
|
|
1724
1777
|
*/
|
|
1725
1778
|
get defaultContext() {
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
'WebDriver Classic has no concept of user contexts.');
|
|
1729
|
-
}
|
|
1779
|
+
this.requireBiDi('defaultContext', 'defaultContext requires BiDi (enableBiDi: true). ' +
|
|
1780
|
+
'WebDriver Classic has no concept of user contexts.');
|
|
1730
1781
|
if (this._defaultContext)
|
|
1731
1782
|
return this._defaultContext;
|
|
1732
1783
|
this._defaultContext = this._wrapContext('default');
|
|
@@ -1924,12 +1975,31 @@ export class Browser {
|
|
|
1924
1975
|
}
|
|
1925
1976
|
gesture = {
|
|
1926
1977
|
swipe: async ({ from, to, durationMs = 300, }) => {
|
|
1978
|
+
this.rejectTouchActionsOnSafari('gesture.swipe()');
|
|
1927
1979
|
await this.driver.performTouchSwipe(from, to, durationMs);
|
|
1928
1980
|
},
|
|
1929
1981
|
pinch: async ({ center, scale = 0.5, distance = 100, durationMs = 250, }) => {
|
|
1982
|
+
this.rejectTouchActionsOnSafari('gesture.pinch()');
|
|
1930
1983
|
await this.driver.performTouchPinch(center, scale, distance, durationMs);
|
|
1931
1984
|
},
|
|
1932
1985
|
};
|
|
1986
|
+
/**
|
|
1987
|
+
* `gesture.swipe()`/`gesture.pinch()` send W3C actions with a `pointer`
|
|
1988
|
+
* input source whose `parameters.pointerType` is `'touch'`. Desktop Safari
|
|
1989
|
+
* has no touchscreen and Apple's Safari 12+ WebDriver command table gives
|
|
1990
|
+
* no indication `safaridriver` synthesizes touch-type pointer input —
|
|
1991
|
+
* unlike `pointerType: 'mouse'`, which is exercised by every other pointer
|
|
1992
|
+
* helper and is squarely in scope. Rather than let the action silently
|
|
1993
|
+
* no-op (or behave unpredictably) on real Safari, fail loudly before
|
|
1994
|
+
* sending it.
|
|
1995
|
+
*/
|
|
1996
|
+
rejectTouchActionsOnSafari(feature) {
|
|
1997
|
+
if (this._browserName !== 'safari')
|
|
1998
|
+
return;
|
|
1999
|
+
throw new CraftdriverError(ErrorCode.UNSUPPORTED, `${feature} sends a touch-type pointer action, which desktop Safari does not support. ` +
|
|
2000
|
+
'Safari has no touchscreen and no documented touch-pointer WebDriver behavior; ' +
|
|
2001
|
+
'this is desktop browser automation, not iPhone/iPad Safari coverage.', { detail: { browserName: 'safari', feature } });
|
|
2002
|
+
}
|
|
1933
2003
|
/**
|
|
1934
2004
|
* Capture a screenshot of the active page (viewport by default), the
|
|
1935
2005
|
* full scrollable document (`fullPage: true`), or an element matching
|
|
@@ -1962,11 +2032,9 @@ export class Browser {
|
|
|
1962
2032
|
buf = Buffer.from(b64, 'base64');
|
|
1963
2033
|
}
|
|
1964
2034
|
else if (opts?.fullPage) {
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
'command with `origin: "document"`, which has no Classic-WebDriver equivalent.');
|
|
1969
|
-
}
|
|
2035
|
+
this.requireBiDi('screenshot({ fullPage: true })', 'screenshot({ fullPage: true }) requires BiDi (enableBiDi: true). ' +
|
|
2036
|
+
'Full-page screenshots use the W3C BiDi `browsingContext.captureScreenshot` ' +
|
|
2037
|
+
'command with `origin: "document"`, which has no Classic-WebDriver equivalent.');
|
|
1970
2038
|
const conn = this.bidiSession.getConnection();
|
|
1971
2039
|
const page = await this.activePage();
|
|
1972
2040
|
const result = await conn.send('browsingContext.captureScreenshot', {
|