upgrade playwright dependencies, screenshots and tests (#29466)
@@ -275,7 +275,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.46.1
|
image: mcr.microsoft.com/playwright:v1.49.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:
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ ${MME2E_DC_SERVER} exec -i -u "$MME2E_UID" -- playwright bash -c "cd e2e-tests/p
|
|||||||
# Documentation on the results.json file: https://playwright.dev/docs/api/class-testcase#test-case-expected-status
|
# Documentation on the results.json file: https://playwright.dev/docs/api/class-testcase#test-case-expected-status
|
||||||
# NB: the following line is needed only for compatibility reasons, to support RollingRelease tests for versions prior to v10.1.0
|
# NB: the following line is needed only for compatibility reasons, to support RollingRelease tests for versions prior to v10.1.0
|
||||||
# It can be removed after releases <=v10.0.x are phased out
|
# It can be removed after releases <=v10.0.x are phased out
|
||||||
mv -v ../playwright/playwright-report/results.json ../playwright/results/results.json 2>/dev/null || true
|
mv -v ../playwright/playwright-report/results.json ../playwright/results/reporter/results.json 2>/dev/null || true
|
||||||
jq -f /dev/stdin ../playwright/results/results.json > ../playwright/results/summary.json <<EOF
|
jq -f /dev/stdin ../playwright/results/reporter/results.json > ../playwright/results/summary.json <<EOF
|
||||||
{
|
{
|
||||||
passed: .stats.expected,
|
passed: .stats.expected,
|
||||||
failed: .stats.unexpected,
|
failed: .stats.unexpected,
|
||||||
|
|||||||
3
e2e-tests/.gitignore
поставляемый
@@ -9,3 +9,6 @@
|
|||||||
|
|
||||||
# node
|
# node
|
||||||
*.lock
|
*.lock
|
||||||
|
|
||||||
|
# build
|
||||||
|
*.tsbuildinfo
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
playwright-report
|
|
||||||
test-results
|
|
||||||
results
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
||||||
"plugins": ["@typescript-eslint"],
|
|
||||||
"root": true,
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"no-console": "error"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,10 +40,10 @@ npm run test
|
|||||||
|
|
||||||
#### 1. Run docker container using latest focal version
|
#### 1. Run docker container using latest focal version
|
||||||
|
|
||||||
Change to the root directory, then run the docker container.
|
Change to the root 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.46.1 /bin/bash
|
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.49.0-noble /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Inside the docker container
|
#### 2. Inside the docker container
|
||||||
|
|||||||
45
e2e-tests/playwright/eslint.config.mjs
Обычный файл
@@ -0,0 +1,45 @@
|
|||||||
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import path from 'node:path';
|
||||||
|
import {fileURLToPath} from 'node:url';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import {FlatCompat} from '@eslint/eslintrc';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['**/node_modules', '**/playwright-report', '**/test-results', '**/results'],
|
||||||
|
},
|
||||||
|
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended').map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.ts', '**/*.s'],
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.s'],
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': typescriptEslint,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: 5,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'no-console': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
2350
e2e-tests/playwright/package-lock.json
сгенерированный
@@ -4,7 +4,7 @@
|
|||||||
"test:update-snapshots": "cross-env PW_SNAPSHOT_ENABLE=true playwright test --update-snapshots",
|
"test:update-snapshots": "cross-env PW_SNAPSHOT_ENABLE=true playwright test --update-snapshots",
|
||||||
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=ipad",
|
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=ipad",
|
||||||
"tsc": "tsc -b",
|
"tsc": "tsc -b",
|
||||||
"lint": "eslint . --ext .js,.ts",
|
"lint": "eslint .",
|
||||||
"prettier": "prettier . --check",
|
"prettier": "prettier . --check",
|
||||||
"prettier:fix": "prettier --write .",
|
"prettier:fix": "prettier --write .",
|
||||||
"check": "npm run tsc && npm run lint && npm run prettier",
|
"check": "npm run tsc && npm run lint && npm run prettier",
|
||||||
@@ -15,27 +15,27 @@
|
|||||||
"postinstall": "script/post_install.sh"
|
"postinstall": "script/post_install.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@axe-core/playwright": "4.10.0",
|
"@axe-core/playwright": "4.10.1",
|
||||||
"@percy/cli": "1.29.3",
|
"@percy/cli": "1.30.3",
|
||||||
"@percy/playwright": "1.0.6",
|
"@percy/playwright": "1.0.7",
|
||||||
"@playwright/test": "1.46.1",
|
"@playwright/test": "1.49.0",
|
||||||
"async-wait-until": "2.0.12",
|
"async-wait-until": "2.0.12",
|
||||||
"axe-core": "4.10.0",
|
"axe-core": "4.10.2",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
"form-data-encoder": "4.0.2",
|
"form-data-encoder": "4.0.2",
|
||||||
"formdata-node": "6.0.3",
|
"formdata-node": "6.0.3",
|
||||||
"uuid": "10.0.0"
|
"uuid": "11.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/uuid": "9.0.8",
|
"@types/uuid": "10.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.2.0",
|
"@typescript-eslint/eslint-plugin": "8.16.0",
|
||||||
"@typescript-eslint/parser": "7.2.0",
|
"@typescript-eslint/parser": "8.16.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "9.16.0",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.4.1",
|
||||||
"typescript": "5.4.5"
|
"typescript": "5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import {defineConfig, devices} from '@playwright/test';
|
|||||||
import {duration} from '@e2e-support/util';
|
import {duration} from '@e2e-support/util';
|
||||||
import testConfig from '@e2e-test.config';
|
import testConfig from '@e2e-test.config';
|
||||||
|
|
||||||
const defaultOutputFolder = './results';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
globalSetup: require.resolve('./global_setup'),
|
globalSetup: require.resolve('./global_setup'),
|
||||||
forbidOnly: testConfig.isCI,
|
forbidOnly: testConfig.isCI,
|
||||||
outputDir: './results/tests',
|
outputDir: './results/output',
|
||||||
retries: testConfig.isCI ? 2 : 0,
|
retries: testConfig.isCI ? 2 : 0,
|
||||||
testDir: 'tests',
|
testDir: 'tests',
|
||||||
timeout: duration.one_min,
|
timeout: duration.one_min,
|
||||||
@@ -80,9 +78,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
reporter: [
|
reporter: [
|
||||||
['html', {open: 'never', outputFolder: defaultOutputFolder}],
|
['html', {open: 'never', outputFolder: './results/reporter'}],
|
||||||
['json', {outputFile: `${defaultOutputFolder}/results.json`}],
|
['json', {outputFile: './results/reporter/results.json'}],
|
||||||
['junit', {outputFile: `${defaultOutputFolder}/results.xml`}],
|
['junit', {outputFile: './results/reporter/results.xml'}],
|
||||||
['list'],
|
['list'],
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ dayjs.extend(duration);
|
|||||||
const {TYPE, SERVER_TYPE, BRANCH, PULL_REQUEST, BUILD_ID, MM_ENV, MM_DOCKER_IMAGE, MM_DOCKER_TAG, RELEASE_DATE} =
|
const {TYPE, SERVER_TYPE, BRANCH, PULL_REQUEST, BUILD_ID, MM_ENV, MM_DOCKER_IMAGE, MM_DOCKER_TAG, RELEASE_DATE} =
|
||||||
process.env;
|
process.env;
|
||||||
|
|
||||||
const resultsFile = 'results/results.json';
|
const resultsFile = 'results/reporter/results.json';
|
||||||
const summaryFile = 'results/summary.json';
|
const summaryFile = 'results/summary.json';
|
||||||
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf8'));
|
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf8'));
|
||||||
const summary = JSON.parse(fs.readFileSync(summaryFile, 'utf8'));
|
const summary = JSON.parse(fs.readFileSync(summaryFile, 'utf8'));
|
||||||
const passRate = summary.passed / (summary.passed + summary.failed);
|
const passRate = (summary.passed * 100) / (summary.passed + summary.failed);
|
||||||
const totalSpecs = summary.passed + summary.failed;
|
const totalSpecs = summary.passed + summary.failed;
|
||||||
const playwrightVersion = results.config.version;
|
const playwrightVersion = results.config.version;
|
||||||
const playwrightDuration = dayjs.duration(results.stats.duration, 'seconds').format('HH:mm:ss');
|
const playwrightDuration = dayjs.duration(results.stats.duration, 'millisecond').format('HH:mm:ss');
|
||||||
|
|
||||||
function generateTitle() {
|
function generateTitle() {
|
||||||
let dockerImageLink = '';
|
let dockerImageLink = '';
|
||||||
@@ -68,7 +68,7 @@ function generateWebhookBody() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const summaryField = `${(passRate * 100).toFixed(2)}% (${summary.passed}/${totalSpecs}) | ${playwrightDuration} | playwright@${playwrightVersion}`;
|
const summaryField = `${passRate.toFixed(2)}% (${summary.passed}/${totalSpecs}) | ${playwrightDuration} | playwright@${playwrightVersion}`;
|
||||||
const serverTypeField = SERVER_TYPE ? '\nTest server: ' + SERVER_TYPE : '';
|
const serverTypeField = SERVER_TYPE ? '\nTest server: ' + SERVER_TYPE : '';
|
||||||
const mmEnvField = MM_ENV ? '\nTest server override: ' + MM_ENV : '';
|
const mmEnvField = MM_ENV ? '\nTest server override: ' + MM_ENV : '';
|
||||||
const rollingReleaseMatchRegex = BUILD_ID?.match(/-rolling(?<version>[^-]+)-/);
|
const rollingReleaseMatchRegex = BUILD_ID?.match(/-rolling(?<version>[^-]+)-/);
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export async function requestTrialLicense() {
|
|||||||
users: 100,
|
users: 100,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
expect(error, 'Failed to request trial license').toBeFalsy();
|
expect(error, 'Failed to request trial license').toBeFalsy();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Should be based only from the generated default config from ./server via "make config-reset"
|
// Should be based only from the generated default config from ./server via "make config-reset"
|
||||||
// Based on v10.2 server
|
// Based on v10.3 server
|
||||||
const defaultServerConfig: AdminConfig = {
|
const defaultServerConfig: AdminConfig = {
|
||||||
ServiceSettings: {
|
ServiceSettings: {
|
||||||
SiteURL: '',
|
SiteURL: '',
|
||||||
@@ -198,6 +198,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
RefreshPostStatsRunTime: '00:00',
|
RefreshPostStatsRunTime: '00:00',
|
||||||
MaximumPayloadSizeBytes: 300000,
|
MaximumPayloadSizeBytes: 300000,
|
||||||
MaximumURLLength: 2048,
|
MaximumURLLength: 2048,
|
||||||
|
ScheduledPosts: true,
|
||||||
},
|
},
|
||||||
TeamSettings: {
|
TeamSettings: {
|
||||||
SiteName: 'Mattermost',
|
SiteName: 'Mattermost',
|
||||||
@@ -685,6 +686,9 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
'com.mattermost.nps': {
|
'com.mattermost.nps': {
|
||||||
Enable: true,
|
Enable: true,
|
||||||
},
|
},
|
||||||
|
'mattermost-ai': {
|
||||||
|
Enable: true,
|
||||||
|
},
|
||||||
playbooks: {
|
playbooks: {
|
||||||
Enable: true,
|
Enable: true,
|
||||||
},
|
},
|
||||||
@@ -724,6 +728,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
TestFeature: 'off',
|
TestFeature: 'off',
|
||||||
TestBoolFeature: false,
|
TestBoolFeature: false,
|
||||||
EnableRemoteClusterService: false,
|
EnableRemoteClusterService: false,
|
||||||
|
EnableSharedChannelsDMs: false,
|
||||||
AppsEnabled: false,
|
AppsEnabled: false,
|
||||||
PermalinkPreviews: false,
|
PermalinkPreviews: false,
|
||||||
NormalizeLdapDNs: false,
|
NormalizeLdapDNs: false,
|
||||||
@@ -741,6 +746,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
WebSocketEventScope: true,
|
WebSocketEventScope: true,
|
||||||
NotificationMonitoring: true,
|
NotificationMonitoring: true,
|
||||||
ExperimentalAuditSettingsSystemConsoleUI: false,
|
ExperimentalAuditSettingsSystemConsoleUI: false,
|
||||||
|
ExperimentalCrossTeamSearch: false,
|
||||||
},
|
},
|
||||||
ImportSettings: {
|
ImportSettings: {
|
||||||
Directory: './import',
|
Directory: './import',
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {expect, Locator} from '@playwright/test';
|
||||||
|
|
||||||
|
export default class SearchPopover {
|
||||||
|
readonly container: Locator;
|
||||||
|
|
||||||
|
readonly messagesButton;
|
||||||
|
readonly filesButton;
|
||||||
|
readonly searchInput;
|
||||||
|
readonly searchBoxClose;
|
||||||
|
readonly selectedSuggestion;
|
||||||
|
readonly searchHints;
|
||||||
|
|
||||||
|
constructor(container: Locator) {
|
||||||
|
this.container = container;
|
||||||
|
|
||||||
|
this.messagesButton = container.getByRole('button', {name: 'Messages'});
|
||||||
|
this.filesButton = container.getByRole('button', {name: 'Files'});
|
||||||
|
this.searchInput = container.getByLabel('Search messages');
|
||||||
|
this.searchBoxClose = container.getByTestId('searchBoxClose');
|
||||||
|
this.selectedSuggestion = container.locator('.suggestion--selected').locator('.suggestion-list__main');
|
||||||
|
this.searchHints = container.locator('#searchHints');
|
||||||
|
}
|
||||||
|
|
||||||
|
async toBeVisible() {
|
||||||
|
await expect(this.container).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedSuggestion() {
|
||||||
|
return this.searchHints.locator('.suggestion--selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {SearchPopover};
|
||||||
@@ -43,6 +43,11 @@ export default class GlobalHeader {
|
|||||||
await expect(this.searchBox).toBeVisible();
|
await expect(this.searchBox).toBeVisible();
|
||||||
await this.searchBox.click();
|
await this.searchBox.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async closeSearch() {
|
||||||
|
await expect(this.searchBox).toBeVisible();
|
||||||
|
await this.searchBox.getByTestId('input-clear').click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {GlobalHeader};
|
export {GlobalHeader};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {FindChannelsModal} from './channels/find_channels_modal';
|
|||||||
import {SettingsModal} from './channels/settings/settings_modal';
|
import {SettingsModal} from './channels/settings/settings_modal';
|
||||||
import {Footer} from './footer';
|
import {Footer} from './footer';
|
||||||
import {GlobalHeader} from './global_header';
|
import {GlobalHeader} from './global_header';
|
||||||
|
import {SearchPopover} from './channels/search_popover';
|
||||||
import {MainHeader} from './main_header';
|
import {MainHeader} from './main_header';
|
||||||
import {PostDotMenu} from './channels/post_dot_menu';
|
import {PostDotMenu} from './channels/post_dot_menu';
|
||||||
import {PostReminderMenu} from './channels/post_reminder_menu';
|
import {PostReminderMenu} from './channels/post_reminder_menu';
|
||||||
@@ -33,6 +34,7 @@ import {SystemUsersColumnToggleMenu} from './system_console/sections/system_user
|
|||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
GlobalHeader,
|
GlobalHeader,
|
||||||
|
SearchPopover,
|
||||||
ChannelsCenterView,
|
ChannelsCenterView,
|
||||||
ChannelsSidebarLeft,
|
ChannelsSidebarLeft,
|
||||||
ChannelsSidebarRight,
|
ChannelsSidebarRight,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default class ChannelsPage {
|
|||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
|
|
||||||
readonly globalHeader;
|
readonly globalHeader;
|
||||||
|
readonly searchPopover;
|
||||||
readonly centerView;
|
readonly centerView;
|
||||||
readonly scheduledDraftDropdown;
|
readonly scheduledDraftDropdown;
|
||||||
readonly scheduledDraftModal;
|
readonly scheduledDraftModal;
|
||||||
@@ -33,6 +34,7 @@ export default class ChannelsPage {
|
|||||||
|
|
||||||
// The main areas of the app
|
// The main areas of the app
|
||||||
this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
|
this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
|
||||||
|
this.searchPopover = new components.SearchPopover(page.locator('#searchPopover'));
|
||||||
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'));
|
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'));
|
||||||
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
||||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs, opti
|
|||||||
|
|
||||||
if (testConfig.snapshotEnabled) {
|
if (testConfig.snapshotEnabled) {
|
||||||
// Visual test with built-in snapshot
|
// Visual test with built-in snapshot
|
||||||
const filename = testInfo.title.replace(illegalRe, '').replace(/\s/g, '-').trim().toLowerCase();
|
const filename = testInfo.title.trim().replace(illegalRe, '').replace(/\s/g, '-').trim().toLowerCase();
|
||||||
await expect(testArgs.page).toHaveScreenshot(`${filename}.png`, {fullPage: true, ...options});
|
await expect(testArgs.page).toHaveScreenshot(`${filename}.png`, {fullPage: true, ...options});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use
|
|||||||
try {
|
try {
|
||||||
await adminClient.deletePost(adminPost.id);
|
await adminClient.deletePost(adminPost.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed to delete post by admin');
|
throw new Error(`Failed to delete post by admin: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// # Open the last post in the channel sent by admin again
|
// # Open the last post in the channel sent by admin again
|
||||||
|
|||||||
@@ -17,16 +17,42 @@ test('Search box suggestion must be case insensitive', async ({pw, pages}) => {
|
|||||||
// # Open the search UI
|
// # Open the search UI
|
||||||
await channelsPage.globalHeader.openSearch();
|
await channelsPage.globalHeader.openSearch();
|
||||||
|
|
||||||
const searchInput = await page.getByPlaceholder('Search messages');
|
const searchWord = 'off';
|
||||||
|
const searchOutput = 'In:off-topic';
|
||||||
|
const channelName = 'Off-Topic';
|
||||||
|
|
||||||
// * it's working when using lowercase
|
// Should work as expected when using lowercase
|
||||||
await searchInput.fill('In:off');
|
// # Type in lowercase "off" to search for the "Off-Topic" channel
|
||||||
await searchInput.press('Enter');
|
const {searchInput} = await channelsPage.searchPopover;
|
||||||
await expect(searchInput).toHaveValue('In:off-topic ');
|
await searchInput.pressSequentially(`In:${searchWord}`);
|
||||||
|
|
||||||
// * it's working when using uppercase
|
// * The suggestion should be visible
|
||||||
await searchInput.clear();
|
await expect(channelsPage.searchPopover.selectedSuggestion).toBeVisible();
|
||||||
await searchInput.fill('In:Off');
|
await expect(channelsPage.searchPopover.selectedSuggestion).toHaveText(channelName);
|
||||||
|
|
||||||
|
// # Press Enter to select the suggestion and another Enter to search
|
||||||
await searchInput.press('Enter');
|
await searchInput.press('Enter');
|
||||||
await expect(searchInput).toHaveValue('In:off-topic ');
|
await searchInput.press('Enter');
|
||||||
|
|
||||||
|
// * The search box should contain the selected suggestion
|
||||||
|
await expect(channelsPage.globalHeader.searchBox.getByText(searchOutput, {exact: true})).toBeVisible();
|
||||||
|
|
||||||
|
// Should work as expected when using uppercase
|
||||||
|
// # Close then open the search UI
|
||||||
|
await channelsPage.globalHeader.closeSearch();
|
||||||
|
await channelsPage.globalHeader.openSearch();
|
||||||
|
|
||||||
|
// # Type in uppercase "OFF" to search for the "Off-Topic" channel
|
||||||
|
await searchInput.pressSequentially(`In:${searchWord.toUpperCase()}`);
|
||||||
|
|
||||||
|
// * The suggestion should be visible
|
||||||
|
await expect(channelsPage.searchPopover.selectedSuggestion).toBeVisible();
|
||||||
|
await expect(channelsPage.searchPopover.selectedSuggestion).toHaveText(channelName);
|
||||||
|
|
||||||
|
// # Press Enter to select the suggestion and another Enter to search
|
||||||
|
await searchInput.press('Enter');
|
||||||
|
await searchInput.press('Enter');
|
||||||
|
|
||||||
|
// * The search box should contain the selected suggestion
|
||||||
|
await expect(channelsPage.globalHeader.searchBox.getByText(searchOutput, {exact: true})).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,15 +8,8 @@ async function interceptConfigWithLandingPage(page: Page, enabled: boolean) {
|
|||||||
route.fulfill({
|
route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
EmailLoginButtonBorderColor: '#2389D7',
|
|
||||||
EmailLoginButtonColor: '#0000',
|
|
||||||
EmailLoginButtonTextColor: '#2389D7',
|
|
||||||
EnableDesktopLandingPage: enabled,
|
EnableDesktopLandingPage: enabled,
|
||||||
EnableSignInWithEmail: 'true',
|
|
||||||
EnableSignInWithUsername: 'true',
|
EnableSignInWithUsername: 'true',
|
||||||
EnableSignUpWithEmail: 'true',
|
|
||||||
SiteName: 'Mattermost',
|
|
||||||
SiteURL: 'http://localhost:8065',
|
|
||||||
}),
|
}),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
});
|
});
|
||||||
@@ -29,7 +22,7 @@ test('MM-T5640_1 should not see landing page ', async ({pw, pages, page}) => {
|
|||||||
await interceptConfigWithLandingPage(page, false);
|
await interceptConfigWithLandingPage(page, false);
|
||||||
|
|
||||||
// Navigate to your starting URL
|
// Navigate to your starting URL
|
||||||
await page.goto('http://localhost:8065');
|
await page.goto('/');
|
||||||
|
|
||||||
// Wait until the URL contains '/login'
|
// Wait until the URL contains '/login'
|
||||||
await page.waitForURL(/.*\/login.*/);
|
await page.waitForURL(/.*\/login.*/);
|
||||||
@@ -44,11 +37,11 @@ test('MM-T5640_1 should not see landing page ', async ({pw, pages, page}) => {
|
|||||||
|
|
||||||
test('MM-T5640_2 should see landing page', async ({pages, isMobile, page}) => {
|
test('MM-T5640_2 should see landing page', async ({pages, isMobile, page}) => {
|
||||||
// Navigate to your starting URL
|
// Navigate to your starting URL
|
||||||
await page.goto('http://localhost:8065');
|
await page.goto('/');
|
||||||
|
|
||||||
await page.evaluate(() => localStorage.clear());
|
await page.evaluate(() => localStorage.clear());
|
||||||
|
|
||||||
await page.goto('http://localhost:8065');
|
await page.goto('/');
|
||||||
|
|
||||||
// Wait until the URL contains '/landing'
|
// Wait until the URL contains '/landing'
|
||||||
await page.waitForURL(/.*\/landing.*/);
|
await page.waitForURL(/.*\/landing.*/);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ test('/login', async ({pw, pages, page, browserName, viewport}, testInfo) => {
|
|||||||
// Go to login page
|
// Go to login page
|
||||||
const {adminClient} = await pw.getAdminClient();
|
const {adminClient} = await pw.getAdminClient();
|
||||||
const adminConfig = await adminClient.getConfig();
|
const adminConfig = await adminClient.getConfig();
|
||||||
|
const license = await adminClient.getClientLicenseOld();
|
||||||
const loginPage = new pages.LoginPage(page, adminConfig);
|
const loginPage = new pages.LoginPage(page, adminConfig);
|
||||||
await loginPage.goto();
|
await loginPage.goto();
|
||||||
await loginPage.toBeVisible();
|
await loginPage.toBeVisible();
|
||||||
@@ -16,7 +17,8 @@ test('/login', async ({pw, pages, page, browserName, viewport}, testInfo) => {
|
|||||||
|
|
||||||
// Match snapshot of login page
|
// Match snapshot of login page
|
||||||
const testArgs = {page, browserName, viewport};
|
const testArgs = {page, browserName, viewport};
|
||||||
await pw.matchSnapshot(testInfo, testArgs);
|
const editionSuffix = license.IsLicensed === 'true' ? '' : 'free edition';
|
||||||
|
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} ${editionSuffix}`}, testArgs);
|
||||||
|
|
||||||
// Click sign in button without entering user credential
|
// Click sign in button without entering user credential
|
||||||
await loginPage.signInButton.click();
|
await loginPage.signInButton.click();
|
||||||
@@ -24,5 +26,5 @@ test('/login', async ({pw, pages, page, browserName, viewport}, testInfo) => {
|
|||||||
await pw.waitForAnimationEnd(loginPage.bodyCard);
|
await pw.waitForAnimationEnd(loginPage.bodyCard);
|
||||||
|
|
||||||
// Match snapshot of login page with error
|
// Match snapshot of login page with error
|
||||||
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error`}, testArgs);
|
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error ${editionSuffix}`}, testArgs);
|
||||||
});
|
});
|
||||||
|
|||||||
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-free-edition-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 138 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-free-edition-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 275 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-free-edition-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 125 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-free-edition-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 139 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-free-edition-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 280 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-free-edition-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 120 KiB |
@@ -7,6 +7,7 @@ test('/signup_email', async ({pw, pages, page, browserName, viewport}, testInfo)
|
|||||||
// Go to login page
|
// Go to login page
|
||||||
const {adminClient} = await pw.getAdminClient();
|
const {adminClient} = await pw.getAdminClient();
|
||||||
const adminConfig = await adminClient.getConfig();
|
const adminConfig = await adminClient.getConfig();
|
||||||
|
const license = await adminClient.getClientLicenseOld();
|
||||||
const loginPage = new pages.LoginPage(page, adminConfig);
|
const loginPage = new pages.LoginPage(page, adminConfig);
|
||||||
await loginPage.goto();
|
await loginPage.goto();
|
||||||
await loginPage.toBeVisible();
|
await loginPage.toBeVisible();
|
||||||
@@ -23,7 +24,8 @@ test('/signup_email', async ({pw, pages, page, browserName, viewport}, testInfo)
|
|||||||
|
|
||||||
// Match snapshot of signup_email page
|
// Match snapshot of signup_email page
|
||||||
const testArgs = {page, browserName, viewport};
|
const testArgs = {page, browserName, viewport};
|
||||||
await pw.matchSnapshot(testInfo, testArgs);
|
const editionSuffix = license.IsLicensed === 'true' ? '' : 'free edition';
|
||||||
|
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} ${editionSuffix}`}, testArgs);
|
||||||
|
|
||||||
// Click sign in button without entering user credential
|
// Click sign in button without entering user credential
|
||||||
const invalidUser = {email: 'invalid', username: 'a', password: 'b'};
|
const invalidUser = {email: 'invalid', username: 'a', password: 'b'};
|
||||||
@@ -34,5 +36,5 @@ test('/signup_email', async ({pw, pages, page, browserName, viewport}, testInfo)
|
|||||||
await pw.waitForAnimationEnd(signupPage.bodyCard);
|
await pw.waitForAnimationEnd(signupPage.bodyCard);
|
||||||
|
|
||||||
// Match snapshot of signup_email page
|
// Match snapshot of signup_email page
|
||||||
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error`}, testArgs);
|
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error ${editionSuffix}`}, testArgs);
|
||||||
});
|
});
|
||||||
|
|||||||
|
После Ширина: | Высота: | Размер: 159 KiB |
|
После Ширина: | Высота: | Размер: 301 KiB |
|
После Ширина: | Высота: | Размер: 161 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-free-edition-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 153 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-free-edition-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 296 KiB |
Двоичные данные
e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-free-edition-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 155 KiB |
@@ -225,6 +225,7 @@ const SearchBox = forwardRef(
|
|||||||
role='searchbox'
|
role='searchbox'
|
||||||
>
|
>
|
||||||
<CloseIcon
|
<CloseIcon
|
||||||
|
data-testid='searchBoxClose'
|
||||||
className='btn btn-icon btn-m'
|
className='btn btn-icon btn-m'
|
||||||
onClick={closeHandler}
|
onClick={closeHandler}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ export type ServiceSettings = {
|
|||||||
EnableAPIPostDeletion: boolean;
|
EnableAPIPostDeletion: boolean;
|
||||||
EnableDesktopLandingPage: boolean;
|
EnableDesktopLandingPage: boolean;
|
||||||
MaximumURLLength: number;
|
MaximumURLLength: number;
|
||||||
|
ScheduledPosts: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TeamSettings = {
|
export type TeamSettings = {
|
||||||
|
|||||||