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
Этот коммит содержится в:
Saturn Abril
2025-04-22 02:02:55 +08:00
коммит произвёл GitHub
родитель de46d798e4
Коммит d631974e88
21 изменённых файлов: 587 добавлений и 485 удалений

Просмотреть файл

@@ -65,6 +65,8 @@ services:
MM_LOGSETTINGS_ENABLEDIAGNOSTICS: "false" MM_LOGSETTINGS_ENABLEDIAGNOSTICS: "false"
MM_LOGSETTINGS_CONSOLELEVEL: "DEBUG" MM_LOGSETTINGS_CONSOLELEVEL: "DEBUG"
network_mode: host network_mode: host
ports:
- "8065:8065"
depends_on: depends_on:
$(for service in $ENABLED_DOCKER_SERVICES; do $(for service in $ENABLED_DOCKER_SERVICES; do
# The server container will start only if all other dependent services are healthy # 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 $(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
echo ' echo '
playwright: playwright:
image: mcr.microsoft.com/playwright:v1.51.1-noble image: mcr.microsoft.com/playwright:v1.52.0-noble
entrypoint: ["/bin/bash", "-c"] entrypoint: ["/bin/bash", "-c"]
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"] command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
env_file: env_file:

Просмотреть файл

@@ -2,20 +2,47 @@
#### 1. Start local server in a separate terminal. #### 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: # Typically run the local server with:
cd server && make run cd server && make run
# Or build and distribute webapp including channels and playbooks # Or run webapp and server on separate terminals for better performance
# so that their product URLs do not rely on Webpack dev server. # First terminal: Build and run the webapp
# Especially important when running tests inside the Playwright's docker container. cd webapp && make run
cd webapp && make dist # Second terminal: Run the server
cd server && make run-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. #### 2. Install dependencies and run the test.
``` ```bash
# Install npm packages # Install npm packages
npm i npm i
@@ -36,19 +63,36 @@ npm run test
#### 3. Inspect test results at `/results/output` folder when something fails unexpectedly. #### 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 ## 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.) Change to the `e2e-tests/playwright` directory, then run the docker container. (See https://playwright.dev/docs/docker for reference.)
``` ```bash
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.51.1-noble /bin/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 #### 2. Inside the docker container
``` ```bash
export PW_BASE_URL=http://host.docker.internal:8065 export PW_BASE_URL=http://host.docker.internal:8065
export PW_HEADLESS=true export PW_HEADLESS=true
cd mattermost/e2e-tests/playwright cd mattermost/e2e-tests/playwright

Просмотреть файл

@@ -45,24 +45,24 @@
"@axe-core/playwright": "4.10.1", "@axe-core/playwright": "4.10.1",
"@mattermost/client": "file:../../../webapp/platform/client", "@mattermost/client": "file:../../../webapp/platform/client",
"@mattermost/types": "file:../../../webapp/platform/types", "@mattermost/types": "file:../../../webapp/platform/types",
"@percy/cli": "1.30.9", "@percy/cli": "1.30.10",
"@percy/playwright": "1.0.7", "@percy/playwright": "1.0.8",
"async-wait-until": "2.0.27", "async-wait-until": "2.0.27",
"axe-core": "4.10.3", "axe-core": "4.10.3",
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
"dotenv": "16.4.7", "dotenv": "16.5.0",
"mime-types": "3.0.1", "mime-types": "3.0.1",
"uuid": "11.1.0" "uuid": "11.1.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "12.1.2", "@rollup/plugin-typescript": "12.1.2",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/node": "22.14.0", "@types/node": "22.14.1",
"@types/react": "19.1.0", "@types/react": "19.1.2",
"rollup": "4.39.0", "rollup": "4.40.0",
"rollup-plugin-copy": "3.5.0" "rollup-plugin-copy": "3.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"@playwright/test": "1.51.1" "@playwright/test": "1.52.0"
} }
} }

Просмотреть файл

@@ -94,11 +94,20 @@ async function printClientInfo(client: Client4) {
- TelemetryId = ${config.TelemetryId} - TelemetryId = ${config.TelemetryId}
- ServiceEnvironment = ${config.ServiceEnvironment}`); - ServiceEnvironment = ${config.ServiceEnvironment}`);
const {LogSettings, ServiceSettings} = await client.getConfig(); const {LogSettings, ServiceSettings, FeatureFlags} = await client.getConfig();
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`Notable Server Config: console.log(`Notable Server Config:
- ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings?.EnableSecurityFixAlert} - ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings?.EnableSecurityFixAlert}
- LogSettings.EnableDiagnostics = ${LogSettings?.EnableDiagnostics}`); - 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) { async function printPluginDetails(client: Client4) {

Просмотреть файл

@@ -277,6 +277,7 @@ const defaultServerConfig: AdminConfig = {
FileCompress: false, FileCompress: false,
FileMaxQueueSize: 1000, FileMaxQueueSize: 1000,
AdvancedLoggingJSON: {}, AdvancedLoggingJSON: {},
Certificate: '',
}, },
NotificationLogSettings: { NotificationLogSettings: {
EnableConsole: true, EnableConsole: true,
@@ -783,4 +784,8 @@ const defaultServerConfig: AdminConfig = {
DisableSharedChannelsStatusSync: false, DisableSharedChannelsStatusSync: false,
MaxPostsPerSync: 50, MaxPostsPerSync: 50,
}, },
AccessControlSettings: {
EnableAttributeBasedAccessControl: false,
EnableChannelScopeAccessControl: false,
},
}; };

Просмотреть файл

@@ -50,6 +50,10 @@ export default class ChannelsCenterView {
await this.postCreate.toBeVisible(); await this.postCreate.toBeVisible();
} }
async postMessage(message: string, files?: string[]) {
await this.postCreate.postMessage(message, files);
}
/** /**
* Click on "See all scheduled messages" * Click on "See all scheduled messages"
*/ */

Просмотреть файл

@@ -45,6 +45,10 @@ export default class ChannelsSidebarRight {
await expect(this.container).toBeVisible(); await expect(this.container).toBeVisible();
} }
async postMessage(message: string) {
await this.postCreate.postMessage(message);
}
/** /**
* Returns the RHS post by post id * Returns the RHS post by post id
* @param postId Just the ID without the prefix * @param postId Just the ID without the prefix

Просмотреть файл

@@ -3,7 +3,7 @@
import {expect, Page} from '@playwright/test'; import {expect, Page} from '@playwright/test';
import {components} from '@/ui/components'; import {ChannelsPost, components} from '@/ui/components';
export default class ChannelsPage { export default class ChannelsPage {
readonly channels = 'Channels'; readonly channels = 'Channels';
@@ -89,9 +89,10 @@ export default class ChannelsPage {
/** /**
* `postMessage` posts a message in the current channel * `postMessage` posts a message in the current channel
* @param message Message to post * @param message Message to post
* @param files Files to attach to the message
*/ */
async postMessage(message: string) { async postMessage(message: string, files?: string[]) {
await this.centerView.postCreate.postMessage(message); await this.centerView.postMessage(message, files);
} }
async openUserAccountMenu() { async openUserAccountMenu() {
@@ -106,4 +107,16 @@ export default class ChannelsPage {
await expect(this.profileModal.container).toBeVisible(); await expect(this.profileModal.container).toBeVisible();
return this.profileModal; 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 сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -24,15 +24,15 @@
"@mattermost/client": "file:../../webapp/platform/client", "@mattermost/client": "file:../../webapp/platform/client",
"@mattermost/playwright-lib": "*", "@mattermost/playwright-lib": "*",
"@mattermost/types": "file:../../webapp/platform/types", "@mattermost/types": "file:../../webapp/platform/types",
"@playwright/test": "1.51.1", "@playwright/test": "1.52.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"dayjs": "1.11.13", "dayjs": "1.11.13",
"zod": "3.24.2" "zod": "3.24.3"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "8.29.0", "@typescript-eslint/eslint-plugin": "8.30.1",
"eslint": "9.24.0", "eslint": "9.25.0",
"eslint-import-resolver-typescript": "4.3.2", "eslint-import-resolver-typescript": "4.3.3",
"eslint-plugin-header": "3.1.1", "eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"prettier": "3.5.3", "prettier": "3.5.3",

Просмотреть файл

@@ -13,7 +13,7 @@ test.fixme('Base channel accessibility', async ({pw, axe}) => {
// # Visit a default channel page // # Visit a default channel page
await channelsPage.goto(); await channelsPage.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage('hello'); await channelsPage.postMessage('hello');
// # Analyze the page // # Analyze the page
// Disable 'color-contrast' to be addressed by MM-53814 // 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 // # Visit a default channel page
await channelsPage.goto(); await channelsPage.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage('hello'); await channelsPage.postMessage('hello');
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.hover(); await post.hover();

Просмотреть файл

@@ -3,10 +3,17 @@
import {expect, test} from '@mattermost/playwright-lib'; 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(); const {user, adminClient, team} = await pw.initSetup();
await adminClient.patchConfig({ await adminClient.patchConfig({
PrivacySettings: { PrivacySettings: {
ShowEmailAddress: false, 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(), '', ''); const testUser2 = await adminClient.createUser(pw.random.user(), '', '');
await adminClient.addToTeam(team.id, testUser2.id); 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); const {channelsPage} = await pw.testBrowser.login(user);
// # Visit default channel page
await channelsPage.goto(); await channelsPage.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
// # Send mentions quickly // 2. Post a message with mentions of both users
await channelsPage.centerView.postCreate.postMessage(`@${user.username} @${testUser2.username}`); await channelsPage.postMessage(`@${user.username} @${testUser2.username}`);
// # Open profile popover for current user // 3. Open profile popover for the current user on first
const firstMention = channelsPage.centerView.container.getByText(`@${user.username}`, {exact: true}); const lastPost = await channelsPage.getLastPost();
const firstMention = await lastPost.container.getByText(`@${user.username}`, {exact: true});
await firstMention.click(); await firstMention.click();
const currentUserProfilePopover = channelsPage.userProfilePopover;
// * Verify all fields are visible for current user // * Verify all fields are visible for current user in the profile popover
const popover = channelsPage.userProfilePopover; await expect(currentUserProfilePopover.container.getByText(`@${user.username}`)).toBeVisible(); // TODO: Fix this
await expect(popover.container.getByText(`@${user.username}`)).toBeVisible(); await expect(currentUserProfilePopover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
await expect(popover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible(); await expect(currentUserProfilePopover.container.getByText(user.email)).toBeVisible();
await expect(popover.container.getByText(user.email)).toBeVisible();
// # Close profile popover // 4. Close the current user's profile popover
await popover.close(); await currentUserProfilePopover.close();
// # Open profile popover for other user // 5. Open profile popover for the other user on second mention
const secondMention = channelsPage.centerView.container.getByText(`@${testUser2.username}`, {exact: true}); const secondMention = await lastPost.container.getByText(`@${testUser2.username}`, {exact: true});
await secondMention.click(); await secondMention.click();
const otherUserProfilePopover = channelsPage.userProfilePopover;
// * Verify only username is visible for other user // * Verify only username is visible for other user in the profile popover
await expect(popover.container.getByText(`@${testUser2.username}`)).toBeVisible(); await expect(otherUserProfilePopover.container.getByText(`@${testUser2.username}`)).toBeVisible();
await expect(popover.container.getByText(testUser2.email)).not.toBeVisible(); await expect(otherUserProfilePopover.container.getByText(testUser2.email)).not.toBeVisible(); // TODO: Fix this
// # Close profile popover // 6. Close the other user's profile popover
await popover.close(); await otherUserProfilePopover.close();
// # Trigger autocomplete // 7. Start typing an at-mention to trigger autocomplete suggestion
await channelsPage.centerView.postCreate.writeMessage(`@${user.username}`); await channelsPage.centerView.postCreate.writeMessage(`@${user.username}`);
// # Wait for autocomplete // * Verify autocomplete suggestion appears
const suggestionList = channelsPage.centerView.postCreate.suggestionList; const suggestionList = channelsPage.centerView.postCreate.suggestionList;
await expect(suggestionList.getByText(`@${user.username}`)).toBeVisible(); await expect(suggestionList.getByText(`@${user.username}`)).toBeVisible();
// # Clear textbox // 8. Clear the message box
await channelsPage.centerView.postCreate.writeMessage(''); await channelsPage.centerView.postCreate.writeMessage('');
// # Open profile popover for current user again // 9. Open profile popover for the current user again
await firstMention.click(); const profilePopoverAgain = await channelsPage.openProfilePopover(lastPost);
// * Verify all fields are still visible // * Verify all fields are still visible
await expect(popover.container.getByText(`@${user.username}`)).toBeVisible(); await expect(profilePopoverAgain.container.getByText(`@${user.username}`)).toBeVisible();
await expect(popover.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible(); await expect(profilePopoverAgain.container.getByText(`${user.first_name} ${user.last_name}`)).toBeVisible();
await expect(popover.container.getByText(user.email)).toBeVisible(); await expect(profilePopoverAgain.container.getByText(user.email)).toBeVisible();
}); });

Просмотреть файл

@@ -16,7 +16,6 @@ import {
deleteCustomProfileAttributes, deleteCustomProfileAttributes,
verifyAttributeInPopover, verifyAttributeInPopover,
verifyAttributeNotInPopover, verifyAttributeNotInPopover,
openProfilePopover,
updateCustomProfileAttributeVisibility, updateCustomProfileAttributeVisibility,
TEST_PHONE, TEST_PHONE,
TEST_URL, TEST_URL,
@@ -129,7 +128,8 @@ test('MM-T5773 Display custom profile attributes in profile popover @custom_prof
await channelsPage.postMessage(TEST_MESSAGE); await channelsPage.postMessage(TEST_MESSAGE);
// 3. Open the profile popover for the user's post // 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 // * Verify each custom attribute is displayed correctly
for (const attribute of customAttributes) { 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); await channelsPage.postMessage(TEST_MESSAGE);
// 3. Open the profile popover for the user's post // 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 // * Verify custom attributes are not displayed
for (const attribute of customAttributes) { 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); await channelsPage.postMessage(TEST_MESSAGE);
// 3. Open the profile popover for the user's post // 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 // * Verify each custom attribute is displayed correctly
for (const attribute of customAttributes) { 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); await setupCustomProfileAttributeValues(userClient, updatedAttributes, attributeFieldsMap);
// 6. Open the profile popover again // 6. Open the profile popover again
await openProfilePopover(channelsPage); await channelsPage.openProfilePopover(lastPost);
// * Verify updated attributes are displayed correctly // * Verify updated attributes are displayed correctly
for (const attribute of updatedAttributes) { 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); await channelsPage.postMessage(TEST_MESSAGE);
// 4. Open the profile popover for the user's post // 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 // * Verify the Department attribute is not displayed
await verifyAttributeNotInPopover(channelsPage, 'Department'); 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); await channelsPage.postMessage(TEST_MESSAGE);
// 4. Open the profile popover for the user's post // 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 // * Verify custom attributes are displayed correctly
for (const attribute of customAttributes) { 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); await channelsPage.postMessage(TEST_MESSAGE);
// 3. Open the profile popover for the other user // 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 // * Verify the Phone attribute is displayed correctly
await verifyAttributeInPopover(channelsPage, 'Phone', TEST_PHONE); 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); await channelsPage.postMessage(TEST_MESSAGE);
// 3. Open the profile popover for the other user // 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 // * Verify the Phone attribute has a clickable link with tel: protocol
const popover = channelsPage.userProfilePopover.container; const popover = channelsPage.userProfilePopover.container;

Просмотреть файл

@@ -213,21 +213,6 @@ export async function editMultiselectAttribute(
await page.waitForTimeout(500); // Wait for save to complete 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 * Helper function to verify an attribute exists in the profile settings
* @param {Page} page - The Playwright page object * @param {Page} page - The Playwright page object

Просмотреть файл

@@ -17,7 +17,6 @@ import {
verifyAttributesExistInSettings, verifyAttributesExistInSettings,
verifyAttributeInPopover, verifyAttributeInPopover,
verifyAttributeNotInPopover, verifyAttributeNotInPopover,
openProfilePopover,
editTextAttribute, editTextAttribute,
editSelectAttribute, editSelectAttribute,
editMultiselectAttribute, editMultiselectAttribute,
@@ -171,7 +170,8 @@ test('MM-T5768 Editing Custom Profile Attributes @custom_profile_attributes', as
await otherChannelsPage.goto(); await otherChannelsPage.goto();
// 9. View the test user's profile popover // 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 // * Profile popover shows updated custom attributes
await verifyAttributeInPopover(otherChannelsPage, 'Department', TEST_UPDATED_DEPARTMENT); 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(); await otherChannelsPage.goto();
// 7. View the test user's profile popover // 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 // * Department attribute is not displayed in the profile popover
await verifyAttributeNotInPopover(otherChannelsPage, 'Department'); await verifyAttributeNotInPopover(otherChannelsPage, 'Department');
@@ -311,7 +312,8 @@ test('MM-T5771 Editing Phone and URL Type Custom Profile Attributes @custom_prof
await otherChannelsPage.goto(); await otherChannelsPage.goto();
// 8. View the test user's profile popover // 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 // * Profile popover shows updated attributes
await verifyAttributeInPopover(otherChannelsPage, 'Phone', TEST_UPDATED_PHONE); 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 // # Post a message as a user
const sidebarRight = channelsPage.sidebarRight; const sidebarRight = channelsPage.sidebarRight;
await sidebarRight.toBeVisible(); 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 // # 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'; 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(); await channelsPage.toBeVisible();
// # Post a message in the channel // # 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 // # Start a thread by clicking on reply menuitem from post options menu
const post = await channelsPage.getLastPost(); 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(); await sidebarRight.toBeVisible();
// # Post a message in the thread // # 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 // # Write a message in the reply thread but don't send it
await sidebarRight.postCreate.writeMessage('I should be in drafts'); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage(originalMessage); await channelsPage.postMessage(originalMessage);
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage(originalMessage); await channelsPage.postMessage(originalMessage);
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.postDotMenu.replyMenuItem.click();
await channelsPage.sidebarRight.toBeVisible(); await channelsPage.sidebarRight.toBeVisible();
await channelsPage.sidebarRight.postCreate.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'); await channelsPage.sidebarRight.toContainText('Replying to the post');
const replyPost = await channelsPage.sidebarRight.getLastPost(); 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.goto();
await channelsPage.toBeVisible(); 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(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage(originalMessage); await channelsPage.postMessage(originalMessage);
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage(originalMessage, [ await channelsPage.postMessage(originalMessage, ['sample_text_file.txt', 'mattermost.png', 'archive.zip']);
'sample_text_file.txt',
'mattermost.png',
'archive.zip',
]);
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); 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(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage(originalMessage, [ await channelsPage.postMessage(originalMessage, ['sample_text_file.txt', 'mattermost.png', 'archive.zip']);
'sample_text_file.txt',
'mattermost.png',
'archive.zip',
]);
const post = await channelsPage.getLastPost(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); 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.goto();
await channelsPage.toBeVisible(); 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(); const post = await channelsPage.getLastPost();
await post.toBeVisible(); await post.toBeVisible();

Просмотреть файл

@@ -53,7 +53,7 @@ test.fixme(
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
// # Send a message // # 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 // # Open the last post sent in RHS
const lastPost = await channelsPage.getLastPost(); 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.goto();
await channelsPage.toBeVisible(); 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 // # Start a thread by clicking on reply menuitem from post options menu
const post = await channelsPage.getLastPost(); 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(); await sidebarRight.toBeVisible();
// # Post a message in the thread // # 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 // # Write a message in the reply thread but don't send it
await sidebarRight.postCreate.writeMessage(draftMessage); 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.goto();
await channelsPage.toBeVisible(); await channelsPage.toBeVisible();
await channelsPage.centerView.postCreate.postMessage('Hello World'); await channelsPage.postMessage('Hello World');
// # Open settings modal // # Open settings modal
const settingsModal = await channelsPage.globalHeader.openSettings(); 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 // # Post a message without the keyword
const messageWithoutKeyword = 'This message does not contain 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(); const lastPostWithoutHighlight = await channelsPage.getLastPost();
// * Verify that the keywords are not highlighted // * 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 // # Post a message with the keyword
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`; const messageWithKeyword = `This message contains the keyword ${keywords[3]}`;
await channelsPage.centerView.postCreate.postMessage(messageWithKeyword); await channelsPage.postMessage(messageWithKeyword);
const lastPostWithHighlight = await channelsPage.getLastPost(); const lastPostWithHighlight = await channelsPage.getLastPost();
// * Verify that the keywords are highlighted // * 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 // # Post a message without the keyword
const messageWithoutKeyword = 'This message does not contain 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(); const lastPostWithoutHighlight = await channelsPage.getLastPost();
// # Open the message in the RHS // # 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 // # Post a message with the keyword in the RHS
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`; 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 // * Verify that the keywords are highlighted
const lastPostWithHighlightInRHS = await channelsPage.sidebarRight.getLastPost(); const lastPostWithHighlightInRHS = await channelsPage.sidebarRight.getLastPost();

Просмотреть файл

@@ -484,6 +484,7 @@ export type ExperimentalAuditSettings = {
FileCompress: boolean; FileCompress: boolean;
FileMaxQueueSize: number; FileMaxQueueSize: number;
AdvancedLoggingJSON: Record<string, any>; AdvancedLoggingJSON: Record<string, any>;
Certificate: string;
}; };
export type NotificationLogSettings = { export type NotificationLogSettings = {
@@ -966,6 +967,11 @@ export type ExportSettings = {
RetentionDays: number; RetentionDays: number;
}; };
export type AccessControlSettings = {
EnableAttributeBasedAccessControl: boolean;
EnableChannelScopeAccessControl: boolean;
};
export type AdminConfig = { export type AdminConfig = {
ServiceSettings: ServiceSettings; ServiceSettings: ServiceSettings;
TeamSettings: TeamSettings; TeamSettings: TeamSettings;
@@ -1011,6 +1017,7 @@ export type AdminConfig = {
ExportSettings: ExportSettings; ExportSettings: ExportSettings;
WranglerSettings: WranglerSettings; WranglerSettings: WranglerSettings;
ConnectedWorkspacesSettings: ConnectedWorkspacesSettings; ConnectedWorkspacesSettings: ConnectedWorkspacesSettings;
AccessControlSettings: AccessControlSettings;
}; };
export type ReplicaLagSetting = { export type ReplicaLagSetting = {