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
This commit is contained in:
Saturnino Abril 2023-09-20 05:28:35 +08:00 committed by GitHub
parent 88d043a971
commit b7b08dbc0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 657 additions and 834 deletions

View File

@ -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

View File

@ -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}`),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

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

View File

@ -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/.

View File

@ -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';

View File

@ -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');

View File

@ -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',

View File

@ -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();

View File

@ -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)),
);
}

View File

@ -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;
}

View File

@ -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};

View File

@ -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};

View File

@ -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},
);
}
}

View File

@ -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');

View File

@ -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,

View File

@ -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};

View File

@ -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};

View File

@ -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();
}

View File

@ -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};

View File

@ -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.');

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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',

View File

@ -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);
});

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 397 KiB

View File

@ -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>;