CLD-5948 Playwright/E2E: Update dependencies, server default config and its types, and remove Boards and mobile view tests (#24583)

* update dependencies

* update dependencies

* remove mobile view

* remove boards

* update default config  and its types

* update snapshots

* fix formatting

* check works and fix styling
Этот коммит содержится в:
Saturnino Abril
2023-09-20 05:28:35 +08:00
коммит произвёл GitHub
родитель 88d043a971
Коммит b7b08dbc0f
63 изменённых файлов: 657 добавлений и 834 удалений

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

@@ -6,7 +6,7 @@
# Typically run the local server with:
cd server && make run
# Or build and distribute webapp including channels, boards and playbooks
# 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 test inside the Playwright's docker container.
cd webapp && make dist
@@ -45,7 +45,7 @@ npm run test
Change to root directory, run docker container
```
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.36.0-focal /bin/bash
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.38.0-jammy /bin/bash
```
#### 2. Inside the docker container

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

@@ -48,7 +48,7 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
await client.createTeam(createRandomTeam(defaultTeam.name, defaultTeam.displayName, 'O', false));
} else if (myDefaultTeam && testConfig.resetBeforeTest) {
await Promise.all(
myTeams.filter((team) => team.name !== defaultTeam.name).map((team) => client.deleteTeam(team.id))
myTeams.filter((team) => team.name !== defaultTeam.name).map((team) => client.deleteTeam(team.id)),
);
const myChannels = await client.getMyChannels(myDefaultTeam.id);
@@ -61,7 +61,7 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
channel.name !== 'off-topic'
);
})
.map((channel) => client.deleteChannel(channel.id))
.map((channel) => client.deleteChannel(channel.id)),
);
}
@@ -170,7 +170,7 @@ async function ensureServerDeployment(client: Client) {
sameClusterName,
sameClusterName
? ''
: `Should have cluster name set and as expected. Got "${ClusterName}" but expected "${haClusterName}"`
: `Should have cluster name set and as expected. Got "${ClusterName}" but expected "${haClusterName}"`,
).toBe(true);
const clusterInfo = await client.getClusterStatus();
@@ -179,12 +179,12 @@ async function ensureServerDeployment(client: Client) {
sameCount,
sameCount
? ''
: `Should match number of nodes in a cluster as expected. Got "${clusterInfo?.length}" but expected "${haClusterNodeCount}"`
: `Should match number of nodes in a cluster as expected. Got "${clusterInfo?.length}" but expected "${haClusterNodeCount}"`,
).toBe(true);
clusterInfo.forEach((info) =>
// eslint-disable-next-line no-console
console.log(`hostname: ${info.hostname}, version: ${info.version}, config_hash: ${info.config_hash}`)
console.log(`hostname: ${info.hostname}, version: ${info.version}, config_hash: ${info.config_hash}`),
);
}
}

950
e2e-tests/playwright/package-lock.json сгенерированный

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

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

@@ -4,34 +4,36 @@
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=iphone --project=ipad",
"tsc": "tsc -b",
"lint": "eslint . --ext .js,.ts",
"prettier": "prettier --write .",
"prettier": "prettier . --check",
"prettier:fix": "prettier --write .",
"check": "npm run tsc && npm run lint && npm run prettier",
"codegen": "cross-env playwright codegen $PW_BASE_URL",
"playwright-ui": "playwright test --ui",
"test-slomo": "cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
"show-report": "npx playwright show-report"
"show-report": "npx playwright show-report",
"postinstall": "npx playwright install"
},
"dependencies": {
"@axe-core/playwright": "4.7.3",
"@percy/cli": "1.26.2",
"@percy/cli": "1.27.1",
"@percy/playwright": "1.0.4",
"@playwright/test": "1.36.1",
"@playwright/test": "1.38.0",
"async-wait-until": "2.0.12",
"axe-core": "4.7.2",
"axe-core": "4.8.1",
"chalk": "4.1.2",
"deepmerge": "4.3.1",
"dotenv": "16.3.1",
"form-data": "4.0.0",
"isomorphic-unfetch": "4.0.2",
"uuid": "9.0.0"
"uuid": "9.0.1"
},
"devDependencies": {
"@types/uuid": "9.0.2",
"@typescript-eslint/eslint-plugin": "6.1.0",
"@typescript-eslint/parser": "6.1.0",
"@types/uuid": "9.0.4",
"@typescript-eslint/eslint-plugin": "6.7.0",
"@typescript-eslint/parser": "6.7.0",
"cross-env": "7.0.3",
"eslint": "8.45.0",
"prettier": "2.8.7",
"typescript": "5.0.4"
"eslint": "8.49.0",
"prettier": "3.0.3",
"typescript": "5.2.2"
}
}

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

