mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-18 18:18:23 -05:00
test: fix user details and attribute tests (#35257)
This commit is contained in:
parent
2bb605cb56
commit
68c1c072dd
3 changed files with 280 additions and 379 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue