Skip to content

Commit 0a96dc2

Browse files
markbrockhoffe-maad
authored andcommitted
test(WPB-19939): add authentication regression tests (#19826)
* test(TC-34579: verify signin button is disabled inc ase of empty credentials * test(TC-3468): Verify sign in error appearance in case of suspended team account * test(TC-3460): Verify current browser is set as temporary device * test(TC-3465): Verify sign in error appearance in case of wrong credentials * test(TC-3472): Ensure history is kept after refresh on permanent device * test(TC-3473): create data driven test to also ensure history is kept on temporary device * test(TC-1311): verify session expires * test(TC-3480): check tls versions allowed by webapp server * test(TC-1311): Ensure history is gone after logging out of public computer
1 parent eb9c43b commit 0a96dc2

File tree

6 files changed

+305
-6
lines changed

6 files changed

+305
-6
lines changed

test/e2e_tests/pageManager/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {ErrorModal} from './webapp/modals/error.modal';
4848
import {ExportBackupModal} from './webapp/modals/exportBackup.modal';
4949
import {importBackupModal} from './webapp/modals/importBackup.modal';
5050
import {LeaveConversationModal} from './webapp/modals/leaveConversation.modal';
51+
import {PasswordModal} from './webapp/modals/password.modal';
5152
import {RemoveMemberModal} from './webapp/modals/removeMember.modal';
5253
import {UnableToOpenConversationModal} from './webapp/modals/unableToOpenConversation.modal';
5354
import {UserProfileModal} from './webapp/modals/userProfile.modal';
@@ -61,6 +62,7 @@ import {ConversationPage} from './webapp/pages/conversation.page';
6162
import {ConversationDetailsPage} from './webapp/pages/conversationDetails.page';
6263
import {ConversationListPage} from './webapp/pages/conversationList.page';
6364
import {DeleteAccountPage} from './webapp/pages/deleteAccount.page';
65+
import {DevicesPage} from './webapp/pages/devices.page';
6466
import {EmailVerificationPage} from './webapp/pages/emailVerification.page';
6567
import {GroupCreationPage} from './webapp/pages/groupCreation.page';
6668
import {GuestOptionsPage} from './webapp/pages/guestOptions.page';
@@ -175,6 +177,7 @@ export class PageManager {
175177
connectRequest: () => this.getOrCreate('webapp.pages.connectRequest', () => new ConnectRequestPage(this.page)),
176178
calling: () => this.getOrCreate('webapp.pages.calling', () => new CallingPage(this.page)),
177179
settings: () => this.getOrCreate('webapp.pages.settings', () => new SettingsPage(this.page)),
180+
devices: () => this.getOrCreate('webapp.pages.devices', () => new DevicesPage(this.page)),
178181
options: () => this.getOrCreate('webapp.pages.options', () => new OptionsPage(this.page)),
179182
audioVideoSettings: () =>
180183
this.getOrCreate('webapp.pages.audioVideoSettings', () => new AudioVideoSettingsPage(this.page)),
@@ -224,6 +227,7 @@ export class PageManager {
224227
this.getOrCreate('webapp.modals.marketingConsent', () => new MarketingConsentModal(this.page)),
225228
acknowledge: () => this.getOrCreate('webapp.modals.marketingConsent', () => new AcknowledgeModal(this.page)),
226229
confirm: () => this.getOrCreate('webapp.modals.confirm', () => new ConfirmModal(this.page)),
230+
password: () => this.getOrCreate('webapp.modals.password', () => new PasswordModal(this.page)),
227231
cellsFileDetailView: () =>
228232
this.getOrCreate('webapp.modals.cellsFileDetailView', () => new CellsFileDetailViewModal(this.page)),
229233
cancelRequest: () =>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {Locator, Page} from '@playwright/test';
21+
22+
import {BaseModal} from './base.modal';
23+
24+
export class PasswordModal extends BaseModal {
25+
readonly passwordInput: Locator;
26+
27+
constructor(page: Page) {
28+
// For some reason the password modal is rendered using the `modal-template-option` attribute
29+
super(page, 'modal-template-option');
30+
31+
this.passwordInput = this.modal.getByLabel('Password');
32+
}
33+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {Locator, Page} from '@playwright/test';
21+
22+
export class DevicesPage {
23+
readonly page: Page;
24+
25+
readonly proteusId: Locator;
26+
readonly activeDevices: Locator;
27+
28+
constructor(page: Page) {
29+
this.page = page;
30+
31+
// The id is not using any label or similar but just multiple paragraphs beneath each other
32+
this.proteusId = page.locator("p:text('Proteus ID') + p");
33+
this.activeDevices = page.getByRole('group', {name: 'Active'}).getByRole('button', {name: /device details/});
34+
}
35+
}

test/e2e_tests/pageManager/webapp/pages/login.page.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class LoginPage {
2828
readonly emailInput: Locator;
2929
readonly passwordInput: Locator;
3030
readonly loginErrorText: Locator;
31+
readonly publicComputerCheckbox: Locator;
3132

3233
constructor(page: Page) {
3334
this.page = page;
@@ -36,6 +37,7 @@ export class LoginPage {
3637
this.emailInput = page.locator('[data-uie-name="enter-email"]');
3738
this.passwordInput = page.locator('[data-uie-name="enter-password"]');
3839
this.loginErrorText = page.locator('[data-uie-name="error-message"]');
40+
this.publicComputerCheckbox = page.getByText('This is a public computer');
3941
}
4042

4143
async login(user: Pick<User, 'email' | 'password'>) {

test/e2e_tests/pageManager/webapp/pages/settings.page.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,21 @@ import {Page, Locator} from '@playwright/test';
2222
export class SettingsPage {
2323
readonly page: Page;
2424

25-
readonly audioVideoSettingsButton: Locator;
2625
readonly accountButton: Locator;
26+
readonly devicesButton: Locator;
2727
readonly optionsButton: Locator;
28+
readonly audioVideoButton: Locator;
2829

2930
constructor(page: Page) {
3031
this.page = page;
31-
this.audioVideoSettingsButton = page.locator("[data-uie-name='go-audio-video']");
32-
this.accountButton = page.locator("[data-uie-name='go-account']");
33-
this.optionsButton = page.locator("[data-uie-name='go-options']");
32+
this.accountButton = page.getByRole('button', {name: 'Account'});
33+
this.devicesButton = page.getByRole('button', {name: 'Devices'});
34+
this.optionsButton = page.getByRole('button', {name: 'Options'});
35+
this.audioVideoButton = page.getByRole('button', {name: 'Audio / Video'});
3436
}
3537

3638
async clickAudioVideoSettingsButton() {
37-
await this.audioVideoSettingsButton.click();
39+
await this.audioVideoButton.click();
3840
}
3941

4042
async clickAccountButton() {

test/e2e_tests/specs/Authentication/authentication.spec.ts

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,34 @@
1717
*
1818
*/
1919

20-
import {test, expect} from 'test/e2e_tests/test.fixtures';
20+
import {IncomingMessage} from 'node:http';
21+
import https from 'node:https';
22+
import {SecureVersion} from 'node:tls';
23+
24+
import {PageManager, webAppPath} from 'test/e2e_tests/pageManager';
25+
import {test, expect, withLogin} from 'test/e2e_tests/test.fixtures';
26+
import {connectWithUser} from 'test/e2e_tests/utils/userActions';
2127

2228
test.describe('Authentication', () => {
29+
test(
30+
'Verify sign in button is disabled in case of empty credentials',
31+
{tag: ['@TC-3457', '@regression']},
32+
async ({pageManager}) => {
33+
const {pages} = pageManager.webapp;
34+
const {emailInput, passwordInput, signInButton} = pages.login();
35+
await pageManager.openLoginPage();
36+
37+
await expect(signInButton).toBeDisabled();
38+
39+
await emailInput.fill('invalid@email');
40+
await passwordInput.fill('invalidPassword');
41+
await expect(signInButton).not.toBeDisabled();
42+
43+
await passwordInput.clear();
44+
await expect(signInButton).toBeDisabled();
45+
},
46+
);
47+
2348
test(
2449
'I want to be asked to share telemetry data when I log in',
2550
{tag: ['@TC-8780', '@regression']},
@@ -33,4 +58,202 @@ test.describe('Authentication', () => {
3358
await expect(modals.dataShareConsent().modalTitle).toBeVisible();
3459
},
3560
);
61+
62+
test(
63+
'Verify sign in error appearance in case of suspended team account',
64+
{tag: ['@TC-3468', '@regression']},
65+
async ({pageManager}) => {
66+
const {pages} = pageManager.webapp;
67+
await pageManager.openLoginPage();
68+
69+
const userOfSuspendedTeam = {email: 'sven+team6@wire.com', password: '12345678'};
70+
await pages.login().login(userOfSuspendedTeam);
71+
72+
await expect(pages.login().loginErrorText).toHaveText('This account is no longer authorized to log in');
73+
},
74+
);
75+
76+
test(
77+
'Verify current browser is set as temporary device',
78+
{tag: ['@TC-3460', '@regression']},
79+
async ({pageManager, createUser}) => {
80+
const user = await createUser();
81+
const {pages, components} = pageManager.webapp;
82+
83+
await test.step('Log in with public computer checked', async () => {
84+
await pageManager.openLoginPage();
85+
await pages.login().publicComputerCheckbox.click();
86+
await pages.login().login(user);
87+
await pages.historyInfo().clickConfirmButton();
88+
});
89+
90+
let proteusId: string;
91+
await test.step('Open device settings and get current proteus id', async () => {
92+
await components.conversationSidebar().clickPreferencesButton();
93+
await pages.settings().devicesButton.click();
94+
95+
proteusId = (await pages.devices().proteusId.textContent()) ?? '';
96+
expect(proteusId).toBeTruthy();
97+
});
98+
99+
await test.step('Log out of public computer', async () => {
100+
await pages.settings().accountButton.click();
101+
await pages.account().clickLogoutButton();
102+
});
103+
104+
await test.step('Log in again on non public computer', async () => {
105+
await pageManager.openLoginPage();
106+
await pages.login().login(user);
107+
});
108+
109+
await test.step("Open device settings and ensure the public computer isn't active and the ID was re-generated", async () => {
110+
await components.conversationSidebar().clickPreferencesButton();
111+
await pages.settings().devicesButton.click();
112+
113+
await expect(pages.devices().activeDevices).toHaveCount(0);
114+
await expect(pages.devices().proteusId).not.toContainText(proteusId);
115+
});
116+
},
117+
);
118+
119+
test(
120+
'Verify sign in error appearance in case of wrong credentials',
121+
{tag: ['@TC-3465', '@regression']},
122+
async ({pageManager}) => {
123+
const {pages} = pageManager.webapp;
124+
await pageManager.openLoginPage();
125+
126+
await pages.login().login({email: 'invalid@wire.com', password: 'invalid'});
127+
128+
await expect(pages.login().loginErrorText).toHaveText('Please verify your details and try again');
129+
},
130+
);
131+
132+
[
133+
{tag: '@TC-3472', deviceType: 'permanent'},
134+
{tag: '@TC-3473', deviceType: 'temporary'},
135+
].forEach(({deviceType, tag}) => {
136+
test(
137+
`I want to keep my history after refreshing the page on ${deviceType} device`,
138+
{tag: [tag, '@regression']},
139+
async ({pageManager, createTeam}) => {
140+
const {pages} = pageManager.webapp;
141+
const team = await createTeam('Test Team', {withMembers: 1});
142+
const userA = team.owner;
143+
const userB = team.members[0];
144+
145+
await test.step('Log in and connect with user B', async () => {
146+
await pageManager.openLoginPage();
147+
148+
if (deviceType === 'temporary') {
149+
await pages.login().publicComputerCheckbox.click();
150+
await pages.login().login(userA);
151+
await pages.historyInfo().clickConfirmButton();
152+
} else {
153+
await pages.login().login(userA);
154+
}
155+
156+
await connectWithUser(pageManager, userB);
157+
});
158+
159+
await test.step('Send a message', async () => {
160+
await pages.conversationList().openConversation(userB.fullName);
161+
await pages.conversation().sendMessage('Before refresh');
162+
});
163+
164+
await test.step('Ensure message is still visible after page refresh', async () => {
165+
const message = pages.conversation().getMessage({content: 'Before refresh'});
166+
await expect(message).toBeVisible();
167+
168+
await pageManager.refreshPage();
169+
170+
await pages.conversationList().openConversation(userB.fullName);
171+
await expect(message).toBeVisible();
172+
});
173+
},
174+
);
175+
});
176+
177+
// Bug: Connecting using TLSv1.2 should not be allowed but succeeds
178+
test.skip(
179+
'I want to make sure i connect to webapp only through TLS >= 1.3 connection',
180+
{tag: ['@TC-3480', '@regression']},
181+
async () => {
182+
const requestWithTlsVersion = (versions: {min?: SecureVersion; max?: SecureVersion}) =>
183+
new Promise<IncomingMessage>((res, rej) => {
184+
https.get(webAppPath, {minVersion: versions.min, maxVersion: versions.max}, res).on('error', rej);
185+
});
186+
187+
await expect(requestWithTlsVersion({max: 'TLSv1.2'})).rejects.toBeDefined();
188+
await expect(requestWithTlsVersion({min: 'TLSv1.3'})).resolves.toBeDefined();
189+
},
190+
);
191+
192+
test(
193+
'Make sure user does not see data of user of previous sessions on same browser',
194+
{tag: ['@TC-1311', '@regression']},
195+
async ({pageManager, createTeam}) => {
196+
const {pages, components} = pageManager.webapp;
197+
const team = await createTeam('Test Team', {withMembers: 1});
198+
const userA = team.owner;
199+
const userB = team.members[0];
200+
201+
await test.step('Log in with public computer checked', async () => {
202+
await pageManager.openLoginPage();
203+
await pages.login().publicComputerCheckbox.click();
204+
await pages.login().login(userA);
205+
await pages.historyInfo().clickConfirmButton();
206+
});
207+
208+
await test.step('Connect with and send message to userB', async () => {
209+
await connectWithUser(pageManager, userB);
210+
await pages.conversationList().openConversation(userB.fullName);
211+
await pages.conversation().sendMessage('Test message');
212+
});
213+
214+
await test.step('Log out of public computer', async () => {
215+
await components.conversationSidebar().clickPreferencesButton();
216+
await pages.settings().accountButton.click();
217+
await pages.account().clickLogoutButton();
218+
});
219+
220+
await test.step('Log in again', async () => {
221+
await pageManager.openLoginPage();
222+
await pages.login().login(userA);
223+
await pages.historyInfo().clickConfirmButton();
224+
});
225+
226+
await test.step('Verify previously sent message is gone', async () => {
227+
await pages.conversationList().openConversation(userB.fullName);
228+
await expect(pages.conversation().getMessage({content: 'Test message'})).not.toBeAttached();
229+
});
230+
},
231+
);
232+
233+
test(
234+
'Verify session expired info is visible on login page',
235+
{tag: ['@TC-1311', '@regression']},
236+
async ({createPage, createUser}) => {
237+
const user = await createUser();
238+
const device1Pages = PageManager.from(await createPage(withLogin(user))).webapp.pages;
239+
240+
const {
241+
pages: device2Pages,
242+
modals: device2Modals,
243+
components: device2Components,
244+
} = PageManager.from(await createPage(withLogin(user))).webapp;
245+
await device2Pages.historyInfo().clickConfirmButton();
246+
247+
await device2Components.conversationSidebar().clickPreferencesButton();
248+
await device2Pages.settings().devicesButton.click();
249+
await device2Pages.devices().activeDevices.getByRole('button', {name: 'Remove Device'}).click();
250+
251+
await device2Modals.password().passwordInput.fill(user.password);
252+
await device2Modals.password().clickAction();
253+
254+
await expect(
255+
device1Pages.singleSignOn().page.getByText('You were signed out because your device was deleted'),
256+
).toBeVisible();
257+
},
258+
);
36259
});

0 commit comments

Comments
 (0)