MM-62954 E2E/Playwright shared library (#30177)
* feat: Add package.json for Playwright library with dependencies * feat: Add explicit exports for test.config in playwright-lib package * feat: Add initialization setup for Mattermost E2E testing with admin and user client * fix: Update package dependencies and resolve TypeScript build errors * feat: Update package exports for test.config to support both CommonJS and ESM * playwright shared library * add README, fix pipeline * keep file structures, move report up to playwright * minimize API, use the prerelease versions of client and types * bump version * update package*.json * resolve merge conflict * update depedencies and merge conflicts * update readme and fix ci * remove unnecessary export and list all external packages * fix import for Client4
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ce9632cca3
Коммит
a47269cfe2
2
.github/workflows/e2e-fulltests-ci.yml
поставляемый
2
.github/workflows/e2e-fulltests-ci.yml
поставляемый
@@ -70,7 +70,7 @@ jobs:
|
|||||||
SERVER: "${{ steps.generate.outputs.SERVER }}"
|
SERVER: "${{ steps.generate.outputs.SERVER }}"
|
||||||
ENABLED_DOCKER_SERVICES: "${{ steps.generate.outputs.ENABLED_DOCKER_SERVICES }}"
|
ENABLED_DOCKER_SERVICES: "${{ steps.generate.outputs.ENABLED_DOCKER_SERVICES }}"
|
||||||
TEST_FILTER_CYPRESS: "${{ steps.generate.outputs.TEST_FILTER_CYPRESS }}"
|
TEST_FILTER_CYPRESS: "${{ steps.generate.outputs.TEST_FILTER_CYPRESS }}"
|
||||||
TEST_FILTER_PLAYWRIGHT: "tests/ --project=chrome" # Note: Run on chrome but eventually will enable to all projects which include firefox and ipad.
|
TEST_FILTER_PLAYWRIGHT: "tests"
|
||||||
BUILD_ID: "${{ steps.generate.outputs.BUILD_ID }}"
|
BUILD_ID: "${{ steps.generate.outputs.BUILD_ID }}"
|
||||||
TM4J_ENABLE: "${{ steps.generate.outputs.TM4J_ENABLE }}"
|
TM4J_ENABLE: "${{ steps.generate.outputs.TM4J_ENABLE }}"
|
||||||
REPORT_TYPE: "${{ steps.generate.outputs.REPORT_TYPE }}"
|
REPORT_TYPE: "${{ steps.generate.outputs.REPORT_TYPE }}"
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ case "${TEST:-$TEST_DEFAULT}" in
|
|||||||
cypress )
|
cypress )
|
||||||
export TEST_FILTER_DEFAULT='--stage=@prod --group=@smoke' ;;
|
export TEST_FILTER_DEFAULT='--stage=@prod --group=@smoke' ;;
|
||||||
playwright )
|
playwright )
|
||||||
export TEST_FILTER_DEFAULT='tests/functional/system_console/system_users/actions.spec.ts --project=chrome' ;;
|
export TEST_FILTER_DEFAULT='functional/system_console/system_users/actions.spec.ts' ;;
|
||||||
* )
|
* )
|
||||||
export TEST_FILTER_DEFAULT='' ;;
|
export TEST_FILTER_DEFAULT='' ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ case "$TEST" in
|
|||||||
if [ -n "$WEBHOOK_URL" ]; then
|
if [ -n "$WEBHOOK_URL" ]; then
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i
|
||||||
# Utilize environment data and report files to generate the webhook body
|
# Utilize environment data and report files to generate the webhook body
|
||||||
./report.webhookgen.js | curl -X POST -fsSL -H 'Content-Type: application/json' -d @- "$WEBHOOK_URL"
|
node report.webhookgen.js | curl -X POST -fsSL -H 'Content-Type: application/json' -d @- "$WEBHOOK_URL"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ $(if mme2e_is_token_in_list "webhook-interactions" "$ENABLED_DOCKER_SERVICES"; t
|
|||||||
$(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
|
$(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
|
||||||
echo '
|
echo '
|
||||||
playwright:
|
playwright:
|
||||||
image: mcr.microsoft.com/playwright:v1.49.1-noble
|
image: mcr.microsoft.com/playwright:v1.51.1-noble
|
||||||
entrypoint: ["/bin/bash", "-c"]
|
entrypoint: ["/bin/bash", "-c"]
|
||||||
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -37,13 +37,18 @@ EOF
|
|||||||
|
|
||||||
# Run Playwright test
|
# Run Playwright test
|
||||||
# NB: do not exit the script if some testcases fail
|
# NB: do not exit the script if some testcases fail
|
||||||
${MME2E_DC_SERVER} exec -i -u "$MME2E_UID" -- playwright bash -c "cd e2e-tests/playwright && npm run test -- ${TEST_FILTER}" | tee ../playwright/logs/playwright.log || true
|
${MME2E_DC_SERVER} exec -i -u "$MME2E_UID" -- playwright bash -c "cd e2e-tests/playwright && npm run test:ci -- ${TEST_FILTER}" | tee ../playwright/logs/playwright.log || true
|
||||||
|
|
||||||
# Collect run results
|
# Collect run results
|
||||||
# Documentation on the results.json file: https://playwright.dev/docs/api/class-testcase#test-case-expected-status
|
# Documentation on the results.json file: https://playwright.dev/docs/api/class-testcase#test-case-expected-status
|
||||||
# NB: the following line is needed only for compatibility reasons, to support RollingRelease tests for versions prior to v10.1.0
|
# NB: the following line is needed only for compatibility reasons, to support RollingRelease tests for versions prior to v10.1.0
|
||||||
# It can be removed after releases <=v10.0.x are phased out
|
# It can be removed after releases <=v10.0.x are phased out
|
||||||
mv -v ../playwright/playwright-report/results.json ../playwright/results/reporter/results.json 2>/dev/null || true
|
mv -v ../playwright/playwright-report/results.json ../playwright/results/reporter/results.json 2>/dev/null || true
|
||||||
|
|
||||||
|
# NB: the following line is needed only for compatibility reasons, to support RollingRelease tests for versions prior to v10.6.0
|
||||||
|
# It can be removed once 10.5 (ESR) is no longer supported
|
||||||
|
mv -v ../playwright/test/results/ ../playwright/ 2>/dev/null || true
|
||||||
|
|
||||||
jq -f /dev/stdin ../playwright/results/reporter/results.json > ../playwright/results/summary.json <<EOF
|
jq -f /dev/stdin ../playwright/results/reporter/results.json > ../playwright/results/summary.json <<EOF
|
||||||
{
|
{
|
||||||
passed: .stats.expected,
|
passed: .stats.expected,
|
||||||
|
|||||||
16
e2e-tests/playwright/.gitignore
поставляемый
Обычный файл
16
e2e-tests/playwright/.gitignore
поставляемый
Обычный файл
@@ -0,0 +1,16 @@
|
|||||||
|
# playwright tests
|
||||||
|
test/logs
|
||||||
|
test/node_modules
|
||||||
|
test/playwright-report
|
||||||
|
test/test-results
|
||||||
|
test/results
|
||||||
|
test/storage_state
|
||||||
|
test/specs-results
|
||||||
|
test/specs/**/*-darwin.png
|
||||||
|
test/specs/**/*-window.png
|
||||||
|
test/specs/accessibility/**/*-snapshots
|
||||||
|
test/.eslintcache
|
||||||
|
|
||||||
|
# build
|
||||||
|
dist
|
||||||
|
*.tsbuildinfo
|
||||||
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
import js from '@eslint/js';
|
import js from '@eslint/js';
|
||||||
import {FlatCompat} from '@eslint/eslintrc';
|
import {FlatCompat} from '@eslint/eslintrc';
|
||||||
|
import eslintPluginHeader from 'eslint-plugin-header';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -14,18 +15,23 @@ const compat = new FlatCompat({
|
|||||||
allConfig: js.configs.all,
|
allConfig: js.configs.all,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eslintPluginHeader.rules.header.meta.schema = false;
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
ignores: ['**/node_modules', '**/playwright-report', '**/test-results', '**/results'],
|
ignores: ['**/node_modules', '**/dist', '**/playwright-report', '**/test-results', '**/results'],
|
||||||
},
|
},
|
||||||
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended').map((config) => ({
|
...compat
|
||||||
...config,
|
.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:import/recommended')
|
||||||
files: ['**/*.ts', '**/*.s'],
|
.map((config) => ({
|
||||||
})),
|
...config,
|
||||||
|
files: ['**/*.ts', '**/*.js'],
|
||||||
|
})),
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.s'],
|
files: ['**/*.ts', '**/*.js'],
|
||||||
plugins: {
|
plugins: {
|
||||||
'@typescript-eslint': typescriptEslint,
|
'@typescript-eslint': typescriptEslint,
|
||||||
|
header: eslintPluginHeader,
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
@@ -35,11 +41,31 @@ export default [
|
|||||||
ecmaVersion: 5,
|
ecmaVersion: 5,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
|
'header/header': [
|
||||||
|
'error',
|
||||||
|
'line',
|
||||||
|
' Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information.',
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'newlines-between': 'always',
|
||||||
|
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
214
e2e-tests/playwright/lib/README.md
Обычный файл
214
e2e-tests/playwright/lib/README.md
Обычный файл
@@ -0,0 +1,214 @@
|
|||||||
|
# @mattermost/playwright-lib
|
||||||
|
|
||||||
|
A comprehensive end-to-end testing library for Mattermost web, desktop and plugin applications using Playwright.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This library provides:
|
||||||
|
|
||||||
|
- Pre-built page objects and components for common Mattermost UI elements
|
||||||
|
- Server configuration and initialization utilities
|
||||||
|
- Test fixtures and helpers
|
||||||
|
- Visual testing support with Percy integration
|
||||||
|
- Accessibility testing support with [axe-core](https://github.com/dequelabs/axe-core)
|
||||||
|
- Browser notification mocking
|
||||||
|
- File handling utilities
|
||||||
|
- Common test actions and assertions
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @mattermost/playwright-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Basic example of logging in and posting a message:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {test, expect} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
|
test('user can post message', async ({pw}) => {
|
||||||
|
// # Create and login a new user
|
||||||
|
const {user} = await pw.initSetup();
|
||||||
|
const {channelsPage} = await pw.testBrowser.login(user);
|
||||||
|
|
||||||
|
// # Navigate and post a message
|
||||||
|
await channelsPage.goto();
|
||||||
|
const message = 'Hello World!';
|
||||||
|
await channelsPage.postMessage(message);
|
||||||
|
|
||||||
|
// * Verify message appears
|
||||||
|
const lastPost = await channelsPage.getLastPost();
|
||||||
|
await expect(lastPost).toHaveText(message);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
### Page Objects
|
||||||
|
|
||||||
|
Ready-to-use page objects for common Mattermost pages:
|
||||||
|
|
||||||
|
- Login
|
||||||
|
- Signup
|
||||||
|
- Channels
|
||||||
|
- System Console
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
### UI Components
|
||||||
|
|
||||||
|
Reusable component objects for UI elements:
|
||||||
|
|
||||||
|
- Headers
|
||||||
|
- Posts
|
||||||
|
- Menus
|
||||||
|
- Modals
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
### Test Utilities
|
||||||
|
|
||||||
|
Helper functions for common testing needs:
|
||||||
|
|
||||||
|
- Server setup and configuration
|
||||||
|
- User/team creation
|
||||||
|
- File handling
|
||||||
|
- Visual testing
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The library can be configured via optional environment variables:
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
All environment variables are optional with sensible defaults.
|
||||||
|
|
||||||
|
#### Server Configuration
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ----------------------------- | ------------------------------------------ | -------------------------------- |
|
||||||
|
| `PW_BASE_URL` | Server URL | `http://localhost:8065` |
|
||||||
|
| `PW_ADMIN_USERNAME` | Admin username | `sysadmin` |
|
||||||
|
| `PW_ADMIN_PASSWORD` | Admin password | `Sys@dmin-sample1` |
|
||||||
|
| `PW_ADMIN_EMAIL` | Admin email | `sysadmin@sample.mattermost.com` |
|
||||||
|
| `PW_ENSURE_PLUGINS_INSTALLED` | Comma-separated list of plugins to install | `[]` |
|
||||||
|
| `PW_RESET_BEFORE_TEST` | Reset server before test | `false` |
|
||||||
|
|
||||||
|
#### High Availability Cluster Settings
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| -------------------------- | ----------------------- | ---------------- |
|
||||||
|
| `PW_HA_CLUSTER_ENABLED` | Enable HA cluster | `false` |
|
||||||
|
| `PW_HA_CLUSTER_NODE_COUNT` | Number of cluster nodes | `2` |
|
||||||
|
| `PW_HA_CLUSTER_NAME` | Cluster name | `mm_dev_cluster` |
|
||||||
|
|
||||||
|
#### Push Notifications
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ----------------------------- | ---------------------------- | ---------------------------------- |
|
||||||
|
| `PW_PUSH_NOTIFICATION_SERVER` | Push notification server URL | `https://push-test.mattermost.com` |
|
||||||
|
|
||||||
|
#### Playwright Settings
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ------------- | ------------------------------- | ------- |
|
||||||
|
| `PW_HEADLESS` | Run tests headless | `true` |
|
||||||
|
| `PW_SLOWMO` | Add delay between actions in ms | `0` |
|
||||||
|
| `PW_WORKERS` | Number of parallel workers | `1` |
|
||||||
|
|
||||||
|
#### Visual Testing
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| -------------------- | --------------------------- | ------- |
|
||||||
|
| `PW_SNAPSHOT_ENABLE` | Enable snapshot testing | `false` |
|
||||||
|
| `PW_PERCY_ENABLE` | Enable Percy visual testing | `false` |
|
||||||
|
|
||||||
|
#### CI Settings
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| -------- | ------------------------------------ | ------- |
|
||||||
|
| `CI` | Set automatically in CI environments | N/A |
|
||||||
|
|
||||||
|
## Accessibility Testing
|
||||||
|
|
||||||
|
The library includes built-in accessibility testing using [axe-core](https://github.com/dequelabs/axe-core):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {test, expect} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
|
test('verify login page accessibility', async ({page, axe}) => {
|
||||||
|
// # Navigate to login page
|
||||||
|
await page.goto('/login');
|
||||||
|
|
||||||
|
// # Run accessibility scan
|
||||||
|
const results = await axe.builder(page).analyze();
|
||||||
|
|
||||||
|
// * Verify no accessibility violations
|
||||||
|
expect(results.violations).toHaveLength(0);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The axe-core integration:
|
||||||
|
|
||||||
|
- Runs WCAG 2.0 Level A & AA rules by default
|
||||||
|
- Provides detailed violation reports
|
||||||
|
- Supports rule customization
|
||||||
|
- Can be configured per-test or globally
|
||||||
|
|
||||||
|
## Visual Testing
|
||||||
|
|
||||||
|
The library supports visual testing through [Playwright's built-in visual comparisons](https://playwright.dev/docs/test-snapshots) and [Percy](https://www.browserstack.com/percy) integration:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {test, expect} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
|
test('verify channel header appearance', async ({pw, browserName, viewport}, testInfo) => {
|
||||||
|
// # Setup and login
|
||||||
|
const {user} = await pw.initSetup();
|
||||||
|
const {page, channelsPage} = await pw.testBrowser.login(user);
|
||||||
|
|
||||||
|
// # Navigate and prepare page
|
||||||
|
await channelsPage.goto();
|
||||||
|
await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
|
||||||
|
await pw.hideDynamicChannelsContent(page);
|
||||||
|
|
||||||
|
// * Take and verify snapshot
|
||||||
|
await pw.matchSnapshot(testInfo, {page, browserName, viewport});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Notifications
|
||||||
|
|
||||||
|
Mock and verify browser notifications:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {test, expect} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
|
test('verify notification on mention', async ({pw}) => {
|
||||||
|
// # Setup users and team
|
||||||
|
const {team, adminUser, user} = await pw.initSetup();
|
||||||
|
|
||||||
|
// # Setup admin browser with notifications
|
||||||
|
const {page: adminPage, channelsPage: adminChannelsPage} = await pw.testBrowser.login(adminUser);
|
||||||
|
await adminChannelsPage.goto(team.name, 'town-square');
|
||||||
|
await pw.stubNotification(adminPage, 'granted');
|
||||||
|
|
||||||
|
// # Setup user browser and post mention
|
||||||
|
const {channelsPage: userChannelsPage} = await pw.testBrowser.login(user);
|
||||||
|
await userChannelsPage.goto(team.name, 'off-topic');
|
||||||
|
await userChannelsPage.postMessage(`@ALL good morning, ${team.name}!`);
|
||||||
|
|
||||||
|
// * Verify notification received
|
||||||
|
const notifications = await pw.waitForNotification(adminPage);
|
||||||
|
expect(notifications.length).toBe(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](https://github.com/mattermost/mattermost/blob/master/CONTRIBUTING.md) for development setup and guidelines.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE.txt](https://github.com/mattermost/mattermost/blob/master/LICENSE.txt) for license information.
|
||||||
68
e2e-tests/playwright/lib/package.json
Обычный файл
68
e2e-tests/playwright/lib/package.json
Обычный файл
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "@mattermost/playwright-lib",
|
||||||
|
"version": "10.6.1",
|
||||||
|
"description": "A comprehensive end-to-end testing library for Mattermost web, desktop and plugin applications using Playwright",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/mattermost/mattermost.git"
|
||||||
|
},
|
||||||
|
"author": "mattermost",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/mattermost/mattermost/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/mattermost/mattermost/tree/master/e2e-tests/playwright/lib#readme",
|
||||||
|
"type": "commonjs",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"mattermost",
|
||||||
|
"e2e",
|
||||||
|
"playwright",
|
||||||
|
"test-automation"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c --bundleConfigAsCjs",
|
||||||
|
"build:watch": "npm run build -- --watch",
|
||||||
|
"build-tsc": "tsc --build --verbose",
|
||||||
|
"build-tsc:watch": "tsc --watch --preserveWatchOutput",
|
||||||
|
"clean": "rm -rf dist node_modules *.tsbuildinfo",
|
||||||
|
"tsc": "tsc -b"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@axe-core/playwright": "4.10.1",
|
||||||
|
"@mattermost/client": "file:../../../webapp/platform/client",
|
||||||
|
"@mattermost/types": "file:../../../webapp/platform/types",
|
||||||
|
"@percy/cli": "1.30.7",
|
||||||
|
"@percy/playwright": "1.0.7",
|
||||||
|
"async-wait-until": "2.0.23",
|
||||||
|
"axe-core": "4.10.3",
|
||||||
|
"deepmerge": "4.3.1",
|
||||||
|
"dotenv": "16.4.7",
|
||||||
|
"mime-types": "3.0.1",
|
||||||
|
"uuid": "11.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-typescript": "12.1.2",
|
||||||
|
"@types/mime-types": "2.1.4",
|
||||||
|
"@types/node": "22.13.14",
|
||||||
|
"@types/react": "19.0.12",
|
||||||
|
"rollup": "4.38.0",
|
||||||
|
"rollup-plugin-copy": "3.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@playwright/test": "1.51.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
e2e-tests/playwright/lib/rollup.config.js
Обычный файл
41
e2e-tests/playwright/lib/rollup.config.js
Обычный файл
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
import copy from 'rollup-plugin-copy';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.ts',
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
dir: 'dist',
|
||||||
|
format: 'cjs', // CommonJS for Playwright
|
||||||
|
sourcemap: true,
|
||||||
|
preserveModules: true, // Keep file structure
|
||||||
|
preserveModulesRoot: 'src',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
typescript(),
|
||||||
|
copy({
|
||||||
|
targets: [{src: 'src/asset/**/*', dest: 'dist/asset'}], // Copy assets to dist/
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
external: [
|
||||||
|
'@playwright/test',
|
||||||
|
'@mattermost/client',
|
||||||
|
'@mattermost/types/config',
|
||||||
|
'@axe-core/playwright',
|
||||||
|
'@percy/playwright',
|
||||||
|
'dotenv',
|
||||||
|
'node:fs/promises',
|
||||||
|
'node:path',
|
||||||
|
'node:fs',
|
||||||
|
'node:os',
|
||||||
|
'mime-types',
|
||||||
|
'uuid',
|
||||||
|
'async-wait-until',
|
||||||
|
'chalk',
|
||||||
|
'deepmerge',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
До Ширина: | Высота: | Размер: 13 KiB После Ширина: | Высота: | Размер: 13 KiB |
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
import {writeFile} from 'node:fs/promises';
|
import {writeFile} from 'node:fs/promises';
|
||||||
|
|
||||||
import {request, Browser, BrowserContext} from '@playwright/test';
|
import {Browser, BrowserContext, request} from '@playwright/test';
|
||||||
|
|
||||||
import {UserProfile} from '@mattermost/types/users';
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
import testConfig from '@e2e-test.config';
|
|
||||||
import pages from '@e2e-support/ui/pages';
|
import {testConfig} from './test_config';
|
||||||
|
import {pages} from './ui/pages';
|
||||||
|
|
||||||
export class TestBrowser {
|
export class TestBrowser {
|
||||||
readonly browser: Browser;
|
readonly browser: Browser;
|
||||||
93
e2e-tests/playwright/lib/src/file.ts
Обычный файл
93
e2e-tests/playwright/lib/src/file.ts
Обычный файл
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
import mime from 'mime-types';
|
||||||
|
|
||||||
|
const commonAssetPath = path.resolve(__dirname, 'asset');
|
||||||
|
export const assetPath = path.resolve(process.cwd(), 'asset');
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const availableFiles = ['mattermost-icon_128x128.png'] as const;
|
||||||
|
type AvailableFilename = (typeof availableFiles)[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data and creates a File object.
|
||||||
|
* @param filePath - The path to the file.
|
||||||
|
* @returns A File object containing the file data.
|
||||||
|
* @throws If the file does not exist.
|
||||||
|
*/
|
||||||
|
export function getFileData(filePath: string): File {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`File not found at path: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeType = mime.lookup(filePath) || undefined;
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
|
|
||||||
|
return new File([fileBuffer], fileName, {type: mimeType});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data and creates a Blob object.
|
||||||
|
* @param filePath - The path to the file.
|
||||||
|
* @returns A Blob object containing the file data.
|
||||||
|
* @throws If the file does not exist.
|
||||||
|
*/
|
||||||
|
export function getBlobData(filePath: string): Blob {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`File not found at path: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeType = mime.lookup(filePath) || undefined;
|
||||||
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
|
|
||||||
|
return new Blob([fileBuffer], {type: mimeType});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data from the "asset" directory and creates a File object.
|
||||||
|
* @param filename - The name of the file in the "asset" directory.
|
||||||
|
* @returns An object containing a File object
|
||||||
|
*/
|
||||||
|
export function getFileFromAsset(filename: string) {
|
||||||
|
const filePath = path.join(assetPath, filename);
|
||||||
|
|
||||||
|
return getFileData(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data from the "asset" directory and creates a Blob object.
|
||||||
|
* @param filename - The name of the file in the "asset" directory.
|
||||||
|
* @returns An object containing a Blob object
|
||||||
|
*/
|
||||||
|
export function getBlobFromAsset(filename: string) {
|
||||||
|
const filePath = path.join(assetPath, filename);
|
||||||
|
|
||||||
|
return getBlobData(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data from the lib "asset" directory and creates a File object.
|
||||||
|
* @param filename - The name of the file in the "asset" directory.
|
||||||
|
* @returns An object containing a File object
|
||||||
|
*/
|
||||||
|
export function getFileFromCommonAsset(filename: AvailableFilename) {
|
||||||
|
const filePath = path.join(commonAssetPath, filename);
|
||||||
|
|
||||||
|
return getFileData(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads file data from the lib "asset" directory and creates a Blob object.
|
||||||
|
* @param filename - The name of the file in the "asset" directory.
|
||||||
|
* @returns An object containing a Blob object
|
||||||
|
*/
|
||||||
|
export function getBlobFromCommonAsset(filename: AvailableFilename) {
|
||||||
|
const filePath = path.join(commonAssetPath, filename);
|
||||||
|
|
||||||
|
return getBlobData(filePath);
|
||||||
|
}
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
|
|
||||||
import {expect} from '@playwright/test';
|
import {expect, test} from '@playwright/test';
|
||||||
|
|
||||||
import {test} from './test_fixture';
|
|
||||||
import {callsPluginId} from './constant';
|
import {callsPluginId} from './constant';
|
||||||
import {getAdminClient} from './server/init';
|
import {getAdminClient} from './server/init';
|
||||||
|
|
||||||
@@ -56,12 +55,17 @@ export async function ensureLicense() {
|
|||||||
|
|
||||||
export async function requestTrialLicense() {
|
export async function requestTrialLicense() {
|
||||||
const {adminClient} = await getAdminClient();
|
const {adminClient} = await getAdminClient();
|
||||||
|
const admin = await adminClient.getMe();
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error This may fail requesting for trial license
|
|
||||||
await adminClient.requestTrialLicense({
|
await adminClient.requestTrialLicense({
|
||||||
receive_emails_accepted: true,
|
receive_emails_accepted: true,
|
||||||
terms_accepted: true,
|
terms_accepted: true,
|
||||||
users: 100,
|
users: 100,
|
||||||
|
contact_name: admin.first_name + ' ' + admin.last_name,
|
||||||
|
contact_email: admin.email,
|
||||||
|
company_name: 'Mattermost Playwright E2E Tests',
|
||||||
|
company_size: '101-250',
|
||||||
|
company_country: 'United States',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error, 'Failed to request trial license').toBeFalsy();
|
expect(error, 'Failed to request trial license').toBeFalsy();
|
||||||
@@ -2,21 +2,22 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect} from '@playwright/test';
|
import {expect} from '@playwright/test';
|
||||||
|
import {Client4} from '@mattermost/client';
|
||||||
import {UserProfile} from '@mattermost/types/users';
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
|
import {PluginManifest} from '@mattermost/types/plugins';
|
||||||
import {PreferenceType} from '@mattermost/types/preferences';
|
import {PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
import {Client, createRandomTeam, getAdminClient, getDefaultAdminUser, makeClient} from './support/server';
|
import {defaultTeam} from './util';
|
||||||
import {defaultTeam} from './support/util';
|
import {createRandomTeam, getAdminClient, getDefaultAdminUser, makeClient} from './server';
|
||||||
import testConfig from './test.config';
|
import {testConfig} from './test_config';
|
||||||
|
|
||||||
async function globalSetup() {
|
export async function baseGlobalSetup() {
|
||||||
let adminClient: Client;
|
let adminClient: Client4;
|
||||||
let adminUser: UserProfile | null;
|
let adminUser: UserProfile | null;
|
||||||
({adminClient, adminUser} = await getAdminClient({skipLog: true}));
|
({adminClient, adminUser} = await getAdminClient({skipLog: true}));
|
||||||
|
|
||||||
if (!adminUser) {
|
if (!adminUser) {
|
||||||
const firstClient = new Client();
|
const firstClient = new Client4();
|
||||||
firstClient.setUrl(testConfig.baseURL);
|
firstClient.setUrl(testConfig.baseURL);
|
||||||
const defaultAdmin = getDefaultAdminUser();
|
const defaultAdmin = getDefaultAdminUser();
|
||||||
await firstClient.createUser(defaultAdmin, '', '');
|
await firstClient.createUser(defaultAdmin, '', '');
|
||||||
@@ -25,13 +26,9 @@ async function globalSetup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await sysadminSetup(adminClient, adminUser);
|
await sysadminSetup(adminClient, adminUser);
|
||||||
|
|
||||||
return function () {
|
|
||||||
// placeholder for teardown setup
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sysadminSetup(client: Client, user: UserProfile | null) {
|
async function sysadminSetup(client: Client4, user: UserProfile | null) {
|
||||||
// Ensure admin's email is verified.
|
// Ensure admin's email is verified.
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await client.verifyUserEmail(client.token);
|
await client.verifyUserEmail(client.token);
|
||||||
@@ -79,7 +76,7 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
|
|||||||
await ensureServerDeployment(client);
|
await ensureServerDeployment(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function printLicenseInfo(client: Client) {
|
async function printLicenseInfo(client: Client4) {
|
||||||
const license = await client.getClientLicenseOld();
|
const license = await client.getClientLicenseOld();
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Server License:
|
console.log(`Server License:
|
||||||
@@ -91,7 +88,7 @@ async function printLicenseInfo(client: Client) {
|
|||||||
- Users = ${license.Users}`);
|
- Users = ${license.Users}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function printClientInfo(client: Client) {
|
async function printClientInfo(client: Client4) {
|
||||||
const config = await client.getClientConfigOld();
|
const config = await client.getClientConfigOld();
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Build Info:
|
console.log(`Build Info:
|
||||||
@@ -111,7 +108,7 @@ async function printClientInfo(client: Client) {
|
|||||||
- LogSettings.EnableDiagnostics = ${LogSettings?.EnableDiagnostics}`);
|
- LogSettings.EnableDiagnostics = ${LogSettings?.EnableDiagnostics}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensurePluginsLoaded(client: Client) {
|
export async function ensurePluginsLoaded(client: Client4) {
|
||||||
const pluginStatus = await client.getPluginStatuses();
|
const pluginStatus = await client.getPluginStatuses();
|
||||||
const plugins = await client.getPlugins();
|
const plugins = await client.getPlugins();
|
||||||
|
|
||||||
@@ -123,7 +120,7 @@ export async function ensurePluginsLoaded(client: Client) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = plugins.active.some((plugin) => plugin.id === pluginId);
|
const isActive = plugins.active.some((plugin: PluginManifest) => plugin.id === pluginId);
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
await client.enablePlugin(pluginId);
|
await client.enablePlugin(pluginId);
|
||||||
|
|
||||||
@@ -136,7 +133,7 @@ export async function ensurePluginsLoaded(client: Client) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function printPluginDetails(client: Client) {
|
async function printPluginDetails(client: Client4) {
|
||||||
const plugins = await client.getPlugins();
|
const plugins = await client.getPlugins();
|
||||||
|
|
||||||
if (plugins.active.length) {
|
if (plugins.active.length) {
|
||||||
@@ -144,7 +141,7 @@ async function printPluginDetails(client: Client) {
|
|||||||
console.log('Active plugins:');
|
console.log('Active plugins:');
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.active.forEach((plugin) => {
|
plugins.active.forEach((plugin: PluginManifest) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
||||||
});
|
});
|
||||||
@@ -154,7 +151,7 @@ async function printPluginDetails(client: Client) {
|
|||||||
console.log('Inactive plugins:');
|
console.log('Inactive plugins:');
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.inactive.forEach((plugin) => {
|
plugins.inactive.forEach((plugin: PluginManifest) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
console.log(` - ${plugin.id}@${plugin.version} | min_server@${plugin.min_server_version}`);
|
||||||
});
|
});
|
||||||
@@ -163,7 +160,7 @@ async function printPluginDetails(client: Client) {
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureServerDeployment(client: Client) {
|
async function ensureServerDeployment(client: Client4) {
|
||||||
if (testConfig.haClusterEnabled) {
|
if (testConfig.haClusterEnabled) {
|
||||||
const {haClusterNodeCount, haClusterName} = testConfig;
|
const {haClusterNodeCount, haClusterName} = testConfig;
|
||||||
|
|
||||||
@@ -194,7 +191,7 @@ async function ensureServerDeployment(client: Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function savePreferences(client: Client, userId: UserProfile['id']) {
|
async function savePreferences(client: Client4, userId: UserProfile['id']) {
|
||||||
try {
|
try {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('userId is not defined');
|
throw new Error('userId is not defined');
|
||||||
@@ -211,5 +208,3 @@ async function savePreferences(client: Client, userId: UserProfile['id']) {
|
|||||||
console.log('Error saving preferences', error);
|
console.log('Error saving preferences', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default globalSetup;
|
|
||||||
22
e2e-tests/playwright/lib/src/index.ts
Обычный файл
22
e2e-tests/playwright/lib/src/index.ts
Обычный файл
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
export {test, expect, PlaywrightExtended} from './test_fixture';
|
||||||
|
export {testConfig} from './test_config';
|
||||||
|
export {baseGlobalSetup} from './global_setup';
|
||||||
|
export {TestBrowser} from './browser_context';
|
||||||
|
export {getBlobFromAsset, getFileFromAsset} from './file';
|
||||||
|
export {duration, wait} from './util';
|
||||||
|
|
||||||
|
export {
|
||||||
|
ChannelsPage,
|
||||||
|
LandingLoginPage,
|
||||||
|
LoginPage,
|
||||||
|
ResetPasswordPage,
|
||||||
|
SignupPage,
|
||||||
|
ScheduledDraftPage,
|
||||||
|
SystemConsolePage,
|
||||||
|
DraftPage,
|
||||||
|
} from './ui/pages';
|
||||||
|
|
||||||
|
export {TestArgs, ScreenshotOptions} from './types';
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {getRandomId} from '@e2e-support/util';
|
|
||||||
import {Channel, ChannelType} from '@mattermost/types/channels';
|
import {Channel, ChannelType} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {getRandomId} from '@/util';
|
||||||
|
|
||||||
type ChannelInput = {
|
type ChannelInput = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
name: string;
|
name: string;
|
||||||
58
e2e-tests/playwright/lib/src/server/client.ts
Обычный файл
58
e2e-tests/playwright/lib/src/server/client.ts
Обычный файл
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {Client4} from '@mattermost/client';
|
||||||
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import {testConfig} from '@/test_config';
|
||||||
|
|
||||||
|
// Variable to hold cache
|
||||||
|
const clients: Record<string, ClientCache> = {};
|
||||||
|
|
||||||
|
export async function makeClient(
|
||||||
|
userRequest?: UserRequest,
|
||||||
|
opts: {useCache?: boolean; skipLog?: boolean} = {useCache: true, skipLog: false},
|
||||||
|
): Promise<ClientCache> {
|
||||||
|
const client = new Client4();
|
||||||
|
client.setUrl(testConfig.baseURL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!userRequest) {
|
||||||
|
return {client, user: null};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = userRequest.username + userRequest.password;
|
||||||
|
if (opts?.useCache && clients[cacheKey] != null) {
|
||||||
|
return clients[cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
const userProfile = await client.login(userRequest.username, userRequest.password);
|
||||||
|
const user = {...userProfile, password: userRequest.password};
|
||||||
|
|
||||||
|
if (opts?.useCache) {
|
||||||
|
clients[cacheKey] = {client, user};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {client, user};
|
||||||
|
} catch (err) {
|
||||||
|
if (!opts?.skipLog) {
|
||||||
|
// 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: Client4;
|
||||||
|
user: UserProfile | null;
|
||||||
|
};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import merge from 'deepmerge';
|
import merge from 'deepmerge';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AdminConfig,
|
AdminConfig,
|
||||||
ClusterSettings,
|
ClusterSettings,
|
||||||
@@ -15,7 +14,8 @@ import {
|
|||||||
ServiceSettings,
|
ServiceSettings,
|
||||||
TeamSettings,
|
TeamSettings,
|
||||||
} from '@mattermost/types/config';
|
} from '@mattermost/types/config';
|
||||||
import testConfig from '@e2e-test.config';
|
|
||||||
|
import {testConfig} from '@/test_config';
|
||||||
|
|
||||||
export function getOnPremServerConfig(): AdminConfig {
|
export function getOnPremServerConfig(): AdminConfig {
|
||||||
return merge<AdminConfig>(defaultServerConfig, onPremServerConfig() as AdminConfig);
|
return merge<AdminConfig>(defaultServerConfig, onPremServerConfig() as AdminConfig);
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
export {Client, makeClient} from './client';
|
export {makeClient} from './client';
|
||||||
export {createRandomChannel} from './channel';
|
export {createRandomChannel} from './channel';
|
||||||
export {getOnPremServerConfig} from './default_config';
|
export {getOnPremServerConfig} from './default_config';
|
||||||
export {initSetup, getAdminClient} from './init';
|
export {initSetup, getAdminClient} from './init';
|
||||||
|
export {createRandomPost} from './post';
|
||||||
export {createRandomTeam} from './team';
|
export {createRandomTeam} from './team';
|
||||||
export {createRandomUser, getDefaultAdminUser} from './user';
|
export {createRandomUser, getDefaultAdminUser} from './user';
|
||||||
@@ -2,16 +2,16 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect} from '@playwright/test';
|
import {expect} from '@playwright/test';
|
||||||
|
|
||||||
import {PreferenceType} from '@mattermost/types/preferences';
|
import {PreferenceType} from '@mattermost/types/preferences';
|
||||||
import testConfig from '@e2e-test.config';
|
|
||||||
import {getFileDataFromAsset} from '@e2e-support/file';
|
|
||||||
|
|
||||||
import {makeClient} from '.';
|
import {makeClient} from './client';
|
||||||
import {getOnPremServerConfig} from './default_config';
|
import {getOnPremServerConfig} from './default_config';
|
||||||
import {createRandomTeam} from './team';
|
import {createRandomTeam} from './team';
|
||||||
import {createRandomUser} from './user';
|
import {createRandomUser} from './user';
|
||||||
|
|
||||||
|
import {getFileFromCommonAsset} from '@/file';
|
||||||
|
import {testConfig} from '@/test_config';
|
||||||
|
|
||||||
export async function initSetup({
|
export async function initSetup({
|
||||||
userPrefix = 'user',
|
userPrefix = 'user',
|
||||||
teamPrefix = {name: 'team', displayName: 'Team'},
|
teamPrefix = {name: 'team', displayName: 'Team'},
|
||||||
@@ -30,7 +30,7 @@ export async function initSetup({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset server config
|
// Reset server config
|
||||||
const adminConfig = await adminClient.updateConfig(getOnPremServerConfig());
|
const adminConfig = await adminClient.updateConfig(getOnPremServerConfig() as any);
|
||||||
|
|
||||||
// Create new team
|
// Create new team
|
||||||
const team = await adminClient.createTeam(createRandomTeam(teamPrefix.name, teamPrefix.displayName));
|
const team = await adminClient.createTeam(createRandomTeam(teamPrefix.name, teamPrefix.displayName));
|
||||||
@@ -45,7 +45,7 @@ export async function initSetup({
|
|||||||
const {client: userClient} = await makeClient(user);
|
const {client: userClient} = await makeClient(user);
|
||||||
|
|
||||||
if (withDefaultProfileImage) {
|
if (withDefaultProfileImage) {
|
||||||
const {file} = getFileDataFromAsset('mattermost-icon_128x128.png', 'image/png');
|
const file = getFileFromCommonAsset('mattermost-icon_128x128.png');
|
||||||
await userClient.uploadProfileImage(user.id, file);
|
await userClient.uploadProfileImage(user.id, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {Post, PostMetadata} from '@mattermost/types/posts';
|
import {Post, PostMetadata} from '@mattermost/types/posts';
|
||||||
import {getRandomId} from '@e2e-support/util';
|
|
||||||
|
import {getRandomId} from '@/util';
|
||||||
|
|
||||||
export function createRandomPost(post?: Partial<Post>): Post {
|
export function createRandomPost(post?: Partial<Post>): Post {
|
||||||
if (post && post.channel_id && post.user_id) {
|
if (post && post.channel_id && post.user_id) {
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {Team, TeamType} from '@mattermost/types/teams';
|
import {Team, TeamType} from '@mattermost/types/teams';
|
||||||
import {getRandomId} from '@e2e-support/util';
|
|
||||||
|
import {getRandomId} from '@/util';
|
||||||
|
|
||||||
export function createRandomTeam(name = 'team', displayName = 'Team', type: TeamType = 'O', unique = true): Team {
|
export function createRandomTeam(name = 'team', displayName = 'Team', type: TeamType = 'O', unique = true): Team {
|
||||||
const randomSuffix = getRandomId();
|
const randomSuffix = getRandomId();
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {UserProfile} from '@mattermost/types/users';
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
import {getRandomId} from '@e2e-support/util';
|
|
||||||
import testConfig from '@e2e-test.config';
|
import {getRandomId} from '@/util';
|
||||||
|
import {testConfig} from '@/test_config';
|
||||||
|
|
||||||
export function createRandomUser(prefix = 'user') {
|
export function createRandomUser(prefix = 'user') {
|
||||||
const randomId = getRandomId();
|
const randomId = getRandomId();
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {Locator, Page} from '@playwright/test';
|
import {Locator, Page} from '@playwright/test';
|
||||||
|
|
||||||
export {waitUntil} from 'async-wait-until';
|
export {waitUntil} from 'async-wait-until';
|
||||||
|
|
||||||
const visibilityHidden = 'visibility: hidden !important;';
|
const visibilityHidden = 'visibility: hidden !important;';
|
||||||
@@ -19,7 +18,3 @@ export async function waitForAnimationEnd(locator: Locator) {
|
|||||||
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished)),
|
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function moveMouseToCenter(page: Page) {
|
|
||||||
await page.mouse.move(0, 0);
|
|
||||||
}
|
|
||||||
63
e2e-tests/playwright/lib/src/test_config.ts
Обычный файл
63
e2e-tests/playwright/lib/src/test_config.ts
Обычный файл
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// All process.env should be defined here
|
||||||
|
export class TestConfig {
|
||||||
|
baseURL: string;
|
||||||
|
adminUsername: string;
|
||||||
|
adminPassword: string;
|
||||||
|
adminEmail: string;
|
||||||
|
ensurePluginsInstalled: string[];
|
||||||
|
haClusterEnabled: boolean;
|
||||||
|
haClusterNodeCount: number;
|
||||||
|
haClusterName: string;
|
||||||
|
pushNotificationServer: string;
|
||||||
|
resetBeforeTest: boolean;
|
||||||
|
isCI: boolean;
|
||||||
|
headless: boolean;
|
||||||
|
slowMo: number;
|
||||||
|
workers: number;
|
||||||
|
snapshotEnabled: boolean;
|
||||||
|
percyEnabled: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Server
|
||||||
|
this.baseURL = process.env.PW_BASE_URL || 'http://localhost:8065';
|
||||||
|
this.adminUsername = process.env.PW_ADMIN_USERNAME || 'sysadmin';
|
||||||
|
this.adminPassword = process.env.PW_ADMIN_PASSWORD || 'Sys@dmin-sample1';
|
||||||
|
this.adminEmail = process.env.PW_ADMIN_EMAIL || 'sysadmin@sample.mattermost.com';
|
||||||
|
this.ensurePluginsInstalled =
|
||||||
|
typeof process.env?.PW_ENSURE_PLUGINS_INSTALLED === 'string'
|
||||||
|
? process.env.PW_ENSURE_PLUGINS_INSTALLED.split(',').filter((plugin) => Boolean(plugin))
|
||||||
|
: [];
|
||||||
|
this.haClusterEnabled = parseBool(process.env.PW_HA_CLUSTER_ENABLED, false);
|
||||||
|
this.haClusterNodeCount = parseNumber(process.env.PW_HA_CLUSTER_NODE_COUNT, 2);
|
||||||
|
this.haClusterName = process.env.PW_HA_CLUSTER_NAME || 'mm_dev_cluster';
|
||||||
|
this.pushNotificationServer = process.env.PW_PUSH_NOTIFICATION_SERVER || 'https://push-test.mattermost.com';
|
||||||
|
this.resetBeforeTest = parseBool(process.env.PW_RESET_BEFORE_TEST, false);
|
||||||
|
// CI
|
||||||
|
this.isCI = !!process.env.CI;
|
||||||
|
// Playwright
|
||||||
|
this.headless = parseBool(process.env.PW_HEADLESS, true);
|
||||||
|
this.slowMo = parseNumber(process.env.PW_SLOWMO, 0);
|
||||||
|
this.workers = parseNumber(process.env.PW_WORKERS, 1);
|
||||||
|
// Visual tests
|
||||||
|
this.snapshotEnabled = parseBool(process.env.PW_SNAPSHOT_ENABLE, false);
|
||||||
|
this.percyEnabled = parseBool(process.env.PW_PERCY_ENABLE, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a singleton instance
|
||||||
|
export const testConfig = new TestConfig();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
import {test as base, Browser, Page} from '@playwright/test';
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {Browser, Page, test as base} from '@playwright/test';
|
||||||
import {AxeResults} from 'axe-core';
|
import {AxeResults} from 'axe-core';
|
||||||
import AxeBuilder from '@axe-core/playwright';
|
import {AxeBuilder} from '@axe-core/playwright';
|
||||||
|
|
||||||
import {TestBrowser} from './browser_context';
|
import {TestBrowser} from './browser_context';
|
||||||
import {
|
import {
|
||||||
|
ensureLicense,
|
||||||
shouldHaveCallsEnabled,
|
shouldHaveCallsEnabled,
|
||||||
shouldHaveFeatureFlag,
|
shouldHaveFeatureFlag,
|
||||||
shouldRunInLinux,
|
shouldRunInLinux,
|
||||||
ensureLicense,
|
|
||||||
skipIfNoLicense,
|
|
||||||
skipIfFeatureFlagNotSet,
|
skipIfFeatureFlagNotSet,
|
||||||
|
skipIfNoLicense,
|
||||||
} from './flag';
|
} from './flag';
|
||||||
import {initSetup, getAdminClient} from './server';
|
import {getBlobFromAsset, getFileFromAsset} from './file';
|
||||||
|
import {
|
||||||
|
createRandomChannel,
|
||||||
|
createRandomPost,
|
||||||
|
createRandomTeam,
|
||||||
|
createRandomUser,
|
||||||
|
getAdminClient,
|
||||||
|
initSetup,
|
||||||
|
} from './server';
|
||||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||||
import pages from './ui/pages';
|
import {pages} from './ui/pages';
|
||||||
import {matchSnapshot} from './visual';
|
import {matchSnapshot} from './visual';
|
||||||
import {stubNotification, waitForNotification} from './mock_browser_api';
|
import {stubNotification, waitForNotification} from './mock_browser_api';
|
||||||
import {duration} from './util';
|
import {duration, getRandomId, simpleEmailRe, wait} from './util';
|
||||||
|
|
||||||
export {expect} from '@playwright/test';
|
export {expect} from '@playwright/test';
|
||||||
|
|
||||||
type ExtendedFixtures = {
|
export type ExtendedFixtures = {
|
||||||
axe: AxeBuilderExtended;
|
axe: AxeBuilderExtended;
|
||||||
pw: PlaywrightExtended;
|
pw: PlaywrightExtended;
|
||||||
};
|
};
|
||||||
@@ -43,7 +54,7 @@ export const test = base.extend<ExtendedFixtures>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class PlaywrightExtended {
|
export class PlaywrightExtended {
|
||||||
// ./browser_context
|
// ./browser_context
|
||||||
readonly testBrowser;
|
readonly testBrowser;
|
||||||
|
|
||||||
@@ -55,6 +66,10 @@ class PlaywrightExtended {
|
|||||||
readonly skipIfNoLicense;
|
readonly skipIfNoLicense;
|
||||||
readonly skipIfFeatureFlagNotSet;
|
readonly skipIfFeatureFlagNotSet;
|
||||||
|
|
||||||
|
// ./file
|
||||||
|
readonly getBlobFromAsset;
|
||||||
|
readonly getFileFromAsset;
|
||||||
|
|
||||||
// ./server
|
// ./server
|
||||||
readonly getAdminClient;
|
readonly getAdminClient;
|
||||||
readonly initSetup;
|
readonly initSetup;
|
||||||
@@ -64,19 +79,27 @@ class PlaywrightExtended {
|
|||||||
readonly waitForAnimationEnd;
|
readonly waitForAnimationEnd;
|
||||||
readonly waitUntil;
|
readonly waitUntil;
|
||||||
|
|
||||||
|
// ./mock_browser_api
|
||||||
|
readonly stubNotification;
|
||||||
|
readonly waitForNotification;
|
||||||
|
|
||||||
|
// ./visual
|
||||||
|
readonly matchSnapshot;
|
||||||
|
|
||||||
|
// ./util
|
||||||
|
readonly duration;
|
||||||
|
readonly simpleEmailRe;
|
||||||
|
readonly wait;
|
||||||
|
|
||||||
|
// random
|
||||||
|
readonly random;
|
||||||
|
|
||||||
// unauthenticated page
|
// unauthenticated page
|
||||||
readonly loginPage;
|
readonly loginPage;
|
||||||
readonly landingLoginPage;
|
readonly landingLoginPage;
|
||||||
readonly signupPage;
|
readonly signupPage;
|
||||||
readonly resetPasswordPage;
|
readonly resetPasswordPage;
|
||||||
|
|
||||||
// ./visual
|
|
||||||
readonly matchSnapshot;
|
|
||||||
|
|
||||||
// ./mock_browser_api
|
|
||||||
readonly stubNotification;
|
|
||||||
readonly waitForNotification;
|
|
||||||
|
|
||||||
readonly hasSeenLandingPage;
|
readonly hasSeenLandingPage;
|
||||||
|
|
||||||
constructor(browser: Browser, page: Page, isMobile: boolean) {
|
constructor(browser: Browser, page: Page, isMobile: boolean) {
|
||||||
@@ -91,6 +114,10 @@ class PlaywrightExtended {
|
|||||||
this.skipIfNoLicense = skipIfNoLicense;
|
this.skipIfNoLicense = skipIfNoLicense;
|
||||||
this.skipIfFeatureFlagNotSet = skipIfFeatureFlagNotSet;
|
this.skipIfFeatureFlagNotSet = skipIfFeatureFlagNotSet;
|
||||||
|
|
||||||
|
// ./file
|
||||||
|
this.getBlobFromAsset = getBlobFromAsset;
|
||||||
|
this.getFileFromAsset = getFileFromAsset;
|
||||||
|
|
||||||
// ./server
|
// ./server
|
||||||
this.initSetup = initSetup;
|
this.initSetup = initSetup;
|
||||||
this.getAdminClient = getAdminClient;
|
this.getAdminClient = getAdminClient;
|
||||||
@@ -106,13 +133,26 @@ class PlaywrightExtended {
|
|||||||
this.signupPage = new pages.SignupPage(page);
|
this.signupPage = new pages.SignupPage(page);
|
||||||
this.resetPasswordPage = new pages.ResetPasswordPage(page);
|
this.resetPasswordPage = new pages.ResetPasswordPage(page);
|
||||||
|
|
||||||
// ./visual
|
|
||||||
this.matchSnapshot = matchSnapshot;
|
|
||||||
|
|
||||||
// ./mock_browser_api
|
// ./mock_browser_api
|
||||||
this.stubNotification = stubNotification;
|
this.stubNotification = stubNotification;
|
||||||
this.waitForNotification = waitForNotification;
|
this.waitForNotification = waitForNotification;
|
||||||
|
|
||||||
|
// ./visual
|
||||||
|
this.matchSnapshot = matchSnapshot;
|
||||||
|
|
||||||
|
// ./util
|
||||||
|
this.duration = duration;
|
||||||
|
this.wait = wait;
|
||||||
|
this.simpleEmailRe = simpleEmailRe;
|
||||||
|
|
||||||
|
this.random = {
|
||||||
|
id: getRandomId,
|
||||||
|
channel: createRandomChannel,
|
||||||
|
post: createRandomPost,
|
||||||
|
team: createRandomTeam,
|
||||||
|
user: createRandomUser,
|
||||||
|
};
|
||||||
|
|
||||||
this.hasSeenLandingPage = async () => {
|
this.hasSeenLandingPage = async () => {
|
||||||
// Visit the base URL to be able to set the localStorage
|
// Visit the base URL to be able to set the localStorage
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -121,7 +161,7 @@ class PlaywrightExtended {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AxeBuilderExtended {
|
export class AxeBuilderExtended {
|
||||||
readonly builder: (page: Page, options?: AxeBuilderOptions) => AxeBuilder;
|
readonly builder: (page: Page, options?: AxeBuilderOptions) => AxeBuilder;
|
||||||
|
|
||||||
// See https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags
|
// See https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags
|
||||||
@@ -9,29 +9,6 @@ export type TestArgs = {
|
|||||||
viewport?: ViewportSize | null;
|
viewport?: ViewportSize | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestConfig = {
|
|
||||||
// Server
|
|
||||||
baseURL: string;
|
|
||||||
adminUsername: string;
|
|
||||||
adminPassword: string;
|
|
||||||
adminEmail: string;
|
|
||||||
ensurePluginsInstalled: string[];
|
|
||||||
resetBeforeTest: boolean;
|
|
||||||
haClusterEnabled: boolean;
|
|
||||||
haClusterNodeCount: number;
|
|
||||||
haClusterName: string;
|
|
||||||
pushNotificationServer: string;
|
|
||||||
// CI
|
|
||||||
isCI: boolean;
|
|
||||||
// Playwright
|
|
||||||
headless: boolean;
|
|
||||||
slowMo: number;
|
|
||||||
workers: number;
|
|
||||||
// Visual tests
|
|
||||||
snapshotEnabled: boolean;
|
|
||||||
percyEnabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Based on https://github.com/microsoft/playwright/blob/d6ec1ae3994f127e38b866a231a34efc6a4cac0d/packages/playwright/types/test.d.ts#L5692-L5759
|
// Based on https://github.com/microsoft/playwright/blob/d6ec1ae3994f127e38b866a231a34efc6a4cac0d/packages/playwright/types/test.d.ts#L5692-L5759
|
||||||
export type ScreenshotOptions = {
|
export type ScreenshotOptions = {
|
||||||
/**
|
/**
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ChannelsAppBar {
|
export default class ChannelsAppBar {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -18,5 +18,3 @@ export default class ChannelsAppBar {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsAppBar};
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import ChannelsHeader from './header';
|
||||||
import {waitUntil} from '@e2e-support/test_action';
|
import ChannelsPostCreate from './post_create';
|
||||||
import {duration} from '@e2e-support/util';
|
import ChannelsPostEdit from './post_edit';
|
||||||
|
import ChannelsPost from './post';
|
||||||
|
|
||||||
|
import {duration} from '@/util';
|
||||||
|
import {waitUntil} from '@/test_action';
|
||||||
|
|
||||||
export default class ChannelsCenterView {
|
export default class ChannelsCenterView {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -25,12 +29,10 @@ export default class ChannelsCenterView {
|
|||||||
constructor(container: Locator) {
|
constructor(container: Locator) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.scheduledDraftChannelInfoMessageLocator = 'span:has-text("Message scheduled for")';
|
this.scheduledDraftChannelInfoMessageLocator = 'span:has-text("Message scheduled for")';
|
||||||
this.header = new components.ChannelsHeader(this.container.locator('.channel-header'));
|
this.header = new ChannelsHeader(this.container.locator('.channel-header'));
|
||||||
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('post-create'));
|
this.postCreate = new ChannelsPostCreate(container.getByTestId('post-create'));
|
||||||
this.scheduledDraftOptions = new components.ChannelsPostCreate(
|
this.scheduledDraftOptions = new ChannelsPostCreate(container.locator('#dropdown_send_post_options'));
|
||||||
container.locator('#dropdown_send_post_options'),
|
this.postEdit = new ChannelsPostEdit(container.locator('.post-edit__container'));
|
||||||
);
|
|
||||||
this.postEdit = new components.ChannelsPostEdit(container.locator('.post-edit__container'));
|
|
||||||
this.postBoxIndicator = container.locator('div.postBoxIndicator');
|
this.postBoxIndicator = container.locator('div.postBoxIndicator');
|
||||||
this.scheduledDraftChannelIcon = container.locator('#create_post i.icon-draft-indicator');
|
this.scheduledDraftChannelIcon = container.locator('#create_post i.icon-draft-indicator');
|
||||||
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
|
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
|
||||||
@@ -58,7 +60,7 @@ export default class ChannelsCenterView {
|
|||||||
async getFirstPost() {
|
async getFirstPost() {
|
||||||
const firstPost = this.container.getByTestId('postView').first();
|
const firstPost = this.container.getByTestId('postView').first();
|
||||||
await firstPost.waitFor();
|
await firstPost.waitFor();
|
||||||
return new components.ChannelsPost(firstPost);
|
return new ChannelsPost(firstPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +69,7 @@ export default class ChannelsCenterView {
|
|||||||
async getLastPost() {
|
async getLastPost() {
|
||||||
const lastPost = this.container.getByTestId('postView').last();
|
const lastPost = this.container.getByTestId('postView').last();
|
||||||
await lastPost.waitFor();
|
await lastPost.waitFor();
|
||||||
return new components.ChannelsPost(lastPost);
|
return new ChannelsPost(lastPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +91,7 @@ export default class ChannelsCenterView {
|
|||||||
async getNthPost(index: number) {
|
async getNthPost(index: number) {
|
||||||
const nthPost = this.container.getByTestId('postView').nth(index);
|
const nthPost = this.container.getByTestId('postView').nth(index);
|
||||||
await nthPost.waitFor();
|
await nthPost.waitFor();
|
||||||
return new components.ChannelsPost(nthPost);
|
return new ChannelsPost(nthPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,7 +101,7 @@ export default class ChannelsCenterView {
|
|||||||
async getPostById(id: string) {
|
async getPostById(id: string) {
|
||||||
const postById = this.container.locator(`[id="post_${id}"]`);
|
const postById = this.container.locator(`[id="post_${id}"]`);
|
||||||
await postById.waitFor();
|
await postById.waitFor();
|
||||||
return new components.ChannelsPost(postById);
|
return new ChannelsPost(postById);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
||||||
@@ -138,5 +140,3 @@ export default class ChannelsCenterView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsCenterView};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class DeletePostConfirmationDialog {
|
export default class DeletePostConfirmationDialog {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -36,5 +36,3 @@ export default class DeletePostConfirmationDialog {
|
|||||||
await this.confirmButton.click();
|
await this.confirmButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {DeletePostConfirmationDialog};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class DeletePostModal {
|
export default class DeletePostModal {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -24,5 +24,3 @@ export default class DeletePostModal {
|
|||||||
await expect(this.container).not.toBeVisible();
|
await expect(this.container).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {DeletePostModal};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class EmojiGifPicker {
|
export default class EmojiGifPicker {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -57,5 +57,3 @@ export default class EmojiGifPicker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {EmojiGifPicker};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class FindChannelsModal {
|
export default class FindChannelsModal {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -19,5 +19,3 @@ export default class FindChannelsModal {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {FindChannelsModal};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the generic confirm modal that is used in the app.
|
* This is the generic confirm modal that is used in the app.
|
||||||
@@ -41,5 +41,3 @@ export default class GenericConfirmModal {
|
|||||||
await expect(this.container).not.toBeVisible();
|
await expect(this.container).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {GenericConfirmModal};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ChannelsHeader {
|
export default class ChannelsHeader {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -14,5 +14,3 @@ export default class ChannelsHeader {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsHeader};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class MessagePriority {
|
export default class MessagePriority {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -74,5 +74,3 @@ export default class MessagePriority {
|
|||||||
await expect(standardOption.locator('svg.StyledCheckIcon-dFKfoY')).toBeVisible();
|
await expect(standardOption.locator('svg.StyledCheckIcon-dFKfoY')).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {MessagePriority};
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import PostMenu from './post_menu';
|
||||||
|
import ThreadFooter from './thread_footer';
|
||||||
|
|
||||||
export default class ChannelsPost {
|
export default class ChannelsPost {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -25,8 +26,8 @@ export default class ChannelsPost {
|
|||||||
|
|
||||||
this.removePostButton = container.locator('.post__remove');
|
this.removePostButton = container.locator('.post__remove');
|
||||||
|
|
||||||
this.postMenu = new components.PostMenu(container.locator('.post-menu'));
|
this.postMenu = new PostMenu(container.locator('.post-menu'));
|
||||||
this.threadFooter = new components.ThreadFooter(container.locator('.ThreadFooter'));
|
this.threadFooter = new ThreadFooter(container.locator('.ThreadFooter'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async toBeVisible() {
|
async toBeVisible() {
|
||||||
@@ -72,5 +73,3 @@ export default class ChannelsPost {
|
|||||||
await expect(this.container).toContainText(text);
|
await expect(this.container).toContainText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsPost};
|
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import {waitUntil} from '@e2e-support/test_action';
|
|
||||||
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
|
import {duration} from '@/util';
|
||||||
|
import {assetPath} from '@/file';
|
||||||
|
import {waitUntil} from '@/test_action';
|
||||||
|
|
||||||
export default class ChannelsPostCreate {
|
export default class ChannelsPostCreate {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -15,6 +19,7 @@ export default class ChannelsPostCreate {
|
|||||||
readonly scheduleDraftMessageButton;
|
readonly scheduleDraftMessageButton;
|
||||||
readonly priorityButton;
|
readonly priorityButton;
|
||||||
readonly suggestionList;
|
readonly suggestionList;
|
||||||
|
readonly filePreview;
|
||||||
|
|
||||||
constructor(container: Locator, isRHS = false) {
|
constructor(container: Locator, isRHS = false) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@@ -31,6 +36,7 @@ export default class ChannelsPostCreate {
|
|||||||
this.scheduleDraftMessageButton = container.getByLabel('Schedule message');
|
this.scheduleDraftMessageButton = container.getByLabel('Schedule message');
|
||||||
this.priorityButton = container.getByLabel('Message priority');
|
this.priorityButton = container.getByLabel('Message priority');
|
||||||
this.suggestionList = container.getByTestId('suggestionList');
|
this.suggestionList = container.getByTestId('suggestionList');
|
||||||
|
this.filePreview = container.locator('.file-preview__container');
|
||||||
}
|
}
|
||||||
|
|
||||||
async toBeVisible() {
|
async toBeVisible() {
|
||||||
@@ -101,18 +107,16 @@ export default class ChannelsPostCreate {
|
|||||||
await this.writeMessage(message);
|
await this.writeMessage(message);
|
||||||
|
|
||||||
if (files) {
|
if (files) {
|
||||||
const filePaths = files.map((file) => path.join(path.resolve(__dirname), '../../../asset', file));
|
const filePaths = files.map((file) => path.join(assetPath, file));
|
||||||
this.container.page().once('filechooser', async (fileChooser) => {
|
this.container.page().once('filechooser', async (fileChooser) => {
|
||||||
await fileChooser.setFiles(filePaths);
|
await fileChooser.setFiles(filePaths);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Click on the attachment button
|
||||||
await this.attachmentButton.click();
|
await this.attachmentButton.click();
|
||||||
|
|
||||||
// wait for all files to be uploaded
|
// Wait until the file preview is displayed
|
||||||
await waitUntil(async () => {
|
await this.waitUntilFilePreviewContains(files);
|
||||||
const attachment = await this.container.locator('.file-preview').count();
|
|
||||||
return attachment === files.length;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sendMessage();
|
await this.sendMessage();
|
||||||
@@ -122,6 +126,18 @@ export default class ChannelsPostCreate {
|
|||||||
await expect(this.emojiButton).toBeVisible();
|
await expect(this.emojiButton).toBeVisible();
|
||||||
await this.emojiButton.click();
|
await this.emojiButton.click();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export {ChannelsPostCreate};
|
async waitUntilFilePreviewContains(files: string[], timeout = duration.ten_sec) {
|
||||||
|
await waitUntil(
|
||||||
|
async () => {
|
||||||
|
const previews = this.filePreview.locator('.file-preview');
|
||||||
|
const details = this.filePreview.locator('.post-image__details');
|
||||||
|
|
||||||
|
const [previewsCount, detailsCount] = await Promise.all([previews.count(), details.count()]);
|
||||||
|
|
||||||
|
return previewsCount === files.length && detailsCount === files.length;
|
||||||
|
},
|
||||||
|
{timeout},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class PostDotMenu {
|
export default class PostDotMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -46,5 +46,3 @@ export default class PostDotMenu {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {PostDotMenu};
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import {components} from '@e2e-support/ui/components';
|
|
||||||
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
|
import DeletePostConfirmationDialog from './delete_post_confirmation_dialog';
|
||||||
|
import RestorePostConfirmationDialog from './restore_post_confirmation_dialog';
|
||||||
|
|
||||||
|
import {assetPath} from '@/file';
|
||||||
|
|
||||||
export default class ChannelsPostEdit {
|
export default class ChannelsPostEdit {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -23,10 +28,8 @@ export default class ChannelsPostEdit {
|
|||||||
this.attachmentButton = container.locator('#fileUploadButton');
|
this.attachmentButton = container.locator('#fileUploadButton');
|
||||||
this.emojiButton = container.getByLabel('select an emoji');
|
this.emojiButton = container.getByLabel('select an emoji');
|
||||||
this.sendMessageButton = container.locator('.save');
|
this.sendMessageButton = container.locator('.save');
|
||||||
this.deleteConfirmationDialog = new components.DeletePostConfirmationDialog(
|
this.deleteConfirmationDialog = new DeletePostConfirmationDialog(container.page().locator('#deletePostModal'));
|
||||||
container.page().locator('#deletePostModal'),
|
this.restorePostConfirmationDialog = new RestorePostConfirmationDialog(
|
||||||
);
|
|
||||||
this.restorePostConfirmationDialog = new components.RestorePostConfirmationDialog(
|
|
||||||
container.page().locator('#restorePostModal'),
|
container.page().locator('#restorePostModal'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -51,7 +54,7 @@ export default class ChannelsPostEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addFiles(files: string[]) {
|
async addFiles(files: string[]) {
|
||||||
const filePaths = files.map((file) => path.join(path.resolve(__dirname), '../../../asset', file));
|
const filePaths = files.map((file) => path.join(assetPath, file));
|
||||||
this.container.page().once('filechooser', async (fileChooser) => {
|
this.container.page().once('filechooser', async (fileChooser) => {
|
||||||
await fileChooser.setFiles(filePaths);
|
await fileChooser.setFiles(filePaths);
|
||||||
});
|
});
|
||||||
@@ -90,5 +93,3 @@ export default class ChannelsPostEdit {
|
|||||||
await expect(this.container).toContainText(text);
|
await expect(this.container).toContainText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsPostEdit};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class PostMenu {
|
export default class PostMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -55,5 +55,3 @@ export default class PostMenu {
|
|||||||
await this.dotMenuButton.click();
|
await this.dotMenuButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {PostMenu};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class PostReminderMenu {
|
export default class PostReminderMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -28,5 +28,3 @@ export default class PostReminderMenu {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {PostReminderMenu};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class RestorePostConfirmationDialog {
|
export default class RestorePostConfirmationDialog {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -30,5 +30,3 @@ export default class RestorePostConfirmationDialog {
|
|||||||
await this.confirmButton.click();
|
await this.confirmButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {RestorePostConfirmationDialog};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ScheduledDraftMenu {
|
export default class ScheduledDraftMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -22,5 +22,3 @@ export default class ScheduledDraftMenu {
|
|||||||
await this.scheduleDraftMessageCustomTimeOption.click();
|
await this.scheduleDraftMessageCustomTimeOption.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ScheduledDraftMenu};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ScheduledDraftModal {
|
export default class ScheduledDraftModal {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -93,5 +93,3 @@ export default class ScheduledDraftModal {
|
|||||||
return pacificTime;
|
return pacificTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ScheduledDraftModal};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class SearchPopover {
|
export default class SearchPopover {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -43,5 +43,3 @@ export default class SearchPopover {
|
|||||||
return this.searchHints.locator('.suggestion--selected');
|
return this.searchHints.locator('.suggestion--selected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SearchPopover};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
type NotificationSettingsSection = 'keysWithHighlight' | 'keysWithNotification';
|
type NotificationSettingsSection = 'keysWithHighlight' | 'keysWithNotification';
|
||||||
|
|
||||||
@@ -52,5 +52,3 @@ export default class NotificationsSettings {
|
|||||||
await this.container.getByText('Save').click();
|
await this.container.getByText('Save').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {NotificationsSettings};
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {NotificationsSettings} from './notification_settings';
|
import NotificationsSettings from './notification_settings';
|
||||||
|
|
||||||
export default class SettingsModal {
|
export default class SettingsModal {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -35,5 +35,3 @@ export default class SettingsModal {
|
|||||||
await expect(this.container).not.toBeVisible();
|
await expect(this.container).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SettingsModal};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ChannelsSidebarLeft {
|
export default class ChannelsSidebarLeft {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -56,5 +56,3 @@ export default class ChannelsSidebarLeft {
|
|||||||
await expect(channel).not.toBeVisible();
|
await expect(channel).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsSidebarLeft};
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import ChannelsPostCreate from './post_create';
|
||||||
|
import ChannelsPostEdit from './post_edit';
|
||||||
|
import ChannelsPost from './post';
|
||||||
|
|
||||||
export default class ChannelsSidebarRight {
|
export default class ChannelsSidebarRight {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -28,11 +30,11 @@ export default class ChannelsSidebarRight {
|
|||||||
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
||||||
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
|
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
|
||||||
this.rhsPostBody = container.locator('.post-message__text');
|
this.rhsPostBody = container.locator('.post-message__text');
|
||||||
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('comment-create'), true);
|
this.postCreate = new ChannelsPostCreate(container.getByTestId('comment-create'), true);
|
||||||
this.closeButton = container.locator('.sidebar--right__close');
|
this.closeButton = container.locator('.sidebar--right__close');
|
||||||
|
|
||||||
this.editTextbox = container.locator('#edit_textbox');
|
this.editTextbox = container.locator('#edit_textbox');
|
||||||
this.postEdit = new components.ChannelsPostEdit(container.locator('.post-edit__container'));
|
this.postEdit = new ChannelsPostEdit(container.locator('.post-edit__container'));
|
||||||
this.currentVersionEditedPosttext = (postID: any) => container.locator(`#rhsPostMessageText_${postID} p`);
|
this.currentVersionEditedPosttext = (postID: any) => container.locator(`#rhsPostMessageText_${postID} p`);
|
||||||
this.restorePreviousPostVersionIcon = container.locator(
|
this.restorePreviousPostVersionIcon = container.locator(
|
||||||
'button[aria-label="Select to restore an old message."]',
|
'button[aria-label="Select to restore an old message."]',
|
||||||
@@ -50,7 +52,7 @@ export default class ChannelsSidebarRight {
|
|||||||
async getPostById(postId: string) {
|
async getPostById(postId: string) {
|
||||||
const post = this.container.locator(`[id="rhsPost_${postId}"]`);
|
const post = this.container.locator(`[id="rhsPost_${postId}"]`);
|
||||||
await post.waitFor();
|
await post.waitFor();
|
||||||
return new components.ChannelsPost(post);
|
return new ChannelsPost(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,13 +61,13 @@ export default class ChannelsSidebarRight {
|
|||||||
async getLastPost() {
|
async getLastPost() {
|
||||||
const post = this.container.getByTestId('rhsPostView').last();
|
const post = this.container.getByTestId('rhsPostView').last();
|
||||||
await post.waitFor();
|
await post.waitFor();
|
||||||
return new components.ChannelsPost(post);
|
return new ChannelsPost(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFirstPost() {
|
async getFirstPost() {
|
||||||
const post = this.container.getByTestId('rhsPostView').first();
|
const post = this.container.getByTestId('rhsPostView').first();
|
||||||
await post.waitFor();
|
await post.waitFor();
|
||||||
return new components.ChannelsPost(post);
|
return new ChannelsPost(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,5 +98,3 @@ export default class ChannelsSidebarRight {
|
|||||||
await this.restorePreviousPostVersionIcon.click();
|
await this.restorePreviousPostVersionIcon.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsSidebarRight};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ThreadFooter {
|
export default class ThreadFooter {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -26,5 +26,3 @@ export default class ThreadFooter {
|
|||||||
await this.replyButton.click();
|
await this.replyButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ThreadFooter};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class UserProfilePopover {
|
export default class UserProfilePopover {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -18,5 +18,3 @@ export default class UserProfilePopover {
|
|||||||
await this.container.getByLabel('Close user profile popover').click();
|
await this.container.getByLabel('Close user profile popover').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {UserProfilePopover};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class Footer {
|
export default class Footer {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -26,5 +26,3 @@ export default class Footer {
|
|||||||
await expect(this.copyright).toBeVisible();
|
await expect(this.copyright).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Footer};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class GlobalHeader {
|
export default class GlobalHeader {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -49,5 +49,3 @@ export default class GlobalHeader {
|
|||||||
await this.searchBox.getByTestId('searchBoxClose').click();
|
await this.searchBox.getByTestId('searchBoxClose').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {GlobalHeader};
|
|
||||||
96
e2e-tests/playwright/lib/src/ui/components/index.ts
Обычный файл
96
e2e-tests/playwright/lib/src/ui/components/index.ts
Обычный файл
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import ChannelsHeader from './channels/header';
|
||||||
|
import ChannelsAppBar from './channels/app_bar';
|
||||||
|
import ChannelsPostCreate from './channels/post_create';
|
||||||
|
import ChannelsPost from './channels/post';
|
||||||
|
import ChannelsCenterView from './channels/center_view';
|
||||||
|
import ChannelsSidebarLeft from './channels/sidebar_left';
|
||||||
|
import ChannelsSidebarRight from './channels/sidebar_right';
|
||||||
|
import DeletePostModal from './channels/delete_post_modal';
|
||||||
|
import FindChannelsModal from './channels/find_channels_modal';
|
||||||
|
import SettingsModal from './channels/settings/settings_modal';
|
||||||
|
import Footer from './footer';
|
||||||
|
import GlobalHeader from './global_header';
|
||||||
|
import SearchPopover from './channels/search_popover';
|
||||||
|
import MainHeader from './main_header';
|
||||||
|
import PostDotMenu from './channels/post_dot_menu';
|
||||||
|
import PostReminderMenu from './channels/post_reminder_menu';
|
||||||
|
import PostMenu from './channels/post_menu';
|
||||||
|
import ThreadFooter from './channels/thread_footer';
|
||||||
|
import EmojiGifPicker from './channels/emoji_gif_picker';
|
||||||
|
import GenericConfirmModal from './channels/generic_confirm_modal';
|
||||||
|
import MessagePriority from './channels/message_priority';
|
||||||
|
import ScheduledDraftMenu from './channels/scheduled_draft_menu';
|
||||||
|
import ScheduledDraftModal from './channels/scheduled_draft_modal';
|
||||||
|
import UserProfilePopover from './channels/user_profile_popover';
|
||||||
|
import SystemConsoleSidebar from './system_console/sidebar';
|
||||||
|
import SystemConsoleNavbar from './system_console/navbar';
|
||||||
|
import SystemUsers from './system_console/sections/system_users/system_users';
|
||||||
|
import SystemUsersFilterPopover from './system_console/sections/system_users/filter_popover';
|
||||||
|
import SystemUsersFilterMenu from './system_console/sections/system_users/filter_menu';
|
||||||
|
import SystemUsersColumnToggleMenu from './system_console/sections/system_users/column_toggle_menu';
|
||||||
|
import ChannelsPostEdit from './channels/post_edit';
|
||||||
|
import DeletePostConfirmationDialog from './channels/delete_post_confirmation_dialog';
|
||||||
|
import RestorePostConfirmationDialog from './channels/restore_post_confirmation_dialog';
|
||||||
|
import SystemConsoleFeatureDiscovery from './system_console/sections/system_users/feature_discovery';
|
||||||
|
import SystemConsoleMobileSecurity from './system_console/sections/system_users/mobile_security';
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
GlobalHeader,
|
||||||
|
SearchPopover,
|
||||||
|
ChannelsCenterView,
|
||||||
|
ChannelsSidebarLeft,
|
||||||
|
ChannelsSidebarRight,
|
||||||
|
ChannelsAppBar,
|
||||||
|
ChannelsHeader,
|
||||||
|
ChannelsPostCreate,
|
||||||
|
ChannelsPostEdit,
|
||||||
|
ChannelsPost,
|
||||||
|
FindChannelsModal,
|
||||||
|
DeletePostModal,
|
||||||
|
SettingsModal,
|
||||||
|
PostDotMenu,
|
||||||
|
PostMenu,
|
||||||
|
ThreadFooter,
|
||||||
|
Footer,
|
||||||
|
MainHeader,
|
||||||
|
PostReminderMenu,
|
||||||
|
EmojiGifPicker,
|
||||||
|
GenericConfirmModal,
|
||||||
|
ScheduledDraftMenu,
|
||||||
|
ScheduledDraftModal,
|
||||||
|
SystemConsoleSidebar,
|
||||||
|
SystemConsoleNavbar,
|
||||||
|
SystemUsers,
|
||||||
|
SystemUsersFilterPopover,
|
||||||
|
SystemUsersFilterMenu,
|
||||||
|
SystemUsersColumnToggleMenu,
|
||||||
|
SystemConsoleFeatureDiscovery,
|
||||||
|
SystemConsoleMobileSecurity,
|
||||||
|
MessagePriority,
|
||||||
|
UserProfilePopover,
|
||||||
|
DeletePostConfirmationDialog,
|
||||||
|
RestorePostConfirmationDialog,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
components,
|
||||||
|
GlobalHeader,
|
||||||
|
ChannelsCenterView,
|
||||||
|
ChannelsSidebarLeft,
|
||||||
|
ChannelsSidebarRight,
|
||||||
|
ChannelsAppBar,
|
||||||
|
ChannelsHeader,
|
||||||
|
ChannelsPostCreate,
|
||||||
|
ChannelsPostEdit,
|
||||||
|
ChannelsPost,
|
||||||
|
FindChannelsModal,
|
||||||
|
DeletePostModal,
|
||||||
|
PostDotMenu,
|
||||||
|
PostMenu,
|
||||||
|
ThreadFooter,
|
||||||
|
MessagePriority,
|
||||||
|
DeletePostConfirmationDialog,
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class MainHeader {
|
export default class MainHeader {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -20,5 +20,3 @@ export default class MainHeader {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {MainHeader};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class SystemConsoleNavbar {
|
export default class SystemConsoleNavbar {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -14,5 +14,3 @@ export default class SystemConsoleNavbar {
|
|||||||
await expect(this.container).toBeVisible();
|
await expect(this.container).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemConsoleNavbar};
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
class SystemUsersColumnToggleMenu {
|
export default class SystemUsersColumnToggleMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
|
|
||||||
constructor(container: Locator) {
|
constructor(container: Locator) {
|
||||||
@@ -48,5 +48,3 @@ class SystemUsersColumnToggleMenu {
|
|||||||
await expect(this.container).not.toBeVisible();
|
await expect(this.container).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemUsersColumnToggleMenu};
|
|
||||||
@@ -21,5 +21,3 @@ export default class FeatureDiscovery {
|
|||||||
await expect(this.container.getByTestId('featureDiscovery_title')).toHaveText(title);
|
await expect(this.container.getByTestId('featureDiscovery_title')).toHaveText(title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {FeatureDiscovery};
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dropdown menu which appears for both Role and Status filter.
|
* The dropdown menu which appears for both Role and Status filter.
|
||||||
*/
|
*/
|
||||||
class SystemUsersFilterMenu {
|
export default class SystemUsersFilterMenu {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
|
|
||||||
constructor(container: Locator) {
|
constructor(container: Locator) {
|
||||||
@@ -42,5 +42,3 @@ class SystemUsersFilterMenu {
|
|||||||
await this.container.press('Escape');
|
await this.container.press('Escape');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemUsersFilterMenu};
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
class SystemUsersFilterPopover {
|
export default class SystemUsersFilterPopover {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
|
|
||||||
readonly teamMenuInput: Locator;
|
readonly teamMenuInput: Locator;
|
||||||
@@ -66,5 +66,3 @@ class SystemUsersFilterPopover {
|
|||||||
await expect(this.container).not.toBeVisible();
|
await expect(this.container).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemUsersFilterPopover};
|
|
||||||
@@ -77,5 +77,3 @@ export default class MobileSecurity {
|
|||||||
await this.saveButton.click();
|
await this.saveButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {MobileSecurity};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System Console -> User Management -> Users
|
* System Console -> User Management -> Users
|
||||||
@@ -130,5 +130,3 @@ export default class SystemUsers {
|
|||||||
await expect(foundUser).not.toBeVisible();
|
await expect(foundUser).not.toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemUsers};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Locator} from '@playwright/test';
|
import {Locator, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class SystemConsoleSidebar {
|
export default class SystemConsoleSidebar {
|
||||||
readonly container: Locator;
|
readonly container: Locator;
|
||||||
@@ -37,5 +37,3 @@ export default class SystemConsoleSidebar {
|
|||||||
await this.searchInput.fill(itemName);
|
await this.searchInput.fill(itemName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemConsoleSidebar};
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import {Page} from '@playwright/test';
|
import {Page} from '@playwright/test';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import {components} from '@/ui/components';
|
||||||
|
|
||||||
export default class ChannelsPage {
|
export default class ChannelsPage {
|
||||||
readonly channels = 'Channels';
|
readonly channels = 'Channels';
|
||||||
@@ -19,6 +19,7 @@ export default class ChannelsPage {
|
|||||||
readonly sidebarRight;
|
readonly sidebarRight;
|
||||||
readonly appBar;
|
readonly appBar;
|
||||||
readonly userProfilePopover;
|
readonly userProfilePopover;
|
||||||
|
readonly messagePriority;
|
||||||
|
|
||||||
readonly findChannelsModal;
|
readonly findChannelsModal;
|
||||||
readonly deletePostModal;
|
readonly deletePostModal;
|
||||||
@@ -40,6 +41,7 @@ export default class ChannelsPage {
|
|||||||
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
||||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||||
|
this.messagePriority = new components.MessagePriority(page.locator('body'));
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
||||||
@@ -88,5 +90,3 @@ export default class ChannelsPage {
|
|||||||
await this.centerView.postCreate.postMessage(message);
|
await this.centerView.postCreate.postMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ChannelsPage};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class DraftPage {
|
export default class DraftPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -124,5 +124,3 @@ export default class DraftPage {
|
|||||||
await this.confirmbutton.click();
|
await this.confirmbutton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {DraftPage};
|
|
||||||
34
e2e-tests/playwright/lib/src/ui/pages/index.ts
Обычный файл
34
e2e-tests/playwright/lib/src/ui/pages/index.ts
Обычный файл
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import ChannelsPage from './channels';
|
||||||
|
import LandingLoginPage from './landing_login';
|
||||||
|
import LoginPage from './login';
|
||||||
|
import ResetPasswordPage from './reset_password';
|
||||||
|
import SignupPage from './signup';
|
||||||
|
import SystemConsolePage from './system_console';
|
||||||
|
import ScheduledDraftPage from './scheduled_draft';
|
||||||
|
import DraftPage from './drafts';
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
ChannelsPage,
|
||||||
|
LandingLoginPage,
|
||||||
|
LoginPage,
|
||||||
|
ResetPasswordPage,
|
||||||
|
SignupPage,
|
||||||
|
ScheduledDraftPage,
|
||||||
|
SystemConsolePage,
|
||||||
|
DraftPage,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
pages,
|
||||||
|
ChannelsPage,
|
||||||
|
LandingLoginPage,
|
||||||
|
LoginPage,
|
||||||
|
ResetPasswordPage,
|
||||||
|
SignupPage,
|
||||||
|
ScheduledDraftPage,
|
||||||
|
SystemConsolePage,
|
||||||
|
DraftPage,
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class LandingLoginPage {
|
export default class LandingLoginPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -37,5 +37,3 @@ export default class LandingLoginPage {
|
|||||||
await this.page.goto('/landing#/login');
|
await this.page.goto('/landing#/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {LandingLoginPage};
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {UserProfile} from '@mattermost/types/users';
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import {components} from '@/ui/components';
|
||||||
|
|
||||||
export default class LoginPage {
|
export default class LoginPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -67,5 +66,3 @@ export default class LoginPage {
|
|||||||
await Promise.all([this.page.waitForNavigation(), this.signInButton.click()]);
|
await Promise.all([this.page.waitForNavigation(), this.signInButton.click()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {LoginPage};
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {components} from '@e2e-support/ui/components';
|
import {components} from '@/ui/components';
|
||||||
|
|
||||||
export default class ResetPasswordPage {
|
export default class ResetPasswordPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -47,5 +47,3 @@ export default class ResetPasswordPage {
|
|||||||
await Promise.all([this.page.waitForNavigation(), this.resetButton.click()]);
|
await Promise.all([this.page.waitForNavigation(), this.resetButton.click()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ResetPasswordPage};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
export default class ScheduledDraftPage {
|
export default class ScheduledDraftPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -157,5 +157,3 @@ export default class ScheduledDraftPage {
|
|||||||
await this.copyIcon.click();
|
await this.copyIcon.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ScheduledDraftPage};
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, Page} from '@playwright/test';
|
import {Page, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {duration, wait} from '@e2e-support/util';
|
import {duration, wait} from '@/util';
|
||||||
import {components} from '@e2e-support/ui/components';
|
import {components} from '@/ui/components';
|
||||||
|
|
||||||
export default class SignupPage {
|
export default class SignupPage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
@@ -86,5 +86,3 @@ export default class SignupPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SignupPage};
|
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {Page} from '@playwright/test';
|
import {Page} from '@playwright/test';
|
||||||
import {components} from '../components';
|
|
||||||
|
|
||||||
class SystemConsolePage {
|
import {components} from '@/ui/components';
|
||||||
|
|
||||||
|
export default class SystemConsolePage {
|
||||||
readonly page: Page;
|
readonly page: Page;
|
||||||
|
|
||||||
readonly sidebar;
|
readonly sidebar;
|
||||||
@@ -39,10 +40,10 @@ class SystemConsolePage {
|
|||||||
|
|
||||||
// Sections and sub-sections
|
// Sections and sub-sections
|
||||||
this.systemUsers = new components.SystemUsers(page.getByTestId('systemUsersSection'));
|
this.systemUsers = new components.SystemUsers(page.getByTestId('systemUsersSection'));
|
||||||
this.mobileSecurity = new components.MobileSecurity(
|
this.mobileSecurity = new components.SystemConsoleMobileSecurity(
|
||||||
page.getByTestId('sysconsole_section_MobileSecuritySettings'),
|
page.getByTestId('sysconsole_section_MobileSecuritySettings'),
|
||||||
);
|
);
|
||||||
this.featureDiscovery = new components.FeatureDiscovery(page.getByTestId('featureDiscovery'));
|
this.featureDiscovery = new components.SystemConsoleFeatureDiscovery(page.getByTestId('featureDiscovery'));
|
||||||
|
|
||||||
// Menus & Popovers
|
// Menus & Popovers
|
||||||
this.systemUsersFilterPopover = new components.SystemUsersFilterPopover(
|
this.systemUsersFilterPopover = new components.SystemUsersFilterPopover(
|
||||||
@@ -84,5 +85,3 @@ class SystemConsolePage {
|
|||||||
await this.saveChangesModal.container.locator('button.btn-primary:has-text("Reset")').click();
|
await this.saveChangesModal.container.locator('button.btn-primary:has-text("Reset")').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {SystemConsolePage};
|
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import {expect, TestInfo} from '@playwright/test';
|
import {TestInfo, expect} from '@playwright/test';
|
||||||
|
|
||||||
import {duration, illegalRe, wait} from '@e2e-support/util';
|
|
||||||
import testConfig from '@e2e-test.config';
|
|
||||||
import {ScreenshotOptions, TestArgs} from '@e2e-types';
|
|
||||||
|
|
||||||
import snapshotWithPercy from './percy';
|
import snapshotWithPercy from './percy';
|
||||||
|
|
||||||
|
import {duration, illegalRe, wait} from '@/util';
|
||||||
|
import {testConfig} from '@/test_config';
|
||||||
|
import {ScreenshotOptions, TestArgs} from '@/types';
|
||||||
|
|
||||||
export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs, options: ScreenshotOptions = {}) {
|
export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs, options: ScreenshotOptions = {}) {
|
||||||
if (os.platform() !== 'linux') {
|
if (os.platform() !== 'linux') {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import testConfig from '@e2e-test.config';
|
import {testConfig} from '@/test_config';
|
||||||
import {TestArgs} from '@e2e-types';
|
import {TestArgs} from '@/types';
|
||||||
|
|
||||||
export default async function snapshotWithPercy(name: string, testArgs: TestArgs) {
|
export default async function snapshotWithPercy(name: string, testArgs: TestArgs) {
|
||||||
if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) {
|
if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) {
|
||||||
20
e2e-tests/playwright/lib/tsconfig.json
Обычный файл
20
e2e-tests/playwright/lib/tsconfig.json
Обычный файл
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"target": "ES2022",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "esnext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"baseUrl": "src",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
10101
e2e-tests/playwright/package-lock.json
сгенерированный
10101
e2e-tests/playwright/package-lock.json
сгенерированный
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,42 +1,34 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"name": "mattermost-playwright",
|
||||||
"test": "cross-env PW_SNAPSHOT_ENABLE=true playwright test",
|
"private": true,
|
||||||
"test:update-snapshots": "cross-env PW_SNAPSHOT_ENABLE=true playwright test --update-snapshots",
|
"workspaces": [
|
||||||
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=ipad",
|
"lib",
|
||||||
"tsc": "tsc -b",
|
"test"
|
||||||
"lint": "eslint .",
|
],
|
||||||
"prettier": "prettier . --check",
|
"scripts": {
|
||||||
"prettier:fix": "prettier --write .",
|
"postinstall": "npm run build",
|
||||||
"check": "npm run tsc && npm run lint && npm run prettier",
|
"build": "npm run build --workspaces --if-present",
|
||||||
"codegen": "cross-env playwright codegen $PW_BASE_URL",
|
"build:watch": "npm run build:watch --workspaces --if-present",
|
||||||
"playwright-ui": "cross-env playwright test --ui",
|
"clean": "rm -rf node_modules logs results test-results && npm run clean --workspaces --if-present",
|
||||||
"test-slomo": "cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
|
"tsc": "npm run tsc --workspaces --if-present",
|
||||||
"show-report": "npx playwright show-report",
|
"lint": "eslint .",
|
||||||
"postinstall": "script/post_install.sh"
|
"prettier": "prettier . --check",
|
||||||
},
|
"prettier:fix": "prettier --write .",
|
||||||
"dependencies": {
|
"check": "npm run lint && npm run prettier && npm run tsc",
|
||||||
"@axe-core/playwright": "4.10.1",
|
"test": "npm run test --workspace=test",
|
||||||
"@percy/cli": "1.30.6",
|
"test:ci": "npm run test:ci --workspace=test"
|
||||||
"@percy/playwright": "1.0.7",
|
},
|
||||||
"@playwright/test": "1.49.1",
|
"dependencies": {
|
||||||
"async-wait-until": "2.0.18",
|
"dayjs": "1.11.13",
|
||||||
"axe-core": "4.10.2",
|
"@playwright/test": "1.51.1"
|
||||||
"chalk": "4.1.2",
|
},
|
||||||
"dayjs": "1.11.13",
|
"devDependencies": {
|
||||||
"deepmerge": "4.3.1",
|
"@typescript-eslint/eslint-plugin": "8.28.0",
|
||||||
"dotenv": "16.4.7",
|
"eslint": "9.23.0",
|
||||||
"form-data-encoder": "4.0.2",
|
"eslint-import-resolver-typescript": "4.2.7",
|
||||||
"formdata-node": "6.0.3",
|
"eslint-plugin-header": "3.1.1",
|
||||||
"uuid": "11.0.5",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"zod": "3.24.1"
|
"prettier": "3.5.3",
|
||||||
},
|
"typescript": "5.7.3"
|
||||||
"devDependencies": {
|
}
|
||||||
"@types/uuid": "10.0.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "8.20.0",
|
|
||||||
"@typescript-eslint/parser": "8.20.0",
|
|
||||||
"cross-env": "7.0.3",
|
|
||||||
"eslint": "9.18.0",
|
|
||||||
"prettier": "3.4.2",
|
|
||||||
"typescript": "5.7.3"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
const duration = require('dayjs/plugin/duration');
|
const duration = require('dayjs/plugin/duration');
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
@@ -90,5 +94,5 @@ function generateWebhookBody() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let webhookBody = generateWebhookBody();
|
const webhookBody = generateWebhookBody();
|
||||||
process.stdout.write(JSON.stringify(webhookBody));
|
process.stdout.write(JSON.stringify(webhookBody));
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
|
|
||||||
const assetPath = path.resolve(__dirname, 'asset');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads file data and creates a File object.
|
|
||||||
* @param filePath - The path to the file.
|
|
||||||
* @param mimeType - The MIME type of the file.
|
|
||||||
* @returns A File object containing the file data.
|
|
||||||
* @throws If the file does not exist.
|
|
||||||
*/
|
|
||||||
export function getFileData(filePath: string, mimeType: string): File {
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
throw new Error(`File not found at path: ${filePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileName = path.basename(filePath);
|
|
||||||
const fileBuffer = fs.readFileSync(filePath);
|
|
||||||
|
|
||||||
return new File([fileBuffer], fileName, {type: mimeType});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads file data from the "asset" directory and creates a File object.
|
|
||||||
* @param filename - The name of the file in the "asset" directory.
|
|
||||||
* @param mimeType - The MIME type of the file.
|
|
||||||
* @returns An object containing a File object and the filename.
|
|
||||||
*/
|
|
||||||
export function getFileDataFromAsset(filename: string, mimeType: string) {
|
|
||||||
const filePath = path.join(assetPath, filename);
|
|
||||||
|
|
||||||
return {file: getFileData(filePath, mimeType), filename: path.basename(filePath)};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads file data and creates a Blob object.
|
|
||||||
* @param filePath - The path to the file.
|
|
||||||
* @param mimeType - The MIME type of the file.
|
|
||||||
* @returns A Blob object containing the file data.
|
|
||||||
* @throws If the file does not exist.
|
|
||||||
*/
|
|
||||||
export function getBlobData(filePath: string, mimeType: string): Blob {
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
throw new Error(`File not found at path: ${filePath}`);
|
|
||||||
}
|
|
||||||
const fileBuffer = fs.readFileSync(filePath);
|
|
||||||
|
|
||||||
return new Blob([fileBuffer], {type: mimeType});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads file data from the "asset" directory and creates a Blob object.
|
|
||||||
* @param filename - The name of the file in the "asset" directory.
|
|
||||||
* @param mimeType - The MIME type of the file.
|
|
||||||
* @returns An object containing a Blob object and the filename.
|
|
||||||
*/
|
|
||||||
export function getBlobDataFromAsset(filename: string, mimeType: string) {
|
|
||||||
const filePath = path.join(assetPath, filename);
|
|
||||||
|
|
||||||
return {blob: getBlobData(filePath, mimeType), filename: path.basename(filePath)};
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
// This is based on "webapp/platform/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 stream from 'stream';
|
|
||||||
|
|
||||||
import {FormData} from 'formdata-node';
|
|
||||||
import {FormDataEncoder} from 'form-data-encoder';
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
const encoder = new FormDataEncoder(formData);
|
|
||||||
|
|
||||||
return {
|
|
||||||
method: 'post',
|
|
||||||
body: stream.Readable.from(encoder.encode()),
|
|
||||||
headers: encoder.headers,
|
|
||||||
duplex: 'half',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
readFileAsFormData = (filePath: string, key: string, additionalFields: Record<string, any> = {}) => {
|
|
||||||
const fileData = fs.readFileSync(filePath);
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append(key, new Blob([fileData]), path.basename(filePath));
|
|
||||||
|
|
||||||
// Append additional fields if provided
|
|
||||||
for (const [field, value] of Object.entries(additionalFields)) {
|
|
||||||
formData.append(field, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadProfileImageX = (userId: string, filePath: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'image');
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<StatusOK>(`${this.getUserRoute(userId)}/image`, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
setTeamIconX = (teamId: string, filePath: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'image');
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<StatusOK>(`${this.getTeamRoute(teamId)}/image`, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
createCustomEmojiX = (emoji: CustomEmoji, filePath: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'image', {emoji: JSON.stringify(emoji)});
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<CustomEmoji>(`${this.getEmojisRoute()}`, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadBrandImageX = (filePath: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'image');
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<StatusOK>(`${this.getBrandRoute()}/image`, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadCertificateX = (filePath: string, route: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'certificate');
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<StatusOK>(route, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadPublicSamlCertificateX = (filePath: string) => {
|
|
||||||
return this.uploadCertificateX(filePath, `${this.getBaseRoute()}/saml/certificate/public`);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadPrivateSamlCertificateX = (filePath: string) => {
|
|
||||||
return this.uploadCertificateX(filePath, `${this.getBaseRoute()}/saml/certificate/private`);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadPublicLdapCertificateX = (filePath: string) => {
|
|
||||||
return this.uploadCertificateX(filePath, `${this.getBaseRoute()}/ldap/certificate/public`);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadPrivateLdapCertificateX = (filePath: string) => {
|
|
||||||
return this.uploadCertificateX(filePath, `${this.getBaseRoute()}/ldap/certificate/private`);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadIdpSamlCertificateX = (filePath: string) => {
|
|
||||||
return this.uploadCertificateX(filePath, `${this.getBaseRoute()}/saml/certificate/idp`);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadLicenseX = (filePath: string) => {
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'license');
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<License>(`${this.getBaseRoute()}/license`, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadPluginX = async (filePath: string, force = false) => {
|
|
||||||
const additionalFields = force ? {force: 'true'} : {};
|
|
||||||
const formData = this.readFileAsFormData(filePath, 'plugin', additionalFields);
|
|
||||||
const options = this.getFormDataOptions(formData);
|
|
||||||
return this.doFetch<PluginManifest>(this.getPluginsRoute(), options);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variable to hold cache
|
|
||||||
const clients: Record<string, ClientCache> = {};
|
|
||||||
|
|
||||||
async function makeClient(
|
|
||||||
userRequest?: UserRequest,
|
|
||||||
opts: {useCache?: boolean; skipLog?: boolean} = {useCache: true, skipLog: false},
|
|
||||||
): Promise<ClientCache> {
|
|
||||||
const client = new Client();
|
|
||||||
client.setUrl(testConfig.baseURL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!userRequest) {
|
|
||||||
return {client, user: null};
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheKey = userRequest.username + userRequest.password;
|
|
||||||
if (opts?.useCache && clients[cacheKey] != null) {
|
|
||||||
return clients[cacheKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
const userProfile = await client.login(userRequest.username, userRequest.password);
|
|
||||||
const user = {...userProfile, password: userRequest.password};
|
|
||||||
|
|
||||||
if (opts?.useCache) {
|
|
||||||
clients[cacheKey] = {client, user};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {client, user};
|
|
||||||
} catch (err) {
|
|
||||||
if (!opts?.skipLog) {
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {Client, makeClient};
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {ChannelsHeader} from './channels/header';
|
|
||||||
import {ChannelsAppBar} from './channels/app_bar';
|
|
||||||
import {ChannelsPostCreate} from './channels/post_create';
|
|
||||||
import {ChannelsPost} from './channels/post';
|
|
||||||
import {ChannelsCenterView} from './channels/center_view';
|
|
||||||
import {ChannelsSidebarLeft} from './channels/sidebar_left';
|
|
||||||
import {ChannelsSidebarRight} from './channels/sidebar_right';
|
|
||||||
import {DeletePostModal} from './channels/delete_post_modal';
|
|
||||||
import {FindChannelsModal} from './channels/find_channels_modal';
|
|
||||||
import {SettingsModal} from './channels/settings/settings_modal';
|
|
||||||
import {Footer} from './footer';
|
|
||||||
import {GlobalHeader} from './global_header';
|
|
||||||
import {SearchPopover} from './channels/search_popover';
|
|
||||||
import {MainHeader} from './main_header';
|
|
||||||
import {PostDotMenu} from './channels/post_dot_menu';
|
|
||||||
import {PostReminderMenu} from './channels/post_reminder_menu';
|
|
||||||
import {PostMenu} from './channels/post_menu';
|
|
||||||
import {ThreadFooter} from './channels/thread_footer';
|
|
||||||
import {EmojiGifPicker} from './channels/emoji_gif_picker';
|
|
||||||
import {GenericConfirmModal} from './channels/generic_confirm_modal';
|
|
||||||
import {MessagePriority} from './channels/message_priority';
|
|
||||||
|
|
||||||
import {ScheduledDraftMenu} from './channels/scheduled_draft_menu';
|
|
||||||
import {ScheduledDraftModal} from './channels/scheduled_draft_modal';
|
|
||||||
import {UserProfilePopover} from './channels/user_profile_popover';
|
|
||||||
import {SystemConsoleSidebar} from './system_console/sidebar';
|
|
||||||
import {SystemConsoleNavbar} from './system_console/navbar';
|
|
||||||
|
|
||||||
import {SystemUsers} from './system_console/sections/system_users/system_users';
|
|
||||||
import {SystemUsersFilterPopover} from './system_console/sections/system_users/filter_popover';
|
|
||||||
import {SystemUsersFilterMenu} from './system_console/sections/system_users/filter_menu';
|
|
||||||
import {SystemUsersColumnToggleMenu} from './system_console/sections/system_users/column_toggle_menu';
|
|
||||||
import ChannelsPostEdit from '@e2e-support/ui/components/channels/post_edit';
|
|
||||||
import DeletePostConfirmationDialog from '@e2e-support/ui/components/channels/delete_post_confirmation_dialog';
|
|
||||||
import RestorePostConfirmationDialog from '@e2e-support/ui/components/channels/restore_post_confirmation_dialog';
|
|
||||||
import MobileSecurity from './system_console/sections/system_users/mobile_security';
|
|
||||||
import FeatureDiscovery from './system_console/sections/system_users/feature_discovery';
|
|
||||||
|
|
||||||
const components = {
|
|
||||||
GlobalHeader,
|
|
||||||
SearchPopover,
|
|
||||||
ChannelsCenterView,
|
|
||||||
ChannelsSidebarLeft,
|
|
||||||
ChannelsSidebarRight,
|
|
||||||
ChannelsAppBar,
|
|
||||||
ChannelsHeader,
|
|
||||||
ChannelsPostCreate,
|
|
||||||
ChannelsPostEdit,
|
|
||||||
ChannelsPost,
|
|
||||||
FindChannelsModal,
|
|
||||||
DeletePostModal,
|
|
||||||
SettingsModal,
|
|
||||||
PostDotMenu,
|
|
||||||
PostMenu,
|
|
||||||
ThreadFooter,
|
|
||||||
Footer,
|
|
||||||
MainHeader,
|
|
||||||
PostReminderMenu,
|
|
||||||
EmojiGifPicker,
|
|
||||||
GenericConfirmModal,
|
|
||||||
ScheduledDraftMenu,
|
|
||||||
ScheduledDraftModal,
|
|
||||||
SystemConsoleSidebar,
|
|
||||||
SystemConsoleNavbar,
|
|
||||||
SystemUsers,
|
|
||||||
SystemUsersFilterPopover,
|
|
||||||
SystemUsersFilterMenu,
|
|
||||||
SystemUsersColumnToggleMenu,
|
|
||||||
MessagePriority,
|
|
||||||
UserProfilePopover,
|
|
||||||
DeletePostConfirmationDialog,
|
|
||||||
RestorePostConfirmationDialog,
|
|
||||||
MobileSecurity,
|
|
||||||
FeatureDiscovery,
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
components,
|
|
||||||
GlobalHeader,
|
|
||||||
ChannelsCenterView,
|
|
||||||
ChannelsSidebarLeft,
|
|
||||||
ChannelsSidebarRight,
|
|
||||||
ChannelsAppBar,
|
|
||||||
ChannelsHeader,
|
|
||||||
ChannelsPostCreate,
|
|
||||||
ChannelsPostEdit,
|
|
||||||
ChannelsPost,
|
|
||||||
FindChannelsModal,
|
|
||||||
DeletePostModal,
|
|
||||||
PostDotMenu,
|
|
||||||
PostMenu,
|
|
||||||
ThreadFooter,
|
|
||||||
MessagePriority,
|
|
||||||
DeletePostConfirmationDialog,
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {ChannelsPage} from './channels';
|
|
||||||
import {LandingLoginPage} from './landing_login';
|
|
||||||
import {LoginPage} from './login';
|
|
||||||
import {ResetPasswordPage} from './reset_password';
|
|
||||||
import {SignupPage} from './signup';
|
|
||||||
import {SystemConsolePage} from './system_console';
|
|
||||||
import {ScheduledDraftPage} from './scheduled_draft';
|
|
||||||
import {DraftPage} from './drafts';
|
|
||||||
|
|
||||||
const pages = {
|
|
||||||
ChannelsPage,
|
|
||||||
LandingLoginPage,
|
|
||||||
LoginPage,
|
|
||||||
ResetPasswordPage,
|
|
||||||
SignupPage,
|
|
||||||
ScheduledDraftPage,
|
|
||||||
SystemConsolePage,
|
|
||||||
DraftPage,
|
|
||||||
};
|
|
||||||
|
|
||||||
export {ChannelsPage, LandingLoginPage, LoginPage, SignupPage, ScheduledDraftPage, DraftPage};
|
|
||||||
export default pages;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
|
|
||||||
import {TestConfig} from '@e2e-types';
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
ensurePluginsInstalled:
|
|
||||||
typeof process.env?.PW_ENSURE_PLUGINS_INSTALLED === 'string'
|
|
||||||
? process.env.PW_ENSURE_PLUGINS_INSTALLED.split(',').filter((plugin) => Boolean(plugin))
|
|
||||||
: [],
|
|
||||||
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',
|
|
||||||
pushNotificationServer: process.env.PW_PUSH_NOTIFICATION_SERVER || 'https://push-test.mattermost.com',
|
|
||||||
resetBeforeTest: parseBool(process.env.PW_RESET_BEFORE_TEST, false),
|
|
||||||
// CI
|
|
||||||
isCI: !!process.env.CI,
|
|
||||||
// Playwright
|
|
||||||
headless: parseBool(process.env.PW_HEADLESS, true),
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
@@ -40,10 +40,10 @@ npm run test
|
|||||||
|
|
||||||
#### 1. Run docker container using latest focal version
|
#### 1. Run docker container using latest focal version
|
||||||
|
|
||||||
Change to the root directory, then run the docker container. (See https://playwright.dev/docs/docker for reference.)
|
Change to the `e2e-tests/playwright` directory, then run the docker container. (See https://playwright.dev/docs/docker for reference.)
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.49.1-noble /bin/bash
|
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.51.1-noble /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Inside the docker container
|
#### 2. Inside the docker container
|
||||||
@@ -51,23 +51,26 @@ docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwr
|
|||||||
```
|
```
|
||||||
export PW_BASE_URL=http://host.docker.internal:8065
|
export PW_BASE_URL=http://host.docker.internal:8065
|
||||||
export PW_HEADLESS=true
|
export PW_HEADLESS=true
|
||||||
cd mattermost/e2e-tests/playwright
|
cd mattermost
|
||||||
|
|
||||||
# Install npm packages. Use "npm ci" to match the automated environment
|
# Install npm packages. Use "npm ci" to match the automated environment
|
||||||
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
||||||
|
|
||||||
# Run specific test. See https://playwright.dev/docs/test-cli.
|
# Run specific test. See https://playwright.dev/docs/test-cli.
|
||||||
npm run test -- login --project=chrome
|
npm run test -- -- login --project=chrome
|
||||||
|
|
||||||
# Or run all tests
|
# Or run all tests
|
||||||
npm run test
|
npm run test
|
||||||
|
|
||||||
# Update snapshots
|
# Run visual tests
|
||||||
npm run test -- login --update-snapshots
|
npm run test -- -- visual
|
||||||
|
|
||||||
|
# Update snapshots of visual tests
|
||||||
|
npm run test -- -- visual --update-snapshots
|
||||||
```
|
```
|
||||||
|
|
||||||
## Page/Component Object Model
|
## Page/Component Object Model
|
||||||
|
|
||||||
See https://playwright.dev/docs/test-pom.
|
See https://playwright.dev/docs/test-pom.
|
||||||
|
|
||||||
Page and component abstractions are located at `./support/ui`. They should be established before writing a spec file so that any future changes in the DOM structure will be made in one place only. No static UI text or fixed locator should be written in the spec file.
|
Page and component abstractions are in shared library located at `./lib/src/ui`. They should be established before writing a spec file so that any future changes in the DOM structure will be made in one place only. No static UI text or fixed locator should be written in the spec file.
|
||||||
Двоичные данные
e2e-tests/playwright/test/asset/mattermost-icon_128x128.png
Обычный файл
Двоичные данные
e2e-tests/playwright/test/asset/mattermost-icon_128x128.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 13 KiB |
|
До Ширина: | Высота: | Размер: 75 KiB После Ширина: | Высота: | Размер: 75 KiB |
22
e2e-tests/playwright/test/global_setup.ts
Обычный файл
22
e2e-tests/playwright/test/global_setup.ts
Обычный файл
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {baseGlobalSetup, testConfig} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
|
async function globalSetup() {
|
||||||
|
try {
|
||||||
|
await baseGlobalSetup();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(
|
||||||
|
`Global setup failed.\n\tEnsure the server at ${testConfig.baseURL} is running and accessible.\n\tPlease check the logs for more details.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
// placeholder for teardown setup
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default globalSetup;
|
||||||
26
e2e-tests/playwright/test/package.json
Обычный файл
26
e2e-tests/playwright/test/package.json
Обычный файл
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@mattermost/playwright-test",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"test": "cross-env PW_SNAPSHOT_ENABLE=true playwright test",
|
||||||
|
"test:ci": "cross-env PW_SNAPSHOT_ENABLE=true playwright test --project=chrome",
|
||||||
|
"test:update-snapshots": "cross-env PW_SNAPSHOT_ENABLE=true playwright test --update-snapshots",
|
||||||
|
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=ipad",
|
||||||
|
"tsc": "tsc -b",
|
||||||
|
"codegen": "cross-env playwright codegen $PW_BASE_URL",
|
||||||
|
"playwright-ui": "cross-env playwright test --ui",
|
||||||
|
"test-slomo": "cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
|
||||||
|
"show-report": "npx playwright show-report",
|
||||||
|
"postinstall": "script/post_install.sh",
|
||||||
|
"clean": "rm -rf dist node_modules *.tsbuildinfo logs results storage_state test-results"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mattermost/client": "file:../../../webapp/platform/client",
|
||||||
|
"@mattermost/playwright-lib": "*",
|
||||||
|
"@mattermost/types": "file:../../../webapp/platform/types",
|
||||||
|
"zod": "3.24.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "7.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,14 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {defineConfig, devices} from '@playwright/test';
|
import {defineConfig, devices} from '@playwright/test';
|
||||||
|
import {duration, testConfig} from '@mattermost/playwright-lib';
|
||||||
import {duration} from '@e2e-support/util';
|
|
||||||
import testConfig from '@e2e-test.config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
globalSetup: require.resolve('./global_setup'),
|
globalSetup: './global_setup.ts',
|
||||||
forbidOnly: testConfig.isCI,
|
forbidOnly: testConfig.isCI,
|
||||||
outputDir: './results/output',
|
outputDir: './results/output',
|
||||||
retries: testConfig.isCI ? 2 : 0,
|
retries: testConfig.isCI ? 2 : 0,
|
||||||
testDir: 'tests',
|
testDir: 'specs',
|
||||||
timeout: duration.one_min,
|
timeout: duration.one_min,
|
||||||
workers: testConfig.workers,
|
workers: testConfig.workers,
|
||||||
expect: {
|
expect: {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, test} from '@e2e-support/test_fixture';
|
import {expect, test} from '@mattermost/playwright-lib';
|
||||||
|
|
||||||
test.fixme('Base channel accessibility', async ({pw, axe}) => {
|
test.fixme('Base channel accessibility', async ({pw, axe}) => {
|
||||||
// # Create and sign in a new user
|
// # Create and sign in a new user
|
||||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user