test: fix user details and attribute tests (#35257)

This commit is contained in:
sabril 2026-02-12 14:50:28 +08:00 committed by GitHub
parent 2bb605cb56
commit 68c1c072dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 280 additions and 379 deletions

View file

@ -3,6 +3,36 @@
import {Locator, expect} from '@playwright/test';
import {ConfirmModal} from '@/ui/components/system_console/base_modal';
/**
* Save Changes confirmation modal on the User Detail page.
* Shown when saving edits to user fields (email, username, auth data, etc.).
*/
export class SaveChangesModal extends ConfirmModal {
readonly messageBody: Locator;
readonly changesList: Locator;
constructor(container: Locator) {
super(container);
this.messageBody = this.container.locator('#confirmModalBody');
this.changesList = this.messageBody.locator('ul.changes-list');
}
/**
* Get the list of change summary texts shown in the modal.
*/
async getChanges(): Promise<string[]> {
const items = this.changesList.locator('li');
const count = await items.count();
const changes: string[] = [];
for (let i = 0; i < count; i++) {
changes.push(((await items.nth(i).textContent()) ?? '').trim());
}
return changes;
}
}
/**
* System Console -> User Management -> Users -> User Detail
* Accessed by clicking on a user row in the Users list
@ -20,6 +50,9 @@ export default class UserDetail {
// Team Membership Panel
readonly teamMembershipPanel: TeamMembershipPanel;
// Save Changes confirmation modal
readonly saveChangesModal: SaveChangesModal;
// Save section
readonly saveButton: Locator;
readonly cancelButton: Locator;
@ -40,6 +73,11 @@ export default class UserDetail {
this.container.locator('.AdminPanel').filter({hasText: 'Team Membership'}),
);
// Save Changes confirmation modal (page-level, rendered outside container via portal)
this.saveChangesModal = new SaveChangesModal(
this.container.page().locator('#admin-userDetail-saveChangesModal'),
);
// Save section
this.saveButton = this.container.getByTestId('saveSetting');
this.cancelButton = this.container.getByRole('button', {name: 'Cancel'});
@ -84,6 +122,12 @@ class AdminUserCard {
readonly twoColumnLayout: Locator;
readonly fieldRows: Locator;
// System field inputs (scoped via wrapping <label> to avoid substring ambiguity)
readonly usernameInput: Locator;
readonly emailInput: Locator;
readonly authDataInput: Locator;
readonly authenticationMethod: Locator;
// Footer section
readonly resetPasswordButton: Locator;
readonly deactivateButton: Locator;
@ -104,6 +148,12 @@ class AdminUserCard {
this.twoColumnLayout = this.body.locator('.two-column-layout');
this.fieldRows = this.body.locator('.field-row');
// System fields — use exact label text to avoid substring matches (e.g., "Email" vs "Work Email")
this.usernameInput = this.getFieldInputByExactLabel('Username');
this.emailInput = this.getFieldInputByExactLabel('Email');
this.authDataInput = this.getFieldInputByExactLabel('Auth Data');
this.authenticationMethod = this.getFieldColumn('Authentication Method').locator('label > span').last();
// Footer
const footer = container.locator('.AdminUserCard__footer');
this.resetPasswordButton = footer.getByRole('button', {name: 'Reset Password'});
@ -115,28 +165,35 @@ class AdminUserCard {
await expect(this.container).toBeVisible();
}
getFieldByLabel(labelText: string): Locator {
return this.body.getByLabel(labelText);
/**
* Get the .field-column container for a field by its exact label text.
*/
private getFieldColumn(labelText: string): Locator {
return this.body
.locator('.field-column')
.filter({has: this.body.page().locator(`span:text-is("${labelText}")`)});
}
getSelectByLabel(labelText: string): Locator {
return this.body.getByLabel(labelText);
/**
* Get the input inside a field column by exact label text.
* Avoids substring ambiguity (e.g., "Email" won't match "Work Email").
*/
getFieldInputByExactLabel(labelText: string): Locator {
return this.getFieldColumn(labelText).locator('input');
}
async fillField(labelText: string, value: string) {
const input = this.getFieldByLabel(labelText);
await input.clear();
await input.fill(value);
/**
* Get the select inside a field column by exact label text.
*/
getSelectByExactLabel(labelText: string): Locator {
return this.getFieldColumn(labelText).locator('select');
}
async getFieldValue(labelText: string): Promise<string> {
const input = this.getFieldByLabel(labelText);
return await input.inputValue();
}
async getUserId(): Promise<string> {
const text = (await this.userId.textContent()) ?? '';
return text.replace('User ID: ', '');
/**
* Get the .field-error validation message locator for a field by its exact label text.
*/
getFieldError(labelText: string): Locator {
return this.getFieldColumn(labelText).locator('.field-error');
}
}
@ -159,56 +216,4 @@ class TeamMembershipPanel {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
async clickAddTeam() {
await this.addTeamButton.click();
}
async getTeamCount(): Promise<number> {
return this.teamRows.count();
}
getTeamRowByIndex(index: number): TeamRow {
return new TeamRow(this.teamRows.nth(index));
}
getTeamRowByName(teamName: string): TeamRow {
return new TeamRow(this.teamRows.filter({hasText: teamName}));
}
}
class TeamRow {
readonly container: Locator;
readonly teamName: Locator;
readonly teamType: Locator;
readonly role: Locator;
readonly actionMenuButton: Locator;
constructor(container: Locator) {
this.container = container;
this.teamName = container.locator('.TeamRow__team-name b');
this.teamType = container.locator('.TeamRow__description').first();
this.role = container.locator('.TeamRow__description').last();
this.actionMenuButton = container.locator('.TeamRow__actions button');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async getTeamName(): Promise<string> {
return (await this.teamName.textContent()) ?? '';
}
async getTeamType(): Promise<string> {
return (await this.teamType.textContent()) ?? '';
}
async getRole(): Promise<string> {
return (await this.role.textContent()) ?? '';
}
async openActionMenu() {
await this.actionMenuButton.click();
}
}

View file

@ -138,41 +138,36 @@ test.describe('System Console - Admin User Profile Editing', () => {
});
test('MM-65126 Should edit custom user attributes from system console', async () => {
// # Find and edit Department field (custom text attribute) - look for input near Department label
const departmentLabel = systemConsolePage.page.locator('label').filter({hasText: /Department/});
const departmentInput = departmentLabel.locator('input').first();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Find and edit Department field (custom text attribute)
const departmentInput = userCard.getFieldInputByExactLabel('Department');
await departmentInput.clear();
await departmentInput.fill('Marketing');
// # Click Save button (using test ID instead of text)
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeEnabled();
await saveButton.click();
// # Confirm save in the confirmation modal
await expect(systemConsolePage.page.locator('[data-testid="admin-userDetail-saveChangesModal"]')).toBeVisible();
await systemConsolePage.page.locator('#confirmModalButton').click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify success (no error message and field retains new value)
const errorMessage = systemConsolePage.page.locator('.error-message');
await expect(errorMessage).not.toBeVisible();
await expect(userDetail.errorMessage).not.toBeVisible();
await expect(departmentInput).toHaveValue('Marketing');
// * Verify Save button becomes disabled after successful save
await expect(saveButton).toBeDisabled();
await userDetail.waitForSaveComplete();
});
test('Should display user attributes in two-column layout', async () => {
// * Verify two-column layout exists
const twoColumnLayout = systemConsolePage.page.locator('.two-column-layout');
await expect(twoColumnLayout).toBeVisible();
const {userCard} = systemConsolePage.users.userDetail;
// * Verify system fields are present (be more specific to avoid multiple matches)
await expect(systemConsolePage.page.locator('label').filter({hasText: /^Username/})).toBeVisible();
await expect(systemConsolePage.page.locator('label').filter({hasText: /^Authentication Method/})).toBeVisible();
// Email field - check for system email (avoid Work Email by being more specific)
const systemEmailExists = (await systemConsolePage.page.locator('input[type="email"]').count()) > 0;
expect(systemEmailExists).toBe(true);
// * Verify two-column layout exists
await expect(userCard.twoColumnLayout).toBeVisible();
// * Verify system fields are present
await expect(userCard.usernameInput).toBeVisible();
await expect(userCard.emailInput).toBeVisible();
await expect(userCard.authenticationMethod).toBeVisible();
// * Verify custom user attributes are present
for (const field of testUserAttributes) {
@ -187,146 +182,128 @@ test.describe('System Console - Admin User Profile Editing', () => {
expect(inputCount).toBeGreaterThan(4);
// * Verify fields are arranged in rows with two columns
const fieldRows = systemConsolePage.page.locator('.field-row');
const rowCount = await fieldRows.count();
const rowCount = await userCard.fieldRows.count();
expect(rowCount).toBeGreaterThan(0);
});
test('Should edit system email attribute and save', async () => {
// # Find system email field
const systemEmailInput = systemConsolePage.page.locator('input[type="email"]').first();
const {userDetail} = systemConsolePage.users;
const {emailInput} = userDetail.userCard;
// # Enter new valid email
const newEmail = `updated-${testUser.email}`;
await systemEmailInput.clear();
await systemEmailInput.fill(newEmail);
await emailInput.clear();
await emailInput.fill(newEmail);
// # Click Save button
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeEnabled();
await saveButton.click();
// # Confirm save in the confirmation modal
await expect(systemConsolePage.page.locator('[data-testid="admin-userDetail-saveChangesModal"]')).toBeVisible();
await systemConsolePage.page.locator('#confirmModalButton').click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify success
const errorMessage = systemConsolePage.page.locator('.error-message');
await expect(errorMessage).not.toBeVisible();
await expect(systemEmailInput).toHaveValue(newEmail);
await expect(saveButton).toBeDisabled();
await expect(userDetail.errorMessage).not.toBeVisible();
await expect(emailInput).toHaveValue(newEmail);
await userDetail.waitForSaveComplete();
});
test('Should edit custom select attribute and save', async () => {
// # Find Location select field near its label
const locationLabel = systemConsolePage.page.locator('label').filter({hasText: /Location/});
const locationSelect = locationLabel.locator('select').first();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Find Location select field
const locationSelect = userCard.getSelectByExactLabel('Location');
// # Get the first available option (since we can't predict the option value/ID)
const firstOption = await locationSelect.locator('option').nth(1); // Skip the default "Select an option"
const firstOptionValue = await firstOption.getAttribute('value');
await locationSelect.selectOption(firstOptionValue || '');
// # Click Save button
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeEnabled();
await saveButton.click();
// # Confirm save in the confirmation modal
await expect(systemConsolePage.page.locator('[data-testid="admin-userDetail-saveChangesModal"]')).toBeVisible();
await systemConsolePage.page.locator('#confirmModalButton').click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify success and persistence
const errorMessage = systemConsolePage.page.locator('.error-message');
await expect(errorMessage).not.toBeVisible();
await expect(userDetail.errorMessage).not.toBeVisible();
// Don't check exact value since it's a generated ID, just verify it's not empty
const selectedValue = await locationSelect.inputValue();
expect(selectedValue).toBeTruthy();
await expect(saveButton).toBeDisabled();
await userDetail.waitForSaveComplete();
});
test('Should display custom multiselect attribute and save form', async () => {
// * Verify Skills multiselect component is displayed
const skillsLabel = systemConsolePage.page.locator('label').filter({hasText: /Skills/});
await expect(skillsLabel).toBeVisible();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// * Verify the multiselect control is present (React Select component)
// Look for common React Select patterns
const hasMultiselectElement =
(await skillsLabel.locator('div, [class*="select"], [class*="Select"]').count()) > 0;
expect(hasMultiselectElement).toBe(true);
// * Verify Skills multiselect component is displayed
const skillsColumn = userCard.getFieldInputByExactLabel('Skills');
await expect(skillsColumn).toBeVisible();
// # Make a change to a different field to trigger save state
const departmentLabel = systemConsolePage.page.locator('label').filter({hasText: /Department/});
const departmentInput = departmentLabel.locator('input').first();
const departmentInput = userCard.getFieldInputByExactLabel('Department');
await departmentInput.fill('Engineering Updated');
// # Verify save button becomes enabled
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeEnabled();
await expect(userDetail.saveButton).toBeEnabled();
// # Save the form
await saveButton.click();
// # Confirm save in the confirmation modal
await expect(systemConsolePage.page.locator('[data-testid="admin-userDetail-saveChangesModal"]')).toBeVisible();
await systemConsolePage.page.locator('#confirmModalButton').click();
// # Save the form and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify success (no error message)
const errorMessage = systemConsolePage.page.locator('.error-message');
await expect(errorMessage).not.toBeVisible();
await expect(userDetail.errorMessage).not.toBeVisible();
// * Verify save completed
await expect(saveButton).toBeDisabled();
await userDetail.waitForSaveComplete();
// * Verify the change persisted
await expect(departmentInput).toHaveValue('Engineering Updated');
});
test('Should validate invalid email and show error with cancel option', async () => {
// # Find CPA email field (Work Email) by its label
const workEmailLabel = systemConsolePage.page.locator('label').filter({hasText: /Work Email/});
const emailInput = workEmailLabel.locator('input[type="email"]').first();
const originalEmail = await emailInput.inputValue();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Find CPA email field (Work Email)
const workEmailInput = userCard.getFieldInputByExactLabel('Work Email');
const originalEmail = await workEmailInput.inputValue();
// # Enter invalid email
await emailInput.clear();
await emailInput.fill('not-an-email');
await workEmailInput.clear();
await workEmailInput.fill('not-an-email');
// * Verify inline validation error appears
const fieldError = workEmailLabel.locator('.field-error');
const fieldError = userCard.getFieldError('Work Email');
await expect(fieldError).toBeVisible();
await expect(fieldError).toContainText('Invalid email address');
// * Verify Save button is disabled due to validation error
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeDisabled();
await expect(userDetail.saveButton).toBeDisabled();
// * Verify Cancel button is visible and enabled
const cancelButton = systemConsolePage.page.locator('button:has-text("Cancel")');
await expect(cancelButton).toBeVisible();
await expect(cancelButton).toBeEnabled();
await expect(userDetail.cancelButton).toBeVisible();
await expect(userDetail.cancelButton).toBeEnabled();
// # Test the cancel functionality
await cancelButton.click();
await userDetail.cancel();
// * Verify email reverts to original value
await expect(emailInput).toHaveValue(originalEmail);
await expect(workEmailInput).toHaveValue(originalEmail);
// * Verify validation error disappears
await expect(fieldError).not.toBeVisible();
// * Verify Cancel button disappears
await expect(cancelButton).not.toBeVisible();
await expect(userDetail.cancelButton).not.toBeVisible();
// * Verify Save button remains disabled (no unsaved changes)
await expect(saveButton).toBeDisabled();
await expect(userDetail.saveButton).toBeDisabled();
});
test('Should validate invalid URL and show error with cancel option', async () => {
// # Find custom URL field (Personal Website) by its label
const websiteLabel = systemConsolePage.page.locator('label').filter({hasText: /Personal Website/});
const urlInput = websiteLabel.locator('input[type="url"]').first();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Find custom URL field (Personal Website)
const urlInput = userCard.getFieldInputByExactLabel('Personal Website');
const originalUrl = await urlInput.inputValue();
// # Enter invalid URL (specifically the one mentioned: "<%>")
@ -334,21 +311,19 @@ test.describe('System Console - Admin User Profile Editing', () => {
await urlInput.fill('<%>');
// * Verify inline validation error appears
const fieldError = websiteLabel.locator('.field-error');
const fieldError = userCard.getFieldError('Personal Website');
await expect(fieldError).toBeVisible();
await expect(fieldError).toContainText('Invalid URL');
// * Verify Save button is disabled due to validation error
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeDisabled();
await expect(userDetail.saveButton).toBeDisabled();
// * Verify Cancel button is visible
const cancelButton = systemConsolePage.page.locator('button:has-text("Cancel")');
await expect(cancelButton).toBeVisible();
await expect(cancelButton).toBeEnabled();
await expect(userDetail.cancelButton).toBeVisible();
await expect(userDetail.cancelButton).toBeEnabled();
// # Test cancel functionality
await cancelButton.click();
await userDetail.cancel();
// * Verify URL reverts to original value
await expect(urlInput).toHaveValue(originalUrl);
@ -357,90 +332,85 @@ test.describe('System Console - Admin User Profile Editing', () => {
await expect(fieldError).not.toBeVisible();
// * Verify Cancel button disappears
await expect(cancelButton).not.toBeVisible();
await expect(userDetail.cancelButton).not.toBeVisible();
});
test('Should validate invalid email in custom email attribute', async () => {
// # Find custom email field (Work Email) by its label
const workEmailLabel = systemConsolePage.page.locator('label').filter({hasText: /Work Email/});
const workEmailInput = workEmailLabel.locator('input[type="email"]').first();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Find custom email field (Work Email)
const workEmailInput = userCard.getFieldInputByExactLabel('Work Email');
// # Enter invalid email
await workEmailInput.clear();
await workEmailInput.fill('not-an-email-either');
// * Verify inline validation error appears
const fieldError = workEmailLabel.locator('.field-error');
const fieldError = userCard.getFieldError('Work Email');
await expect(fieldError).toBeVisible();
await expect(fieldError).toContainText('Invalid email address');
// * Verify Save button is disabled due to validation error
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeDisabled();
await expect(userDetail.saveButton).toBeDisabled();
// * Verify Cancel button is available
const cancelButton = systemConsolePage.page.locator('button:has-text("Cancel")');
await expect(cancelButton).toBeVisible();
await expect(userDetail.cancelButton).toBeVisible();
});
test('Should show save/cancel buttons when changes are made', async () => {
// * Initially, Save should be disabled and Cancel should not be visible
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
const cancelButton = systemConsolePage.page.locator('button:has-text("Cancel")');
await expect(saveButton).toBeDisabled();
await expect(cancelButton).not.toBeVisible();
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Make a change to trigger save needed state - find Department field by label
const departmentLabel = systemConsolePage.page.locator('label').filter({hasText: /Department/});
const departmentInput = departmentLabel.locator('input').first();
// * Initially, Save should be disabled and Cancel should not be visible
await expect(userDetail.saveButton).toBeDisabled();
await expect(userDetail.cancelButton).not.toBeVisible();
// # Make a change to trigger save needed state
const departmentInput = userCard.getFieldInputByExactLabel('Department');
const originalValue = await departmentInput.inputValue();
await departmentInput.clear();
await departmentInput.fill('Changed Value');
// * Verify Save button becomes enabled and Cancel button appears
await expect(saveButton).toBeEnabled();
await expect(cancelButton).toBeVisible();
await expect(cancelButton).toBeEnabled();
await expect(userDetail.saveButton).toBeEnabled();
await expect(userDetail.cancelButton).toBeVisible();
await expect(userDetail.cancelButton).toBeEnabled();
// # Click Cancel
await cancelButton.click();
await userDetail.cancel();
// * Verify changes are reverted
await expect(departmentInput).toHaveValue(originalValue);
// * Verify Cancel button disappears
await expect(cancelButton).not.toBeVisible();
await expect(userDetail.cancelButton).not.toBeVisible();
// * Verify Save button is disabled
await expect(saveButton).toBeDisabled();
await expect(userDetail.saveButton).toBeDisabled();
});
test('Should save all user attribute changes atomically', async () => {
// # Make changes to both system and custom attributes
const systemEmailInput = systemConsolePage.page.locator('input[type="email"]').first();
const newEmail = `atomic-test-${testUser.email}`;
await systemEmailInput.clear();
await systemEmailInput.fill(newEmail);
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
const departmentLabel = systemConsolePage.page.locator('label').filter({hasText: /Department/});
const departmentInput = departmentLabel.locator('input').first();
// # Make changes to both system and custom attributes
const newEmail = `atomic-test-${testUser.email}`;
await userCard.emailInput.clear();
await userCard.emailInput.fill(newEmail);
const departmentInput = userCard.getFieldInputByExactLabel('Department');
await departmentInput.clear();
await departmentInput.fill('Sales');
// # Click Save button
const saveButton = systemConsolePage.page.locator('[data-testid="saveSetting"]');
await expect(saveButton).toBeEnabled();
await saveButton.click();
// # Confirm save in the confirmation modal
await expect(systemConsolePage.page.locator('[data-testid="admin-userDetail-saveChangesModal"]')).toBeVisible();
await systemConsolePage.page.locator('#confirmModalButton').click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify both changes were saved successfully
const errorMessage = systemConsolePage.page.locator('.error-message');
await expect(errorMessage).not.toBeVisible();
await expect(systemEmailInput).toHaveValue(newEmail);
await expect(userDetail.errorMessage).not.toBeVisible();
await expect(userCard.emailInput).toHaveValue(newEmail);
await expect(departmentInput).toHaveValue('Sales');
await expect(saveButton).toBeDisabled();
await userDetail.waitForSaveComplete();
});
});

View file

@ -1,53 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {type PlaywrightExtended, expect, test} from '@mattermost/playwright-lib';
import {UserProfile} from '@mattermost/types/users';
/**
* Setup a user and navigate to their detail page
* @param pw PlaywrightExtended instance
* @returns User object and system console page
*/
async function setupUserDetailPage(pw: PlaywrightExtended) {
const {adminUser, adminClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
// # Log in as admin
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Create a test user
const user = await adminClient.createUser(await pw.random.user(), '', '');
const team = await adminClient.createTeam(await pw.random.team());
await adminClient.addToTeam(team.id, user.id);
// # Visit system console users section
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
// # Search for the user
await systemConsolePage.systemUsers.enterSearchText(user.email);
const userRow = await systemConsolePage.systemUsers.getNthRow(1);
await userRow.getByText(user.email).waitFor();
// # Click on the username to navigate to user detail page
await userRow.getByText(user.username, {exact: true}).click();
// # Wait for user detail page to load
await systemConsolePage.page.waitForURL(`**/admin_console/user_management/user/${user.id}`);
return {user, systemConsolePage, adminClient};
}
import {expect, test, SystemConsolePage} from '@mattermost/playwright-lib';
/**
* @objective Verifies that authentication data field is displayed and editable for users with auth_service
*/
test('displays and allows editing of authentication data field', {tag: '@user_management'}, async ({pw}) => {
const {user, systemConsolePage, adminClient} = await setupUserDetailPage(pw);
const {user, adminUser, adminClient} = await pw.initSetup();
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Generate unique auth data to avoid unique constraint errors
const originalAuthData = `auth-data-${await pw.random.id()}`;
@ -59,43 +22,27 @@ test('displays and allows editing of authentication data field', {tag: '@user_ma
auth_data: originalAuthData,
});
// # Refresh the page to load the updated user data
await systemConsolePage.page.reload();
await systemConsolePage.page.waitForLoadState('networkidle');
// # Navigate to user detail page
await navigateToUserDetail(systemConsolePage, user);
const {userDetail} = systemConsolePage.users;
// * Verify auth data field is visible
const authDataLabel = systemConsolePage.page.getByText('Auth Data');
await expect(authDataLabel).toBeVisible();
// * Verify auth data input field is present and contains current value
const authDataInput = systemConsolePage.page.locator('input[placeholder="Enter auth data"]');
// * Verify auth data field is visible with correct value
const {authDataInput} = userDetail.userCard;
await expect(authDataInput).toBeVisible();
await expect(authDataInput).toHaveValue(originalAuthData);
// # Update the auth data value
await authDataInput.clear();
await authDataInput.fill(newAuthData);
// * Verify Save button is enabled after change
const saveButton = systemConsolePage.page.getByRole('button', {name: 'Save'});
await expect(saveButton).toBeEnabled();
await expect(userDetail.saveButton).toBeEnabled();
// # Click Save button
await saveButton.click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify confirmation modal appears
const confirmModal = systemConsolePage.page.getByText('Confirm Changes');
await expect(confirmModal).toBeVisible();
// * Verify the modal shows the auth data change
const authDataChange = systemConsolePage.page.getByText(`Auth Data: ${originalAuthData}${newAuthData}`);
await expect(authDataChange).toBeVisible();
// # Confirm the save
const confirmSaveButton = systemConsolePage.page.getByRole('button', {name: 'Save Changes'});
await confirmSaveButton.click();
// * Verify modal closes and success
await expect(confirmModal).not.toBeVisible();
// * Verify the auth data field retains new value
await expect(authDataInput).toHaveValue(newAuthData);
// * Verify the change was saved by checking API
@ -107,29 +54,25 @@ test('displays and allows editing of authentication data field', {tag: '@user_ma
* @objective Verifies that email and username fields are disabled with tooltips when user has auth_service
*/
test('disables email and username fields for users with auth service', {tag: '@user_management'}, async ({pw}) => {
const {user, systemConsolePage, adminClient} = await setupUserDetailPage(pw);
const {user, adminUser, adminClient} = await pw.initSetup();
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Update user to have an auth service (use unique auth_data to avoid constraint errors)
// # Update user to have an auth service
await adminClient.updateUserAuth(user.id, {
auth_service: 'ldap',
auth_data: `ldap-user-data-${await pw.random.id()}`,
});
// # Refresh the page to load updated user data
await systemConsolePage.page.reload();
await systemConsolePage.page.waitForLoadState('networkidle');
// # Navigate to user detail page
await navigateToUserDetail(systemConsolePage, user);
const {userDetail} = systemConsolePage.users;
// * Verify email field is disabled
const emailInput = systemConsolePage.page.locator('label:has-text("Email") input');
// * Verify email and username fields are disabled and read-only
const {emailInput, usernameInput} = userDetail.userCard;
await expect(emailInput).toBeDisabled();
await expect(emailInput).toHaveAttribute('readonly');
await expect(emailInput).toHaveCSS('cursor', 'not-allowed');
// * Verify username field is disabled
const usernameInput = systemConsolePage.page.locator('label:has-text("Username") input');
await expect(emailInput).toHaveAttribute('readonly', '');
await expect(usernameInput).toBeDisabled();
await expect(usernameInput).toHaveAttribute('readonly');
await expect(usernameInput).toHaveCSS('cursor', 'not-allowed');
await expect(usernameInput).toHaveAttribute('readonly', '');
// # Hover over email field to verify tooltip
await emailInput.hover();
@ -146,54 +89,37 @@ test('disables email and username fields for users with auth service', {tag: '@u
* @objective Verifies that email and username fields are editable when user has no auth_service
*/
test('allows editing email and username fields for regular users', {tag: '@user_management'}, async ({pw}) => {
const {user, systemConsolePage, adminClient} = await setupUserDetailPage(pw);
const {user, adminUser, adminClient} = await pw.initSetup();
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Ensure user has no auth service (regular email/password user)
const currentUser = await adminClient.getUser(user.id);
expect(currentUser.auth_service).toBe('');
// # Navigate to user detail page
await navigateToUserDetail(systemConsolePage, user);
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// * Verify email field is editable
const emailInput = systemConsolePage.page.locator('label:has-text("Email") input');
await expect(emailInput).toBeEnabled();
await expect(emailInput).not.toHaveAttribute('readonly');
// * Verify username field is editable
const usernameInput = systemConsolePage.page.locator('label:has-text("Username") input');
await expect(usernameInput).toBeEnabled();
await expect(usernameInput).not.toHaveAttribute('readonly');
// * Verify email and username fields are editable
await expect(userCard.emailInput).toBeEnabled();
await expect(userCard.usernameInput).toBeEnabled();
// # Update both email and username
const newEmail = `updated-${await pw.random.id()}@example.com`;
const newUsername = `updated-${await pw.random.id()}`;
await emailInput.fill(newEmail);
await usernameInput.fill(newUsername);
await userCard.emailInput.clear();
await userCard.emailInput.fill(newEmail);
await userCard.usernameInput.clear();
await userCard.usernameInput.fill(newUsername);
// * Verify Save button is enabled after changes
const saveButton = systemConsolePage.page.getByRole('button', {name: 'Save'});
await expect(saveButton).toBeEnabled();
await expect(userDetail.saveButton).toBeEnabled();
// # Click Save button
await saveButton.click();
// # Click Save button and confirm
await userDetail.save();
await userDetail.saveChangesModal.confirm();
// * Verify confirmation modal shows both changes
const confirmModal = systemConsolePage.page.getByText('Confirm Changes');
await expect(confirmModal).toBeVisible();
const emailChange = systemConsolePage.page.getByText(`Email: ${user.email}${newEmail}`);
await expect(emailChange).toBeVisible();
const usernameChange = systemConsolePage.page.getByText(`Username: ${user.username}${newUsername}`);
await expect(usernameChange).toBeVisible();
// # Confirm the save
const confirmSaveButton = systemConsolePage.page.getByRole('button', {name: 'Save Changes'});
await confirmSaveButton.click();
// * Verify modal closes and changes are saved
await expect(confirmModal).not.toBeVisible();
await expect(emailInput).toHaveValue(newEmail);
await expect(usernameInput).toHaveValue(newUsername);
// * Verify fields retain new values
await expect(userCard.emailInput).toHaveValue(newEmail);
await expect(userCard.usernameInput).toHaveValue(newUsername);
// * Verify the changes were saved by checking API
const updatedUser = await adminClient.getUser(user.id);
@ -202,81 +128,81 @@ test('allows editing email and username fields for regular users', {tag: '@user_
});
/**
* @objective Verifies inline validation for email and username fields
* @objective Verifies inline validation for email field
*/
test('displays inline validation errors for invalid email and username', {tag: '@user_management'}, async ({pw}) => {
const {systemConsolePage} = await setupUserDetailPage(pw);
test('displays inline validation errors for invalid email', {tag: '@user_management'}, async ({pw}) => {
const {user, adminUser} = await pw.initSetup();
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Navigate to user detail page
await navigateToUserDetail(systemConsolePage, user);
const {userDetail} = systemConsolePage.users;
const {userCard} = userDetail;
// # Enter invalid email
const emailInput = systemConsolePage.page.locator('label:has-text("Email") input');
await emailInput.fill('invalid-email');
await userCard.emailInput.clear();
await userCard.emailInput.fill('invalid-email');
// * Verify email validation error appears
const emailError = systemConsolePage.page.locator('div.field-error').filter({hasText: 'Invalid email address'});
const emailError = userCard.getFieldError('Email');
await expect(emailError).toBeVisible();
// * Verify email input has error styling
await expect(emailInput).toHaveClass(/error/);
// # Enter empty username
const usernameInput = systemConsolePage.page.locator('label:has-text("Username") input');
await usernameInput.fill('');
// * Verify username validation error appears
const usernameError = systemConsolePage.page
.locator('div.field-error')
.filter({hasText: 'Username cannot be empty'});
await expect(usernameError).toBeVisible();
// * Verify username input has error styling
await expect(usernameInput).toHaveClass(/error/);
// * Verify Save button is disabled due to validation errors
const saveButton = systemConsolePage.page.getByRole('button', {name: 'Save'});
await expect(saveButton).toBeDisabled();
// * Verify Save button is disabled due to validation error
await expect(userDetail.saveButton).toBeDisabled();
// # Fix the email
await emailInput.fill('valid@example.com');
await userCard.emailInput.clear();
await userCard.emailInput.fill('valid@example.com');
// * Verify email error disappears and styling is removed
// * Verify email error disappears
await expect(emailError).not.toBeVisible();
await expect(emailInput).not.toHaveClass(/error/);
// # Fix the username
await usernameInput.fill('validusername');
// * Verify username error disappears and styling is removed
await expect(usernameError).not.toBeVisible();
await expect(usernameInput).not.toHaveClass(/error/);
// * Verify Save button is now enabled
await expect(saveButton).toBeEnabled();
await expect(userDetail.saveButton).toBeEnabled();
});
/**
* @objective Verifies confirmation dialog can be cancelled
*/
test('allows cancelling save confirmation dialog', {tag: '@user_management'}, async ({pw}) => {
const {systemConsolePage} = await setupUserDetailPage(pw);
const {user, adminUser} = await pw.initSetup();
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Navigate to user detail page
await navigateToUserDetail(systemConsolePage, user);
const {userDetail} = systemConsolePage.users;
// # Update email field
const emailInput = systemConsolePage.page.locator('label:has-text("Email") input');
const newEmail = `cancelled-${await pw.random.id()}@example.com`;
await emailInput.fill(newEmail);
await userDetail.userCard.emailInput.clear();
await userDetail.userCard.emailInput.fill(newEmail);
// # Click Save button
const saveButton = systemConsolePage.page.getByRole('button', {name: 'Save'});
await saveButton.click();
await userDetail.save();
// * Verify confirmation modal appears
const confirmModal = systemConsolePage.page.getByText('Confirm Changes');
await expect(confirmModal).toBeVisible();
await userDetail.saveChangesModal.toBeVisible();
// # Click Cancel
const cancelButton = systemConsolePage.page.getByRole('button', {name: 'Cancel'});
await cancelButton.click();
await userDetail.saveChangesModal.cancel();
await expect(userDetail.userCard.emailInput).toHaveValue(newEmail);
// * Verify modal closes and field retains the edited value
await expect(confirmModal).not.toBeVisible();
await expect(emailInput).toHaveValue(newEmail);
// * Verify Save button is still enabled (unsaved changes remain)
await expect(userDetail.saveButton).toBeEnabled();
});
/**
* Navigate to the user detail page for a given user.
*/
async function navigateToUserDetail(systemConsolePage: SystemConsolePage, user: UserProfile) {
await systemConsolePage.goto();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.users.searchUsers(user.email);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
await expect(userRow.container.getByText(user.email)).toBeVisible();
await userRow.container.getByText(user.email).click();
await systemConsolePage.users.userDetail.toBeVisible();
}