diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2024-05-22 12:29:13 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2024-05-28 15:41:20 +0200 |
| commit | 58556c0da8a4f3abc441d18c9dd0c026b1986dfb (patch) | |
| tree | 0d53951c8d6939526d41173269cba9d57e58b611 /gui | |
| parent | 973a31567940c383e2234c50182a5c95907a22ec (diff) | |
| download | mullvadvpn-58556c0da8a4f3abc441d18c9dd0c026b1986dfb.tar.xz mullvadvpn-58556c0da8a4f3abc441d18c9dd0c026b1986dfb.zip | |
Add macos split tunneling test
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/src/renderer/components/SplitTunnelingSettings.tsx | 6 | ||||
| -rw-r--r-- | gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts | 162 |
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)); +} |
