From f2e5a0a34817da3ef479a0fc05ebf00959db2459 Mon Sep 17 00:00:00 2001 From: John Kim Date: Thu, 10 Oct 2024 15:05:31 -0400 Subject: [PATCH 1/3] structure --- .../src/e2e/tests/userManagement.spec.ts | 438 +++++++++--------- 1 file changed, 218 insertions(+), 220 deletions(-) diff --git a/webui/react/src/e2e/tests/userManagement.spec.ts b/webui/react/src/e2e/tests/userManagement.spec.ts index 674434ae035..98e82c6dafd 100644 --- a/webui/react/src/e2e/tests/userManagement.spec.ts +++ b/webui/react/src/e2e/tests/userManagement.spec.ts @@ -30,259 +30,257 @@ test.describe('User Management', () => { } }); - test.describe('With User Teardown', () => { - test.afterAll(async ({ backgroundApiUser }) => { - await test.step('Deactivate Users', async () => { - for (const [id] of testUsers) { - await backgroundApiUser.patchUser(id, { active: false }); - } + test.afterAll(async ({ backgroundApiUser }) => { + await test.step('Deactivate Users', async () => { + for (const [id] of testUsers) { + await backgroundApiUser.patchUser(id, { active: false }); + } + }); + }); + + test.describe('User Management UI CRUD', () => { + let testUser: V1PostUserRequest; + + test.beforeEach(async ({ user }) => { + await test.step('Create User', async () => { + testUser = await user.createUser(); + saveTestUser(testUser, testUsers); }); }); - test.describe('With a User for Each Test', () => { - let testUser: V1PostUserRequest; + test('User Table Read', async ({ user }) => { + await user.validateUser(testUser); + }); - test.beforeEach(async ({ user }) => { - await test.step('Create User', async () => { - testUser = await user.createUser(); - saveTestUser(testUser, testUsers); - }); + test('New User Access', async ({ page, auth }) => { + const userManagementPage = new UserManagement(page); + await auth.logout(); + await auth.login({ password: testUser.password, username: testUser.user?.username }); + await userManagementPage.nav.sidebar.headerDropdown.open(); + await userManagementPage.nav.sidebar.headerDropdown.settings.pwLocator.waitFor(); + await userManagementPage.nav.sidebar.headerDropdown.admin.pwLocator.waitFor({ + state: 'hidden', }); + }); - test('User Table Read', async ({ user }) => { + test('Edit User', async ({ user }) => { + await test.step('Edit Once', async () => { + if (testUser.user === undefined) { + throw new Error('Trying to edit an undefined user.'); + } + testUser = await user.editUser(testUser, { + displayName: testUser.user.username + '_edited', + }); + await user.validateUser(testUser); + }); + await test.step('Edit Again', async () => { + testUser = await user.editUser(testUser, { admin: true, displayName: '' }); await user.validateUser(testUser); }); + }); - test('New User Access', async ({ page, auth }) => { - const userManagementPage = new UserManagement(page); + test('Deactivate and Reactivate', async ({ page, user, auth, newAdmin }) => { + const userManagementPage = new UserManagement(page); + const signInPage = new SignIn(page); + await test.step('Deactivate', async () => { + testUser = await user.changeStatusUser(testUser, false); + saveTestUser(testUser, testUsers); + await user.validateUser(testUser); + }); + await test.step('Attempt Sign In With Deactivated User', async () => { await auth.logout(); - await auth.login({ password: testUser.password, username: testUser.user?.username }); - await userManagementPage.nav.sidebar.headerDropdown.open(); - await userManagementPage.nav.sidebar.headerDropdown.settings.pwLocator.waitFor(); - await userManagementPage.nav.sidebar.headerDropdown.admin.pwLocator.waitFor({ - state: 'hidden', + await auth.login({ + expectedURL: /login/, + password: testUser.password, + username: testUser.user?.username, }); + await expect(page).toHaveDeterminedTitle(signInPage.title); + await expect(page).toHaveURL(/login/); + await expect(signInPage.detAuth.errors.pwLocator).toBeVisible(); + await expect(signInPage.detAuth.errors.alert.pwLocator).toBeVisible(); + expect(await signInPage.detAuth.errors.message.pwLocator.textContent()).toContain( + 'Login failed', + ); + expect(await signInPage.detAuth.errors.description.pwLocator.textContent()).toContain( + 'user is not active', + ); }); - - test('Edit User', async ({ user }) => { - await test.step('Edit Once', async () => { - if (testUser.user === undefined) { - throw new Error('Trying to edit an undefined user.'); - } - testUser = await user.editUser(testUser, { - displayName: testUser.user.username + '_edited', - }); - await user.validateUser(testUser); - }); - await test.step('Edit Again', async () => { - testUser = await user.editUser(testUser, { admin: true, displayName: '' }); - await user.validateUser(testUser); + await test.step('Reactivate', async () => { + await userManagementPage.goto({ verify: false }); + // TODO the verify false on the line above isn't working as expected + // if we don't expect this url, the automation runs too fast and login + // thinks we've already logged in, skipping the login automation. + // We might need to find a way to be more explicit about the page state. + await expect(page).toHaveURL(/login/); + await auth.login({ + expectedURL: userManagementPage.url, + password: newAdmin.request.password, + username: newAdmin.response.user?.username, }); + testUser = await user.changeStatusUser(testUser, true); + saveTestUser(testUser, testUsers); + await user.validateUser(testUser); + }); + await test.step('Successful Sign In', async () => { + test.slow(); + await auth.logout(); + await auth.login({ password: testUser.password, username: testUser.user?.username }); }); + }); + }); - test('Deactivate and Reactivate', async ({ page, user, auth, newAdmin }) => { - const userManagementPage = new UserManagement(page); - const signInPage = new SignIn(page); - await test.step('Deactivate', async () => { - testUser = await user.changeStatusUser(testUser, false); - saveTestUser(testUser, testUsers); - await user.validateUser(testUser); - }); - await test.step('Attempt Sign In With Deactivated User', async () => { - await auth.logout(); - await auth.login({ - expectedURL: /login/, - password: testUser.password, - username: testUser.user?.username, - }); - await expect(page).toHaveDeterminedTitle(signInPage.title); - await expect(page).toHaveURL(/login/); - await expect(signInPage.detAuth.errors.pwLocator).toBeVisible(); - await expect(signInPage.detAuth.errors.alert.pwLocator).toBeVisible(); - expect(await signInPage.detAuth.errors.message.pwLocator.textContent()).toContain( - 'Login failed', + test.describe('User Management List', () => { + const usernamePrefix = 'test-user-pagination'; + test.beforeAll(async ({ backgroundApiUser }) => { + await test.step('Create User', async () => { + // pagination will be 10 per page, so create 11 users + for (let i = 0; i < 11; i++) { + const user = await backgroundApiUser.createUser( + backgroundApiUser.new({ usernamePrefix }), ); - expect(await signInPage.detAuth.errors.description.pwLocator.textContent()).toContain( - 'user is not active', - ); - }); - await test.step('Reactivate', async () => { - await userManagementPage.goto({ verify: false }); - // TODO the verify false on the line above isn't working as expected - // if we don't expect this url, the automation runs too fast and login - // thinks we've already logged in, skipping the login automation. - // We might need to find a way to be more explicit about the page state. - await expect(page).toHaveURL(/login/); - await auth.login({ - expectedURL: userManagementPage.url, - password: newAdmin.request.password, - username: newAdmin.response.user?.username, - }); - testUser = await user.changeStatusUser(testUser, true); - saveTestUser(testUser, testUsers); - await user.validateUser(testUser); - }); - await test.step('Successful Sign In', async () => { - test.slow(); - await auth.logout(); - await auth.login({ password: testUser.password, username: testUser.user?.username }); - }); + saveTestUser(user, testUsers); + } }); }); - test.describe('With 10 Users', () => { - const usernamePrefix = 'test-user-pagination'; - test.beforeAll(async ({ backgroundApiUser }) => { - await test.step('Create User', async () => { - // pagination will be 10 per page, so create 11 users - for (let i = 0; i < 11; i++) { - const user = await backgroundApiUser.createUser( - backgroundApiUser.new({ usernamePrefix }), - ); - saveTestUser(user, testUsers); + test('[ET-233] Bulk Actions', async ({ page, user, playwright }) => { + const userManagementPage = new UserManagement(page); + + await test.step('Setup Table Filters', async () => { + // set pagination to 10 + await expect( + repeatWithFallback( + async () => { + await userManagementPage.table.table.pagination.perPage.openMenu(); + await userManagementPage.table.table.pagination.perPage.perPage10.pwLocator.click(); + }, + async () => { + // BUG [ET-233] + await userManagementPage.goto(); + }, + ), + ).toPass({ timeout: 15_000 }); + // filter by active users + await userManagementPage.filterStatus.openMenu(); + await userManagementPage.filterStatus.activeUsers.pwLocator.click(); + await expect(async () => { + expect( + await userManagementPage.table.table.filterRows(async (row) => { + return (await row.status.pwLocator.textContent()) === 'Active'; + }), + ).toHaveLength(10); + }).toPass({ timeout: 10_000 }); + // search for users created this session and wait for table stable + await userManagementPage.search.pwLocator.fill(usernamePrefix + sessionRandomHash); + await expect(async () => { + expect( + await userManagementPage.table.table.filterRows(async (row) => { + return (await row.user.name.pwLocator.textContent())?.indexOf(usernamePrefix) === 0; + }), + ).toHaveLength(10); + }).toPass({ timeout: 10_000 }); + // go to page 2 to see users + await expect(async () => { + // BUG [ET-240] + await userManagementPage.table.table.pagination.pageButtonLocator(2).click(); + await expect(userManagementPage.table.table.pagination.pageButtonLocator(2)).toHaveClass( + /ant-pagination-item-active/, + ); + await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount(1, { + timeout: 2_000, + }); + }).toPass({ timeout: 10_000 }); + }); + await test.step("Deactivate All Users on the Table's Page (1 User)", async () => { + await userManagementPage.actions.pwLocator.waitFor({ state: 'hidden' }); + await user.deactivateTestUsersOnTable(testUsers); + }); + await test.step('Check That the 1 User is Disabled', async () => { + // wait for table to be stable and check that pagination and "no data" both dont show + await userManagementPage.table.table.pwLocator.click({ trial: true }); + try { + await userManagementPage.table.table.noData.pwLocator.waitFor(); + await userManagementPage.table.table.pagination.pwLocator.waitFor(); + // if we see these elements, we should fail the test + // sometimes BUG [ET-240] makes this test pass unexpectedly + test.fail(); + throw new Error('Expected table to have data and no pagination'); + } catch (error) { + // if we see a timeout error, that means we don't see "no data" + if (!(error instanceof playwright.errors.TimeoutError)) { + // if we see any other error, we should still fail the test + throw error; } - }); + } + // Expect to see rows from page 1 + await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount(10); }); + }); - test('[ET-233] Bulk Actions', async ({ page, user, playwright }) => { - const userManagementPage = new UserManagement(page); + test('Users Table Row Count matches Users Tab Value', async ({ page }) => { + const userManagementPage = new UserManagement(page); + const getExpectedRowCount = async (): Promise => { + const match = (await userManagementPage.userTab.pwLocator.innerText()).match( + /Users \((\d+)\)/, + ); + if (match === null) { + throw new Error('Number not present in tab.'); + } + return Number(match[1]); + }; - await test.step('Setup Table Filters', async () => { - // set pagination to 10 + const pagination = userManagementPage.table.table.pagination; + for await (const { name, paginationOption } of [ + { + name: '10', + paginationOption: pagination.perPage.perPage10, + }, + { + name: '20', + paginationOption: pagination.perPage.perPage20, + }, + { + name: '50', + paginationOption: pagination.perPage.perPage50, + }, + { + name: '100', + paginationOption: pagination.perPage.perPage100, + }, + ]) { + await test.step(`Compare Table Rows With Pagination ${name}`, async () => { await expect( repeatWithFallback( async () => { - await userManagementPage.table.table.pagination.perPage.openMenu(); - await userManagementPage.table.table.pagination.perPage.perPage10.pwLocator.click(); + await pagination.perPage.openMenu(); + await paginationOption.pwLocator.click(); }, async () => { // BUG [ET-233] await userManagementPage.goto(); }, ), - ).toPass({ timeout: 15_000 }); - // filter by active users - await userManagementPage.filterStatus.openMenu(); - await userManagementPage.filterStatus.activeUsers.pwLocator.click(); - await expect(async () => { - expect( - await userManagementPage.table.table.filterRows(async (row) => { - return (await row.status.pwLocator.textContent()) === 'Active'; - }), - ).toHaveLength(10); - }).toPass({ timeout: 10_000 }); - // search for users created this session and wait for table stable - await userManagementPage.search.pwLocator.fill(usernamePrefix + sessionRandomHash); - await expect(async () => { - expect( - await userManagementPage.table.table.filterRows(async (row) => { - return (await row.user.name.pwLocator.textContent())?.indexOf(usernamePrefix) === 0; - }), - ).toHaveLength(10); - }).toPass({ timeout: 10_000 }); - // go to page 2 to see users - await expect(async () => { - // BUG [ET-240] - await userManagementPage.table.table.pagination.pageButtonLocator(2).click(); - await expect( - userManagementPage.table.table.pagination.pageButtonLocator(2), - ).toHaveClass(/ant-pagination-item-active/); - await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount(1, { - timeout: 2_000, - }); - }).toPass({ timeout: 10_000 }); - }); - await test.step("Deactivate All Users on the Table's Page (1 User)", async () => { - await userManagementPage.actions.pwLocator.waitFor({ state: 'hidden' }); - await user.deactivateTestUsersOnTable(testUsers); - }); - await test.step('Check That the 1 User is Disabled', async () => { - // wait for table to be stable and check that pagination and "no data" both dont show - await userManagementPage.table.table.pwLocator.click({ trial: true }); - try { - await userManagementPage.table.table.noData.pwLocator.waitFor(); - await userManagementPage.table.table.pagination.pwLocator.waitFor(); - // if we see these elements, we should fail the test - // sometimes BUG [ET-240] makes this test pass unexpectedly - test.fail(); - throw new Error('Expected table to have data and no pagination'); - } catch (error) { - // if we see a timeout error, that means we don't see "no data" - if (!(error instanceof playwright.errors.TimeoutError)) { - // if we see any other error, we should still fail the test - throw error; - } - } - // Expect to see rows from page 1 - await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount(10); + ).toPass({ timeout: 25_000 }); + await expect(userManagementPage.skeletonTable.pwLocator).not.toBeVisible(); + const paginationSelection = Number(name); + await expect( + repeatWithFallback( + async () => { + // grab the count of the table rows and big number at the top at the same time + // in case the table refreshes with more users during a parallel run + await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount( + Math.min(paginationSelection, await getExpectedRowCount()), + ); + }, + async () => { + // if the above doesn't pass, refresh the page and try again. This is to handle + // the case where the table refreshes with more users, but the other number hasn't refreshed yet + await userManagementPage.goto(); + }, + ), + ).toPass({ timeout: 20_000 }); }); - }); - - test('Users Table Row Count matches Users Tab Value', async ({ page }) => { - const userManagementPage = new UserManagement(page); - const getExpectedRowCount = async (): Promise => { - const match = (await userManagementPage.userTab.pwLocator.innerText()).match( - /Users \((\d+)\)/, - ); - if (match === null) { - throw new Error('Number not present in tab.'); - } - return Number(match[1]); - }; - - const pagination = userManagementPage.table.table.pagination; - for await (const { name, paginationOption } of [ - { - name: '10', - paginationOption: pagination.perPage.perPage10, - }, - { - name: '20', - paginationOption: pagination.perPage.perPage20, - }, - { - name: '50', - paginationOption: pagination.perPage.perPage50, - }, - { - name: '100', - paginationOption: pagination.perPage.perPage100, - }, - ]) { - await test.step(`Compare Table Rows With Pagination ${name}`, async () => { - await expect( - repeatWithFallback( - async () => { - await pagination.perPage.openMenu(); - await paginationOption.pwLocator.click(); - }, - async () => { - // BUG [ET-233] - await userManagementPage.goto(); - }, - ), - ).toPass({ timeout: 25_000 }); - await expect(userManagementPage.skeletonTable.pwLocator).not.toBeVisible(); - const paginationSelection = Number(name); - await expect( - repeatWithFallback( - async () => { - // grab the count of the table rows and big number at the top at the same time - // in case the table refreshes with more users during a parallel run - await expect(userManagementPage.table.table.rows.pwLocator).toHaveCount( - Math.min(paginationSelection, await getExpectedRowCount()), - ); - }, - async () => { - // if the above doesn't pass, refresh the page and try again. This is to handle - // the case where the table refreshes with more users, but the other number hasn't refreshed yet - await userManagementPage.goto(); - }, - ), - ).toPass({ timeout: 20_000 }); - }); - } - }); + } }); }); }); From 680f24fb7c4a685ae6ae85d5148df0543f45fe97 Mon Sep 17 00:00:00 2001 From: John Kim Date: Fri, 11 Oct 2024 17:46:26 -0400 Subject: [PATCH 2/3] update test user handling --- webui/react/src/e2e/fixtures/user.fixture.ts | 41 +++---------------- .../src/e2e/tests/userManagement.spec.ts | 40 +++++++++--------- webui/react/src/e2e/utils/users.ts | 7 +--- 3 files changed, 27 insertions(+), 61 deletions(-) diff --git a/webui/react/src/e2e/fixtures/user.fixture.ts b/webui/react/src/e2e/fixtures/user.fixture.ts index 3e2697f0bb7..6c7960913f9 100644 --- a/webui/react/src/e2e/fixtures/user.fixture.ts +++ b/webui/react/src/e2e/fixtures/user.fixture.ts @@ -130,21 +130,8 @@ export class UserFixture { await this.singleUserSearchAndEdit(user); await expect(this.userManagementPage.createUserModal.username.pwLocator).toBeDisabled(); expect( - await this.userManagementPage.createUserModal.displayName.pwLocator.getAttribute('value'), - ).toEqual(user.user.displayName || ''); - const checkedAttribute = - await this.userManagementPage.createUserModal.adminToggle.pwLocator.getAttribute( - 'aria-checked', - ); - if (checkedAttribute === null) { - throw new Error('Expected attribute aria-checked to be present.'); - } - const adminState = JSON.parse(checkedAttribute); - if (user.user.admin) { - expect(adminState).toBeTruthy(); - } else { - expect(adminState).not.toBeTruthy(); - } + await this.userManagementPage.createUserModal.username.pwLocator.getAttribute('value'), + ).toEqual(user.user.username || ''); await expect( repeatWithFallback( async () => await this.fillUserForm(edit), @@ -160,13 +147,9 @@ export class UserFixture { if (editedUser.user === undefined) { throw new Error('Result from edit user is Undefined.'); } - editedUser.password = edit.password; - if (edit.admin !== undefined) { - editedUser.user.admin = edit.admin; - } - if (edit.displayName !== undefined) { - editedUser.user.displayName = edit.displayName; - } + if (edit.password !== undefined) editedUser.password = edit.password; + if (edit.admin !== undefined) editedUser.user.admin = edit.admin; + if (edit.displayName !== undefined) editedUser.user.displayName = edit.displayName; return editedUser; } @@ -213,9 +196,7 @@ export class UserFixture { /** * Deactivates all users present on the table. */ - async deactivateTestUsersOnTable(users: Map): Promise { - // get all user ids so we can update the status later - const ids = (await this.userManagementPage.table.table.allRowKeys()).map((id) => parseInt(id)); + async deactivateTestUsersOnTable(): Promise { // select all users await this.userManagementPage.table.table.headRow.selectAll.pwLocator.click(); await expect(this.userManagementPage.table.table.headRow.selectAll.pwLocator).toBeChecked(); @@ -226,16 +207,6 @@ export class UserFixture { await this.userManagementPage.changeUserStatusModal.status.openMenu(); await this.userManagementPage.changeUserStatusModal.status.deactivate.pwLocator.click(); await this.userManagementPage.changeUserStatusModal.footer.submit.pwLocator.click(); - for (const id of ids) { - const user = users.get(id); - if (user?.user === undefined) { - throw new Error( - `Expected user with id ${id} present on the table to have been created during this session`, - ); - } - user.user.active = false; - users.set(id, user); - } } /** diff --git a/webui/react/src/e2e/tests/userManagement.spec.ts b/webui/react/src/e2e/tests/userManagement.spec.ts index 98e82c6dafd..04be6879315 100644 --- a/webui/react/src/e2e/tests/userManagement.spec.ts +++ b/webui/react/src/e2e/tests/userManagement.spec.ts @@ -3,7 +3,7 @@ import { UserManagement } from 'e2e/models/pages/Admin/UserManagement'; import { SignIn } from 'e2e/models/pages/SignIn'; import { sessionRandomHash } from 'e2e/utils/naming'; import { repeatWithFallback } from 'e2e/utils/polling'; -import { saveTestUser } from 'e2e/utils/users'; +import { saveTestUserId } from 'e2e/utils/users'; import { V1PostUserRequest } from 'services/api-ts-sdk/api'; test.describe('User Management', () => { @@ -11,7 +11,7 @@ test.describe('User Management', () => { // call of the user fixture to deactivate all users created by each test. // Note: This can't collide when running tests in parallel because playwright // workers can't share variables. - const testUsers = new Map(); + const testUserIds: number[] = []; test.beforeEach(async ({ authedPage }) => { const userManagementPage = new UserManagement(authedPage); await userManagementPage.goto(); @@ -32,7 +32,7 @@ test.describe('User Management', () => { test.afterAll(async ({ backgroundApiUser }) => { await test.step('Deactivate Users', async () => { - for (const [id] of testUsers) { + for (const id of testUserIds) { await backgroundApiUser.patchUser(id, { active: false }); } }); @@ -42,14 +42,14 @@ test.describe('User Management', () => { let testUser: V1PostUserRequest; test.beforeEach(async ({ user }) => { - await test.step('Create User', async () => { - testUser = await user.createUser(); - saveTestUser(testUser, testUsers); - }); - }); - - test('User Table Read', async ({ user }) => { - await user.validateUser(testUser); + // share one user created via UI across tests: + if (!testUser) { + await test.step('Create User', async () => { + testUser = await user.createUser(); + saveTestUserId(testUser, testUserIds); + await user.validateUser(testUser); + }); + } }); test('New User Access', async ({ page, auth }) => { @@ -64,7 +64,7 @@ test.describe('User Management', () => { }); test('Edit User', async ({ user }) => { - await test.step('Edit Once', async () => { + await test.step('Edit display name', async () => { if (testUser.user === undefined) { throw new Error('Trying to edit an undefined user.'); } @@ -73,21 +73,20 @@ test.describe('User Management', () => { }); await user.validateUser(testUser); }); - await test.step('Edit Again', async () => { - testUser = await user.editUser(testUser, { admin: true, displayName: '' }); + await test.step('Revert display name edit', async () => { + testUser = await user.editUser(testUser, { displayName: '' }); await user.validateUser(testUser); }); }); - test('Deactivate and Reactivate', async ({ page, user, auth, newAdmin }) => { + test('Deactivate and Reactivate User', async ({ page, user, auth, newAdmin }) => { const userManagementPage = new UserManagement(page); const signInPage = new SignIn(page); await test.step('Deactivate', async () => { testUser = await user.changeStatusUser(testUser, false); - saveTestUser(testUser, testUsers); await user.validateUser(testUser); }); - await test.step('Attempt Sign In With Deactivated User', async () => { + await test.step('Cannot sign in with deactivated user', async () => { await auth.logout(); await auth.login({ expectedURL: /login/, @@ -118,10 +117,9 @@ test.describe('User Management', () => { username: newAdmin.response.user?.username, }); testUser = await user.changeStatusUser(testUser, true); - saveTestUser(testUser, testUsers); await user.validateUser(testUser); }); - await test.step('Successful Sign In', async () => { + await test.step('Successful sign in with reactivated user', async () => { test.slow(); await auth.logout(); await auth.login({ password: testUser.password, username: testUser.user?.username }); @@ -138,7 +136,7 @@ test.describe('User Management', () => { const user = await backgroundApiUser.createUser( backgroundApiUser.new({ usernamePrefix }), ); - saveTestUser(user, testUsers); + saveTestUserId(user, testUserIds); } }); }); @@ -193,7 +191,7 @@ test.describe('User Management', () => { }); await test.step("Deactivate All Users on the Table's Page (1 User)", async () => { await userManagementPage.actions.pwLocator.waitFor({ state: 'hidden' }); - await user.deactivateTestUsersOnTable(testUsers); + await user.deactivateTestUsersOnTable(); }); await test.step('Check That the 1 User is Disabled', async () => { // wait for table to be stable and check that pagination and "no data" both dont show diff --git a/webui/react/src/e2e/utils/users.ts b/webui/react/src/e2e/utils/users.ts index 32a2b90b0b1..d5ae0121b29 100644 --- a/webui/react/src/e2e/utils/users.ts +++ b/webui/react/src/e2e/utils/users.ts @@ -1,11 +1,8 @@ import { V1PostUserRequest } from 'services/api-ts-sdk/api'; -export const saveTestUser = ( - user: V1PostUserRequest, - users: Map, -): void => { +export const saveTestUserId = (user: V1PostUserRequest, userIds: number[]): void => { if (user.user?.id === undefined) { throw new Error('User has an object but has no data.'); } - users.set(user.user.id, user); + userIds.push(user.user.id); }; From 67b38d65e95d195b98d5e173fd4ecea0a614a8a5 Mon Sep 17 00:00:00 2001 From: John Kim Date: Mon, 14 Oct 2024 14:26:31 -0400 Subject: [PATCH 3/3] types --- webui/react/src/e2e/fixtures/user.fixture.ts | 107 ++++++++---------- .../src/e2e/tests/userManagement.spec.ts | 26 ++--- webui/react/src/e2e/utils/users.ts | 13 +-- 3 files changed, 66 insertions(+), 80 deletions(-) diff --git a/webui/react/src/e2e/fixtures/user.fixture.ts b/webui/react/src/e2e/fixtures/user.fixture.ts index 6c7960913f9..a941c5013aa 100644 --- a/webui/react/src/e2e/fixtures/user.fixture.ts +++ b/webui/react/src/e2e/fixtures/user.fixture.ts @@ -1,19 +1,20 @@ import { Page } from '@playwright/test'; -import _ from 'lodash'; import { expect } from 'e2e/fixtures/global-fixtures'; import { UserManagement } from 'e2e/models/pages/Admin/UserManagement'; import { safeName } from 'e2e/utils/naming'; import { repeatWithFallback } from 'e2e/utils/polling'; -import { V1PostUserRequest } from 'services/api-ts-sdk/api'; +import { TestUser } from 'e2e/utils/users'; -interface UserArgs { +interface CreateUserFields { username?: string; displayName?: string; admin?: boolean; password?: string; } +type EditUserFields = Omit; + export class UserFixture { readonly userManagementPage: UserManagement; @@ -23,19 +24,23 @@ export class UserFixture { /** * Fills the create/edit user form and submits it. - * @param {V1PostUserRequest} edit user settings to set user too. + * @param {CreateUserFields} formValues values to use in the create/edit user form */ - async fillUserForm(edit: UserArgs): Promise { + async fillUserForm(formValues: CreateUserFields): Promise { await this.userManagementPage._page.waitForTimeout(500); // ant/Popover - menus may reset input shortly after opening [ET-283] - if (edit.username !== undefined) { - await this.userManagementPage.createUserModal.username.pwLocator.fill(edit.username); + if (formValues.username !== undefined) { + await this.userManagementPage.createUserModal.username.pwLocator.fill(formValues.username); } - if (edit.displayName !== undefined) { - await this.userManagementPage.createUserModal.displayName.pwLocator.fill(edit.displayName); + if (formValues.displayName !== undefined) { + await this.userManagementPage.createUserModal.displayName.pwLocator.fill( + formValues.displayName, + ); } - if (edit.password !== undefined) { - await this.userManagementPage.createUserModal.password.pwLocator.fill(edit.password); - await this.userManagementPage.createUserModal.confirmPassword.pwLocator.fill(edit.password); + if (formValues.password !== undefined) { + await this.userManagementPage.createUserModal.password.pwLocator.fill(formValues.password); + await this.userManagementPage.createUserModal.confirmPassword.pwLocator.fill( + formValues.password, + ); } const checkedAttribute = @@ -46,7 +51,7 @@ export class UserFixture { throw new Error('Expected attribute aria-checked to be present.'); } const adminState = JSON.parse(checkedAttribute); - if (!!edit.admin !== adminState) { + if (!!formValues.admin !== adminState) { await this.userManagementPage.createUserModal.adminToggle.pwLocator.click(); } @@ -59,19 +64,19 @@ export class UserFixture { /** * Creates a user with the given parameters via the UI. - * @param {UserArgs} obj + * @param {CreateUserFields} obj * @param {string} [obj.username] - The username to create * @param {string} [obj.displayName] - The display name to create - * @param {boolean} [obj.isAdmin] - Whether the user should be an admin + * @param {boolean} [obj.admin] - Whether the user should be an admin * @param {string} [obj.password] - Password to set - * @returns {Promise} Representation of the created user + * @returns {Promise} Representation of the created user */ async createUser({ username = 'test-user', displayName, admin = false, password = 'TestPassword1', - }: UserArgs = {}): Promise { + }: CreateUserFields = {}): Promise { const safeUsername = safeName(username); await expect( repeatWithFallback( @@ -99,39 +104,33 @@ export class UserFixture { await expect(this.userManagementPage.toast.pwLocator).toHaveCount(0); const row = await this.userManagementPage.getRowByUsernameSearch(safeUsername); const id = parseInt(await row.getId()); - const user: V1PostUserRequest = { - isHashed: false, + const user: TestUser = { + active: true, + admin: !!admin, + displayName, + id, password, - user: { - active: true, - admin: !!admin, - displayName, - id, - username: safeUsername, - }, + username: safeUsername, }; return user; } /** * Edit a user with the given parameters via the UI. - * @param {User} user - Representation of the user to edit - * @param {UserArgs} edit - * @param {string} [edit.username] - The username to edit + * @param {TestUser} user - Representation of the user to edit + * @param {EditUserFields} edit - fields to edit + * @param {string} [edit.password] - The password to edit * @param {string} [edit.displayName] - The display name to edit - * @param {boolean} [edit.isAdmin] - Whether the user should be an admin - * @returns {Promise} Representation of the edited user + * @param {boolean} [edit.admin] - Whether the user should be an admin + * @returns {Promise} Representation of the edited user */ - async editUser(user: V1PostUserRequest, edit: UserArgs = {}): Promise { - if (user.user === undefined) { - throw new Error('Trying to edit user that is Undefined.'); - } + async editUser(user: TestUser, edit: EditUserFields = {}): Promise { const editedUser = { ...user }; await this.singleUserSearchAndEdit(user); await expect(this.userManagementPage.createUserModal.username.pwLocator).toBeDisabled(); expect( await this.userManagementPage.createUserModal.username.pwLocator.getAttribute('value'), - ).toEqual(user.user.username || ''); + ).toEqual(user.username); await expect( repeatWithFallback( async () => await this.fillUserForm(edit), @@ -144,22 +143,17 @@ export class UserFixture { ); await this.userManagementPage.toast.close.pwLocator.click(); await expect(this.userManagementPage.toast.pwLocator).toHaveCount(0); - if (editedUser.user === undefined) { - throw new Error('Result from edit user is Undefined.'); - } if (edit.password !== undefined) editedUser.password = edit.password; - if (edit.admin !== undefined) editedUser.user.admin = edit.admin; - if (edit.displayName !== undefined) editedUser.user.displayName = edit.displayName; + if (edit.admin !== undefined) editedUser.admin = edit.admin; + if (edit.displayName !== undefined) editedUser.displayName = edit.displayName; return editedUser; } - private async singleUserSearchAndEdit(user: V1PostUserRequest) { + private async singleUserSearchAndEdit(user: TestUser) { await expect( repeatWithFallback( async () => { - const row = await this.userManagementPage.getRowByUsernameSearch( - user.user?.username ?? '', - ); + const row = await this.userManagementPage.getRowByUsernameSearch(user.username); await (await row.actions.open()).edit.pwLocator.click(); await expect(this.userManagementPage.createUserModal.pwLocator).toBeVisible(); await expect( @@ -175,12 +169,9 @@ export class UserFixture { /** * Validate a user via the UI matches the expected. - * @param {User} obj - Representation of the user to validate against the table + * @param {TestUser} user - Representation of the user to validate against the table */ - async validateUser({ user }: V1PostUserRequest): Promise { - if (user === undefined) { - throw new Error('Can not validate undefined user.'); - } + async validateUser(user: TestUser): Promise { const row = await this.userManagementPage.getRowByUsernameSearch(user.username); expect(Number(await row.getId())).toEqual(user.id); await expect(row.user.name.pwLocator).toContainText(user.username); @@ -211,21 +202,17 @@ export class UserFixture { /** * Changes the status of a user. - * @param {User} user - The user to change the status of + * @param {TestUser} user - The user to change the status of * @param {boolean} activate - Whether to activate or deactivate the user - * @returns {Promise} The updated user + * @returns {Promise} The updated user */ - async changeStatusUser(user: V1PostUserRequest, activate: boolean): Promise { - if (user.user?.active === activate) { + async changeStatusUser(user: TestUser, activate: boolean): Promise { + if (user.active === activate) { return user; } await expect(async () => { - if (user.user === undefined) { - throw new Error('Trying to change status on user that is Undefined.'); - } // user table can flake if running in parrallel - const actions = (await this.userManagementPage.getRowByUsernameSearch(user.user.username)) - .actions; + const actions = (await this.userManagementPage.getRowByUsernameSearch(user.username)).actions; await actions.open(); if ( (await actions.state.pwLocator.textContent()) !== (activate ? 'Activate' : 'Deactivate') @@ -238,7 +225,7 @@ export class UserFixture { ); await this.userManagementPage.toast.close.pwLocator.click(); }).toPass({ timeout: 35_000 }); - const editedUser = _.merge(user, { user: { active: activate } }); + const editedUser = Object.assign(user, { active: activate }); return editedUser; } } diff --git a/webui/react/src/e2e/tests/userManagement.spec.ts b/webui/react/src/e2e/tests/userManagement.spec.ts index 04be6879315..162b80c8345 100644 --- a/webui/react/src/e2e/tests/userManagement.spec.ts +++ b/webui/react/src/e2e/tests/userManagement.spec.ts @@ -3,8 +3,7 @@ import { UserManagement } from 'e2e/models/pages/Admin/UserManagement'; import { SignIn } from 'e2e/models/pages/SignIn'; import { sessionRandomHash } from 'e2e/utils/naming'; import { repeatWithFallback } from 'e2e/utils/polling'; -import { saveTestUserId } from 'e2e/utils/users'; -import { V1PostUserRequest } from 'services/api-ts-sdk/api'; +import { TestUser } from 'e2e/utils/users'; test.describe('User Management', () => { // One list of users per test session. This is to encourage a final teardown @@ -39,14 +38,14 @@ test.describe('User Management', () => { }); test.describe('User Management UI CRUD', () => { - let testUser: V1PostUserRequest; + let testUser: TestUser; test.beforeEach(async ({ user }) => { // share one user created via UI across tests: if (!testUser) { await test.step('Create User', async () => { testUser = await user.createUser(); - saveTestUserId(testUser, testUserIds); + testUserIds.push(testUser.id); await user.validateUser(testUser); }); } @@ -55,7 +54,7 @@ test.describe('User Management', () => { test('New User Access', async ({ page, auth }) => { const userManagementPage = new UserManagement(page); await auth.logout(); - await auth.login({ password: testUser.password, username: testUser.user?.username }); + await auth.login({ password: testUser.password, username: testUser.username }); await userManagementPage.nav.sidebar.headerDropdown.open(); await userManagementPage.nav.sidebar.headerDropdown.settings.pwLocator.waitFor(); await userManagementPage.nav.sidebar.headerDropdown.admin.pwLocator.waitFor({ @@ -65,11 +64,8 @@ test.describe('User Management', () => { test('Edit User', async ({ user }) => { await test.step('Edit display name', async () => { - if (testUser.user === undefined) { - throw new Error('Trying to edit an undefined user.'); - } testUser = await user.editUser(testUser, { - displayName: testUser.user.username + '_edited', + displayName: testUser.username + '_edited', }); await user.validateUser(testUser); }); @@ -91,7 +87,7 @@ test.describe('User Management', () => { await auth.login({ expectedURL: /login/, password: testUser.password, - username: testUser.user?.username, + username: testUser.username, }); await expect(page).toHaveDeterminedTitle(signInPage.title); await expect(page).toHaveURL(/login/); @@ -122,7 +118,7 @@ test.describe('User Management', () => { await test.step('Successful sign in with reactivated user', async () => { test.slow(); await auth.logout(); - await auth.login({ password: testUser.password, username: testUser.user?.username }); + await auth.login({ password: testUser.password, username: testUser.username }); }); }); }); @@ -133,10 +129,14 @@ test.describe('User Management', () => { await test.step('Create User', async () => { // pagination will be 10 per page, so create 11 users for (let i = 0; i < 11; i++) { - const user = await backgroundApiUser.createUser( + const userResponse = await backgroundApiUser.createUser( backgroundApiUser.new({ usernamePrefix }), ); - saveTestUserId(user, testUserIds); + if (userResponse.user?.id) { + testUserIds.push(userResponse.user.id); + } else { + throw new Error('createUser: invalid API response'); + } } }); }); diff --git a/webui/react/src/e2e/utils/users.ts b/webui/react/src/e2e/utils/users.ts index d5ae0121b29..e1c178fe0ad 100644 --- a/webui/react/src/e2e/utils/users.ts +++ b/webui/react/src/e2e/utils/users.ts @@ -1,8 +1,7 @@ -import { V1PostUserRequest } from 'services/api-ts-sdk/api'; +import { V1User } from 'services/api-ts-sdk/api'; -export const saveTestUserId = (user: V1PostUserRequest, userIds: number[]): void => { - if (user.user?.id === undefined) { - throw new Error('User has an object but has no data.'); - } - userIds.push(user.user.id); -}; +export interface TestUser extends V1User { + password?: string; + // require: + id: number; +}