MM-63820 chore(e2e): update dependencies and docs (#30807)
* chore(e2e): update dependencies and docs * update the use of postMessage, expose 8065 on test server and fix flakiness on profile popover
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
de46d798e4
Коммит
d631974e88
@@ -65,6 +65,8 @@ services:
|
||||
MM_LOGSETTINGS_ENABLEDIAGNOSTICS: "false"
|
||||
MM_LOGSETTINGS_CONSOLELEVEL: "DEBUG"
|
||||
network_mode: host
|
||||
ports:
|
||||
- "8065:8065"
|
||||
depends_on:
|
||||
$(for service in $ENABLED_DOCKER_SERVICES; do
|
||||
# The server container will start only if all other dependent services are healthy
|
||||
@@ -277,7 +279,7 @@ $(if mme2e_is_token_in_list "webhook-interactions" "$ENABLED_DOCKER_SERVICES"; t
|
||||
$(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
|
||||
echo '
|
||||
playwright:
|
||||
image: mcr.microsoft.com/playwright:v1.51.1-noble
|
||||
image: mcr.microsoft.com/playwright:v1.52.0-noble
|
||||
entrypoint: ["/bin/bash", "-c"]
|
||||
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
||||
env_file:
|
||||
|
||||
@@ -2,20 +2,47 @@
|
||||
|
||||
#### 1. Start local server in a separate terminal.
|
||||
|
||||
```
|
||||
There are two ways to run the local server:
|
||||
|
||||
**Option 1: Run from source**
|
||||
|
||||
```bash
|
||||
# Typically run the local server with:
|
||||
cd server && make run
|
||||
|
||||
# Or build and distribute webapp including channels and playbooks
|
||||
# so that their product URLs do not rely on Webpack dev server.
|
||||
# Especially important when running tests inside the Playwright's docker container.
|
||||
cd webapp && make dist
|
||||
# Or run webapp and server on separate terminals for better performance
|
||||
# First terminal: Build and run the webapp
|
||||
cd webapp && make run
|
||||
# Second terminal: Run the server
|
||||
cd server && make run-server
|
||||
```
|
||||
|
||||
**Option 2: Run using Docker (recommended for testing)**
|
||||
|
||||
```bash
|
||||
# 1. Configure environment variables in e2e-tests/.ci/env
|
||||
# Create this file if it doesn't exist
|
||||
|
||||
# 2. Set the server image (optional)
|
||||
# To use the latest master image:
|
||||
SERVER_IMAGE="mattermostdevelopment/mattermost-enterprise-edition:master"
|
||||
# If not set, it will use the current commit: mattermostdevelopment/mattermost-enterprise-edition:$(git rev-parse --short=7 HEAD)
|
||||
# Note: The image must exist in Docker Hub at https://hub.docker.com/r/mattermostdevelopment/mattermost-enterprise-edition/tags
|
||||
|
||||
# 3. Add your license if needed
|
||||
MM_LICENSE=<your-license-key>
|
||||
|
||||
# 4. For additional configuration options, see e2e-tests/README.md
|
||||
|
||||
# 5. Run the server and Playwright's smoke tests from the e2e-tests directory
|
||||
cd e2e-tests && TEST=playwright make
|
||||
```
|
||||
|
||||
This approach uses the server's Docker image to create a consistent testing environment. It automatically configures the server with the necessary settings for Playwright tests and handles dependencies.
|
||||
|
||||
#### 2. Install dependencies and run the test.
|
||||
|
||||
```
|
||||
```bash
|
||||
# Install npm packages
|
||||
npm i
|
||||
|
||||
@@ -36,19 +63,36 @@ npm run test
|
||||
|
||||
#### 3. Inspect test results at `/results/output` folder when something fails unexpectedly.
|
||||
|
||||
## Run tests in UI mode
|
||||
|
||||
Check out https://playwright.dev/docs/test-ui-mode for detailed guide on UI Mode to learn more about its features.
|
||||
|
||||
```bash
|
||||
npm run playwright-ui
|
||||
```
|
||||
|
||||
> **Note:** If no tests appear in the UI, check your filter settings:
|
||||
>
|
||||
> - Test name filters
|
||||
> - Project filters (setup, ipad, chrome, firefox)
|
||||
> - Tag filters (@tag)
|
||||
> - Execution status filters
|
||||
>
|
||||
> The "setup" project runs the initial configuration tests in `specs/test_setup.ts` (ensuring plugins are loaded and server deployment is correct). These setup tests are typically run only once before other tests and may be unchecked for subsequent runs, though they can remain checked if needed.
|
||||
|
||||
## Updating screenshots is done strictly via Playwright's docker container for consistency
|
||||
|
||||
#### 1. Run docker container using latest focal version
|
||||
#### 1. Run Playwright's docker container
|
||||
|
||||
Change to the `e2e-tests/playwright` directory, then run the docker container. (See https://playwright.dev/docs/docker for reference.)
|
||||
|
||||
```
|
||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.51.1-noble /bin/bash
|
||||
```bash
|
||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.52.0-noble /bin/bash
|
||||
```
|
||||
|
||||
#### 2. Inside the docker container
|
||||
|
||||
```
|
||||
```bash
|
||||
export PW_BASE_URL=http://host.docker.internal:8065
|
||||
export PW_HEADLESS=true
|
||||
cd mattermost/e2e-tests/playwright
|
||||
|
||||
@@ -45,24 +45,24 @@
|
||||
"@axe-core/playwright": "4.10.1",
|
||||
"@mattermost/client": "file:../../../webapp/platform/client",
|
||||
"@mattermost/types": "file:../../../webapp/platform/types",
|
||||
"@percy/cli": "1.30.9",
|
||||
"@percy/playwright": "1.0.7",
|
||||
"@percy/cli": "1.30.10",
|
||||
"@percy/playwright": "1.0.8",
|
||||
"async-wait-until": "2.0.27",
|
||||
"axe-core": "4.10.3",
|
||||
"deepmerge": "4.3.1",
|
||||
"dotenv": "16.4.7",
|
||||
"dotenv": "16.5.0",
|
||||
"mime-types": "3.0.1",
|
||||
"uuid": "11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "12.1.2",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/node": "22.14.0",
|
||||
"@types/react": "19.1.0",
|
||||
"rollup": "4.39.0",
|
||||
"@types/node": "22.14.1",
|
||||
"@types/react": "19.1.2",
|
||||
"rollup": "4.40.0",
|
||||
"rollup-plugin-copy": "3.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": "1.51.1"
|
||||
"@playwright/test": "1.52.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +94,20 @@ async function printClientInfo(client: Client4) {
|
||||
- TelemetryId = ${config.TelemetryId}
|
||||
- ServiceEnvironment = ${config.ServiceEnvironment}`);
|
||||
|
||||
const {LogSettings, ServiceSettings} = await client.getConfig();
|
||||
const {LogSettings, ServiceSettings, FeatureFlags} = await client.getConfig();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Notable Server Config:
|
||||
- ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings?.EnableSecurityFixAlert}
|
||||
- LogSettings.EnableDiagnostics = ${LogSettings?.EnableDiagnostics}`);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Feature Flags:');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
Object.entries(FeatureFlags)
|
||||
.map(([key, value]) => ` - ${key} = ${value}`)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
async function printPluginDetails(client: Client4) {
|
||||
|
||||
@@ -277,6 +277,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
FileCompress: false,
|
||||
FileMaxQueueSize: 1000,
|
||||
AdvancedLoggingJSON: {},
|
||||
Certificate: '',
|
||||
},
|
||||
NotificationLogSettings: {
|
||||
EnableConsole: true,
|
||||
@@ -783,4 +784,8 @@ const defaultServerConfig: AdminConfig = {
|
||||
DisableSharedChannelsStatusSync: false,
|
||||
MaxPostsPerSync: 50,
|
||||
},
|
||||
AccessControlSettings: {
|
||||
EnableAttributeBasedAccessControl: false,
|
||||
EnableChannelScopeAccessControl: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,6 +50,10 @@ export default class ChannelsCenterView {
|
||||
await this.postCreate.toBeVisible();
|
||||
}
|
||||
|
||||
async postMessage(message: string, files?: string[]) {
|
||||
await this.postCreate.postMessage(message, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on "See all scheduled messages"
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,10 @@ export default class ChannelsSidebarRight {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
await this.postCreate.postMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RHS post by post id
|
||||
* @param postId Just the ID without the prefix
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {components} from '@/ui/components';
|
||||
import {ChannelsPost, components} from '@/ui/components';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
@@ -89,9 +89,10 @@ export default class ChannelsPage {
|
||||
/**
|
||||
* `postMessage` posts a message in the current channel
|
||||
* @param message Message to post
|
||||
* @param files Files to attach to the message
|
||||
*/
|
||||
async postMessage(message: string) {
|
||||
await this.centerView.postCreate.postMessage(message);
|
||||
async postMessage(message: string, files?: string[]) {
|
||||
await this.centerView.postMessage(message, files);
|
||||
}
|
||||
|
||||
async openUserAccountMenu() {
|
||||
@@ -106,4 +107,16 @@ export default class ChannelsPage {
|
||||
await expect(this.profileModal.container).toBeVisible();
|
||||
return this.profileModal;
|
||||
}
|
||||
|
||||
async openProfilePopover(post: ChannelsPost) {
|
||||
// Find and click the post's user avatar to open the profile popover
|
||||
await post.hover();
|
||||
await post.profileIcon.click();
|
||||
|
||||
// Wait for the profile popover to be visible
|
||||
const popover = this.userProfilePopover;
|
||||
await expect(popover.container).toBeVisible();
|
||||
|
||||
return popover;
|
||||
}
|
||||
}
|
||||
|
||||
756
e2e-tests/playwright/package-lock.json
сгенерированный
756
e2e-tests/playwright/package-lock.json
сгенерированный
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -24,15 +24,15 @@
|
||||
"@mattermost/client": "file:../../webapp/platform/client",
|
||||
"@mattermost/playwright-lib": "*",
|
||||
"@mattermost/types": "file:../../webapp/platform/types",
|
||||
"@playwright/test": "1.51.1",
|
||||
"@playwright/test": "1.52.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dayjs": "1.11.13",
|
||||
"zod": "3.24.2"
|
||||
"zod": "3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.29.0",
|
||||
"eslint": "9.24.0",
|
||||
"eslint-import-resolver-typescript": "4.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.30.1",
|
||||
"eslint": "9.25.0",
|
||||
"eslint-import-resolver-typescript": "4.3.3",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"prettier": "3.5.3",
|
||||
|
||||
@@ -13,7 +13,7 @@ test.fixme('Base channel accessibility', async ({pw, axe}) => {
|
||||
// # Visit a default channel page
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage('hello');
|
||||
await channelsPage.postMessage('hello');
|
||||
|
||||
// # Analyze the page
|
||||
// Disable 'color-contrast' to be addressed by MM-53814
|
||||
@@ -35,7 +35,7 @@ test('Post actions tab support', async ({pw, axe}) => {
|
||||
// # Visit a default channel page
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage('hello');
|
||||
await channelsPage.postMessage('hello');
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.hover();
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
test('MM-T53377 Profile popover should show correct fields after at-mention autocomplete', async ({pw}) => {
|
||||
// # Initialize with specific config and get admin client
|
||||
/**
|
||||
* Verify that profile popover shows correct user information based on privacy settings
|
||||
* and continues to show correct information after at-mention autocomplete is used.
|
||||
*
|
||||
* Precondition:
|
||||
* 1. A test server configured with privacy settings to hide email and full name
|
||||
* 2. Two user accounts, with one user able to see their own information
|
||||
*/
|
||||
test('Profile popover should show correct fields after at-mention autocomplete @user_profile', async ({pw}) => {
|
||||
// Initialize with user's privacy settings set to hide email and full name
|
||||
const {user, adminClient, team} = await pw.initSetup();
|
||||
|
||||
await adminClient.patchConfig({
|
||||
PrivacySettings: {
|
||||
ShowEmailAddress: false,
|
||||
@@ -14,59 +21,59 @@ test('MM-T53377 Profile popover should show correct fields after at-mention auto
|
||||
},
|
||||
});
|
||||
|
||||
// # Create and add another user using admin client
|
||||
// Create and add another user using admin client
|
||||
const testUser2 = await adminClient.createUser(pw.random.user(), '', '');
|
||||
await adminClient.addToTeam(team.id, testUser2.id);
|
||||
|
||||
// # Log in as user in new browser context
|
||||
// 1. Login as the first user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Send mentions quickly
|
||||
await channelsPage.centerView.postCreate.postMessage(`@${user.username} @${testUser2.username}`);
|
||||
// 2. Post a message with mentions of both users
|
||||
await channelsPage.postMessage(`@${user.username} @${testUser2.username}`);
|
||||
|
||||
// # Open profile popover for current user
|
||||
const firstMention = channelsPage.centerView.container.getByText(`@${user.username}`, {exact: true});
|
||||
// 3. Open profile popover for the current user on first
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
const firstMention = await lastPost.container.getByText(`@${user.username}`, {exact: true});
|
||||
await firstMention.click();
|
||||
const currentUserProfilePopover = channelsPage.userProfilePopover;
|
||||
|
||||
// * Verify all fields are visible for current user
|
||||
const popover = channelsPage.userProfilePopover;
|
||||
await expect(popover.container.getByText(`@${user.username}`)).toBeVisible();
|
||||
await expect(popover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
|
||||
await expect(popover.container.getByText(user.email)).toBeVisible();
|
||||
// * Verify all fields are visible for current user in the profile popover
|
||||
await expect(currentUserProfilePopover.container.getByText(`@${user.username}`)).toBeVisible(); // TODO: Fix this
|
||||
await expect(currentUserProfilePopover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
|
||||
await expect(currentUserProfilePopover.container.getByText(user.email)).toBeVisible();
|
||||
|
||||
// # Close profile popover
|
||||
await popover.close();
|
||||
// 4. Close the current user's profile popover
|
||||
await currentUserProfilePopover.close();
|
||||
|
||||
// # Open profile popover for other user
|
||||
const secondMention = channelsPage.centerView.container.getByText(`@${testUser2.username}`, {exact: true});
|
||||
// 5. Open profile popover for the other user on second mention
|
||||
const secondMention = await lastPost.container.getByText(`@${testUser2.username}`, {exact: true});
|
||||
await secondMention.click();
|
||||
const otherUserProfilePopover = channelsPage.userProfilePopover;
|
||||
|
||||
// * Verify only username is visible for other user
|
||||
await expect(popover.container.getByText(`@${testUser2.username}`)).toBeVisible();
|
||||
await expect(popover.container.getByText(testUser2.email)).not.toBeVisible();
|
||||
// * Verify only username is visible for other user in the profile popover
|
||||
await expect(otherUserProfilePopover.container.getByText(`@${testUser2.username}`)).toBeVisible();
|
||||
await expect(otherUserProfilePopover.container.getByText(testUser2.email)).not.toBeVisible(); // TODO: Fix this
|
||||
|
||||
// # Close profile popover
|
||||
await popover.close();
|
||||
// 6. Close the other user's profile popover
|
||||
await otherUserProfilePopover.close();
|
||||
|
||||
// # Trigger autocomplete
|
||||
// 7. Start typing an at-mention to trigger autocomplete suggestion
|
||||
await channelsPage.centerView.postCreate.writeMessage(`@${user.username}`);
|
||||
|
||||
// # Wait for autocomplete
|
||||
// * Verify autocomplete suggestion appears
|
||||
const suggestionList = channelsPage.centerView.postCreate.suggestionList;
|
||||
await expect(suggestionList.getByText(`@${user.username}`)).toBeVisible();
|
||||
|
||||
// # Clear textbox
|
||||
// 8. Clear the message box
|
||||
await channelsPage.centerView.postCreate.writeMessage('');
|
||||
|
||||
// # Open profile popover for current user again
|
||||
await firstMention.click();
|
||||
// 9. Open profile popover for the current user again
|
||||
const profilePopoverAgain = await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify all fields are still visible
|
||||
await expect(popover.container.getByText(`@${user.username}`)).toBeVisible();
|
||||
await expect(popover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
|
||||
await expect(popover.container.getByText(user.email)).toBeVisible();
|
||||
await expect(profilePopoverAgain.container.getByText(`@${user.username}`)).toBeVisible();
|
||||
await expect(profilePopoverAgain.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
|
||||
await expect(profilePopoverAgain.container.getByText(user.email)).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
deleteCustomProfileAttributes,
|
||||
verifyAttributeInPopover,
|
||||
verifyAttributeNotInPopover,
|
||||
openProfilePopover,
|
||||
updateCustomProfileAttributeVisibility,
|
||||
TEST_PHONE,
|
||||
TEST_URL,
|
||||
@@ -129,7 +128,8 @@ test('MM-T5773 Display custom profile attributes in profile popover @custom_prof
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 3. Open the profile popover for the user's post
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify each custom attribute is displayed correctly
|
||||
for (const attribute of customAttributes) {
|
||||
@@ -158,7 +158,8 @@ test('MM-T5774 Do not display custom profile attributes if none exist @custom_pr
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 3. Open the profile popover for the user's post
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify custom attributes are not displayed
|
||||
for (const attribute of customAttributes) {
|
||||
@@ -185,7 +186,8 @@ test('MM-T5775 Update custom profile attributes when changed @custom_profile_att
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 3. Open the profile popover for the user's post
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify each custom attribute is displayed correctly
|
||||
for (const attribute of customAttributes) {
|
||||
@@ -213,7 +215,7 @@ test('MM-T5775 Update custom profile attributes when changed @custom_profile_att
|
||||
await setupCustomProfileAttributeValues(userClient, updatedAttributes, attributeFieldsMap);
|
||||
|
||||
// 6. Open the profile popover again
|
||||
await openProfilePopover(channelsPage);
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify updated attributes are displayed correctly
|
||||
for (const attribute of updatedAttributes) {
|
||||
@@ -250,7 +252,8 @@ test('MM-T5776 Hide custom profile attributes when visibility is set to hidden @
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 4. Open the profile popover for the user's post
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify the Department attribute is not displayed
|
||||
await verifyAttributeNotInPopover(channelsPage, 'Department');
|
||||
@@ -283,7 +286,8 @@ test('MM-T5777 Always display custom profile attributes with visibility set to a
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 4. Open the profile popover for the user's post
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify custom attributes are displayed correctly
|
||||
for (const attribute of customAttributes) {
|
||||
@@ -319,7 +323,8 @@ test('MM-T5778 Display phone and URL type custom profile attributes correctly @c
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 3. Open the profile popover for the other user
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify the Phone attribute is displayed correctly
|
||||
await verifyAttributeInPopover(channelsPage, 'Phone', TEST_PHONE);
|
||||
@@ -349,7 +354,8 @@ test('MM-T5779 Verify phone and URL attributes are clickable in profile popover
|
||||
await channelsPage.postMessage(TEST_MESSAGE);
|
||||
|
||||
// 3. Open the profile popover for the other user
|
||||
await openProfilePopover(channelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Verify the Phone attribute has a clickable link with tel: protocol
|
||||
const popover = channelsPage.userProfilePopover.container;
|
||||
|
||||
@@ -213,21 +213,6 @@ export async function editMultiselectAttribute(
|
||||
await page.waitForTimeout(500); // Wait for save to complete
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to open the profile popover for the test user
|
||||
* @param {ChannelsPage} channelsPage - The Playwright channels page object
|
||||
*/
|
||||
export async function openProfilePopover(channelsPage: ChannelsPage): Promise<void> {
|
||||
// Find and click the last post's user avatar to open the profile popover
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await lastPost.hover();
|
||||
await lastPost.profileIcon.click();
|
||||
|
||||
// Wait for the profile popover to be visible
|
||||
const popover = channelsPage.userProfilePopover;
|
||||
await expect(popover.container).toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to verify an attribute exists in the profile settings
|
||||
* @param {Page} page - The Playwright page object
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
verifyAttributesExistInSettings,
|
||||
verifyAttributeInPopover,
|
||||
verifyAttributeNotInPopover,
|
||||
openProfilePopover,
|
||||
editTextAttribute,
|
||||
editSelectAttribute,
|
||||
editMultiselectAttribute,
|
||||
@@ -171,7 +170,8 @@ test('MM-T5768 Editing Custom Profile Attributes @custom_profile_attributes', as
|
||||
await otherChannelsPage.goto();
|
||||
|
||||
// 9. View the test user's profile popover
|
||||
await openProfilePopover(otherChannelsPage);
|
||||
const lastPost = await otherChannelsPage.getLastPost();
|
||||
await otherChannelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Profile popover shows updated custom attributes
|
||||
await verifyAttributeInPopover(otherChannelsPage, 'Department', TEST_UPDATED_DEPARTMENT);
|
||||
@@ -220,7 +220,8 @@ test('MM-T5769 Clearing Custom Profile Attributes @custom_profile_attributes', a
|
||||
await otherChannelsPage.goto();
|
||||
|
||||
// 7. View the test user's profile popover
|
||||
await openProfilePopover(otherChannelsPage);
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
await channelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Department attribute is not displayed in the profile popover
|
||||
await verifyAttributeNotInPopover(otherChannelsPage, 'Department');
|
||||
@@ -311,7 +312,8 @@ test('MM-T5771 Editing Phone and URL Type Custom Profile Attributes @custom_prof
|
||||
await otherChannelsPage.goto();
|
||||
|
||||
// 8. View the test user's profile popover
|
||||
await openProfilePopover(otherChannelsPage);
|
||||
const lastPost = await otherChannelsPage.getLastPost();
|
||||
await otherChannelsPage.openProfilePopover(lastPost);
|
||||
|
||||
// * Profile popover shows updated attributes
|
||||
await verifyAttributeInPopover(otherChannelsPage, 'Phone', TEST_UPDATED_PHONE);
|
||||
|
||||
@@ -41,7 +41,7 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use
|
||||
// # Post a message as a user
|
||||
const sidebarRight = channelsPage.sidebarRight;
|
||||
await sidebarRight.toBeVisible();
|
||||
await sidebarRight.postCreate.postMessage('Replying to a thread');
|
||||
await sidebarRight.postMessage('Replying to a thread');
|
||||
|
||||
// # Write a message in the reply thread but don't send it now so that it becomes a draft
|
||||
const draftMessageByUser = 'I should be in drafts by User';
|
||||
@@ -86,7 +86,7 @@ test('MM-T5435_2 Global Drafts link in sidebar should be hidden when user delete
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Post a message in the channel
|
||||
await channelsPage.centerView.postCreate.postMessage('Message which will be deleted');
|
||||
await channelsPage.postMessage('Message which will be deleted');
|
||||
|
||||
// # Start a thread by clicking on reply menuitem from post options menu
|
||||
const post = await channelsPage.getLastPost();
|
||||
@@ -98,7 +98,7 @@ test('MM-T5435_2 Global Drafts link in sidebar should be hidden when user delete
|
||||
await sidebarRight.toBeVisible();
|
||||
|
||||
// # Post a message in the thread
|
||||
await sidebarRight.postCreate.postMessage('Replying to a thread');
|
||||
await sidebarRight.postMessage('Replying to a thread');
|
||||
|
||||
// # Write a message in the reply thread but don't send it
|
||||
await sidebarRight.postCreate.writeMessage('I should be in drafts');
|
||||
|
||||
@@ -13,7 +13,7 @@ test('MM-T5654_1 should be able to add attachments while editing a post', async
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage);
|
||||
await channelsPage.postMessage(originalMessage);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -41,7 +41,7 @@ test('MM-T5654_2 should be able to add attachments while editing a threaded post
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage);
|
||||
await channelsPage.postMessage(originalMessage);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -54,7 +54,7 @@ test('MM-T5654_2 should be able to add attachments while editing a threaded post
|
||||
await channelsPage.postDotMenu.replyMenuItem.click();
|
||||
await channelsPage.sidebarRight.toBeVisible();
|
||||
await channelsPage.sidebarRight.postCreate.toBeVisible();
|
||||
await channelsPage.sidebarRight.postCreate.postMessage('Replying to the post');
|
||||
await channelsPage.sidebarRight.postMessage('Replying to the post');
|
||||
await channelsPage.sidebarRight.toContainText('Replying to the post');
|
||||
|
||||
const replyPost = await channelsPage.sidebarRight.getLastPost();
|
||||
@@ -119,7 +119,7 @@ test('MM-T5654_3 should be able to edit post message originally containing files
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
await channelsPage.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -147,7 +147,7 @@ test('MM-T5654_4 should be able to add files when editing a post', async ({pw})
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage);
|
||||
await channelsPage.postMessage(originalMessage);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -192,11 +192,7 @@ test('MM-5654_5 should be able to remove attachments while editing a post', asyn
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage, [
|
||||
'sample_text_file.txt',
|
||||
'mattermost.png',
|
||||
'archive.zip',
|
||||
]);
|
||||
await channelsPage.postMessage(originalMessage, ['sample_text_file.txt', 'mattermost.png', 'archive.zip']);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -232,7 +228,7 @@ test('MM-T5655_1 removing message content and files should delete the post', asy
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
await channelsPage.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -266,11 +262,7 @@ test('MM-T5655_2 should be able to remove all files when editing a post', async
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage, [
|
||||
'sample_text_file.txt',
|
||||
'mattermost.png',
|
||||
'archive.zip',
|
||||
]);
|
||||
await channelsPage.postMessage(originalMessage, ['sample_text_file.txt', 'mattermost.png', 'archive.zip']);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
@@ -309,7 +301,7 @@ test('MM-T5656_1 should be able to restore previously edited post version that c
|
||||
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.centerView.postCreate.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
await channelsPage.postMessage(originalMessage, ['sample_text_file.txt']);
|
||||
|
||||
const post = await channelsPage.getLastPost();
|
||||
await post.toBeVisible();
|
||||
|
||||
@@ -53,7 +53,7 @@ test.fixme(
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Send a message
|
||||
await channelsPage.centerView.postCreate.postMessage('Message to open RHS');
|
||||
await channelsPage.postMessage('Message to open RHS');
|
||||
|
||||
// # Open the last post sent in RHS
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
|
||||
@@ -53,7 +53,7 @@ test('MM-T5643_6 should create a scheduled message under a thread post ', async
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
await channelsPage.centerView.postCreate.postMessage('Root Message');
|
||||
await channelsPage.postMessage('Root Message');
|
||||
|
||||
// # Start a thread by clicking on reply menuitem from post options menu
|
||||
const post = await channelsPage.getLastPost();
|
||||
@@ -63,7 +63,7 @@ test('MM-T5643_6 should create a scheduled message under a thread post ', async
|
||||
await sidebarRight.toBeVisible();
|
||||
|
||||
// # Post a message in the thread
|
||||
await sidebarRight.postCreate.postMessage('Replying to a thread');
|
||||
await sidebarRight.postMessage('Replying to a thread');
|
||||
|
||||
// # Write a message in the reply thread but don't send it
|
||||
await sidebarRight.postCreate.writeMessage(draftMessage);
|
||||
|
||||
@@ -23,7 +23,7 @@ test('MM-T5465-1 Should add the keyword when enter, comma or tab is pressed on t
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
await channelsPage.centerView.postCreate.postMessage('Hello World');
|
||||
await channelsPage.postMessage('Hello World');
|
||||
|
||||
// # Open settings modal
|
||||
const settingsModal = await channelsPage.globalHeader.openSettings();
|
||||
@@ -97,7 +97,7 @@ test('MM-T5465-2 Should highlight the keywords when a message is sent with the k
|
||||
|
||||
// # Post a message without the keyword
|
||||
const messageWithoutKeyword = 'This message does not contain the keyword';
|
||||
await channelsPage.centerView.postCreate.postMessage(messageWithoutKeyword);
|
||||
await channelsPage.postMessage(messageWithoutKeyword);
|
||||
const lastPostWithoutHighlight = await channelsPage.getLastPost();
|
||||
|
||||
// * Verify that the keywords are not highlighted
|
||||
@@ -108,7 +108,7 @@ test('MM-T5465-2 Should highlight the keywords when a message is sent with the k
|
||||
|
||||
// # Post a message with the keyword
|
||||
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`;
|
||||
await channelsPage.centerView.postCreate.postMessage(messageWithKeyword);
|
||||
await channelsPage.postMessage(messageWithKeyword);
|
||||
const lastPostWithHighlight = await channelsPage.getLastPost();
|
||||
|
||||
// * Verify that the keywords are highlighted
|
||||
@@ -151,7 +151,7 @@ test('MM-T5465-3 Should highlight the keywords when a message is sent with the k
|
||||
|
||||
// # Post a message without the keyword
|
||||
const messageWithoutKeyword = 'This message does not contain the keyword';
|
||||
await channelsPage.centerView.postCreate.postMessage(messageWithoutKeyword);
|
||||
await channelsPage.postMessage(messageWithoutKeyword);
|
||||
const lastPostWithoutHighlight = await channelsPage.getLastPost();
|
||||
|
||||
// # Open the message in the RHS
|
||||
@@ -162,7 +162,7 @@ test('MM-T5465-3 Should highlight the keywords when a message is sent with the k
|
||||
|
||||
// # Post a message with the keyword in the RHS
|
||||
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`;
|
||||
await channelsPage.sidebarRight.postCreate.postMessage(messageWithKeyword);
|
||||
await channelsPage.sidebarRight.postMessage(messageWithKeyword);
|
||||
|
||||
// * Verify that the keywords are highlighted
|
||||
const lastPostWithHighlightInRHS = await channelsPage.sidebarRight.getLastPost();
|
||||
|
||||
@@ -484,6 +484,7 @@ export type ExperimentalAuditSettings = {
|
||||
FileCompress: boolean;
|
||||
FileMaxQueueSize: number;
|
||||
AdvancedLoggingJSON: Record<string, any>;
|
||||
Certificate: string;
|
||||
};
|
||||
|
||||
export type NotificationLogSettings = {
|
||||
@@ -966,6 +967,11 @@ export type ExportSettings = {
|
||||
RetentionDays: number;
|
||||
};
|
||||
|
||||
export type AccessControlSettings = {
|
||||
EnableAttributeBasedAccessControl: boolean;
|
||||
EnableChannelScopeAccessControl: boolean;
|
||||
};
|
||||
|
||||
export type AdminConfig = {
|
||||
ServiceSettings: ServiceSettings;
|
||||
TeamSettings: TeamSettings;
|
||||
@@ -1011,6 +1017,7 @@ export type AdminConfig = {
|
||||
ExportSettings: ExportSettings;
|
||||
WranglerSettings: WranglerSettings;
|
||||
ConnectedWorkspacesSettings: ConnectedWorkspacesSettings;
|
||||
AccessControlSettings: AccessControlSettings;
|
||||
};
|
||||
|
||||
export type ReplicaLagSetting = {
|
||||
|
||||
Ссылка в новой задаче
Block a user