diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-10-03 17:19:50 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-10-03 17:19:50 +0200 |
| commit | 65ec42f0fa3979c13a91cf8ce0db89bfd55b4bb6 (patch) | |
| tree | fcc404cb27d25560c3ac32a7362ca09413eec162 /gui | |
| parent | 2b5c9c0ed5f05f463b36c39593a1ff7a2481fc8e (diff) | |
| parent | 672bda049a754cdc165b620d24c55b1de5224e39 (diff) | |
| download | mullvadvpn-65ec42f0fa3979c13a91cf8ce0db89bfd55b4bb6.tar.xz mullvadvpn-65ec42f0fa3979c13a91cf8ce0db89bfd55b4bb6.zip | |
Merge branch 'add-daemon-support-to-tests'
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/package.json | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/Marquee.tsx | 10 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/preload.ts | 4 | ||||
| -rw-r--r-- | gui/test/e2e/daemon/daemon-utils.ts | 5 | ||||
| -rw-r--r-- | gui/test/e2e/daemon/location.spec.ts | 26 | ||||
| -rw-r--r-- | gui/test/e2e/main.spec.ts | 21 | ||||
| -rw-r--r-- | gui/test/e2e/mocked/main.spec.ts | 20 | ||||
| -rw-r--r-- | gui/test/e2e/mocked/mocked-utils.ts | 62 | ||||
| -rw-r--r-- | gui/test/e2e/mocked/settings.spec.ts (renamed from gui/test/e2e/settings.spec.ts) | 20 | ||||
| -rw-r--r-- | gui/test/e2e/mocked/tunnel-state.spec.ts (renamed from gui/test/e2e/tunnel-state.spec.ts) | 34 | ||||
| -rw-r--r-- | gui/test/e2e/utils.ts | 62 |
12 files changed, 168 insertions, 104 deletions
diff --git a/gui/package.json b/gui/package.json index fcf86629f8..5d06305e5d 100644 --- a/gui/package.json +++ b/gui/package.json @@ -95,7 +95,9 @@ "format": "prettier \"**/*.{js,css,ts,tsx}\" --write", "tsc": "tsc -p . --noEmit", "e2e": "npm run build && npm run e2e:no-build", - "e2e:no-build": "xvfb-maybe -- playwright test", + "e2e:no-build": "xvfb-maybe -- playwright test mocked", + "e2e:with-daemon": "npm run build && npm run e2e:with-daemon:no-build", + "e2e:with-daemon:no-build": "xvfb-maybe -- playwright test daemon", "e2e:update-snapshots": "npm run e2e:no-build -- --update-snapshots", "develop": "gulp develop", "test": "cross-env NODE_ENV=test electron-mocha --renderer --reporter spec --require ts-node/register --require \"test/unit/setup/renderer.ts\" \"test/unit/**/*.{ts,tsx}\"", diff --git a/gui/src/renderer/components/Marquee.tsx b/gui/src/renderer/components/Marquee.tsx index ed5999c279..5175fdc4a0 100644 --- a/gui/src/renderer/components/Marquee.tsx +++ b/gui/src/renderer/components/Marquee.tsx @@ -22,7 +22,7 @@ interface IMarqueeProps { children?: React.ReactNode; } -interface IMarqueeState { +interface IMarqueeState extends React.HTMLAttributes<HTMLSpanElement> { alignRight: boolean; // uniqueKey is used to force the Text component to remount to achieve the initial position of the // text without using a transition. @@ -60,16 +60,18 @@ export default class Marquee extends React.Component<IMarqueeProps, IMarqueeStat } public render() { + const { children, ...otherProps } = this.props; + return ( <Container> <Text key={this.state.uniqueKey} ref={this.textRef} - className={this.props.className} overflow={this.calculateOverflow()} alignRight={this.state.alignRight} - onTransitionEnd={this.scheduleToggleAlignRight}> - {this.props.children} + onTransitionEnd={this.scheduleToggleAlignRight} + {...otherProps}> + {children} </Text> </Container> ); diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx index 4d210ed2c2..0e282d88aa 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -221,7 +221,7 @@ export default class TunnelControl extends React.Component<ITunnelControlProps> const city = this.props.city === undefined ? '' : relayLocations.gettext(this.props.city); return ( <LocationRow> - <StyledMarquee>{city}</StyledMarquee> + <StyledMarquee data-test-id="city">{city}</StyledMarquee> </LocationRow> ); } @@ -231,7 +231,7 @@ export default class TunnelControl extends React.Component<ITunnelControlProps> this.props.country === undefined ? '' : relayLocations.gettext(this.props.country); return ( <LocationRow> - <StyledMarquee>{country}</StyledMarquee> + <StyledMarquee data-test-id="country">{country}</StyledMarquee> </LocationRow> ); } diff --git a/gui/src/renderer/preload.ts b/gui/src/renderer/preload.ts index 61b92a5962..864d1dc4d0 100644 --- a/gui/src/renderer/preload.ts +++ b/gui/src/renderer/preload.ts @@ -9,3 +9,7 @@ contextBridge.exposeInMainWorld('env', { development: process.env.NODE_ENV === 'development', platform: process.platform, }); + +if (process.env.CI) { + contextBridge.exposeInMainWorld('__REACT_DEVTOOLS_GLOBAL_HOOK__', { isDisabled: true }); +} diff --git a/gui/test/e2e/daemon/daemon-utils.ts b/gui/test/e2e/daemon/daemon-utils.ts new file mode 100644 index 0000000000..a5aeacc376 --- /dev/null +++ b/gui/test/e2e/daemon/daemon-utils.ts @@ -0,0 +1,5 @@ +import { startApp, StartAppResponse } from '../utils'; + +export const startAppWithDaemon = async (): Promise<StartAppResponse> => { + return startApp('.'); +}; diff --git a/gui/test/e2e/daemon/location.spec.ts b/gui/test/e2e/daemon/location.spec.ts new file mode 100644 index 0000000000..15a86abb37 --- /dev/null +++ b/gui/test/e2e/daemon/location.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from '@playwright/test'; +import { Page } from 'playwright'; + +import { GetByTestId } from '../utils'; +import { startAppWithDaemon } from './daemon-utils'; + +let page: Page; +let getByTestId: GetByTestId; + +test.beforeAll(async () => { + ({ page, getByTestId } = await startAppWithDaemon()); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('App should have a country', async () => { + const countryLabel = getByTestId('country'); + await expect(countryLabel).not.toBeEmpty(); + + const cityLabel = getByTestId('city'); + const noCityLabel = await cityLabel.count() === 0; + expect(noCityLabel).toBeTruthy(); +}); + diff --git a/gui/test/e2e/main.spec.ts b/gui/test/e2e/main.spec.ts deleted file mode 100644 index 85f69f9cf6..0000000000 --- a/gui/test/e2e/main.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { Page } from 'playwright'; - -import { startApp } from './utils'; - -let appWindow: Page; - -test.beforeAll(async () => { - const startAppResponse = await startApp(); - appWindow = startAppResponse.appWindow; -}); - -test.afterAll(async () => { - await appWindow.close(); -}); - -test('Validate title', async () => { - const title = await appWindow.title(); - expect(title).toBe('Mullvad VPN'); - await expect(appWindow.locator('header')).toBeVisible(); -}); diff --git a/gui/test/e2e/mocked/main.spec.ts b/gui/test/e2e/mocked/main.spec.ts new file mode 100644 index 0000000000..a3f85c0ab6 --- /dev/null +++ b/gui/test/e2e/mocked/main.spec.ts @@ -0,0 +1,20 @@ +import { expect, test } from '@playwright/test'; +import { Page } from 'playwright'; + +import { startAppWithMocking } from './mocked-utils'; + +let page: Page; + +test.beforeAll(async () => { + ({ page } = await startAppWithMocking()); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('Validate title', async () => { + const title = await page.title(); + expect(title).toBe('Mullvad VPN'); + await expect(page.locator('header')).toBeVisible(); +}); diff --git a/gui/test/e2e/mocked/mocked-utils.ts b/gui/test/e2e/mocked/mocked-utils.ts new file mode 100644 index 0000000000..f7e604f350 --- /dev/null +++ b/gui/test/e2e/mocked/mocked-utils.ts @@ -0,0 +1,62 @@ +import { ElectronApplication } from 'playwright'; + +import { startApp, StartAppResponse } from '../utils'; + +interface StartMockedAppResponse extends StartAppResponse { + mockIpcHandle: MockIpcHandle; + sendMockIpcResponse: SendMockIpcResponse; +} + +export const startAppWithMocking = async (): Promise<StartMockedAppResponse> => { + const startAppResult = await startApp('build/test/e2e/setup/main.js'); + const mockIpcHandle = generateMockIpcHandle(startAppResult.app); + const sendMockIpcResponse = generateSendMockIpcResponse(startAppResult.app); + + return { + ...startAppResult, + mockIpcHandle, + sendMockIpcResponse, + } +}; + +type MockIpcHandleProps<T> = { + channel: string; + response: T; +}; + +export type MockIpcHandle = ReturnType<typeof generateMockIpcHandle>; + +export const generateMockIpcHandle = (electronApp: ElectronApplication) => { + return async <T>({ channel, response }: MockIpcHandleProps<T>): Promise<void> => { + 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; +}; + +export type SendMockIpcResponse = ReturnType<typeof generateMockIpcHandle>; + +export const generateSendMockIpcResponse = (electronApp: ElectronApplication) => { + return async <T>({ channel, response }: SendMockIpcResponseProps<T>) => { + await electronApp.evaluate( + ({ webContents }, { channel, response }) => { + webContents.getAllWebContents()[0].send(channel, response); + }, + { channel, response }, + ); + }; +}; diff --git a/gui/test/e2e/settings.spec.ts b/gui/test/e2e/mocked/settings.spec.ts index e4b1f47108..510c5e97e6 100644 --- a/gui/test/e2e/settings.spec.ts +++ b/gui/test/e2e/mocked/settings.spec.ts @@ -1,31 +1,31 @@ import { expect, test } from '@playwright/test'; import { Page } from 'playwright'; -import { sendMockIpcResponse, startApp } from './utils'; -import { IAccountData } from '../../src/shared/daemon-rpc-types'; +import { SendMockIpcResponse, startAppWithMocking } from './mocked-utils'; +import { IAccountData } from '../../../src/shared/daemon-rpc-types'; -let appWindow: Page; +let page: Page; +let sendMockIpcResponse: SendMockIpcResponse; test.beforeAll(async () => { - const startAppResponse = await startApp(); - appWindow = startAppResponse.appWindow; - await appWindow.click('button[aria-label="Settings"]'); + ({ page, sendMockIpcResponse } = await startAppWithMocking()); + await page.click('button[aria-label="Settings"]'); }); test.afterAll(async () => { - await appWindow.close(); + await page.close(); }); test('Settings Page', async () => { - const title = appWindow.locator('h1'); + const title = page.locator('h1'); await expect(title).toContainText('Settings'); - const closeButton = appWindow.locator('button[aria-label="Close"]'); + const closeButton = page.locator('button[aria-label="Close"]'); await expect(closeButton).toBeVisible(); }); test('Account button should be displayed correctly', async () => { - const accountButton = appWindow.locator('button:has-text("Account")'); + const accountButton = page.locator('button:has-text("Account")'); await expect(accountButton).toBeVisible(); let expiryText = accountButton.locator('span'); diff --git a/gui/test/e2e/tunnel-state.spec.ts b/gui/test/e2e/mocked/tunnel-state.spec.ts index 985ac9e665..c1f0317e24 100644 --- a/gui/test/e2e/tunnel-state.spec.ts +++ b/gui/test/e2e/mocked/tunnel-state.spec.ts @@ -1,15 +1,10 @@ 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'; +import { colors } from '../../../src/config.json'; +import { ILocation, ITunnelEndpoint, TunnelState } from '../../../src/shared/daemon-rpc-types'; +import { getBackgroundColor, getColor } from '../utils'; +import { startAppWithMocking, MockIpcHandle, SendMockIpcResponse } from './mocked-utils'; const UNSECURED_COLOR = colors.red; const SECURE_COLOR = colors.green; @@ -23,18 +18,19 @@ const mockLocation: ILocation = { mullvadExitIp: false, }; -const getLabel = () => appWindow.locator('span[role="status"]'); -const getHeader = () => appWindow.locator('header'); +const getLabel = () => page.locator('span[role="status"]'); +const getHeader = () => page.locator('header'); -let appWindow: Page; +let page: Page; +let mockIpcHandle: MockIpcHandle; +let sendMockIpcResponse: SendMockIpcResponse; test.beforeAll(async () => { - const startAppResponse = await startApp(); - appWindow = startAppResponse.appWindow; + ({ page, mockIpcHandle, sendMockIpcResponse } = await startAppWithMocking()); }); test.afterAll(async () => { - await appWindow.close(); + await page.close(); }); /** @@ -60,7 +56,7 @@ test('App should show disconnected tunnel state', async () => { const headerColor = await getBackgroundColor(header); expect(headerColor).toBe(UNSECURED_COLOR); - const button = appWindow.locator('button', { hasText: /secure my connection/i }); + const button = page.locator('button', { hasText: /secure my connection/i }); const buttonColor = await getBackgroundColor(button); expect(buttonColor).toBe(SECURE_COLOR); }); @@ -88,7 +84,7 @@ test('App should show connecting tunnel state', async () => { const headerColor = await getBackgroundColor(header); expect(headerColor).toBe(SECURE_COLOR); - const button = appWindow.locator('button', { hasText: /cancel/i }); + const button = page.locator('button', { hasText: /cancel/i }); const buttonColor = await getBackgroundColor(button); expect(buttonColor).toBe('rgba(227, 64, 57, 0.6)'); }); @@ -123,7 +119,7 @@ test('App should show connected tunnel state', async () => { const headerColor = await getBackgroundColor(header); expect(headerColor).toBe(SECURE_COLOR); - const button = appWindow.locator('button', { hasText: /switch location/i }); + const button = page.locator('button', { hasText: /switch location/i }); const buttonColor = await getBackgroundColor(button); expect(buttonColor).toBe('rgba(255, 255, 255, 0.2)'); }); @@ -149,7 +145,7 @@ test('App should show disconnecting tunnel state', async () => { const headerColor = await getBackgroundColor(header); expect(headerColor).toBe(UNSECURED_COLOR); - const button = appWindow.locator('button', { hasText: /secure my connection/i }); + const button = page.locator('button', { hasText: /secure my connection/i }); const buttonColor = await getBackgroundColor(button); expect(buttonColor).toBe(SECURE_COLOR); }); diff --git a/gui/test/e2e/utils.ts b/gui/test/e2e/utils.ts index 865d74bb1c..0c68d47d49 100644 --- a/gui/test/e2e/utils.ts +++ b/gui/test/e2e/utils.ts @@ -1,65 +1,33 @@ -import { Locator, Page } from 'playwright'; -import { _electron as electron, ElectronApplication } from 'playwright-core'; +import { Locator, Page, _electron as electron, ElectronApplication } from 'playwright'; -interface StartAppResponse { - electronApp: ElectronApplication; - appWindow: Page; -} +export type GetByTestId = (id: string) => Locator; -let electronApp: ElectronApplication; +export interface StartAppResponse { + app: ElectronApplication; + page: Page; + getByTestId: GetByTestId; +} -export const startApp = async (): Promise<StartAppResponse> => { +export const startApp = async (mainPath: string): Promise<StartAppResponse> => { process.env.CI = 'e2e'; - electronApp = await electron.launch({ - args: ['build/test/e2e/setup/main.js'], + const app = await electron.launch({ + args: [mainPath], }); - const appWindow = await electronApp.firstWindow(); + const page = await app.firstWindow(); - appWindow.on('pageerror', (error) => { + page.on('pageerror', (error) => { console.log(error); }); - appWindow.on('console', (msg) => { + page.on('console', (msg) => { console.log(msg.text()); }); - return { electronApp, appWindow }; -}; + const getByTestId = (id: string) => page.locator(`data-test-id=${id}`); -type MockIpcHandleProps<T> = { - channel: string; - response: T; -}; - -export 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; -}; - -export const sendMockIpcResponse = async <T>({ channel, response }: SendMockIpcResponseProps<T>) => { - await electronApp.evaluate( - ({ webContents }, { channel, response }) => { - webContents.getAllWebContents()[0].send(channel, response); - }, - { channel, response }, - ); + return { app, page, getByTestId }; }; const getStyleProperty = (locator: Locator, property: string) => { |
