Skip to content

Commit ea5776c

Browse files
ironAiken2claude
andcommitted
e2e(FR-2434): fix BAINameActionCell action locators and cleanup test stability
- Add clickRowAction helper to handle icon-only buttons in BAINameActionCell - Fix Edit button locator using icon aria-label instead of button name - Fix Deactivate action: remove incorrect popconfirm expectation (mutation fires directly) - Fix cleanup test: use dispatchEvent for checkbox click to handle table re-renders - Increase cleanup test timeout to 60s for reliability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e8391f1 commit ea5776c

File tree

4 files changed

+187
-154
lines changed

4 files changed

+187
-154
lines changed

e2e/E2E_COVERAGE_REPORT.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# E2E Test Coverage Report
22

3-
> **Last Updated:** 2026-03-31
3+
> **Last Updated:** 2026-04-01
44
> **Router Source:** [`react/src/routes.tsx`](../react/src/routes.tsx)
55
> **E2E Root:** [`e2e/`](.)
66
>
@@ -33,7 +33,7 @@
3333
| Configurations | `/settings` | 10 | 8 | 🔶 80% |
3434
| Resources | `/agent-summary`, `/agent` | 10 | 3 | 🔶 30% |
3535
| Resource Policy | `/resource-policy` | 13 | 10 | 🔶 77% |
36-
| User Credentials | `/credential` | 19 | 12 | 🔶 63% |
36+
| User Credentials | `/credential` | 20 | 13 | 🔶 65% |
3737
| Maintenance | `/maintenance` | 3 | 2 | 🔶 67% |
3838
| User Settings | `/usersettings` | 10 | 0 | ❌ 0% |
3939
| Project | `/project` | 6 | 5 | 🔶 83% |
@@ -584,7 +584,7 @@
584584

585585
### 17. User Credentials (`/credential`)
586586

587-
**Test files:** [`e2e/user/user-crud.spec.ts`](user/user-crud.spec.ts), [`e2e/user/bulk-user-creation.spec.ts`](user/bulk-user-creation.spec.ts), [`e2e/credential/credential-keypair.spec.ts`](credential/credential-keypair.spec.ts)
587+
**Test files:** [`e2e/user/user-crud.spec.ts`](user/user-crud.spec.ts), [`e2e/user/bulk-user-creation.spec.ts`](user/bulk-user-creation.spec.ts), [`e2e/credential/credential-keypair.spec.ts`](credential/credential-keypair.spec.ts), [`e2e/user-profile/user-ip-restriction-enforcement.spec.ts`](user-profile/user-ip-restriction-enforcement.spec.ts)
588588

589589
**Tabs:** Users | Credentials
590590

@@ -607,6 +607,7 @@
607607
| Reactivate user || `Admin can reactivate an inactive user` |
608608
| Purge user → PurgeUsersModal || `Admin can deactivate and permanently delete` |
609609
| Deleted user login blocked || `Deleted user cannot log in` |
610+
| Allowed IP restriction enforcement (active session) || `User can access pages when their current IP is in the allowed list` / `User is denied access after admin revokes their IP` |
610611
| User name click → UserInfoModal || - |
611612
| Bulk edit → UpdateUsersModal || - |
612613
| User table filtering || - |
@@ -627,7 +628,7 @@
627628
| Edit keypair → KeypairSettingModal || - |
628629
| SSH key management → SSHKeypairManagementModal || - |
629630

630-
**Coverage: 🔶 12/19 features**
631+
**Coverage: 🔶 13/20 features**
631632

632633
---
633634

e2e/user-profile/user-ip-restriction-enforcement.spec.ts

Lines changed: 26 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,22 @@ import {
88
loginAsCreatedAccount,
99
navigateTo,
1010
} from '../utils/test-util';
11-
import test, { expect, Page } from '@playwright/test';
11+
import {
12+
clickRowAction,
13+
openProfileModal,
14+
getCurrentClientIp,
15+
addIpTags,
16+
removeAllIpTags,
17+
deriveDifferentIp,
18+
} from '../utils/user-profile-util';
19+
import test, { expect } from '@playwright/test';
1220

1321
// Generate unique identifiers for this test run
1422
const TEST_RUN_ID = Date.now().toString(36);
1523
const EMAIL = `e2e-ip-restrict-${TEST_RUN_ID}@lablup.com`;
1624
const USERNAME = `e2e-ip-restrict-${TEST_RUN_ID}`;
1725
const PASSWORD = 'testing@123';
1826

