Skip to content

Commit d5b9971

Browse files
feat: Implement wadUrl capability (#306)
1 parent 6cd17ae commit d5b9971

File tree

5 files changed

+105
-22
lines changed

5 files changed

+105
-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: 82 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,22 +138,34 @@ 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._prepareSessionWithCustomServer(this.opts.url);
146+
} else {
147+
const isForceQuitEnabled = caps['ms:forcequit'] === true;
148+
await this._prepareSessionWithBuiltInServer(isForceQuitEnabled);
149+
}
147150

151+
await this._startSession(caps);
152+
}
153+
154+
/**
155+
* @param {boolean} isForceQuitEnabled
156+
* @returns {Promise<void>}
157+
*/
158+
async _prepareSessionWithBuiltInServer(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

168+
/** @type {import('@appium/types').ProxyOptions} */
157169
const proxyOpts = {
158170
log: this.log,
159171
base: this.process.base,
@@ -190,22 +202,65 @@ export class WinAppDriver {
190202
});
191203
} catch (e) {
192204
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`);
205+
throw new Error(
206+
`WinAppDriver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
207+
`Make sure it could be started manually`
208+
);
195209
}
196210
throw e;
197211
}
198212
const pid = this.process.proc?.pid;
199213
RUNNING_PROCESS_IDS.push(pid);
200214
this.process.proc?.on('exit', () => void _.pull(RUNNING_PROCESS_IDS, pid));
215+
}
201216

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

205-
async _startSession (desiredCapabilities) {
257+
/**
258+
* @param {WindowsDriverCaps} caps
259+
*/
260+
async _startSession (caps) {
206261
const {
207262
createSessionTimeout = DEFAULT_CREATE_SESSION_TIMEOUT_MS
208-
} = desiredCapabilities;
263+
} = caps;
209264
this.log.debug(`Starting WinAppDriver session. Will timeout in '${createSessionTimeout}' ms.`);
210265
let retryIteration = 0;
211266
let lastError;
@@ -214,7 +269,7 @@ export class WinAppDriver {
214269
lastError = null;
215270
retryIteration++;
216271
try {
217-
await this.proxy?.command('/session', 'POST', {desiredCapabilities});
272+
await this.proxy?.command('/session', 'POST', {desiredCapabilities: caps});
218273
return true;
219274
} catch (error) {
220275
lastError = error;
@@ -264,6 +319,19 @@ export default WinAppDriver;
264319

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

0 commit comments

Comments
 (0)