Move /e2e -> /e2e-tests
Этот коммит содержится в:
Двоичные данные
e2e-tests/playwright/support/asset/mattermost-icon_128x128.png
Обычный файл
Двоичные данные
e2e-tests/playwright/support/asset/mattermost-icon_128x128.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 13 KiB |
69
e2e-tests/playwright/support/browser_context.ts
Обычный файл
69
e2e-tests/playwright/support/browser_context.ts
Обычный файл
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {writeFile} from 'node:fs/promises';
|
||||
|
||||
import {request, Browser} from '@playwright/test';
|
||||
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
import testConfig from '@e2e-test.config';
|
||||
|
||||
export class TestBrowser {
|
||||
readonly browser: Browser;
|
||||
|
||||
constructor(browser: Browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
async login(user: UserProfile | null) {
|
||||
const options = {storageState: ''};
|
||||
if (user) {
|
||||
// Log in via API request and save user storage
|
||||
const storagePath = await loginByAPI(user.username, user.password);
|
||||
options.storageState = storagePath;
|
||||
}
|
||||
|
||||
// Sign in a user in new browser context
|
||||
const context = await this.browser.newContext(options);
|
||||
const page = await context.newPage();
|
||||
|
||||
return {context, page};
|
||||
}
|
||||
}
|
||||
|
||||
export async function loginByAPI(loginId: string, password: string, token = '', ldapOnly = false) {
|
||||
const requestContext = await request.newContext();
|
||||
|
||||
const data: any = {
|
||||
login_id: loginId,
|
||||
password,
|
||||
token,
|
||||
deviceId: '',
|
||||
};
|
||||
|
||||
if (ldapOnly) {
|
||||
data.ldap_only = 'true';
|
||||
}
|
||||
|
||||
// Log in via API
|
||||
await requestContext.post(`${testConfig.baseURL}/api/v4/users/login`, {
|
||||
data,
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
|
||||
// Save signed-in state to a folder
|
||||
const storagePath = `storage_state/${Date.now()}_${loginId}_${password}${token ? '_' + token : ''}${
|
||||
ldapOnly ? '_ldap' : ''
|
||||
}.json`;
|
||||
const storageState = await requestContext.storageState({path: storagePath});
|
||||
await requestContext.dispose();
|
||||
|
||||
// Append origins to bypass seeing landing page then write to file
|
||||
storageState.origins.push({
|
||||
origin: testConfig.baseURL,
|
||||
localStorage: [{name: '__landingPageSeen__', value: 'true'}],
|
||||
});
|
||||
await writeFile(storagePath, JSON.stringify(storageState));
|
||||
|
||||
return storagePath;
|
||||
}
|
||||
7
e2e-tests/playwright/support/constant.ts
Обычный файл
7
e2e-tests/playwright/support/constant.ts
Обычный файл
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const boardsPluginId = 'focalboard';
|
||||
export const boardsProductId = 'boards';
|
||||
export const callsPluginId = 'com.mattermost.calls';
|
||||
export const playbooksPluginId = 'playbooks';
|
||||
52
e2e-tests/playwright/support/flag.ts
Обычный файл
52
e2e-tests/playwright/support/flag.ts
Обычный файл
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import os from 'node:os';
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
|
||||
import {boardsPluginId, callsPluginId} from './constant';
|
||||
import {getAdminClient} from './server/init';
|
||||
import {isSmallScreen} from './util';
|
||||
|
||||
export async function shouldHaveBoardsEnabled(enabled = true) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
const config = await adminClient.getConfig();
|
||||
|
||||
const boardsEnabled =
|
||||
(typeof config.FeatureFlags.BoardsProduct === 'boolean' && config.FeatureFlags.BoardsProduct) ||
|
||||
config.PluginSettings.PluginStates[boardsPluginId].Enable;
|
||||
|
||||
const matched = boardsEnabled === enabled;
|
||||
expect(matched, matched ? '' : `Boards expect "${enabled}" but actual "${boardsEnabled}"`).toBeTruthy();
|
||||
}
|
||||
|
||||
export async function shouldHaveCallsEnabled(enabled = true) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
const config = await adminClient.getConfig();
|
||||
|
||||
const callsEnabled = config.PluginSettings.PluginStates[callsPluginId].Enable;
|
||||
|
||||
const matched = callsEnabled === enabled;
|
||||
expect(matched, matched ? '' : `Calls expect "${enabled}" but actual "${callsEnabled}"`).toBeTruthy();
|
||||
}
|
||||
|
||||
export async function shouldHaveFeatureFlag(name: string, value: string | boolean) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
const config = await adminClient.getConfig();
|
||||
|
||||
const matched = config.FeatureFlags[name] === value;
|
||||
expect(
|
||||
matched,
|
||||
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');
|
||||
}
|
||||
28
e2e-tests/playwright/support/server/channel.ts
Обычный файл
28
e2e-tests/playwright/support/server/channel.ts
Обычный файл
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getRandomId} from '@e2e-support/util';
|
||||
import {Channel, ChannelType} from '@mattermost/types/channels';
|
||||
|
||||
export function createRandomChannel(
|
||||
teamId: string,
|
||||
name: string,
|
||||
displayName: string,
|
||||
type: ChannelType = 'O',
|
||||
purpose = '',
|
||||
header = '',
|
||||
unique = true
|
||||
): Channel {
|
||||
const randomSuffix = getRandomId();
|
||||
|
||||
const channel = {
|
||||
team_id: teamId,
|
||||
name: unique ? `${name}-${randomSuffix}` : name,
|
||||
display_name: unique ? `${displayName} ${randomSuffix}` : displayName,
|
||||
type,
|
||||
purpose,
|
||||
header,
|
||||
};
|
||||
|
||||
return channel as Channel;
|
||||
}
|
||||
213
e2e-tests/playwright/support/server/client.ts
Обычный файл
213
e2e-tests/playwright/support/server/client.ts
Обычный файл
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// This is based on "packages/client/src/client4.ts". Modified for node client.
|
||||
// Update should be made in comparison with the base Client4.
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import FormData from 'form-data';
|
||||
import 'isomorphic-unfetch';
|
||||
|
||||
import testConfig from '@e2e-test.config';
|
||||
import Client4 from '@mattermost/client/client4';
|
||||
import {Options, StatusOK} from '@mattermost/types/client4';
|
||||
import {License} from '@mattermost/types/config';
|
||||
import {CustomEmoji} from '@mattermost/types/emojis';
|
||||
import {PluginManifest} from '@mattermost/types/plugins';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
export default class Client extends Client4 {
|
||||
getFormDataOptions = (formData: FormData): Options => {
|
||||
return {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
uploadProfileImageX = (userId: string, filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('image', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getUserRoute(userId)}/image`, options);
|
||||
};
|
||||
|
||||
setTeamIconX = (teamId: string, filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('image', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getTeamRoute(teamId)}/image`, options);
|
||||
};
|
||||
|
||||
createCustomEmojiX = (emoji: CustomEmoji, filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('image', fileData, path.basename(filePath));
|
||||
formData.append('emoji', JSON.stringify(emoji));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<CustomEmoji>(`${this.getEmojisRoute()}`, options);
|
||||
};
|
||||
|
||||
uploadBrandImageX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('image', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBrandRoute()}/image`, options);
|
||||
};
|
||||
|
||||
uploadPublicSamlCertificateX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('certificate', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBaseRoute()}/saml/certificate/public`, options);
|
||||
};
|
||||
|
||||
uploadPrivateSamlCertificateX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('certificate', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBaseRoute()}/saml/certificate/private`, options);
|
||||
};
|
||||
|
||||
uploadPublicLdapCertificateX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('certificate', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBaseRoute()}/ldap/certificate/public`, options);
|
||||
};
|
||||
|
||||
uploadPrivateLdapCertificateX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('certificate', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBaseRoute()}/ldap/certificate/private`, options);
|
||||
};
|
||||
|
||||
uploadIdpSamlCertificateX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('certificate', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<StatusOK>(`${this.getBaseRoute()}/saml/certificate/idp`, options);
|
||||
};
|
||||
|
||||
uploadLicenseX = (filePath: string) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
formData.append('license', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<License>(`${this.getBaseRoute()}/license`, options);
|
||||
};
|
||||
|
||||
uploadPluginX = async (filePath: string, force = false) => {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
const formData = new FormData();
|
||||
if (force) {
|
||||
formData.append('force', 'true');
|
||||
}
|
||||
formData.append('plugin', fileData, path.basename(filePath));
|
||||
const options = this.getFormDataOptions(formData);
|
||||
|
||||
return this.doFetch<PluginManifest>(this.getPluginsRoute(), options);
|
||||
};
|
||||
|
||||
// *****************************************************************************
|
||||
// Boards client
|
||||
// based on https://github.com/mattermost/focalboard/blob/main/webapp/src/octoClient.ts
|
||||
// *****************************************************************************
|
||||
|
||||
async patchUserConfig(userID: string, patch: UserConfigPatch): Promise<UserPreference[] | undefined> {
|
||||
const path = `/users/${encodeURIComponent(userID)}/config`;
|
||||
const options = {
|
||||
method: 'put',
|
||||
body: JSON.stringify(patch),
|
||||
};
|
||||
|
||||
return this.doFetch<UserPreference[]>(this.getBoardsRoute() + path, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Variable to hold cache
|
||||
const clients: Record<string, ClientCache> = {};
|
||||
|
||||
async function makeClient(userRequest?: UserRequest, useCache = true): Promise<ClientCache> {
|
||||
const client = new Client();
|
||||
client.setUrl(testConfig.baseURL);
|
||||
|
||||
try {
|
||||
if (!userRequest) {
|
||||
return {client, user: null};
|
||||
}
|
||||
|
||||
const cacheKey = userRequest.username + userRequest.password;
|
||||
if (useCache && clients[cacheKey] != null) {
|
||||
return clients[cacheKey];
|
||||
}
|
||||
|
||||
const userProfile = await client.login(userRequest.username, userRequest.password);
|
||||
const user = {...userProfile, password: userRequest.password};
|
||||
const config = await client.getClientConfigOld();
|
||||
client.setUseBoardsProduct(config.FeatureFlagBoardsProduct === 'true');
|
||||
|
||||
if (useCache) {
|
||||
clients[cacheKey] = {client, user};
|
||||
}
|
||||
|
||||
return {client, user};
|
||||
} catch (err) {
|
||||
// log an error for debugging
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('makeClient', err);
|
||||
return {client, user: null};
|
||||
}
|
||||
}
|
||||
|
||||
// Client types
|
||||
|
||||
type UserRequest = {
|
||||
username: string;
|
||||
email?: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
type ClientCache = {
|
||||
client: Client;
|
||||
user: UserProfile | null;
|
||||
};
|
||||
|
||||
// Boards types
|
||||
|
||||
interface UserPreference {
|
||||
user_id: string;
|
||||
category: string;
|
||||
name: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface UserConfigPatch {
|
||||
updatedFields?: Record<string, string>;
|
||||
deletedFields?: string[];
|
||||
}
|
||||
|
||||
export {Client, makeClient};
|
||||
709
e2e-tests/playwright/support/server/default_config.ts
Обычный файл
709
e2e-tests/playwright/support/server/default_config.ts
Обычный файл
@@ -0,0 +1,709 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import {
|
||||
AdminConfig,
|
||||
ExperimentalSettings,
|
||||
FeatureFlags,
|
||||
PasswordSettings,
|
||||
ServiceSettings,
|
||||
TeamSettings,
|
||||
PluginSettings,
|
||||
ClusterSettings,
|
||||
CollapsedThreads,
|
||||
} from '@mattermost/types/config';
|
||||
import testConfig from '@e2e-test.config';
|
||||
|
||||
export function getOnPremServerConfig(): AdminConfig {
|
||||
return merge<AdminConfig>(defaultServerConfig, onPremServerConfig() as AdminConfig);
|
||||
}
|
||||
|
||||
type TestAdminConfig = {
|
||||
ClusterSettings: Partial<ClusterSettings>;
|
||||
ExperimentalSettings: Partial<ExperimentalSettings>;
|
||||
FeatureFlags: Partial<FeatureFlags>;
|
||||
PasswordSettings: Partial<PasswordSettings>;
|
||||
PluginSettings: Partial<PluginSettings>;
|
||||
ServiceSettings: Partial<ServiceSettings>;
|
||||
TeamSettings: Partial<TeamSettings>;
|
||||
};
|
||||
|
||||
// On-prem setting that is different from the default
|
||||
const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
||||
return {
|
||||
ClusterSettings: {
|
||||
Enable: testConfig.haClusterEnabled,
|
||||
ClusterName: testConfig.haClusterName,
|
||||
},
|
||||
ExperimentalSettings: {
|
||||
EnableAppBar: true,
|
||||
},
|
||||
FeatureFlags: {
|
||||
BoardsProduct: testConfig.boardsProductEnabled,
|
||||
},
|
||||
PasswordSettings: {
|
||||
MinimumLength: 5,
|
||||
Lowercase: false,
|
||||
Number: false,
|
||||
Uppercase: false,
|
||||
Symbol: false,
|
||||
},
|
||||
PluginSettings: {
|
||||
EnableUploads: true,
|
||||
Plugins: {
|
||||
'com.mattermost.calls': {
|
||||
defaultenabled: true,
|
||||
},
|
||||
},
|
||||
PluginStates: {
|
||||
focalboard: {
|
||||
Enable: !testConfig.boardsProductEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
ServiceSettings: {
|
||||
SiteURL: testConfig.baseURL,
|
||||
EnableOnboardingFlow: false,
|
||||
},
|
||||
TeamSettings: {
|
||||
EnableOpenServer: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Should be based only from the generated default config from ./server via "make config-reset"
|
||||
// Based on v7.10 server
|
||||
const defaultServerConfig: AdminConfig = {
|
||||
ServiceSettings: {
|
||||
SiteURL: '',
|
||||
WebsocketURL: '',
|
||||
LicenseFileLocation: '',
|
||||
ListenAddress: ':8065',
|
||||
ConnectionSecurity: '',
|
||||
TLSCertFile: '',
|
||||
TLSKeyFile: '',
|
||||
TLSMinVer: '1.2',
|
||||
TLSStrictTransport: false,
|
||||
TLSStrictTransportMaxAge: 63072000,
|
||||
TLSOverwriteCiphers: [],
|
||||
UseLetsEncrypt: false,
|
||||
LetsEncryptCertificateCacheFile: './config/letsencrypt.cache',
|
||||
Forward80To443: false,
|
||||
TrustedProxyIPHeader: [],
|
||||
ReadTimeout: 300,
|
||||
WriteTimeout: 300,
|
||||
IdleTimeout: 60,
|
||||
MaximumLoginAttempts: 10,
|
||||
GoroutineHealthThreshold: -1,
|
||||
EnableOAuthServiceProvider: true,
|
||||
EnableIncomingWebhooks: true,
|
||||
EnableOutgoingWebhooks: true,
|
||||
EnableCommands: true,
|
||||
EnablePostUsernameOverride: false,
|
||||
EnablePostIconOverride: false,
|
||||
GoogleDeveloperKey: '',
|
||||
EnableLinkPreviews: true,
|
||||
EnablePermalinkPreviews: true,
|
||||
RestrictLinkPreviews: '',
|
||||
EnableTesting: false,
|
||||
EnableDeveloper: false,
|
||||
DeveloperFlags: '',
|
||||
EnableClientPerformanceDebugging: false,
|
||||
EnableOpenTracing: false,
|
||||
EnableSecurityFixAlert: true,
|
||||
EnableInsecureOutgoingConnections: false,
|
||||
AllowedUntrustedInternalConnections: '',
|
||||
EnableMultifactorAuthentication: false,
|
||||
EnforceMultifactorAuthentication: false,
|
||||
EnableUserAccessTokens: false,
|
||||
AllowCorsFrom: '',
|
||||
CorsExposedHeaders: '',
|
||||
CorsAllowCredentials: false,
|
||||
CorsDebug: false,
|
||||
AllowCookiesForSubdomains: false,
|
||||
ExtendSessionLengthWithActivity: true,
|
||||
SessionLengthWebInDays: 30,
|
||||
SessionLengthWebInHours: 720,
|
||||
SessionLengthMobileInDays: 30,
|
||||
SessionLengthMobileInHours: 720,
|
||||
SessionLengthSSOInDays: 30,
|
||||
SessionLengthSSOInHours: 720,
|
||||
SessionCacheInMinutes: 10,
|
||||
SessionIdleTimeoutInMinutes: 43200,
|
||||
WebsocketSecurePort: 443,
|
||||
WebsocketPort: 80,
|
||||
WebserverMode: 'gzip',
|
||||
EnableGifPicker: true,
|
||||
GfycatAPIKey: '2_KtH_W5',
|
||||
GfycatAPISecret: '3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof',
|
||||
EnableCustomEmoji: true,
|
||||
EnableEmojiPicker: true,
|
||||
PostEditTimeLimit: -1,
|
||||
TimeBetweenUserTypingUpdatesMilliseconds: 5000,
|
||||
EnablePostSearch: true,
|
||||
EnableFileSearch: true,
|
||||
MinimumHashtagLength: 3,
|
||||
EnableUserTypingMessages: true,
|
||||
EnableChannelViewedMessages: true,
|
||||
EnableUserStatuses: true,
|
||||
ExperimentalEnableAuthenticationTransfer: true,
|
||||
ClusterLogTimeoutMilliseconds: 2000,
|
||||
EnablePreviewFeatures: true,
|
||||
EnableTutorial: true,
|
||||
EnableOnboardingFlow: true,
|
||||
ExperimentalEnableDefaultChannelLeaveJoinMessages: true,
|
||||
ExperimentalGroupUnreadChannels: 'disabled',
|
||||
EnableAPITeamDeletion: false,
|
||||
EnableAPITriggerAdminNotifications: false,
|
||||
EnableAPIUserDeletion: false,
|
||||
ExperimentalEnableHardenedMode: false,
|
||||
ExperimentalStrictCSRFEnforcement: false,
|
||||
EnableEmailInvitations: false,
|
||||
DisableBotsWhenOwnerIsDeactivated: true,
|
||||
EnableBotAccountCreation: false,
|
||||
EnableSVGs: false,
|
||||
EnableLatex: false,
|
||||
EnableInlineLatex: true,
|
||||
PostPriority: true,
|
||||
EnableAPIChannelDeletion: false,
|
||||
EnableLocalMode: false,
|
||||
LocalModeSocketLocation: '/var/tmp/mattermost_local.socket',
|
||||
EnableAWSMetering: false,
|
||||
SplitKey: '',
|
||||
FeatureFlagSyncIntervalSeconds: 30,
|
||||
DebugSplit: false,
|
||||
ThreadAutoFollow: true,
|
||||
CollapsedThreads: CollapsedThreads.ALWAYS_ON,
|
||||
ManagedResourcePaths: '',
|
||||
EnableCustomGroups: true,
|
||||
SelfHostedPurchase: true,
|
||||
AllowSyncedDrafts: true,
|
||||
SelfHostedExpansion: false,
|
||||
},
|
||||
TeamSettings: {
|
||||
SiteName: 'Mattermost',
|
||||
MaxUsersPerTeam: 50,
|
||||
EnableUserCreation: true,
|
||||
EnableOpenServer: false,
|
||||
EnableUserDeactivation: false,
|
||||
RestrictCreationToDomains: '',
|
||||
EnableCustomUserStatuses: true,
|
||||
EnableCustomBrand: false,
|
||||
CustomBrandText: '',
|
||||
CustomDescriptionText: '',
|
||||
RestrictDirectMessage: 'any',
|
||||
EnableLastActiveTime: true,
|
||||
UserStatusAwayTimeout: 300,
|
||||
MaxChannelsPerTeam: 2000,
|
||||
MaxNotificationsPerChannel: 1000,
|
||||
EnableConfirmNotificationsToChannel: true,
|
||||
TeammateNameDisplay: 'username',
|
||||
ExperimentalViewArchivedChannels: true,
|
||||
ExperimentalEnableAutomaticReplies: false,
|
||||
LockTeammateNameDisplay: false,
|
||||
ExperimentalPrimaryTeam: '',
|
||||
ExperimentalDefaultChannels: [],
|
||||
},
|
||||
ClientRequirements: {
|
||||
AndroidLatestVersion: '',
|
||||
AndroidMinVersion: '',
|
||||
IosLatestVersion: '',
|
||||
IosMinVersion: '',
|
||||
},
|
||||
SqlSettings: {
|
||||
DriverName: 'postgres',
|
||||
DataSource:
|
||||
'postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\u0026connect_timeout=10\u0026binary_parameters=yes',
|
||||
DataSourceReplicas: [],
|
||||
DataSourceSearchReplicas: [],
|
||||
MaxIdleConns: 20,
|
||||
ConnMaxLifetimeMilliseconds: 3600000,
|
||||
ConnMaxIdleTimeMilliseconds: 300000,
|
||||
MaxOpenConns: 300,
|
||||
Trace: false,
|
||||
AtRestEncryptKey: '',
|
||||
QueryTimeout: 30,
|
||||
DisableDatabaseSearch: false,
|
||||
MigrationsStatementTimeoutSeconds: 100000,
|
||||
ReplicaLagSettings: [],
|
||||
},
|
||||
LogSettings: {
|
||||
EnableConsole: true,
|
||||
ConsoleLevel: 'DEBUG',
|
||||
ConsoleJson: true,
|
||||
EnableColor: false,
|
||||
EnableFile: true,
|
||||
FileLevel: 'INFO',
|
||||
FileJson: true,
|
||||
FileLocation: '',
|
||||
EnableWebhookDebugging: true,
|
||||
EnableDiagnostics: true,
|
||||
VerboseDiagnostics: false,
|
||||
EnableSentry: true,
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
ExperimentalAuditSettings: {
|
||||
FileEnabled: false,
|
||||
FileName: '',
|
||||
FileMaxSizeMB: 100,
|
||||
FileMaxAgeDays: 0,
|
||||
FileMaxBackups: 0,
|
||||
FileCompress: false,
|
||||
FileMaxQueueSize: 1000,
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
NotificationLogSettings: {
|
||||
EnableConsole: true,
|
||||
ConsoleLevel: 'DEBUG',
|
||||
ConsoleJson: true,
|
||||
EnableColor: false,
|
||||
EnableFile: true,
|
||||
FileLevel: 'INFO',
|
||||
FileJson: true,
|
||||
FileLocation: '',
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
PasswordSettings: {
|
||||
MinimumLength: 8,
|
||||
Lowercase: false,
|
||||
Number: false,
|
||||
Uppercase: false,
|
||||
Symbol: false,
|
||||
},
|
||||
FileSettings: {
|
||||
EnableFileAttachments: true,
|
||||
EnableMobileUpload: true,
|
||||
EnableMobileDownload: true,
|
||||
MaxFileSize: 104857600,
|
||||
MaxImageResolution: 33177600,
|
||||
MaxImageDecoderConcurrency: -1,
|
||||
DriverName: 'local',
|
||||
Directory: './data/',
|
||||
EnablePublicLink: false,
|
||||
ExtractContent: true,
|
||||
ArchiveRecursion: false,
|
||||
PublicLinkSalt: '',
|
||||
InitialFont: 'nunito-bold.ttf',
|
||||
AmazonS3AccessKeyId: '',
|
||||
AmazonS3SecretAccessKey: '',
|
||||
AmazonS3Bucket: '',
|
||||
AmazonS3PathPrefix: '',
|
||||
AmazonS3Region: '',
|
||||
AmazonS3Endpoint: 's3.amazonaws.com',
|
||||
AmazonS3SSL: true,
|
||||
AmazonS3SignV2: false,
|
||||
AmazonS3SSE: false,
|
||||
AmazonS3Trace: false,
|
||||
AmazonS3RequestTimeoutMilliseconds: 30000,
|
||||
},
|
||||
EmailSettings: {
|
||||
EnableSignUpWithEmail: true,
|
||||
EnableSignInWithEmail: true,
|
||||
EnableSignInWithUsername: true,
|
||||
SendEmailNotifications: true,
|
||||
UseChannelInEmailNotifications: false,
|
||||
RequireEmailVerification: false,
|
||||
FeedbackName: '',
|
||||
FeedbackEmail: 'test@example.com',
|
||||
ReplyToAddress: 'test@example.com',
|
||||
FeedbackOrganization: '',
|
||||
EnableSMTPAuth: false,
|
||||
SMTPUsername: '',
|
||||
SMTPPassword: '',
|
||||
SMTPServer: 'localhost',
|
||||
SMTPPort: '10025',
|
||||
SMTPServerTimeout: 10,
|
||||
ConnectionSecurity: '',
|
||||
SendPushNotifications: true,
|
||||
PushNotificationServer: 'https://push-test.mattermost.com',
|
||||
PushNotificationContents: 'full',
|
||||
PushNotificationBuffer: 1000,
|
||||
EnableEmailBatching: false,
|
||||
EmailBatchingBufferSize: 256,
|
||||
EmailBatchingInterval: 30,
|
||||
EnablePreviewModeBanner: true,
|
||||
SkipServerCertificateVerification: false,
|
||||
EmailNotificationContentsType: 'full',
|
||||
LoginButtonColor: '#0000',
|
||||
LoginButtonBorderColor: '#2389D7',
|
||||
LoginButtonTextColor: '#2389D7',
|
||||
EnableInactivityEmail: true,
|
||||
},
|
||||
RateLimitSettings: {
|
||||
Enable: false,
|
||||
PerSec: 10,
|
||||
MaxBurst: 100,
|
||||
MemoryStoreSize: 10000,
|
||||
VaryByRemoteAddr: true,
|
||||
VaryByUser: false,
|
||||
VaryByHeader: '',
|
||||
},
|
||||
PrivacySettings: {
|
||||
ShowEmailAddress: true,
|
||||
ShowFullName: true,
|
||||
},
|
||||
SupportSettings: {
|
||||
TermsOfServiceLink: 'https://mattermost.com/terms-of-use/',
|
||||
PrivacyPolicyLink: 'https://mattermost.com/privacy-policy/',
|
||||
AboutLink: 'https://docs.mattermost.com/about/product.html/',
|
||||
HelpLink: 'https://mattermost.com/default-help/',
|
||||
ReportAProblemLink: 'https://mattermost.com/default-report-a-problem/',
|
||||
SupportEmail: '',
|
||||
CustomTermsOfServiceEnabled: false,
|
||||
CustomTermsOfServiceReAcceptancePeriod: 365,
|
||||
EnableAskCommunityLink: true,
|
||||
},
|
||||
AnnouncementSettings: {
|
||||
EnableBanner: false,
|
||||
BannerText: '',
|
||||
BannerColor: '#f2a93b',
|
||||
BannerTextColor: '#333333',
|
||||
AllowBannerDismissal: true,
|
||||
AdminNoticesEnabled: true,
|
||||
UserNoticesEnabled: true,
|
||||
NoticesURL: 'https://notices.mattermost.com/',
|
||||
NoticesFetchFrequency: 3600,
|
||||
NoticesSkipCache: false,
|
||||
},
|
||||
ThemeSettings: {
|
||||
EnableThemeSelection: true,
|
||||
DefaultTheme: 'default',
|
||||
AllowCustomThemes: true,
|
||||
AllowedThemes: [],
|
||||
},
|
||||
GitLabSettings: {
|
||||
Enable: false,
|
||||
Secret: '',
|
||||
Id: '',
|
||||
Scope: '',
|
||||
AuthEndpoint: '',
|
||||
TokenEndpoint: '',
|
||||
UserAPIEndpoint: '',
|
||||
DiscoveryEndpoint: '',
|
||||
ButtonText: '',
|
||||
ButtonColor: '',
|
||||
},
|
||||
GoogleSettings: {
|
||||
Enable: false,
|
||||
Secret: '',
|
||||
Id: '',
|
||||
Scope: 'profile email',
|
||||
AuthEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
TokenEndpoint: 'https://www.googleapis.com/oauth2/v4/token',
|
||||
UserAPIEndpoint:
|
||||
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata',
|
||||
DiscoveryEndpoint: '',
|
||||
ButtonText: '',
|
||||
ButtonColor: '',
|
||||
},
|
||||
Office365Settings: {
|
||||
Enable: false,
|
||||
Secret: '',
|
||||
Id: '',
|
||||
Scope: 'User.Read',
|
||||
AuthEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
TokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
||||
UserAPIEndpoint: 'https://graph.microsoft.com/v1.0/me',
|
||||
DiscoveryEndpoint: '',
|
||||
DirectoryId: '',
|
||||
},
|
||||
OpenIdSettings: {
|
||||
Enable: false,
|
||||
Secret: '',
|
||||
Id: '',
|
||||
Scope: 'profile openid email',
|
||||
AuthEndpoint: '',
|
||||
TokenEndpoint: '',
|
||||
UserAPIEndpoint: '',
|
||||
DiscoveryEndpoint: '',
|
||||
ButtonText: '',
|
||||
ButtonColor: '#145DBF',
|
||||
},
|
||||
LdapSettings: {
|
||||
Enable: false,
|
||||
EnableSync: false,
|
||||
LdapServer: '',
|
||||
LdapPort: 389,
|
||||
ConnectionSecurity: '',
|
||||
BaseDN: '',
|
||||
BindUsername: '',
|
||||
BindPassword: '',
|
||||
UserFilter: '',
|
||||
GroupFilter: '',
|
||||
GuestFilter: '',
|
||||
EnableAdminFilter: false,
|
||||
AdminFilter: '',
|
||||
GroupDisplayNameAttribute: '',
|
||||
GroupIdAttribute: '',
|
||||
FirstNameAttribute: '',
|
||||
LastNameAttribute: '',
|
||||
EmailAttribute: '',
|
||||
UsernameAttribute: '',
|
||||
NicknameAttribute: '',
|
||||
IdAttribute: '',
|
||||
PositionAttribute: '',
|
||||
LoginIdAttribute: '',
|
||||
PictureAttribute: '',
|
||||
SyncIntervalMinutes: 60,
|
||||
SkipCertificateVerification: false,
|
||||
PublicCertificateFile: '',
|
||||
PrivateKeyFile: '',
|
||||
QueryTimeout: 60,
|
||||
MaxPageSize: 0,
|
||||
LoginFieldName: '',
|
||||
LoginButtonColor: '#0000',
|
||||
LoginButtonBorderColor: '#2389D7',
|
||||
LoginButtonTextColor: '#2389D7',
|
||||
Trace: false,
|
||||
},
|
||||
ComplianceSettings: {
|
||||
Enable: false,
|
||||
Directory: './data/',
|
||||
EnableDaily: false,
|
||||
BatchSize: 30000,
|
||||
},
|
||||
LocalizationSettings: {
|
||||
DefaultServerLocale: 'en',
|
||||
DefaultClientLocale: 'en',
|
||||
AvailableLocales: '',
|
||||
},
|
||||
SamlSettings: {
|
||||
Enable: false,
|
||||
EnableSyncWithLdap: false,
|
||||
EnableSyncWithLdapIncludeAuth: false,
|
||||
IgnoreGuestsLdapSync: false,
|
||||
Verify: true,
|
||||
Encrypt: true,
|
||||
SignRequest: false,
|
||||
IdpURL: '',
|
||||
IdpDescriptorURL: '',
|
||||
IdpMetadataURL: '',
|
||||
ServiceProviderIdentifier: '',
|
||||
AssertionConsumerServiceURL: '',
|
||||
SignatureAlgorithm: 'RSAwithSHA1',
|
||||
CanonicalAlgorithm: 'Canonical1.0',
|
||||
ScopingIDPProviderId: '',
|
||||
ScopingIDPName: '',
|
||||
IdpCertificateFile: '',
|
||||
PublicCertificateFile: '',
|
||||
PrivateKeyFile: '',
|
||||
IdAttribute: '',
|
||||
GuestAttribute: '',
|
||||
EnableAdminAttribute: false,
|
||||
AdminAttribute: '',
|
||||
FirstNameAttribute: '',
|
||||
LastNameAttribute: '',
|
||||
EmailAttribute: '',
|
||||
UsernameAttribute: '',
|
||||
NicknameAttribute: '',
|
||||
LocaleAttribute: '',
|
||||
PositionAttribute: '',
|
||||
LoginButtonText: 'SAML',
|
||||
LoginButtonColor: '#34a28b',
|
||||
LoginButtonBorderColor: '#2389D7',
|
||||
LoginButtonTextColor: '#ffffff',
|
||||
},
|
||||
NativeAppSettings: {
|
||||
AppCustomURLSchemes: ['mmauth://', 'mmauthbeta://'],
|
||||
AppDownloadLink: 'https://mattermost.com/download/#mattermostApps',
|
||||
AndroidAppDownloadLink: 'https://mattermost.com/mattermost-android-app/',
|
||||
IosAppDownloadLink: 'https://mattermost.com/mattermost-ios-app/',
|
||||
},
|
||||
ClusterSettings: {
|
||||
Enable: false,
|
||||
ClusterName: '',
|
||||
OverrideHostname: '',
|
||||
NetworkInterface: '',
|
||||
BindAddress: '',
|
||||
AdvertiseAddress: '',
|
||||
UseIPAddress: true,
|
||||
EnableGossipCompression: true,
|
||||
EnableExperimentalGossipEncryption: false,
|
||||
ReadOnlyConfig: true,
|
||||
GossipPort: 8074,
|
||||
StreamingPort: 8075,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 128,
|
||||
IdleConnTimeoutMilliseconds: 90000,
|
||||
},
|
||||
MetricsSettings: {
|
||||
Enable: false,
|
||||
BlockProfileRate: 0,
|
||||
ListenAddress: ':8067',
|
||||
},
|
||||
ExperimentalSettings: {
|
||||
ClientSideCertEnable: false,
|
||||
ClientSideCertCheck: 'secondary',
|
||||
LinkMetadataTimeoutMilliseconds: 5000,
|
||||
RestrictSystemAdmin: false,
|
||||
UseNewSAMLLibrary: false,
|
||||
EnableSharedChannels: false,
|
||||
EnableRemoteClusterService: false,
|
||||
EnableAppBar: false,
|
||||
PatchPluginsReactDOM: false,
|
||||
},
|
||||
AnalyticsSettings: {
|
||||
MaxUsersForStatistics: 2500,
|
||||
},
|
||||
ElasticsearchSettings: {
|
||||
ConnectionURL: 'http://localhost:9200',
|
||||
Username: 'elastic',
|
||||
Password: 'changeme',
|
||||
EnableIndexing: false,
|
||||
EnableSearching: false,
|
||||
EnableAutocomplete: false,
|
||||
Sniff: true,
|
||||
PostIndexReplicas: 1,
|
||||
PostIndexShards: 1,
|
||||
ChannelIndexReplicas: 1,
|
||||
ChannelIndexShards: 1,
|
||||
UserIndexReplicas: 1,
|
||||
UserIndexShards: 1,
|
||||
AggregatePostsAfterDays: 365,
|
||||
PostsAggregatorJobStartTime: '03:00',
|
||||
IndexPrefix: '',
|
||||
LiveIndexingBatchSize: 1,
|
||||
BatchSize: 10000,
|
||||
RequestTimeoutSeconds: 30,
|
||||
SkipTLSVerification: false,
|
||||
CA: '',
|
||||
ClientCert: '',
|
||||
ClientKey: '',
|
||||
Trace: '',
|
||||
},
|
||||
BleveSettings: {
|
||||
IndexDir: '',
|
||||
EnableIndexing: false,
|
||||
EnableSearching: false,
|
||||
EnableAutocomplete: false,
|
||||
BatchSize: 10000,
|
||||
},
|
||||
DataRetentionSettings: {
|
||||
EnableMessageDeletion: false,
|
||||
EnableFileDeletion: false,
|
||||
EnableBoardsDeletion: false,
|
||||
MessageRetentionDays: 365,
|
||||
FileRetentionDays: 365,
|
||||
BoardsRetentionDays: 365,
|
||||
DeletionJobStartTime: '02:00',
|
||||
BatchSize: 3000,
|
||||
},
|
||||
MessageExportSettings: {
|
||||
EnableExport: false,
|
||||
ExportFormat: 'actiance',
|
||||
DailyRunTime: '01:00',
|
||||
ExportFromTimestamp: 0,
|
||||
BatchSize: 10000,
|
||||
DownloadExportResults: false,
|
||||
GlobalRelaySettings: {
|
||||
CustomerType: 'A9',
|
||||
SMTPUsername: '',
|
||||
SMTPPassword: '',
|
||||
EmailAddress: '',
|
||||
SMTPServerTimeout: 1800,
|
||||
},
|
||||
},
|
||||
JobSettings: {
|
||||
RunJobs: true,
|
||||
RunScheduler: true,
|
||||
CleanupJobsThresholdDays: -1,
|
||||
CleanupConfigThresholdDays: -1,
|
||||
},
|
||||
ProductSettings: {
|
||||
EnablePublicSharedBoards: false,
|
||||
},
|
||||
PluginSettings: {
|
||||
Enable: true,
|
||||
EnableUploads: false,
|
||||
AllowInsecureDownloadURL: false,
|
||||
EnableHealthCheck: true,
|
||||
Directory: './plugins',
|
||||
ClientDirectory: './client/plugins',
|
||||
Plugins: {},
|
||||
PluginStates: {
|
||||
'com.mattermost.apps': {
|
||||
Enable: true,
|
||||
},
|
||||
'com.mattermost.calls': {
|
||||
Enable: true,
|
||||
},
|
||||
'com.mattermost.nps': {
|
||||
Enable: true,
|
||||
},
|
||||
focalboard: {
|
||||
Enable: true,
|
||||
},
|
||||
playbooks: {
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
EnableMarketplace: true,
|
||||
EnableRemoteMarketplace: true,
|
||||
AutomaticPrepackagedPlugins: true,
|
||||
RequirePluginSignature: false,
|
||||
MarketplaceURL: 'https://api.integrations.mattermost.com',
|
||||
SignaturePublicKeyFiles: [],
|
||||
ChimeraOAuthProxyURL: '',
|
||||
},
|
||||
DisplaySettings: {
|
||||
CustomURLSchemes: [],
|
||||
ExperimentalTimezone: true,
|
||||
},
|
||||
GuestAccountsSettings: {
|
||||
Enable: false,
|
||||
AllowEmailAccounts: true,
|
||||
EnforceMultifactorAuthentication: false,
|
||||
RestrictCreationToDomains: '',
|
||||
},
|
||||
ImageProxySettings: {
|
||||
Enable: false,
|
||||
ImageProxyType: 'local',
|
||||
RemoteImageProxyURL: '',
|
||||
RemoteImageProxyOptions: '',
|
||||
},
|
||||
CloudSettings: {
|
||||
CWSURL: 'https://customers.mattermost.com',
|
||||
CWSAPIURL: 'https://portal.internal.prod.cloud.mattermost.com',
|
||||
},
|
||||
FeatureFlags: {
|
||||
TestFeature: 'off',
|
||||
TestBoolFeature: false,
|
||||
EnableRemoteClusterService: false,
|
||||
AppsEnabled: true,
|
||||
PluginPlaybooks: '',
|
||||
PluginApps: '',
|
||||
PluginFocalboard: '',
|
||||
PluginCalls: '',
|
||||
PermalinkPreviews: true,
|
||||
CallsEnabled: true,
|
||||
BoardsFeatureFlags: '',
|
||||
BoardsDataRetention: false,
|
||||
NormalizeLdapDNs: false,
|
||||
EnableInactivityCheckJob: true,
|
||||
UseCaseOnboarding: true,
|
||||
GraphQL: false,
|
||||
InsightsEnabled: true,
|
||||
CommandPalette: false,
|
||||
BoardsProduct: false,
|
||||
SendWelcomePost: true,
|
||||
WorkTemplate: false,
|
||||
PostPriority: true,
|
||||
WysiwygEditor: false,
|
||||
PeopleProduct: false,
|
||||
ReduceOnBoardingTaskList: false,
|
||||
OnboardingAutoShowLinkedBoard: true,
|
||||
ThreadsEverywhere: false,
|
||||
GlobalDrafts: true,
|
||||
OnboardingTourTips: true,
|
||||
},
|
||||
ImportSettings: {
|
||||
Directory: './import',
|
||||
RetentionDays: 30,
|
||||
},
|
||||
ExportSettings: {
|
||||
Directory: './export',
|
||||
RetentionDays: 30,
|
||||
},
|
||||
};
|
||||
9
e2e-tests/playwright/support/server/index.ts
Обычный файл
9
e2e-tests/playwright/support/server/index.ts
Обычный файл
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export {Client, makeClient} from './client';
|
||||
export {createRandomChannel} from './channel';
|
||||
export {getOnPremServerConfig} from './default_config';
|
||||
export {initSetup, getAdminClient} from './init';
|
||||
export {createRandomTeam} from './team';
|
||||
export {createRandomUser, getDefaultAdminUser} from './user';
|
||||
100
e2e-tests/playwright/support/server/init.ts
Обычный файл
100
e2e-tests/playwright/support/server/init.ts
Обычный файл
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import path from 'node:path';
|
||||
import {expect} from '@playwright/test';
|
||||
|
||||
import {PreferenceType} from '@mattermost/types/preferences';
|
||||
import testConfig from '@e2e-test.config';
|
||||
|
||||
import {makeClient} from '.';
|
||||
import {getOnPremServerConfig} from './default_config';
|
||||
import {createRandomTeam} from './team';
|
||||
import {createRandomUser} from './user';
|
||||
|
||||
const boardsUserConfigPatch = {
|
||||
updatedFields: {
|
||||
welcomePageViewed: '1',
|
||||
onboardingTourStep: '999',
|
||||
tourCategory: 'board',
|
||||
version72MessageCanceled: 'true',
|
||||
},
|
||||
};
|
||||
|
||||
export async function initSetup({
|
||||
userPrefix = 'user',
|
||||
teamPrefix = {name: 'team', displayName: 'Team'},
|
||||
withDefaultProfileImage = true,
|
||||
skipBoardsUserConfig = true,
|
||||
} = {}) {
|
||||
try {
|
||||
// Login the admin user via API
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
// Reset server config
|
||||
const adminConfig = await adminClient.updateConfig(getOnPremServerConfig());
|
||||
|
||||
// Create new team
|
||||
const team = await adminClient.createTeam(createRandomTeam(teamPrefix.name, teamPrefix.displayName));
|
||||
|
||||
// Create new user and add to newly created team
|
||||
const randomUser = createRandomUser(userPrefix);
|
||||
const user = await adminClient.createUser(randomUser, '', '');
|
||||
user.password = randomUser.password;
|
||||
await adminClient.addToTeam(team.id, user.id);
|
||||
|
||||
// Log in new user via API
|
||||
const {client: userClient} = await makeClient(user);
|
||||
|
||||
if (withDefaultProfileImage) {
|
||||
// Set user profile image
|
||||
const fullPath = path.join(path.resolve(__dirname), '../', 'asset/mattermost-icon_128x128.png');
|
||||
await userClient.uploadProfileImageX(user.id, fullPath);
|
||||
}
|
||||
|
||||
// Update user preference
|
||||
const preferences: PreferenceType[] = [
|
||||
{user_id: user.id, category: 'tutorial_step', name: user.id, value: '999'},
|
||||
];
|
||||
await userClient.savePreferences(user.id, preferences);
|
||||
|
||||
if (skipBoardsUserConfig) {
|
||||
await userClient.patchUserConfig(user.id, boardsUserConfigPatch);
|
||||
}
|
||||
|
||||
return {
|
||||
adminClient,
|
||||
adminUser,
|
||||
adminConfig,
|
||||
user,
|
||||
userClient,
|
||||
team,
|
||||
offTopicUrl: getUrl(team.name, 'off-topic'),
|
||||
townSquareUrl: getUrl(team.name, 'town-square'),
|
||||
};
|
||||
} catch (err) {
|
||||
// log an error for debugging
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
expect(err, 'Should not throw an error').toBeFalsy();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAdminClient() {
|
||||
const {client: adminClient, user: adminUser} = await makeClient({
|
||||
username: testConfig.adminUsername,
|
||||
password: testConfig.adminPassword,
|
||||
});
|
||||
|
||||
return {adminClient, adminUser};
|
||||
}
|
||||
|
||||
function getUrl(teamName: string, channelName: string) {
|
||||
return `/${teamName}/channels/${channelName}`;
|
||||
}
|
||||
17
e2e-tests/playwright/support/server/team.ts
Обычный файл
17
e2e-tests/playwright/support/server/team.ts
Обычный файл
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Team, TeamType} from '@mattermost/types/teams';
|
||||
import {getRandomId} from '@e2e-support/util';
|
||||
|
||||
export function createRandomTeam(name = 'team', displayName = 'Team', type: TeamType = 'O', unique = true): Team {
|
||||
const randomSuffix = getRandomId();
|
||||
|
||||
const team = {
|
||||
name: unique ? `${name}-${randomSuffix}` : name,
|
||||
display_name: unique ? `${displayName} ${randomSuffix}` : displayName,
|
||||
type,
|
||||
};
|
||||
|
||||
return team as Team;
|
||||
}
|
||||
33
e2e-tests/playwright/support/server/user.ts
Обычный файл
33
e2e-tests/playwright/support/server/user.ts
Обычный файл
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
import {getRandomId} from '@e2e-support/util';
|
||||
import testConfig from '@e2e-test.config';
|
||||
|
||||
export function createRandomUser(prefix = 'user') {
|
||||
const randomId = getRandomId();
|
||||
|
||||
const user = {
|
||||
email: `${prefix}${randomId}@sample.mattermost.com`,
|
||||
username: `${prefix}${randomId}`,
|
||||
password: 'passwd',
|
||||
first_name: `First${randomId}`,
|
||||
last_name: `Last${randomId}`,
|
||||
nickname: `Nickname${randomId}`,
|
||||
};
|
||||
|
||||
return user as UserProfile;
|
||||
}
|
||||
|
||||
export function getDefaultAdminUser() {
|
||||
const admin = {
|
||||
username: testConfig.adminUsername,
|
||||
password: testConfig.adminPassword,
|
||||
first_name: 'Kenneth',
|
||||
last_name: 'Moreno',
|
||||
email: testConfig.adminEmail,
|
||||
};
|
||||
|
||||
return admin as UserProfile;
|
||||
}
|
||||
21
e2e-tests/playwright/support/test_action.ts
Обычный файл
21
e2e-tests/playwright/support/test_action.ts
Обычный файл
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, Page} from '@playwright/test';
|
||||
|
||||
export {waitUntil} from 'async-wait-until';
|
||||
|
||||
const visibilityHidden = 'visibility: hidden !important;';
|
||||
const hideTeamHeader = `.test-team-header {${visibilityHidden}} `;
|
||||
const hidePostHeaderTime = `.post__time {${visibilityHidden}} `;
|
||||
const hidePostProfileIcon = `.profile-icon {${visibilityHidden}} `;
|
||||
|
||||
export async function hideDynamicChannelsContent(page: Page) {
|
||||
await page.addStyleTag({content: hideTeamHeader + hidePostHeaderTime + hidePostProfileIcon});
|
||||
}
|
||||
|
||||
export async function waitForAnimationEnd(locator: Locator) {
|
||||
return locator.evaluate((element) =>
|
||||
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished))
|
||||
);
|
||||
}
|
||||
86
e2e-tests/playwright/support/test_fixture.ts
Обычный файл
86
e2e-tests/playwright/support/test_fixture.ts
Обычный файл
@@ -0,0 +1,86 @@
|
||||
import {test as base, Browser} from '@playwright/test';
|
||||
|
||||
import {TestBrowser} from './browser_context';
|
||||
import {
|
||||
shouldHaveBoardsEnabled,
|
||||
shouldHaveCallsEnabled,
|
||||
shouldHaveFeatureFlag,
|
||||
shouldSkipInSmallScreen,
|
||||
shouldRunInLinux,
|
||||
} from './flag';
|
||||
import {initSetup, getAdminClient} from './server';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||
import {pages} from './ui/pages';
|
||||
import {matchSnapshot} from './visual';
|
||||
|
||||
export {expect} from '@playwright/test';
|
||||
|
||||
type ExtendedFixtures = {
|
||||
pw: PlaywrightExtended;
|
||||
pages: typeof pages;
|
||||
};
|
||||
|
||||
export const test = base.extend<ExtendedFixtures>({
|
||||
pw: async ({browser}, use) => {
|
||||
const pw = new PlaywrightExtended(browser);
|
||||
await use(pw);
|
||||
},
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
pages: async ({}, use) => {
|
||||
await use(pages);
|
||||
},
|
||||
});
|
||||
|
||||
class PlaywrightExtended {
|
||||
// ./browser_context
|
||||
readonly testBrowser: TestBrowser;
|
||||
|
||||
// ./flag
|
||||
readonly shouldHaveBoardsEnabled;
|
||||
readonly shouldHaveCallsEnabled;
|
||||
readonly shouldHaveFeatureFlag;
|
||||
readonly shouldSkipInSmallScreen;
|
||||
readonly shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
readonly getAdminClient;
|
||||
readonly initSetup;
|
||||
|
||||
// ./test_action
|
||||
readonly hideDynamicChannelsContent;
|
||||
readonly waitForAnimationEnd;
|
||||
readonly waitUntil;
|
||||
|
||||
// ./ui/pages
|
||||
readonly pages;
|
||||
|
||||
// ./visual
|
||||
readonly matchSnapshot;
|
||||
|
||||
constructor(browser: Browser) {
|
||||
// ./browser_context
|
||||
this.testBrowser = new TestBrowser(browser);
|
||||
|
||||
// ./flag
|
||||
this.shouldHaveBoardsEnabled = shouldHaveBoardsEnabled;
|
||||
this.shouldHaveCallsEnabled = shouldHaveCallsEnabled;
|
||||
this.shouldHaveFeatureFlag = shouldHaveFeatureFlag;
|
||||
this.shouldSkipInSmallScreen = shouldSkipInSmallScreen;
|
||||
this.shouldRunInLinux = shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
this.initSetup = initSetup;
|
||||
this.getAdminClient = getAdminClient;
|
||||
|
||||
// ./test_action
|
||||
this.hideDynamicChannelsContent = hideDynamicChannelsContent;
|
||||
this.waitForAnimationEnd = waitForAnimationEnd;
|
||||
this.waitUntil = waitUntil;
|
||||
|
||||
// ./ui/pages
|
||||
this.pages = pages;
|
||||
|
||||
// ./visual
|
||||
this.matchSnapshot = matchSnapshot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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};
|
||||
28
e2e-tests/playwright/support/ui/components/boards/sidebar.ts
Обычный файл
28
e2e-tests/playwright/support/ui/components/boards/sidebar.ts
Обычный файл
@@ -0,0 +1,28 @@
|
||||
// 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};
|
||||
22
e2e-tests/playwright/support/ui/components/channels/app_bar.ts
Обычный файл
22
e2e-tests/playwright/support/ui/components/channels/app_bar.ts
Обычный файл
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsAppBar {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly playbooksIcon;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.playbooksIcon = container.locator('#app-bar-icon-playbooks').getByRole('img');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsAppBar};
|
||||
18
e2e-tests/playwright/support/ui/components/channels/header.ts
Обычный файл
18
e2e-tests/playwright/support/ui/components/channels/header.ts
Обычный файл
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsHeader {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsHeader};
|
||||
42
e2e-tests/playwright/support/ui/components/channels/post.ts
Обычный файл
42
e2e-tests/playwright/support/ui/components/channels/post.ts
Обычный файл
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsPost {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly body;
|
||||
readonly profileIcon;
|
||||
readonly replyButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.post__body');
|
||||
this.profileIcon = container.locator('.profile-icon');
|
||||
this.replyButton = container.getByRole('button', {name: 'reply'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async getId() {
|
||||
const id = await this.container.getAttribute('id');
|
||||
expect(id, 'No post ID found.').toBeTruthy();
|
||||
return (id || '').substring('post_'.length);
|
||||
}
|
||||
|
||||
async getProfileImage(username: string) {
|
||||
return await this.profileIcon.getByAltText(`${username} profile image`);
|
||||
}
|
||||
|
||||
async openRHS() {
|
||||
await this.container.hover();
|
||||
await this.replyButton.waitFor();
|
||||
await this.replyButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPost};
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsPostCreate {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly input;
|
||||
readonly attachmentButton;
|
||||
readonly emojiButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.input = container.getByTestId('post_textbox');
|
||||
this.attachmentButton = container.getByLabel('attachment');
|
||||
this.emojiButton = container.getByLabel('select an emoji');
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
await this.input.fill(message);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
await expect(this.input).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPostCreate};
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsSidebarRight {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsSidebarRight};
|
||||
27
e2e-tests/playwright/support/ui/components/global_header.ts
Обычный файл
27
e2e-tests/playwright/support/ui/components/global_header.ts
Обычный файл
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class GlobalHeader {
|
||||
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 {GlobalHeader};
|
||||
31
e2e-tests/playwright/support/ui/components/index.ts
Обычный файл
31
e2e-tests/playwright/support/ui/components/index.ts
Обычный файл
@@ -0,0 +1,31 @@
|
||||
// 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 {ChannelsAppBar} from './channels/app_bar';
|
||||
import {ChannelsPostCreate} from './channels/post_create';
|
||||
import {ChannelsPost} from './channels/post';
|
||||
import {ChannelsSidebarRight} from './channels/sidebar_right';
|
||||
import {GlobalHeader} from './global_header';
|
||||
|
||||
const components = {
|
||||
BoardsSidebar,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarRight,
|
||||
GlobalHeader,
|
||||
};
|
||||
|
||||
export {
|
||||
components,
|
||||
BoardsSidebar,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarRight,
|
||||
GlobalHeader,
|
||||
};
|
||||
47
e2e-tests/playwright/support/ui/pages/boards_create.ts
Обычный файл
47
e2e-tests/playwright/support/ui/pages/boards_create.ts
Обычный файл
@@ -0,0 +1,47 @@
|
||||
// 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};
|
||||
60
e2e-tests/playwright/support/ui/pages/boards_view.ts
Обычный файл
60
e2e-tests/playwright/support/ui/pages/boards_view.ts
Обычный файл
@@ -0,0 +1,60 @@
|
||||
// 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};
|
||||
106
e2e-tests/playwright/support/ui/pages/channels.ts
Обычный файл
106
e2e-tests/playwright/support/ui/pages/channels.ts
Обычный файл
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
import {waitUntil} from '@e2e-support/test_action';
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
import {duration, isSmallScreen} from '@e2e-support/util';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
readonly page: Page;
|
||||
readonly postCreate;
|
||||
readonly globalHeader;
|
||||
readonly header;
|
||||
readonly appBar;
|
||||
readonly sidebarRight;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create'));
|
||||
this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
|
||||
this.header = new components.ChannelsHeader(page.locator('.channel-header'));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||
}
|
||||
|
||||
async goto(teamName = '', channelName = '') {
|
||||
let channelsUrl = '/';
|
||||
if (teamName) {
|
||||
channelsUrl += `${teamName}`;
|
||||
if (channelName) {
|
||||
channelsUrl += `/${channelName}`;
|
||||
}
|
||||
}
|
||||
|
||||
await this.page.goto(channelsUrl);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
if (!isSmallScreen(this.page.viewportSize())) {
|
||||
await this.globalHeader.toBeVisible(this.channels);
|
||||
}
|
||||
await this.postCreate.toBeVisible();
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
await this.postCreate.input.waitFor();
|
||||
await this.postCreate.postMessage(message);
|
||||
}
|
||||
|
||||
async getFirstPost() {
|
||||
await this.page.getByTestId('postView').first().waitFor();
|
||||
const post = await this.page.getByTestId('postView').first();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getLastPost() {
|
||||
await this.page.getByTestId('postView').last().waitFor();
|
||||
const post = await this.page.getByTestId('postView').last();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getNthPost(index: number) {
|
||||
await this.page.getByTestId('postView').nth(index).waitFor();
|
||||
const post = await this.page.getByTestId('postView').nth(index);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getPostById(id: string) {
|
||||
await this.page.locator(`[id="post_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="post_${id}"]`);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getRHSPostById(id: string) {
|
||||
await this.page.locator(`[id="rhsPost_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="rhsPost_${id}"]`);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getLastPost();
|
||||
const content = await post.container.textContent();
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
|
||||
async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getPostById(id);
|
||||
const content = await post.container.textContent();
|
||||
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPage};
|
||||
20
e2e-tests/playwright/support/ui/pages/index.ts
Обычный файл
20
e2e-tests/playwright/support/ui/pages/index.ts
Обычный файл
@@ -0,0 +1,20 @@
|
||||
// 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';
|
||||
import {SignupPage} from './signup';
|
||||
|
||||
const pages = {
|
||||
BoardsCreatePage,
|
||||
BoardsViewPage,
|
||||
ChannelsPage,
|
||||
LandingLoginPage,
|
||||
LoginPage,
|
||||
SignupPage,
|
||||
};
|
||||
|
||||
export {pages, BoardsCreatePage, BoardsViewPage, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};
|
||||
41
e2e-tests/playwright/support/ui/pages/landing_login.ts
Обычный файл
41
e2e-tests/playwright/support/ui/pages/landing_login.ts
Обычный файл
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
export default class LandingLoginPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly isMobile?: boolean;
|
||||
|
||||
readonly viewInAppButton;
|
||||
readonly viewInDesktopAppButton;
|
||||
readonly viewInBrowserButton;
|
||||
|
||||
constructor(page: Page, isMobile?: boolean) {
|
||||
this.page = page;
|
||||
this.isMobile = isMobile;
|
||||
|
||||
this.viewInAppButton = page.locator('text=View in App');
|
||||
this.viewInDesktopAppButton = page.locator('text=View in Desktop App');
|
||||
this.viewInBrowserButton = page.locator('text=View in Browser');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
|
||||
if (this.isMobile) {
|
||||
await expect(this.viewInAppButton).toBeVisible();
|
||||
} else {
|
||||
await expect(this.viewInDesktopAppButton).toBeVisible();
|
||||
}
|
||||
|
||||
await expect(this.viewInBrowserButton).toBeVisible();
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/landing#/login');
|
||||
}
|
||||
}
|
||||
|
||||
export {LandingLoginPage};
|
||||
67
e2e-tests/playwright/support/ui/pages/login.ts
Обычный файл
67
e2e-tests/playwright/support/ui/pages/login.ts
Обычный файл
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {AdminConfig} from '@mattermost/types/config';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
export default class LoginPage {
|
||||
readonly adminConfig: AdminConfig;
|
||||
|
||||
readonly page: Page;
|
||||
|
||||
readonly title;
|
||||
readonly subtitle;
|
||||
readonly bodyCard;
|
||||
readonly loginInput;
|
||||
readonly loginPlaceholder;
|
||||
readonly passwordInput;
|
||||
readonly signInButton;
|
||||
readonly createAccountLink;
|
||||
readonly forgotPasswordLink;
|
||||
readonly userErrorLabel;
|
||||
readonly fieldWithError;
|
||||
readonly formContainer;
|
||||
|
||||
constructor(page: Page, adminConfig: AdminConfig) {
|
||||
this.page = page;
|
||||
this.adminConfig = adminConfig;
|
||||
|
||||
const loginInputPlaceholder = adminConfig.LdapSettings.Enable
|
||||
? 'Email, Username or AD/LDAP Username'
|
||||
: 'Email or Username';
|
||||
|
||||
this.title = page.locator('h1:has-text("Log in to your account")');
|
||||
this.subtitle = page.locator('text=Collaborate with your team in real-time');
|
||||
this.bodyCard = page.locator('.login-body-card');
|
||||
this.loginInput = page.locator('#input_loginId');
|
||||
this.loginPlaceholder = page.locator(`[placeholder="${loginInputPlaceholder}"]`);
|
||||
this.passwordInput = page.locator('#input_password-input');
|
||||
this.signInButton = page.locator('button:has-text("Log in")');
|
||||
this.createAccountLink = page.locator("text=Don't have an account?");
|
||||
this.forgotPasswordLink = page.locator('text=Forgot your password?');
|
||||
this.userErrorLabel = page.locator('text=Please enter your email or username');
|
||||
this.fieldWithError = page.locator('.with-error');
|
||||
this.formContainer = page.locator('.signup-team__container');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await expect(this.title).toBeVisible();
|
||||
await expect(this.loginInput).toBeVisible();
|
||||
await expect(this.passwordInput).toBeVisible();
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/login');
|
||||
}
|
||||
|
||||
async login(user: UserProfile, useUsername = true) {
|
||||
await this.loginInput.fill(useUsername ? user.username : user.email);
|
||||
await this.passwordInput.fill(user.password);
|
||||
await Promise.all([this.page.waitForNavigation(), this.signInButton.click()]);
|
||||
}
|
||||
}
|
||||
|
||||
export {LoginPage};
|
||||
67
e2e-tests/playwright/support/ui/pages/signup.ts
Обычный файл
67
e2e-tests/playwright/support/ui/pages/signup.ts
Обычный файл
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {duration, wait} from '@e2e-support/util';
|
||||
|
||||
export default class SignupPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly title;
|
||||
readonly subtitle;
|
||||
readonly bodyCard;
|
||||
readonly emailInput;
|
||||
readonly usernameInput;
|
||||
readonly passwordInput;
|
||||
readonly createAccountButton;
|
||||
readonly loginLink;
|
||||
readonly emailError;
|
||||
readonly usernameError;
|
||||
readonly passwordError;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.title = page.locator('h1:has-text("Let’s get started")');
|
||||
this.subtitle = page.locator('text=Create your Mattermost account to start collaborating with your team');
|
||||
this.bodyCard = page.locator('.signup-body-card');
|
||||
this.emailInput = page.locator('#input_email');
|
||||
this.usernameInput = page.locator('#input_name');
|
||||
this.passwordInput = page.locator('#input_password-input');
|
||||
this.createAccountButton = page.locator('button:has-text("Create Account")');
|
||||
this.loginLink = page.locator('text=Click here to sign in.');
|
||||
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.'
|
||||
);
|
||||
this.passwordError = page.locator('text=Must be 5-64 characters long.');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await this.page.waitForLoadState('domcontentloaded');
|
||||
await wait(duration.half_sec);
|
||||
await expect(this.title).toBeVisible();
|
||||
await expect(this.emailInput).toBeVisible();
|
||||
await expect(this.usernameInput).toBeVisible();
|
||||
await expect(this.passwordInput).toBeVisible();
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/signup_email');
|
||||
}
|
||||
|
||||
async create(user: {email: string; username: string; password: string}, waitForRedirect = true) {
|
||||
await this.emailInput.fill(user.email);
|
||||
await this.usernameInput.fill(user.username);
|
||||
await this.passwordInput.fill(user.password);
|
||||
await this.createAccountButton.click();
|
||||
|
||||
if (waitForRedirect) {
|
||||
await this.page.waitForNavigation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {SignupPage};
|
||||
52
e2e-tests/playwright/support/util.ts
Обычный файл
52
e2e-tests/playwright/support/util.ts
Обычный файл
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
import {ViewportSize} from '@playwright/test';
|
||||
|
||||
const second = 1000;
|
||||
const minute = 60 * 1000;
|
||||
|
||||
export const duration = {
|
||||
half_sec: second / 2,
|
||||
one_sec: second,
|
||||
two_sec: second * 2,
|
||||
four_sec: second * 4,
|
||||
ten_sec: second * 10,
|
||||
half_min: minute / 2,
|
||||
one_min: minute,
|
||||
two_min: minute * 2,
|
||||
four_min: minute * 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit `wait` should not normally used but made available for special cases.
|
||||
* @param {number} ms - duration in millisecond
|
||||
* @return {Promise} promise with timeout
|
||||
*/
|
||||
export const wait = async (ms = 0) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Number} length - length on random string to return, e.g. 7 (default)
|
||||
* @return {String} random string
|
||||
*/
|
||||
export function getRandomId(length = 7): string {
|
||||
const MAX_SUBSTRING_INDEX = 27;
|
||||
|
||||
return uuidv4()
|
||||
.replace(/-/g, '')
|
||||
.substring(MAX_SUBSTRING_INDEX - length, MAX_SUBSTRING_INDEX);
|
||||
}
|
||||
|
||||
// Default team is meant for sysadmin's primary team,
|
||||
// selected for compatibility with existing local development.
|
||||
// It should not be used for testing.
|
||||
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;
|
||||
}
|
||||
38
e2e-tests/playwright/support/visual/index.ts
Обычный файл
38
e2e-tests/playwright/support/visual/index.ts
Обычный файл
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import os from 'node:os';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {expect, TestInfo} from '@playwright/test';
|
||||
|
||||
import {illegalRe} from '@e2e-support/util';
|
||||
import testConfig, {TestArgs} from '@e2e-test.config';
|
||||
|
||||
import snapshotWithPercy from './percy';
|
||||
|
||||
export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs) {
|
||||
if (os.platform() !== 'linux') {
|
||||
// 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.`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (testConfig.snapshotEnabled) {
|
||||
// Visual test with built-in snapshot
|
||||
const filename = testInfo.title.replace(illegalRe, '').replace(/\s/g, '-').trim().toLowerCase();
|
||||
expect(await testArgs.page.screenshot({fullPage: true})).toMatchSnapshot(`${filename}.png`);
|
||||
}
|
||||
|
||||
if (testConfig.percyEnabled) {
|
||||
// Used to easily identify the screenshot when viewing from third-party service provider.
|
||||
const name = `[${testInfo.project.name}, ${testArgs?.viewport?.width}px] > ${testInfo.file} > ${testInfo.title}`;
|
||||
|
||||
// Visual test with Percy
|
||||
await snapshotWithPercy(name, testArgs);
|
||||
}
|
||||
}
|
||||
22
e2e-tests/playwright/support/visual/percy.ts
Обычный файл
22
e2e-tests/playwright/support/visual/percy.ts
Обычный файл
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import percySnapshot from '@percy/playwright';
|
||||
|
||||
import testConfig, {TestArgs} from '@e2e-test.config';
|
||||
|
||||
export default async function snapshotWithPercy(name: string, testArgs: TestArgs) {
|
||||
if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) {
|
||||
if (!testConfig.percyToken) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error: Token is missing! Please set using: "export PERCY_TOKEN=<change_me>"');
|
||||
}
|
||||
|
||||
const {page, viewport} = testArgs;
|
||||
|
||||
// Ignore since percy is using Playwright.Page
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await percySnapshot(page, name, {widths: [viewport.width], minHeight: viewport.height});
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user