19-
/**
20-
* Opens the User Profile Setting Modal by clicking the user dropdown
21-
* and selecting the "My Account" menu item.
22-
*/
23-
async function openProfileModal(page: Page) {
24-
await page.getByTestId('user-dropdown-button').click();
25-
await page.getByText('My Account').click();
26-
await page.locator('.ant-modal').waitFor({ state: 'visible' });
27-
}
28-
29-
/**
30-
* Reads the current client IP displayed in the modal's helper text.
31-
*/
32-
async function getCurrentClientIp(page: Page): Promise<string> {
33-
const ipText = await page.getByText(/Current client IP:/).textContent();
34-
const match = ipText?.match(/Current client IP:\s*(.+)/);
35-
return match?.[1]?.trim() ?? '';
36-
}
37-
38-
/**
39-
* Adds IP tags to the Allowed Client IP select field within the admin edit modal.
40-
*/
41-
async function addIpTagsInModal(
42-
modal: ReturnType<Page['locator']>,
43-
ips: string[],
44-
) {
45-
const formItem = modal
46-
.locator('.ant-form-item')
47-
.filter({ hasText: 'Allowed client IP' });
48-
const selectInput = formItem.getByRole('combobox');
49-
for (const ip of ips) {
50-
await selectInput.click();
51-
await selectInput.fill(ip);
52-
await selectInput.press('Enter');
53-
}
54-
await selectInput.press('Tab');
55-
}
56-
57-
/**
58-
* Removes all IP tags from the Allowed Client IP select field within a modal.
59-
*/
60-
async function removeAllIpTagsInModal(modal: ReturnType<Page['locator']>) {
61-
const formItem = modal
62-
.locator('.ant-form-item')
63-
.filter({ hasText: 'Allowed client IP' });
64-
const removeButtons = formItem.locator('.ant-tag .anticon-close');
65-
const count = await removeButtons.count();
66-
for (let i = count - 1; i >= 0; i--) {
67-
await removeButtons.nth(i).click();
68-
}
69-
}
70-
7127
test.describe.serial(
7228
'IP restriction enforcement during active session',
7329
{ tag: ['@functional', '@regression', '@user-profile'] },
@@ -133,15 +89,15 @@ test.describe.serial(
13389
// Find and edit the test user
13490
const userRow = adminPage.getByRole('row').filter({ hasText: EMAIL });
13591
await expect(userRow).toBeVisible();
136-
await userRow.getByRole('button', { name: 'Edit' }).click();
92+
await clickRowAction(adminPage, userRow, 'Edit');
13793

13894
// Wait for edit modal
13995
const editModal = UserSettingModal.forEdit(adminPage);
14096
await editModal.waitForVisible();
14197

14298
// Add the current client IP to allowed list
14399
const modal = editModal.getModal();
144-
await addIpTagsInModal(modal, [currentClientIp]);
100+
await addIpTags(modal, [currentClientIp]);
145101
await editModal.clickOk();
146102
await editModal.waitForHidden();
147103

@@ -190,15 +146,16 @@ test.describe.serial(
190146
// Find and edit the test user
191147
const userRow = adminPage.getByRole('row').filter({ hasText: EMAIL });
192148
await expect(userRow).toBeVisible();
193-
await userRow.getByRole('button', { name: 'Edit' }).click();
149+
await clickRowAction(adminPage, userRow, 'Edit');
194150

195151
const editModal = UserSettingModal.forEdit(adminPage);
196152
await editModal.waitForVisible();
197153

198154
// Remove existing IP tags and add an arbitrary IP
199155
const modal = editModal.getModal();
200-
await removeAllIpTagsInModal(modal);
201-
await addIpTagsInModal(modal, ['99.99.99.99']);
156+
await removeAllIpTags(modal);
157+
const arbitraryAllowedIp = deriveDifferentIp(currentClientIp);
158+
await addIpTags(modal, [arbitraryAllowedIp]);
202159
await editModal.clickOk();
203160
await editModal.waitForHidden();
204161

@@ -229,6 +186,7 @@ test.describe.serial(
229186
page,
230187
request,
231188
}) => {
189+
test.setTimeout(60000);
232190
// 1. Login as admin
233191
await loginAsAdmin(page, request);
234192

@@ -243,29 +201,31 @@ test.describe.serial(
243201
.catch(() => false);
244202

245203
if (isActive) {
246-
await userRow.getByRole('button', { name: 'Edit' }).click();
204+
await clickRowAction(page, userRow, 'Edit');
247205
const editModal = UserSettingModal.forEdit(page);
248206
await editModal.waitForVisible();
249207

250208
const modal = editModal.getModal();
251-
await removeAllIpTagsInModal(modal);
209+
await removeAllIpTags(modal);
252210
await editModal.clickOk();
253211
await editModal.waitForHidden();
254212

255-
// 4. Deactivate the user
213+
// 4. Deactivate the user (no popconfirm — mutation fires directly)
256214
await expect(userRow).toBeVisible();
257-
await userRow.getByRole('button', { name: 'Deactivate' }).click();
258-
const popconfirm = page.locator('.ant-popconfirm');
259-
await popconfirm.getByRole('button', { name: 'Deactivate' }).click();
215+
await clickRowAction(page, userRow, 'Deactivate');
260216
await expect(userRow).toBeHidden({ timeout: 10000 });
261217
}
262218

263219
// 5. Switch to Inactive and purge the user
220+
// Re-navigate to credential page to clear any stale state
221+
await navigateTo(page, 'credential');
222+
await expect(page.getByRole('tab', { name: 'Users' })).toBeVisible();
264223
await page.getByText('Inactive', { exact: true }).click();
265224
const inactiveUserRow = page.getByRole('row').filter({ hasText: EMAIL });
266-
await expect(inactiveUserRow).toBeVisible({ timeout: 5000 });
225+
await expect(inactiveUserRow).toBeVisible({ timeout: 10000 });
267226

268-
await inactiveUserRow.getByRole('checkbox').click();
227+
// Use dispatchEvent to bypass DOM detachment during table re-renders
228+
await inactiveUserRow.getByRole('checkbox').dispatchEvent('click');
269229
await page.getByRole('button', { name: 'trash bin' }).click();
270230

271231
const purgeModal = new PurgeUsersModal(page);

0 commit comments

Comments
 (0)