diff options
| author | Hank <hank@mullvad.net> | 2022-09-08 12:10:12 +0200 |
|---|---|---|
| committer | Hank <hank@mullvad.net> | 2022-09-20 11:32:56 +0200 |
| commit | 5e353ee5c25666ea0041ed1610007233c73c4860 (patch) | |
| tree | 2037f6eae48294b5b32d369124e13420229f4dee | |
| parent | 830510699ffd4680d61154554ed691b4ef61ea55 (diff) | |
| download | mullvadvpn-5e353ee5c25666ea0041ed1610007233c73c4860.tar.xz mullvadvpn-5e353ee5c25666ea0041ed1610007233c73c4860.zip | |
Refactor and implement tunnel-state tests
| -rw-r--r-- | gui/e2e/main.spec.ts | 74 | ||||
| -rw-r--r-- | gui/e2e/tunnel-state.spec.ts | 176 | ||||
| -rw-r--r-- | gui/e2e/utils.ts | 59 |
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 }; |
