From 625b0505b8db120df07adb8d8f2cbc829ff3ec37 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 19:39:45 +0800 Subject: [PATCH 1/7] fix Windows WebView2 smoke on Edge 150 Signed-off-by: xufeihong.xfh --- apps/desktop/wdio.installed.conf.mjs | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/apps/desktop/wdio.installed.conf.mjs b/apps/desktop/wdio.installed.conf.mjs index 613f3ae..fd46d12 100644 --- a/apps/desktop/wdio.installed.conf.mjs +++ b/apps/desktop/wdio.installed.conf.mjs @@ -8,7 +8,35 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(currentDir, '../..'); const artifactsDir = path.join(repoRoot, 'artifacts', 'desktop-smoke'); const driverPort = Number(process.env.TAURI_DRIVER_PORT ?? '4444'); +const webviewDataDir = path.join(artifactsDir, `webview2-${process.pid}-${Date.now()}`); let driverProcess; +let devToolsActivePortTimer; + +function mirrorNestedDevToolsActivePort() { + const nestedPath = path.join(webviewDataDir, 'EBWebView', 'DevToolsActivePort'); + const expectedPath = path.join(webviewDataDir, 'DevToolsActivePort'); + if (fs.existsSync(expectedPath) || !fs.existsSync(nestedPath)) { + return; + } + + try { + const contents = fs.readFileSync(nestedPath, 'utf8'); + if (contents.trim().split(/\r?\n/).length < 2) { + return; + } + + const temporaryPath = `${expectedPath}.${process.pid}.tmp`; + fs.writeFileSync(temporaryPath, contents); + fs.renameSync(temporaryPath, expectedPath); + clearInterval(devToolsActivePortTimer); + devToolsActivePortTimer = undefined; + console.log('Mirrored nested WebView2 DevToolsActivePort for EdgeDriver compatibility'); + } catch (error) { + if (error?.code !== 'ENOENT') { + console.warn(`Could not mirror WebView2 DevToolsActivePort: ${error}`); + } + } +} function waitForPort(port, timeoutMs) { const deadline = Date.now() + timeoutMs; @@ -44,6 +72,9 @@ export const config = { maxInstances: 1, 'tauri:options': { application: process.env.DESKTOP_APP_PATH, + webviewOptions: { + userDataFolder: webviewDataDir, + }, }, }, ], @@ -72,6 +103,13 @@ export const config = { process.env.NO_AT_BRIDGE ||= '1'; process.env.WEBKIT_DISABLE_DMABUF_RENDERER ||= '1'; + fs.mkdirSync(webviewDataDir, { recursive: true }); + // Edge/WebView2 150 may create this file under EBWebView while EdgeDriver + // still waits for it at the configured user-data root. See: + // https://github.com/MicrosoftEdge/EdgeWebDriver/issues/109 + devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 100); + devToolsActivePortTimer.unref(); + const logPath = path.join(artifactsDir, 'tauri-driver.log'); const logFd = fs.openSync(logPath, 'a'); driverProcess = spawn( @@ -93,6 +131,10 @@ export const config = { await waitForPort(driverPort, 30000); }, onComplete: () => { + if (devToolsActivePortTimer) { + clearInterval(devToolsActivePortTimer); + devToolsActivePortTimer = undefined; + } if (driverProcess && driverProcess.exitCode === null) { driverProcess.kill(); } From cbf034307d9c13c0e3acfa141fa980b17c95e315 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 19:59:35 +0800 Subject: [PATCH 2/7] use the Tauri WebView2 data directory Signed-off-by: xufeihong.xfh --- apps/desktop/wdio.installed.conf.mjs | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/desktop/wdio.installed.conf.mjs b/apps/desktop/wdio.installed.conf.mjs index fd46d12..4839bd4 100644 --- a/apps/desktop/wdio.installed.conf.mjs +++ b/apps/desktop/wdio.installed.conf.mjs @@ -8,7 +8,13 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(currentDir, '../..'); const artifactsDir = path.join(repoRoot, 'artifacts', 'desktop-smoke'); const driverPort = Number(process.env.TAURI_DRIVER_PORT ?? '4444'); -const webviewDataDir = path.join(artifactsDir, `webview2-${process.pid}-${Date.now()}`); +const tauriConfig = JSON.parse( + fs.readFileSync(path.join(currentDir, 'src-tauri', 'tauri.conf.json'), 'utf8'), +); +const webviewDataDir = + process.platform === 'win32' && process.env.LOCALAPPDATA + ? path.win32.join(process.env.LOCALAPPDATA, tauriConfig.identifier) + : path.join(artifactsDir, `webview2-${process.pid}-${Date.now()}`); let driverProcess; let devToolsActivePortTimer; @@ -21,7 +27,7 @@ function mirrorNestedDevToolsActivePort() { try { const contents = fs.readFileSync(nestedPath, 'utf8'); - if (contents.trim().split(/\r?\n/).length < 2) { + if (!/^\d+\r?\n/.test(contents)) { return; } @@ -72,9 +78,13 @@ export const config = { maxInstances: 1, 'tauri:options': { application: process.env.DESKTOP_APP_PATH, - webviewOptions: { - userDataFolder: webviewDataDir, - }, + ...(process.platform === 'win32' + ? { + webviewOptions: { + userDataFolder: webviewDataDir, + }, + } + : {}), }, }, ], @@ -103,12 +113,16 @@ export const config = { process.env.NO_AT_BRIDGE ||= '1'; process.env.WEBKIT_DISABLE_DMABUF_RENDERER ||= '1'; - fs.mkdirSync(webviewDataDir, { recursive: true }); - // Edge/WebView2 150 may create this file under EBWebView while EdgeDriver - // still waits for it at the configured user-data root. See: - // https://github.com/MicrosoftEdge/EdgeWebDriver/issues/109 - devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 100); - devToolsActivePortTimer.unref(); + if (process.platform === 'win32') { + fs.mkdirSync(webviewDataDir, { recursive: true }); + fs.rmSync(path.join(webviewDataDir, 'DevToolsActivePort'), { force: true }); + // Tauri 2 uses the app's LocalData directory for WebView2. Edge 150 may + // create this file under EBWebView while EdgeDriver still waits for it at + // that LocalData root. See: + // https://github.com/MicrosoftEdge/EdgeWebDriver/issues/109 + devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 100); + devToolsActivePortTimer.unref(); + } const logPath = path.join(artifactsDir, 'tauri-driver.log'); const logFd = fs.openSync(logPath, 'a'); From 4e086e336936522f461a58936063d9a0a5fe98ad Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 20:18:17 +0800 Subject: [PATCH 3/7] diagnose nested WebView2 active port paths Signed-off-by: xufeihong.xfh --- apps/desktop/wdio.installed.conf.mjs | 62 +++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/apps/desktop/wdio.installed.conf.mjs b/apps/desktop/wdio.installed.conf.mjs index 4839bd4..a772de4 100644 --- a/apps/desktop/wdio.installed.conf.mjs +++ b/apps/desktop/wdio.installed.conf.mjs @@ -15,27 +15,75 @@ const webviewDataDir = process.platform === 'win32' && process.env.LOCALAPPDATA ? path.win32.join(process.env.LOCALAPPDATA, tauriConfig.identifier) : path.join(artifactsDir, `webview2-${process.pid}-${Date.now()}`); +const webviewDiagnosticsPath = path.join(artifactsDir, 'webview2-diagnostics.txt'); let driverProcess; let devToolsActivePortTimer; +let devToolsActivePortCandidate; + +function recordWebViewDiagnostic(message) { + fs.appendFileSync(webviewDiagnosticsPath, `${new Date().toISOString()} ${message}\n`); +} + +function findNestedDevToolsActivePort(root, maxDepth = 6) { + const expectedPath = path.join(root, 'DevToolsActivePort'); + const pending = [{ directory: root, depth: 0 }]; + + while (pending.length > 0) { + const { directory, depth } = pending.pop(); + let entries; + try { + entries = fs.readdirSync(directory, { withFileTypes: true }); + } catch (error) { + if (!['EACCES', 'ENOENT'].includes(error?.code)) { + recordWebViewDiagnostic(`Could not inspect ${directory}: ${error}`); + } + continue; + } + + for (const entry of entries) { + const entryPath = path.join(directory, entry.name); + if (entry.isFile() && entry.name === 'DevToolsActivePort' && entryPath !== expectedPath) { + return entryPath; + } + if (entry.isDirectory() && depth < maxDepth) { + pending.push({ directory: entryPath, depth: depth + 1 }); + } + } + } + + return undefined; +} function mirrorNestedDevToolsActivePort() { - const nestedPath = path.join(webviewDataDir, 'EBWebView', 'DevToolsActivePort'); const expectedPath = path.join(webviewDataDir, 'DevToolsActivePort'); - if (fs.existsSync(expectedPath) || !fs.existsSync(nestedPath)) { + if (fs.existsSync(expectedPath)) { return; } try { - const contents = fs.readFileSync(nestedPath, 'utf8'); - if (!/^\d+\r?\n/.test(contents)) { + const nestedPath = findNestedDevToolsActivePort(webviewDataDir); + if (!nestedPath) { return; } + const stats = fs.statSync(nestedPath); + const candidate = `${nestedPath}:${stats.size}:${stats.mtimeMs}`; + if (candidate !== devToolsActivePortCandidate) { + devToolsActivePortCandidate = candidate; + recordWebViewDiagnostic( + `Observed ${nestedPath} (${stats.size} bytes); waiting for a stable write`, + ); + return; + } + + const contents = fs.readFileSync(nestedPath); + const temporaryPath = `${expectedPath}.${process.pid}.tmp`; fs.writeFileSync(temporaryPath, contents); fs.renameSync(temporaryPath, expectedPath); clearInterval(devToolsActivePortTimer); devToolsActivePortTimer = undefined; + recordWebViewDiagnostic(`Mirrored ${nestedPath} to ${expectedPath} (${contents.length} bytes)`); console.log('Mirrored nested WebView2 DevToolsActivePort for EdgeDriver compatibility'); } catch (error) { if (error?.code !== 'ENOENT') { @@ -116,11 +164,15 @@ export const config = { if (process.platform === 'win32') { fs.mkdirSync(webviewDataDir, { recursive: true }); fs.rmSync(path.join(webviewDataDir, 'DevToolsActivePort'), { force: true }); + fs.writeFileSync( + webviewDiagnosticsPath, + `${new Date().toISOString()} Watching ${webviewDataDir} (identifier=${tauriConfig.identifier})\n`, + ); // Tauri 2 uses the app's LocalData directory for WebView2. Edge 150 may // create this file under EBWebView while EdgeDriver still waits for it at // that LocalData root. See: // https://github.com/MicrosoftEdge/EdgeWebDriver/issues/109 - devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 100); + devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 250); devToolsActivePortTimer.unref(); } From 8f0744e0958f95c094086d6096c3cc2365f35d78 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 20:34:58 +0800 Subject: [PATCH 4/7] enable WebView2 remote debugging for smoke tests Signed-off-by: xufeihong.xfh --- apps/desktop/wdio.installed.conf.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/wdio.installed.conf.mjs b/apps/desktop/wdio.installed.conf.mjs index a772de4..249afe7 100644 --- a/apps/desktop/wdio.installed.conf.mjs +++ b/apps/desktop/wdio.installed.conf.mjs @@ -130,6 +130,7 @@ export const config = { ? { webviewOptions: { userDataFolder: webviewDataDir, + additionalBrowserArguments: ['remote-debugging-port=0'], }, } : {}), @@ -162,11 +163,14 @@ export const config = { process.env.WEBKIT_DISABLE_DMABUF_RENDERER ||= '1'; if (process.platform === 'win32') { + process.env.WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS ||= '--remote-debugging-port=0'; fs.mkdirSync(webviewDataDir, { recursive: true }); fs.rmSync(path.join(webviewDataDir, 'DevToolsActivePort'), { force: true }); fs.writeFileSync( webviewDiagnosticsPath, - `${new Date().toISOString()} Watching ${webviewDataDir} (identifier=${tauriConfig.identifier})\n`, + `${new Date().toISOString()} Watching ${webviewDataDir} ` + + `(identifier=${tauriConfig.identifier}, ` + + `browserArgs=${process.env.WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS})\n`, ); // Tauri 2 uses the app's LocalData directory for WebView2. Edge 150 may // create this file under EBWebView while EdgeDriver still waits for it at From 46666499d79ad2abc4469f7e016054cdf8bd2355 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 21:00:46 +0800 Subject: [PATCH 5/7] replace Windows WebDriver smoke with Playwright CDP Signed-off-by: xufeihong.xfh --- .github/workflows/release.yml | 12 +- apps/desktop/package.json | 1 + .../tests/installed-app.windows-smoke.mjs | 156 ++++++++++++++++++ apps/desktop/wdio.installed.conf.mjs | 112 ------------- scripts/desktop_api_smoke.py | 7 +- 5 files changed, 165 insertions(+), 123 deletions(-) create mode 100644 apps/desktop/tests/installed-app.windows-smoke.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0945ff7..caa07fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,17 +86,9 @@ jobs: xvfb - name: Install Tauri WebDriver - if: runner.os != 'macOS' + if: runner.os == 'Linux' run: cargo install tauri-driver --locked - - name: Install Microsoft Edge WebDriver - if: runner.os == 'Windows' - shell: pwsh - run: | - cargo install --git https://github.com/chippers/msedgedriver-tool - msedgedriver-tool - $PWD.Path | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Install backend (with packaging extras) run: | python -m pip install --upgrade pip @@ -163,7 +155,7 @@ jobs: DESKTOP_APP_PATH: ${{ steps.desktop-install.outputs.app_path }} ZVEC_PORT: "17862" ZVEC_STUDIO_DATA_DIR: ${{ github.workspace }}/artifacts/desktop-smoke/wdio-data - run: pnpm --filter desktop e2e:installed + run: pnpm --filter desktop e2e:installed:windows - name: Upload desktop smoke diagnostics if: failure() || always() diff --git a/apps/desktop/package.json b/apps/desktop/package.json index aeec315..8bac7fb 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -8,6 +8,7 @@ "tauri:dev": "tauri dev", "tauri:build": "tauri build && bash scripts/post-build-sign.sh", "e2e:installed": "wdio run wdio.installed.conf.mjs", + "e2e:installed:windows": "node tests/installed-app.windows-smoke.mjs", "lint": "echo '(desktop shell uses cargo for lint -- run `make desktop.check`)' && exit 0", "typecheck": "echo '(desktop shell typecheck happens via cargo check)' && exit 0", "test": "echo '(desktop tests run via cargo test -- see `make desktop.test`)' && exit 0", diff --git a/apps/desktop/tests/installed-app.windows-smoke.mjs b/apps/desktop/tests/installed-app.windows-smoke.mjs new file mode 100644 index 0000000..cec9e56 --- /dev/null +++ b/apps/desktop/tests/installed-app.windows-smoke.mjs @@ -0,0 +1,156 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import net from 'node:net'; +import { createRequire } from 'node:module'; +import os from 'node:os'; +import path from 'node:path'; +import { spawn, spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const currentDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(currentDir, '../../..'); +const frontendRequire = createRequire(path.join(repoRoot, 'apps', 'frontend', 'package.json')); +const { chromium, expect } = frontendRequire('@playwright/test'); +const artifactsDir = path.join(repoRoot, 'artifacts', 'desktop-smoke'); +const appPath = process.env.DESKTOP_APP_PATH; +const cdpPort = Number(process.env.WEBVIEW2_CDP_PORT ?? '9222'); +const cdpUrl = `http://127.0.0.1:${cdpPort}`; +let appProcess; +let browser; + +function waitForPort(port, timeoutMs) { + const deadline = Date.now() + timeoutMs; + return new Promise((resolve, reject) => { + function attempt() { + const socket = net.createConnection({ host: '127.0.0.1', port }); + socket.once('connect', () => { + socket.end(); + resolve(); + }); + socket.once('error', (error) => { + socket.destroy(); + if (Date.now() >= deadline) { + reject(error); + return; + } + setTimeout(attempt, 250); + }); + } + attempt(); + }); +} + +function stopApp() { + if (!appProcess || appProcess.exitCode !== null) { + return; + } + + if (process.platform === 'win32') { + spawnSync('taskkill', ['/PID', String(appProcess.pid), '/T', '/F'], { + stdio: 'ignore', + }); + } else { + appProcess.kill('SIGTERM'); + } +} + +async function waitForPage() { + const deadline = Date.now() + 60000; + while (Date.now() < deadline) { + const page = browser + .contexts() + .flatMap((context) => context.pages()) + .find((candidate) => candidate.url() !== 'about:blank'); + if (page) { + return page; + } + await new Promise((resolve) => setTimeout(resolve, 250)); + } + throw new Error('Timed out waiting for the Tauri WebView2 page'); +} + +async function tauriInvoke(page, command) { + return page.evaluate(async (cmd) => { + const invoke = window.__TAURI_INTERNALS__?.invoke ?? window.__TAURI__?.invoke; + if (!invoke) return null; + return invoke(cmd); + }, command); +} + +async function main() { + if (process.platform !== 'win32') { + throw new Error('The WebView2 CDP smoke test only supports Windows'); + } + if (!appPath || !fs.existsSync(appPath)) { + throw new Error(`DESKTOP_APP_PATH does not exist: ${appPath}`); + } + + fs.mkdirSync(artifactsDir, { recursive: true }); + const appLog = fs.openSync(path.join(artifactsDir, 'windows-app.log'), 'a'); + const env = { + ...process.env, + ZVEC_HOST: process.env.ZVEC_HOST ?? '127.0.0.1', + ZVEC_PORT: process.env.ZVEC_PORT ?? '17862', + ZVEC_READY_TIMEOUT_SECS: process.env.ZVEC_READY_TIMEOUT_SECS ?? '60', + ZVEC_STUDIO_DATA_DIR: + process.env.ZVEC_STUDIO_DATA_DIR ?? path.join(artifactsDir, 'windows-data'), + WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${cdpPort}`, + }; + + appProcess = spawn(appPath, [], { + env, + stdio: ['ignore', appLog, appLog], + }); + appProcess.once('exit', (code, signal) => { + if (code && code !== 0) { + console.error(`installed app exited with code ${code}`); + } + if (signal) { + console.error(`installed app exited via signal ${signal}`); + } + }); + + await waitForPort(cdpPort, 60000); + browser = await chromium.connectOverCDP(cdpUrl); + const page = await waitForPage(); + await expect(page.getByTestId('app-shell')).toBeVisible({ timeout: 60000 }); + + await expect + .poll(async () => (await tauriInvoke(page, 'sidecar_status'))?.running, { + timeout: 60000, + message: 'sidecar_status did not report a running sidecar', + }) + .toBe(true); + const status = await tauriInvoke(page, 'sidecar_status'); + assert.equal(status.running, true); + assert.match(status.base_url, /^http:\/\/127\.0\.0\.1:\d+$/); + + const suffix = Date.now().toString(36); + const collectionName = `uitest${suffix}`; + const collectionPath = path.join(os.tmpdir(), `zvec-${collectionName}`); + + await page.getByTestId('zv-welcome-create').click(); + await page.getByTestId('zv-create-name').fill(collectionName); + await page.getByTestId('zv-create-path').fill(collectionPath); + await page.getByTestId('zv-create-submit').click(); + + await expect(page.getByTestId('zv-detail-tab-overview')).toBeVisible({ + timeout: 60000, + }); + assert.equal(new URL(page.url()).pathname, `/collections/${collectionName}`); + + for (const tabName of ['overview', 'browse', 'query', 'write']) { + const tab = page.getByTestId(`zv-detail-tab-${tabName}`); + await tab.click(); + await expect(tab).toHaveClass(/zv-detail-tab--active/, { timeout: 30000 }); + } + + console.log('installed Windows desktop UI smoke: ok'); +} + +try { + await main(); +} finally { + await browser?.close().catch(() => {}); + stopApp(); +} diff --git a/apps/desktop/wdio.installed.conf.mjs b/apps/desktop/wdio.installed.conf.mjs index 249afe7..613f3ae 100644 --- a/apps/desktop/wdio.installed.conf.mjs +++ b/apps/desktop/wdio.installed.conf.mjs @@ -8,89 +8,7 @@ const currentDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(currentDir, '../..'); const artifactsDir = path.join(repoRoot, 'artifacts', 'desktop-smoke'); const driverPort = Number(process.env.TAURI_DRIVER_PORT ?? '4444'); -const tauriConfig = JSON.parse( - fs.readFileSync(path.join(currentDir, 'src-tauri', 'tauri.conf.json'), 'utf8'), -); -const webviewDataDir = - process.platform === 'win32' && process.env.LOCALAPPDATA - ? path.win32.join(process.env.LOCALAPPDATA, tauriConfig.identifier) - : path.join(artifactsDir, `webview2-${process.pid}-${Date.now()}`); -const webviewDiagnosticsPath = path.join(artifactsDir, 'webview2-diagnostics.txt'); let driverProcess; -let devToolsActivePortTimer; -let devToolsActivePortCandidate; - -function recordWebViewDiagnostic(message) { - fs.appendFileSync(webviewDiagnosticsPath, `${new Date().toISOString()} ${message}\n`); -} - -function findNestedDevToolsActivePort(root, maxDepth = 6) { - const expectedPath = path.join(root, 'DevToolsActivePort'); - const pending = [{ directory: root, depth: 0 }]; - - while (pending.length > 0) { - const { directory, depth } = pending.pop(); - let entries; - try { - entries = fs.readdirSync(directory, { withFileTypes: true }); - } catch (error) { - if (!['EACCES', 'ENOENT'].includes(error?.code)) { - recordWebViewDiagnostic(`Could not inspect ${directory}: ${error}`); - } - continue; - } - - for (const entry of entries) { - const entryPath = path.join(directory, entry.name); - if (entry.isFile() && entry.name === 'DevToolsActivePort' && entryPath !== expectedPath) { - return entryPath; - } - if (entry.isDirectory() && depth < maxDepth) { - pending.push({ directory: entryPath, depth: depth + 1 }); - } - } - } - - return undefined; -} - -function mirrorNestedDevToolsActivePort() { - const expectedPath = path.join(webviewDataDir, 'DevToolsActivePort'); - if (fs.existsSync(expectedPath)) { - return; - } - - try { - const nestedPath = findNestedDevToolsActivePort(webviewDataDir); - if (!nestedPath) { - return; - } - - const stats = fs.statSync(nestedPath); - const candidate = `${nestedPath}:${stats.size}:${stats.mtimeMs}`; - if (candidate !== devToolsActivePortCandidate) { - devToolsActivePortCandidate = candidate; - recordWebViewDiagnostic( - `Observed ${nestedPath} (${stats.size} bytes); waiting for a stable write`, - ); - return; - } - - const contents = fs.readFileSync(nestedPath); - - const temporaryPath = `${expectedPath}.${process.pid}.tmp`; - fs.writeFileSync(temporaryPath, contents); - fs.renameSync(temporaryPath, expectedPath); - clearInterval(devToolsActivePortTimer); - devToolsActivePortTimer = undefined; - recordWebViewDiagnostic(`Mirrored ${nestedPath} to ${expectedPath} (${contents.length} bytes)`); - console.log('Mirrored nested WebView2 DevToolsActivePort for EdgeDriver compatibility'); - } catch (error) { - if (error?.code !== 'ENOENT') { - console.warn(`Could not mirror WebView2 DevToolsActivePort: ${error}`); - } - } -} function waitForPort(port, timeoutMs) { const deadline = Date.now() + timeoutMs; @@ -126,14 +44,6 @@ export const config = { maxInstances: 1, 'tauri:options': { application: process.env.DESKTOP_APP_PATH, - ...(process.platform === 'win32' - ? { - webviewOptions: { - userDataFolder: webviewDataDir, - additionalBrowserArguments: ['remote-debugging-port=0'], - }, - } - : {}), }, }, ], @@ -162,24 +72,6 @@ export const config = { process.env.NO_AT_BRIDGE ||= '1'; process.env.WEBKIT_DISABLE_DMABUF_RENDERER ||= '1'; - if (process.platform === 'win32') { - process.env.WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS ||= '--remote-debugging-port=0'; - fs.mkdirSync(webviewDataDir, { recursive: true }); - fs.rmSync(path.join(webviewDataDir, 'DevToolsActivePort'), { force: true }); - fs.writeFileSync( - webviewDiagnosticsPath, - `${new Date().toISOString()} Watching ${webviewDataDir} ` + - `(identifier=${tauriConfig.identifier}, ` + - `browserArgs=${process.env.WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS})\n`, - ); - // Tauri 2 uses the app's LocalData directory for WebView2. Edge 150 may - // create this file under EBWebView while EdgeDriver still waits for it at - // that LocalData root. See: - // https://github.com/MicrosoftEdge/EdgeWebDriver/issues/109 - devToolsActivePortTimer = setInterval(mirrorNestedDevToolsActivePort, 250); - devToolsActivePortTimer.unref(); - } - const logPath = path.join(artifactsDir, 'tauri-driver.log'); const logFd = fs.openSync(logPath, 'a'); driverProcess = spawn( @@ -201,10 +93,6 @@ export const config = { await waitForPort(driverPort, 30000); }, onComplete: () => { - if (devToolsActivePortTimer) { - clearInterval(devToolsActivePortTimer); - devToolsActivePortTimer = undefined; - } if (driverProcess && driverProcess.exitCode === null) { driverProcess.kill(); } diff --git a/scripts/desktop_api_smoke.py b/scripts/desktop_api_smoke.py index 2a6ca43..0a10c2f 100644 --- a/scripts/desktop_api_smoke.py +++ b/scripts/desktop_api_smoke.py @@ -114,7 +114,12 @@ def _terminate(proc: subprocess.Popen[bytes]) -> None: if proc.poll() is not None: return if os.name == "nt": - proc.terminate() + subprocess.run( + ["taskkill", "/PID", str(proc.pid), "/T", "/F"], + check=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) else: proc.send_signal(signal.SIGTERM) try: From b7bf2a49d13d25baf1bd1f45197be84a86339836 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 21:17:24 +0800 Subject: [PATCH 6/7] enable WebView2 CDP through the registry Signed-off-by: xufeihong.xfh --- .../tests/installed-app.windows-smoke.mjs | 88 +++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/apps/desktop/tests/installed-app.windows-smoke.mjs b/apps/desktop/tests/installed-app.windows-smoke.mjs index cec9e56..9893227 100644 --- a/apps/desktop/tests/installed-app.windows-smoke.mjs +++ b/apps/desktop/tests/installed-app.windows-smoke.mjs @@ -1,6 +1,5 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import net from 'node:net'; import { createRequire } from 'node:module'; import os from 'node:os'; import path from 'node:path'; @@ -13,28 +12,80 @@ const frontendRequire = createRequire(path.join(repoRoot, 'apps', 'frontend', 'p const { chromium, expect } = frontendRequire('@playwright/test'); const artifactsDir = path.join(repoRoot, 'artifacts', 'desktop-smoke'); const appPath = process.env.DESKTOP_APP_PATH; -const cdpPort = Number(process.env.WEBVIEW2_CDP_PORT ?? '9222'); -const cdpUrl = `http://127.0.0.1:${cdpPort}`; +const tauriConfig = JSON.parse( + fs.readFileSync(path.join(repoRoot, 'apps', 'desktop', 'src-tauri', 'tauri.conf.json'), 'utf8'), +); +const registryKey = + 'HKCU\\Software\\Policies\\Microsoft\\Edge\\WebView2\\AdditionalBrowserArguments'; +const appExecutableName = appPath ? path.win32.basename(appPath) : undefined; +const webviewUserDataDir = process.env.LOCALAPPDATA + ? path.win32.join(process.env.LOCALAPPDATA, tauriConfig.identifier, 'EBWebView') + : undefined; +const devToolsActivePortPath = webviewUserDataDir + ? path.win32.join(webviewUserDataDir, 'DevToolsActivePort') + : undefined; let appProcess; let browser; +let registryValueCreated = false; -function waitForPort(port, timeoutMs) { +function enableRemoteDebugging() { + if (process.env.GITHUB_ACTIONS !== 'true') { + return; + } + + const result = spawnSync( + 'reg.exe', + [ + 'add', + registryKey, + '/v', + appExecutableName, + '/t', + 'REG_SZ', + '/d', + '--remote-debugging-port=0', + '/f', + ], + { encoding: 'utf8' }, + ); + if (result.status !== 0) { + throw new Error(`Could not enable WebView2 remote debugging: ${result.stderr}`); + } + registryValueCreated = true; +} + +function disableRemoteDebugging() { + if (!registryValueCreated) { + return; + } + spawnSync('reg.exe', ['delete', registryKey, '/v', appExecutableName, '/f'], { + stdio: 'ignore', + }); + registryValueCreated = false; +} + +function waitForCdpEndpoint(timeoutMs) { const deadline = Date.now() + timeoutMs; return new Promise((resolve, reject) => { function attempt() { - const socket = net.createConnection({ host: '127.0.0.1', port }); - socket.once('connect', () => { - socket.end(); - resolve(); - }); - socket.once('error', (error) => { - socket.destroy(); - if (Date.now() >= deadline) { + try { + const [portLine] = fs.readFileSync(devToolsActivePortPath, 'utf8').split(/\r?\n/); + const port = Number(portLine); + if (Number.isInteger(port) && port > 0) { + resolve(`http://127.0.0.1:${port}`); + return; + } + } catch (error) { + if (error?.code !== 'ENOENT') { reject(error); return; } - setTimeout(attempt, 250); - }); + } + if (Date.now() >= deadline) { + reject(new Error(`Timed out waiting for ${devToolsActivePortPath}`)); + return; + } + setTimeout(attempt, 250); } attempt(); }); @@ -84,8 +135,13 @@ async function main() { if (!appPath || !fs.existsSync(appPath)) { throw new Error(`DESKTOP_APP_PATH does not exist: ${appPath}`); } + if (!devToolsActivePortPath) { + throw new Error('LOCALAPPDATA is required for the Windows WebView2 smoke test'); + } fs.mkdirSync(artifactsDir, { recursive: true }); + fs.rmSync(devToolsActivePortPath, { force: true }); + enableRemoteDebugging(); const appLog = fs.openSync(path.join(artifactsDir, 'windows-app.log'), 'a'); const env = { ...process.env, @@ -94,7 +150,6 @@ async function main() { ZVEC_READY_TIMEOUT_SECS: process.env.ZVEC_READY_TIMEOUT_SECS ?? '60', ZVEC_STUDIO_DATA_DIR: process.env.ZVEC_STUDIO_DATA_DIR ?? path.join(artifactsDir, 'windows-data'), - WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${cdpPort}`, }; appProcess = spawn(appPath, [], { @@ -110,7 +165,7 @@ async function main() { } }); - await waitForPort(cdpPort, 60000); + const cdpUrl = await waitForCdpEndpoint(60000); browser = await chromium.connectOverCDP(cdpUrl); const page = await waitForPage(); await expect(page.getByTestId('app-shell')).toBeVisible({ timeout: 60000 }); @@ -153,4 +208,5 @@ try { } finally { await browser?.close().catch(() => {}); stopApp(); + disableRemoteDebugging(); } From 9f42732fd6d145bcb13baeb24af90abe4df838e7 Mon Sep 17 00:00:00 2001 From: "xufeihong.xfh" Date: Thu, 6 Aug 2026 21:59:27 +0800 Subject: [PATCH 7/7] honor WebView2 browser arguments in desktop shell Signed-off-by: xufeihong.xfh --- apps/desktop/src-tauri/src/lib.rs | 24 ++++++++++- .../tests/installed-app.windows-smoke.mjs | 43 +------------------ 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 0393c0f..2a1e435 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -90,6 +90,28 @@ pub fn run() { config: config.clone(), }; + let context = tauri::generate_context!(); + + #[cfg(target_os = "windows")] + let context = { + let mut context = context; + if let Ok(additional_args) = env::var("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS") { + let additional_args = additional_args.trim(); + if !additional_args.is_empty() { + for window in &mut context.config_mut().app.windows { + window.additional_browser_args = + Some(match window.additional_browser_args.take() { + Some(configured_args) => format!("{configured_args} {additional_args}"), + None => format!( + "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection {additional_args}" + ), + }); + } + } + } + context + }; + tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .manage(app_state) @@ -97,7 +119,7 @@ pub fn run() { commands::sidecar_status, commands::sidecar_url, ]) - .build(tauri::generate_context!()) + .build(context) .expect("failed to build the Tauri application") .run(|app_handle, event| { let should_shutdown = matches!( diff --git a/apps/desktop/tests/installed-app.windows-smoke.mjs b/apps/desktop/tests/installed-app.windows-smoke.mjs index 9893227..ba5b8ed 100644 --- a/apps/desktop/tests/installed-app.windows-smoke.mjs +++ b/apps/desktop/tests/installed-app.windows-smoke.mjs @@ -15,9 +15,6 @@ const appPath = process.env.DESKTOP_APP_PATH; const tauriConfig = JSON.parse( fs.readFileSync(path.join(repoRoot, 'apps', 'desktop', 'src-tauri', 'tauri.conf.json'), 'utf8'), ); -const registryKey = - 'HKCU\\Software\\Policies\\Microsoft\\Edge\\WebView2\\AdditionalBrowserArguments'; -const appExecutableName = appPath ? path.win32.basename(appPath) : undefined; const webviewUserDataDir = process.env.LOCALAPPDATA ? path.win32.join(process.env.LOCALAPPDATA, tauriConfig.identifier, 'EBWebView') : undefined; @@ -26,43 +23,6 @@ const devToolsActivePortPath = webviewUserDataDir : undefined; let appProcess; let browser; -let registryValueCreated = false; - -function enableRemoteDebugging() { - if (process.env.GITHUB_ACTIONS !== 'true') { - return; - } - - const result = spawnSync( - 'reg.exe', - [ - 'add', - registryKey, - '/v', - appExecutableName, - '/t', - 'REG_SZ', - '/d', - '--remote-debugging-port=0', - '/f', - ], - { encoding: 'utf8' }, - ); - if (result.status !== 0) { - throw new Error(`Could not enable WebView2 remote debugging: ${result.stderr}`); - } - registryValueCreated = true; -} - -function disableRemoteDebugging() { - if (!registryValueCreated) { - return; - } - spawnSync('reg.exe', ['delete', registryKey, '/v', appExecutableName, '/f'], { - stdio: 'ignore', - }); - registryValueCreated = false; -} function waitForCdpEndpoint(timeoutMs) { const deadline = Date.now() + timeoutMs; @@ -141,7 +101,6 @@ async function main() { fs.mkdirSync(artifactsDir, { recursive: true }); fs.rmSync(devToolsActivePortPath, { force: true }); - enableRemoteDebugging(); const appLog = fs.openSync(path.join(artifactsDir, 'windows-app.log'), 'a'); const env = { ...process.env, @@ -150,6 +109,7 @@ async function main() { ZVEC_READY_TIMEOUT_SECS: process.env.ZVEC_READY_TIMEOUT_SECS ?? '60', ZVEC_STUDIO_DATA_DIR: process.env.ZVEC_STUDIO_DATA_DIR ?? path.join(artifactsDir, 'windows-data'), + WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: '--remote-debugging-port=0', }; appProcess = spawn(appPath, [], { @@ -208,5 +168,4 @@ try { } finally { await browser?.close().catch(() => {}); stopApp(); - disableRemoteDebugging(); }