diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-03-24 09:28:25 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-03-27 10:18:28 +0200 |
| commit | 7900bee0590832c0dfdb922b4b71510ead8813e5 (patch) | |
| tree | 8d0ebbd33f7c2cc5c3c4fc0c6d6adddb2ecb1339 | |
| parent | 872e02022ad4fc43441c398d3e7ac3e14c67e0fb (diff) | |
| download | mullvadvpn-7900bee0590832c0dfdb922b4b71510ead8813e5.tar.xz mullvadvpn-7900bee0590832c0dfdb922b4b71510ead8813e5.zip | |
Add login tests
| -rw-r--r-- | gui/src/renderer/components/AccountTokenLabel.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/components/ClipboardLabel.tsx | 18 | ||||
| -rw-r--r-- | gui/src/renderer/components/Login.tsx | 2 | ||||
| -rw-r--r-- | gui/test/e2e/installed/requires-input/login.spec.ts | 32 | ||||
| -rw-r--r-- | gui/test/e2e/installed/state-dependent/login.spec.ts | 94 |
5 files changed, 105 insertions, 42 deletions
diff --git a/gui/src/renderer/components/AccountTokenLabel.tsx b/gui/src/renderer/components/AccountTokenLabel.tsx index 704f41af10..479beed9a1 100644 --- a/gui/src/renderer/components/AccountTokenLabel.tsx +++ b/gui/src/renderer/components/AccountTokenLabel.tsx @@ -14,6 +14,7 @@ export default function AccountTokenLabel(props: IAccountTokenLabelProps) { displayValue={formatAccountToken(props.accountToken)} obscureValue={props.obscureValue} className={props.className} + data-testid="account-number" /> ); } diff --git a/gui/src/renderer/components/ClipboardLabel.tsx b/gui/src/renderer/components/ClipboardLabel.tsx index 98fcede85e..582e03b02d 100644 --- a/gui/src/renderer/components/ClipboardLabel.tsx +++ b/gui/src/renderer/components/ClipboardLabel.tsx @@ -10,12 +10,11 @@ import ImageView from './ImageView'; const COPIED_ICON_DURATION = 2000; -interface IProps { +interface IProps extends React.HTMLAttributes<HTMLElement> { value: string; displayValue?: string; obscureValue?: boolean; message?: string; - className?: string; } const StyledLabelContainer = styled.div({ @@ -42,29 +41,30 @@ const StyledCopyButton = styled(StyledButton)({ }); export default function ClipboardLabel(props: IProps) { - const [obscured, , , toggleObscured] = useBoolean(props.obscureValue ?? true); + const { value, obscureValue, displayValue, message, ...otherProps } = props; + + const [obscured, , , toggleObscured] = useBoolean(obscureValue ?? true); const [justCopied, setJustCopied, resetJustCopied] = useBoolean(false); const copiedScheduler = useScheduler(); const onCopy = useCallback(async () => { try { - await navigator.clipboard.writeText(props.value); + await navigator.clipboard.writeText(value); copiedScheduler.schedule(resetJustCopied, COPIED_ICON_DURATION); setJustCopied(); } catch (e) { const error = e as Error; log.error(`Failed to copy to clipboard: ${error.message}`); } - }, [props.value, copiedScheduler, setJustCopied, resetJustCopied]); + }, [value, copiedScheduler, setJustCopied, resetJustCopied]); - const value = props.displayValue ?? props.value; return ( <StyledLabelContainer> - <StyledLabel className={props.className} aria-hidden={obscured}> - {obscured ? '●●●● ●●●● ●●●● ●●●●' : value} + <StyledLabel aria-hidden={obscured} {...otherProps}> + {obscured ? '●●●● ●●●● ●●●● ●●●●' : displayValue ?? value} </StyledLabel> - {props.obscureValue !== false && ( + {obscureValue !== false && ( <StyledButton onClick={toggleObscured} aria-label={ diff --git a/gui/src/renderer/components/Login.tsx b/gui/src/renderer/components/Login.tsx index b1d2a1392b..4219c0572f 100644 --- a/gui/src/renderer/components/Login.tsx +++ b/gui/src/renderer/components/Login.tsx @@ -274,7 +274,7 @@ export default class Login extends React.Component<IProps, IState> { return ( <> - <StyledSubtitle>{this.formSubtitle()}</StyledSubtitle> + <StyledSubtitle data-testid="subtitle">{this.formSubtitle()}</StyledSubtitle> <StyledAccountInputGroup active={allowInteraction && this.state.isActive} editable={allowInteraction} diff --git a/gui/test/e2e/installed/requires-input/login.spec.ts b/gui/test/e2e/installed/requires-input/login.spec.ts deleted file mode 100644 index 2ff0fd576c..0000000000 --- a/gui/test/e2e/installed/requires-input/login.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { Page } from 'playwright'; -import { RoutePath } from '../../../../src/renderer/lib/routes'; -import { TestUtils } from '../../utils'; -import { assertDisconnected } from '../../shared/tunnel-state'; - -import { startInstalledApp } from '../installed-utils'; - -// This test expects the daemon to be logged out and then log in. - -let page: Page; -let util: TestUtils; - -test.beforeAll(async () => { - ({ page, util } = await startInstalledApp()); -}); - -test.afterAll(async () => { - await page.close(); -}); - -// Disables timeout since it's handled by the rust test -test.setTimeout(0); - -test('App should go from login view to main view when daemon logs in', async () => { - expect(await util.currentRoute()).toEqual(RoutePath.login); - - // Waiting for the daemon to log in - expect(await util.waitForNavigation()).toEqual(RoutePath.main); - - await assertDisconnected(page); -}); diff --git a/gui/test/e2e/installed/state-dependent/login.spec.ts b/gui/test/e2e/installed/state-dependent/login.spec.ts new file mode 100644 index 0000000000..21568a6130 --- /dev/null +++ b/gui/test/e2e/installed/state-dependent/login.spec.ts @@ -0,0 +1,94 @@ +import { exec } from 'child_process'; +import { expect, test } from '@playwright/test'; +import { Locator, Page } from 'playwright'; +import { RoutePath } from '../../../../src/renderer/lib/routes'; +import { TestUtils } from '../../utils'; + +import { startInstalledApp } from '../installed-utils'; + +// This test expects the daemon to be logged out. + +let page: Page; +let util: TestUtils; + +let accountNumber: string; + +test.beforeAll(async () => { + ({ page, util } = await startInstalledApp()); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('App should fail to login', async () => { + expect(await util.currentRoute()).toEqual(RoutePath.login); + + const title = page.locator('h1') + const subtitle = page.getByTestId('subtitle'); + const loginInput = getInput(page); + + await expect(title).toHaveText('Login'); + await expect(subtitle).toHaveText('Enter your account number'); + + await loginInput.fill('1234 1234 1324 1234'); + await loginInput.press('Enter'); + + await expect(title).toHaveText('Logging in...'); + await expect(subtitle).toHaveText('Checking account number'); + await expect(title).toHaveText('Login failed'); + await expect(subtitle).toHaveText('Invalid account number'); + + loginInput.fill(''); +}); + +test('App should create account', async () => { + expect(await util.currentRoute()).toEqual(RoutePath.login); + + const title = page.locator('h1') + const subtitle = page.getByTestId('subtitle'); + + await page.getByText('Create account').click(); + await expect(title).toHaveText('Creating account...'); + await expect(subtitle).toHaveText('Please wait'); + + await expect(title).toHaveText('Account created'); + await expect(subtitle).toHaveText('Logged in'); + + expect(await util.waitForNavigation()).toEqual(RoutePath.main); + + const inputValue = await page.getByTestId('account-number').textContent(); + expect(inputValue).toHaveLength(19); + accountNumber = inputValue!.replaceAll(' ', ''); +}); + +test('App should log out', async () => { + expect(await util.waitForNavigation(() => { + exec('mullvad account logout'); + })).toEqual(RoutePath.login); +}); + +test('App should log in', async () => { + expect(await util.currentRoute()).toEqual(RoutePath.login); + + const title = page.locator('h1') + const subtitle = page.getByTestId('subtitle'); + const loginInput = getInput(page); + + await expect(title).toHaveText('Login'); + await expect(subtitle).toHaveText('Enter your account number'); + + await loginInput.type(accountNumber); + await loginInput.press('Enter'); + + await expect(title).toHaveText('Logging in...'); + await expect(subtitle).toHaveText('Checking account number'); + await expect(title).toHaveText('Logged in'); + await expect(subtitle).toHaveText('Valid account number'); + + expect(await util.waitForNavigation()).toEqual(RoutePath.main); +}); + +function getInput(page: Page): Locator { + return page.getByPlaceholder('0000 0000 0000 0000'); +} |
