appium-mac2-driver 4.4.0 → 4.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 +6 -0
- package/WebDriverAgentMac/IntegrationTests/AMWindowTests.m +427 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/AMWindowCommands.m +63 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/FBDebugCommands.m +2 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBCommandStatus.h +3 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBCommandStatus.m +13 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMScreenUtils.h +4 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMScreenUtils.m +23 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.h +18 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.m +43 -28
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMWindowUtils.h +111 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMWindowUtils.m +780 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMXCTAccessibility.h +61 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMXCTAccessibility.m +108 -0
- package/WebDriverAgentMac/WebDriverAgentMac.xcodeproj/project.pbxproj +20 -0
- package/build/lib/commands/native-record-screen.d.ts +2 -0
- package/build/lib/commands/native-record-screen.d.ts.map +1 -1
- package/build/lib/commands/native-record-screen.js.map +1 -1
- package/build/lib/commands/windows.d.ts +22 -0
- package/build/lib/commands/windows.d.ts.map +1 -0
- package/build/lib/commands/windows.js +32 -0
- package/build/lib/commands/windows.js.map +1 -0
- package/build/lib/driver.d.ts +12 -0
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +3 -0
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts +9 -0
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +9 -0
- package/build/lib/execute-method-map.js.map +1 -1
- package/build/lib/types.d.ts +26 -0
- package/build/lib/types.d.ts.map +1 -1
- package/lib/commands/native-record-screen.ts +2 -0
- package/lib/commands/windows.ts +44 -0
- package/lib/driver.ts +4 -0
- package/lib/execute-method-map.ts +9 -0
- package/lib/types.ts +28 -0
- package/node_modules/bare-url/README.md +4 -0
- package/node_modules/bare-url/index.d.ts +3 -0
- package/node_modules/bare-url/index.js +42 -18
- package/node_modules/bare-url/lib/errors.js +4 -0
- package/node_modules/bare-url/package.json +1 -1
- package/node_modules/bare-url/prebuilds/android-arm/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-ia32/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-x64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-x64/bare-url.bare +0 -0
- package/package.json +3 -2
- package/scripts/setup-accessibility.mjs +175 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type {Mac2Driver} from '../driver.js';
|
|
2
|
+
import type {AdjustedWindowInfo, WindowInfo, WindowState} from '../types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lists the windows of the current application
|
|
6
|
+
*
|
|
7
|
+
* @returns Information about each window, including its element identifier
|
|
8
|
+
*/
|
|
9
|
+
export async function macosListWindows(this: Mac2Driver): Promise<WindowInfo[]> {
|
|
10
|
+
return (await this.wda.proxy.command('/wda/windows', 'GET')) as WindowInfo[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Changes the state and/or geometry of an application window, optionally moving it to another display
|
|
15
|
+
*
|
|
16
|
+
* @param windowId - Identifier of the window element. The main window is used by default
|
|
17
|
+
* @param state - The state to put the window into
|
|
18
|
+
* @param x - Horizontal origin. Global, or relative to the display if `displayId` is set
|
|
19
|
+
* @param y - Vertical origin. Global, or relative to the display if `displayId` is set
|
|
20
|
+
* @param width - Window width
|
|
21
|
+
* @param height - Window height
|
|
22
|
+
* @param displayId - Identifier of the display to move the window to
|
|
23
|
+
* @returns The updated window information. `windowId` is null if a minimized window was adjusted
|
|
24
|
+
*/
|
|
25
|
+
export async function macosAdjustWindow(
|
|
26
|
+
this: Mac2Driver,
|
|
27
|
+
windowId?: string,
|
|
28
|
+
state?: WindowState,
|
|
29
|
+
x?: number,
|
|
30
|
+
y?: number,
|
|
31
|
+
width?: number,
|
|
32
|
+
height?: number,
|
|
33
|
+
displayId?: number,
|
|
34
|
+
): Promise<AdjustedWindowInfo> {
|
|
35
|
+
return (await this.wda.proxy.command('/wda/window/adjust', 'POST', {
|
|
36
|
+
windowId,
|
|
37
|
+
state,
|
|
38
|
+
x,
|
|
39
|
+
y,
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
displayId,
|
|
43
|
+
})) as AdjustedWindowInfo;
|
|
44
|
+
}
|
package/lib/driver.ts
CHANGED
|
@@ -25,6 +25,7 @@ import * as navigationCommands from './commands/navigation.js';
|
|
|
25
25
|
import * as recordScreenCommands from './commands/record-screen.js';
|
|
26
26
|
import * as screenshotCommands from './commands/screenshots.js';
|
|
27
27
|
import * as sourceCommands from './commands/source.js';
|
|
28
|
+
import * as windowCommands from './commands/windows.js';
|
|
28
29
|
import MAC2_CONSTRAINTS, {type Mac2Constraints} from './constraints.js';
|
|
29
30
|
import {executeMethodMap} from './execute-method-map.js';
|
|
30
31
|
import log from './logger.js';
|
|
@@ -111,6 +112,9 @@ export class Mac2Driver
|
|
|
111
112
|
|
|
112
113
|
macosSource = sourceCommands.macosSource;
|
|
113
114
|
|
|
115
|
+
macosListWindows = windowCommands.macosListWindows;
|
|
116
|
+
macosAdjustWindow = windowCommands.macosAdjustWindow;
|
|
117
|
+
|
|
114
118
|
_videoChunksBroadcaster!: nativeScreenRecordingCommands.NativeVideoChunksBroadcaster;
|
|
115
119
|
_screenRecorder: recordScreenCommands.ScreenRecorder | null = null;
|
|
116
120
|
public proxyReqRes!: (...args: any) => any;
|
|
@@ -130,6 +130,15 @@ export const executeMethodMap = {
|
|
|
130
130
|
optional: ['displayId'],
|
|
131
131
|
},
|
|
132
132
|
},
|
|
133
|
+
'macos: listWindows': {
|
|
134
|
+
command: 'macosListWindows',
|
|
135
|
+
},
|
|
136
|
+
'macos: adjustWindow': {
|
|
137
|
+
command: 'macosAdjustWindow',
|
|
138
|
+
params: {
|
|
139
|
+
optional: ['windowId', 'state', 'x', 'y', 'width', 'height', 'displayId'],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
133
142
|
'macos: appleScript': {
|
|
134
143
|
command: 'macosExecAppleScript',
|
|
135
144
|
params: {
|
package/lib/types.ts
CHANGED
|
@@ -26,3 +26,31 @@ export interface ScreenshotInfo {
|
|
|
26
26
|
|
|
27
27
|
/** A dictionary where each key contains a unique display identifier */
|
|
28
28
|
export type ScreenshotsInfo = StringRecord<ScreenshotInfo>;
|
|
29
|
+
|
|
30
|
+
export interface Rect {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type WindowState = 'normal' | 'minimized' | 'maximized' | 'fullscreen';
|
|
38
|
+
|
|
39
|
+
export interface WindowInfo {
|
|
40
|
+
/** Identifier of the window element */
|
|
41
|
+
windowId: string;
|
|
42
|
+
title: string;
|
|
43
|
+
rect: Rect;
|
|
44
|
+
/** The display that contains most of the window, if any */
|
|
45
|
+
displayId: number | null;
|
|
46
|
+
/** Traits below are null if the Accessibility permission is not granted */
|
|
47
|
+
isMain: boolean | null;
|
|
48
|
+
isFocused: boolean | null;
|
|
49
|
+
isMinimized: boolean | null;
|
|
50
|
+
isFullscreen: boolean | null;
|
|
51
|
+
isMovable: boolean | null;
|
|
52
|
+
isResizable: boolean | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `windowId` is null if a minimized window has been adjusted */
|
|
56
|
+
export type AdjustedWindowInfo = Omit<WindowInfo, 'windowId'> & {windowId: string | null};
|
|
@@ -28,6 +28,10 @@ require('bare-url/global')
|
|
|
28
28
|
|
|
29
29
|
See the [`bare-url` reference](https://docs.pears.com/reference/bare/modules/bare-url).
|
|
30
30
|
|
|
31
|
+
## Threat model
|
|
32
|
+
|
|
33
|
+
`bare-url` is one of the addons Bare compiles into its binary, so it inherits [Bare's threat model](https://github.com/holepunchto/bare/blob/main/docs/threat-model.md). See [`docs/threat-model.md`](docs/threat-model.md) for where this addon sits in it.
|
|
34
|
+
|
|
31
35
|
## License
|
|
32
36
|
|
|
33
37
|
Apache-2.0
|
|
@@ -87,6 +87,9 @@ declare namespace URL {
|
|
|
87
87
|
/**
|
|
88
88
|
* Convert a platform-specific file path to a `file:` URL.
|
|
89
89
|
* @param pathname - The platform-specific file path to convert.
|
|
90
|
+
* @throws {INVALID_ARG_VALUE} (Windows) the path is a UNC path with an empty server name or
|
|
91
|
+
* no resource path.
|
|
92
|
+
* @throws {INVALID_URL} the path does not form a valid `file:` URL.
|
|
90
93
|
*/
|
|
91
94
|
export function pathToFileURL(pathname: string): URL
|
|
92
95
|
|
|
@@ -494,6 +494,26 @@ function encodePath(value) {
|
|
|
494
494
|
return value.replace(pathDelimiterAll, (match) => escapes[match])
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
function encodeFilePath(value) {
|
|
498
|
+
// Paths hardly ever contain any of these, so one pass to rule them out is
|
|
499
|
+
// cheaper than the six or seven replacements it stands in for.
|
|
500
|
+
if (!reserved.test(value)) return value
|
|
501
|
+
|
|
502
|
+
value = value
|
|
503
|
+
.replaceAll('%', '%25') // Must be first
|
|
504
|
+
.replaceAll('#', '%23')
|
|
505
|
+
.replaceAll('?', '%3f')
|
|
506
|
+
.replaceAll('\n', '%0a')
|
|
507
|
+
.replaceAll('\r', '%0d')
|
|
508
|
+
.replaceAll('\t', '%09')
|
|
509
|
+
|
|
510
|
+
if (!isWindows) {
|
|
511
|
+
value = value.replaceAll('\\', '%5c')
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return value
|
|
515
|
+
}
|
|
516
|
+
|
|
497
517
|
function encodeQuery(value) {
|
|
498
518
|
if (value.indexOf('#') === -1) return value
|
|
499
519
|
|
|
@@ -600,7 +620,27 @@ exports.fileURLToPath = function fileURLToPath(url) {
|
|
|
600
620
|
}
|
|
601
621
|
|
|
602
622
|
exports.pathToFileURL = function pathToFileURL(pathname) {
|
|
603
|
-
|
|
623
|
+
const isUNC = isWindows && pathname.startsWith('\\\\')
|
|
624
|
+
|
|
625
|
+
let resolved = isUNC ? pathname : path.resolve(pathname)
|
|
626
|
+
|
|
627
|
+
if (isUNC || (isWindows && resolved.startsWith('\\\\'))) {
|
|
628
|
+
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
|
|
629
|
+
const hostStart = resolved.startsWith('\\\\?\\UNC\\') ? 8 : 2
|
|
630
|
+
const hostEnd = resolved.indexOf('\\', hostStart)
|
|
631
|
+
|
|
632
|
+
if (hostEnd === -1) {
|
|
633
|
+
throw errors.INVALID_ARG_VALUE('Missing UNC resource path')
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (hostEnd === 2) {
|
|
637
|
+
throw errors.INVALID_ARG_VALUE('Empty UNC servername')
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const hostname = truncateHost('file', resolved.slice(hostStart, hostEnd))
|
|
641
|
+
|
|
642
|
+
return new URL('file://' + hostname + encodeFilePath(resolved.slice(hostEnd)))
|
|
643
|
+
}
|
|
604
644
|
|
|
605
645
|
if (pathname[pathname.length - 1] === '/') {
|
|
606
646
|
resolved += '/'
|
|
@@ -608,23 +648,7 @@ exports.pathToFileURL = function pathToFileURL(pathname) {
|
|
|
608
648
|
resolved += '\\'
|
|
609
649
|
}
|
|
610
650
|
|
|
611
|
-
|
|
612
|
-
// cheaper than the six or seven replacements it stands in for.
|
|
613
|
-
if (reserved.test(resolved)) {
|
|
614
|
-
resolved = resolved
|
|
615
|
-
.replaceAll('%', '%25') // Must be first
|
|
616
|
-
.replaceAll('#', '%23')
|
|
617
|
-
.replaceAll('?', '%3f')
|
|
618
|
-
.replaceAll('\n', '%0a')
|
|
619
|
-
.replaceAll('\r', '%0d')
|
|
620
|
-
.replaceAll('\t', '%09')
|
|
621
|
-
|
|
622
|
-
if (!isWindows) {
|
|
623
|
-
resolved = resolved.replaceAll('\\', '%5c')
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return new URL('file:' + resolved)
|
|
651
|
+
return new URL('file:' + encodeFilePath(resolved))
|
|
628
652
|
}
|
|
629
653
|
|
|
630
654
|
exports.format = function format(parts) {
|
|
@@ -30,4 +30,8 @@ module.exports = class URLError extends Error {
|
|
|
30
30
|
static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
|
|
31
31
|
return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
static INVALID_ARG_VALUE(msg = 'Invalid argument value') {
|
|
35
|
+
return new URLError(msg, URLError.INVALID_ARG_VALUE)
|
|
36
|
+
}
|
|
33
37
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-mac2-driver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "XCTest-based Appium driver for macOS apps automation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"XCTest",
|
|
@@ -69,7 +69,8 @@
|
|
|
69
69
|
],
|
|
70
70
|
"mainClass": "Mac2Driver",
|
|
71
71
|
"scripts": {
|
|
72
|
-
"open-wda": "./scripts/open-wda.mjs"
|
|
72
|
+
"open-wda": "./scripts/open-wda.mjs",
|
|
73
|
+
"setup-accessibility": "./scripts/setup-accessibility.mjs"
|
|
73
74
|
},
|
|
74
75
|
"doctor": {
|
|
75
76
|
"checks": [
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
|
|
6
|
+
import {logger} from 'appium/support.js';
|
|
7
|
+
import {exec} from 'teen_process';
|
|
8
|
+
|
|
9
|
+
const RUNNER_NAME = 'WebDriverAgentRunner-Runner.app';
|
|
10
|
+
const RUNNER_SCHEME = 'WebDriverAgentRunner';
|
|
11
|
+
// Keep the build arguments in sync with the driver, so it reuses this build without re-signing it
|
|
12
|
+
const DISABLE_STORE_ARG = 'COMPILER_INDEX_STORE_ENABLE=NO';
|
|
13
|
+
const XCODEPROJ_NAME = 'WebDriverAgentMac.xcodeproj';
|
|
14
|
+
const DERIVED_DATA_ROOT = path.join(os.homedir(), 'Library', 'Developer', 'Xcode', 'DerivedData');
|
|
15
|
+
const SETTINGS_URL = 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility';
|
|
16
|
+
const NO_OPEN_FLAG = '--no-open';
|
|
17
|
+
const RUNNER_PATH_ENV = 'WDA_RUNNER_PATH';
|
|
18
|
+
const KEEP_RUNNING_FLAG = '--keep-running';
|
|
19
|
+
const RUNNER_EXECUTABLE_FRAGMENT = `/${RUNNER_NAME}/Contents/MacOS/`;
|
|
20
|
+
|
|
21
|
+
const log = logger.getLogger('WDA');
|
|
22
|
+
|
|
23
|
+
async function exists(p) {
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(p);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Xcode stores the project path of each derived data folder in its info.plist
|
|
34
|
+
*/
|
|
35
|
+
async function readWorkspacePath(derivedDataDir) {
|
|
36
|
+
try {
|
|
37
|
+
const {stdout} = await exec('plutil', [
|
|
38
|
+
'-extract',
|
|
39
|
+
'WorkspacePath',
|
|
40
|
+
'raw',
|
|
41
|
+
'-o',
|
|
42
|
+
'-',
|
|
43
|
+
path.join(derivedDataDir, 'info.plist'),
|
|
44
|
+
]);
|
|
45
|
+
return stdout.trim();
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Finds the runner app built from the WebDriverAgent project of this driver.
|
|
53
|
+
* The newest one wins if there are many.
|
|
54
|
+
*/
|
|
55
|
+
async function findRunner(projectPath) {
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = await fs.readdir(DERIVED_DATA_ROOT);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const candidates = [];
|
|
63
|
+
for (const entry of entries.filter((name) => name.startsWith('WebDriverAgentMac-'))) {
|
|
64
|
+
const derivedDataDir = path.join(DERIVED_DATA_ROOT, entry);
|
|
65
|
+
const runnerPath = path.join(derivedDataDir, 'Build', 'Products', 'Debug', RUNNER_NAME);
|
|
66
|
+
if (!(await exists(runnerPath)) || (await readWorkspacePath(derivedDataDir)) !== projectPath) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
candidates.push({runnerPath, mtimeMs: (await fs.stat(runnerPath)).mtimeMs});
|
|
70
|
+
}
|
|
71
|
+
return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]?.runnerPath ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Stops the runner and the xcodebuild process that hosts it. macOS evaluates the permission
|
|
76
|
+
* when a process starts, so a runner started before the grant stays untrusted.
|
|
77
|
+
* Appium itself keeps running, but an active session loses its runner.
|
|
78
|
+
*/
|
|
79
|
+
async function stopRunningRunners() {
|
|
80
|
+
const {stdout} = await exec('ps', ['-axo', 'pid=,command=']);
|
|
81
|
+
const pids = [];
|
|
82
|
+
for (const line of stdout.split('\n')) {
|
|
83
|
+
const match = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
84
|
+
if (!match) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const pid = Number(match[1]);
|
|
88
|
+
const command = match[2];
|
|
89
|
+
const isRunner = command.includes(RUNNER_EXECUTABLE_FRAGMENT);
|
|
90
|
+
const isHost =
|
|
91
|
+
/(^|\/)xcodebuild\s/.test(command) &&
|
|
92
|
+
command.includes('test-without-building') &&
|
|
93
|
+
command.includes(`-scheme ${RUNNER_SCHEME}`);
|
|
94
|
+
if ((isRunner || isHost) && pid !== process.pid && pid !== process.ppid) {
|
|
95
|
+
pids.push(pid);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const pid of pids) {
|
|
99
|
+
try {
|
|
100
|
+
process.kill(pid, 'SIGTERM');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
log.warn(`Cannot stop the process ${pid}: ${e.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (pids.length > 0) {
|
|
106
|
+
log.info(`Stopped the running WebDriverAgent processes: ${pids.join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Builds the runner the same way the driver does
|
|
112
|
+
*/
|
|
113
|
+
async function buildRunner(projectPath) {
|
|
114
|
+
log.info(`Building ${RUNNER_NAME}. This may take a few minutes`);
|
|
115
|
+
try {
|
|
116
|
+
await exec(
|
|
117
|
+
'xcodebuild',
|
|
118
|
+
['build-for-testing', '-project', projectPath, '-scheme', RUNNER_SCHEME, DISABLE_STORE_ARG],
|
|
119
|
+
{cwd: path.dirname(projectPath)},
|
|
120
|
+
);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const output = String(e.stdout ?? '')
|
|
123
|
+
.trim()
|
|
124
|
+
.split('\n')
|
|
125
|
+
.slice(-20)
|
|
126
|
+
.join('\n');
|
|
127
|
+
throw new Error(`Cannot build ${RUNNER_NAME}: ${e.stderr || e.message}\n${output}`, {cause: e});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function setupAccessibility() {
|
|
132
|
+
const args = process.argv.slice(2);
|
|
133
|
+
const shouldOpen = !args.includes(NO_OPEN_FLAG);
|
|
134
|
+
const shouldStop = !args.includes(KEEP_RUNNING_FLAG);
|
|
135
|
+
const explicitPath = args.find((arg) => !arg.startsWith('--')) ?? process.env[RUNNER_PATH_ENV];
|
|
136
|
+
|
|
137
|
+
if (shouldStop) {
|
|
138
|
+
await stopRunningRunners();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
142
|
+
const projectPath = path.resolve(__dirname, '..', 'WebDriverAgentMac', XCODEPROJ_NAME);
|
|
143
|
+
let runnerPath = explicitPath ? path.resolve(explicitPath) : await findRunner(projectPath);
|
|
144
|
+
if (!runnerPath && !explicitPath) {
|
|
145
|
+
await buildRunner(projectPath);
|
|
146
|
+
runnerPath = await findRunner(projectPath);
|
|
147
|
+
}
|
|
148
|
+
if (!runnerPath || !(await exists(runnerPath))) {
|
|
149
|
+
log.error(
|
|
150
|
+
`Cannot find ${RUNNER_NAME}${explicitPath ? ` at '${runnerPath}'` : ` built from '${projectPath}'`}. ` +
|
|
151
|
+
`If your Xcode derived data folder is not in '${DERIVED_DATA_ROOT}', ` +
|
|
152
|
+
`set the ${RUNNER_PATH_ENV} environment variable to the full path to ${RUNNER_NAME}.`,
|
|
153
|
+
);
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log.info(`WebDriverAgent runner: '${runnerPath}'`);
|
|
159
|
+
log.info(
|
|
160
|
+
`Add the runner to System Settings > Privacy & Security > Accessibility and turn it on. ` +
|
|
161
|
+
`Then restart your Appium session. Do not rebuild the runner after granting the permission, ` +
|
|
162
|
+
`because the grant may be tied to the exact build.`,
|
|
163
|
+
);
|
|
164
|
+
if (!shouldOpen) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Selects the runner in Finder, so it can be dragged into the Accessibility list
|
|
168
|
+
await exec('open', ['-R', runnerPath]);
|
|
169
|
+
await exec('open', [SETTINGS_URL]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setupAccessibility().catch((e) => {
|
|
173
|
+
log.error(e.message);
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
});
|