mirror of
https://github.com/aleksey-hoffman/sigma-file-manager.git
synced 2026-10-19 01:31:05 -05:00
Dev: auto-match Edge WebDriver to WebView2 and add navigator folder navigation test
This commit is contained in:
1 parent
97b5f41b27
commit
fa654d3cf5
8 files changed
+265
-62
No files matched your search
@@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
.drivers/
|
||||
.drivers-*/
|
||||
@@ -1,14 +1,9 @@
|
||||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { download } from 'edgedriver';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
import { resolveEdgeDriverPath } from './resolve-edge-driver.mjs';
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const driversDirectory = path.join(__dirname, '..', '.drivers');
|
||||
const driverPath = await download(undefined, driversDirectory);
|
||||
const driverPath = await resolveEdgeDriverPath({ forceRefresh: true });
|
||||
process.stdout.write(`${driverPath}\n`);
|
||||
@@ -0,0 +1,134 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { download as downloadEdgeDriver } from 'edgedriver';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const driversDirectory = path.join(__dirname, '..', '.drivers');
|
||||
const edgeDriverPath = path.join(driversDirectory, 'msedgedriver.exe');
|
||||
const versionMarkerPath = path.join(driversDirectory, 'webview-version.txt');
|
||||
|
||||
function compareVersionStrings(left, right) {
|
||||
const leftParts = left.split('.').map(Number);
|
||||
const rightParts = right.split('.').map(Number);
|
||||
|
||||
for (let index = 0; index < 4; index += 1) {
|
||||
const leftValue = leftParts[index] ?? 0;
|
||||
const rightValue = rightParts[index] ?? 0;
|
||||
if (leftValue !== rightValue) {
|
||||
return leftValue - rightValue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function readVersionMarker() {
|
||||
if (!fs.existsSync(versionMarkerPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fs.readFileSync(versionMarkerPath, 'utf8').trim() || null;
|
||||
}
|
||||
|
||||
function writeVersionMarker(webViewVersion) {
|
||||
fs.mkdirSync(driversDirectory, { recursive: true });
|
||||
fs.writeFileSync(versionMarkerPath, `${webViewVersion}\n`);
|
||||
}
|
||||
|
||||
function getInstalledDriverVersion(driverPath) {
|
||||
if (!fs.existsSync(driverPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = spawnSync(driverPath, ['--version'], { encoding: 'utf8' });
|
||||
const output = `${result.stdout}\n${result.stderr}`;
|
||||
const match = output.match(/(\d+\.\d+\.\d+\.\d+)/);
|
||||
return match?.[1] ?? null;
|
||||
}
|
||||
|
||||
function getWebView2VersionOnWindows() {
|
||||
const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
||||
const webViewRoot = path.join(programFilesX86, 'Microsoft', 'EdgeWebView', 'Application');
|
||||
|
||||
if (!fs.existsSync(webViewRoot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const versionDirectories = fs.readdirSync(webViewRoot, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => entry.name)
|
||||
.filter(name => /^\d+\.\d+\.\d+\.\d+$/.test(name))
|
||||
.sort(compareVersionStrings);
|
||||
|
||||
return versionDirectories.at(-1) ?? null;
|
||||
}
|
||||
|
||||
function removeInstalledDriver() {
|
||||
if (fs.existsSync(edgeDriverPath)) {
|
||||
fs.unlinkSync(edgeDriverPath);
|
||||
}
|
||||
|
||||
const driverNotesDirectory = path.join(driversDirectory, 'Driver_Notes');
|
||||
if (fs.existsSync(driverNotesDirectory)) {
|
||||
fs.rmSync(driverNotesDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (fs.existsSync(versionMarkerPath)) {
|
||||
fs.unlinkSync(versionMarkerPath);
|
||||
}
|
||||
}
|
||||
|
||||
function driverMatchesWebViewVersion(driverVersion, webViewVersion) {
|
||||
if (!driverVersion || !webViewVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return compareVersionStrings(driverVersion, webViewVersion) === 0;
|
||||
}
|
||||
|
||||
export async function resolveEdgeDriverPath(options = {}) {
|
||||
const { forceRefresh = false } = options;
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const explicitDriverPath = process.env.SFM_MS_EDGE_DRIVER_PATH;
|
||||
if (explicitDriverPath) {
|
||||
return explicitDriverPath;
|
||||
}
|
||||
|
||||
const webViewVersion = getWebView2VersionOnWindows();
|
||||
if (!webViewVersion) {
|
||||
throw new Error(
|
||||
'Could not detect Microsoft Edge WebView2 runtime version. Install WebView2 or set SFM_MS_EDGE_DRIVER_PATH.',
|
||||
);
|
||||
}
|
||||
|
||||
const markerVersion = readVersionMarker();
|
||||
const installedDriverVersion = getInstalledDriverVersion(edgeDriverPath);
|
||||
const hasMatchingDriver = fs.existsSync(edgeDriverPath)
|
||||
&& markerVersion === webViewVersion
|
||||
&& driverMatchesWebViewVersion(installedDriverVersion, webViewVersion);
|
||||
|
||||
if (!forceRefresh && hasMatchingDriver) {
|
||||
return edgeDriverPath;
|
||||
}
|
||||
|
||||
removeInstalledDriver();
|
||||
fs.mkdirSync(driversDirectory, { recursive: true });
|
||||
|
||||
const downloadedDriverPath = await downloadEdgeDriver(webViewVersion, driversDirectory);
|
||||
const downloadedDriverVersion = getInstalledDriverVersion(downloadedDriverPath);
|
||||
|
||||
if (!driverMatchesWebViewVersion(downloadedDriverVersion, webViewVersion)) {
|
||||
throw new Error(
|
||||
`Downloaded msedgedriver ${downloadedDriverVersion ?? 'unknown'} does not match WebView2 ${webViewVersion}.`,
|
||||
);
|
||||
}
|
||||
|
||||
writeVersionMarker(webViewVersion);
|
||||
return downloadedDriverPath;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ export const ADDRESS_BAR_BREADCRUMBS_SELECTOR = '[data-e2e-root="address-bar-bre
|
||||
export const FILE_BROWSER_LOADING_SELECTOR = '[data-e2e-root="file-browser-loading"]';
|
||||
export const FILE_BROWSER_ENTRIES_SELECTOR = '[data-e2e-root="file-browser-entries"]';
|
||||
export const FILE_BROWSER_EMPTY_DIRECTORY_SELECTOR = '[data-e2e-root="file-browser-empty-directory"]';
|
||||
export const FILE_BROWSER_DIRECTORY_ENTRY_SELECTOR = '.file-browser-list-view__entry--dir[data-entry-path]';
|
||||
export const ADDRESS_BAR_PARENT_BREADCRUMB_SELECTOR = '.address-bar__part:not(.address-bar__part--last)';
|
||||
|
||||
export async function focusWindowThatContainsMainSidebar() {
|
||||
await browser.waitUntil(async () => {
|
||||
@@ -35,3 +37,55 @@ export async function waitForPathname(expectedPathname) {
|
||||
timeoutMsg: `Route did not become ${expectedPathname}`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function openNavigatorFromSidebar() {
|
||||
const navigatorButton = $('button.nav-sidebar-item[value="navigator"]');
|
||||
await navigatorButton.waitForClickable({ timeout: 30000 });
|
||||
await navigatorButton.click();
|
||||
await waitForPathname('/navigator');
|
||||
}
|
||||
|
||||
export async function waitForNavigatorDirectoryListing() {
|
||||
const breadcrumbs = $(ADDRESS_BAR_BREADCRUMBS_SELECTOR);
|
||||
await breadcrumbs.waitForDisplayed({ timeout: 30000 });
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
return !(await $(FILE_BROWSER_LOADING_SELECTOR).isExisting());
|
||||
}, {
|
||||
timeout: 120000,
|
||||
timeoutMsg: 'Timed out waiting for navigator directory content to finish loading',
|
||||
});
|
||||
|
||||
const hasEntryList = await $(FILE_BROWSER_ENTRIES_SELECTOR).isExisting();
|
||||
const hasEmptyDirectoryState = await $(FILE_BROWSER_EMPTY_DIRECTORY_SELECTOR).isExisting();
|
||||
|
||||
if (!hasEntryList && !hasEmptyDirectoryState) {
|
||||
throw new Error('Expected file list or empty directory state after navigator load');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNavigatorCurrentPath() {
|
||||
return $(ADDRESS_BAR_BREADCRUMBS_SELECTOR).getAttribute('data-e2e-current-path');
|
||||
}
|
||||
|
||||
export async function waitForNavigatorCurrentPath(expectedPath) {
|
||||
await browser.waitUntil(async () => {
|
||||
const currentPath = await getNavigatorCurrentPath();
|
||||
return currentPath === expectedPath;
|
||||
}, {
|
||||
timeout: 30000,
|
||||
interval: 100,
|
||||
timeoutMsg: `Timed out waiting for navigator path to become ${expectedPath}`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function waitForNavigatorCurrentPathChange(previousPath) {
|
||||
await browser.waitUntil(async () => {
|
||||
const currentPath = await getNavigatorCurrentPath();
|
||||
return Boolean(currentPath) && currentPath !== previousPath;
|
||||
}, {
|
||||
timeout: 30000,
|
||||
interval: 100,
|
||||
timeoutMsg: `Timed out waiting for navigator path to change from ${previousPath}`,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { $, browser } from '@wdio/globals';
|
||||
import {
|
||||
ADDRESS_BAR_PARENT_BREADCRUMB_SELECTOR,
|
||||
FILE_BROWSER_DIRECTORY_ENTRY_SELECTOR,
|
||||
focusWindowThatContainsMainSidebar,
|
||||
getNavigatorCurrentPath,
|
||||
openNavigatorFromSidebar,
|
||||
waitForNavigatorCurrentPath,
|
||||
waitForNavigatorCurrentPathChange,
|
||||
waitForNavigatorDirectoryListing,
|
||||
} from '../helpers/main-window.js';
|
||||
|
||||
describe('Navigator folder navigation', () => {
|
||||
it('opens a subfolder on double-click and returns via the parent breadcrumb', async function () {
|
||||
await focusWindowThatContainsMainSidebar();
|
||||
await openNavigatorFromSidebar();
|
||||
await waitForNavigatorDirectoryListing();
|
||||
|
||||
const initialPath = await getNavigatorCurrentPath();
|
||||
assert.ok(initialPath, 'Expected navigator to resolve a current directory path');
|
||||
|
||||
const directoryEntries = await $$(FILE_BROWSER_DIRECTORY_ENTRY_SELECTOR);
|
||||
if (directoryEntries.length === 0) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
const firstDirectoryEntry = directoryEntries[0];
|
||||
const folderPath = await firstDirectoryEntry.getAttribute('data-entry-path');
|
||||
assert.ok(folderPath, 'Expected directory entry to expose data-entry-path');
|
||||
|
||||
await firstDirectoryEntry.doubleClick();
|
||||
await waitForNavigatorCurrentPathChange(initialPath);
|
||||
await waitForNavigatorDirectoryListing();
|
||||
|
||||
const openedPath = await getNavigatorCurrentPath();
|
||||
assert.equal(openedPath, folderPath);
|
||||
|
||||
const parentBreadcrumbs = await $$(ADDRESS_BAR_PARENT_BREADCRUMB_SELECTOR);
|
||||
assert.ok(parentBreadcrumbs.length > 0, 'Expected a parent breadcrumb for back navigation');
|
||||
|
||||
const immediateParentBreadcrumb = parentBreadcrumbs[parentBreadcrumbs.length - 1];
|
||||
await immediateParentBreadcrumb.waitForClickable({ timeout: 30000 });
|
||||
await immediateParentBreadcrumb.click();
|
||||
|
||||
await waitForNavigatorCurrentPath(initialPath);
|
||||
await waitForNavigatorDirectoryListing();
|
||||
|
||||
const returnedPath = await getNavigatorCurrentPath();
|
||||
assert.equal(returnedPath, initialPath);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,9 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { $, browser } from '@wdio/globals';
|
||||
import { browser } from '@wdio/globals';
|
||||
import {
|
||||
ADDRESS_BAR_BREADCRUMBS_SELECTOR,
|
||||
FILE_BROWSER_EMPTY_DIRECTORY_SELECTOR,
|
||||
FILE_BROWSER_ENTRIES_SELECTOR,
|
||||
FILE_BROWSER_LOADING_SELECTOR,
|
||||
focusWindowThatContainsMainSidebar,
|
||||
waitForPathname,
|
||||
getNavigatorCurrentPath,
|
||||
openNavigatorFromSidebar,
|
||||
waitForNavigatorDirectoryListing,
|
||||
} from '../helpers/main-window.js';
|
||||
|
||||
function looksLikeFilesystemPath(path) {
|
||||
@@ -32,37 +29,16 @@ function looksLikeFilesystemPath(path) {
|
||||
describe('Navigator page', () => {
|
||||
it('opens on a real folder and shows a directory listing or empty state', async () => {
|
||||
await focusWindowThatContainsMainSidebar();
|
||||
|
||||
const navigatorButton = $('button.nav-sidebar-item[value="navigator"]');
|
||||
await navigatorButton.waitForClickable({ timeout: 30000 });
|
||||
await navigatorButton.click();
|
||||
await waitForPathname('/navigator');
|
||||
|
||||
const breadcrumbs = $(ADDRESS_BAR_BREADCRUMBS_SELECTOR);
|
||||
await breadcrumbs.waitForDisplayed({ timeout: 30000 });
|
||||
await openNavigatorFromSidebar();
|
||||
await waitForNavigatorDirectoryListing();
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
const pathValue = await breadcrumbs.getAttribute('data-e2e-current-path');
|
||||
const pathValue = await getNavigatorCurrentPath();
|
||||
return looksLikeFilesystemPath(pathValue);
|
||||
}, {
|
||||
timeout: 30000,
|
||||
interval: 100,
|
||||
timeoutMsg: 'Timed out waiting for navigator address bar to show a resolved filesystem path',
|
||||
});
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
return !(await $(FILE_BROWSER_LOADING_SELECTOR).isExisting());
|
||||
}, {
|
||||
timeout: 120000,
|
||||
timeoutMsg: 'Timed out waiting for navigator directory content to finish loading',
|
||||
});
|
||||
|
||||
const hasEntryList = await $(FILE_BROWSER_ENTRIES_SELECTOR).isExisting();
|
||||
const hasEmptyDirectoryState = await $(FILE_BROWSER_EMPTY_DIRECTORY_SELECTOR).isExisting();
|
||||
|
||||
assert.ok(
|
||||
hasEntryList || hasEmptyDirectoryState,
|
||||
'Expected file list or empty directory state after load',
|
||||
);
|
||||
});
|
||||
});
|
||||
+14
-23
@@ -3,10 +3,9 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { download as downloadEdgeDriver } from 'edgedriver';
|
||||
import { resolveEdgeDriverPath } from './scripts/resolve-edge-driver.mjs';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const vendoredEdgeDriverPath = path.join(__dirname, '.drivers', 'msedgedriver.exe');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const tauriBinaryName = process.platform === 'win32' ? 'sigma-file-manager.exe' : 'sigma-file-manager';
|
||||
@@ -39,6 +38,17 @@ export const config = {
|
||||
|
||||
onPrepare: () => {
|
||||
if (process.env.SFM_SKIP_TAURI_BUILD_FOR_WEBDRIVER === '1') {
|
||||
const distIndexPath = path.join(repoRoot, 'dist', 'index.html');
|
||||
if (!fs.existsSync(applicationPath)) {
|
||||
throw new Error(
|
||||
'Missing debug SFM binary for WebDriver e2e. Run: npm run tauri -- build --debug --no-bundle',
|
||||
);
|
||||
}
|
||||
if (!fs.existsSync(distIndexPath)) {
|
||||
throw new Error(
|
||||
'Missing frontend dist for WebDriver e2e. Run: npm run tauri -- build --debug --no-bundle',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const buildResult = spawnSync(
|
||||
@@ -61,27 +71,8 @@ export const config = {
|
||||
beforeSession: async () => {
|
||||
const tauriDriverArguments = [];
|
||||
if (process.platform === 'win32') {
|
||||
const explicitEdgeDriverPath = process.env.SFM_MS_EDGE_DRIVER_PATH;
|
||||
if (explicitEdgeDriverPath) {
|
||||
tauriDriverArguments.push('--native-driver', explicitEdgeDriverPath);
|
||||
}
|
||||
else if (fs.existsSync(vendoredEdgeDriverPath)) {
|
||||
tauriDriverArguments.push('--native-driver', vendoredEdgeDriverPath);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const driversDirectory = path.join(__dirname, '.drivers');
|
||||
const downloadedEdgeDriverPath = await downloadEdgeDriver(undefined, driversDirectory);
|
||||
tauriDriverArguments.push('--native-driver', downloadedEdgeDriverPath);
|
||||
}
|
||||
catch (downloadError) {
|
||||
throw new Error(
|
||||
'Microsoft Edge WebDriver (msedgedriver.exe) is required. Add it to PATH, set SFM_MS_EDGE_DRIVER_PATH, '
|
||||
+ 'or run: npm run install-edge-driver --prefix e2e-webdriver',
|
||||
{ cause: downloadError },
|
||||
);
|
||||
}
|
||||
}
|
||||
const edgeDriverPath = await resolveEdgeDriverPath();
|
||||
tauriDriverArguments.push('--native-driver', edgeDriverPath);
|
||||
}
|
||||
tauriDriverProcess = spawn(
|
||||
tauriDriverPath,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"test": "npm run test:unit:run",
|
||||
"test:e2e": "npm run test:e2e:webdriver:skip-build",
|
||||
"test:e2e": "npm run test:e2e:webdriver",
|
||||
"test:e2e:webdriver": "npm run test --prefix e2e-webdriver",
|
||||
"test:e2e:webdriver:skip-build": "cross-env SFM_SKIP_TAURI_BUILD_FOR_WEBDRIVER=1 npm run test --prefix e2e-webdriver",
|
||||
"test:e2e:webdriver:install-edge-driver": "npm run install-edge-driver --prefix e2e-webdriver",
|
||||
|
||||
Reference in new issue
Block a user