Skip to content

Commit caff121

Browse files
feat: Migrate driver and helpers to typescript
1 parent 0c728be commit caff121

File tree

12 files changed

+324
-305
lines changed

12 files changed

+324
-305
lines changed
File renamed without changes.

lib/desired-caps.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type {Constraints} from '@appium/types';
2+
13
export const desiredCapConstraints = {
24
// https://github.com/microsoft/WinAppDriver/blob/master/Docs/AuthoringTestScripts.md#supported-capabilities
35
platformName: {
@@ -47,6 +49,4 @@ export const desiredCapConstraints = {
4749
wadUrl: {
4850
isString: true
4951
},
50-
} as const;
51-
52-
export default desiredCapConstraints;
52+
} as const satisfies Constraints;

lib/driver.js renamed to lib/driver.ts

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import _ from 'lodash';
2+
import type {
3+
RouteMatcher,
4+
HTTPMethod,
5+
HTTPBody,
6+
DefaultCreateSessionResult,
7+
DriverData,
8+
InitialOpts,
9+
StringRecord,
10+
ExternalDriver,
11+
DriverOpts,
12+
W3CDriverCaps,
13+
} from '@appium/types';
214
import { BaseDriver } from 'appium/driver';
315
import { system } from 'appium/support';
416
import { WinAppDriver } from './winappdriver';
17+
import type { WindowsDriverCaps } from './winappdriver';
518
import { desiredCapConstraints } from './desired-caps';
619
import * as appManagementCommands from './commands/app-management';
720
import * as clipboardCommands from './commands/clipboard';
@@ -19,8 +32,7 @@ import { POWER_SHELL_FEATURE } from './constants';
1932
import { newMethodMap } from './method-map';
2033
import { executeMethodMap } from './execute-method-map';
2134

22-
/** @type {import('@appium/types').RouteMatcher[]} */
23-
const NO_PROXY = [
35+
const NO_PROXY: RouteMatcher[] = [
2436
['GET', new RegExp('^/session/[^/]+/appium/(?!app/)[^/]+')],
2537
['POST', new RegExp('^/session/[^/]+/appium/(?!app/)[^/]+')],
2638
['POST', new RegExp('^/session/[^/]+/element/[^/]+/elements?$')],
@@ -48,28 +60,20 @@ const NO_PROXY = [
4860
];
4961

5062
// Appium instantiates this class
51-
/**
52-
* @implements {ExternalDriver<WindowsDriverConstraints, string>}
53-
* @extends {BaseDriver<WindowsDriverConstraints>}
54-
*/
55-
export class WindowsDriver extends BaseDriver {
56-
/** @type {boolean} */
57-
isProxyActive;
58-
59-
/** @type {import('@appium/types').RouteMatcher[]} */
60-
jwpProxyAvoid;
61-
62-
/** @type {WinAppDriver} */
63-
winAppDriver;
64-
65-
/** @type {import('./commands/record-screen').ScreenRecorder | null} */
66-
_screenRecorder;
63+
export class WindowsDriver
64+
extends BaseDriver<WindowsDriverConstraints, StringRecord>
65+
implements ExternalDriver<WindowsDriverConstraints, string, StringRecord>
66+
{
67+
private isProxyActive: boolean;
68+
private jwpProxyAvoid: RouteMatcher[];
69+
private _winAppDriver: WinAppDriver | null;
70+
_screenRecorder: recordScreenCommands.ScreenRecorder | null;
71+
public proxyReqRes: (...args: any) => any;
6772

6873
static newMethodMap = newMethodMap;
6974
static executeMethodMap = executeMethodMap;
7075

71-
constructor (opts = {}, shouldValidateCaps = true) {
72-
// @ts-ignore TODO: Make opts typed
76+
constructor(opts: InitialOpts, shouldValidateCaps = true) {
7377
super(opts, shouldValidateCaps);
7478
this.desiredCapConstraints = desiredCapConstraints;
7579
this.locatorStrategies = [
@@ -83,74 +87,67 @@ export class WindowsDriver extends BaseDriver {
8387
this.resetState();
8488
}
8589

86-
resetState () {
87-
this.jwpProxyAvoid = NO_PROXY;
88-
this.isProxyActive = false;
89-
// @ts-ignore It's ok
90-
this.winAppDriver = null;
91-
this._screenRecorder = null;
90+
get winAppDriver(): WinAppDriver {
91+
if (!this._winAppDriver) {
92+
throw new Error('WinAppDriver is not started');
93+
}
94+
return this._winAppDriver;
9295
}
9396

94-
// @ts-ignore TODO: Make args typed
95-
async createSession (...args) {
97+
override async createSession(
98+
w3cCaps1: W3CWindowsDriverCaps,
99+
w3cCaps2?: W3CWindowsDriverCaps,
100+
w3cCaps3?: W3CWindowsDriverCaps,
101+
driverData?: DriverData[]
102+
): Promise<DefaultCreateSessionResult<WindowsDriverConstraints>> {
96103
if (!system.isWindows()) {
97104
throw new Error('WinAppDriver tests only run on Windows');
98105
}
99106

100107
try {
101-
// @ts-ignore TODO: Make args typed
102-
const [sessionId, caps] = await super.createSession(...args);
108+
const [sessionId, caps] = await super.createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData);
109+
this.caps = caps;
110+
this.opts = this.opts as WindowsDriverOpts;
103111
if (caps.prerun) {
104112
this.log.info('Executing prerun PowerShell script');
105-
if (!_.isString(caps.prerun.command) && !_.isString(caps.prerun.script)) {
113+
const prerun = caps.prerun as PrerunCapability;
114+
if (!_.isString(prerun.command) && !_.isString(prerun.script)) {
106115
throw new Error(`'prerun' capability value must either contain ` +
107116
`'script' or 'command' entry of string type`);
108117
}
109118
this.assertFeatureEnabled(POWER_SHELL_FEATURE);
110-
const output = await this.execPowerShell(caps.prerun);
119+
const output = await this.execPowerShell(prerun);
111120
if (output) {
112121
this.log.info(`Prerun script output: ${output}`);
113122
}
114123
}
115124
await this.startWinAppDriverSession();
116125
return [sessionId, caps];
117-
} catch (e) {
126+
} catch (e: any) {
118127
await this.deleteSession();
119128
throw e;
120129
}
121130
}
122131

123-
async startWinAppDriverSession () {
124-
this.winAppDriver = new WinAppDriver(this.log, {
125-
url: this.opts.wadUrl,
126-
port: this.opts.systemPort,
127-
reqBasePath: this.basePath,
128-
});
129-
await this.winAppDriver.start(this.caps);
130-
this.proxyReqRes = this.winAppDriver.proxy?.proxyReqRes.bind(this.winAppDriver.proxy);
131-
// now that everything has started successfully, turn on proxying so all
132-
// subsequent session requests go straight to/from WinAppDriver
133-
this.isProxyActive = true;
134-
}
135-
136-
async deleteSession () {
132+
override async deleteSession(): Promise<void> {
137133
this.log.debug('Deleting WinAppDriver session');
138134
await this._screenRecorder?.stop(true);
139-
await this.winAppDriver?.stop();
135+
await this._winAppDriver?.stop();
140136

141-
if (this.opts.postrun) {
142-
if (!_.isString(this.opts.postrun.command) && !_.isString(this.opts.postrun.script)) {
137+
const postrun = this.opts.postrun as PostrunCapability | undefined;
138+
if (postrun) {
139+
if (!_.isString(postrun.command) && !_.isString(postrun.script)) {
143140
this.log.error(`'postrun' capability value must either contain ` +
144141
`'script' or 'command' entry of string type`);
145142
} else {
146143
this.log.info('Executing postrun PowerShell script');
147144
try {
148145
this.assertFeatureEnabled(POWER_SHELL_FEATURE);
149-
const output = await this.execPowerShell(this.opts.postrun);
146+
const output = await this.execPowerShell(postrun);
150147
if (output) {
151148
this.log.info(`Postrun script output: ${output}`);
152149
}
153-
} catch (e) {
150+
} catch (e: any) {
154151
this.log.error(e.message);
155152
}
156153
}
@@ -162,25 +159,45 @@ export class WindowsDriver extends BaseDriver {
162159
}
163160

164161
// eslint-disable-next-line @typescript-eslint/no-unused-vars
165-
proxyActive (sessionId) {
162+
override proxyActive(sessionId: string): boolean {
166163
return this.isProxyActive;
167164
}
168165

169-
canProxy () {
166+
override canProxy(): boolean {
170167
// we can always proxy to the WinAppDriver server
171168
return true;
172169
}
173170

174171
// eslint-disable-next-line @typescript-eslint/no-unused-vars
175-
getProxyAvoidList (sessionId) {
172+
override getProxyAvoidList(sessionId: string): RouteMatcher[] {
176173
return this.jwpProxyAvoid;
177174
}
178175

179-
async proxyCommand (url, method, body) {
176+
async proxyCommand(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<any> {
180177
if (!this.winAppDriver?.proxy) {
181178
throw new Error('The proxy must be defined in order to send commands');
182179
}
183-
return /** @type {any} */ (await this.winAppDriver.proxy.command(url, method, body));
180+
return await this.winAppDriver.proxy.command(url, method, body);
181+
}
182+
183+
async startWinAppDriverSession(): Promise<void> {
184+
this._winAppDriver = new WinAppDriver(this.log, {
185+
url: this.opts.wadUrl,
186+
port: this.opts.systemPort,
187+
reqBasePath: this.basePath,
188+
});
189+
await this.winAppDriver.start(this.caps as any as WindowsDriverCaps);
190+
this.proxyReqRes = this.winAppDriver.proxy?.proxyReqRes.bind(this.winAppDriver.proxy);
191+
// now that everything has started successfully, turn on proxying so all
192+
// subsequent session requests go straight to/from WinAppDriver
193+
this.isProxyActive = true;
194+
}
195+
196+
private resetState(): void {
197+
this.jwpProxyAvoid = NO_PROXY;
198+
this.isProxyActive = false;
199+
this._winAppDriver = null;
200+
this._screenRecorder = null;
184201
}
185202

186203
windowsLaunchApp = appManagementCommands.windowsLaunchApp;
@@ -197,7 +214,6 @@ export class WindowsDriver extends BaseDriver {
197214
windowsDeleteFile = fileCommands.windowsDeleteFile;
198215
windowsDeleteFolder = fileCommands.windowsDeleteFolder;
199216

200-
// @ts-ignore This is expected
201217
findElOrEls = findCommands.findElOrEls;
202218

203219
getWindowSize = generalCommands.getWindowSize;
@@ -230,13 +246,16 @@ export class WindowsDriver extends BaseDriver {
230246

231247
export default WindowsDriver;
232248

233-
/**
234-
* @typedef {typeof desiredCapConstraints} WindowsDriverConstraints
235-
* @typedef {import('@appium/types').DriverOpts<WindowsDriverConstraints>} WindowsDriverOpts
236-
*/
249+
interface PrerunCapability {
250+
command?: string;
251+
script?: string;
252+
}
253+
254+
interface PostrunCapability {
255+
command?: string;
256+
script?: string;
257+
}
237258

238-
/**
239-
* @template {import('@appium/types').Constraints} C
240-
* @template [Ctx=string]
241-
* @typedef {import('@appium/types').ExternalDriver<C, Ctx>} ExternalDriver
242-
*/
259+
type WindowsDriverConstraints = typeof desiredCapConstraints;
260+
type WindowsDriverOpts = DriverOpts<WindowsDriverConstraints>;
261+
type W3CWindowsDriverCaps = W3CDriverCaps<WindowsDriverConstraints>;

lib/execute-method-map.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { ExecuteMethodMap } from '@appium/types';
1+
import type { ExecuteMethodMap } from '@appium/types';
2+
import type { WindowsDriver } from './driver';
23

34
export const executeMethodMap = {
45
'windows: startRecordingScreen': {
6+
// @ts-ignore Type checked is confused
57
command: 'windowsStartRecordingScreen',
68
params: {
79
optional: [
@@ -142,4 +144,4 @@ export const executeMethodMap = {
142144
],
143145
},
144146
},
145-
} as const satisfies ExecuteMethodMap<any>;
147+
} as const satisfies ExecuteMethodMap<WindowsDriver>;

lib/installer.js renamed to lib/installer.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { fs, tempDir } from 'appium/support';
33
import path from 'path';
44
import { exec } from 'teen_process';
55
import { log } from './logger';
6-
import { queryRegistry } from './registry';
6+
import { queryRegistry, type RegEntry } from './registry';
77
import { runElevated } from './utils';
88

99
const POSSIBLE_WAD_INSTALL_ROOTS = [
@@ -16,7 +16,7 @@ const UNINSTALL_REG_ROOT = 'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\
1616
const REG_ENTRY_VALUE = 'Windows Application Driver';
1717
const REG_ENTRY_KEY = 'DisplayName';
1818
const REG_ENTRY_TYPE = 'REG_SZ';
19-
const INST_LOCATION_SCRIPT_BY_GUID = (guid) => `
19+
const INST_LOCATION_SCRIPT_BY_GUID = (guid: string): string => `
2020
Set installer = CreateObject("WindowsInstaller.Installer")
2121
Set session = installer.OpenProduct("${guid}")
2222
session.DoAction("CostInitialize")
@@ -25,11 +25,12 @@ WScript.Echo session.Property("INSTALLFOLDER")
2525
`.replace(/\n/g, '\r\n');
2626

2727
/**
28+
* Fetches the MSI installation location for a given installer GUID
2829
*
29-
* @param {string} installerGuid
30-
* @returns {Promise<string>} install location
30+
* @param installerGuid - The MSI installer GUID
31+
* @returns The installation location path
3132
*/
32-
async function fetchMsiInstallLocation (installerGuid) {
33+
async function fetchMsiInstallLocation(installerGuid: string): Promise<string> {
3334
const tmpRoot = await tempDir.openDir();
3435
const scriptPath = path.join(tmpRoot, 'get_wad_inst_location.vbs');
3536
try {
@@ -43,7 +44,7 @@ async function fetchMsiInstallLocation (installerGuid) {
4344

4445
class WADNotFoundError extends Error {}
4546

46-
export const getWADExecutablePath = _.memoize(async function getWADInstallPath () {
47+
export const getWADExecutablePath = _.memoize(async function getWADInstallPath(): Promise<string> {
4748
const wadPath = process.env.APPIUM_WAD_PATH ?? '';
4849
if (await fs.exists(wadPath)) {
4950
log.debug(`Loaded WinAppDriver path from the APPIUM_WAD_PATH environment variable: ${wadPath}`);
@@ -53,9 +54,8 @@ export const getWADExecutablePath = _.memoize(async function getWADInstallPath (
5354
// TODO: WAD installer should write the full path to it into the system registry
5455
const pathCandidates = POSSIBLE_WAD_INSTALL_ROOTS
5556
// remove unset env variables
56-
.filter(Boolean)
57+
.filter((root): root is string => Boolean(root))
5758
// construct full path
58-
// @ts-ignore The above filter does the job
5959
.map((root) => path.resolve(root, REG_ENTRY_VALUE, WAD_EXE_NAME));
6060
for (const result of pathCandidates) {
6161
if (await fs.exists(result)) {
@@ -66,12 +66,12 @@ export const getWADExecutablePath = _.memoize(async function getWADInstallPath (
6666
log.debug('Checking the system registry for the corresponding MSI entry');
6767
try {
6868
const uninstallEntries = await queryRegistry(UNINSTALL_REG_ROOT);
69-
const wadEntry = uninstallEntries.find(({key, type, value}) =>
70-
key === REG_ENTRY_KEY && value === REG_ENTRY_VALUE && type === REG_ENTRY_TYPE
69+
const wadEntry = uninstallEntries.find((entry: RegEntry) =>
70+
entry.key === REG_ENTRY_KEY && entry.value === REG_ENTRY_VALUE && entry.type === REG_ENTRY_TYPE
7171
);
7272
if (wadEntry) {
7373
log.debug(`Found MSI entry: ${JSON.stringify(wadEntry)}`);
74-
const installerGuid = /** @type {string} */ (_.last(wadEntry.root.split('\\')));
74+
const installerGuid = _.last(wadEntry.root.split('\\')) as string;
7575
// WAD MSI installer leaves InstallLocation registry value empty,
7676
// so we need to be hacky here
7777
const result = path.join(
@@ -86,7 +86,7 @@ export const getWADExecutablePath = _.memoize(async function getWADInstallPath (
8686
} else {
8787
log.debug('No WAD MSI entries have been found');
8888
}
89-
} catch (e) {
89+
} catch (e: any) {
9090
if (e.stderr) {
9191
log.debug(e.stderr);
9292
}
@@ -102,14 +102,16 @@ export const getWADExecutablePath = _.memoize(async function getWADInstallPath (
102102
});
103103

104104
/**
105+
* Checks if the current process is running with administrator privileges
105106
*
106-
* @returns {Promise<boolean>}
107+
* @returns Promise that resolves to true if running as admin, false otherwise
107108
*/
108-
export async function isAdmin () {
109+
export async function isAdmin(): Promise<boolean> {
109110
try {
110111
await exec('fsutil.exe', ['dirty', 'query', process.env.SystemDrive || 'C:']);
111112
return true;
112113
} catch {
113114
return false;
114115
}
115-
};
116+
}
117+
File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// https://github.com/microsoft/WinAppDriver/blob/master/Docs/SupportedAPIs.md
22

3-
export const newMethodMap = /** @type {const} */ ({
3+
export const newMethodMap = {
44
'/session/:sessionId/appium/start_recording_screen': {
55
POST: {
66
command: 'startRecordingScreen',
@@ -114,4 +114,4 @@ export const newMethodMap = /** @type {const} */ ({
114114
'/session/:sessionId/element/:elementId/equals/:otherId': {
115115
GET: {command: 'equalsElement'},
116116
},
117-
});
117+
} as const;

0 commit comments

Comments
 (0)