Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
2
e2e/playwright/.eslintignore
Обычный файл
@@ -0,0 +1,2 @@
|
||||
results
|
||||
node_modules
|
||||
14
e2e/playwright/.eslintrc.json
Обычный файл
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-console": "error"
|
||||
}
|
||||
}
|
||||
3
e2e/playwright/.percy.yml
Обычный файл
@@ -0,0 +1,3 @@
|
||||
version: 2
|
||||
percy:
|
||||
defer-uploads: true
|
||||
5
e2e/playwright/.prettierignore
Обычный файл
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
playwright-report
|
||||
storage_state
|
||||
6
e2e/playwright/.prettierrc.json
Обычный файл
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"bracketSpacing": false,
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
||||
61
e2e/playwright/README.md
Обычный файл
@@ -0,0 +1,61 @@
|
||||
## Local development
|
||||
|
||||
#### 1. Start local server in a separate terminal.
|
||||
|
||||
#### 2. Install dependencies and run the test.
|
||||
|
||||
```
|
||||
# Install npm packages
|
||||
npm i
|
||||
|
||||
# Install browser binaries as prompted if Playwright is just installed or updated
|
||||
# See https://playwright.dev/docs/browsers
|
||||
npx playwright install
|
||||
|
||||
# Run specific test of all projects -- chrome, firefox, iphone and ipad.
|
||||
# See https://playwright.dev/docs/test-cli.
|
||||
npm run test -- login
|
||||
|
||||
# Run specific test of a project
|
||||
npm run test -- login --project=chrome
|
||||
|
||||
# Or run all tests
|
||||
npm run test
|
||||
```
|
||||
|
||||
#### 3. Inspect test results at `/test-results` folder when something failed unexpectedly.
|
||||
|
||||
## Updating screenshots is strictly via Playwright's docker container for consistency
|
||||
|
||||
#### 1. Run docker container using latest focal version
|
||||
|
||||
Change to root directory, run docker container
|
||||
|
||||
```
|
||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.30.0-focal /bin/bash
|
||||
```
|
||||
|
||||
#### 2. Inside the docker container
|
||||
|
||||
```
|
||||
export PW_BASE_URL=http://host.docker.internal:8065
|
||||
cd mattermost/e2e/playwright
|
||||
|
||||
# Install npm packages. Use "npm ci" to match the automated environment
|
||||
npm ci
|
||||
|
||||
# Run specific test. See https://playwright.dev/docs/test-cli.
|
||||
npm run test -- login --project=chrome
|
||||
|
||||
# Or run all tests
|
||||
npm run test
|
||||
|
||||
# Update snapshots
|
||||
npm run test -- login --update-snapshots
|
||||
```
|
||||
|
||||
## Page/Component Object Model
|
||||
|
||||
See https://playwright.dev/docs/test-pom.
|
||||
|
||||
Page and component abstractions are located at `./support/ui`. It should be first class before writing a spec file so that any future change in DOM structure will be done in one place only. No static UI text and fixed locator should be written in the spec file.
|
||||
226
e2e/playwright/global_setup.ts
Обычный файл
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {AdminConfig} from '@mattermost/types/config';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
import {PluginsResponse} from '@mattermost/types/plugins';
|
||||
|
||||
import {
|
||||
Client,
|
||||
createRandomTeam,
|
||||
getAdminClient,
|
||||
getDefaultAdminUser,
|
||||
getOnPremServerConfig,
|
||||
makeClient,
|
||||
} from './support/server';
|
||||
import {defaultTeam} from './support/util';
|
||||
import testConfig from './test.config';
|
||||
|
||||
async function globalSetup() {
|
||||
let adminClient: Client;
|
||||
let adminUser: UserProfile | null;
|
||||
({adminClient, adminUser} = await getAdminClient());
|
||||
|
||||
if (!adminUser) {
|
||||
const {client: firstClient} = await makeClient();
|
||||
const defaultAdmin = getDefaultAdminUser();
|
||||
await firstClient.createUser(defaultAdmin, '', '');
|
||||
|
||||
({client: adminClient, user: adminUser} = await makeClient(defaultAdmin));
|
||||
}
|
||||
|
||||
await sysadminSetup(adminClient, adminUser);
|
||||
|
||||
return function () {
|
||||
// placeholder for teardown setup
|
||||
};
|
||||
}
|
||||
|
||||
async function sysadminSetup(client: Client, user: UserProfile | null) {
|
||||
// Ensure admin's email is verified.
|
||||
if (!user) {
|
||||
await client.verifyUserEmail(client.token);
|
||||
}
|
||||
|
||||
// Update default server config
|
||||
const adminConfig = await client.updateConfig(getOnPremServerConfig());
|
||||
|
||||
// Log license and config info
|
||||
await printLicenseInfo(client);
|
||||
await printClientInfo(client);
|
||||
|
||||
// Create default team if not present.
|
||||
// Otherwise, create other teams and channels other than the default team cna channels (town-square and off-topic).
|
||||
const myTeams = await client.getMyTeams();
|
||||
const myDefaultTeam = myTeams && myTeams.length > 0 && myTeams.find((team) => team.name === defaultTeam.name);
|
||||
if (!myDefaultTeam) {
|
||||
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))
|
||||
);
|
||||
|
||||
const myChannels = await client.getMyChannels(myDefaultTeam.id);
|
||||
await Promise.all(
|
||||
myChannels
|
||||
.filter((channel) => {
|
||||
return (
|
||||
channel.team_id === myDefaultTeam.id &&
|
||||
channel.name !== 'town-square' &&
|
||||
channel.name !== 'off-topic'
|
||||
);
|
||||
})
|
||||
.map((channel) => client.deleteChannel(channel.id))
|
||||
);
|
||||
}
|
||||
|
||||
// Log boards product status
|
||||
printBoardsProductStatus(adminConfig);
|
||||
|
||||
// Ensure all products as plugin are installed and active.
|
||||
await ensurePluginsLoaded(client);
|
||||
|
||||
// Log plugin details
|
||||
await printPluginDetails(client);
|
||||
|
||||
// Ensure server deployment type is as expected
|
||||
await ensureServerDeployment(client);
|
||||
}
|
||||
|
||||
async function printLicenseInfo(client: Client) {
|
||||
const license = await client.getClientLicenseOld();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Server License:
|
||||
- IsLicensed = ${license.IsLicensed}
|
||||
- IsTrial = ${license.IsTrial}
|
||||
- SkuName = ${license.SkuName}
|
||||
- SkuShortName = ${license.SkuShortName}
|
||||
- Cloud = ${license.Cloud}
|
||||
- Users = ${license.Users}`);
|
||||
}
|
||||
|
||||
async function printClientInfo(client: Client) {
|
||||
const config = await client.getClientConfigOld();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Build Info:
|
||||
- BuildNumber = ${config.BuildNumber}
|
||||
- BuildDate = ${config.BuildDate}
|
||||
- Version = ${config.Version}
|
||||
- BuildHash = ${config.BuildHash}
|
||||
- BuildHashEnterprise = ${config.BuildHashEnterprise}
|
||||
- BuildEnterpriseReady = ${config.BuildEnterpriseReady}
|
||||
- BuildHashBoards = ${config.BuildHashBoards}
|
||||
- BuildBoards = ${config.BuildBoards}
|
||||
- BuildHashPlaybooks = ${config.BuildHashPlaybooks}
|
||||
- FeatureFlagAppsEnabled = ${config.FeatureFlagAppsEnabled}
|
||||
- FeatureFlagBoardsProduct = ${config.FeatureFlagBoardsProduct}
|
||||
- FeatureFlagCallsEnabled = ${config.FeatureFlagCallsEnabled}
|
||||
- TelemetryId = ${config.TelemetryId}`);
|
||||
}
|
||||
|
||||
function getProductsAsPlugin() {
|
||||
const productsAsPlugin = ['com.mattermost.calls', 'playbooks'];
|
||||
|
||||
if (!testConfig.boardsProductEnabled) {
|
||||
productsAsPlugin.push('focalboard');
|
||||
}
|
||||
|
||||
return productsAsPlugin;
|
||||
}
|
||||
|
||||
async function ensurePluginsLoaded(client: Client) {
|
||||
const pluginStatus = await client.getPluginStatuses();
|
||||
const plugins = (await client.getPlugins()) as PluginsResponse;
|
||||
|
||||
getProductsAsPlugin().forEach(async (pluginId) => {
|
||||
const isInstalled = pluginStatus.some((plugin) => plugin.plugin_id === pluginId);
|
||||
if (!isInstalled) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${pluginId} is not installed. Related visual test will fail.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const isActive = plugins.active.some((plugin) => plugin.id === pluginId);
|
||||
if (!isActive) {
|
||||
await client.enablePlugin(pluginId);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${pluginId} is installed and has been activated.`);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${pluginId} is installed and active.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printBoardsProductStatus(config: AdminConfig) {
|
||||
// Ensure boards as product is enabled
|
||||
if (!config.FeatureFlags.BoardsProduct) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FeatureFlags.BoardsProduct is disabled. Related visual test will fail.');
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FeatureFlags.BoardsProduct is enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
async function printPluginDetails(client: Client) {
|
||||
const plugins = (await client.getPlugins()) as PluginsResponse;
|
||||
|
||||
if (plugins.active.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Active plugins:');
|
||||
}
|
||||
|
||||
plugins.active.forEach((plugin) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
||||
});
|
||||
|
||||
if (plugins.inactive.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Inactive plugins:');
|
||||
}
|
||||
|
||||
plugins.inactive.forEach((plugin) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async function ensureServerDeployment(client: Client) {
|
||||
if (testConfig.haClusterEnabled) {
|
||||
const {haClusterNodeCount, haClusterName} = testConfig;
|
||||
|
||||
const {Enable, ClusterName} = (await client.getConfig()).ClusterSettings;
|
||||
expect(Enable, Enable ? '' : 'Should have cluster enabled').toBe(true);
|
||||
|
||||
const sameClusterName = ClusterName === haClusterName;
|
||||
expect(
|
||||
sameClusterName,
|
||||
sameClusterName
|
||||
? ''
|
||||
: `Should have cluster name set and as expected. Got "${ClusterName}" but expected "${haClusterName}"`
|
||||
).toBe(true);
|
||||
|
||||
const clusterInfo = await client.getClusterStatus();
|
||||
const sameCount = clusterInfo?.length === haClusterNodeCount;
|
||||
expect(
|
||||
sameCount,
|
||||
sameCount
|
||||
? ''
|
||||
: `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}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
4007
e2e/playwright/package-lock.json
сгенерированный
Обычный файл
32
e2e/playwright/package.json
Обычный файл
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"scripts": {
|
||||
"test": "PW_SNAPSHOT_ENABLE=true playwright test",
|
||||
"percy": "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 .",
|
||||
"check": "npm run tsc && npm run lint && npm run prettier",
|
||||
"codegen": "playwright codegen $PW_BASE_URL",
|
||||
"test-slomo": "PW_SNAPSHOT_ENABLE=true PW_HEADLESS=false PW_SLOWMO=1000 playwright test",
|
||||
"show-report": "npx playwright show-report"
|
||||
},
|
||||
"dependencies": {
|
||||
"@percy/cli": "1.18.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.30.0",
|
||||
"chalk": "4.1.2",
|
||||
"deepmerge": "4.3.0",
|
||||
"dotenv": "16.0.3",
|
||||
"form-data": "4.0.0",
|
||||
"isomorphic-unfetch": "4.0.2",
|
||||
"uuid": "9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.51.0",
|
||||
"eslint": "8.34.0",
|
||||
"prettier": "2.8.4",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
||||
85
e2e/playwright/playwright.config.ts
Обычный файл
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineConfig, devices} from '@playwright/test';
|
||||
|
||||
import {duration} from '@e2e-support/util';
|
||||
import testConfig from '@e2e-test.config';
|
||||
|
||||
const defaultOutputFolder = 'playwright-report';
|
||||
|
||||
export default defineConfig({
|
||||
globalSetup: require.resolve('./global_setup'),
|
||||
forbidOnly: testConfig.isCI,
|
||||
outputDir: './test-results',
|
||||
testDir: 'tests',
|
||||
timeout: duration.one_min,
|
||||
workers: testConfig.workers,
|
||||
expect: {
|
||||
timeout: duration.ten_sec,
|
||||
toMatchSnapshot: {
|
||||
threshold: 0.4,
|
||||
maxDiffPixelRatio: 0.0001,
|
||||
},
|
||||
},
|
||||
use: {
|
||||
baseURL: testConfig.baseURL,
|
||||
headless: testConfig.headless,
|
||||
locale: 'en-US',
|
||||
launchOptions: {
|
||||
slowMo: testConfig.slowMo,
|
||||
},
|
||||
screenshot: 'only-on-failure',
|
||||
timezoneId: 'America/Los_Angeles',
|
||||
trace: 'off',
|
||||
video: 'on-first-retry',
|
||||
actionTimeout: duration.half_min,
|
||||
storageState: {
|
||||
cookies: [],
|
||||
origins: [
|
||||
{
|
||||
origin: testConfig.baseURL,
|
||||
localStorage: [{name: '__landingPageSeen__', value: 'true'}],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'iphone',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
...devices['iPhone 13 Pro'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
...devices['iPad Pro 11'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'chrome',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
permissions: ['notifications'],
|
||||
viewport: {width: 1280, height: 1024},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
browserName: 'firefox',
|
||||
permissions: ['notifications'],
|
||||
viewport: {width: 1280, height: 1024},
|
||||
},
|
||||
},
|
||||
],
|
||||
reporter: [
|
||||
['html', {open: 'never', outputFolder: defaultOutputFolder}],
|
||||
['json', {outputFile: `${defaultOutputFolder}/results.json`}],
|
||||
['junit', {outputFile: `${defaultOutputFolder}/results.xml`}],
|
||||
['list'],
|
||||
],
|
||||
});
|
||||
Двоичные данные
e2e/playwright/support/asset/mattermost-icon_128x128.png
Обычный файл
|
После Ширина: | Высота: | Размер: 13 KiB |
65
e2e/playwright/support/browser_context.ts
Обычный файл
@@ -0,0 +1,65 @@
|
||||
// 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) {
|
||||
// Log in via API request and save user storage
|
||||
const storagePath = await loginByAPI(user.username, user.password);
|
||||
|
||||
// Sign in a user in new browser context
|
||||
const context = await this.browser.newContext({storageState: storagePath});
|
||||
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;
|
||||
}
|
||||
41
e2e/playwright/support/flag.ts
Обычный файл
@@ -0,0 +1,41 @@
|
||||
// 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 {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['focalboard'].Enable;
|
||||
|
||||
const matched = boardsEnabled === enabled;
|
||||
expect(matched, matched ? '' : `Boards expect "${enabled}" but actual "${boardsEnabled}"`).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/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/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};
|
||||
713
e2e/playwright/support/server/default_config.ts
Обычный файл
@@ -0,0 +1,713 @@
|
||||
// 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 mattermost-server via "make config-reset"
|
||||
// Based on v7.9 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,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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: false,
|
||||
},
|
||||
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: true,
|
||||
SendWelcomePost: true,
|
||||
WorkTemplate: false,
|
||||
PostPriority: true,
|
||||
WysiwygEditor: false,
|
||||
PeopleProduct: false,
|
||||
AnnualSubscription: false,
|
||||
ReduceOnBoardingTaskList: false,
|
||||
OnboardingAutoShowLinkedBoard: true,
|
||||
ThreadsEverywhere: false,
|
||||
GlobalDrafts: true,
|
||||
OnboardingTourTips: true,
|
||||
},
|
||||
ImportSettings: {
|
||||
Directory: './import',
|
||||
RetentionDays: 30,
|
||||
},
|
||||
ExportSettings: {
|
||||
Directory: './export',
|
||||
RetentionDays: 30,
|
||||
},
|
||||
};
|
||||
9
e2e/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/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/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/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;
|
||||
}
|
||||
19
e2e/playwright/support/test_action.ts
Обычный файл
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, Page} from '@playwright/test';
|
||||
|
||||
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))
|
||||
);
|
||||
}
|
||||
76
e2e/playwright/support/test_fixture.ts
Обычный файл
@@ -0,0 +1,76 @@
|
||||
import {test as base, Browser} from '@playwright/test';
|
||||
|
||||
import {TestBrowser} from './browser_context';
|
||||
import {shouldHaveBoardsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
|
||||
import {initSetup, getAdminClient} from './server';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd} 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: typeof shouldHaveBoardsEnabled;
|
||||
readonly shouldHaveFeatureFlag: typeof shouldHaveFeatureFlag;
|
||||
readonly shouldSkipInSmallScreen: typeof shouldSkipInSmallScreen;
|
||||
readonly shouldRunInLinux: typeof shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
readonly getAdminClient: typeof getAdminClient;
|
||||
readonly initSetup: typeof initSetup;
|
||||
|
||||
// ./test_action
|
||||
readonly hideDynamicChannelsContent: typeof hideDynamicChannelsContent;
|
||||
readonly waitForAnimationEnd: typeof waitForAnimationEnd;
|
||||
|
||||
// ./ui/pages
|
||||
readonly pages: typeof pages;
|
||||
|
||||
// ./visual
|
||||
readonly matchSnapshot: typeof matchSnapshot;
|
||||
|
||||
constructor(browser: Browser) {
|
||||
// ./browser_context
|
||||
this.testBrowser = new TestBrowser(browser);
|
||||
|
||||
// ./flag
|
||||
this.shouldHaveBoardsEnabled = shouldHaveBoardsEnabled;
|
||||
this.shouldHaveFeatureFlag = shouldHaveFeatureFlag;
|
||||
this.shouldSkipInSmallScreen = shouldSkipInSmallScreen;
|
||||
this.shouldRunInLinux = shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
this.initSetup = initSetup;
|
||||
this.getAdminClient = getAdminClient;
|
||||
|
||||
// ./test_action
|
||||
this.hideDynamicChannelsContent = hideDynamicChannelsContent;
|
||||
this.waitForAnimationEnd = waitForAnimationEnd;
|
||||
|
||||
// ./ui/pages
|
||||
this.pages = pages;
|
||||
|
||||
// ./visual
|
||||
this.matchSnapshot = matchSnapshot;
|
||||
}
|
||||
}
|
||||
26
e2e/playwright/support/ui/components/boards/create_modal.ts
Обычный файл
@@ -0,0 +1,26 @@
|
||||
// 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 locator: Locator;
|
||||
readonly productSwitchMenu: Locator;
|
||||
|
||||
constructor(locator: Locator) {
|
||||
this.locator = locator;
|
||||
|
||||
this.productSwitchMenu = locator.getByRole('button', {name: 'Product switch menu'});
|
||||
}
|
||||
|
||||
async switchProduct(name: string) {
|
||||
await this.productSwitchMenu.click();
|
||||
await this.locator.getByRole('link', {name: ` ${name}`}).click();
|
||||
}
|
||||
|
||||
async toBeVisible(name: string) {
|
||||
await expect(this.locator.getByRole('heading', {name})).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {BoardsCreateModal};
|
||||
27
e2e/playwright/support/ui/components/boards/sidebar.ts
Обычный файл
@@ -0,0 +1,27 @@
|
||||
// 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: Locator;
|
||||
readonly createNewBoardMenuItem: Locator;
|
||||
readonly createNewCategoryMenuItem: Locator;
|
||||
readonly titles: Locator;
|
||||
|
||||
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};
|
||||
21
e2e/playwright/support/ui/components/channels/app_bar.ts
Обычный файл
@@ -0,0 +1,21 @@
|
||||
// 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: Locator;
|
||||
|
||||
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};
|
||||
31
e2e/playwright/support/ui/components/channels/post.ts
Обычный файл
@@ -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 ChannelsPost {
|
||||
readonly container: Locator;
|
||||
readonly profileIcon: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.profileIcon = container.locator('.profile-icon');
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPost};
|
||||
30
e2e/playwright/support/ui/components/channels/post_create.ts
Обычный файл
@@ -0,0 +1,30 @@
|
||||
// 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: Locator;
|
||||
readonly attachmentButton: Locator;
|
||||
readonly emojiButton: Locator;
|
||||
|
||||
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};
|
||||
26
e2e/playwright/support/ui/components/global_header.ts
Обычный файл
@@ -0,0 +1,26 @@
|
||||
// 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: Locator;
|
||||
|
||||
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};
|
||||
18
e2e/playwright/support/ui/components/index.ts
Обычный файл
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BoardsSidebar} from './boards/sidebar';
|
||||
import {ChannelsAppBar} from './channels/app_bar';
|
||||
import {ChannelsPostCreate} from './channels/post_create';
|
||||
import {ChannelsPost} from './channels/post';
|
||||
import {GlobalHeader} from './global_header';
|
||||
|
||||
const components = {
|
||||
BoardsSidebar,
|
||||
ChannelsAppBar,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
GlobalHeader,
|
||||
};
|
||||
|
||||
export {components, BoardsSidebar, ChannelsAppBar, ChannelsPostCreate, ChannelsPost, GlobalHeader};
|
||||
45
e2e/playwright/support/ui/pages/boards_create.ts
Обычный файл
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
|
||||
import {GlobalHeader} from '@e2e-support/ui/components';
|
||||
|
||||
export default class BoardsCreatePage {
|
||||
readonly boards = 'Boards';
|
||||
readonly page: Page;
|
||||
readonly globalHeader: GlobalHeader;
|
||||
readonly createBoardHeading: Locator;
|
||||
readonly createEmptyBoardButton: Locator;
|
||||
readonly useTemplateButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.globalHeader = new 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};
|
||||
58
e2e/playwright/support/ui/pages/boards_view.ts
Обычный файл
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
|
||||
import {BoardsSidebar, GlobalHeader} from '@e2e-support/ui/components';
|
||||
|
||||
export default class BoardsViewPage {
|
||||
readonly boards = 'Boards';
|
||||
readonly page: Page;
|
||||
readonly sidebar: BoardsSidebar;
|
||||
readonly globalHeader: GlobalHeader;
|
||||
readonly topHead: Locator;
|
||||
readonly editableTitle: Locator;
|
||||
readonly shareButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.sidebar = new BoardsSidebar(page.locator('.octo-sidebar'));
|
||||
this.globalHeader = new 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};
|
||||
72
e2e/playwright/support/ui/pages/channels.ts
Обычный файл
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
import {ChannelsAppBar, ChannelsPost, ChannelsPostCreate, GlobalHeader} from '@e2e-support/ui/components';
|
||||
import {isSmallScreen} from '@e2e-support/util';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
readonly page: Page;
|
||||
readonly postCreate: ChannelsPostCreate;
|
||||
readonly globalHeader: GlobalHeader;
|
||||
readonly appBar: ChannelsAppBar;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.postCreate = new ChannelsPostCreate(page.locator('#post-create'));
|
||||
this.globalHeader = new GlobalHeader(this.page.locator('#global-header'));
|
||||
this.appBar = new ChannelsAppBar(page.locator('.app-bar'));
|
||||
}
|
||||
|
||||
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 ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getLastPost() {
|
||||
await this.page.getByTestId('postView').last().waitFor();
|
||||
const post = await this.page.getByTestId('postView').last();
|
||||
return new 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 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 ChannelsPost(post);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPage};
|
||||
20
e2e/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};
|
||||
39
e2e/playwright/support/ui/pages/landing_login.ts
Обычный файл
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
|
||||
export default class LandingLoginPage {
|
||||
readonly page: Page;
|
||||
readonly isMobile?: boolean;
|
||||
readonly viewInAppButton: Locator;
|
||||
readonly viewInDesktopAppButton: Locator;
|
||||
readonly viewInBrowserButton: Locator;
|
||||
|
||||
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};
|
||||
66
e2e/playwright/support/ui/pages/login.ts
Обычный файл
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, 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: Locator;
|
||||
readonly subtitle: Locator;
|
||||
readonly bodyCard: Locator;
|
||||
readonly loginInput: Locator;
|
||||
readonly loginPlaceholder: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly signInButton: Locator;
|
||||
readonly createAccountLink: Locator;
|
||||
readonly forgotPasswordLink: Locator;
|
||||
readonly userErrorLabel: Locator;
|
||||
readonly fieldWithError: Locator;
|
||||
readonly formContainer: Locator;
|
||||
|
||||
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};
|
||||
66
e2e/playwright/support/ui/pages/signup.ts
Обычный файл
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
|
||||
import {duration, wait} from '@e2e-support/util';
|
||||
|
||||
export default class SignupPage {
|
||||
readonly page: Page;
|
||||
readonly title: Locator;
|
||||
readonly subtitle: Locator;
|
||||
readonly bodyCard: Locator;
|
||||
readonly emailInput: Locator;
|
||||
readonly usernameInput: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly createAccountButton: Locator;
|
||||
readonly loginLink: Locator;
|
||||
readonly emailError: Locator;
|
||||
readonly usernameError: Locator;
|
||||
readonly passwordError: Locator;
|
||||
|
||||
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/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/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/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});
|
||||
}
|
||||
}
|
||||
69
e2e/playwright/test.config.ts
Обычный файл
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page, ViewportSize} from '@playwright/test';
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
export type TestArgs = {
|
||||
page: Page;
|
||||
browserName: string;
|
||||
viewport?: ViewportSize | null;
|
||||
};
|
||||
|
||||
export type TestConfig = {
|
||||
// Server
|
||||
baseURL: string;
|
||||
adminUsername: string;
|
||||
adminPassword: string;
|
||||
adminEmail: string;
|
||||
boardsProductEnabled: boolean;
|
||||
resetBeforeTest: boolean;
|
||||
haClusterEnabled: boolean;
|
||||
haClusterNodeCount: number;
|
||||
haClusterName: string;
|
||||
// CI
|
||||
isCI: boolean;
|
||||
// Playwright
|
||||
headless: boolean;
|
||||
slowMo: number;
|
||||
workers: number;
|
||||
// Visual tests
|
||||
snapshotEnabled: boolean;
|
||||
percyEnabled: boolean;
|
||||
percyToken?: string;
|
||||
};
|
||||
|
||||
// All process.env should be defined here
|
||||
const config: TestConfig = {
|
||||
// Server
|
||||
baseURL: process.env.PW_BASE_URL || 'http://localhost:8065',
|
||||
adminUsername: process.env.PW_ADMIN_USERNAME || 'sysadmin',
|
||||
adminPassword: process.env.PW_ADMIN_PASSWORD || 'Sys@dmin-sample1',
|
||||
adminEmail: process.env.PW_ADMIN_EMAIL || 'sysadmin@sample.mattermost.com',
|
||||
boardsProductEnabled: parseBool(process.env.PW_BOARDS_PRODUCT_ENABLED, true),
|
||||
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',
|
||||
resetBeforeTest: parseBool(process.env.PW_RESET_BEFORE_TEST, false),
|
||||
// CI
|
||||
isCI: !!process.env.CI,
|
||||
// Playwright
|
||||
headless: parseBool(process.env.PW_HEADLESS, false),
|
||||
slowMo: parseNumber(process.env.PW_SLOWMO, 0),
|
||||
workers: parseNumber(process.env.PW_WORKERS, 1),
|
||||
// Visual tests
|
||||
snapshotEnabled: parseBool(process.env.PW_SNAPSHOT_ENABLE, false),
|
||||
percyEnabled: parseBool(process.env.PW_PERCY_ENABLE, false),
|
||||
percyToken: process.env.PERCY_TOKEN,
|
||||
};
|
||||
|
||||
function parseBool(actualValue: string | undefined, defaultValue: boolean) {
|
||||
return actualValue ? actualValue === 'true' : defaultValue;
|
||||
}
|
||||
|
||||
function parseNumber(actualValue: string | undefined, defaultValue: number) {
|
||||
return actualValue ? parseInt(actualValue, 10) : defaultValue;
|
||||
}
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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}) => {
|
||||
await pw.shouldHaveBoardsEnabled();
|
||||
|
||||
// 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);
|
||||
});
|
||||
26
e2e/playwright/tests/visual/boards/board_template.spec.ts
Обычный файл
@@ -0,0 +1,26 @@
|
||||
// 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) => {
|
||||
await pw.shouldHaveBoardsEnabled();
|
||||
|
||||
// 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);
|
||||
});
|
||||
Двоичные данные
e2e/playwright/tests/visual/boards/board_template.spec.ts-snapshots/board-template-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 129 KiB |
Двоичные данные
e2e/playwright/tests/visual/boards/board_template.spec.ts-snapshots/board-template-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 184 KiB |
Двоичные данные
e2e/playwright/tests/visual/boards/board_template.spec.ts-snapshots/board-template-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 246 KiB |
34
e2e/playwright/tests/visual/boards/view_untitled_board.spec.ts
Обычный файл
@@ -0,0 +1,34 @@
|
||||
// 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) => {
|
||||
await pw.shouldHaveBoardsEnabled();
|
||||
|
||||
// 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);
|
||||
});
|
||||
Двоичные данные
e2e/playwright/tests/visual/boards/view_untitled_board.spec.ts-snapshots/view-untitled-board-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 38 KiB |
Двоичные данные
e2e/playwright/tests/visual/boards/view_untitled_board.spec.ts-snapshots/view-untitled-board-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 65 KiB |
Двоичные данные
e2e/playwright/tests/visual/boards/view_untitled_board.spec.ts-snapshots/view-untitled-board-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 85 KiB |
36
e2e/playwright/tests/visual/channels/intro_channel.spec.ts
Обычный файл
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@e2e-support/test_fixture';
|
||||
import {duration, isSmallScreen, wait} from '@e2e-support/util';
|
||||
|
||||
test('Intro to channel as regular user', 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);
|
||||
|
||||
// Visit a default channel page
|
||||
const channelsPage = new pages.ChannelsPage(page);
|
||||
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 (!isSmallScreen(viewport)) {
|
||||
await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
|
||||
}
|
||||
|
||||
// Hide dynamic elements of Channels page
|
||||
await pw.hideDynamicChannelsContent(page);
|
||||
|
||||
// Match snapshot of channel intro page
|
||||
const testArgs = {page, browserName, viewport};
|
||||
await pw.matchSnapshot(testInfo, testArgs);
|
||||
});
|
||||
|
После Ширина: | Высота: | Размер: 77 KiB |
|
После Ширина: | Высота: | Размер: 113 KiB |
Двоичные данные
e2e/playwright/tests/visual/channels/intro_channel.spec.ts-snapshots/intro-to-channel-as-regular-user-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 183 KiB |
|
После Ширина: | Высота: | Размер: 178 KiB |
14
e2e/playwright/tests/visual/common/landing_page.spec.ts
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {test} from '@e2e-support/test_fixture';
|
||||
|
||||
test('/landing#/login', async ({pw, pages, page, isMobile, browserName, viewport}, testInfo) => {
|
||||
// Go to landing login page
|
||||
const landingLoginPage = new pages.LandingLoginPage(page, isMobile);
|
||||
await landingLoginPage.goto();
|
||||
await landingLoginPage.toBeVisible();
|
||||
|
||||
// Match snapshot of landing page
|
||||
await pw.matchSnapshot(testInfo, {page, browserName, viewport});
|
||||
});
|
||||
Двоичные данные
e2e/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 105 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 169 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 212 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-iphone-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 157 KiB |
28
e2e/playwright/tests/visual/common/login.spec.ts
Обычный файл
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {test} from '@e2e-support/test_fixture';
|
||||
|
||||
test('/login', async ({pw, pages, page, browserName, viewport}, testInfo) => {
|
||||
// Go to login page
|
||||
const {adminClient} = await pw.getAdminClient();
|
||||
const adminConfig = await adminClient.getConfig();
|
||||
const loginPage = new pages.LoginPage(page, adminConfig);
|
||||
await loginPage.goto();
|
||||
await loginPage.toBeVisible();
|
||||
|
||||
// Click to other element to remove focus from email input
|
||||
await loginPage.title.click();
|
||||
|
||||
// Match snapshot of login page
|
||||
const testArgs = {page, browserName, viewport};
|
||||
await pw.matchSnapshot(testInfo, testArgs);
|
||||
|
||||
// Click sign in button without entering user credential
|
||||
await loginPage.signInButton.click();
|
||||
await loginPage.userErrorLabel.waitFor();
|
||||
await pw.waitForAnimationEnd(loginPage.bodyCard);
|
||||
|
||||
// Match snapshot of login page with error
|
||||
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error`}, testArgs);
|
||||
});
|
||||
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 150 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 147 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 270 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 312 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-iphone-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 241 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 276 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 297 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/login.spec.ts-snapshots/login-iphone-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 230 KiB |
38
e2e/playwright/tests/visual/common/signup_email.spec.ts
Обычный файл
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {test} from '@e2e-support/test_fixture';
|
||||
|
||||
test('/signup_email', async ({pw, pages, page, browserName, viewport}, testInfo) => {
|
||||
// Go to login page
|
||||
const {adminClient} = await pw.getAdminClient();
|
||||
const adminConfig = await adminClient.getConfig();
|
||||
const loginPage = new pages.LoginPage(page, adminConfig);
|
||||
await loginPage.goto();
|
||||
await loginPage.toBeVisible();
|
||||
|
||||
// Create an account
|
||||
await loginPage.createAccountLink.click();
|
||||
|
||||
// Should have redirected to signup page
|
||||
const signupPage = new pw.pages.SignupPage(page);
|
||||
await signupPage.toBeVisible();
|
||||
|
||||
// Click to other element to remove focus from email input
|
||||
await signupPage.title.click();
|
||||
|
||||
// Match snapshot of signup_email page
|
||||
const testArgs = {page, browserName, viewport};
|
||||
await pw.matchSnapshot(testInfo, testArgs);
|
||||
|
||||
// Click sign in button without entering user credential
|
||||
const invalidUser = {email: 'invalid', username: 'a', password: 'b'};
|
||||
await signupPage.create(invalidUser, false);
|
||||
await signupPage.emailError.waitFor();
|
||||
await signupPage.usernameError.waitFor();
|
||||
await signupPage.passwordError.waitFor();
|
||||
await pw.waitForAnimationEnd(signupPage.bodyCard);
|
||||
|
||||
// Match snapshot of signup_email page
|
||||
await pw.matchSnapshot({...testInfo, title: `${testInfo.title} error`}, testArgs);
|
||||
});
|
||||
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 156 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 160 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 285 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 368 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-iphone-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 276 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-firefox-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 280 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 348 KiB |
Двоичные данные
e2e/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-iphone-linux.png
Обычный файл
|
После Ширина: | Высота: | Размер: 256 KiB |
17
e2e/playwright/tsconfig.json
Обычный файл
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@mattermost/client/*": ["../../webapp/platform/client/lib/*"],
|
||||
"@mattermost/types/*": ["../../webapp/platform/types/lib/*"],
|
||||
"@e2e-support/*": ["support/*"],
|
||||
"@e2e-test.config": ["test.config.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
}
|
||||