@@ -52,13 +52,6 @@ export default defineConfig({
},
},
projects: [
{
name: 'iphone',
use: {
browserName: 'chromium',
...devices['iPhone 13 Pro'],
},
},
{
name: 'ipad',
use: {

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

@@ -16,41 +16,38 @@
# 5. PW_ADMIN_EMAIL
# - Default to "sysadmin@sample.mattermost.com" if not set.
# 6. PW_BOARDS_PRODUCT_ENABLED
# - Default to "true" if not set. Used to correctly set server config.
# 7. PW_HA_CLUSTER_ENABLED
# 6. PW_HA_CLUSTER_ENABLED
# - Default to "false" if not set. Set to true if the test server is with HA enabled.
# 8. PW_HA_CLUSTER_NODE_COUNT
# 7. PW_HA_CLUSTER_NODE_COUNT
# - Default to "2" if not set.
# 9. PW_HA_CLUSTER_NAME
# 8. PW_HA_CLUSTER_NAME
# - Default to "mm_dev_cluster" if not set.
# 10. PW_RESET_BEFORE_TEST
# 9. PW_RESET_BEFORE_TEST
# - Default to "false" if not set. If true, the setup deletes all teams and channels other than the default team which is "ad-1".
# 11. CI
# 10. CI
# - Default to "false" if not set.
# 12. PW_HEADLESS
# 11. PW_HEADLESS
# - Default to "false" or headless mode if not set. Set to true to run test in headed mode.
# 13. PW_SLOWMO
# 12. PW_SLOWMO
# - Default to "0" if not set which means normal test speed run. Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
# 14. PW_WORKERS
# 13. PW_WORKERS
# - Default to "1" if not set. The maximum number of concurrent worker processes to use for parallelizing tests.
# 15. PW_SNAPSHOT_ENABLE
# 14. PW_SNAPSHOT_ENABLE
# - Default to "false" if not set. Set to true to enable snapshot testing.
# Note that, snapshot testing should be done in Playwright docker image only.
# This is to ensure that, there's a common base platform for all contributors
# regardless of each local development platform.
# 16. PW_PERCY_ENABLE
# 15. PW_PERCY_ENABLE
# - Default to "false" if not set. Use to save and compare results via https://percy.io/.
# 17. PERCY_TOKEN
# 16. PERCY_TOKEN
# - A token required by https://percy.io/.

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

@@ -2,6 +2,5 @@
// See LICENSE.txt for license information.
export const appsPluginId = 'com.mattermost.apps';
export const boardsPluginId = 'focalboard';
export const callsPluginId = 'com.mattermost.calls';
export const playbooksPluginId = 'playbooks';

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

@@ -3,11 +3,10 @@
import os from 'node:os';
import {expect, test} from '@playwright/test';
import {expect} from '@playwright/test';
import {callsPluginId} from './constant';
import {getAdminClient} from './server/init';
import {isSmallScreen} from './util';
export async function shouldHaveCallsEnabled(enabled = true) {
const {adminClient} = await getAdminClient();
@@ -26,14 +25,10 @@ export async function shouldHaveFeatureFlag(name: string, value: string | boolea
const matched = config.FeatureFlags[name] === value;
expect(
matched,
matched ? '' : `FeatureFlags["${name}'] expect "${value}" but actual "${config.FeatureFlags[name]}"`
matched ? '' : `FeatureFlags["${name}'] expect "${value}" but actual "${config.FeatureFlags[name]}"`,
).toBeTruthy();
}
export function shouldSkipInSmallScreen() {
test.skip(({viewport}) => isSmallScreen(viewport), 'Not applicable to mobile device');
}
export async function shouldRunInLinux() {
const platform = os.platform();
await expect(platform, 'Run in Linux or Playwright docker image only').toBe('linux');

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

@@ -35,9 +35,6 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
Enable: testConfig.haClusterEnabled,
ClusterName: testConfig.haClusterName,
},
ExperimentalSettings: {
DisableAppBar: false,
},
PasswordSettings: {
MinimumLength: 5,
Lowercase: false,
@@ -48,9 +45,15 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
},
PluginSettings: {
EnableUploads: true,
Plugins: {
PluginStates: {
'com.mattermost.calls': {
defaultenabled: true,
Enable: false,
},
'com.mattermost.nps': {
Enable: false,
},
playbooks: {
Enable: true,
},
},
},
@@ -65,7 +68,7 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
};
// Should be based only from the generated default config from ./server via "make config-reset"
// Based on v7.10 server
// Based on v9.1 server
const defaultServerConfig: AdminConfig = {
ServiceSettings: {
SiteURL: '',
@@ -157,6 +160,11 @@ const defaultServerConfig: AdminConfig = {
EnableLatex: false,
EnableInlineLatex: true,
PostPriority: true,
AllowPersistentNotifications: true,
AllowPersistentNotificationsForGuests: false,
PersistentNotificationIntervalMinutes: 5,
PersistentNotificationMaxCount: 6,
PersistentNotificationMaxRecipients: 5,
EnableAPIChannelDeletion: false,
EnableLocalMode: false,
LocalModeSocketLocation: '/var/tmp/mattermost_local.socket',
@@ -170,15 +178,11 @@ const defaultServerConfig: AdminConfig = {
EnableCustomGroups: true,
SelfHostedPurchase: true,
AllowSyncedDrafts: true,
AllowPersistentNotifications: true,
PersistentNotificationMaxCount: 6,
PersistentNotificationMaxRecipients: 5,
PersistentNotificationIntervalMinutes: 5,
AllowPersistentNotificationsForGuests: false,
},
TeamSettings: {
SiteName: 'Mattermost',
MaxUsersPerTeam: 50,
EnableJoinLeaveMessageByDefault: true,
EnableUserCreation: true,
EnableOpenServer: false,
EnableUserDeactivation: false,
@@ -222,6 +226,7 @@ const defaultServerConfig: AdminConfig = {
DisableDatabaseSearch: false,
MigrationsStatementTimeoutSeconds: 100000,
ReplicaLagSettings: [],
ReplicaMonitorIntervalSeconds: 5,
},
LogSettings: {
EnableConsole: true,
@@ -236,6 +241,7 @@ const defaultServerConfig: AdminConfig = {
EnableDiagnostics: true,
VerboseDiagnostics: false,
EnableSentry: true,
AdvancedLoggingJSON: {},
AdvancedLoggingConfig: '',
},
ExperimentalAuditSettings: {
@@ -246,6 +252,7 @@ const defaultServerConfig: AdminConfig = {
FileMaxBackups: 0,
FileCompress: false,
FileMaxQueueSize: 1000,
AdvancedLoggingJSON: {},
AdvancedLoggingConfig: '',
},
NotificationLogSettings: {
@@ -257,6 +264,7 @@ const defaultServerConfig: AdminConfig = {
FileLevel: 'INFO',
FileJson: true,
FileLocation: '',
AdvancedLoggingJSON: {},
AdvancedLoggingConfig: '',
},
PasswordSettings: {
@@ -292,6 +300,21 @@ const defaultServerConfig: AdminConfig = {
AmazonS3SSE: false,
AmazonS3Trace: false,
AmazonS3RequestTimeoutMilliseconds: 30000,
DedicatedExportStore: false,
ExportDriverName: 'local',
ExportDirectory: './data/',
ExportAmazonS3AccessKeyId: '',
ExportAmazonS3SecretAccessKey: '',
ExportAmazonS3Bucket: '',
ExportAmazonS3PathPrefix: '',
ExportAmazonS3Region: '',
ExportAmazonS3Endpoint: 's3.amazonaws.com',
ExportAmazonS3SSL: true,
ExportAmazonS3SignV2: false,
ExportAmazonS3SSE: false,
ExportAmazonS3Trace: false,
ExportAmazonS3RequestTimeoutMilliseconds: 30000,
ExportAmazonS3PresignExpiresSeconds: 21600,
},
EmailSettings: {
EnableSignUpWithEmail: true,
@@ -343,7 +366,7 @@ const defaultServerConfig: AdminConfig = {
SupportSettings: {
TermsOfServiceLink: 'https://mattermost.com/pl/terms-of-use/',
PrivacyPolicyLink: 'https://mattermost.com/pl/privacy-policy/',
AboutLink: 'https://docs.mattermost.com/pl/about-mattermost',
AboutLink: 'https://mattermost.com/pl/about-mattermost',
HelpLink: 'https://mattermost.com/pl/help/',
ReportAProblemLink: 'https://mattermost.com/pl/report-a-bug',
ForgotPasswordLink: '',
@@ -538,7 +561,7 @@ const defaultServerConfig: AdminConfig = {
UseNewSAMLLibrary: false,
EnableSharedChannels: false,
EnableRemoteClusterService: false,
DisableAppBar: true,
DisableAppBar: false,
DisableRefetchingOnBrowserFocus: false,
DelayChannelAutocomplete: false,
},
@@ -582,10 +605,14 @@ const defaultServerConfig: AdminConfig = {
DataRetentionSettings: {
EnableMessageDeletion: false,
EnableFileDeletion: false,
EnableBoardsDeletion: false,
MessageRetentionDays: 365,
FileRetentionDays: 365,
BoardsRetentionDays: 365,
DeletionJobStartTime: '02:00',
BatchSize: 3000,
TimeBetweenBatchesMilliseconds: 100,
RetentionIdsBatchSize: 100,
},
MessageExportSettings: {
EnableExport: false,
@@ -624,6 +651,9 @@ const defaultServerConfig: AdminConfig = {
'com.mattermost.nps': {
Enable: true,
},
playbooks: {
Enable: true,
},
},
EnableMarketplace: true,
EnableRemoteMarketplace: true,
@@ -635,6 +665,7 @@ const defaultServerConfig: AdminConfig = {
},
DisplaySettings: {
CustomURLSchemes: [],
MaxMarkdownNodes: 0,
ExperimentalTimezone: true,
},
GuestAccountsSettings: {
@@ -653,30 +684,24 @@ const defaultServerConfig: AdminConfig = {
CloudSettings: {
CWSURL: 'https://customers.mattermost.com',
CWSAPIURL: 'https://portal.internal.prod.cloud.mattermost.com',
CWSMock: false,
},
FeatureFlags: {
TestFeature: 'off',
TestBoolFeature: false,
EnableRemoteClusterService: false,
AppsEnabled: true,
PluginPlaybooks: '',
PluginApps: '',
PluginFocalboard: '',
PluginCalls: '',
PermalinkPreviews: true,
PermalinkPreviews: false,
CallsEnabled: true,
BoardsFeatureFlags: '',
NormalizeLdapDNs: false,
GraphQL: false,
CommandPalette: false,
SendWelcomePost: true,
PostPriority: true,
PostPriority: false,
WysiwygEditor: false,
ThreadsEverywhere: false,
OnboardingTourTips: true,
DeprecateCloudFree: false,
CloudReverseTrial: false,
StreamlinedMarketplace: true
EnableExportDirectDownload: false,
StreamlinedMarketplace: true,
},
ImportSettings: {
Directory: './import',

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

@@ -24,7 +24,7 @@ export async function initSetup({
const {adminClient, adminUser} = await getAdminClient();
if (!adminClient) {
throw new Error(
"Failed to setup admin: Check that you're able to access the server using the same admin credential."
"Failed to setup admin: Check that you're able to access the server using the same admin credential.",
);
}
@@ -83,8 +83,8 @@ export async function initSetup({
// eslint-disable-next-line no-console
console.log(
chalk.green(
`This failed due to the experimental fetch support in Node.js starting v18.0.0.\nYou may set environment variable: "export NODE_OPTIONS='--no-experimental-fetch'", then try again.'`
)
`This failed due to the experimental fetch support in Node.js starting v18.0.0.\nYou may set environment variable: "export NODE_OPTIONS='--no-experimental-fetch'", then try again.'`,
),
);
}
expect(err, 'Should not throw an error').toBeFalsy();

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

@@ -16,6 +16,6 @@ export async function hideDynamicChannelsContent(page: Page) {
export async function waitForAnimationEnd(locator: Locator) {
return locator.evaluate((element) =>
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished))
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished)),
);
}

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

@@ -1,11 +1,10 @@
import {test as base, Browser, Page, ViewportSize} from '@playwright/test';
import {test as base, Browser, Page} from '@playwright/test';
import {AxeResults} from 'axe-core';
import AxeBuilder from '@axe-core/playwright';
import {TestBrowser} from './browser_context';
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldRunInLinux} from './flag';
import {initSetup, getAdminClient} from './server';
import {isSmallScreen} from './util';
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
import {pages} from './ui/pages';
import {matchSnapshot} from './visual';
@@ -29,8 +28,8 @@ export const test = base.extend<ExtendedFixtures>({
const ab = new AxeBuilderExtended();
await use(ab);
},
pw: async ({browser, viewport}, use) => {
const pw = new PlaywrightExtended(browser, viewport);
pw: async ({browser}, use) => {
const pw = new PlaywrightExtended(browser);
await use(pw);
await pw.testBrowser.close();
},
@@ -47,7 +46,6 @@ class PlaywrightExtended {
// ./flag
readonly shouldHaveCallsEnabled;
readonly shouldHaveFeatureFlag;
readonly shouldSkipInSmallScreen;
readonly shouldRunInLinux;
// ./server
@@ -62,20 +60,16 @@ class PlaywrightExtended {
// ./ui/pages
readonly pages;
// ./util
readonly isSmallScreen;
// ./visual
readonly matchSnapshot;
constructor(browser: Browser, viewport: ViewportSize | null) {
constructor(browser: Browser) {
// ./browser_context
this.testBrowser = new TestBrowser(browser);
// ./flag
this.shouldHaveCallsEnabled = shouldHaveCallsEnabled;
this.shouldHaveFeatureFlag = shouldHaveFeatureFlag;
this.shouldSkipInSmallScreen = shouldSkipInSmallScreen;
this.shouldRunInLinux = shouldRunInLinux;
// ./server
@@ -90,9 +84,6 @@ class PlaywrightExtended {
// ./ui/pages
this.pages = pages;
// ./util
this.isSmallScreen = () => isSmallScreen(viewport);
// ./visual
this.matchSnapshot = matchSnapshot;
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class BoardsCreateModal {
readonly container: Locator;
readonly productSwitchMenu;
constructor(container: Locator) {
this.container = container;
this.productSwitchMenu = container.getByRole('button', {name: 'Product switch menu'});
}
async switchProduct(name: string) {
await this.productSwitchMenu.click();
await this.container.getByRole('link', {name: `${name}`}).click();
}
async toBeVisible(name: string) {
await expect(this.container.getByRole('heading', {name})).toBeVisible();
}
}
export {BoardsCreateModal};

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

@@ -1,28 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator} from '@playwright/test';
export default class BoardsSidebar {
readonly container: Locator;
readonly plusButton;
readonly createNewBoardMenuItem;
readonly createNewCategoryMenuItem;
readonly titles;
constructor(container: Locator) {
this.container = container;
this.plusButton = container.locator('.add-board-icon');
this.createNewBoardMenuItem = container.getByRole('button', {name: 'Create new board'});
this.createNewCategoryMenuItem = container.getByRole('button', {name: 'Create New Category'});
this.titles = container.locator('.SidebarBoardItem > .octo-sidebar-title');
}
async waitForTitle(name: string) {
await this.container.getByRole('button', {name: `${name}`}).waitFor({state: 'visible'});
}
}
export {BoardsSidebar};

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

@@ -47,8 +47,8 @@ export default class ChannelsCenterView {
/**
* Return the Nth post in the Center from the top
* @param index
* @returns
* @param index
* @returns
*/
async getNthPost(index: number) {
const nthPost = this.container.getByTestId('postView').nth(index);
@@ -73,7 +73,7 @@ export default class ChannelsCenterView {
const content = await post.container.textContent();
return content?.includes(text);
},
{timeout}
{timeout},
);
}
@@ -85,7 +85,7 @@ export default class ChannelsCenterView {
return content?.includes(text);
},
{timeout}
{timeout},
);
}
}

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

@@ -15,7 +15,7 @@ export default class EmojiGifPicker {
this.gifTab = container.getByText('GIFs');
this.gifSearchInput = container.getByPlaceholder('Search GIPHY');
this.gifPickerItems = container.locator('.gif-picker__items')
this.gifPickerItems = container.locator('.gif-picker__items');
}
async toBeVisible() {
@@ -24,7 +24,7 @@ export default class EmojiGifPicker {
async openGifTab() {
await expect(this.gifTab).toBeVisible();
await this.gifTab.click({force: true});
await expect(this.gifSearchInput).toBeVisible();
@@ -41,7 +41,7 @@ export default class EmojiGifPicker {
await this.gifPickerItems.locator('img').nth(n).waitFor();
const nthGif = this.gifPickerItems.locator('img').nth(n);
await expect(nthGif).toBeVisible()
await expect(nthGif).toBeVisible();
const nthGifSrc = await nthGif.getAttribute('src');
const nthGifAlt = await nthGif.getAttribute('alt');

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

@@ -1,13 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BoardsSidebar} from './boards/sidebar';
import {ChannelsHeader} from './channels/header';
import {ChannelsHeaderMobile} from './channels/header_mobile';
import {ChannelsAppBar} from './channels/app_bar';
import {ChannelsPostCreate} from './channels/post_create';
import {ChannelsPost} from './channels/post';
import {ChannelsCenterView} from './channels/center_view'
import {ChannelsCenterView} from './channels/center_view';
import {ChannelsSidebarLeft} from './channels/sidebar_left';
import {ChannelsSidebarRight} from './channels/sidebar_right';
import {DeletePostModal} from './channels/delete_post_modal';
@@ -22,7 +21,6 @@ import {ThreadFooter} from './channels/thread_footer';
import {EmojiGifPicker} from './channels/emoji_gif_picker';
const components = {
BoardsSidebar,
GlobalHeader,
ChannelsCenterView,
ChannelsSidebarLeft,
@@ -45,7 +43,6 @@ const components = {
export {
components,
BoardsSidebar,
GlobalHeader,
ChannelsCenterView,
ChannelsSidebarLeft,

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

@@ -1,47 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Page} from '@playwright/test';
import {components} from '@e2e-support/ui/components';
export default class BoardsCreatePage {
readonly boards = 'Boards';
readonly page: Page;
readonly globalHeader;
readonly createBoardHeading;
readonly createEmptyBoardButton;
readonly useTemplateButton;
constructor(page: Page) {
this.page = page;
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
this.createBoardHeading = page.getByRole('heading', {name: 'Create a board'});
this.createEmptyBoardButton = page.getByRole('button', {name: ' Create an empty board'});
this.useTemplateButton = page.getByRole('button', {name: 'Use this template'});
}
async goto(teamId = '') {
let boardsUrl = '/boards';
if (teamId) {
boardsUrl += `/team/${teamId}`;
}
await this.page.goto(boardsUrl);
}
async toBeVisible() {
await this.globalHeader.toBeVisible(this.boards);
await expect(this.createEmptyBoardButton).toBeVisible();
await expect(this.useTemplateButton).toBeVisible();
await expect(this.createBoardHeading).toBeVisible();
}
async createEmptyBoard() {
await this.createEmptyBoardButton.click();
}
}
export {BoardsCreatePage};

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

@@ -1,60 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Page} from '@playwright/test';
import {components} from '@e2e-support/ui/components';
export default class BoardsViewPage {
readonly boards = 'Boards';
readonly page: Page;
readonly sidebar;
readonly globalHeader;
readonly topHead;
readonly editableTitle;
readonly shareButton;
constructor(page: Page) {
this.page = page;
this.sidebar = new components.BoardsSidebar(page.locator('.octo-sidebar'));
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
this.topHead = page.locator('.top-head');
this.editableTitle = this.topHead.getByPlaceholder('Untitled board');
this.shareButton = page.getByRole('button', {name: '󰍁 Share'});
}
async goto(teamId = '', boardId = '', viewId = '', cardId = '') {
let boardsUrl = '/boards';
if (teamId) {
boardsUrl += `/team/${teamId}`;
if (boardId) {
boardsUrl += `/${boardId}`;
if (viewId) {
boardsUrl += `/${viewId}`;
if (cardId) {
boardsUrl += `/${cardId}`;
}
}
}
}
await this.page.goto(boardsUrl);
}
async toBeVisible() {
await this.page.waitForLoadState('networkidle');
await this.globalHeader.toBeVisible(this.boards);
await expect(this.shareButton).toBeVisible();
await expect(this.topHead).toBeVisible();
}
async shouldHaveUntitledBoard() {
await this.editableTitle.isVisible();
expect(await this.editableTitle.getAttribute('value')).toBe('');
await expect(this.page.getByTitle('(Untitled Board)')).toBeVisible();
}
}
export {BoardsViewPage};

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

@@ -4,7 +4,6 @@
import {Page} from '@playwright/test';
import {components} from '@e2e-support/ui/components';
import {isSmallScreen} from '@e2e-support/util';
export default class ChannelsPage {
readonly channels = 'Channels';
@@ -19,7 +18,7 @@ export default class ChannelsPage {
readonly findChannelsModal;
readonly deletePostModal;
readonly postDotMenu;
readonly postReminderMenu;
@@ -35,7 +34,7 @@ export default class ChannelsPage {
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
// Modals
// Modals
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal'));
@@ -48,10 +47,6 @@ export default class ChannelsPage {
}
async toBeVisible() {
if (!isSmallScreen(this.page.viewportSize())) {
await this.globalHeader.toBeVisible(this.channels);
}
await this.centerView.toBeVisible();
}

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

@@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BoardsCreatePage} from './boards_create';
import {BoardsViewPage} from './boards_view';
import {ChannelsPage} from './channels';
import {LandingLoginPage} from './landing_login';
import {LoginPage} from './login';
@@ -10,8 +8,6 @@ import {ResetPasswordPage} from './reset_password';
import {SignupPage} from './signup';
const pages = {
BoardsCreatePage,
BoardsViewPage,
ChannelsPage,
LandingLoginPage,
LoginPage,
@@ -19,4 +15,4 @@ const pages = {
SignupPage,
};
export {pages, BoardsCreatePage, BoardsViewPage, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};
export {pages, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};

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

@@ -44,7 +44,7 @@ export default class SignupPage {
this.createAccountButton = page.locator('button:has-text("Create Account")');
this.emailError = page.locator('text=Please enter a valid email address');
this.usernameError = page.locator(
'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.'
'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.',
);
this.passwordError = page.locator('text=Must be 5-64 characters long.');

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

@@ -2,7 +2,6 @@
// See LICENSE.txt for license information.
import {v4 as uuidv4} from 'uuid';
import {ViewportSize} from '@playwright/test';
const second = 1000;
const minute = 60 * 1000;
@@ -46,7 +45,3 @@ export function getRandomId(length = 7): string {
export const defaultTeam = {name: 'ad-1', displayName: 'eligendi', type: 'O'};
export const illegalRe = /[/?<>\\:*|":&();]/g;
export function isSmallScreen(viewport?: ViewportSize | {width: number; height: number} | null) {
return viewport?.width ? Boolean(viewport?.width <= 390) : true;
}

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

@@ -16,8 +16,8 @@ export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs) {
// eslint-disable-next-line no-console
console.log(
chalk.yellow(
`^ Warning: No visual test performed. Run in Linux or Playwright docker image to match snapshot.`
)
`^ Warning: No visual test performed. Run in Linux or Playwright docker image to match snapshot.`,
),
);
return;
}

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

@@ -4,8 +4,6 @@
import {Page, ViewportSize} from '@playwright/test';
import * as dotenv from 'dotenv';
import {callsPluginId} from '@e2e-support/constant';
dotenv.config();
export type TestArgs = {
@@ -47,7 +45,7 @@ const config: TestConfig = {
ensurePluginsInstalled:
typeof process.env?.PW_ENSURE_PLUGINS_INSTALLED === 'string'
? process.env.PW_ENSURE_PLUGINS_INSTALLED.split(',')
: [callsPluginId],
: [],
haClusterEnabled: parseBool(process.env.PW_HA_CLUSTER_ENABLED, false),
haClusterNodeCount: parseNumber(process.env.PW_HA_CLUSTER_NODE_COUNT, 2),
haClusterName: process.env.PW_HA_CLUSTER_NAME || 'mm_dev_cluster',

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

@@ -1,44 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture';
import {shouldSkipInSmallScreen} from '@e2e-support/flag';
shouldSkipInSmallScreen();
test('MM-T4274 Create an Empty Board', async ({pw, pages}) => {
// Create and sign in a new user
const {user} = await pw.initSetup();
// Log in a user in new browser context
const {page} = await pw.testBrowser.login(user);
// Visit a default channel page
const channelsPage = new pages.ChannelsPage(page);
await channelsPage.goto();
await channelsPage.toBeVisible();
// Switch to Boards page
await channelsPage.globalHeader.switchProduct('Boards');
// Should have redirected to boards create page
const boardsCreatePage = new pages.BoardsCreatePage(page);
await boardsCreatePage.toBeVisible();
// Create empty board
await boardsCreatePage.createEmptyBoard();
// Should have redirected to boards view page
const boardsViewPage = new pages.BoardsViewPage(page);
await boardsViewPage.toBeVisible();
await boardsViewPage.shouldHaveUntitledBoard();
// Type new title and hit enter
const title = 'Testing';
await boardsViewPage.editableTitle.fill(title);
await boardsViewPage.editableTitle.press('Enter');
// Should update the title in heading and in sidebar
expect(await boardsViewPage.editableTitle.getAttribute('value')).toBe(title);
await boardsViewPage.sidebar.waitForTitle(title);
});

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

@@ -22,7 +22,7 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use
createRandomPost({
channel_id: channel.id,
user_id: adminUser.id,
})
}),
);
// # Log in as user in new browser context

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

@@ -28,7 +28,8 @@ test('MM-T5445 Should search, select and post correct Gif when Gif picker is ope
await channelPage.emojiGifPickerPopup.searchGif('hello');
// # Select the first gif
const {img: firstSearchGifResult, alt: altOfFirstSearchGifResult} = await channelPage.emojiGifPickerPopup.getNthGif(0);
const {img: firstSearchGifResult, alt: altOfFirstSearchGifResult} =
await channelPage.emojiGifPickerPopup.getNthGif(0);
await firstSearchGifResult.click();
// # Send the selected gif as a message
@@ -82,7 +83,8 @@ test('MM-T5446 Should search, select and post correct Gif when Gif picker is ope
await channelPage.emojiGifPickerPopup.searchGif('hello');
// # Select the first gif
const {img: firstSearchGifResult, alt: altOfFirstSearchGifResult} = await channelPage.emojiGifPickerPopup.getNthGif(0);
const {img: firstSearchGifResult, alt: altOfFirstSearchGifResult} =
await channelPage.emojiGifPickerPopup.getNthGif(0);
await firstSearchGifResult.click();
// # Send the selected gif as a message in the thread

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

@@ -37,9 +37,7 @@ test('MM-T5424 Find channel search returns only 50 results when there are more t
await channelsPage.toBeVisible();
// # Click on "Find channel" and type "test_channel"
if (pw.isSmallScreen()) {
await channelsPage.centerView.headerMobile.toggleSidebar();
}
await channelsPage.centerView.headerMobile.toggleSidebar();
await channelsPage.sidebarLeft.findChannelButton.click();
await channelsPage.findChannelsModal.toBeVisible();

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

@@ -1,24 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {test} from '@e2e-support/test_fixture';
import {shouldSkipInSmallScreen} from '@e2e-support/flag';
shouldSkipInSmallScreen();
test('Board template', async ({pw, pages, browserName, viewport}, testInfo) => {
// Create and sign in a new user
const {user} = await pw.initSetup();
// Log in a user in new browser context
const {page} = await pw.testBrowser.login(user);
// Should have redirected to boards create page
const boardsCreatePage = new pages.BoardsCreatePage(page);
await boardsCreatePage.goto();
await boardsCreatePage.toBeVisible();
// Match snapshot of create board page
const testArgs = {page, browserName, viewport};
await pw.matchSnapshot(testInfo, testArgs);
});

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 127 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 238 KiB

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

@@ -1,32 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {test} from '@e2e-support/test_fixture';
import {shouldSkipInSmallScreen} from '@e2e-support/flag';
shouldSkipInSmallScreen();
test('View untitled board', async ({pw, pages, browserName, viewport}, testInfo) => {
// Create and sign in a new user
const {user} = await pw.initSetup();
// Log in a user in new browser context
const {page} = await pw.testBrowser.login(user);
// Should have redirected to boards create page
const boardsCreatePage = new pages.BoardsCreatePage(page);
await boardsCreatePage.goto();
await boardsCreatePage.toBeVisible();
// Create empty board
await boardsCreatePage.createEmptyBoard();
// Should have redirected to boards view page
const boardsViewPage = new pages.BoardsViewPage(page);
await boardsViewPage.toBeVisible();
await boardsViewPage.shouldHaveUntitledBoard();
// Match snapshot of create board page
const testArgs = {page, browserName, viewport};
await pw.matchSnapshot(testInfo, testArgs);
});

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 38 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 63 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 84 KiB

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

@@ -15,16 +15,8 @@ test('Intro to channel as regular user', async ({pw, pages, browserName, viewpor
await channelsPage.goto();
await channelsPage.toBeVisible();
// Wait for Boards' bot image to be loaded
// await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
// const boardsWelcomePost = await channelsPage.getFirstPost();
// await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
// await wait(duration.one_sec);
// Wait for Playbooks icon to be loaded in App bar, except in iphone
if (!pw.isSmallScreen()) {
await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
}
await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
// Hide dynamic elements of Channels page
await pw.hideDynamicChannelsContent(page);

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 71 KiB

После

Ширина:  |  Высота:  |  Размер: 69 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 106 KiB

После

Ширина:  |  Высота:  |  Размер: 101 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 167 KiB

После

Ширина:  |  Высота:  |  Размер: 161 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 148 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 105 KiB

После

Ширина:  |  Высота:  |  Размер: 107 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 170 KiB

После

Ширина:  |  Высота:  |  Размер: 170 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 213 KiB

После

Ширина:  |  Высота:  |  Размер: 220 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 156 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 150 KiB

После

Ширина:  |  Высота:  |  Размер: 153 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 147 KiB

После

Ширина:  |  Высота:  |  Размер: 153 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 271 KiB

После

Ширина:  |  Высота:  |  Размер: 271 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 312 KiB

После

Ширина:  |  Высота:  |  Размер: 319 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 241 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 277 KiB

После

Ширина:  |  Высота:  |  Размер: 278 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 297 KiB

После

Ширина:  |  Высота:  |  Размер: 302 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 230 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 165 KiB

После

Ширина:  |  Высота:  |  Размер: 167 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 170 KiB

После

Ширина:  |  Высота:  |  Размер: 172 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 296 KiB

После

Ширина:  |  Высота:  |  Размер: 297 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 411 KiB

После

Ширина:  |  Высота:  |  Размер: 429 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 355 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 291 KiB

После

Ширина:  |  Высота:  |  Размер: 291 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 393 KiB

После

Ширина:  |  Высота:  |  Размер: 397 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 266 KiB

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

@@ -405,6 +405,7 @@ export type TeamSettings = {
ExperimentalPrimaryTeam: string;
ExperimentalDefaultChannels: string[];
EnableLastActiveTime: boolean;
EnableJoinLeaveMessageByDefault: boolean;
};
export type ClientRequirements = {
@@ -429,6 +430,7 @@ export type SqlSettings = {
DisableDatabaseSearch: boolean;
MigrationsStatementTimeoutSeconds: number;
ReplicaLagSettings: ReplicaLagSetting[];
ReplicaMonitorIntervalSeconds: number;
};
export type LogSettings = {
@@ -445,6 +447,7 @@ export type LogSettings = {
VerboseDiagnostics: boolean;
EnableSentry: boolean;
AdvancedLoggingConfig: string;
AdvancedLoggingJSON: Record<string, any>;
};
export type ExperimentalAuditSettings = {
@@ -456,6 +459,7 @@ export type ExperimentalAuditSettings = {
FileCompress: boolean;
FileMaxQueueSize: number;
AdvancedLoggingConfig: string;
AdvancedLoggingJSON: Record<string, any>;
};
export type NotificationLogSettings = {
@@ -468,6 +472,7 @@ export type NotificationLogSettings = {
FileJson: boolean;
FileLocation: string;
AdvancedLoggingConfig: string;
AdvancedLoggingJSON: Record<string, any>;
};
export type PasswordSettings = {
@@ -504,6 +509,21 @@ export type FileSettings = {
AmazonS3SSE: boolean;
AmazonS3Trace: boolean;
AmazonS3RequestTimeoutMilliseconds: number;
DedicatedExportStore: boolean;
ExportDriverName: string;
ExportDirectory: string;
ExportAmazonS3AccessKeyId: string;
ExportAmazonS3SecretAccessKey: string;
ExportAmazonS3Bucket: string;
ExportAmazonS3PathPrefix: string;
ExportAmazonS3Region: string;
ExportAmazonS3Endpoint: string;
ExportAmazonS3SSL: boolean;
ExportAmazonS3SignV2: boolean;
ExportAmazonS3SSE: boolean;
ExportAmazonS3Trace: boolean;
ExportAmazonS3RequestTimeoutMilliseconds: number;
ExportAmazonS3PresignExpiresSeconds: number;
};
export type EmailSettings = {
@@ -793,6 +813,10 @@ export type DataRetentionSettings = {
FileRetentionDays: number;
DeletionJobStartTime: string;
BatchSize: number;
EnableBoardsDeletion: boolean,
BoardsRetentionDays: number;
TimeBetweenBatchesMilliseconds: number;
RetentionIdsBatchSize: number;
};
export type MessageExportSettings = {
@@ -842,6 +866,7 @@ export type PluginSettings = {
export type DisplaySettings = {
CustomURLSchemes: string[];
ExperimentalTimezone: boolean;
MaxMarkdownNodes: number;
};
export type GuestAccountsSettings = {
@@ -862,6 +887,7 @@ export type ImageProxySettings = {
export type CloudSettings = {
CWSURL: string;
CWSAPIURL: string;
CWSMock: boolean;
};
export type FeatureFlags = Record<string, string | boolean>;