diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2024-04-19 11:42:12 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2024-04-19 11:42:12 +0200 |
| commit | eced1187524fb2d56341fa17749002cf9ed0df1c (patch) | |
| tree | 7b4513be33bb6b037584a104ba4ec4a13a57a390 /gui | |
| parent | f9e4201c906f1591ba8ae56eabacd69bbe9a0652 (diff) | |
| parent | 9931f2c602ac9028bba7a05f52b86d707d77043f (diff) | |
| download | mullvadvpn-eced1187524fb2d56341fa17749002cf9ed0df1c.tar.xz mullvadvpn-eced1187524fb2d56341fa17749002cf9ed0df1c.zip | |
Merge branch 'add-custom-bridge-test-des-820'
Diffstat (limited to 'gui')
7 files changed, 207 insertions, 6 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 1976d04278..9126cb9c99 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -262,6 +262,10 @@ msgid "%(title)s, View loaded" msgstr "" msgctxt "accessibility" +msgid "Add new custom bridge" +msgstr "" + +msgctxt "accessibility" msgid "Close notification" msgstr "" @@ -276,6 +280,10 @@ msgid "Copy account number" msgstr "" msgctxt "accessibility" +msgid "Edit custom bridge" +msgstr "" + +msgctxt "accessibility" msgid "Expand %(location)s" msgstr "" diff --git a/gui/src/renderer/components/EditCustomBridge.tsx b/gui/src/renderer/components/EditCustomBridge.tsx index 2692217ea0..7cc403aa6e 100644 --- a/gui/src/renderer/components/EditCustomBridge.tsx +++ b/gui/src/renderer/components/EditCustomBridge.tsx @@ -95,7 +95,11 @@ function CustomBridgeForm() { <SmallButton key="cancel" onClick={hideDeleteDialog}> {messages.gettext('Cancel')} </SmallButton>, - <SmallButton key="delete" color={SmallButtonColor.red} onClick={onDelete}> + <SmallButton + key="delete" + color={SmallButtonColor.red} + onClick={onDelete} + data-testid="delete-confirm"> {messages.gettext('Delete')} </SmallButton>, ]} diff --git a/gui/src/renderer/components/OpenVpnSettings.tsx b/gui/src/renderer/components/OpenVpnSettings.tsx index dee1e64089..5013b158f8 100644 --- a/gui/src/renderer/components/OpenVpnSettings.tsx +++ b/gui/src/renderer/components/OpenVpnSettings.tsx @@ -269,6 +269,7 @@ function BridgeModeSelector() { label: messages.gettext('On'), value: 'on', disabled: tunnelProtocol !== 'openvpn' || transportProtocol === 'udp', + 'data-testid': 'bridge-mode-on', }, { label: messages.gettext('Off'), @@ -367,7 +368,7 @@ function BridgeModeSelector() { <SmallButton key="cancel" onClick={hideConfirmationDialog}> {messages.gettext('Cancel')} </SmallButton>, - <SmallButton key="confirm" onClick={confirmBridgeState}> + <SmallButton key="confirm" onClick={confirmBridgeState} data-testid="enable-confirm"> {messages.gettext('Enable')} </SmallButton>, ]} diff --git a/gui/src/renderer/components/cell/Selector.tsx b/gui/src/renderer/components/cell/Selector.tsx index e527017435..5e5d685a59 100644 --- a/gui/src/renderer/components/cell/Selector.tsx +++ b/gui/src/renderer/components/cell/Selector.tsx @@ -16,6 +16,8 @@ export interface SelectorItem<T> { label: string; value: T; disabled?: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention + 'data-testid'?: string; } // T represents the available values and U represent the value of "Automatic"/"Any" if there is one. @@ -51,7 +53,8 @@ export default function Selector<T, U>(props: SelectorProps<T, U>) { isSelected={selected} disabled={props.disabled || item.disabled} forwardedRef={ref} - onSelect={props.onSelect}> + onSelect={props.onSelect} + data-testid={item['data-testid']}> {item.label} </SelectorCell> ); @@ -133,6 +136,8 @@ interface SelectorCellProps<T> { onSelect: (value: T) => void; children: React.ReactNode | Array<React.ReactNode>; forwardedRef?: React.Ref<HTMLButtonElement>; + // eslint-disable-next-line @typescript-eslint/naming-convention + 'data-testid'?: string; } function SelectorCell<T>(props: SelectorCellProps<T>) { @@ -150,7 +155,8 @@ function SelectorCell<T>(props: SelectorCellProps<T>) { disabled={props.disabled} role="option" aria-selected={props.isSelected} - aria-disabled={props.disabled}> + aria-disabled={props.disabled} + data-testid={props['data-testid']}> <StyledCellIcon $visible={props.isSelected} source="icon-tick" diff --git a/gui/src/renderer/components/select-location/SpecialLocationList.tsx b/gui/src/renderer/components/select-location/SpecialLocationList.tsx index 9579a6b4b5..a347638b0e 100644 --- a/gui/src/renderer/components/select-location/SpecialLocationList.tsx +++ b/gui/src/renderer/components/select-location/SpecialLocationList.tsx @@ -108,7 +108,8 @@ export function CustomBridgeLocationRow( const history = useHistory(); const bridgeSettings = useSelector((state) => state.settings.bridgeSettings); - const icon = bridgeSettings.custom !== undefined ? 'icon-edit' : 'icon-add'; + const bridgeConfigured = bridgeSettings.custom !== undefined; + const icon = bridgeConfigured ? 'icon-edit' : 'icon-add'; const selectedRef = props.source.selected ? props.selectedElementRef : undefined; const background = getButtonColor(props.source.selected, 0, props.source.disabled); @@ -135,7 +136,14 @@ export function CustomBridgeLocationRow( 'A custom bridge server can be used to circumvent censorship when regular Mullvad bridge servers don’t work.', )} /> - <StyledLocationRowIcon {...background} onClick={navigate}> + <StyledLocationRowIcon + {...background} + aria-label={ + bridgeConfigured + ? messages.pgettext('accessibility', 'Edit custom bridge') + : messages.pgettext('accessibility', 'Add new custom bridge') + } + onClick={navigate}> <StyledSpecialLocationSideButton source={icon} width={18} diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts index 08ed2dccf3..027185e416 100644 --- a/gui/src/renderer/components/select-location/select-location-hooks.ts +++ b/gui/src/renderer/components/select-location/select-location-hooks.ts @@ -121,6 +121,7 @@ export function useOnSelectBridgeLocation() { case SpecialBridgeLocationType.closestToExit: return setLocation( bridgeSettingsModifier((bridgeSettings) => { + bridgeSettings.type = 'normal'; bridgeSettings.normal.location = 'any'; return bridgeSettings; }), diff --git a/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts b/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts new file mode 100644 index 0000000000..5efd110213 --- /dev/null +++ b/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts @@ -0,0 +1,173 @@ +import { expect, test } from '@playwright/test'; +import { Page } from 'playwright'; + +import { startInstalledApp } from '../installed-utils'; +import { TestUtils } from '../../utils'; +import { colors } from '../../../../src/config.json'; +import { RoutePath } from '../../../../src/renderer/lib/routes'; + +// This test expects the daemon to be logged in and not have a custom bridge configured. +// Env parameters: +// `SHADOWSOCKS_SERVER_IP` +// `SHADOWSOCKS_SERVER_PORT` +// `SHADOWSOCKS_SERVER_CIPHER` +// `SHADOWSOCKS_SERVER_PASSWORD` + +let page: Page; +let util: TestUtils; + +test.beforeAll(async () => { + ({ page, util } = await startInstalledApp()); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('App should enable bridge mode', async () => { + await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]')); + expect( + await util.waitForNavigation(async () => await page.getByText('VPN settings').click()), + ).toBe(RoutePath.vpnSettings); + + await page.getByRole('option', { name: 'OpenVPN' }).click(); + + expect( + await util.waitForNavigation(async () => await page.getByText('OpenVPN settings').click()), + ).toBe(RoutePath.openVpnSettings); + + await page.getByTestId('bridge-mode-on').click(); + await expect(page.getByText('Enable bridge mode?')).toBeVisible(); + + page.getByTestId('enable-confirm').click(); + + await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]')); + await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]')); + expect( + await util.waitForNavigation(async () => await page.click('button[aria-label="Close"]')), + ).toBe(RoutePath.main); +}); + +test('App display disabled custom bridge', async () => { + expect( + await util.waitForNavigation( + async () => await page.click('button[aria-label^="Select location"]'), + ), + ).toBe(RoutePath.selectLocation); + + const title = page.locator('h1') + await expect(title).toHaveText('Select location'); + + await page.getByText(/^Entry$/).click(); + + const customBridgeButton = page.getByText('Custom bridge'); + await expect(customBridgeButton).toBeDisabled(); +}); + +test('App should add new custom bridge', async () => { + expect( + await util.waitForNavigation( + async () => await page.click('button[aria-label="Add new custom bridge"]'), + ), + ).toBe(RoutePath.editCustomBridge); + + const title = page.locator('h1') + await expect(title).toHaveText('Add custom bridge'); + + const inputs = page.locator('input'); + const addButton = page.locator('button:has-text("Add")'); + await expect(addButton).toBeVisible(); + await expect(addButton).toBeDisabled(); + + await inputs.first().fill(process.env.SHADOWSOCKS_SERVER_IP!); + await expect(addButton).toBeDisabled(); + + await inputs.nth(1).fill('443'); + await expect(addButton).toBeEnabled(); + + await inputs.nth(2).fill(process.env.SHADOWSOCKS_SERVER_PASSWORD!); + + await page.getByTestId('ciphers').click(); + await page.getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true }).click(); + + expect( + await util.waitForNavigation(async () => await addButton.click()) + ).toEqual(RoutePath.selectLocation); + + const customBridgeButton = page.getByText('Custom bridge'); + await expect(customBridgeButton).toBeEnabled(); + + await expect(page.locator('button[aria-label="Edit custom bridge"]')).toBeVisible(); +}); + +test('App should select custom bridge', async () => { + const customBridgeButton = page.locator('button:has-text("Custom bridge")'); + await expect(customBridgeButton).toHaveCSS('background-color', colors.green); + + const automaticButton = page.getByText('Automatic'); + await automaticButton.click(); + await page.getByText(/^Entry$/).click(); + await expect(customBridgeButton).not.toHaveCSS('background-color', colors.green); + + + await customBridgeButton.click(); + await page.getByText(/^Entry$/).click(); + await expect(customBridgeButton).toHaveCSS('background-color', colors.green); + +}); + +test('App should edit custom bridge', async () => { + const automaticButton = page.getByText('Automatic'); + await automaticButton.click(); + await page.getByText(/^Entry$/).click(); + + expect( + await util.waitForNavigation( + async () => await page.click('button[aria-label="Edit custom bridge"]'), + ), + ).toBe(RoutePath.editCustomBridge); + + const title = page.locator('h1') + await expect(title).toHaveText('Edit custom bridge'); + + const inputs = page.locator('input'); + const saveButton = page.locator('button:has-text("Save")'); + await expect(saveButton).toBeVisible(); + await expect(saveButton).toBeEnabled(); + + await inputs.nth(1).fill(process.env.SHADOWSOCKS_SERVER_PORT!); + await expect(saveButton).toBeEnabled(); + + + expect( + await util.waitForNavigation(async () => await saveButton.click()) + ).toEqual(RoutePath.selectLocation); + + const customBridgeButton = page.locator('button:has-text("Custom bridge")'); + await expect(customBridgeButton).toBeEnabled(); + await expect(customBridgeButton).toHaveCSS('background-color', colors.green); +}); + +test('App should delete custom bridge', async () => { + expect( + await util.waitForNavigation( + async () => await page.click('button[aria-label="Edit custom bridge"]'), + ), + ).toBe(RoutePath.editCustomBridge); + + const deleteButton = page.locator('button:has-text("Delete")'); + await expect(deleteButton).toBeVisible(); + await expect(deleteButton).toBeEnabled(); + + await deleteButton.click(); + await expect(page.getByText('Delete custom bridge?')).toBeVisible(); + + const confirmButton = page.getByTestId('delete-confirm'); + expect( + await util.waitForNavigation(async () => await confirmButton.click()) + ).toEqual(RoutePath.selectLocation); + + const customBridgeButton = page.locator('button:has-text("Custom bridge")'); + await expect(customBridgeButton).toBeDisabled(); + await expect(customBridgeButton).not.toHaveCSS('background-color', colors.green); +}); |
