Skip to content

Commit 095b2af

Browse files
committed
feat: making screen recorder ffmpeg auto-downloadable and updated webview capability names
1 parent a08f775 commit 095b2af

File tree

10 files changed

+377
-137
lines changed

10 files changed

+377
-137
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,8 @@ build/
140140

141141
# Ignore package-lock.json in favour of npm-shrinkwrap.json for npm publishing
142142
package-lock.json*
143+
144+
# Ignore downloaded chromedriver/edgedriver/ffmpeg binaries
145+
chromedriver/
146+
edgedriver/
147+
ffmpeg/

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ appWorkingDir | Optional working directory path for the application.
4848
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'}`
4949
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.
5050
isolatedScriptExecution | Whether PowerShell scripts are executed in an isolated session. Default is `false`.
51-
enableWebView | Whether to enable WebView support. Set to true to allow switching into WebView contexts. Default is `false`.
51+
webviewEnabled | Whether to enable WebView support. Set to true to allow switching into WebView contexts. Default is `false`.
5252
webviewDevtoolsPort | The local port number to use for devtools communication. By default the first free port from 10900..11000 range is selected. Set a custom port if running parallel tests or when app is "none"/"root"/appTopLevelWindow is specified.
53+
chromedriverCdnUrl | Base URL used to download ChromeDriver binaries for automating Chromium-based WebViews in desktop applications. Defaults to `https://storage.googleapis.com/chrome-for-testing-public`.
54+
edgedriverCdnUrl | Base URL used to download EdgeDriver binaries for automating Edge (WebView2)-based WebViews in desktop applications. Defaults to `https://msedgedriver.microsoft.com`.
55+
edgedriverExecutablePath | Absolute file path to a locally provided Microsoft Edge WebDriver binary. When this is set, automatic download via edgedriverCdnUrl is disabled and the provided executable is used directly. The binary must be explicitly supplied by the user (e.g. downloaded manually or stored in CI artifacts). It is the user’s responsibility to ensure the driver version matches the installed Edge / WebView2 runtime version used by the target environment, otherwise automation may fail due to version incompatibility.
56+
chromedriverExecutablePath | Absolute file path to a locally provided ChromeDriver binary. When this is set, automatic download via chromedriverCdnUrl is disabled and the provided executable is used directly. The binary must be explicitly supplied (manually downloaded or managed externally, such as in CI). The user must ensure version compatibility between ChromeDriver and the target Chromium / WebView version, as mismatches can break automation.
57+
ffmpegExecutablePath | Absolute file path to a locally provided FFmpeg executable binary. When this is set, automatic download of FFmpeg is disabled and the provided executable is used directly. The binary must be supplied manually (e.g. downloaded and stored in CI artifacts or bundled externally). It is the user’s responsibility to ensure the FFmpeg build is compatible with the target Windows environment. If the path is invalid or the file does not exist, execution will fail with an error.
5358

5459
Please note that more capabilities will be added as the development of this driver progresses. Since it is still in its early stages, some features may be missing or subject to change. If you need a specific capability or encounter any issues, please feel free to open an issue.
5560

