Skip to content

Commit 6fa061f

Browse files
feat: Implement wadUrl capability
1 parent 6cd17ae commit 6fa061f

File tree

5 files changed

+103
-22
lines changed

5 files changed

+103
-22
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ appium:systemPort | The port number to execute Appium Windows Driver server list
4444
appium:prerun | An object containing either `script` or `command` key. The value of each key must be a valid PowerShell script or command to be executed prior to the WinAppDriver session startup. See [Power Shell commands execution](#power-shell-commands-execution) for more details. Example: `{script: 'Get-Process outlook -ErrorAction SilentlyContinue'}`
4545
appium:postrun | An object containing either `script` or `command` key. The value of each key must be a valid PowerShell script or command to be executed after WinAppDriver session is stopped. See [Power Shell commands execution](#power-shell-commands-execution) for more details.
4646
appium:newCommandTimeout | How long (in seconds) the driver should wait for a new command from the client before assuming the client has stopped sending requests. After the timeout, the session is going to be deleted. `60` seconds by default. Setting it to zero disables the timer.
47+
appium:wadUrl | Allows to provide a custom URL to the WAD server. The server must be already running when a new session starts. If this URL is provided explicitly then the driver won't try to either autodetect or start WinAppDriver automatically, and it is expected that the server lifecycle is managed externally.
4748

4849
## Driver Scripts
4950

lib/commands/general.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export async function getWindowRect () {
7777
* @param {number} y
7878
* @param {number} width
7979
* @param {number} height
80-
* @returns {Promise<void>}
80+
* @returns {Promise<import('@appium/types').Rect>}
8181
*/
8282
export async function setWindowRect (x, y, width, height) {
8383
let didProcess = false;
@@ -99,6 +99,7 @@ export async function setWindowRect (x, y, width, height) {
9999
if (!didProcess) {
100100
this.log.info('Either x and y or width and height must be defined. Doing nothing');
101101
}
102+
return {x, y, width, height};
102103
}
103104

104105
/**
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const desiredCapConstraints = /** @type {const} */ ({
1+
export const desiredCapConstraints = {
22
// https://github.com/microsoft/WinAppDriver/blob/master/Docs/AuthoringTestScripts.md#supported-capabilities
33
platformName: {
44
presence: true,
@@ -43,8 +43,10 @@ const desiredCapConstraints = /** @type {const} */ ({
4343
},
4444
postrun: {
4545
isObject: true
46-
}
47-
});
46+
},
47+
wadUrl: {
48+
isString: true
49+
},
50+
} as const;
4851

49-
export { desiredCapConstraints };
5052
export default desiredCapConstraints;

lib/driver.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ const NO_PROXY = [
4343
];
4444

4545
// Appium instantiates this class
46+
/**
47+
* @implements {ExternalDriver<WindowsDriverConstraints, string>}
48+
* @extends {BaseDriver<WindowsDriverConstraints>}
49+
*/
4650
export class WindowsDriver extends BaseDriver {
4751
/** @type {boolean} */
4852
isProxyActive;
@@ -113,6 +117,7 @@ export class WindowsDriver extends BaseDriver {
113117

114118
async startWinAppDriverSession () {
115119
this.winAppDriver = new WinAppDriver(this.log, {
120+
url: this.opts.wadUrl,
116121
port: this.opts.systemPort,
117122
reqBasePath: this.basePath,
118123
});
@@ -166,11 +171,11 @@ export class WindowsDriver extends BaseDriver {
166171
return this.jwpProxyAvoid;
167172
}
168173

169-
async proxyCommand (url, method, body = null) {
174+
async proxyCommand (url, method, body) {
170175
if (!this.winAppDriver?.proxy) {
171176
throw new Error('The proxy must be defined in order to send commands');
172177
}
173-
return await this.winAppDriver.proxy.command(url, method, body);
178+
return /** @type {any} */ (await this.winAppDriver.proxy.command(url, method, body));
174179
}
175180

176181
windowsLaunchApp = appManagementCommands.windowsLaunchApp;
@@ -221,4 +226,10 @@ export default WindowsDriver;
221226
/**
222227
* @typedef {typeof desiredCapConstraints} WindowsDriverConstraints
223228
* @typedef {import('@appium/types').DriverOpts<WindowsDriverConstraints>} WindowsDriverOpts
224-
*/
229+
*/
230+
231+
/**
232+
* @template {import('@appium/types').Constraints} C
233+
* @template [Ctx=string]
234+
* @typedef {import('@appium/types').ExternalDriver<C, Ctx>} ExternalDriver
235+
*/

lib/winappdriver.js

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class WADProcess {
4343
/**
4444
*
4545
* @param {import('@appium/types').AppiumLogger} log
46-
* @param {{base: string, port: number, executablePath: string, isForceQuitEnabled: boolean}} opts
46+
* @param {WADProcessOptions} opts
4747
*/
4848
constructor (log, opts) {
4949
this.log = log;
@@ -138,19 +138,30 @@ export class WinAppDriver {
138138
}
139139

140140
/**
141-
*
142-
* @param {import('@appium/types').StringRecord} caps
141+
* @param {WindowsDriverCaps} caps
143142
*/
144143
async start (caps) {
145-
const executablePath = await getWADExecutablePath();
146-
const isForceQuitEnabled = caps['ms:forcequit'] === true;
144+
if (this.opts.url) {
145+
await this._startSessionWithCustomServer(this.opts.url);
146+
} else {
147+
const isForceQuitEnabled = caps['ms:forcequit'] === true;
148+
await this._startSessionWithBuiltInServer(isForceQuitEnabled);
149+
}
147150

151+
await this._startSession(caps);
152+
}
153+
154+
/**
155+
* @param {boolean} isForceQuitEnabled
156+
* @returns {Promise<void>}
157+
*/
158+
async _startSessionWithBuiltInServer(isForceQuitEnabled) {
159+
const executablePath = await getWADExecutablePath();
148160
this.process = new WADProcess(this.log, {
149-
// XXXYD TODO: would be better if WinAppDriver didn't require passing in /wd/hub as a param
150161
base: DEFAULT_BASE_PATH,
151162
port: this.opts.port,
152163
executablePath,
153-
isForceQuitEnabled
164+
isForceQuitEnabled,
154165
});
155166
await this.process.start();
156167

@@ -190,22 +201,64 @@ export class WinAppDriver {
190201
});
191202
} catch (e) {
192203
if (/Condition unmet/.test(e.message)) {
193-
throw new Error(`WinAppDriver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
194-
`Make sure it could be started manually`);
204+
throw new Error(
205+
`WinAppDriver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
206+
`Make sure it could be started manually`
207+
);
195208
}
196209
throw e;
197210
}
198211
const pid = this.process.proc?.pid;
199212
RUNNING_PROCESS_IDS.push(pid);
200213
this.process.proc?.on('exit', () => void _.pull(RUNNING_PROCESS_IDS, pid));
214+
}
201215

202-
await this._startSession(caps);
216+
/**
217+
*
218+
* @param {string} url
219+
* @returns {Promise<void>}
220+
*/
221+
async _startSessionWithCustomServer (url) {
222+
this.log.info(`Using custom WinAppDriver server URL: ${url}`);
223+
224+
/** @type {URL} */
225+
let parsedUrl;
226+
try {
227+
parsedUrl = new URL(url);
228+
} catch (e) {
229+
throw new Error(
230+
`Cannot parse the provided WinAppDriver URL '${url}'. Original error: ${e.message}`
231+
);
232+
}
233+
const proxyOpts = {
234+
log: this.log,
235+
base: parsedUrl.pathname,
236+
server: parsedUrl.hostname,
237+
port: parsedUrl.port,
238+
scheme: _.trimEnd(parsedUrl.protocol, ':'),
239+
};
240+
if (this.opts.reqBasePath) {
241+
proxyOpts.reqBasePath = this.opts.reqBasePath;
242+
}
243+
this.proxy = new WADProxy(proxyOpts);
244+
245+
try {
246+
await this.proxy.command('/status', 'GET');
247+
} catch {
248+
throw new Error(
249+
`WinAppDriver server is not listening at ${url}. ` +
250+
`Make sure it is running and the provided URL is correct`
251+
);
252+
}
203253
}
204254

205-
async _startSession (desiredCapabilities) {
255+
/**
256+
* @param {WindowsDriverCaps} caps
257+
*/
258+
async _startSession (caps) {
206259
const {
207260
createSessionTimeout = DEFAULT_CREATE_SESSION_TIMEOUT_MS
208-
} = desiredCapabilities;
261+
} = caps;
209262
this.log.debug(`Starting WinAppDriver session. Will timeout in '${createSessionTimeout}' ms.`);
210263
let retryIteration = 0;
211264
let lastError;
@@ -214,7 +267,7 @@ export class WinAppDriver {
214267
lastError = null;
215268
retryIteration++;
216269
try {
217-
await this.proxy?.command('/session', 'POST', {desiredCapabilities});
270+
await this.proxy?.command('/session', 'POST', {desiredCapabilities: caps});
218271
return true;
219272
} catch (error) {
220273
lastError = error;
@@ -264,6 +317,19 @@ export default WinAppDriver;
264317

265318
/**
266319
* @typedef {Object} WinAppDriverOptions
267-
* @property {number} port
320+
* @property {number} [port]
268321
* @property {string} [reqBasePath]
322+
* @property {string} [url]
323+
*/
324+
325+
/**
326+
* @typedef {Object} WADProcessOptions
327+
* @property {string} base
328+
* @property {number} [port]
329+
* @property {string} executablePath
330+
* @property {boolean} isForceQuitEnabled
331+
*/
332+
333+
/**
334+
* @typedef {import('@appium/types').DriverCaps<import('./driver').WindowsDriverConstraints>} WindowsDriverCaps
269335
*/

0 commit comments

Comments
 (0)