Skip to content

Commit 62c0398

Browse files
committed
Add macos split tunneling test
1 parent 66c6d78 commit 62c0398

File tree

2 files changed

+167
-1
lines changed

2 files changed

+167
-1
lines changed

gui/src/renderer/components/SplitTunnelingSettings.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
446446
<Accordion expanded={showSplitSection}>
447447
<Cell.Section sectionTitle={excludedTitle}>
448448
<ApplicationList
449+
data-testid="split-applications"
449450
applications={filteredSplitApplications}
450451
rowRenderer={excludedRowRenderer}
451452
/>
@@ -455,6 +456,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
455456
<Accordion expanded={showNonSplitSection}>
456457
<Cell.Section sectionTitle={allTitle}>
457458
<ApplicationList
459+
data-testid="non-split-applications"
458460
applications={filteredNonSplitApplications}
459461
rowRenderer={includedRowRenderer}
460462
/>
@@ -484,6 +486,7 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
484486
interface IApplicationListProps<T extends IApplication> {
485487
applications: T[] | undefined;
486488
rowRenderer: (application: T) => React.ReactElement;
489+
'data-testid'?: string;
487490
}
488491

489492
function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>) {
@@ -495,8 +498,9 @@ function ApplicationList<T extends IApplication>(props: IApplicationListProps<T>
495498
);
496499
} else {
497500
return (
498-
<StyledListContainer>
501+
<StyledListContainer data-testid={props['data-testid']}>
499502
<List
503+
data-testid={props['data-testid']}
500504
items={props.applications.sort((a, b) => a.name.localeCompare(b.name))}
501505
getKey={applicationGetKey}>
502506
{props.rowRenderer}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { Locator, expect, test } from '@playwright/test';
2+
import { Page } from 'playwright';
3+
import { execSync } from 'child_process';
4+
5+
import { startInstalledApp } from '../installed-utils';
6+
import { TestUtils } from '../../utils';
7+
import { RoutePath } from '../../../../src/renderer/lib/routes';
8+
9+
// macOS only. This test expects the daemon to be logged in and for split tunneling to be off and
10+
// have no split applications.
11+
12+
let page: Page;
13+
let util: TestUtils;
14+
15+
test.beforeAll(async () => {
16+
({ page, util } = await startInstalledApp());
17+
});
18+
19+
test.afterAll(async () => {
20+
await page.close();
21+
});
22+
23+
async function navigateToSplitTunneling() {
24+
await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
25+
26+
expect(
27+
await util.waitForNavigation(async () => await page.getByText('Split tunneling').click())
28+
).toEqual(RoutePath.splitTunneling);
29+
30+
const title = page.locator('h1')
31+
await expect(title).toHaveText('Split tunneling');
32+
}
33+
34+
test('App should enable split tunneling', async () => {
35+
await navigateToSplitTunneling();
36+
37+
const toggle = page.getByRole('checkbox');
38+
await expect(toggle).not.toBeChecked();
39+
40+
const splitList = page.getByTestId('split-applications');
41+
const nonSplitList = page.getByTestId('non-split-applications');
42+
43+
await expect(splitList).not.toBeVisible();
44+
await expect(nonSplitList).not.toBeVisible();
45+
46+
const launchPadApp = page.getByText('launchpad');
47+
await expect(launchPadApp).not.toBeVisible();
48+
49+
toggle.click();
50+
await expect(toggle).toBeChecked();
51+
await expect(splitList).not.toBeVisible();
52+
await expect(nonSplitList).toBeVisible();
53+
await expect(launchPadApp).toBeVisible();
54+
expect(await numberOfApplicationsInList('split-applications')).toBe(0);
55+
expect(getDaemonSplitTunnelingApplications()).toHaveLength(0);
56+
});
57+
58+
test('App should split launchpad', async () => {
59+
const splitList = page.getByTestId('split-applications');
60+
const nonSplitList = page.getByTestId('non-split-applications');
61+
62+
const splitLaunchPadApp = splitList.getByText('launchpad');
63+
const nonSplitLaunchPadApp = nonSplitList.getByText('launchpad');
64+
65+
await expect(splitLaunchPadApp).not.toBeVisible();
66+
await expect(nonSplitLaunchPadApp).toBeVisible();
67+
68+
await toggleApplication(nonSplitLaunchPadApp);
69+
70+
await expect(splitLaunchPadApp).toBeVisible();
71+
await expect(nonSplitLaunchPadApp).not.toBeVisible();
72+
expect(await numberOfApplicationsInList('split-applications')).toBe(1);
73+
74+
const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
75+
expect(daemonSplitTunnelingApplications).toHaveLength(1);
76+
expect(isSplitInDaemon('launchpad')).toBeTruthy();
77+
});
78+
79+
test('App should split clock', async () => {
80+
const splitList = page.getByTestId('split-applications');
81+
const nonSplitList = page.getByTestId('non-split-applications');
82+
83+
const splitClockApp = splitList.getByText('clock');
84+
const nonSplitClockApp = nonSplitList.getByText('clock');
85+
86+
await expect(splitClockApp).not.toBeVisible();
87+
await expect(nonSplitClockApp).toBeVisible();
88+
89+
await toggleApplication(nonSplitClockApp);
90+
91+
await expect(splitClockApp).toBeVisible();
92+
await expect(nonSplitClockApp).not.toBeVisible();
93+
expect(await numberOfApplicationsInList('split-applications')).toBe(2);
94+
95+
const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
96+
expect(daemonSplitTunnelingApplications).toHaveLength(2);
97+
expect(isSplitInDaemon('launchpad')).toBeTruthy();
98+
expect(isSplitInDaemon('clock')).toBeTruthy();
99+
});
100+
101+
test('App should unsplit launchpad', async () => {
102+
const splitList = page.getByTestId('split-applications');
103+
const nonSplitList = page.getByTestId('non-split-applications');
104+
105+
const splitLaunchPadApp = splitList.getByText('launchpad');
106+
const nonSplitLaunchPadApp = nonSplitList.getByText('launchpad');
107+
108+
await expect(splitLaunchPadApp).toBeVisible();
109+
await expect(nonSplitLaunchPadApp).not.toBeVisible();
110+
111+
await toggleApplication(splitLaunchPadApp);
112+
113+
await expect(splitLaunchPadApp).not.toBeVisible();
114+
await expect(nonSplitLaunchPadApp).toBeVisible();
115+
expect(await numberOfApplicationsInList('split-applications')).toBe(1);
116+
117+
const daemonSplitTunnelingApplications = getDaemonSplitTunnelingApplications();
118+
expect(daemonSplitTunnelingApplications).toHaveLength(1);
119+
expect(isSplitInDaemon('launchpad')).toBeFalsy();
120+
expect(isSplitInDaemon('clock')).toBeTruthy();
121+
});
122+
123+
test('App should disable split tunneling', async () => {
124+
const toggle = page.getByRole('checkbox');
125+
await expect(toggle).toBeChecked();
126+
127+
const splitList = page.getByTestId('split-applications');
128+
const nonSplitList = page.getByTestId('non-split-applications');
129+
130+
await expect(splitList).toBeVisible();
131+
await expect(nonSplitList).toBeVisible();
132+
133+
const launchPadApp = page.getByText('launchpad');
134+
await expect(launchPadApp).toBeVisible();
135+
136+
toggle.click();
137+
await expect(toggle).not.toBeChecked();
138+
});
139+
140+
async function toggleApplication(applicationLocator: Locator) {
141+
await applicationLocator.locator('~ div').click();
142+
}
143+
144+
async function numberOfApplicationsInList(listTestid: string) {
145+
const list = page.getByTestId(listTestid);
146+
const listHidden = await list.isHidden();
147+
if (listHidden) {
148+
return 0;
149+
}
150+
151+
return await list.locator('button').count();
152+
}
153+
154+
function getDaemonSplitTunnelingApplications() {
155+
const output = execSync('mullvad split-tunnel get').toString().trim().split('\n');
156+
return output.slice(output.indexOf('Excluded applications:') + 1);
157+
}
158+
159+
function isSplitInDaemon(app: string): boolean {
160+
return !!getDaemonSplitTunnelingApplications()
161+
.find((splitApp) => splitApp.toLowerCase().includes(app));
162+
}

0 commit comments

Comments
 (0)