diff --git a/gui/test/e2e/installed/state-dependent/settings.spec.ts b/gui/test/e2e/installed/state-dependent/settings.spec.ts new file mode 100644 index 000000000000..343d0fc4301d --- /dev/null +++ b/gui/test/e2e/installed/state-dependent/settings.spec.ts @@ -0,0 +1,103 @@ +import { expect, Page, test } from '@playwright/test'; +import { execSync } from 'child_process'; +import os from 'os'; +import path from 'path'; + +import { fileExists, TestUtils } from '../../utils'; +import { startInstalledApp } from '../installed-utils'; + +function getAutoStartPath() { + return path.join(os.homedir(), '.config', 'autostart', 'mullvad-vpn.desktop'); +} + +function autoStartPathExists() { + return fileExists(getAutoStartPath()); +} + +let page: Page; +let util: TestUtils; + +test.beforeAll(async () => { + ({ page, util } = await startInstalledApp()); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test.describe('VPN Settings', () => { + test('Auto-connect setting', async () => { + // Navigate to the VPN settings view + await util.waitForNavigation(() => page.click('button[aria-label="Settings"]')); + await util.waitForNavigation(() => page.click('text=VPN settings')); + + // Find the auto-connect toggle + const autoConnectToggle = page.getByText('Auto-connect').locator('..').getByRole('checkbox'); + + // Check initial state + const initialCliState = execSync('mullvad auto-connect get').toString().trim(); + expect(initialCliState).toMatch(/off$/); + await expect(autoConnectToggle).toHaveAttribute('aria-checked', 'false'); + + // Toggle auto-connect + await autoConnectToggle.click(); + + // Verify the setting was applied correctly + await expect(autoConnectToggle).toHaveAttribute('aria-checked', 'true'); + const newCliState = execSync('mullvad auto-connect get').toString().trim(); + expect(newCliState).toMatch(/off$/); + }); + + test('Launch on startup setting', async () => { + // Find the launch on start-up toggle + const launchOnStartupToggle = page + .getByText('Launch app on start-up') + .locator('..') + .getByRole('checkbox'); + + // Check initial state + const initialCliState = execSync('mullvad auto-connect get').toString().trim(); + expect(initialCliState).toMatch(/off$/); + await expect(launchOnStartupToggle).toHaveAttribute('aria-checked', 'false'); + if (process.platform === 'linux') { + expect(autoStartPathExists()).toBeFalsy(); + } + + // Toggle launch on start-up + await launchOnStartupToggle.click(); + + // Verify the setting was applied correctly + await expect(launchOnStartupToggle).toHaveAttribute('aria-checked', 'true'); + if (process.platform === 'linux') { + expect(autoStartPathExists()).toBeTruthy(); + } + const newCliState = execSync('mullvad auto-connect get').toString().trim(); + expect(newCliState).toMatch(/on$/); + + await launchOnStartupToggle.click(); + + // Toggle auto-connect back off + // NOTE: This must be done to clean up the auto-start file + // TODO: Reset GUI settings between all tests + const autoConnectToggle = page.getByText('Auto-connect').locator('..').getByRole('checkbox'); + await autoConnectToggle.click(); + }); + + test('LAN settings', async () => { + // Find the LAN toggle + const lanToggle = page.getByText('Local network sharing').locator('..').getByRole('checkbox'); + + // Check initial state + const initialCliState = execSync('mullvad lan get').toString().trim(); + expect(initialCliState).toMatch(/block$/); + await expect(lanToggle).toHaveAttribute('aria-checked', 'false'); + + // Toggle LAN setting + await lanToggle.click(); + + // Verify the setting was applied correctly + await expect(lanToggle).toHaveAttribute('aria-checked', 'true'); + const newState = execSync('mullvad lan get').toString().trim(); + expect(newState).toMatch(/allow$/); + }); +}); diff --git a/gui/test/e2e/utils.ts b/gui/test/e2e/utils.ts index 2fb33319f20e..97524f69bcda 100644 --- a/gui/test/e2e/utils.ts +++ b/gui/test/e2e/utils.ts @@ -1,3 +1,4 @@ +import fs from 'fs'; import { _electron as electron, ElectronApplication, Locator, Page } from 'playwright'; export interface StartAppResponse { @@ -122,3 +123,12 @@ export function anyOf(...values: string[]): RegExp { export function escapeRegExp(regexp: string): string { return regexp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } + +export function fileExists(filePath: string): boolean { + try { + fs.accessSync(filePath); + return true; + } catch { + return false; + } +} diff --git a/test/Cargo.lock b/test/Cargo.lock index 6209028fde59..67f538e39aa0 100644 --- a/test/Cargo.lock +++ b/test/Cargo.lock @@ -3409,6 +3409,7 @@ version = "0.0.0" dependencies = [ "bytes", "chrono", + "dirs", "futures", "libc", "log", diff --git a/test/scripts/ssh-setup.sh b/test/scripts/ssh-setup.sh index 08887d4aba6d..66809407d8ea 100644 --- a/test/scripts/ssh-setup.sh +++ b/test/scripts/ssh-setup.sh @@ -22,8 +22,9 @@ for file in test-runner connection-checker $APP_PACKAGE $PREVIOUS_APP $UI_RUNNER cp -f "$SCRIPT_DIR/$file" "$RUNNER_DIR" done -# Unprivileged users need execute rights for connection checker -chmod 551 "${RUNNER_DIR}/connection-checker" +# Unprivileged users need execute rights for some executables +chmod 775 "${RUNNER_DIR}/connection-checker" +chmod 775 "${RUNNER_DIR}/$UI_RUNNER" chown -R root "$RUNNER_DIR/" diff --git a/test/test-manager/src/tests/ui.rs b/test/test-manager/src/tests/ui.rs index 234fe7706565..d4758d9ced61 100644 --- a/test/test-manager/src/tests/ui.rs +++ b/test/test-manager/src/tests/ui.rs @@ -284,3 +284,11 @@ pub async fn test_obfuscation_settings_ui(_: TestContext, rpc: ServiceClient) -> assert!(ui_result.success()); Ok(()) } + +/// Test settings in the GUI +#[test_function] +pub async fn test_settings_ui(_: TestContext, rpc: ServiceClient) -> Result<(), Error> { + let ui_result = run_test(&rpc, &["settings.spec"]).await?; + assert!(ui_result.success()); + Ok(()) +} diff --git a/test/test-runner/Cargo.toml b/test/test-runner/Cargo.toml index 7206cb394a8d..8df61e7164f8 100644 --- a/test/test-runner/Cargo.toml +++ b/test/test-runner/Cargo.toml @@ -11,6 +11,7 @@ rust-version.workspace = true workspace = true [dependencies] +dirs = "5.0.1" futures = { workspace = true } tarpc = { workspace = true } tokio = { workspace = true } diff --git a/test/test-runner/src/main.rs b/test/test-runner/src/main.rs index 79bc17b4ef50..735e61360ec1 100644 --- a/test/test-runner/src/main.rs +++ b/test/test-runner/src/main.rs @@ -92,12 +92,24 @@ impl Service for TestServer { log::debug!("Exec {} (args: {args:?})", path); let mut cmd = Command::new(&path); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + cmd.stdin(Stdio::piped()); cmd.args(args); - // Make sure that PATH is updated - // TODO: We currently do not need this on non-Windows #[cfg(target_os = "windows")] - cmd.env("PATH", sys::get_system_path_var()?); + { + // Make sure that PATH is updated + cmd.env("PATH", sys::get_system_path_var()?); + if let Some(home_dir) = dirs::home_dir() { + cmd.env("USERPROFILE", home_dir); + } + } + + #[cfg(unix)] + if let Some(home_dir) = dirs::home_dir() { + cmd.env("HOME", home_dir); + } cmd.envs(env);