Skip to content

Commit 8be1305

Browse files
authored
[CHORE] [MER-0000] Stabilize Playwright login and menu interactions (#6584)
* Fixed the 4 user_account tests to be much more reliable * Avoid ambiguous sign-out locator * Resolve visible admin sign-out control
1 parent 02b962c commit 8be1305

3 files changed

Lines changed: 105 additions & 10 deletions

File tree

assets/automation/src/systems/torus/pom/home/LoginPO.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,51 @@ export class LoginPO {
4545
}
4646

4747
async fillEmail(email: string) {
48+
await this.waitForLoginForm();
4849
await this.emailInput.click();
4950
await this.emailInput.clear();
5051
await this.emailInput.fill(email);
52+
await Verifier.expectToHaveValue(this.emailInput, email);
5153
}
5254

5355
async fillPassword(password: string) {
56+
await this.waitForLoginForm();
5457
await this.passwordInput.click();
5558
await this.passwordInput.clear();
5659
await this.passwordInput.fill(password);
60+
await Verifier.expectToHaveValue(this.passwordInput, password);
61+
}
62+
63+
async signIn(email: string, password: string) {
64+
await this.waitForLoginForm();
65+
await this.emailInput.fill(email);
66+
await this.passwordInput.fill(password);
67+
await Verifier.expectToHaveValue(this.emailInput, email);
68+
await Verifier.expectToHaveValue(this.passwordInput, password);
69+
await this.clickSignInButton();
5770
}
5871

5972
async clickSignInButton() {
6073
await this.signInButton.click();
6174
}
6275

76+
private async waitForLoginForm() {
77+
await Waiter.waitForLoadState(this.page);
78+
79+
try {
80+
await Waiter.waitFor(this.page.locator('div.phx-connected').first(), 'attached');
81+
await Waiter.waitFor(this.page.locator('div.phx-loading').first(), 'detached');
82+
} catch {
83+
// Some login pages may render without a LiveView root; the form checks below are the source of truth.
84+
}
85+
86+
await Waiter.waitFor(this.emailInput, 'visible');
87+
await Waiter.waitFor(this.passwordInput, 'visible');
88+
await Verifier.expectIsEnabled(this.emailInput);
89+
await Verifier.expectIsEnabled(this.passwordInput);
90+
await Verifier.expectIsEnabled(this.signInButton);
91+
}
92+
6393
async selectRoleAccount(role: TypeUser) {
6494
const navbarco = new NavbarCO(this.page);
6595

assets/automation/src/systems/torus/pom/home/MenuDropdownCO.ts

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,44 @@ export class MenuDropdownCO {
66
private readonly menuButtonAdmin: Locator;
77
private readonly workspaceMenu: Locator;
88
private readonly adminPanelLink: Locator;
9-
private readonly signOutLink: Locator;
9+
private readonly workspaceSignOutLink: Locator;
10+
private readonly page: Page;
1011

1112
constructor(page: Page) {
13+
this.page = page;
1214
this.menuButton = page.locator('#workspace-user-menu');
1315
this.menuButtonAdmin = page.getByRole('button', { name: 'Playwright Admin profile' });
1416
this.workspaceMenu = page.locator('#workspace-user-menu-dropdown');
1517
this.adminPanelLink = this.workspaceMenu.getByRole('link', { name: 'Admin Panel' });
16-
this.signOutLink = page.getByRole('link', { name: 'Sign out' });
18+
this.workspaceSignOutLink = this.workspaceMenu.getByRole('link', { name: 'Sign out' });
1719
}
1820

1921
async open(isAdminScreen = false) {
2022
if (isAdminScreen) {
2123
await this.menuButtonAdmin.click();
24+
await this.waitForAdminSignOutControl();
2225
} else {
2326
await this.menuButton.click();
27+
await Waiter.waitFor(this.workspaceMenu, 'visible');
2428
}
2529
}
2630

2731
async goToAdminPanel() {
28-
await this.adminPanelLink.click();
32+
await Waiter.waitFor(this.adminPanelLink, 'visible');
33+
await this.adminPanelLink.click({ force: true });
2934
}
3035

3136
async signOut(isAdminScreen = false) {
3237
const menuButton = isAdminScreen ? this.menuButtonAdmin : this.menuButton;
3338

3439
// Try to open the dropdown (two attempts in case of stale click)
35-
for (let i = 0; i < 2; i++) {
40+
for (let i = 0; i < 2 && !(await this.isSignOutMenuVisible(isAdminScreen)); i++) {
3641
await menuButton.click();
37-
const visible = await this.workspaceMenu.waitFor({ state: 'visible', timeout: 1000 }).catch(() => false);
42+
const visible = await this.waitForSignOutMenu(isAdminScreen);
3843
if (visible) break;
3944
}
4045

41-
const link = this.workspaceMenu.getByRole('link', { name: 'Sign out' });
46+
const link = isAdminScreen ? await this.getAdminSignOutControl() : this.workspaceSignOutLink;
4247

4348
// Preferred path: click the visible sign-out link
4449
const clicked = await link
@@ -49,8 +54,70 @@ export class MenuDropdownCO {
4954
if (clicked) return;
5055

5156
// Fallback: clear session cookies and reload
52-
const page = this.signOutLink.page();
57+
const page = this.menuButton.page();
5358
await page.context().clearCookies();
5459
await page.goto('/');
5560
}
61+
62+
private async isSignOutMenuVisible(isAdminScreen: boolean) {
63+
if (isAdminScreen) {
64+
return (await this.findVisibleAdminSignOutControl()) !== undefined;
65+
}
66+
67+
return await this.workspaceMenu.isVisible();
68+
}
69+
70+
private async waitForSignOutMenu(isAdminScreen: boolean) {
71+
if (isAdminScreen) {
72+
return await this.waitForAdminSignOutControl()
73+
.then(() => true)
74+
.catch(() => false);
75+
}
76+
77+
return await this.workspaceMenu
78+
.waitFor({ state: 'visible', timeout: 1000 })
79+
.then(() => true)
80+
.catch(() => false);
81+
}
82+
83+
private async waitForAdminSignOutControl() {
84+
const deadline = Date.now() + 1000;
85+
86+
while (Date.now() < deadline) {
87+
const control = await this.findVisibleAdminSignOutControl();
88+
89+
if (control) {
90+
return control;
91+
}
92+
93+
await this.page.waitForTimeout(100);
94+
}
95+
96+
throw new Error('Admin sign-out control was not visible');
97+
}
98+
99+
private async getAdminSignOutControl() {
100+
const visibleControl = await this.findVisibleAdminSignOutControl();
101+
102+
return visibleControl ?? (await this.waitForAdminSignOutControl());
103+
}
104+
105+
private async findVisibleAdminSignOutControl() {
106+
for (const controls of [
107+
this.page.getByRole('button', { name: 'Sign out' }),
108+
this.page.getByRole('link', { name: 'Sign out' }),
109+
]) {
110+
const count = await controls.count();
111+
112+
for (let i = 0; i < count; i++) {
113+
const control = controls.nth(i);
114+
115+
if (await control.isVisible().catch(() => false)) {
116+
return control;
117+
}
118+
}
119+
}
120+
121+
return undefined;
122+
}
56123
}

assets/automation/src/systems/torus/tasks/HomeTask.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ export class HomeTask {
4545
await this.loginpo.verifyTitle(dataUser.pageTitle);
4646
await this.loginpo.verifyRole(dataUser.role);
4747
await this.loginpo.verifyWelcomeText(dataUser.welcomeText);
48-
await this.loginpo.fillEmail(dataUser.email);
49-
await this.loginpo.fillPassword(dataUser.pass);
50-
await this.loginpo.clickSignInButton();
48+
await this.loginpo.signIn(dataUser.email, dataUser.pass);
5149
await this.loginpo.verifyWelcomeTitle(dataUser.welcomeTitle);
5250

5351
if (role === 'administrator') {

0 commit comments

Comments
 (0)