playwright: restructure page and components model (#22602)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
19c7c6fa7a
Коммит
82c9d647b8
@@ -1,2 +1,3 @@
|
||||
results
|
||||
node_modules
|
||||
playwright-report
|
||||
test-results
|
||||
|
||||
@@ -2,18 +2,10 @@
|
||||
// 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 {Client, createRandomTeam, getAdminClient, getDefaultAdminUser, makeClient} from './support/server';
|
||||
import {boardsPluginId, callsPluginId} from './support/constant';
|
||||
import {defaultTeam} from './support/util';
|
||||
import testConfig from './test.config';
|
||||
|
||||
@@ -43,9 +35,6 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
|
||||
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);
|
||||
@@ -75,9 +64,6 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
|
||||
);
|
||||
}
|
||||
|
||||
// Log boards product status
|
||||
printBoardsProductStatus(adminConfig);
|
||||
|
||||
// Ensure all products as plugin are installed and active.
|
||||
await ensurePluginsLoaded(client);
|
||||
|
||||
@@ -110,9 +96,6 @@ async function printClientInfo(client: Client) {
|
||||
- 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}
|
||||
@@ -120,10 +103,10 @@ async function printClientInfo(client: Client) {
|
||||
}
|
||||
|
||||
function getProductsAsPlugin() {
|
||||
const productsAsPlugin = ['com.mattermost.calls', 'playbooks'];
|
||||
const productsAsPlugin = [callsPluginId];
|
||||
|
||||
if (!testConfig.boardsProductEnabled) {
|
||||
productsAsPlugin.push('focalboard');
|
||||
productsAsPlugin.push(boardsPluginId);
|
||||
}
|
||||
|
||||
return productsAsPlugin;
|
||||
@@ -131,7 +114,7 @@ function getProductsAsPlugin() {
|
||||
|
||||
async function ensurePluginsLoaded(client: Client) {
|
||||
const pluginStatus = await client.getPluginStatuses();
|
||||
const plugins = (await client.getPlugins()) as PluginsResponse;
|
||||
const plugins = await client.getPlugins();
|
||||
|
||||
getProductsAsPlugin().forEach(async (pluginId) => {
|
||||
const isInstalled = pluginStatus.some((plugin) => plugin.plugin_id === pluginId);
|
||||
@@ -154,19 +137,8 @@ async function ensurePluginsLoaded(client: Client) {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
const plugins = await client.getPlugins();
|
||||
|
||||
if (plugins.active.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
15
e2e/playwright/package-lock.json
сгенерированный
15
e2e/playwright/package-lock.json
сгенерированный
@@ -9,6 +9,7 @@
|
||||
"@percy/cli": "1.18.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.30.0",
|
||||
"async-wait-until": "2.0.12",
|
||||
"chalk": "4.1.2",
|
||||
"deepmerge": "4.3.0",
|
||||
"dotenv": "16.0.3",
|
||||
@@ -740,6 +741,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/async-wait-until": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/async-wait-until/-/async-wait-until-2.0.12.tgz",
|
||||
"integrity": "sha512-SXy/vDs6UPJMG6YeEYOQ4ilA/JnGxk187KPGqFx9O+qVxsjkSl+jH+3P50qSNyMpEmDgr8qOFGOKCJckWb1i7A==",
|
||||
"engines": {
|
||||
"node": ">= 0.12.0",
|
||||
"npm": ">= 1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -2861,6 +2871,11 @@
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"async-wait-until": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/async-wait-until/-/async-wait-until-2.0.12.tgz",
|
||||
"integrity": "sha512-SXy/vDs6UPJMG6YeEYOQ4ilA/JnGxk187KPGqFx9O+qVxsjkSl+jH+3P50qSNyMpEmDgr8qOFGOKCJckWb1i7A=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@percy/cli": "1.18.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.30.0",
|
||||
"async-wait-until": "2.0.12",
|
||||
"chalk": "4.1.2",
|
||||
"deepmerge": "4.3.0",
|
||||
"dotenv": "16.0.3",
|
||||
|
||||
@@ -24,9 +24,16 @@ export default defineConfig({
|
||||
},
|
||||
use: {
|
||||
baseURL: testConfig.baseURL,
|
||||
ignoreHTTPSErrors: true,
|
||||
headless: testConfig.headless,
|
||||
locale: 'en-US',
|
||||
launchOptions: {
|
||||
args: ['--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream'],
|
||||
firefoxUserPrefs: {
|
||||
'media.navigator.streams.fake': true,
|
||||
'permissions.default.microphone': 1,
|
||||
'permissions.default.camera': 1,
|
||||
},
|
||||
slowMo: testConfig.slowMo,
|
||||
},
|
||||
screenshot: 'only-on-failure',
|
||||
|
||||
56
e2e/playwright/sample.env
Обычный файл
56
e2e/playwright/sample.env
Обычный файл
@@ -0,0 +1,56 @@
|
||||
# 1. PW_BASE_URL
|
||||
# - Default to "http://localhost:8065" if not set.
|
||||
# - When running test inside the Playwright docker image
|
||||
# PW_BASE_URL=http://host.docker.internal:8065
|
||||
|
||||
# 2. NODE_TLS_REJECT_UNAUTHORIZED
|
||||
# This node environment variable disables the verification of local certificates. Set for local testing only.
|
||||
# NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
|
||||
# 3. PW_ADMIN_USERNAME
|
||||
# - Default to "sysadmin" if not set.
|
||||
|
||||
# 4. PW_ADMIN_PASSWORD
|
||||
# - Default to "Sys@dmin-sample1" if not set.
|
||||
|
||||
# 5. PW_ADMIN_EMAIL
|
||||
# - Default to "sysadmin@sample.mattermost.com" if not set.
|
||||
|
||||
# 6. PW_BOARDS_PRODUCT_ENABLED
|
||||
# - Default to "true" if not set. Used to correctly set server config.
|
||||
|
||||
# 7. PW_HA_CLUSTER_ENABLED
|
||||
# - Default to "false" if not set. Set to true if the test server is with HA enabled.
|
||||
|
||||
# 8. PW_HA_CLUSTER_NODE_COUNT
|
||||
# - Default to "2" if not set.
|
||||
|
||||
# 9. PW_HA_CLUSTER_NAME
|
||||
# - Default to "mm_dev_cluster" if not set.
|
||||
|
||||
# 10. PW_RESET_BEFORE_TEST
|
||||
# - Default to "false" if not set. If true, the setup deletes all teams and channels other than the default team which is "ad-1".
|
||||
|
||||
# 11. CI
|
||||
# - Default to "false" if not set.
|
||||
|
||||
# 12. PW_HEADLESS
|
||||
# - Default to "true" if not set. Set to false to run test in head mode.
|
||||
|
||||
# 13. PW_SLOWMO
|
||||
# - Default to "0" if not set which means normal test speed run. Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
|
||||
# 14. PW_WORKERS
|
||||
# - Default to "1" if not set. The maximum number of concurrent worker processes to use for parallelizing tests.
|
||||
|
||||
# 15. PW_SNAPSHOT_ENABLE
|
||||
# - Default to "false" if not set. Set to true to enable snapshot testing.
|
||||
# Note that, snapshot testing should be done in Playwright docker image only.
|
||||
# This is to ensure that, there's a common base platform for all contributors
|
||||
# regardless of each local development platform.
|
||||
|
||||
# 16. PW_PERCY_ENABLE
|
||||
# - Default to "false" if not set. Use to save and compare results via https://percy.io/.
|
||||
|
||||
# 17. PERCY_TOKEN
|
||||
# - A token required by https://percy.io/.
|
||||
@@ -15,12 +15,16 @@ export class TestBrowser {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
async login(user: UserProfile) {
|
||||
// Log in via API request and save user storage
|
||||
const storagePath = await loginByAPI(user.username, user.password);
|
||||
async login(user: UserProfile | null) {
|
||||
const options = {storageState: ''};
|
||||
if (user) {
|
||||
// Log in via API request and save user storage
|
||||
const storagePath = await loginByAPI(user.username, user.password);
|
||||
options.storageState = storagePath;
|
||||
}
|
||||
|
||||
// Sign in a user in new browser context
|
||||
const context = await this.browser.newContext({storageState: storagePath});
|
||||
const context = await this.browser.newContext(options);
|
||||
const page = await context.newPage();
|
||||
|
||||
return {context, page};
|
||||
|
||||
7
e2e/playwright/support/constant.ts
Обычный файл
7
e2e/playwright/support/constant.ts
Обычный файл
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const boardsPluginId = 'focalboard';
|
||||
export const boardsProductId = 'boards';
|
||||
export const callsPluginId = 'com.mattermost.calls';
|
||||
export const playbooksPluginId = 'playbooks';
|
||||
@@ -5,6 +5,7 @@ import os from 'node:os';
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
|
||||
import {boardsPluginId, callsPluginId} from './constant';
|
||||
import {getAdminClient} from './server/init';
|
||||
import {isSmallScreen} from './util';
|
||||
|
||||
@@ -14,12 +15,22 @@ export async function shouldHaveBoardsEnabled(enabled = true) {
|
||||
|
||||
const boardsEnabled =
|
||||
(typeof config.FeatureFlags.BoardsProduct === 'boolean' && config.FeatureFlags.BoardsProduct) ||
|
||||
config.PluginSettings.PluginStates['focalboard'].Enable;
|
||||
config.PluginSettings.PluginStates[boardsPluginId].Enable;
|
||||
|
||||
const matched = boardsEnabled === enabled;
|
||||
expect(matched, matched ? '' : `Boards expect "${enabled}" but actual "${boardsEnabled}"`).toBeTruthy();
|
||||
}
|
||||
|
||||
export async function shouldHaveCallsEnabled(enabled = true) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
const config = await adminClient.getConfig();
|
||||
|
||||
const callsEnabled = config.PluginSettings.PluginStates[callsPluginId].Enable;
|
||||
|
||||
const matched = callsEnabled === enabled;
|
||||
expect(matched, matched ? '' : `Calls expect "${enabled}" but actual "${callsEnabled}"`).toBeTruthy();
|
||||
}
|
||||
|
||||
export async function shouldHaveFeatureFlag(name: string, value: string | boolean) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
const config = await adminClient.getConfig();
|
||||
|
||||
@@ -73,8 +73,8 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
||||
};
|
||||
};
|
||||
|
||||
// Should be based only from the generated default config from mattermost-server via "make config-reset"
|
||||
// Based on v7.9 server
|
||||
// Should be based only from the generated default config from ./server via "make config-reset"
|
||||
// Based on v7.10 server
|
||||
const defaultServerConfig: AdminConfig = {
|
||||
ServiceSettings: {
|
||||
SiteURL: '',
|
||||
@@ -180,9 +180,6 @@ const defaultServerConfig: AdminConfig = {
|
||||
EnableCustomGroups: true,
|
||||
SelfHostedPurchase: true,
|
||||
AllowSyncedDrafts: true,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
SelfHostedExpansion: false,
|
||||
},
|
||||
TeamSettings: {
|
||||
@@ -636,7 +633,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
Enable: true,
|
||||
},
|
||||
focalboard: {
|
||||
Enable: false,
|
||||
Enable: true,
|
||||
},
|
||||
playbooks: {
|
||||
Enable: true,
|
||||
@@ -689,13 +686,12 @@ const defaultServerConfig: AdminConfig = {
|
||||
GraphQL: false,
|
||||
InsightsEnabled: true,
|
||||
CommandPalette: false,
|
||||
BoardsProduct: true,
|
||||
BoardsProduct: false,
|
||||
SendWelcomePost: true,
|
||||
WorkTemplate: false,
|
||||
PostPriority: true,
|
||||
WysiwygEditor: false,
|
||||
PeopleProduct: false,
|
||||
AnnualSubscription: false,
|
||||
ReduceOnBoardingTaskList: false,
|
||||
OnboardingAutoShowLinkedBoard: true,
|
||||
ThreadsEverywhere: false,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
import {Locator, Page} from '@playwright/test';
|
||||
|
||||
export {waitUntil} from 'async-wait-until';
|
||||
|
||||
const visibilityHidden = 'visibility: hidden !important;';
|
||||
const hideTeamHeader = `.test-team-header {${visibilityHidden}} `;
|
||||
const hidePostHeaderTime = `.post__time {${visibilityHidden}} `;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import {test as base, Browser} from '@playwright/test';
|
||||
|
||||
import {TestBrowser} from './browser_context';
|
||||
import {shouldHaveBoardsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
|
||||
import {
|
||||
shouldHaveBoardsEnabled,
|
||||
shouldHaveCallsEnabled,
|
||||
shouldHaveFeatureFlag,
|
||||
shouldSkipInSmallScreen,
|
||||
shouldRunInLinux,
|
||||
} from './flag';
|
||||
import {initSetup, getAdminClient} from './server';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd} from './test_action';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||
import {pages} from './ui/pages';
|
||||
import {matchSnapshot} from './visual';
|
||||
|
||||
@@ -30,24 +36,26 @@ class PlaywrightExtended {
|
||||
readonly testBrowser: TestBrowser;
|
||||
|
||||
// ./flag
|
||||
readonly shouldHaveBoardsEnabled: typeof shouldHaveBoardsEnabled;
|
||||
readonly shouldHaveFeatureFlag: typeof shouldHaveFeatureFlag;
|
||||
readonly shouldSkipInSmallScreen: typeof shouldSkipInSmallScreen;
|
||||
readonly shouldRunInLinux: typeof shouldRunInLinux;
|
||||
readonly shouldHaveBoardsEnabled;
|
||||
readonly shouldHaveCallsEnabled;
|
||||
readonly shouldHaveFeatureFlag;
|
||||
readonly shouldSkipInSmallScreen;
|
||||
readonly shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
readonly getAdminClient: typeof getAdminClient;
|
||||
readonly initSetup: typeof initSetup;
|
||||
readonly getAdminClient;
|
||||
readonly initSetup;
|
||||
|
||||
// ./test_action
|
||||
readonly hideDynamicChannelsContent: typeof hideDynamicChannelsContent;
|
||||
readonly waitForAnimationEnd: typeof waitForAnimationEnd;
|
||||
readonly hideDynamicChannelsContent;
|
||||
readonly waitForAnimationEnd;
|
||||
readonly waitUntil;
|
||||
|
||||
// ./ui/pages
|
||||
readonly pages: typeof pages;
|
||||
readonly pages;
|
||||
|
||||
// ./visual
|
||||
readonly matchSnapshot: typeof matchSnapshot;
|
||||
readonly matchSnapshot;
|
||||
|
||||
constructor(browser: Browser) {
|
||||
// ./browser_context
|
||||
@@ -55,6 +63,7 @@ class PlaywrightExtended {
|
||||
|
||||
// ./flag
|
||||
this.shouldHaveBoardsEnabled = shouldHaveBoardsEnabled;
|
||||
this.shouldHaveCallsEnabled = shouldHaveCallsEnabled;
|
||||
this.shouldHaveFeatureFlag = shouldHaveFeatureFlag;
|
||||
this.shouldSkipInSmallScreen = shouldSkipInSmallScreen;
|
||||
this.shouldRunInLinux = shouldRunInLinux;
|
||||
@@ -66,6 +75,7 @@ class PlaywrightExtended {
|
||||
// ./test_action
|
||||
this.hideDynamicChannelsContent = hideDynamicChannelsContent;
|
||||
this.waitForAnimationEnd = waitForAnimationEnd;
|
||||
this.waitUntil = waitUntil;
|
||||
|
||||
// ./ui/pages
|
||||
this.pages = pages;
|
||||
|
||||
@@ -4,22 +4,23 @@
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class BoardsCreateModal {
|
||||
readonly locator: Locator;
|
||||
readonly productSwitchMenu: Locator;
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(locator: Locator) {
|
||||
this.locator = locator;
|
||||
readonly productSwitchMenu;
|
||||
|
||||
this.productSwitchMenu = locator.getByRole('button', {name: 'Product switch menu'});
|
||||
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.locator.getByRole('link', {name: ` ${name}`}).click();
|
||||
await this.container.getByRole('link', {name: ` ${name}`}).click();
|
||||
}
|
||||
|
||||
async toBeVisible(name: string) {
|
||||
await expect(this.locator.getByRole('heading', {name})).toBeVisible();
|
||||
await expect(this.container.getByRole('heading', {name})).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@ import {Locator} from '@playwright/test';
|
||||
|
||||
export default class BoardsSidebar {
|
||||
readonly container: Locator;
|
||||
readonly plusButton: Locator;
|
||||
readonly createNewBoardMenuItem: Locator;
|
||||
readonly createNewCategoryMenuItem: Locator;
|
||||
readonly titles: Locator;
|
||||
|
||||
readonly plusButton;
|
||||
readonly createNewBoardMenuItem;
|
||||
readonly createNewCategoryMenuItem;
|
||||
readonly titles;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
@@ -5,7 +5,8 @@ import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsAppBar {
|
||||
readonly container: Locator;
|
||||
readonly playbooksIcon: Locator;
|
||||
|
||||
readonly playbooksIcon;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
18
e2e/playwright/support/ui/components/channels/header.ts
Обычный файл
18
e2e/playwright/support/ui/components/channels/header.ts
Обычный файл
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsHeader {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsHeader};
|
||||
@@ -5,12 +5,17 @@ import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsPost {
|
||||
readonly container: Locator;
|
||||
readonly profileIcon: Locator;
|
||||
|
||||
readonly body;
|
||||
readonly profileIcon;
|
||||
readonly replyButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.post__body');
|
||||
this.profileIcon = container.locator('.profile-icon');
|
||||
this.replyButton = container.getByRole('button', {name: 'reply'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -26,6 +31,12 @@ export default class ChannelsPost {
|
||||
async getProfileImage(username: string) {
|
||||
return await this.profileIcon.getByAltText(`${username} profile image`);
|
||||
}
|
||||
|
||||
async openRHS() {
|
||||
await this.container.hover();
|
||||
await this.replyButton.waitFor();
|
||||
await this.replyButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPost};
|
||||
|
||||
@@ -5,9 +5,10 @@ import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsPostCreate {
|
||||
readonly container: Locator;
|
||||
readonly input: Locator;
|
||||
readonly attachmentButton: Locator;
|
||||
readonly emojiButton: Locator;
|
||||
|
||||
readonly input;
|
||||
readonly attachmentButton;
|
||||
readonly emojiButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
18
e2e/playwright/support/ui/components/channels/sidebar_right.ts
Обычный файл
18
e2e/playwright/support/ui/components/channels/sidebar_right.ts
Обычный файл
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class ChannelsSidebarRight {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsSidebarRight};
|
||||
@@ -5,7 +5,8 @@ import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class GlobalHeader {
|
||||
readonly container: Locator;
|
||||
readonly productSwitchMenu: Locator;
|
||||
|
||||
readonly productSwitchMenu;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BoardsSidebar} from './boards/sidebar';
|
||||
import {ChannelsHeader} from './channels/header';
|
||||
import {ChannelsAppBar} from './channels/app_bar';
|
||||
import {ChannelsPostCreate} from './channels/post_create';
|
||||
import {ChannelsPost} from './channels/post';
|
||||
import {ChannelsSidebarRight} from './channels/sidebar_right';
|
||||
import {GlobalHeader} from './global_header';
|
||||
|
||||
const components = {
|
||||
BoardsSidebar,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarRight,
|
||||
GlobalHeader,
|
||||
};
|
||||
|
||||
export {components, BoardsSidebar, ChannelsAppBar, ChannelsPostCreate, ChannelsPost, GlobalHeader};
|
||||
export {
|
||||
components,
|
||||
BoardsSidebar,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarRight,
|
||||
GlobalHeader,
|
||||
};
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {GlobalHeader} from '@e2e-support/ui/components';
|
||||
import {components} 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;
|
||||
|
||||
readonly globalHeader;
|
||||
|
||||
readonly createBoardHeading;
|
||||
readonly createEmptyBoardButton;
|
||||
readonly useTemplateButton;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.globalHeader = new GlobalHeader(this.page.locator('#global-header'));
|
||||
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
|
||||
this.createBoardHeading = page.getByRole('heading', {name: 'Create a board'});
|
||||
this.createEmptyBoardButton = page.getByRole('button', {name: ' Create an empty board'});
|
||||
this.useTemplateButton = page.getByRole('button', {name: 'Use this template'});
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {BoardsSidebar, GlobalHeader} from '@e2e-support/ui/components';
|
||||
import {components} 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;
|
||||
|
||||
readonly sidebar;
|
||||
readonly globalHeader;
|
||||
|
||||
readonly topHead;
|
||||
readonly editableTitle;
|
||||
readonly shareButton;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.sidebar = new BoardsSidebar(page.locator('.octo-sidebar'));
|
||||
this.globalHeader = new GlobalHeader(this.page.locator('#global-header'));
|
||||
this.sidebar = new components.BoardsSidebar(page.locator('.octo-sidebar'));
|
||||
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
|
||||
this.topHead = page.locator('.top-head');
|
||||
this.editableTitle = this.topHead.getByPlaceholder('Untitled board');
|
||||
this.shareButton = page.getByRole('button', {name: ' Share'});
|
||||
|
||||
@@ -3,27 +3,32 @@
|
||||
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
import {ChannelsAppBar, ChannelsPost, ChannelsPostCreate, GlobalHeader} from '@e2e-support/ui/components';
|
||||
import {isSmallScreen} from '@e2e-support/util';
|
||||
import {waitUntil} from '@e2e-support/test_action';
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
import {duration, isSmallScreen} from '@e2e-support/util';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
readonly page: Page;
|
||||
readonly postCreate: ChannelsPostCreate;
|
||||
readonly globalHeader: GlobalHeader;
|
||||
readonly appBar: ChannelsAppBar;
|
||||
readonly postCreate;
|
||||
readonly globalHeader;
|
||||
readonly header;
|
||||
readonly appBar;
|
||||
readonly sidebarRight;
|
||||
|
||||
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'));
|
||||
this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create'));
|
||||
this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
|
||||
this.header = new components.ChannelsHeader(page.locator('.channel-header'));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||
}
|
||||
|
||||
async goto(teamName = '', channelName = '') {
|
||||
let channelsUrl = '/';
|
||||
if (teamName) {
|
||||
channelsUrl += `/${teamName}`;
|
||||
channelsUrl += `${teamName}`;
|
||||
if (channelName) {
|
||||
channelsUrl += `/${channelName}`;
|
||||
}
|
||||
@@ -47,25 +52,54 @@ export default class ChannelsPage {
|
||||
async getFirstPost() {
|
||||
await this.page.getByTestId('postView').first().waitFor();
|
||||
const post = await this.page.getByTestId('postView').first();
|
||||
return new ChannelsPost(post);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getLastPost() {
|
||||
await this.page.getByTestId('postView').last().waitFor();
|
||||
const post = await this.page.getByTestId('postView').last();
|
||||
return new ChannelsPost(post);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getNthPost(index: number) {
|
||||
await this.page.getByTestId('postView').nth(index).waitFor();
|
||||
const post = await this.page.getByTestId('postView').nth(index);
|
||||
return new ChannelsPost(post);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getPostById(id: string) {
|
||||
await this.page.locator(`[id="post_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="post_${id}"]`);
|
||||
return new ChannelsPost(post);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getRHSPostById(id: string) {
|
||||
await this.page.locator(`[id="rhsPost_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="rhsPost_${id}"]`);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getLastPost();
|
||||
const content = await post.container.textContent();
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
|
||||
async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getPostById(id);
|
||||
const content = await post.container.textContent();
|
||||
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
export default class LandingLoginPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly isMobile?: boolean;
|
||||
readonly viewInAppButton: Locator;
|
||||
readonly viewInDesktopAppButton: Locator;
|
||||
readonly viewInBrowserButton: Locator;
|
||||
|
||||
readonly viewInAppButton;
|
||||
readonly viewInDesktopAppButton;
|
||||
readonly viewInBrowserButton;
|
||||
|
||||
constructor(page: Page, isMobile?: boolean) {
|
||||
this.page = page;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {AdminConfig} from '@mattermost/types/config';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
@@ -10,18 +10,19 @@ 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;
|
||||
|
||||
readonly title;
|
||||
readonly subtitle;
|
||||
readonly bodyCard;
|
||||
readonly loginInput;
|
||||
readonly loginPlaceholder;
|
||||
readonly passwordInput;
|
||||
readonly signInButton;
|
||||
readonly createAccountLink;
|
||||
readonly forgotPasswordLink;
|
||||
readonly userErrorLabel;
|
||||
readonly fieldWithError;
|
||||
readonly formContainer;
|
||||
|
||||
constructor(page: Page, adminConfig: AdminConfig) {
|
||||
this.page = page;
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator, Page} from '@playwright/test';
|
||||
import {expect, 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;
|
||||
|
||||
readonly title;
|
||||
readonly subtitle;
|
||||
readonly bodyCard;
|
||||
readonly emailInput;
|
||||
readonly usernameInput;
|
||||
readonly passwordInput;
|
||||
readonly createAccountButton;
|
||||
readonly loginLink;
|
||||
readonly emailError;
|
||||
readonly usernameError;
|
||||
readonly passwordError;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
"@e2e-test.config": ["test.config.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["./**/*"]
|
||||
"include": ["./**/*"],
|
||||
"exclude": ["playwright-report"]
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user