lib/commands/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { sleep } from '../util';
1717
import { errors, W3C_ELEMENT_KEY } from '@appium/base-driver';
1818
import {
19-
getWindowAllHandlesForProcessIds,
19+
// getWindowAllHandlesForProcessIds,
2020
keyDown,
2121
keyUp,
2222
trySetForegroundWindow,

lib/commands/contexts.ts

Lines changed: 38 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import { Chromedriver, ChromedriverOpts } from 'appium-chromedriver';
22
import { fs, node, system, tempDir, zip } from '@appium/support';
3-
import http from 'node:http';
4-
import https from 'node:https';
53
import path from 'node:path';
6-
import { pipeline } from 'node:stream/promises';
7-
import { sleep } from '../util';
4+
import { cdpRequest, downloadFile, sleep, MODULE_NAME } from '../util';
85
import { NovaWindowsDriver } from '../driver';
96
import { errors } from '@appium/base-driver';
107

118
const NATIVE_APP = 'NATIVE_APP';
129
const WEBVIEW = 'WEBVIEW';
1310
const WEBVIEW_BASE = `${WEBVIEW}_`;
1411

15-
const MODULE_NAME = 'appium-novawindows-driver';
16-
1712
export async function getCurrentContext(this: NovaWindowsDriver): Promise<string> {
1813
return this.currentContext ??= NATIVE_APP;
1914
}
@@ -159,7 +154,7 @@ interface CDPListResponseEntry {
159154
type CDPListResponse = CDPListResponseEntry[];
160155

161156
export async function getWebViewDetails(this: NovaWindowsDriver, waitForWebviewMs?: number): Promise<WebViewDetails> {
162-
if (!this.caps.enableWebView) {
157+
if (!this.caps.webviewEnabled) {
163158
throw new errors.InvalidArgumentError('WebView support is not enabled. Please set the "enableWebView" capability to true and try again.');
164159
}
165160

@@ -207,28 +202,57 @@ async function getDriverExecutable(this: NovaWindowsDriver, browserType: 'Edge'
207202
await fs.mkdir(driverDir);
208203
}
209204

210-
let downloadUrl = '';
211205
const fileName = browserType === 'Edge' ? 'msedgedriver.exe' : 'chromedriver.exe';
212206
const finalPath = path.join(driverDir, browserVersion, fileName);
213207

214208
if (await fs.exists(finalPath)) {
215209
return finalPath;
216-
};
210+
}
211+
212+
const executablePath = browserType === 'Edge'
213+
? this.caps.edgedriverExecutablePath
214+
: this.caps.chromedriverExecutablePath;
215+
216+
if (executablePath) {
217+
const exists = await fs.exists(executablePath);
218+
if (!exists) {
219+
throw new errors.InvalidArgumentError(`Driver executable not found at path: ${executablePath}`);
220+
}
221+
222+
this.log.debug(
223+
`Using local ${browserType} driver executable at ${executablePath}. ` +
224+
`Automatic download is disabled and CDN URLs are ignored. ` +
225+
`You must ensure this binary matches the WebView/Chromium version (${browserVersion}).`
226+
);
227+
228+
return executablePath;
229+
}
217230

218231
const arch = await system.arch();
219232
const zipFilename = `${driverType}${browserType === 'Edge' ? '_' : '-'}win${arch}.zip`;
220233

234+
const CHROME_BASE_URL = this.caps.chromedriverCdnUrl || 'https://storage.googleapis.com/chrome-for-testing-public';
235+
const EDGE_BASE_URL = this.caps.edgedriverCdnUrl || 'https://msedgedriver.microsoft.com';
236+
237+
let downloadUrl = '';
238+
221239
if (browserType === 'Chrome') {
222-
downloadUrl = `https://storage.googleapis.com/chrome-for-testing-public/${browserVersion}/win${arch}/${zipFilename}`;
223-
} else if (browserType === 'Edge') {
224-
downloadUrl = `https://msedgedriver.microsoft.com/${browserVersion}/${zipFilename}`;
240+
const url = new URL(CHROME_BASE_URL);
241+
url.pathname = path.posix.join(url.pathname, browserVersion, `win${arch}`, zipFilename);
242+
downloadUrl = url.toString();
243+
} else {
244+
const url = new URL(EDGE_BASE_URL);
245+
url.pathname = path.posix.join(url.pathname, browserVersion, zipFilename);
246+
downloadUrl = url.toString();
225247
}
226248

227249
this.log.debug(`Downloading ${browserType} driver version ${browserVersion}...`);
228250
const tmpRoot = await tempDir.openDir();
229251
await downloadFile(downloadUrl, tmpRoot);
252+
230253
try {
231254
await zip.extractAllTo(path.join(tmpRoot, zipFilename), tmpRoot);
255+
232256
const driverPath = await fs.walkDir(
233257
tmpRoot,
234258
true,
@@ -237,78 +261,12 @@ async function getDriverExecutable(this: NovaWindowsDriver, browserType: 'Edge'
237261
if (!driverPath) {
238262
throw new errors.UnknownError(`The archive was unzipped properly, but did not find any ${driverType} executable.`);
239263
}
264+
240265
this.log.debug(`Moving the extracted '${fileName}' to '${finalPath}'`);
241266
await fs.mv(driverPath, finalPath, { mkdirp: true });
242267
} finally {
243268
await fs.rimraf(tmpRoot);
244269
}
245-
return finalPath;
246-
}
247-
248-
async function cdpRequest<T = unknown>(this: NovaWindowsDriver, { host, port, endpoint, timeout }): Promise<T> {
249-
if (this?.log) {
250-
this.log.debug(`Sending request to ${host}:${port}${endpoint}`);
251-
}
252270

253-
return new Promise<T>((resolve, reject) => {
254-
const options = {
255-
hostname: host,
256-
port,
257-
path: endpoint,
258-
method: 'GET',
259-
agent: new http.Agent({ keepAlive: false }),
260-
timeout,
261-
};
262-
263-
const req = http.request(options, (res) => {
264-
let data = '';
265-
res.on('data', (chunk) => {
266-
data += chunk;
267-
});
268-
res.on('end', () => {
269-
try {
270-
resolve(JSON.parse(data));
271-
} catch (err) {
272-
reject(err);
273-
}
274-
});
275-
});
276-
277-
req.on('error', reject);
278-
req.on('timeout', () => {
279-
req.destroy(new Error('Request timed out'));
280-
});
281-
282-
req.end();
283-
});
284-
}
285-
286-
async function downloadFile(url: string, destPath: string, timeout = 30000): Promise<void> {
287-
const protocol = url.startsWith('https') ? https : http;
288-
const fileName = path.basename(new URL(url).pathname);
289-
290-
const fullFilePath = path.join(destPath, fileName);
291-
292-
return new Promise<void>((resolve, reject) => {
293-
const req = protocol.get(url, async (res) => {
294-
if (res.statusCode !== 200) {
295-
return reject(new Error(`Download failed: ${res.statusCode}`));
296-
}
297-
298-
try {
299-
const fileStream = fs.createWriteStream(fullFilePath);
300-
await pipeline(res, fileStream);
301-
resolve();
302-
} catch (err) {
303-
await fs.unlink(fullFilePath).catch(() => { });
304-
reject(err);
305-
}
306-
});
307-
308-
req.on('error', reject);
309-
req.setTimeout(timeout, () => {
310-
req.destroy();
311-
reject(new Error(`Timeout downloading from ${url}`));
312-
});
313-
});
271+
return finalPath;
314272
}

lib/commands/extension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export async function execute(this: NovaWindowsDriver, script: string, args: any
120120
}
121121

122122
if (script === 'mobile:getContexts') {
123-
if (!this.caps.enableWebView) {
123+
if (!this.caps.webviewEnabled) {
124124
throw new errors.InvalidArgumentError('WebView support is not enabled. To use this command, enable WebView support by setting the "enableWebView" capability to true.');
125125
}
126126
const { waitForWebviewMs }: { waitForWebviewMs?: number } = args[0] || {};
@@ -720,7 +720,7 @@ export async function startRecordingScreen(this: NovaWindowsDriver, args?: {
720720
}
721721
}
722722
const videoPath = outputPath ?? join(tmpdir(), `novawindows-recording-${Date.now()}.${DEFAULT_EXT}`);
723-
this._screenRecorder = new ScreenRecorder(videoPath, this.log, {
723+
this._screenRecorder = new ScreenRecorder(videoPath, this, {
724724
fps: fps !== undefined ? parseInt(String(fps), 10) : undefined,
725725
timeLimit: timeLimit !== undefined ? parseInt(String(timeLimit), 10) : undefined,
726726
preset,

lib/commands/powershell.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export async function startPowerShellSession(this: NovaWindowsDriver): Promise<v
8080
this.caps.app = this.caps.app.replaceAll(`%${envVar}%`, process.env[envVar.toUpperCase()] ?? '');
8181
}
8282

83-
if (this.caps.enableWebView) {
83+
if (this.caps.webviewEnabled) {
8484
this.webviewDevtoolsPort = this.caps.webviewDevtoolsPort
8585
? Number(this.caps.webviewDevtoolsPort)
8686
: await findFreePort(DEFAULT_WEBVIEW_DEVTOOLS_PORT_LOWER, DEFAULT_WEBVIEW_DEVTOOLS_PORT_UPPER);

0 commit comments

Comments
 (0)