summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHank <hank@mullvad.net>2022-09-08 12:10:12 +0200
committerHank <hank@mullvad.net>2022-09-20 11:32:56 +0200
commit5e353ee5c25666ea0041ed1610007233c73c4860 (patch)
tree2037f6eae48294b5b32d369124e13420229f4dee
parent830510699ffd4680d61154554ed691b4ef61ea55 (diff)
downloadmullvadvpn-5e353ee5c25666ea0041ed1610007233c73c4860.tar.xz
mullvadvpn-5e353ee5c25666ea0041ed1610007233c73c4860.zip
Refactor and implement tunnel-state tests
-rw-r--r--gui/e2e/main.spec.ts74
-rw-r--r--gui/e2e/tunnel-state.spec.ts176
-rw-r--r--gui/e2e/utils.ts59
3 files changed, 232 insertions, 77 deletions
diff --git a/gui/e2e/main.spec.ts b/gui/e2e/main.spec.ts
index 92e40d1af3..85f69f9cf6 100644
--- a/gui/e2e/main.spec.ts
+++ b/gui/e2e/main.spec.ts
@@ -1,17 +1,13 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { ElectronApplication } from 'playwright-core';
-import { ILocation, ITunnelEndpoint, TunnelState } from '../src/shared/daemon-rpc-types';
import { startApp } from './utils';
let appWindow: Page;
-let electronApp: ElectronApplication;
test.beforeAll(async () => {
const startAppResponse = await startApp();
appWindow = startAppResponse.appWindow;
- electronApp = startAppResponse.electronApp;
});
test.afterAll(async () => {
@@ -23,73 +19,3 @@ test('Validate title', async () => {
expect(title).toBe('Mullvad VPN');
await expect(appWindow.locator('header')).toBeVisible();
});
-
-test('Validate status and header', async () => {
- await electronApp.evaluate(({ ipcMain, webContents }) => {
- const mockLocationResponse = {
- city: 'Uddevalla',
- country: 'Norway',
- latitude: 62,
- longitude: 17,
- mullvadExitIp: false,
- };
- ipcMain.removeHandler('location-get');
- ipcMain.handle('location-get', () => {
- return Promise.resolve({
- type: 'success',
- value: mockLocationResponse,
- });
- });
- webContents.getAllWebContents()[0].send('tunnel-', { state: 'disconnected' } as TunnelState);
- });
-
- await appWindow.screenshot({ path: 'e2e/screenshots/unsecured.png' });
- const statusSpan = appWindow.locator('span[role="status"]');
- const header = appWindow.locator('header');
- await expect(statusSpan).toContainText('UNSECURED CONNECTION');
- let headerColor = await header.evaluate((el) => {
- return window.getComputedStyle(el).getPropertyValue('background-color');
- });
- expect(headerColor).toBe('rgb(227, 64, 57)');
-
- await appWindow.locator('text=Secure my connection').click();
-
- await electronApp.evaluate(({ ipcMain, webContents }) => {
- const mockLocation: ILocation = {
- city: 'Uddevalla',
- country: 'Norway',
- latitude: 62,
- longitude: 17,
- mullvadExitIp: true,
- };
- const mockEndpoint: ITunnelEndpoint = {
- address: 'wg10:80',
- protocol: 'tcp',
- quantumResistant: false,
- tunnelType: 'wireguard',
- };
-
- ipcMain.removeHandler('location-get');
- ipcMain.handle('location-get', () => {
- return Promise.resolve({
- type: 'success',
- value: mockLocation,
- });
- });
-
- webContents.getAllWebContents()[0].send('tunnel-', {
- state: 'connected',
- details: {
- endpoint: mockEndpoint,
- location: mockLocation,
- },
- } as TunnelState);
- });
-
- await expect(statusSpan).toContainText('SECURE CONNECTION');
- headerColor = await header.evaluate((el) => {
- return window.getComputedStyle(el).getPropertyValue('background-color');
- });
- expect(headerColor).toBe('rgb(68, 173, 77)');
- await appWindow.screenshot({ path: 'e2e/screenshots/secure.png' });
-});
diff --git a/gui/e2e/tunnel-state.spec.ts b/gui/e2e/tunnel-state.spec.ts
new file mode 100644
index 0000000000..fa3a425903
--- /dev/null
+++ b/gui/e2e/tunnel-state.spec.ts
@@ -0,0 +1,176 @@
+import { expect, test } from '@playwright/test';
+import { Page } from 'playwright';
+
+import { colors } from '../src/config.json';
+import { ILocation, ITunnelEndpoint, TunnelState } from '../src/shared/daemon-rpc-types';
+import {
+ getBackgroundColor,
+ getColor,
+ mockIpcHandle,
+ sendMockIpcResponse,
+ startApp,
+} from './utils';
+
+const UNSECURED_COLOR = colors.red;
+const SECURE_COLOR = colors.green;
+const WHITE_COLOR = colors.white;
+
+const mockLocation: ILocation = {
+ country: 'Sweden',
+ city: 'Gothenburg',
+ latitude: 58,
+ longitude: 12,
+ mullvadExitIp: false,
+};
+
+const getLabel = () => appWindow.locator('span[role="status"]');
+const getHeader = () => appWindow.locator('header');
+const getScreenShotPath = (test: string) => `e2e/screenshots/tunnel-state/${test}.png`;
+
+let appWindow: Page;
+
+test.beforeAll(async () => {
+ const startAppResponse = await startApp();
+ appWindow = startAppResponse.appWindow;
+});
+
+test.afterAll(async () => {
+ await appWindow.close();
+});
+
+/**
+ * Disconnected state
+ */
+test('App should show disconnected tunnel state', async () => {
+ await mockIpcHandle<ILocation>({
+ channel: 'location-get',
+ response: mockLocation,
+ });
+
+ await sendMockIpcResponse<TunnelState>({
+ channel: 'tunnel-',
+ response: { state: 'disconnected' },
+ });
+
+ const statusLabel = getLabel();
+ await expect(statusLabel).toContainText(/unsecured connection/i);
+ const labelColor = await getColor(statusLabel);
+ expect(labelColor).toBe(UNSECURED_COLOR);
+
+ const header = getHeader();
+ const headerColor = await getBackgroundColor(header);
+ expect(headerColor).toBe(UNSECURED_COLOR);
+
+ await appWindow.screenshot({ path: getScreenShotPath('unsecured') });
+});
+
+/**
+ * Connecting state
+ */
+test('App should show connecting tunnel state', async () => {
+ await mockIpcHandle<ILocation>({
+ channel: 'location-get',
+ response: mockLocation,
+ });
+
+ await sendMockIpcResponse<TunnelState>({
+ channel: 'tunnel-',
+ response: { state: 'connecting' },
+ });
+
+ const statusLabel = getLabel();
+ await expect(statusLabel).toContainText(/creating secure connection/i);
+ const labelColor = await getColor(statusLabel);
+ expect(labelColor).toBe(WHITE_COLOR);
+
+ const header = getHeader();
+ const headerColor = await getBackgroundColor(header);
+ expect(headerColor).toBe(SECURE_COLOR);
+
+ await appWindow.screenshot({ path: getScreenShotPath('connecting') });
+});
+
+/**
+ * Connected state
+ */
+test('App should show connected tunnel state', async () => {
+ const location: ILocation = { ...mockLocation, mullvadExitIp: true };
+ await mockIpcHandle<ILocation>({
+ channel: 'location-get',
+ response: location,
+ });
+
+ const endpoint: ITunnelEndpoint = {
+ address: 'wg10:80',
+ protocol: 'tcp',
+ quantumResistant: false,
+ tunnelType: 'wireguard',
+ };
+ await sendMockIpcResponse<TunnelState>({
+ channel: 'tunnel-',
+ response: { state: 'connected', details: { endpoint, location } },
+ });
+
+ const statusLabel = getLabel();
+ await expect(statusLabel).toContainText(/secure connection/i);
+ const labelColor = await getColor(statusLabel);
+ expect(labelColor).toBe(SECURE_COLOR);
+
+ const header = getHeader();
+ const headerColor = await getBackgroundColor(header);
+ expect(headerColor).toBe(SECURE_COLOR);
+
+ await appWindow.screenshot({ path: getScreenShotPath('secure') });
+});
+
+/**
+ * Disconnecting state
+ */
+test('App should show disconnecting tunnel state', async () => {
+ await mockIpcHandle<ILocation>({
+ channel: 'location-get',
+ response: mockLocation,
+ });
+
+ await sendMockIpcResponse<TunnelState>({
+ channel: 'tunnel-',
+ response: { state: 'disconnecting', details: 'nothing' },
+ });
+
+ const statusLabel = getLabel();
+ await expect(statusLabel).toBeEmpty();
+ const labelColor = await getColor(statusLabel);
+ expect(labelColor).toBe(WHITE_COLOR);
+
+ const header = getHeader();
+ const headerColor = await getBackgroundColor(header);
+ expect(headerColor).toBe(UNSECURED_COLOR);
+
+ await appWindow.screenshot({ path: getScreenShotPath('disconnecting') });
+});
+
+/**
+ * Error state
+ */
+test('App should show error tunnel state', async () => {
+ await mockIpcHandle<ILocation>({
+ channel: 'location-get',
+ response: mockLocation,
+ });
+
+ await sendMockIpcResponse<TunnelState>({
+ channel: 'tunnel-',
+ response: { state: 'error', details: { cause: { reason: 'is_offline' } } },
+ });
+
+ const statusLabel = getLabel();
+ await expect(statusLabel).toContainText(/blocked connection/i);
+ const labelColor = await getColor(statusLabel);
+ expect(labelColor).toBe(WHITE_COLOR);
+
+ const header = getHeader();
+ const headerColor = await getBackgroundColor(header);
+ expect(headerColor).toBe(SECURE_COLOR);
+
+ await appWindow.screenshot({ path: getScreenShotPath('error') });
+});
diff --git a/gui/e2e/utils.ts b/gui/e2e/utils.ts
index 2fb14dc027..1d9cf957b6 100644
--- a/gui/e2e/utils.ts
+++ b/gui/e2e/utils.ts
@@ -1,4 +1,4 @@
-import { Page } from 'playwright';
+import { Locator, Page } from 'playwright';
import { _electron as electron, ElectronApplication } from 'playwright-core';
interface StartAppResponse {
@@ -6,10 +6,12 @@ interface StartAppResponse {
appWindow: Page;
}
+let electronApp: ElectronApplication;
+
const startApp = async (): Promise<StartAppResponse> => {
process.env.CI = 'e2e';
- const electronApp = await electron.launch({
+ electronApp = await electron.launch({
args: ['build/e2e/setup/main.js'],
});
@@ -26,4 +28,55 @@ const startApp = async (): Promise<StartAppResponse> => {
return { electronApp, appWindow };
};
-export { startApp };
+type MockIpcHandleProps<T> = {
+ channel: string;
+ response: T;
+};
+
+const mockIpcHandle = async <T>({ channel, response }: MockIpcHandleProps<T>) => {
+ await electronApp.evaluate(
+ ({ ipcMain }, { channel, response }) => {
+ ipcMain.removeHandler(channel);
+ ipcMain.handle(channel, () => {
+ return Promise.resolve({
+ type: 'success',
+ value: response,
+ });
+ });
+ },
+ { channel, response },
+ );
+};
+
+type SendMockIpcResponseProps<T> = {
+ channel: string;
+ response: T;
+};
+
+const sendMockIpcResponse = async <T>({ channel, response }: SendMockIpcResponseProps<T>) => {
+ await electronApp.evaluate(
+ ({ webContents }, { channel, response }) => {
+ webContents.getAllWebContents()[0].send(channel, response);
+ },
+ { channel, response },
+ );
+};
+
+const _getStyleProperty = async (locator: Locator, property: string) => {
+ return locator.evaluate(
+ (el, { property }) => {
+ return window.getComputedStyle(el).getPropertyValue(property);
+ },
+ { property },
+ );
+};
+
+const getColor = async (locator: Locator) => {
+ return _getStyleProperty(locator, 'color');
+};
+
+const getBackgroundColor = async (locator: Locator) => {
+ return _getStyleProperty(locator, 'background-color');
+};
+
+export { startApp, mockIpcHandle, sendMockIpcResponse, getColor, getBackgroundColor };