Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ appium:systemPort | The port number to execute Appium Windows Driver server list
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'}`
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.
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.
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.

## Driver Scripts

Expand Down
3 changes: 2 additions & 1 deletion lib/commands/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function getWindowRect () {
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {Promise<void>}
* @returns {Promise<import('@appium/types').Rect>}
*/
export async function setWindowRect (x, y, width, height) {
let didProcess = false;
Expand All @@ -99,6 +99,7 @@ export async function setWindowRect (x, y, width, height) {
if (!didProcess) {
this.log.info('Either x and y or width and height must be defined. Doing nothing');
}
return {x, y, width, height};
}

/**
Expand Down
10 changes: 6 additions & 4 deletions lib/desired-caps.js → lib/desired-caps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const desiredCapConstraints = /** @type {const} */ ({
export const desiredCapConstraints = {
// https://github.com/microsoft/WinAppDriver/blob/master/Docs/AuthoringTestScripts.md#supported-capabilities
platformName: {
presence: true,
Expand Down Expand Up @@ -43,8 +43,10 @@ const desiredCapConstraints = /** @type {const} */ ({
},
postrun: {
isObject: true
}
});
},
wadUrl: {
isString: true
},
} as const;

export { desiredCapConstraints };
export default desiredCapConstraints;
17 changes: 14 additions & 3 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const NO_PROXY = [
];

// Appium instantiates this class
/**
* @implements {ExternalDriver<WindowsDriverConstraints, string>}
* @extends {BaseDriver<WindowsDriverConstraints>}
*/
export class WindowsDriver extends BaseDriver {
/** @type {boolean} */
isProxyActive;
Expand Down Expand Up @@ -113,6 +117,7 @@ export class WindowsDriver extends BaseDriver {

async startWinAppDriverSession () {
this.winAppDriver = new WinAppDriver(this.log, {
url: this.opts.wadUrl,
port: this.opts.systemPort,
reqBasePath: this.basePath,
});
Expand Down Expand Up @@ -166,11 +171,11 @@ export class WindowsDriver extends BaseDriver {
return this.jwpProxyAvoid;
}

async proxyCommand (url, method, body = null) {
async proxyCommand (url, method, body) {
if (!this.winAppDriver?.proxy) {
throw new Error('The proxy must be defined in order to send commands');
}
return await this.winAppDriver.proxy.command(url, method, body);
return /** @type {any} */ (await this.winAppDriver.proxy.command(url, method, body));
}

windowsLaunchApp = appManagementCommands.windowsLaunchApp;
Expand Down Expand Up @@ -221,4 +226,10 @@ export default WindowsDriver;
/**
* @typedef {typeof desiredCapConstraints} WindowsDriverConstraints
* @typedef {import('@appium/types').DriverOpts<WindowsDriverConstraints>} WindowsDriverOpts
*/
*/

/**
* @template {import('@appium/types').Constraints} C
* @template [Ctx=string]
* @typedef {import('@appium/types').ExternalDriver<C, Ctx>} ExternalDriver
*/
96 changes: 82 additions & 14 deletions lib/winappdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class WADProcess {
/**
*
* @param {import('@appium/types').AppiumLogger} log
* @param {{base: string, port: number, executablePath: string, isForceQuitEnabled: boolean}} opts
* @param {WADProcessOptions} opts
*/
constructor (log, opts) {
this.log = log;
Expand Down Expand Up @@ -138,22 +138,34 @@ export class WinAppDriver {
}

/**
*
* @param {import('@appium/types').StringRecord} caps
* @param {WindowsDriverCaps} caps
*/
async start (caps) {
const executablePath = await getWADExecutablePath();
const isForceQuitEnabled = caps['ms:forcequit'] === true;
if (this.opts.url) {
await this._prepareSessionWithCustomServer(this.opts.url);
} else {
const isForceQuitEnabled = caps['ms:forcequit'] === true;
await this._prepareSessionWithBuiltInServer(isForceQuitEnabled);
}

await this._startSession(caps);
}

/**
* @param {boolean} isForceQuitEnabled
* @returns {Promise<void>}
*/
async _prepareSessionWithBuiltInServer(isForceQuitEnabled) {
const executablePath = await getWADExecutablePath();
this.process = new WADProcess(this.log, {
// XXXYD TODO: would be better if WinAppDriver didn't require passing in /wd/hub as a param
base: DEFAULT_BASE_PATH,
port: this.opts.port,
executablePath,
isForceQuitEnabled
isForceQuitEnabled,
});
await this.process.start();

/** @type {import('@appium/types').ProxyOptions} */
const proxyOpts = {
log: this.log,
base: this.process.base,
Expand Down Expand Up @@ -190,22 +202,65 @@ export class WinAppDriver {
});
} catch (e) {
if (/Condition unmet/.test(e.message)) {
throw new Error(`WinAppDriver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
`Make sure it could be started manually`);
throw new Error(
`WinAppDriver server is not listening within ${STARTUP_TIMEOUT_MS}ms timeout. ` +
`Make sure it could be started manually`
);
}
throw e;
}
const pid = this.process.proc?.pid;
RUNNING_PROCESS_IDS.push(pid);
this.process.proc?.on('exit', () => void _.pull(RUNNING_PROCESS_IDS, pid));
}

await this._startSession(caps);
/**
*
* @param {string} url
* @returns {Promise<void>}
*/
async _prepareSessionWithCustomServer (url) {
this.log.info(`Using custom WinAppDriver server URL: ${url}`);

/** @type {URL} */
let parsedUrl;
try {
parsedUrl = new URL(url);
} catch (e) {
throw new Error(
`Cannot parse the provided WinAppDriver URL '${url}'. Original error: ${e.message}`
);
}
/** @type {import('@appium/types').ProxyOptions} */
const proxyOpts = {
log: this.log,
base: parsedUrl.pathname,
server: parsedUrl.hostname,
port: parseInt(parsedUrl.port, 10),
scheme: _.trimEnd(parsedUrl.protocol, ':'),
};
if (this.opts.reqBasePath) {
proxyOpts.reqBasePath = this.opts.reqBasePath;
}
this.proxy = new WADProxy(proxyOpts);

try {
await this.proxy.command('/status', 'GET');
} catch {
throw new Error(
`WinAppDriver server is not listening at ${url}. ` +
`Make sure it is running and the provided wadUrl is correct`
);
}
}

async _startSession (desiredCapabilities) {
/**
* @param {WindowsDriverCaps} caps
*/
async _startSession (caps) {
const {
createSessionTimeout = DEFAULT_CREATE_SESSION_TIMEOUT_MS
} = desiredCapabilities;
} = caps;
this.log.debug(`Starting WinAppDriver session. Will timeout in '${createSessionTimeout}' ms.`);
let retryIteration = 0;
let lastError;
Expand All @@ -214,7 +269,7 @@ export class WinAppDriver {
lastError = null;
retryIteration++;
try {
await this.proxy?.command('/session', 'POST', {desiredCapabilities});
await this.proxy?.command('/session', 'POST', {desiredCapabilities: caps});
return true;
} catch (error) {
lastError = error;
Expand Down Expand Up @@ -264,6 +319,19 @@ export default WinAppDriver;

/**
* @typedef {Object} WinAppDriverOptions
* @property {number} port
* @property {number} [port]
* @property {string} [reqBasePath]
* @property {string} [url]
*/

/**
* @typedef {Object} WADProcessOptions
* @property {string} base
* @property {number} [port]
* @property {string} executablePath
* @property {boolean} isForceQuitEnabled
*/

/**
* @typedef {import('@appium/types').DriverCaps<import('./driver').WindowsDriverConstraints>} WindowsDriverCaps
*/