@@ -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
1422const TEST_RUN_ID = Date . now ( ) . toString ( 36 ) ;
1523const EMAIL = `e2e-ip-restrict-${ TEST_RUN_ID } @lablup.com` ;
1624const USERNAME = `e2e-ip-restrict-${ TEST_RUN_ID } ` ;
1725const 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 ( / C u r r e n t c l i e n t I P : / ) . textContent ( ) ;
34- const match = ipText ?. match ( / C u r r e n t c l i e n t I P : \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-
7127test . 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