summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2024-05-22 12:29:13 +0200
committerOskar Nyberg <oskar@mullvad.net>2024-05-28 15:41:20 +0200
commit58556c0da8a4f3abc441d18c9dd0c026b1986dfb (patch)
tree0d53951c8d6939526d41173269cba9d57e58b611 /gui
parent973a31567940c383e2234c50182a5c95907a22ec (diff)
downloadmullvadvpn-58556c0da8a4f3abc441d18c9dd0c026b1986dfb.tar.xz
mullvadvpn-58556c0da8a4f3abc441d18c9dd0c026b1986dfb.zip
Add macos split tunneling test
Diffstat (limited to 'gui')
-rw-r--r--gui/src/renderer/components/SplitTunnelingSettings.tsx6
-rw-r--r--gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts162
2 files changed, 167 insertions, 1 deletions
diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx
index 8f57b00a87..2cee823b3e 100644
--- a/gui/src/renderer/components/SplitTunnelingSettings.tsx
+++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx
@@ -446,6 +446,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
<Accordion expanded={showSplitSection}>
<Cell.Section sectionTitle={excludedTitle}>
<ApplicationList
+ data-testid="split-applications"
applications={filteredSplitApplications}
rowRenderer={excludedRowRenderer}
/>
@@ -455,6 +456,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
<Accordion expanded={showNonSplitSection}>
<Cell.Section sectionTitle={allTitle}>
<ApplicationList
+ data-testid="non-split-applications"
applications={filteredNonSplitApplications}
rowRenderer={includedRowRenderer}
/>
@@ -484,6 +486,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
interface IApplicationListProps<T extends IApplication> {
applications: T[] | undefined;
rowRenderer: (application: T) => React.ReactElement;
+ 'data-testid'?: string;
}
function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>) {
@@ -495,8 +498,9 @@ function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>
);
} else {
return (
- <StyledListContainer>
+ <StyledListContainer data-testid={props['data-testid']}>
<List
+ data-testid={props['data-testid']}
items={props.applications.sort((a, b) => a.name.localeCompare(b.name))}
getKey={applicationGetKey}>
{props.rowRenderer}
diff --git a/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts b/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts
new file mode 100644
index 0000000000..0f1680bf44
--- /dev/null
+++ b/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts
@@ -0,0 +1,162 @@
+import { Locator, expect, test } from '@playwright/test';
+import { Page } from 'playwright';
+import { execSync } from 'child_process';
+
+import { startInstalledApp } from '../installed-utils';
+import { TestUtils } from '../../utils';
+import { RoutePath } from '../../../../src/renderer/lib/routes';
+
+// macOS only. This test expects the daemon to be logged in and for split tunneling to be off and
+// have no split applications.
+
+let page: Page;
+let util: TestUtils;
+
+test.beforeAll(async () => {
+ ({ page, util } = await startInstalledApp());
+});
+
+test.afterAll(async () => {
+ await page.close();
+});
+
+async function navigateToSplitTunneling() {
+ await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
+
+ expect(
+ await util.waitForNavigation(async () => await page.getByText('Split tunneling').click())
+ ).toEqual(RoutePath.splitTunneling);
+
+ const title = page.locator('h1')
+ await expect(title).toHaveText('Split tunneling');
+}
+
+test('App should enable split tunneling', async () => {
+ await navigateToSplitTunneling();
+
+ const toggle = page.getByRole('checkbox');
+ await expect(toggle).not.toBeChecked();
+
+ const splitList = page.getByTestId('split-applications');
+ const nonSplitList = page.getByTestId('non-split-applications');
+
+ await expect(splitList).not.toBeVisible();
+ await expect(nonSplitList).not.toBeVisible();
+
+ const launchPadApp = page.getByText('launchpad');
+ await expect(launchPadApp).not.toBeVisible();
+
+ toggle.click();
+ await expect(toggle).toBeChecked();
+ await expect(splitList).not.toBeVisible();
+ await expect(nonSplitList).toBeVisible();
+ await expect(launchPadApp).toBeVisible();
+ expect(await numberOfApplicationsInList('split-applications')).toBe(0);
+ expect(getDaemonSplitTunnelingApplications()).toHaveLength(0);
+});
+
+test('App should split launchpad', async () => {
+ const splitList = page.getByTestId('split-applications');
+ const nonSplitList = page.getByTestId('non-split-applications');
+
+ const splitLaunchPadApp = splitList.getByText('launchpad');
+ const nonSplitLaunchPadApp = nonSplitList.getByText('launchpad');
+
+ await expect(splitLaunchPadApp).not.toBeVisible();
+ await expect(nonSplitLaunchPadApp).toBeVisible();
+
+ await toggleApplication(nonSplitLaunchPadApp);
+
+ await expect(splitLaunchPadApp).toBeVisible();
+ await expect(nonSplitLaunchPadApp).not.toBeVisible();
+ expect(await numberOfApplicationsInList('split-applications')).toBe(1);
+
+ const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
+ expect(daemonSplitTunnelingApplications).toHaveLength(1);
+ expect(isSplitInDaemon('launchpad')).toBeTruthy();
+});
+
+test('App should split clock', async () => {
+ const splitList = page.getByTestId('split-applications');
+ const nonSplitList = page.getByTestId('non-split-applications');
+
+ const splitClockApp = splitList.getByText('clock');
+ const nonSplitClockApp = nonSplitList.getByText('clock');
+
+ await expect(splitClockApp).not.toBeVisible();
+ await expect(nonSplitClockApp).toBeVisible();
+
+ await toggleApplication(nonSplitClockApp);
+
+ await expect(splitClockApp).toBeVisible();
+ await expect(nonSplitClockApp).not.toBeVisible();
+ expect(await numberOfApplicationsInList('split-applications')).toBe(2);
+
+ const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
+ expect(daemonSplitTunnelingApplications).toHaveLength(2);
+ expect(isSplitInDaemon('launchpad')).toBeTruthy();
+ expect(isSplitInDaemon('clock')).toBeTruthy();
+});
+
+test('App should unsplit launchpad', async () => {
+ const splitList = page.getByTestId('split-applications');
+ const nonSplitList = page.getByTestId('non-split-applications');
+
+ const splitLaunchPadApp = splitList.getByText('launchpad');
+ const nonSplitLaunchPadApp = nonSplitList.getByText('launchpad');
+
+ await expect(splitLaunchPadApp).toBeVisible();
+ await expect(nonSplitLaunchPadApp).not.toBeVisible();
+
+ await toggleApplication(splitLaunchPadApp);
+
+ await expect(splitLaunchPadApp).not.toBeVisible();
+ await expect(nonSplitLaunchPadApp).toBeVisible();
+ expect(await numberOfApplicationsInList('split-applications')).toBe(1);
+
+ const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
+ expect(daemonSplitTunnelingApplications).toHaveLength(1);
+ expect(isSplitInDaemon('launchpad')).toBeFalsy();
+ expect(isSplitInDaemon('clock')).toBeTruthy();
+});
+
+test('App should disable split tunneling', async () => {
+ const toggle = page.getByRole('checkbox');
+ await expect(toggle).toBeChecked();
+
+ const splitList = page.getByTestId('split-applications');
+ const nonSplitList = page.getByTestId('non-split-applications');
+
+ await expect(splitList).toBeVisible();
+ await expect(nonSplitList).toBeVisible();
+
+ const launchPadApp = page.getByText('launchpad');
+ await expect(launchPadApp).toBeVisible();
+
+ toggle.click();
+ await expect(toggle).not.toBeChecked();
+});
+
+async function toggleApplication(applicationLocator: Locator) {
+ await applicationLocator.locator('~ div').click();
+}
+
+async function numberOfApplicationsInList(listTestid: string) {
+ const list = page.getByTestId(listTestid);
+ const listHidden = await list.isHidden();
+ if (listHidden) {
+ return 0;
+ }
+
+ return await list.locator('button').count();
+}
+
+function getDaemonSplitTunnelingApplications() {
+ const output = execSync('mullvad split-tunnel get').toString().trim().split('\n');
+ return output.slice(output.indexOf('Excluded applications:') + 1);
+}
+
+function isSplitInDaemon(app: string): boolean {
+ return !!getDaemonSplitTunnelingApplications()
+ .find((splitApp) => splitApp.toLowerCase().includes(app));
+}