Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class AccountPage {
private readonly page: Page;

readonly sendUsageDataCheckbox: Locator;
readonly appLockCheckboxLabel: Locator;
readonly appLockCheckbox: Locator;
readonly deleteAccountButton: Locator;
readonly backUpButton: Locator;
readonly backupFileInput: Locator;
Expand All @@ -44,6 +46,8 @@ export class AccountPage {
this.page = page;

this.sendUsageDataCheckbox = page.locator("[data-uie-name='status-preference-telemetry']+label");
this.appLockCheckboxLabel = page.locator("[data-uie-name='status-preference-applock']+label");
this.appLockCheckbox = page.locator("[data-uie-name='status-preference-applock']");
this.deleteAccountButton = page.getByTestId('go-delete-account');
this.backUpButton = page.getByTestId('do-backup-export');
this.backupFileInput = page.getByTestId('input-import-file');
Expand Down
301 changes: 133 additions & 168 deletions apps/webapp/test/e2e_tests/specs/AppLock/AppLock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,210 +19,175 @@

import {User} from 'test/e2e_tests/data/user';
import {PageManager} from 'test/e2e_tests/pageManager';
import {test, expect, withLogin, PagePlugin, Team, LOGIN_TIMEOUT} from 'test/e2e_tests/test.fixtures';
import {AppLockModal} from 'test/e2e_tests/pageManager/webapp/modals/appLock.modal';
import {checkAnyIndexedDBExists} from 'test/e2e_tests/utils/indexedDB.util';
import {handleAppLockState} from 'test/e2e_tests/utils/userActions';

/** Page plugin specific to this feature to set the app lock code upon the first login */
const withAppLock =
(code: string): PagePlugin =>
async page => {
await new AppLockModal(page).setPasscode(code);
};
import {test, expect, withLogin} from '../../test.fixtures';

test.describe('AppLock', () => {
let user: User;
let team: Team;
let owner: User;
let memberA: User;
const teamName = 'AppLock';
const appLockPassCode = '1a3!567N4';

test.beforeEach(async ({createTeam}) => {
team = await createTeam('AppLock Team');
user = team.owner;
});
test.beforeEach(async ({api, createTeam, createUser}) => {
memberA = await createUser();
const team = await createTeam(teamName, {users: [memberA]});
owner = team.owner;

test.describe('AppLock enforced for team', async () => {
test.beforeEach(async ({api}) => {
// Enforce app lock for the whole team
await api.brig.toggleAppLock(team.teamId, 'enabled', true);
});
await api.brig.toggleAppLock(owner.teamId, 'enabled', true);
});

test(
'Web: I should not be able to close app lock setup modal if app lock is enforced',
{tag: ['@TC-2740']},
async ({createPage}) => {
const page = await createPage();
const pageManager = PageManager.from(page);
const {modals} = pageManager.webapp;
test(
'I want to see app lock setup modal on login after app lock has been enforced for the team',
{tag: ['@TC-2744', '@TC-2740', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(memberA));
const {modals} = PageManager.from(page).webapp;

await pageManager.openLoginPage();
await pageManager.webapp.pages.login().login(user);
await expect(modals.appLock().appLockModal).toBeVisible({timeout: LOGIN_TIMEOUT});
await expect(modals.appLock().appLockModal).toBeVisible();

await page.mouse.click(200, 350); // click outside the modal
await test.step('Web: I should not be able to close app lock setup modal if app lock is enforced', async () => {
// click outside the modal
await page.mouse.click(200, 350);
// check if the modal still there
await expect(modals.appLock().appLockModal).toBeVisible();
},
);

test(
'I want to see app lock setup modal on login after app lock has been enforced for the team',
{tag: ['@TC-2744', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user));
const {modals} = PageManager.from(page).webapp;
});
},
);

await expect(modals.appLock().appLockModal).toBeVisible();
(
[
{
title: 'Web: I want the app to lock when I switch back to webapp tab after inactivity timeout expired',
tag: '@TC-2752',
},
);

test.fixme(
'Web: App should not lock if I switch back to webapp tab in time (during inactivity timeout)',
{tag: ['@TC-2752', '@TC-2753', '@regression']},
async ({browser, createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const pageManager = PageManager.from(page);
const {modals} = pageManager.webapp;

const unrelatedPage = await browser.newPage();
await unrelatedPage.goto('about:blank');
await unrelatedPage.bringToFront();
await unrelatedPage.waitForTimeout(2_000); // open be only 2 sec in the other tab
await page.bringToFront();

await expect(modals.appLock().appLockModalHeader).not.toBeVisible();

await test.step('Web: I want the app to lock when I switch back to webapp tab after inactivity timeout expired', async () => {
await unrelatedPage.goto('about:blank');
await unrelatedPage.bringToFront();
await unrelatedPage.waitForTimeout(31_000);
await page.bringToFront();
await expect(modals.appLock().appLockModalHeader).toBeVisible();
});
{
title: 'Web: App should not lock if I switch back to webapp tab in time (during inactivity timeout)',
tag: '@TC-2753',
},
);
] as const
).forEach(({title, tag}) => {
const shouldLock = tag === '@TC-2752';

test(title, {tag: [tag, '@regression']}, async ({createPage}) => {
const page = await createPage(withLogin(memberA));
const pageManager = PageManager.from(page);
await handleAppLockState(pageManager, appLockPassCode);

const {modals} = pageManager.webapp;
await page.dispatchEvent('body', 'blur');
await page.waitForTimeout(shouldLock ? 61_000 : 3_000);
await page.dispatchEvent('body', 'focus');

if (shouldLock) {
await expect(modals.appLock().appLockModalHeader).toBeVisible();
} else {
await expect(modals.appLock().appLockModalHeader).not.toBeVisible();
}
});
});

test(
'Web: I want the app to automatically lock after refreshing the page',
{tag: ['@TC-2754', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {modals} = PageManager.from(page).webapp;
test(
'Web: I want to unlock the app with passphrase after login',
{tag: ['@TC-2754', '@TC-2755', '@TC-2758', '@TC-2763', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(memberA));
const pageManager = PageManager.from(page);
const {modals, pages} = pageManager.webapp;

await page.reload();
await test.step('Web: I want the app to automatically lock after refreshing the page', async () => {
await handleAppLockState(pageManager, appLockPassCode);
await pageManager.refreshPage();

await expect(modals.appLock().appLockModal).toBeVisible();
},
);

test(
'Web: I want to unlock the app with passphrase after login',
{tag: ['@TC-2755', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {modals} = PageManager.from(page).webapp;

await page.reload();
await modals.appLock().unlockAppWithPasscode(appLockPassCode);

await expect(modals.appLock().appLockModal).not.toBeVisible();
},
);

test(
'Web: I should not be able to unlock the app with wrong passphrase',
{tag: ['@TC-2758', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {modals} = PageManager.from(page).webapp;

await page.reload();
await modals.appLock().unlockAppWithPasscode('wrongCredentials');

await expect(modals.appLock().errorMessage).toContainText('Wrong passcode');
},
);
});

test(
'I want to wipe database when I forgot my app lock passphrase',
{tag: ['@TC-2761', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {pages, modals} = PageManager.from(page).webapp;
await test.step('Web: I should not be able to unlock the app with wrong passphrase', async () => {
await handleAppLockState(pageManager, 'wrongCredentials');
await expect(modals.appLock().errorMessage).toHaveText('Wrong passcode');
});

await page.reload();
await test.step('Web: I should not be able to wipe database with wrong account password', async () => {
await modals.appLock().clickForgotPassphrase();
await modals.appLock().clickWipeDB();
await modals.appLock().clickReset();
await modals.appLock().inputUserPassword(user.password);
await modals.appLock().inputUserPassword('wrong password');

expect(await checkAnyIndexedDBExists(page)).toBeTruthy();
});

await test.step('I want to wipe database when I forgot my app lock passphrase', async () => {
await modals.appLock().inputUserPassword(memberA.password);

// After redirect to login page verify the whole indexDB was cleared
await expect(pages.singleSignOn().ssoCodeEmailInput).toBeVisible();
await expect.poll(() => page.evaluate(() => indexedDB.databases())).toHaveLength(0);
},
);
expect(await checkAnyIndexedDBExists(page)).toBeFalsy();
});
},
);

test(
'Web: I should not be able to wipe database with wrong account password',
{tag: ['@TC-2763', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {modals} = PageManager.from(page).webapp;
test(
'I want to wipe database when I forgot my app lock passphrase',
{tag: ['@TC-2761', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(memberA));
const pageManager = PageManager.from(page);
const {pages, modals} = PageManager.from(page).webapp;
await handleAppLockState(pageManager, appLockPassCode);

await page.reload();
await modals.appLock().clickForgotPassphrase();
await modals.appLock().clickWipeDB();
await modals.appLock().clickReset();
await modals.appLock().inputUserPassword(memberA.password);

// After redirect to login page verify the whole indexDB was cleared
await expect(pages.singleSignOn().ssoCodeEmailInput).toBeVisible();
await expect.poll(() => page.evaluate(() => indexedDB.databases())).toHaveLength(0);
},
);

await page.reload();
await modals.appLock().clickForgotPassphrase();
await modals.appLock().clickWipeDB();
await modals.appLock().clickReset();
await modals.appLock().inputUserPassword('invalid');
test(
'I should not be able to switch off app lock if it is enforced for the team',
{tag: ['@TC-2770', '@TC-2767', '@regression']},
async ({createPage}) => {
const page = await createPage(withLogin(memberA));
const pageManager = PageManager.from(page);
const {components, pages} = pageManager.webapp;
await handleAppLockState(pageManager, appLockPassCode);
await components.conversationSidebar().clickPreferencesButton();

// The modal should show an error for the invalid password and not wipe indexDB
await expect(modals.appLock().errorMessage).toContainText('Wrong password');
await expect.poll(() => page.evaluate(() => indexedDB.databases())).not.toHaveLength(0);
},
);
await expect(pages.account().appLockCheckbox).toBeDisabled();
// check here string

[
{title: 'Web: Verify inactivity timeout set on a team level applies to team member accounts', tag: '@TC-2767'},
{title: 'I should not be able to switch off app lock if it is enforced for the team', tag: '@TC-2770'},
].forEach(({title, tag}) => {
test(title, {tag: [tag, '@regression']}, async ({createPage}) => {
const page = await createPage(withLogin(user), withAppLock(appLockPassCode));
const {components, pages} = PageManager.from(page).webapp;

await components.conversationSidebar().clickPreferencesButton();
await expect(pages.account().privacySection.appLock.checkbox).toBeDisabled();
await expect(pages.account().privacySection).toContainText(
'Lock Wire after 30 seconds in the background. Unlock with Touch ID or enter your passcode',
);
});
});
});
await expect(
page.getByText('Lock Wire after 30 seconds in the background. Unlock with Touch ID or enter your passcode.'),
).toHaveCount(1);
},
);

test('I want to switch off app lock', {tag: ['@TC-2771', '@regression']}, async ({createPage}) => {
const {components, pages, modals} = PageManager.from(await createPage(withLogin(user))).webapp;
test('I want to switch off app lock', {tag: ['@TC-2771', '@TC-2772', '@regression']}, async ({api, createPage}) => {
await api.brig.toggleAppLock(owner.teamId, 'enabled', false);

await test.step('Enable app lock', async () => {
await components.conversationSidebar().clickPreferencesButton();
await pages.account().privacySection.appLock.label.click();
await modals.appLock().setPasscode(appLockPassCode);
});
const page = await createPage(withLogin(memberA));
const pageManager = PageManager.from(page);
const {components, pages, modals} = pageManager.webapp;

await test.step('Switch off app lock', async () => {
await pages.account().privacySection.appLock.label.click();
await modals.confirm().actionButton.click();
});
await components.conversationSidebar().clickPreferencesButton();
await pages.account().appLockCheckboxLabel.click();
await handleAppLockState(pageManager, appLockPassCode);

await expect(pages.account().privacySection.appLock.checkbox).not.toBeChecked();
await pages.account().appLockCheckboxLabel.click();

await modals.confirm().actionButton.click();
await expect(pages.account().appLockCheckbox).not.toBeChecked();
});

test(
test.skip(
'Web: Verify inactivity timeout can be set if app lock is not enforced on a team level',
{tag: ['@TC-2772', '@regression']},
async ({createPage}) => {
const {components, pages, modals} = PageManager.from(await createPage(withLogin(user))).webapp;

await components.conversationSidebar().clickPreferencesButton();
await pages.account().privacySection.appLock.label.click();
await modals.appLock().setPasscode(appLockPassCode);

await expect(pages.account().privacySection.appLock.checkbox).toBeChecked();
async () => {
// not implemented
},
);
});
Loading
Loading