mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge remote-tracking branch 'origin/master' into plugin-metrics
This commit is contained in:
commit
593ce26f37
@ -8,7 +8,7 @@ insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
[*.go]
|
||||
[*.{go,sql}]
|
||||
indent_style = tab
|
||||
|
||||
[*.{js,jsx,json,html,ts,tsx}]
|
||||
@ -21,12 +21,21 @@ indent_size = 2
|
||||
[i18n/**.json]
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
[Makefile,*.mk]
|
||||
indent_style = tab
|
||||
|
||||
[*.scss]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[.npm-upgrade.json]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.yml,*.yaml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
29
.github/workflows/e2e-tests-ci.yml
vendored
29
.github/workflows/e2e-tests-ci.yml
vendored
@ -8,10 +8,6 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
update-initial-status:
|
||||
runs-on: ubuntu-22.04
|
||||
@ -84,25 +80,46 @@ jobs:
|
||||
npm run check
|
||||
|
||||
smoketests:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
strategy:
|
||||
matrix:
|
||||
#
|
||||
# Note that smoketests should be run only on ubuntu, for QA purposes.
|
||||
# But it's useful to be able to run and debug the smoketests for different OSes.
|
||||
# Notes:
|
||||
# - For MacOS: works on developer machines, but uses too many resources to be able to run on Github Actions
|
||||
# - for Windows: cannot currently run on Github Actions, since the runners do not support running linux containers, at the moment
|
||||
#
|
||||
#os: [ubuntu-latest-8-cores, windows-2022, macos-12-xl]
|
||||
os: [ubuntu-latest-8-cores]
|
||||
runs-on: "${{ matrix.os }}"
|
||||
needs:
|
||||
- cypress-check
|
||||
- playwright-check
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: e2e-tests
|
||||
steps:
|
||||
- name: ci/checkout-repo
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: ci/setup-macos-docker
|
||||
if: runner.os == 'macos'
|
||||
# https://github.com/actions/runner-images/issues/17#issuecomment-1537238473
|
||||
run: |
|
||||
brew install docker docker-compose
|
||||
colima start
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
ln -sfn /usr/local/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose
|
||||
sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
|
||||
- name: ci/e2e-smoketests
|
||||
run: |
|
||||
make
|
||||
- name: ci/e2e-smoketests-store-results
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: e2e-smoketests-results
|
||||
name: e2e-smoketests-results-${{ matrix.os }}
|
||||
path: |
|
||||
e2e-tests/cypress/logs/
|
||||
e2e-tests/cypress/results/
|
||||
|
54
.github/workflows/migration.yml
vendored
Normal file
54
.github/workflows/migration.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Database Migration Test
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
test:
|
||||
name: MySQL -> Postgres Migration
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
COMPOSE_PROJECT_NAME: ghactions
|
||||
BUILD_IMAGE: mattermost/mattermost-build-server:20230904_golang-1.20.7
|
||||
defaults:
|
||||
run:
|
||||
working-directory: server
|
||||
steps:
|
||||
- name: Checkout mattermost project
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- name: Run docker compose
|
||||
run: |
|
||||
cd build
|
||||
docker-compose --ansi never run --rm start_dependencies
|
||||
docker-compose --ansi never exec -T minio sh -c 'mkdir -p /data/mattermost-test';
|
||||
docker-compose --ansi never ps
|
||||
- name: Generate test-data
|
||||
run: |
|
||||
docker run --net ${COMPOSE_PROJECT_NAME}_mm-test \
|
||||
--ulimit nofile=8096:8096 \
|
||||
--env-file=build/dotenv/migration.env \
|
||||
-v $(go env GOCACHE):/go/cache \
|
||||
-e GOCACHE=/go/cache \
|
||||
-v $PWD:/mattermost \
|
||||
-w /mattermost \
|
||||
$BUILD_IMAGE \
|
||||
make test-data
|
||||
- name: Migrate the DB and compare
|
||||
run: |
|
||||
docker run --net ${COMPOSE_PROJECT_NAME}_mm-test \
|
||||
--ulimit nofile=8096:8096 \
|
||||
--env-file=build/dotenv/migration.env \
|
||||
-v $(go env GOCACHE):/go/cache \
|
||||
-e GOCACHE=/go/cache \
|
||||
-v $PWD:/mattermost \
|
||||
-w /mattermost \
|
||||
$BUILD_IMAGE \
|
||||
make test-migration
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: Migration logs
|
||||
path: server/migration.log
|
||||
retention-days: 7
|
||||
- name: Stop docker compose
|
||||
run: |
|
||||
cd build
|
||||
docker-compose --ansi never stop
|
14
.github/workflows/mmctl-test-template.yml
vendored
14
.github/workflows/mmctl-test-template.yml
vendored
@ -15,14 +15,14 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
go-version: "1.19.5"
|
||||
go-version: "1.20.7"
|
||||
jobs:
|
||||
test:
|
||||
name: ${{ inputs.name }}
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
COMPOSE_PROJECT_NAME: ghactions
|
||||
BUILD_IMAGE: mattermost/mattermost-build-server:20230118_golang-1.19.5
|
||||
BUILD_IMAGE: mattermost/mattermost-build-server:20230904_golang-1.20.7
|
||||
steps:
|
||||
- name: Checkout mattermost project
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
@ -30,10 +30,15 @@ jobs:
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.go-version }}
|
||||
cache-dependency-path: server/go.sum
|
||||
- name: Run setup-go-work
|
||||
run: |
|
||||
cd server
|
||||
make setup-go-work
|
||||
- name: Setup needed prepackaged plugins
|
||||
run: |
|
||||
cd server
|
||||
make prepackaged-plugins PLUGIN_PACKAGES=mattermost-plugin-jira-v3.2.5
|
||||
- name: Run docker compose
|
||||
run: |
|
||||
cd server/build
|
||||
@ -43,6 +48,9 @@ jobs:
|
||||
docker-compose --ansi never ps
|
||||
- name: Run mmctl Tests
|
||||
run: |
|
||||
if [[ ${{ github.ref_name }} == 'master' ]]; then
|
||||
export MMCTL_TESTFLAGS="-timeout 30m -race"
|
||||
fi
|
||||
docker run --net ghactions_mm-test \
|
||||
--ulimit nofile=8096:8096 \
|
||||
--env-file=server/build/dotenv/test.env \
|
||||
@ -52,7 +60,7 @@ jobs:
|
||||
-v $PWD:/mattermost \
|
||||
-w /mattermost/server \
|
||||
$BUILD_IMAGE \
|
||||
make test-mmctl-coverage BUILD_NUMBER=$GITHUB_HEAD_REF-$GITHUB_RUN_ID
|
||||
make test-mmctl BUILD_NUMBER=$GITHUB_HEAD_REF-$GITHUB_RUN_ID
|
||||
- name: Stop docker compose
|
||||
run: |
|
||||
cd server/build
|
||||
|
37
.github/workflows/scorecards-analysis.yml
vendored
37
.github/workflows/scorecards-analysis.yml
vendored
@ -3,45 +3,52 @@ on:
|
||||
# Only the default branch is supported.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '44 6 * * *'
|
||||
- cron: "44 6 * * *"
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
name: Scorecard analysis
|
||||
if: github.repository_owner == 'mattermost'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # v1.0.4
|
||||
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# Read-only PAT token. To create it,
|
||||
# follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||
repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
||||
# Publish the results to enable scorecard badges. For more details, see
|
||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories, `publish_results` will automatically be set to `false`,
|
||||
# regardless of the value entered here.
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional).
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@ -49,6 +56,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26
|
||||
uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/server-ci-master.yml
vendored
2
.github/workflows/server-ci-master.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
- release-*
|
||||
- mono-repo*
|
||||
env:
|
||||
go-version: "1.19.5"
|
||||
go-version: "1.20.7"
|
||||
|
||||
jobs:
|
||||
master-ci:
|
||||
|
2
.github/workflows/server-ci-pr.yml
vendored
2
.github/workflows/server-ci-pr.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
- "e2e-tests/**"
|
||||
- ".github/**"
|
||||
env:
|
||||
go-version: "1.19.5"
|
||||
go-version: "1.20.7"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
6
.github/workflows/server-ci-template.yml
vendored
6
.github/workflows/server-ci-template.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
go-version: "1.19.5"
|
||||
go-version: "1.20.7"
|
||||
|
||||
jobs:
|
||||
check-mocks:
|
||||
@ -297,3 +297,7 @@ jobs:
|
||||
name: server-build-artifact
|
||||
path: server/build/
|
||||
retention-days: 14
|
||||
test-migration:
|
||||
name: MySQL to PostgreSQL Migration
|
||||
uses: ./.github/workflows/migration.yml
|
||||
secrets: inherit
|
||||
|
23
.github/workflows/server-test-template.yml
vendored
23
.github/workflows/server-test-template.yml
vendored
@ -15,14 +15,14 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
go-version: "1.19.5"
|
||||
go-version: "1.20.7"
|
||||
jobs:
|
||||
test:
|
||||
name: ${{ inputs.name }}
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
COMPOSE_PROJECT_NAME: ghactions
|
||||
BUILD_IMAGE: mattermost/mattermost-build-server:20230118_golang-1.19.5
|
||||
BUILD_IMAGE: mattermost/mattermost-build-server:20230904_golang-1.20.7
|
||||
steps:
|
||||
- name: Checkout mattermost project
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
@ -76,12 +76,25 @@ jobs:
|
||||
report_paths: server/report.xml
|
||||
check_name: ${{ inputs.name }} (Results)
|
||||
require_tests: true
|
||||
- name: Report retried tests via webhook
|
||||
if: ${{ steps.report.outputs.retried > 0 }}
|
||||
- name: Report retried tests via webhook (master || release-*)
|
||||
if: ${{ steps.report.outputs.retried > 0 && (github.ref_name == 'master' || startsWith(github.ref_name, 'release-')) }}
|
||||
run: |
|
||||
curl \
|
||||
--fail \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"text\":\"#### ⚠️ One or more flaky tests detected ⚠️\\n* Failing job: [github.com/mattermost/mattermost:${{ inputs.name }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\\n* If this is your pull request, double check your code to ensure you haven't introduced a flaky test.\\n* If this occurred on master or a release branch, reply to this message if you're willing to help.\\n* Submit a separate pull request to skip the flaky tests (e.g. [23360](https://github.com/mattermost/mattermost/pull/23360)) and file JIRA ticket (e.g. [MM-52743](https://mattermost.atlassian.net/browse/MM-52743)) for later investigation.\\n* Finally, reply to this message with a link to the created JIRA ticket.\\ncc @devs\"}" \
|
||||
-d "{\"text\":\"#### ⚠️ One or more flaky tests detected ⚠️\\n* Failing job: [github.com/mattermost/mattermost:${{ inputs.name }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\\n* Ideally, this would have been caught in a pull request, but now a volunteer is required. If you're willing to help, submit a separate pull request to skip the flaky tests (e.g. [23360](https://github.com/mattermost/mattermost/pull/23360)) and file JIRA ticket (e.g. [MM-52743](https://mattermost.atlassian.net/browse/MM-52743)) for later investigation.\\n* Finally, reply to this message with a link to the created JIRA ticket.\\n\"}" \
|
||||
${{ secrets.MM_COMMUNITY_DEVELOPERS_INCOMING_WEBHOOK_FROM_GH_ACTIONS }}
|
||||
- name: Report retried tests (pull request)
|
||||
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
|
||||
if: ${{ steps.report.outputs.retried > 0 && !(github.ref_name == 'master' || startsWith(github.ref_name, 'release-')) }}
|
||||
with:
|
||||
script: |
|
||||
const body = `#### ⚠️ One or more flaky tests detected ⚠️\n* Failing job: [github.com/mattermost/mattermost:${{ inputs.name }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\n* Double check your code to ensure you haven't introduced a flaky test.\n* If this seems to be unrelated to your changes, submit a separate pull request to skip the flaky tests (e.g. [23360](https://github.com/mattermost/mattermost/pull/23360)) and file JIRA ticket (e.g. [MM-52743](https://mattermost.atlassian.net/browse/MM-52743)) for later investigation.`
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: body
|
||||
})
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -44,6 +44,9 @@ e2e-tests/playwright/tests/**/*-window.png
|
||||
e2e-tests/playwright/tests/accessibility/**/*-snapshots
|
||||
e2e-tests/playwright/.eslintcache
|
||||
|
||||
# ignore temporary added configuration for pgloader
|
||||
server/tests/temp.load
|
||||
|
||||
# Enterprise & products imports files
|
||||
imports/imports.go
|
||||
|
||||
|
@ -12,7 +12,7 @@ include:
|
||||
|
||||
variables:
|
||||
BUILD: "yes"
|
||||
IMAGE_BUILD_SERVER: $CI_REGISTRY/mattermost/ci/images/mattermost-build-server:20230118_golang-1.19.5
|
||||
IMAGE_BUILD_SERVER: $CI_REGISTRY/mattermost/ci/images/mattermost-build-server:20230904_golang-1.20.7
|
||||
IMAGE_BUILD_DOCKER: $CI_REGISTRY/mattermost/ci/devops-images/mattermost-build-docker:23.0.1-0
|
||||
IMAGE_DIND: $CI_REGISTRY/mattermost/ci/devops-images/docker-dind:23.0.1-0
|
||||
|
||||
|
@ -41,7 +41,6 @@ build-v4: node_modules playbooks
|
||||
@cat $(V4_SRC)/schemes.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/service_terms.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/sharedchannels.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/opengraph.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/reactions.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/actions.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/bots.yaml >> $(V4_YAML)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 31 KiB |
@ -1,38 +0,0 @@
|
||||
/api/v4/opengraph:
|
||||
post:
|
||||
tags:
|
||||
- OpenGraph
|
||||
summary: Get open graph metadata for url
|
||||
description: >
|
||||
Get Open Graph Metadata for a specif URL. Use the Open Graph protocol to
|
||||
get some generic metadata about a URL. Used for creating link previews.
|
||||
|
||||
|
||||
__Minimum server version__: 3.10
|
||||
|
||||
|
||||
##### Permissions
|
||||
|
||||
No permission required but must be logged in.
|
||||
operationId: OpenGraph
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- url
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description: The URL to get Open Graph Metadata.
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
description: Open Graph retrieval successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OpenGraph"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
@ -3,10 +3,23 @@
|
||||
export MME2E_DC_SERVER="docker compose -p mmserver -f $(readlink -f ../../server/build/gitlab-dc.postgres.yml) -f $(readlink -f ./server.override.yml)"
|
||||
export MME2E_DC_DASHBOARD="docker compose -p mmdashboard -f $(readlink -f ./dashboard/docker/docker-compose.yml) -f $(readlink -f ./dashboard.override.yml)"
|
||||
export MME2E_UID=$(id -u)
|
||||
export MME2E_OSTYPE=$(docker version -f '{{ .Client.Os }}')
|
||||
export MME2E_ARCHTYPE=$(docker version -f '{{ .Client.Arch }}')
|
||||
export NODE_VERSION_REQUIRED=$(cat ../../.nvmrc)
|
||||
|
||||
# Default the optional variables that are used in the docker-compose file
|
||||
# Default values for optional variables
|
||||
export SERVER_IMAGE_DEFAULT="mattermostdevelopment/mm-ee-test:$(git rev-parse --short=7 HEAD)"
|
||||
export BROWSER_DEFAULT="chrome"
|
||||
case $MME2E_OSTYPE in
|
||||
# OS specific defaults overrides
|
||||
darwin )
|
||||
BROWSER_DEFAULT="electron" ;;
|
||||
* )
|
||||
esac
|
||||
|
||||
# Populate the optional variables that are used in the docker-compose file
|
||||
export SERVER_IMAGE=${SERVER_IMAGE:-$SERVER_IMAGE_DEFAULT}
|
||||
export BROWSER=${BROWSER:-$BROWSER_DEFAULT}
|
||||
|
||||
# Function definitions
|
||||
mme2e_log () { echo "[$(date +%Y-%m-%dT%H:%M:%S%Z)]" "$@"; }
|
||||
@ -69,7 +82,14 @@ mme2e_legacy_setup () {
|
||||
# These functions are needed until every pipeline depending on the `server/build/gitlab-dc.*.yml` files is adapted to not use external docker networking anymore
|
||||
# After that is fixed, this function and the external network in the docker-compose files may be removed
|
||||
export COMPOSE_PROJECT_NAME=mmserver_legacy
|
||||
docker network inspect ${COMPOSE_PROJECT_NAME} >/dev/null 2>&1 || docker network create ${COMPOSE_PROJECT_NAME}
|
||||
case $MME2E_OSTYPE in
|
||||
windows )
|
||||
# https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies
|
||||
DOCKER_NETWORK_DRIVER="nat" ;;
|
||||
* )
|
||||
DOCKER_NETWORK_DRIVER="bridge" ;;
|
||||
esac
|
||||
docker network inspect ${COMPOSE_PROJECT_NAME} >/dev/null 2>&1 || docker network create --driver=${DOCKER_NETWORK_DRIVER} ${COMPOSE_PROJECT_NAME}
|
||||
}
|
||||
|
||||
# Utility alias, for interactive shell usage. Can be reversed with 'unalias docker-compose' in your shell
|
||||
|
@ -1,24 +1,23 @@
|
||||
---
|
||||
version: '3.1'
|
||||
version: "3.1"
|
||||
|
||||
services:
|
||||
dashboard:
|
||||
image: mattermostdevelopment/mirrored-node:16.17.0
|
||||
environment:
|
||||
PG_URI: postgres://mmuser:mostest@db:5432/automation_dashboard_db
|
||||
JWT_SECRET: s8gGBA3ujKRohSw1L8HLOY7Jjnu2ZYv8 # Generated with e.g. `dd if=/dev/urandom count=24 bs=1 2>/dev/null | base64 -w0`
|
||||
JWT_USER: cypress-test
|
||||
JWT_ROLE: integration
|
||||
JWT_ALG: HS256
|
||||
JWT_EXPIRES_IN: 365d
|
||||
ALLOWED_USER: cypress-test
|
||||
ALLOWED_ROLE: integration
|
||||
user: "${MME2E_UID}"
|
||||
volumes:
|
||||
- "../:/app"
|
||||
- "../../dashboard.entrypoint.sh:/usr/local/bin/dashboard.entrypoint.sh:ro"
|
||||
- "../../.env.dashboard:/var/local/.env.dashboard:rw"
|
||||
working_dir: /app
|
||||
entrypoint: /usr/local/bin/dashboard.entrypoint.sh
|
||||
ports:
|
||||
- 4000:4000
|
||||
dashboard:
|
||||
image: mattermostdevelopment/mirrored-node:18.17
|
||||
environment:
|
||||
PG_URI: postgres://mmuser:mostest@db:5432/automation_dashboard_db
|
||||
JWT_SECRET: s8gGBA3ujKRohSw1L8HLOY7Jjnu2ZYv8 # Generated with e.g. `dd if=/dev/urandom count=24 bs=1 2>/dev/null | base64 -w0`
|
||||
JWT_USER: cypress-test
|
||||
JWT_ROLE: integration
|
||||
JWT_ALG: HS256
|
||||
JWT_EXPIRES_IN: 365d
|
||||
ALLOWED_USER: cypress-test
|
||||
ALLOWED_ROLE: integration
|
||||
volumes:
|
||||
- "../:/app"
|
||||
- "../../dashboard.entrypoint.sh:/usr/local/bin/dashboard.entrypoint.sh:ro"
|
||||
- "../../.env.dashboard:/var/local/.env.dashboard:rw"
|
||||
working_dir: /app
|
||||
entrypoint: /usr/local/bin/dashboard.entrypoint.sh
|
||||
ports:
|
||||
- 4000:4000
|
||||
|
@ -18,13 +18,18 @@ mme2e_log "Starting the dashboard"
|
||||
${MME2E_DC_DASHBOARD} up -d db dashboard
|
||||
|
||||
mme2e_log "Generating the dashboard's local URL"
|
||||
MME2E_DC_DASHBOARD_NETWORK=$(${MME2E_DC_DASHBOARD} ps -q dashboard | xargs -l docker inspect | jq -r '.[0].NetworkSettings.Networks | (keys|.[0])')
|
||||
MME2E_DC_DASHBOARD_GATEWAY=$(docker network inspect $MME2E_DC_DASHBOARD_NETWORK | jq -r '.[0].IPAM.Config[0].Gateway')
|
||||
AUTOMATION_DASHBOARD_URL="http://${MME2E_DC_DASHBOARD_GATEWAY}:4000/api"
|
||||
case $MME2E_OSTYPE in
|
||||
darwin )
|
||||
AUTOMATION_DASHBOARD_IP=$(ifconfig $(route get 1.1.1.1 | grep interface | awk '{print $2}') | grep -w inet | awk '{print $2}') ;;
|
||||
* )
|
||||
MME2E_DC_DASHBOARD_NETWORK=$(docker inspect $(${MME2E_DC_DASHBOARD} ps -q dashboard) | jq -r '.[0].NetworkSettings.Networks | (keys|.[0])')
|
||||
AUTOMATION_DASHBOARD_IP=$(docker network inspect $MME2E_DC_DASHBOARD_NETWORK | jq -r '.[0].IPAM.Config[0].Gateway')
|
||||
esac
|
||||
AUTOMATION_DASHBOARD_URL="http://${AUTOMATION_DASHBOARD_IP}:4000/api"
|
||||
|
||||
mme2e_log "Generating a signed JWT token for accessing the dashboard"
|
||||
${MME2E_DC_DASHBOARD} exec -T -u $MME2E_UID dashboard npm i
|
||||
AUTOMATION_DASHBOARD_TOKEN=$(${MME2E_DC_DASHBOARD} exec -T -u $MME2E_UID dashboard node script/sign.js | awk '{ print $2; }') # The token secret is specified in the dashboard.override.yml file
|
||||
${MME2E_DC_DASHBOARD} exec -T dashboard npm i
|
||||
AUTOMATION_DASHBOARD_TOKEN=$(${MME2E_DC_DASHBOARD} exec -T dashboard node script/sign.js | awk '{ print $2; }') # The token secret is specified in the dashboard.override.yml file
|
||||
|
||||
mme2e_log "Generating the .env.dashboard file, to point Cypress to the dashboard URL"
|
||||
mme2e_generate_envfile_from_var_names >.env.dashboard <<EOF
|
||||
|
@ -1,9 +1,8 @@
|
||||
---
|
||||
# Image hashes in this file are for amd64 systems
|
||||
# NB: paths relative to the `server/build` directory, which contains the original compose file that this yaml is overriding
|
||||
version: '2.4'
|
||||
version: "2.4"
|
||||
services:
|
||||
|
||||
server:
|
||||
image: "${SERVER_IMAGE}"
|
||||
restart: always
|
||||
@ -28,7 +27,7 @@ services:
|
||||
MM_FEATUREFLAGS_ONBOARDINGTOURTIPS: "false"
|
||||
MM_SERVICEENVIRONMENT: "test"
|
||||
volumes:
|
||||
- "server-config:/mattermost/config"
|
||||
- "server-config:/mattermost/config"
|
||||
ports:
|
||||
- "8065:8065"
|
||||
depends_on:
|
||||
@ -54,12 +53,12 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
webhook-interactions:
|
||||
image: mattermostdevelopment/mirrored-node:16.10.0
|
||||
image: mattermostdevelopment/mirrored-node:${NODE_VERSION_REQUIRED}
|
||||
command: sh -c "npm install -g axios express client-oauth2@larkox/js-client-oauth2#e24e2eb5dfcbbbb3a59d095e831dbe0012b0ac49 && exec node webhook_serve.js"
|
||||
environment:
|
||||
NODE_PATH: /usr/local/lib/node_modules/
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-s", "-o/dev/null", "127.0.0.1:3000" ]
|
||||
test: ["CMD", "curl", "-s", "-o/dev/null", "127.0.0.1:3000"]
|
||||
interval: 10s
|
||||
timeout: 15s
|
||||
retries: 12
|
||||
@ -72,16 +71,17 @@ services:
|
||||
- webhook-interactions
|
||||
|
||||
cypress:
|
||||
image: "mattermostdevelopment/mirrored-cypress-browsers-public:node16.14.2-slim-chrome100-ff99-edge"
|
||||
entrypoint: [ "/bin/bash", "-c" ]
|
||||
command: [ "until [ -f /var/run/mm_terminate ]; do sleep 5; done" ]
|
||||
image: "cypress/browsers:node-18.16.1-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1"
|
||||
### Temorarily disabling this image, until both the amd64 and arm64 version are mirrored
|
||||
# image: "mattermostdevelopment/mirrored-cypress-browsers-public:node-18.16.1-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1"
|
||||
entrypoint: ["/bin/bash", "-c"]
|
||||
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
||||
env_file:
|
||||
- "../../e2e-tests/.ci/.env.dashboard"
|
||||
- "../../e2e-tests/.ci/.env.cypress"
|
||||
environment:
|
||||
REPO: "mattermost-server"
|
||||
# Cypress configuration
|
||||
BROWSER: "chrome"
|
||||
HEADLESS: "true"
|
||||
CYPRESS_baseUrl: "http://server:8065"
|
||||
CYPRESS_dbConnection: "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10"
|
||||
@ -107,25 +107,25 @@ services:
|
||||
# https://github.com/cypress-io/cypress/issues/1243
|
||||
CI: "1"
|
||||
# Ensure we're independent from the global node environment
|
||||
PATH: /app/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
PATH: /cypress/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 8096
|
||||
hard: 1048576
|
||||
working_dir: /app
|
||||
working_dir: /cypress
|
||||
volumes:
|
||||
- "../../e2e-tests/cypress/:/app"
|
||||
- "../../e2e-tests/cypress/:/cypress"
|
||||
|
||||
utils:
|
||||
image: "mattermostdevelopment/mirrored-golang:1.19.5"
|
||||
entrypoint: [ "/bin/bash", "-c" ]
|
||||
command: [ "until [ -f /var/run/mm_terminate ]; do sleep 5; done" ]
|
||||
image: "mattermost/mattermost-build-server:20230904_golang-1.20.7"
|
||||
entrypoint: ["/bin/bash", "-c"]
|
||||
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
||||
environment:
|
||||
MM_SERVICEENVIRONMENT: "test" # Currently required for running the config_generator with the right environment
|
||||
working_dir: "/opt/mattermost-server"
|
||||
volumes:
|
||||
- "../../:/opt/mattermost-server"
|
||||
- "server-config:/opt/server-config"
|
||||
- "../../:/opt/mattermost-server"
|
||||
- "server-config:/opt/server-config"
|
||||
|
||||
volumes:
|
||||
server-config:
|
||||
|
@ -29,6 +29,7 @@ mme2e_generate_envfile_from_var_names >.env.cypress <<EOF
|
||||
BRANCH
|
||||
BUILD_ID
|
||||
CI_BASE_URL
|
||||
BROWSER
|
||||
AUTOMATION_DASHBOARD_URL
|
||||
AUTOMATION_DASHBOARD_TOKEN
|
||||
EOF
|
||||
@ -42,7 +43,6 @@ mme2e_log "Creating E2E containers and generating server config"
|
||||
${MME2E_DC_SERVER} create
|
||||
${MME2E_DC_SERVER} up -d -- utils
|
||||
${MME2E_DC_SERVER} exec -T -w /opt/mattermost-server/server -- utils bash <<EOF
|
||||
apt update && apt install -y jq
|
||||
OUTPUT_CONFIG=/tmp/config_generated.json go run scripts/config_generator/main.go
|
||||
jq "
|
||||
.ServiceSettings.SiteURL=\"http://server:8065\"
|
||||
|
@ -7,20 +7,20 @@ stop: stop-server stop-dashboard clean-env-placeholders
|
||||
|
||||
.PHONY: start-server prepare-server run-cypress stop-server restart-server
|
||||
start-server:
|
||||
./.ci/server.start.sh
|
||||
bash ./.ci/server.start.sh
|
||||
prepare-server:
|
||||
./.ci/server.prepare.sh
|
||||
bash ./.ci/server.prepare.sh
|
||||
run-cypress:
|
||||
./.ci/server.run_cypress.sh
|
||||
bash ./.ci/server.run_cypress.sh
|
||||
stop-server:
|
||||
./.ci/server.stop.sh
|
||||
bash ./.ci/server.stop.sh
|
||||
restart-server: stop-server start-server
|
||||
|
||||
.PHONY: start-dashboard stop-dashboard
|
||||
start-dashboard:
|
||||
./.ci/dashboard.start.sh
|
||||
bash ./.ci/dashboard.start.sh
|
||||
stop-dashboard:
|
||||
./.ci/dashboard.stop.sh
|
||||
bash ./.ci/dashboard.stop.sh
|
||||
|
||||
.PHONY: print-env-placeholders clean-env-placeholders
|
||||
print-env-placeholders:
|
||||
|
@ -4,30 +4,35 @@ This directory contains the E2E testing code for the Mattermost web client.
|
||||
|
||||
### How to run locally
|
||||
|
||||
The E2E testing scripts depend on the following tools being installed on your system: `docker`, `docker-compose`, `make`, `git`, `jq`, and some common utilities (`coreutils`, `findutils`, `bash`, `awk`, `sed`, `grep`)
|
||||
##### For test case development
|
||||
|
||||
Please refer to the [dedicated developer documentation](https://developers.mattermost.com/contribute/more-info/webapp/e2e-testing/) for instructions.
|
||||
|
||||
##### For pipeline debugging
|
||||
|
||||
The E2E testing pipeline's scripts depend on the following tools being installed on your system: `docker`, `docker-compose`, `make`, `git`, `jq`, and some common utilities (`coreutils`, `findutils`, `bash`, `awk`, `sed`, `grep`)
|
||||
|
||||
Instructions, tl;dr: create a local branch with your E2E test changes, then open a PR to the `mattermost-server` repo targeting the `master` branch (so that CI will produce the image that docker-compose needs), then run `make` in this directory.
|
||||
|
||||
Instructions, detailed:
|
||||
1. Create the `.ci/env` file, and populate the variables you need, keeping in mind that:
|
||||
1. (optional, undefined variables are set to sane defaults) Create the `.ci/env` file, and populate it with the variables you need out of the following list:
|
||||
* The following variables will be passed over to the server container: `MM_LICENSE` (no enterprise features will be available if this is unset), and the exploded `MM_ENV` (a comma-separated list of env var specifications)
|
||||
* The following variables will be passed over to the cypress container: `BRANCH`, `BUILD_ID`, `CI_BASE_URL`, `AUTOMATION_DASHBOARD_URL` and `AUTOMATION_DASHBOARD_TOKEN`
|
||||
* The `SERVER_IMAGE` variable can also be set, if you want to select a custom mattermost-server image
|
||||
* The `TEST_FILTER` variable can also be set, to customize which tests you want cypress to run
|
||||
* All variables are optional, and will be set to sane defaults
|
||||
* The following variables will be passed over to the cypress container: `BRANCH`, `BUILD_ID`, `CI_BASE_URL`, `BROWSER`, `AUTOMATION_DASHBOARD_URL` and `AUTOMATION_DASHBOARD_TOKEN`
|
||||
* The `SERVER_IMAGE` variable can also be set, if you want to select a custom mattermost-server image. If not specified, the value of the `SERVER_IMAGE_DEFAULT` variable defined in file `.ci/.e2erc` is used.
|
||||
* The `TEST_FILTER` variable can also be set, to customize which tests you want cypress to run. If not specified, only the smoke tests will run. Please check the `e2e-tests/cypress/run_tests.js` file for details about its format.
|
||||
2. (optional) `make start-dashboard`: start the automation-dashboard in the background
|
||||
* This also sets the `AUTOMATION_DASHBOARD_URL` and `AUTOMATION_DASHBOARD_TOKEN` variables for the cypress container
|
||||
* Note that if you run the dashboard locally, but also specify other `AUTOMATION_DASHBOARD_*` variables in your env, the latter variables will take precedence
|
||||
* Note that if you run the dashboard locally, but also specify other `AUTOMATION_DASHBOARD_*` variables in your `.ci/env` file, the latter variables will take precedence
|
||||
3. `make`: start and prepare the server, then run the cypress tests
|
||||
* You can track the progress of the run in the `http://localhost:4000/cycles` dashboard, if you launched it locally
|
||||
4. `make stop`: tears down the server (and the dashboard, if running), then cleans up the env placeholder files
|
||||
|
||||
Notes:
|
||||
- Setting a variable in `.ci/env` is functionally equivalent to exporting variables in your current shell's environment, before invoking the makefile.
|
||||
- The `.ci/.env.*` files are auto generated by the pipeline scripts, and aren't meant to be modified manually. The only file you should edit to control the containers' environment is `.ci/env`, as specified in the instructions above.
|
||||
- Aside from some exceptions (e.g. `TEST_FILTER`), most of the variables in `.ci/env` must be set before the `make start-server` command is run. Modifying that file afterwards has no effect, because the containers' env files are generated in that step.
|
||||
- If you restart the dashboard at any point, you must also restart the server containers, so that it picks up the new IP of the dashboard from the newly generated `.env.dashboard` file
|
||||
- If you started the dashboard locally in the past, but want to point to another dashboard later, you can run `make clean-env-placeholders` to remove references to the local dashboard (you'll likely need to restart the server)
|
||||
- Dynamically set variables for the server or cypress should be managed within the `.env.*` files, rather than in the docker-compose files, to streamline their management.
|
||||
|
||||
##### How to control which tests to run
|
||||
|
||||
The `TEST_FILTER` variable will control which test files to run Cypress tests against. Please check the `e2e-tests/cypress/run_tests.js` file for details about its format.
|
||||
- If new variables need to be passed to any of the containers:
|
||||
* If their value is fixed (e.g. a static server configuration): these may be simply added to the `.ci/server.override.yml` file, to the appropriate container.
|
||||
* If you need to introduce variables that you want to control from `.ci/env`: you need to update the scripts under the `.ci/` dir, and configure them to write the new variables' values over to the appropriate `.env.*` file. In particular, avoid defining variables that depend on other variables within the docker-compose override files: this is to ensure uniformity in their availability, and simplifies the question of what container has access to which variable considerably.
|
||||
|
@ -95,6 +95,7 @@
|
||||
"format": ["PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
2,
|
||||
|
17326
e2e-tests/cypress/package-lock.json
generated
17326
e2e-tests/cypress/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,64 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.21.8",
|
||||
"@babel/eslint-plugin": "7.19.1",
|
||||
"@cypress/request": "2.88.11",
|
||||
"@mattermost/types": "7.10.0",
|
||||
"@testing-library/cypress": "9.0.0",
|
||||
"@types/async": "3.2.20",
|
||||
"@types/authenticator": "1.1.1",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/fs-extra": "11.0.1",
|
||||
"@types/lodash": "4.14.194",
|
||||
"@babel/eslint-parser": "7.22.15",
|
||||
"@babel/eslint-plugin": "7.22.10",
|
||||
"@cypress/request": "3.0.1",
|
||||
"@mattermost/client": "9.0.0",
|
||||
"@mattermost/types": "9.0.0",
|
||||
"@testing-library/cypress": "10.0.1",
|
||||
"@types/async": "3.2.21",
|
||||
"@types/authenticator": "1.1.2",
|
||||
"@types/express": "4.17.18",
|
||||
"@types/fs-extra": "11.0.2",
|
||||
"@types/lodash": "4.14.199",
|
||||
"@types/lodash.intersection": "4.4.7",
|
||||
"@types/lodash.mapkeys": "4.6.7",
|
||||
"@types/lodash.without": "4.4.7",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/mochawesome": "6.2.1",
|
||||
"@types/pdf-parse": "1.1.1",
|
||||
"@types/recursive-readdir": "2.2.1",
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/uuid": "9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
||||
"@typescript-eslint/parser": "5.59.2",
|
||||
"@types/mime-types": "2.1.2",
|
||||
"@types/mochawesome": "6.2.2",
|
||||
"@types/pdf-parse": "1.1.2",
|
||||
"@types/recursive-readdir": "2.2.2",
|
||||
"@types/shelljs": "0.8.13",
|
||||
"@types/uuid": "9.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||
"@typescript-eslint/parser": "6.7.4",
|
||||
"async": "3.2.4",
|
||||
"authenticator": "1.1.5",
|
||||
"aws-sdk": "2.1371.0",
|
||||
"axios": "1.4.0",
|
||||
"axios-retry": "3.4.0",
|
||||
"chai": "4.3.7",
|
||||
"aws-sdk": "2.1468.0",
|
||||
"axios": "1.5.1",
|
||||
"axios-retry": "3.8.0",
|
||||
"chai": "4.3.10",
|
||||
"chalk": "4.1.2",
|
||||
"client-oauth2": "github:larkox/js-client-oauth2#e24e2eb5dfcbbbb3a59d095e831dbe0012b0ac49",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "12.11.0",
|
||||
"cypress": "13.3.0",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"cypress-multi-reporters": "1.6.3",
|
||||
"cypress-plugin-tab": "1.0.5",
|
||||
"cypress-wait-until": "1.7.2",
|
||||
"dayjs": "1.11.7",
|
||||
"cypress-wait-until": "2.0.1",
|
||||
"dayjs": "1.11.10",
|
||||
"deepmerge": "4.3.1",
|
||||
"dotenv": "16.0.3",
|
||||
"eslint": "8.39.0",
|
||||
"eslint-import-resolver-webpack": "0.13.2",
|
||||
"eslint-plugin-cypress": "2.13.3",
|
||||
"dotenv": "16.3.1",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-import-resolver-webpack": "0.13.7",
|
||||
"eslint-plugin-cypress": "2.15.1",
|
||||
"eslint-plugin-header": "3.1.1",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#5b0c972eacf19286e4c66221b39113bf8728a99e",
|
||||
"eslint-plugin-no-only-tests": "3.1.0",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"express": "4.18.2",
|
||||
"extract-zip": "2.0.1",
|
||||
"knex": "2.4.2",
|
||||
"knex": "2.5.1",
|
||||
"localforage": "1.10.0",
|
||||
"lodash.intersection": "4.4.0",
|
||||
"lodash.mapkeys": "4.6.0",
|
||||
"lodash.without": "4.4.0",
|
||||
"lodash.xor": "4.5.0",
|
||||
"mattermost-redux": "5.33.1",
|
||||
"mime": "3.0.0",
|
||||
"mime-types": "2.1.35",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-junit-reporter": "2.2.0",
|
||||
"mocha-junit-reporter": "2.2.1",
|
||||
"mocha-multi-reporters": "1.5.1",
|
||||
"mochawesome": "7.1.3",
|
||||
"mochawesome-merge": "4.3.0",
|
||||
@ -67,16 +67,23 @@
|
||||
"mysql": "2.18.1",
|
||||
"path": "0.12.7",
|
||||
"pdf-parse": "1.1.1",
|
||||
"pg": "8.10.0",
|
||||
"pg": "8.11.3",
|
||||
"recursive-readdir": "2.2.3",
|
||||
"shelljs": "0.8.5",
|
||||
"timezones.json": "1.7.0",
|
||||
"typescript": "5.0.4",
|
||||
"uuid": "9.0.0",
|
||||
"timezones.json": "1.7.1",
|
||||
"typescript": "5.2.2",
|
||||
"uuid": "9.0.1",
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"overrides": {
|
||||
"@mattermost/client": {
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"@mattermost/types": {
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
"check-types": "tsc -b",
|
||||
"cypress:open": "cross-env TZ=Etc/UTC cypress open",
|
||||
"cypress:run": "cross-env TZ=Etc/UTC cypress run",
|
||||
@ -92,8 +99,5 @@
|
||||
"uniq-meta": "grep -r \"^// $META:\" cypress | grep -ow '@\\w*' | sort | uniq",
|
||||
"check": "eslint --ext .js,.ts . --quiet --cache",
|
||||
"fix": "eslint --ext .js,.ts . --quiet --fix --cache"
|
||||
},
|
||||
"dependencies": {
|
||||
"patch-package": "7.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
diff --git a/node_modules/@testing-library/cypress/dist/index.js b/node_modules/@testing-library/cypress/dist/index.js
|
||||
index 9a03c94..b2d3aac 100644
|
||||
--- a/node_modules/@testing-library/cypress/dist/index.js
|
||||
+++ b/node_modules/@testing-library/cypress/dist/index.js
|
||||
@@ -38,7 +38,7 @@ function createQuery(queryName, implementationName) {
|
||||
};
|
||||
const log = options.log !== false && Cypress.log({
|
||||
name: queryName,
|
||||
- type: this.get('prev').get('chainerId') === this.get('chainerId') ? 'child' : 'parent',
|
||||
+ type: this.get('prev') && this.get('prev').get('chainerId') === this.get('chainerId') ? 'child' : 'parent',
|
||||
message: inputArr,
|
||||
timeout: options.timeout,
|
||||
consoleProps: () => consoleProps
|
@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
describe('Card badges', () => {
|
||||
before(() => {
|
||||
// # Login as new user
|
||||
cy.apiInitSetup({loginAfter: true});
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
it('MM-T5395 Shows and hides card badges', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// Create new board
|
||||
cy.uiCreateNewBoard('Testing');
|
||||
|
||||
// Add a new card
|
||||
cy.uiAddNewCard('Card');
|
||||
|
||||
// Add some comments
|
||||
cy.log('**Add some comments**');
|
||||
addComment('Some comment');
|
||||
addComment('Another comment');
|
||||
addComment('Additional comment');
|
||||
|
||||
// Add card description
|
||||
cy.log('**Add card description**');
|
||||
cy.findByText('Add a description...').click();
|
||||
cy.findByRole('combobox').type('## Header\n- [ ] one\n- [x] two{esc}');
|
||||
|
||||
// Add checkboxes
|
||||
cy.log('**Add checkboxes**');
|
||||
cy.findByRole('button', {name: 'Add content'}).click();
|
||||
cy.findByRole('button', {name: 'checkbox'}).click();
|
||||
cy.focused().type('three{enter}');
|
||||
cy.focused().type('four{enter}');
|
||||
cy.focused().type('{esc}');
|
||||
cy.findByDisplayValue('three').prev().click();
|
||||
|
||||
// Close card dialog
|
||||
cy.log('**Close card dialog**');
|
||||
cy.findByRole('button', {name: 'Close dialog'}).click();
|
||||
cy.findByRole('dialog').should('not.exist');
|
||||
|
||||
// Show card badges
|
||||
cy.log('**Show card badges**');
|
||||
cy.findByRole('button', {name: 'Properties menu'}).click();
|
||||
cy.findByRole('button', {name: 'Comments and description'}).click();
|
||||
cy.findByTitle('This card has a description').should('exist');
|
||||
cy.findByTitle('Comments').contains('3').should('exist');
|
||||
cy.findByTitle('Checkboxes').contains('2/4').should('exist');
|
||||
|
||||
// Hide card badges
|
||||
cy.log('**Hide card badges**');
|
||||
cy.findByRole('button', {name: 'Comments and description'}).click();
|
||||
cy.findByRole('button', {name: 'Properties menu'}).click();
|
||||
cy.findByTitle('This card has a description').should('not.exist');
|
||||
cy.findByTitle('Comments').should('not.exist');
|
||||
cy.findByTitle('Checkboxes').should('not.exist');
|
||||
});
|
||||
|
||||
const addComment = (text: string) => {
|
||||
cy.findByText('Add a comment...').click();
|
||||
cy.findByRole('combobox').type(text).blur();
|
||||
cy.findByRole('button', {name: 'Send'}).click();
|
||||
};
|
||||
});
|
@ -1,118 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
describe('Card URL Property', () => {
|
||||
before(() => {
|
||||
// # Login as new user
|
||||
cy.apiInitSetup({loginAfter: true});
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
const url = 'https://mattermost.com';
|
||||
const changedURL = 'https://mattermost.com/blog';
|
||||
|
||||
it('MM-T5396 Allows to create and edit URL property', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// Create new board
|
||||
cy.uiCreateNewBoard('Testing');
|
||||
|
||||
// Add a new card
|
||||
cy.uiAddNewCard('Card');
|
||||
|
||||
// Add URL property
|
||||
cy.log('**Add URL property**');
|
||||
cy.findByRole('button', {name: '+ Add a property'}).click();
|
||||
cy.findByRole('button', {name: 'URL'}).click();
|
||||
cy.findByRole('textbox', {name: 'URL'}).type('{enter}');
|
||||
|
||||
// Enter URL
|
||||
cy.log('**Enter URL**');
|
||||
cy.findByPlaceholderText('Empty').type(`${url}{enter}`);
|
||||
|
||||
// Check buttons
|
||||
cy.log('**Check link**');
|
||||
cy.get('.URLProperty').trigger('mouseover');
|
||||
|
||||
cy.log('**Check buttons**');
|
||||
|
||||
// Change URL
|
||||
cy.log('**Change URL**');
|
||||
|
||||
cy.get('.URLProperty Button[title=\'Edit\']').click({force: true});
|
||||
cy.findByRole('textbox', {name: url}).clear().type(`${changedURL}{enter}`);
|
||||
cy.findByRole('link', {name: changedURL}).should('exist');
|
||||
|
||||
// Close card dialog
|
||||
cy.log('**Close card dialog**');
|
||||
cy.findByRole('button', {name: 'Close dialog'}).click();
|
||||
cy.findByRole('dialog').should('not.exist');
|
||||
|
||||
// Show URL property
|
||||
showURLProperty();
|
||||
|
||||
// Copy URL to clipboard
|
||||
cy.log('**Copy URL to clipboard**');
|
||||
cy.document().then((doc) => cy.spy(doc, 'execCommand')).as('exec');
|
||||
cy.get('.URLProperty Button[title=\'Edit\']').should('not.exist');
|
||||
cy.get('.URLProperty Button[title=\'Copy\']').click({force: true});
|
||||
cy.findByText('Copied!').should('exist');
|
||||
cy.findByText('Copied!').should('not.exist');
|
||||
cy.get('@exec').should('have.been.calledOnceWith', 'copy');
|
||||
|
||||
// Add table view
|
||||
addView('Table');
|
||||
|
||||
// Check buttons
|
||||
cy.log('**Check buttons**');
|
||||
cy.get('.URLProperty Button[title=\'Edit\']').should('exist');
|
||||
cy.get('.URLProperty Button[title=\'Copy\']').should('exist');
|
||||
cy.findByRole('button', {name: 'Copy'}).should('not.exist');
|
||||
|
||||
// Add gallery view
|
||||
addView('Gallery');
|
||||
showURLProperty();
|
||||
|
||||
// Check buttons
|
||||
cy.log('**Check buttons**');
|
||||
cy.get('.URLProperty Button[title=\'Edit\']').should('not.exist');
|
||||
cy.get('.URLProperty Button[title=\'Copy\']').should('exist');
|
||||
|
||||
// Add calendar view
|
||||
addView('Calendar');
|
||||
showURLProperty();
|
||||
|
||||
// Check buttons
|
||||
cy.log('**Check buttons**');
|
||||
cy.get('.URLProperty Button[title=\'Edit\']').should('not.exist');
|
||||
cy.get('.URLProperty Button[title=\'Copy\']').should('exist');
|
||||
});
|
||||
|
||||
type ViewType = 'Board' | 'Table' | 'Gallery' | 'Calendar'
|
||||
|
||||
const addView = (type: ViewType) => {
|
||||
cy.log(`**Add ${type} view**`);
|
||||
|
||||
cy.findByRole('button', {name: 'View menu'}).click();
|
||||
cy.findByText('Add view').trigger('mouseover');
|
||||
cy.findByRole('button', {name: type}).click();
|
||||
cy.findByRole('textbox', {name: `${type} view`}).should('exist');
|
||||
};
|
||||
|
||||
const showURLProperty = () => {
|
||||
cy.log('**Show URL property**');
|
||||
cy.findByRole('button', {name: 'Properties'}).click();
|
||||
cy.findByRole('button', {name: 'URL'}).click();
|
||||
cy.findByRole('button', {name: 'Properties'}).click();
|
||||
cy.findByRole('link', {name: changedURL}).should('exist');
|
||||
};
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
describe('channels > channel header', {testIsolation: true}, () => {
|
||||
let testTeam: {name: string};
|
||||
let testUser: {username: string};
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
describe('App Bar enabled', () => {
|
||||
it('webapp should hide the Boards channel header button', () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate directly to a channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify channel header button is not showing
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('[data-testid="boardsIcon"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('App Bar disabled', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
it('webapp should show the Boards channel header button', () => {
|
||||
// # Navigate directly to a channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify channel header button is showing
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('#incidentIcon').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('tooltip text should show "Boards" for Boards channel header button', () => {
|
||||
// # Navigate directly to a channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Hover over the channel header icon
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('[data-testid="boardsIcon"]').trigger('mouseover');
|
||||
});
|
||||
|
||||
// * Verify tooltip text
|
||||
cy.get('#pluginTooltip').contains('Boards');
|
||||
});
|
||||
|
||||
it('webapp should make the Boards channel header button active when opened', () => {
|
||||
// # Navigate directly to a channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
cy.get('#channel-header').within(() => {
|
||||
// # Click the channel header button
|
||||
cy.get('[data-testid="boardsIcon"]').as('icon').click();
|
||||
|
||||
// * Verify channel header button is showing active className
|
||||
cy.get('@icon').parent().
|
||||
should('have.class', 'channel-header__icon--active-inverted');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,339 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
import timeouts from '../../fixtures/timeouts';
|
||||
|
||||
describe('Create and delete board / card', () => {
|
||||
const timestamp = new Date().toLocaleString();
|
||||
const boardTitle = `Test Board (${timestamp})`;
|
||||
const cardTitle = `Test Card (${timestamp})`;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as new user
|
||||
cy.apiAdminLogin().apiInitSetup({loginAfter: true});
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
it('MM-T4274 Create an Empty Board', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// Tests for template selector
|
||||
cy.findByText('Use this template').should('exist').click();
|
||||
|
||||
// Some options are present
|
||||
cy.contains('Meeting Agenda').should('exist');
|
||||
cy.contains('Personal Goals').should('exist');
|
||||
cy.contains('Project Tasks').should('exist');
|
||||
|
||||
// Create empty board
|
||||
cy.findByText('Create an empty board').should('exist').click({force: true});
|
||||
cy.get('.BoardComponent').should('exist');
|
||||
|
||||
// Change Title
|
||||
cy.findByPlaceholderText('Untitled board').should('be.visible').wait(timeouts.HALF_SEC).as('editableTitle');
|
||||
cy.get('@editableTitle').should('be.visible').
|
||||
clear().
|
||||
type('Testing').
|
||||
type('{enter}').
|
||||
should('have.value', 'Testing');
|
||||
});
|
||||
|
||||
it('MM-T4275 Set up Board description', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// # Create an empty board and change tile to Testing
|
||||
cy.findByText('Create an empty board').should('exist').click({force: true});
|
||||
cy.get('.BoardComponent').should('exist');
|
||||
|
||||
// # Change Title
|
||||
cy.findByPlaceholderText('Untitled board').should('be.visible').wait(timeouts.HALF_SEC);
|
||||
|
||||
// * Assert that the title is changed to "testing"
|
||||
cy.findByPlaceholderText('Untitled board').
|
||||
clear().
|
||||
type('Testing').
|
||||
type('{enter}').
|
||||
should('have.value', 'Testing');
|
||||
|
||||
// # "Add icon" and "Show description" options appear
|
||||
cy.findByText('Add icon').should('exist').click({force: true});
|
||||
cy.findByText('show description').should('exist').click({force: true});
|
||||
|
||||
// # Click on "Add a description" below the board title and type "for testing purposes only"
|
||||
cy.findByText('Add a description...').should('be.visible').wait(timeouts.HALF_SEC);
|
||||
|
||||
// * Assert that the editable description should be visible
|
||||
cy.findByText('Add a description...').should('be.visible');
|
||||
cy.findByText('Add a description...').click({force: true});
|
||||
cy.get('.description').
|
||||
click().
|
||||
get('.description .MarkdownEditorInput').
|
||||
type('for testing purposes only');
|
||||
|
||||
// # Click to other element to give some time for the description to be saved.
|
||||
cy.findByPlaceholderText('Untitled board').click();
|
||||
|
||||
// * Assert that the description is changed to "for testing purposes only"
|
||||
cy.findByText('for testing purposes only').should('be.visible');
|
||||
|
||||
// # Hide Description options should appear and click on it to hide description
|
||||
cy.findByText('hide description').should('exist').click({force: true});
|
||||
|
||||
// * Assert that description should not appear"
|
||||
cy.get('.description').should('not.exist');
|
||||
|
||||
// # Show Description options should appear and click on it to show description
|
||||
cy.findByText('show description').should('exist').click({force: true});
|
||||
|
||||
// * Assert that the description "for testing purposes should be visible"
|
||||
cy.findByText('for testing purposes only').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T4276 Set up Board emoji', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// # Create an empty board and change tile to Testing
|
||||
cy.findByText('Create an empty board').should('exist').click({force: true});
|
||||
cy.get('.BoardComponent').should('exist');
|
||||
|
||||
// # Change Title
|
||||
cy.findByPlaceholderText('Untitled board').should('be.visible').wait(timeouts.HALF_SEC);
|
||||
|
||||
// * Assert that the title is changed to "testing"
|
||||
cy.findByPlaceholderText('Untitled board').
|
||||
clear().
|
||||
type('Testing').
|
||||
type('{enter}').
|
||||
should('have.value', 'Testing');
|
||||
|
||||
// # "Add icon" and "Show description" options appear
|
||||
cy.findByText('Add icon').should('exist');
|
||||
cy.findByText('show description').should('exist');
|
||||
|
||||
// # Click on "Add icon"
|
||||
cy.findByText('Add icon').should('exist').click({force: true});
|
||||
|
||||
// * Assert that a random emoji is selected and added at the beginning of the board title
|
||||
cy.get('.IconSelector').should('exist');
|
||||
|
||||
// # Click on the emoji next to the board title
|
||||
cy.get('.IconSelector .MenuWrapper').should('exist').click({force: true});
|
||||
|
||||
// * Assert that Dropdown menu with 3 options appears
|
||||
cy.findByText('Random').should('exist');
|
||||
cy.findByText('Pick icon').should('exist');
|
||||
cy.findByText('Remove icon').should('exist');
|
||||
|
||||
// # Hover your mouse over the "Pick Icon" option
|
||||
cy.findByText('Pick icon').trigger('mouseover');
|
||||
|
||||
// * Assert that emoji picker menu appears
|
||||
cy.get('.IconSelector .menu-contents').should('exist');
|
||||
|
||||
// # Click on the emoji from the picker
|
||||
cy.get('.EmojiPicker').should('exist').and('be.visible').within(() => {
|
||||
// # Click on the emoji
|
||||
cy.get("[aria-label='😀, grinning']").should('exist');
|
||||
cy.get("[aria-label='😀, grinning']").eq(0).click({force: true});
|
||||
});
|
||||
|
||||
// * Assert that Selected emoji is now displayed next to the board title
|
||||
cy.get('.IconSelector span').contains('😀');
|
||||
|
||||
// # Click on the emoji next to the board title
|
||||
cy.get('.IconSelector .MenuWrapper').should('exist').click({force: true});
|
||||
|
||||
// * Assert that Dropdown menu with 3 options appears
|
||||
cy.findByText('Random').should('exist');
|
||||
cy.findByText('Pick icon').should('exist');
|
||||
cy.findByText('Remove icon').should('exist');
|
||||
|
||||
// # Click "Remove icon"
|
||||
cy.findByText('Remove icon').click({force: true});
|
||||
|
||||
// * Assert that Icon next to the board title is removed
|
||||
cy.get('.IconSelector').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T5397 Can create and delete a board and a card', () => {
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
// Change board title
|
||||
cy.log('**Change board title**');
|
||||
cy.get('.Editable.title').
|
||||
type(boardTitle).
|
||||
type('{enter}').
|
||||
should('have.value', boardTitle);
|
||||
|
||||
// Rename board view
|
||||
cy.log('**Rename board view**');
|
||||
const boardViewTitle = `Test board (${timestamp})`;
|
||||
cy.get(".ViewHeader>.viewSelector>.Editable[title='Board view']").should('exist');
|
||||
cy.get('.ViewHeader>.viewSelector>.Editable').
|
||||
clear().
|
||||
type(boardViewTitle).
|
||||
type('{esc}');
|
||||
cy.get(`.ViewHeader .Editable[title='${boardViewTitle}']`).should('exist');
|
||||
|
||||
// Create card
|
||||
cy.log('**Create card**');
|
||||
cy.get('.ViewHeader').contains('New').click();
|
||||
cy.get('.CardDetail').should('exist');
|
||||
|
||||
//Check title has focus when card is created
|
||||
cy.log('**Check title has focus when card is created**');
|
||||
cy.get('.CardDetail .EditableArea.title').
|
||||
should('have.focus');
|
||||
|
||||
// Change card title
|
||||
cy.log('**Change card title**');
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.get('.CardDetail .EditableArea.title').
|
||||
click().
|
||||
should('have.focus').
|
||||
type(cardTitle).
|
||||
should('have.value', cardTitle);
|
||||
|
||||
// Close card dialog
|
||||
cy.log('**Close card dialog**');
|
||||
cy.get('.Dialog Button[title=\'Close dialog\']').
|
||||
should('be.visible').
|
||||
click();
|
||||
|
||||
// Create a card by clicking on the + button
|
||||
cy.log('**Create a card by clicking on the + button**');
|
||||
cy.get('.KanbanColumnHeader button .AddIcon').click();
|
||||
cy.get('.CardDetail').should('exist');
|
||||
cy.get('.Dialog.dialog-back .wrapper').click({force: true});
|
||||
|
||||
// Create table view
|
||||
cy.log('**Create table view**');
|
||||
cy.get('.ViewHeader').get('.DropdownIcon').first().parent().click();
|
||||
cy.get('.ViewHeader').contains('Add view').trigger('mouseover');
|
||||
cy.get('.ViewHeader').
|
||||
contains('Add view').
|
||||
parent().
|
||||
contains('Table').
|
||||
click();
|
||||
cy.get(".ViewHeader .Editable[title='Table view']").should('exist');
|
||||
cy.get(`.TableRow [value='${cardTitle}']`).should('exist');
|
||||
|
||||
// Rename table view
|
||||
cy.log('**Rename table view**');
|
||||
const tableViewTitle = `Test table (${timestamp})`;
|
||||
cy.get(".ViewHeader .Editable[title='Table view']").
|
||||
clear().
|
||||
type(tableViewTitle).
|
||||
type('{esc}');
|
||||
cy.get(`.ViewHeader .Editable[title='${tableViewTitle}']`).should('exist');
|
||||
|
||||
// Sort the table
|
||||
cy.log('**Sort the table**');
|
||||
cy.get('.ViewHeader').contains('Sort').click();
|
||||
cy.get('.ViewHeader').
|
||||
contains('Sort').
|
||||
parent().
|
||||
contains('Name').
|
||||
click();
|
||||
|
||||
// Delete board
|
||||
cy.log('**Delete board**');
|
||||
cy.get('.Sidebar .octo-sidebar-list').then((el) => {
|
||||
cy.log(el.text());
|
||||
});
|
||||
cy.get('.Sidebar .octo-sidebar-list').
|
||||
contains(boardTitle).
|
||||
parent().
|
||||
find('.MenuWrapper').
|
||||
find('button.IconButton').
|
||||
click({force: true});
|
||||
cy.contains('Delete board').click({force: true});
|
||||
cy.get('.DeleteBoardDialog button.danger').click({force: true});
|
||||
cy.contains(boardTitle).should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T4433 Scrolls the kanban board when dragging card to edge', () => {
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
// Create 10 empty groups
|
||||
cy.log('**Create new empty groups**');
|
||||
for (let i = 0; i < 10; i++) {
|
||||
cy.contains('+ Add a group').scrollIntoView().should('be.visible').click();
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('have.length', i + 1);
|
||||
}
|
||||
|
||||
// Create empty card in last group
|
||||
cy.log('**Create new empty card in first group**');
|
||||
cy.get('.octo-board-column').last().contains('+ New').scrollIntoView().click();
|
||||
cy.get('.Dialog').should('exist');
|
||||
cy.get('.Dialog Button[title=\'Close dialog\']').should('be.visible').click();
|
||||
cy.get('.KanbanCard').scrollIntoView().should('exist');
|
||||
|
||||
// Drag card to right corner and expect scroll to occur
|
||||
cy.get('.Kanban').invoke('scrollLeft').should('not.equal', 0);
|
||||
|
||||
cy.get('.KanbanCard').
|
||||
trigger('dragstart');
|
||||
|
||||
// wait necessary to trigger scroll animation for some time
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.get('.Kanban').
|
||||
trigger('dragover', {clientX: 400, clientY: Cypress.config().viewportHeight / 2}).
|
||||
wait(timeouts.TEN_SEC).
|
||||
trigger('dragend');
|
||||
|
||||
cy.get('.Kanban').invoke('scrollLeft').should('equal', 0);
|
||||
});
|
||||
|
||||
it('MM-T5398 cut/undo/redo work in comments', () => {
|
||||
const isMAC = navigator.userAgent.indexOf('Mac') !== -1;
|
||||
const ctrlKey = isMAC ? 'meta' : 'ctrl';
|
||||
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
// Create card
|
||||
cy.log('**Create card**');
|
||||
cy.get('.ViewHeader').contains('New').click();
|
||||
cy.get('.CardDetail').should('exist');
|
||||
|
||||
cy.log('**Add comment**');
|
||||
cy.get('.CommentsList').
|
||||
should('exist').
|
||||
findAllByTestId('preview-element').
|
||||
click();
|
||||
|
||||
cy.get('.CommentsList .MarkdownEditor').
|
||||
type('Test Text');
|
||||
|
||||
cy.log('**Cut comment**');
|
||||
cy.get('.CommentsList .MarkdownEditor').
|
||||
type('{selectAll}').
|
||||
trigger('cut').
|
||||
should('have.text', '');
|
||||
|
||||
cy.log('**Undo comment**');
|
||||
cy.get('.CommentsList .MarkdownEditor').
|
||||
type(`{${ctrlKey}+z}`).
|
||||
should('have.text', 'Test Text');
|
||||
|
||||
cy.log('**Redo comment**');
|
||||
cy.get('.CommentsList .MarkdownEditor').
|
||||
type(`{shift+${ctrlKey}+z}`).
|
||||
should('have.text', '');
|
||||
});
|
||||
});
|
@ -1,68 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
describe('Group board by different properties', () => {
|
||||
before(() => {
|
||||
// # Login as new user
|
||||
cy.apiInitSetup({loginAfter: true});
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
it('MM-T4291 Group by different property', () => {
|
||||
cy.visit('/boards');
|
||||
|
||||
// Create new board
|
||||
cy.uiCreateNewBoard('Testing');
|
||||
|
||||
// Add a new group
|
||||
cy.uiAddNewGroup('Group 1');
|
||||
|
||||
// Add a new card to the group
|
||||
cy.log('**Add a new card to the group**');
|
||||
cy.findAllByRole('button', {name: '+ New'}).eq(1).click();
|
||||
cy.findByRole('dialog').should('exist');
|
||||
cy.findByTestId('select-non-editable').findByText('Group 1').should('exist');
|
||||
cy.get('#mainBoardBody').findByText('Untitled').should('exist');
|
||||
|
||||
// Add new select property
|
||||
cy.log('**Add new select property**');
|
||||
cy.findAllByRole('button', {name: '+ Add a property'}).click();
|
||||
cy.findAllByRole('button', {name: 'Select'}).click();
|
||||
cy.findByRole('textbox', {name: 'Select'}).type('{enter}');
|
||||
cy.findByRole('dialog').findByRole('button', {name: 'Select'}).should('exist');
|
||||
|
||||
// Close card dialog
|
||||
cy.log('**Close card dialog**');
|
||||
cy.findByRole('button', {name: 'Close dialog'}).should('exist').click();
|
||||
|
||||
cy.findByRole('dialog').should('not.exist');
|
||||
|
||||
// Group by new select property
|
||||
cy.log('**Group by new select property**');
|
||||
cy.findByRole('button', {name: /Group by:/}).click();
|
||||
cy.findByRole('button', {name: 'Status'}).get('.CheckIcon').should('exist');
|
||||
cy.findByRole('button', {name: 'Select'}).click();
|
||||
cy.findByTitle(/empty Select property/).contains('No Select');
|
||||
cy.get('#mainBoardBody').findByText('Untitled').should('exist');
|
||||
|
||||
// Add another new group
|
||||
cy.log('**Add another new group**');
|
||||
cy.findByRole('button', {name: '+ Add a group'}).click();
|
||||
cy.findByRole('textbox', {name: 'New group'}).should('exist');
|
||||
|
||||
// Add a new card to another group
|
||||
cy.log('**Add a new card to another group**');
|
||||
cy.findAllByRole('button', {name: '+ New'}).eq(1).click();
|
||||
cy.findByRole('dialog').should('exist');
|
||||
cy.findAllByTestId('select-non-editable').last().findByText('New group').should('exist');
|
||||
});
|
||||
});
|
@ -1,104 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @boards
|
||||
|
||||
describe('Manage groups', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as new user
|
||||
cy.apiAdminLogin().apiInitSetup({loginAfter: true});
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
it('MM-T4284 Adding a group', () => {
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
cy.contains('+ Add a group').click({force: true});
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('exist');
|
||||
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').
|
||||
clear().
|
||||
type('Group 1').
|
||||
blur();
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'Group 1\']').should('exist');
|
||||
});
|
||||
|
||||
it('MM-T4285 Adding group color', () => {
|
||||
// Visit a page and create new empty board
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
cy.contains('+ Add a group').click({force: true});
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('exist');
|
||||
|
||||
cy.get('.KanbanColumnHeader').last().within(() => {
|
||||
cy.get('.icon-dots-horizontal').click({force: true});
|
||||
cy.get('.menu-options').should('exist').first().within(() => {
|
||||
cy.contains('Hide').should('exist');
|
||||
cy.contains('Delete').should('exist');
|
||||
|
||||
// Some colours
|
||||
cy.contains('Brown').should('exist');
|
||||
cy.contains('Gray').should('exist');
|
||||
cy.contains('Orange').should('exist');
|
||||
|
||||
// Click on green
|
||||
cy.contains('Green').should('be.visible').click();
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('.KanbanColumnHeader').last().within(() => {
|
||||
cy.get('.Label.propColorGreen').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4287 Hiding/unhiding a group', () => {
|
||||
// Step 1: Create an empty board and add a group
|
||||
cy.visit('/boards');
|
||||
cy.uiCreateEmptyBoard();
|
||||
|
||||
cy.contains('+ Add a group').click({force: true});
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('exist');
|
||||
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').
|
||||
clear().
|
||||
type('Group 1').
|
||||
blur();
|
||||
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'Group 1\']').should('exist');
|
||||
|
||||
// Step 2: Click on the three dots next to "Group 1"
|
||||
cy.get('.KanbanColumnHeader').last().within(() => {
|
||||
cy.get('.icon-dots-horizontal').click({force: true});
|
||||
cy.get('.menu-options').should('exist').first().within(() => {
|
||||
cy.contains('Hide').should('exist');
|
||||
cy.contains('Delete').should('exist');
|
||||
|
||||
// Some colours
|
||||
cy.contains('Brown').should('exist');
|
||||
cy.contains('Gray').should('exist');
|
||||
cy.contains('Orange').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
// Step 3: Click on "Hide"
|
||||
cy.contains('Hide').click({force: true});
|
||||
cy.get('.octo-board-hidden-item').contains('Group 1').should('exist');
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'Group 1\']').should('not.exist');
|
||||
|
||||
// Step 4: Click "Group 1", then click "Show" in the dropdown
|
||||
cy.contains('Group 1').click({force: true});
|
||||
cy.contains('Show').click({force: true});
|
||||
cy.get('.octo-board-hidden-item').should('not.exist');
|
||||
cy.get('.KanbanColumnHeader .Editable[value=\'Group 1\']').should('exist');
|
||||
});
|
||||
});
|
@ -34,7 +34,7 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
|
||||
{key: 'desktop', label: 'Desktop Notifications', type: 'radio'},
|
||||
{key: 'email', label: 'Email Notifications', type: 'radio'},
|
||||
{key: 'push', label: 'Mobile Push Notifications', type: 'radio'},
|
||||
{key: 'keys', label: 'Words That Trigger Mentions', type: 'checkbox'},
|
||||
{key: 'keysWithNotification', label: 'Keywords that trigger Notifications', type: 'checkbox'},
|
||||
{key: 'comments', label: 'Reply notifications', type: 'radio'},
|
||||
],
|
||||
display: [
|
||||
|
@ -130,10 +130,10 @@ describe('Authentication', () => {
|
||||
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Password');
|
||||
|
||||
cy.findByTestId('passwordMinimumLengthinput').should('be.visible').and('have.value', '8');
|
||||
cy.findByLabelText('At least one lowercase letter').get('input').should('not.be.checked');
|
||||
cy.findByLabelText('At least one uppercase letter').get('input').should('not.be.checked');
|
||||
cy.findByLabelText('At least one number').get('input').should('not.be.checked');
|
||||
cy.findByLabelText('At least one symbol (e.g. "~!@#$%^&*()")').get('input').should('not.be.checked');
|
||||
cy.findByText('At least one lowercase letter').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one uppercase letter').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one number').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one symbol (e.g. "~!@#$%^&*()")').siblings().should('not.be.checked');
|
||||
|
||||
if (!isCloudLicensed) {
|
||||
cy.findByTestId('maximumLoginAttemptsinput').should('be.visible').and('have.value', '10');
|
||||
|
@ -16,6 +16,7 @@ describe('Leave an archived channel', () => {
|
||||
let testTeam;
|
||||
let offTopicUrl;
|
||||
const channelType = {
|
||||
all: 'Channel Type: All',
|
||||
public: 'Channel Type: Public',
|
||||
archived: 'Channel Type: Archived',
|
||||
};
|
||||
@ -98,19 +99,18 @@ describe('Leave an archived channel', () => {
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// # Click on dropdown
|
||||
cy.findByText(channelType.public).should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Archived Channels').click();
|
||||
// # Click on dropdown
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Modal should contain created channel
|
||||
cy.get('#moreChannelsList').should('contain', channel.display_name);
|
||||
});
|
||||
// # Click archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
// # Modal should contain created channel
|
||||
cy.get('#moreChannelsList').should('contain', channel.display_name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T1699 - Browse Channels for all channel types shows archived channels option', () => {
|
||||
@ -146,12 +146,12 @@ describe('Leave an archived channel', () => {
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// # Public channel list opens by default
|
||||
cy.findByText(channelType.public).should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # All channel list opens by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Click on archived channels
|
||||
cy.findByText('Archived Channels').click();
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain newly created channels
|
||||
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel.name);
|
||||
@ -199,12 +199,12 @@ describe('Leave an archived channel', () => {
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// # Public channels are shown by default
|
||||
cy.findByText(channelType.public).should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # All channels are shown by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived Channels').click();
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain both archived public channels
|
||||
cy.get('#moreChannelsList').should('contain', archivedPublicChannel1.display_name);
|
||||
@ -253,12 +253,12 @@ describe('Leave an archived channel', () => {
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// # Show public channels is visible by default
|
||||
cy.findByText(channelType.public).should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # Show all channels is visible by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived Channels').click();
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain only the private channel user is a member of
|
||||
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel1.name);
|
||||
@ -287,12 +287,12 @@ describe('Leave an archived channel', () => {
|
||||
// # Click on browse channels
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # More channels modal opens and lands on public channels
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
cy.findByText(channelType.public).should('be.visible').click();
|
||||
// # More channels modal opens and lands on all channels
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived Channels').click();
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # More channels list should contain the archived channel
|
||||
cy.get('#moreChannelsList').should('contain', archivedChannel.display_name);
|
||||
@ -323,7 +323,8 @@ describe('Leave an archived channel', () => {
|
||||
cy.get('#showMoreChannels').click();
|
||||
|
||||
// # Modal should not contain the created channel
|
||||
cy.get('#channelsMoreDropdown').should('not.exist');
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
cy.findByText('Archived channels').should('not.exist');
|
||||
cy.get('#moreChannelsList').should('not.contain', channel.name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
|
@ -15,6 +15,7 @@ import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
import {createPrivateChannel} from '../enterprise/elasticsearch_autocomplete/helpers';
|
||||
|
||||
const channelType = {
|
||||
all: 'Channel Type: All',
|
||||
public: 'Channel Type: Public',
|
||||
archived: 'Channel Type: Archived',
|
||||
};
|
||||
@ -69,8 +70,8 @@ describe('Channels', () => {
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// * Dropdown should be visible, defaulting to "Public Channels"
|
||||
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).wait(TIMEOUTS.HALF_SEC);
|
||||
// * Dropdown should be visible, defaulting to "All Channels"
|
||||
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
|
||||
@ -111,27 +112,27 @@ describe('Channels', () => {
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// # CLick dropdown to open selection
|
||||
cy.get('#channelsMoreDropdown').should('be.visible').click().within((el) => {
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived Channels').should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// * Channel test should be visible as an archived channel in the list
|
||||
cy.wrap(el).should('contain', channelType.archived);
|
||||
});
|
||||
// # CLick dropdown to open selection
|
||||
cy.get('#menuWrapper').should('be.visible').click();
|
||||
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').children().should('have.length', 1).within(() => {
|
||||
cy.findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived channels').should('be.visible').click();
|
||||
|
||||
// * Test channel should be visible as a archived channel in the list
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
// # Click to view archived channel
|
||||
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
|
||||
});
|
||||
// * Menu text should be updated to reflect the selection
|
||||
cy.get('#menuWrapper').should('contain', channelType.archived);
|
||||
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').children().should('have.length', 1).within(() => {
|
||||
cy.findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
|
||||
// * Test channel should be visible as a archived channel in the list
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
// # Click to view archived channel
|
||||
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Assert that channel is archived and new messages can't be posted.
|
||||
@ -177,7 +178,7 @@ describe('Channels', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1702 Search works when changing public/archived options in the dropdown', () => {
|
||||
it('MM-T1702 Search works when changing public/all options in the dropdown', () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
@ -231,32 +232,34 @@ describe('Channels', () => {
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
// * Dropdown should be visible, defaulting to "Public Channels"
|
||||
cy.get('#channelsMoreDropdown').should('be.visible').within((el) => {
|
||||
cy.wrap(el).should('contain', channelType.public);
|
||||
// * Dropdown should be visible, defaulting to "All channels"
|
||||
cy.get('#menuWrapper').should('be.visible').within((el) => {
|
||||
cy.wrap(el).should('contain', channelType.all);
|
||||
});
|
||||
|
||||
// * Users should be able to type and search
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type('iv').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
cy.findByText(newChannel.display_name).should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// * Users should be able to switch to "Archived Channels" list
|
||||
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).click().within((el) => {
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived Channels').should('be.visible').click();
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// * Modal should show the archived channels list
|
||||
cy.wrap(el).should('contain', channelType.archived);
|
||||
}).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
|
||||
cy.get('#moreChannelsList').within(() => {
|
||||
cy.findByText(testArchivedChannel.display_name).should('be.visible');
|
||||
cy.findByText(testPrivateArchivedChannel.display_name).should('be.visible');
|
||||
});
|
||||
// * Users should be able to switch to "Archived Channels" list
|
||||
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).click().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived channels').should('be.visible').click();
|
||||
|
||||
// * Modal menu should be updated accordingly
|
||||
cy.get('#menuWrapper').should('contain', channelType.archived);
|
||||
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
|
||||
cy.get('#moreChannelsList').within(() => {
|
||||
cy.findByText(testArchivedChannel.display_name).should('be.visible');
|
||||
cy.findByText(testPrivateArchivedChannel.display_name).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -287,9 +290,10 @@ function verifyBrowseChannelsModal(isEnabled) {
|
||||
// * Verify that the browse channels modal is open and with or without option to view archived channels
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
if (isEnabled) {
|
||||
cy.get('#channelsMoreDropdown').should('be.visible').and('have.text', channelType.public);
|
||||
cy.get('#menuWrapper').should('be.visible').and('have.text', channelType.all);
|
||||
} else {
|
||||
cy.get('#channelsMoreDropdown').should('not.exist');
|
||||
cy.get('#menuWrapper').click();
|
||||
cy.findByText('Archived channels').should('not.exist');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ function verifyNoChannelToJoinMessage(isVisible) {
|
||||
cy.findByText('No public channels').should(isVisible ? 'be.visible' : 'not.exist');
|
||||
}
|
||||
|
||||
describe('more public channels', () => {
|
||||
describe('browse public channels', () => {
|
||||
let testUser;
|
||||
let otherUser;
|
||||
let testTeam;
|
||||
@ -41,7 +41,7 @@ describe('more public channels', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1664 Channels do not disappear from More Channels modal', () => {
|
||||
it('MM-T1664 Channels do not disappear from Browse Channels modal', () => {
|
||||
// # Login as other user
|
||||
cy.apiLogin(otherUser);
|
||||
|
||||
@ -51,8 +51,14 @@ describe('more public channels', () => {
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
// * Assert that the moreChannelsModel is visible
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').within(() => {
|
||||
// * Assert that the browse channel modal is visible
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
|
||||
// # Click on dropdown
|
||||
cy.findByText('Channel Type: All').should('be.visible').click();
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Public channels').should('be.visible').click();
|
||||
|
||||
// # Click hide joined checkbox if not already checked
|
||||
cy.findByText('Hide Joined').should('be.visible').then(($checkbox) => {
|
||||
if (!$checkbox.prop('checked')) {
|
||||
@ -92,7 +98,13 @@ describe('more public channels', () => {
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
// * Assert the moreChannelsModel is visible
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').within(() => {
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
|
||||
// # Click on dropdown
|
||||
cy.findByText('Channel Type: All').should('be.visible').click();
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Public channels').should('be.visible').click();
|
||||
|
||||
// * Assert that the "No more channels to join" message is visible
|
||||
verifyNoChannelToJoinMessage(true);
|
||||
});
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {UserProfile} from 'mattermost-redux/types/users';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
@ -13,7 +11,7 @@ import {UserProfile} from 'mattermost-redux/types/users';
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Leave and Archive channel actions display as destructive', () => {
|
||||
let testUser: UserProfile;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let offTopicUrl: string;
|
||||
|
||||
before(() => {
|
||||
|
@ -30,6 +30,13 @@ describe('Leave channel', () => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiSaveUserPreference([{
|
||||
user_id: user.id,
|
||||
category: 'crt_thread_pane_step',
|
||||
name: user.id,
|
||||
value: '0',
|
||||
}], user.id);
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('New Channel modal with Boards enabled', () => {
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
});
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
cy.apiLogin(sysadmin);
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T5141 New Channel is created with an associated Board', () => {
|
||||
// # Create new channel with board
|
||||
const channelName = 'Test Channel With Board';
|
||||
cy.uiCreateChannel({
|
||||
prefix: 'channel-',
|
||||
isPrivate: false,
|
||||
purpose: '',
|
||||
name: channelName,
|
||||
createBoard: 'Roadmap',
|
||||
}).then(() => {
|
||||
// * Verify that new channel is in the sidebar and is active
|
||||
cy.url().should('include', `/${testTeam.name}/channels/test-channel`);
|
||||
cy.get('#channelHeaderTitle').should('contain', channelName);
|
||||
cy.get(`.SidebarChannel.active:contains(${channelName})`).should('be.visible');
|
||||
|
||||
// * Verify the board is created - check the message sent
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes('linked the board'));
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
@ -10,29 +10,53 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings @smoke
|
||||
|
||||
import {getRandomId} from '../../../utils';
|
||||
|
||||
describe('Channel Settings', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let firstUser: Cypress.UserProfile;
|
||||
let addedUsersChannel: Cypress.Channel;
|
||||
let username: string;
|
||||
let groupId: string;
|
||||
const usernames: string[] = [];
|
||||
|
||||
const users: Cypress.UserProfile[] = [];
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
firstUser = user;
|
||||
const teamId = testTeam.id;
|
||||
|
||||
// # Add 4 users
|
||||
for (let i = 0; i < 4; i++) {
|
||||
cy.apiCreateUser().then(({user: newUser}) => { // eslint-disable-line
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id);
|
||||
// # Add 10 users
|
||||
for (let i = 0; i < 10; i++) {
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
users.push(newUser);
|
||||
cy.apiAddUserToTeam(teamId, newUser.id);
|
||||
});
|
||||
}
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
cy.apiCreateChannel(teamId, 'channel-test', 'Channel').then(({channel}) => {
|
||||
addedUsersChannel = channel;
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't add team members
|
||||
cy.apiGetRolesByNames(['team_user']).then((result: any) => {
|
||||
if (result.roles) {
|
||||
const role = result.roles[0];
|
||||
const permissions = role.permissions.filter((permission) => {
|
||||
return !(['add_user_to_team'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(firstUser);
|
||||
}).then(() => {
|
||||
groupId = getRandomId();
|
||||
cy.apiCreateCustomUserGroup(`group${groupId}`, `group${groupId}`, [users[0].id, users[1].id]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -42,7 +66,7 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Add users to channel
|
||||
addNumberOfUsersToChannel(1);
|
||||
addNumberOfUsersToChannel(1, false);
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
// * The system message should contain 'added to the channel by you'
|
||||
@ -59,7 +83,7 @@ describe('Channel Settings', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
addNumberOfUsersToChannel(3);
|
||||
addNumberOfUsersToChannel(3, false);
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
||||
@ -82,10 +106,10 @@ describe('Channel Settings', () => {
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type into the input box to search for a user
|
||||
cy.get('#selectItems input').typeWithForce('u');
|
||||
cy.get('#selectItems input').typeWithForce('user');
|
||||
|
||||
// # First add one user in order to see them disappearing from the list
|
||||
cy.get('#multiSelectList > div').first().then((el) => {
|
||||
cy.get('#multiSelectList > div').not(':contains("Already in channel")').first().then((el) => {
|
||||
const childNodes = Array.from(el[0].childNodes);
|
||||
childNodes.map((child: HTMLElement) => usernames.push(child.innerText));
|
||||
|
||||
@ -113,7 +137,7 @@ describe('Channel Settings', () => {
|
||||
});
|
||||
|
||||
// Add two more users
|
||||
addNumberOfUsersToChannel(2);
|
||||
addNumberOfUsersToChannel(2, false);
|
||||
|
||||
// Verify that the system post reflects the number of added users
|
||||
cy.getLastPostId().then((id) => {
|
||||
@ -148,6 +172,179 @@ describe('Channel Settings', () => {
|
||||
});
|
||||
cy.get('body').type('{esc}');
|
||||
});
|
||||
|
||||
it('Add group members to channel', () => {
|
||||
cy.apiLogin(firstUser);
|
||||
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-channel', 'New Channel').then(({channel}) => {
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group'+ id created in beforeAll into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`group${groupId}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
// * The system message should contain 'added to the channel by you'
|
||||
cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
|
||||
// # Verify username link
|
||||
verifyMentionedUserAndProfilePopover(id);
|
||||
});
|
||||
|
||||
// * Check that the number of channel members is 3
|
||||
cy.get('#channelMemberCountText').
|
||||
should('be.visible').
|
||||
and('have.text', '3');
|
||||
});
|
||||
});
|
||||
|
||||
it('Add group members that are not team members', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`newgroup${id}`, `newgroup${id}`, [newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-group-channel', 'New Group Channel').then(({channel}) => {
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`newgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '1 user was not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the team invite banner
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Cancel').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Add group members and guests that are not team members', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
// # Create a guest user
|
||||
cy.apiCreateGuestUser({}).then(({guest}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`guestgroup${id}`, `guestgroup${id}`, [guest.id, newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'group-guest-channel', 'Channel').then(({channel}) => {
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`guestgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '2 users were not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the invite to team portion
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
|
||||
// * Check the guest username is in the warning message and won't be added to the team
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${guest.username} is a guest user`);
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Cancel').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('User doesn\'t have permission to add user to team', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`newgroup${id}`, `newgroup${id}`, [newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-group-channel', 'Channel').then(({channel}) => {
|
||||
cy.apiLogin(firstUser);
|
||||
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`newgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '1 user was not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the team invite banner
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyMentionedUserAndProfilePopover(postId: string) {
|
||||
@ -169,7 +366,7 @@ function verifyMentionedUserAndProfilePopover(postId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function addNumberOfUsersToChannel(num = 1) {
|
||||
function addNumberOfUsersToChannel(num = 1, allowExisting = false) {
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
@ -177,8 +374,14 @@ function addNumberOfUsersToChannel(num = 1) {
|
||||
// * Assert that modal appears
|
||||
// # Click the first row for a number of times
|
||||
Cypress._.times(num, () => {
|
||||
cy.get('#selectItems input').typeWithForce('u');
|
||||
cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#selectItems input').typeWithForce('user');
|
||||
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
if (allowExisting) {
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
} else {
|
||||
cy.get('#multiSelectList').should('exist').children().not(':contains("Already in channel")').first().click();
|
||||
}
|
||||
});
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
|
@ -25,10 +25,22 @@ describe('Collapsed Reply Threads', () => {
|
||||
});
|
||||
|
||||
// # Log in as user that hasn't had CRT enabled before
|
||||
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true, userPrefix: 'tipbutton'}).then(({team, channel}) => {
|
||||
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true, userPrefix: 'tipbutton'}).then(({team, channel, user}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiSaveUserPreference([{
|
||||
user_id: user.id,
|
||||
category: 'crt_thread_pane_step',
|
||||
name: user.id,
|
||||
value: '0',
|
||||
}, {
|
||||
user_id: user.id,
|
||||
category: 'crt_tutorial_triggered',
|
||||
name: user.id,
|
||||
value: '0',
|
||||
}], user.id);
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
|
@ -86,7 +86,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1467 Accessibility Support in More Channels Dialog screen', () => {
|
||||
it('MM-T1467 Accessibility Support in Browse Channels Dialog screen', () => {
|
||||
function getChannelAriaLabel(channel) {
|
||||
return channel.display_name.toLowerCase() + ', ' + channel.purpose.toLowerCase();
|
||||
}
|
||||
@ -117,8 +117,8 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
// # Hide already joined channels
|
||||
cy.findByText('Hide Joined').click();
|
||||
|
||||
// # Focus on the Create Channel button and TAB three time
|
||||
cy.get('#createNewChannelButton').focus().tab().tab().tab();
|
||||
// # Focus on the Create Channel button and TAB four time
|
||||
cy.get('#createNewChannelButton').focus().tab().tab().tab().tab();
|
||||
|
||||
// * Verify channel name is highlighted and reader reads the channel name and channel description
|
||||
cy.get('#moreChannelsList').within(() => {
|
||||
@ -155,11 +155,11 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.findByRole('heading', {name: modalName});
|
||||
|
||||
// * Verify the accessibility support in search input
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
should('have.attr', 'aria-autocomplete', 'list');
|
||||
|
||||
// # Search for a text and then check up and down arrow
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
typeWithForce('u').
|
||||
wait(TIMEOUTS.HALF_SEC).
|
||||
typeWithForce('{downarrow}{downarrow}{downarrow}{uparrow}');
|
||||
@ -184,7 +184,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
});
|
||||
|
||||
// # Search for an invalid text and check if reader can read no results
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
typeWithForce('somethingwhichdoesnotexist').
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
|
@ -10,116 +10,6 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @cloud_only @cloud_trial
|
||||
|
||||
function simulateSubscription() {
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription/invoices', {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{
|
||||
id: 'in_1Lz8b0I67GP2qpb43kcuMyFP',
|
||||
number: '8D53267B-0006',
|
||||
create_at: 1667263490000,
|
||||
total: 65,
|
||||
tax: 0,
|
||||
status: 'open',
|
||||
description: '',
|
||||
period_start: 1664582400000,
|
||||
period_end: 1667260800000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUYiI67GP2qpb48DXFukcJ',
|
||||
total: 65,
|
||||
quantity: 0.06451612903225806,
|
||||
price_per_unit: 1000,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'in_1LntnKI67GP2qpb4VObu3NgP',
|
||||
number: '8D53267B-0005',
|
||||
create_at: 1664584986000,
|
||||
total: 733,
|
||||
tax: 0,
|
||||
status: 'failed',
|
||||
description: '',
|
||||
period_start: 1661990400000,
|
||||
period_end: 1664582400000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUZ2I67GP2qpb45uTS89eb',
|
||||
total: 733,
|
||||
quantity: 0.7333333333333333,
|
||||
price_per_unit: 999,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'in_1LntnKI67GP2qpb4VObu3NgV',
|
||||
number: '8D53267B-0005',
|
||||
create_at: 1664584986000,
|
||||
total: 733,
|
||||
tax: 0,
|
||||
status: 'paid',
|
||||
description: '',
|
||||
period_start: 1661990400000,
|
||||
period_end: 1664582400000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUZ2I67GP2qpb45uTS89eb',
|
||||
total: 733,
|
||||
quantity: 0.7333333333333333,
|
||||
price_per_unit: 999,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
});
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 'sub_test1',
|
||||
is_free_trial: 'true',
|
||||
customer_id: '5zqhakmibpgyix9juiwwkpfnmr',
|
||||
product_id: 'prod_K0AxuWCDoDD9Qq',
|
||||
seats: 25,
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
cy.intercept('GET', '**/api/v4/cloud/products**', {
|
||||
statusCode: 200,
|
||||
body:
|
||||
[
|
||||
{
|
||||
id: 'prod_LSBESgGXq9KlLj',
|
||||
sku: 'cloud-starter',
|
||||
price_per_seat: 0,
|
||||
name: 'Cloud Free',
|
||||
},
|
||||
{
|
||||
id: 'prod_K0AxuWCDoDD9Qq',
|
||||
sku: 'cloud-professional',
|
||||
price_per_seat: 10,
|
||||
name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'prod_Jh6tBLcgWWOOog',
|
||||
sku: 'cloud-enterprise',
|
||||
price_per_seat: 30,
|
||||
name: 'Cloud Enterprise',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
describe('System Console - Billing History', () => {
|
||||
before(() => {
|
||||
simulateSubscription();
|
||||
@ -166,6 +56,116 @@ describe('System Console - Billing History', () => {
|
||||
cy.get('@tableRows').eq(1).find('td').eq(4).find('a').should('have.attr', 'href').and('include', 'invoices/in_1LntnKI67GP2qpb4VObu3NgP/pdf');
|
||||
cy.get('@tableRows').eq(2).find('td').eq(4).find('a').should('have.attr', 'href').and('include', 'invoices/in_1LntnKI67GP2qpb4VObu3NgV/pdf');
|
||||
});
|
||||
|
||||
function simulateSubscription() {
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription/invoices', {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{
|
||||
id: 'in_1Lz8b0I67GP2qpb43kcuMyFP',
|
||||
number: '8D53267B-0006',
|
||||
create_at: 1667263490000,
|
||||
total: 65,
|
||||
tax: 0,
|
||||
status: 'open',
|
||||
description: '',
|
||||
period_start: 1664582400000,
|
||||
period_end: 1667260800000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUYiI67GP2qpb48DXFukcJ',
|
||||
total: 65,
|
||||
quantity: 0.06451612903225806,
|
||||
price_per_unit: 1000,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'in_1LntnKI67GP2qpb4VObu3NgP',
|
||||
number: '8D53267B-0005',
|
||||
create_at: 1664584986000,
|
||||
total: 733,
|
||||
tax: 0,
|
||||
status: 'failed',
|
||||
description: '',
|
||||
period_start: 1661990400000,
|
||||
period_end: 1664582400000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUZ2I67GP2qpb45uTS89eb',
|
||||
total: 733,
|
||||
quantity: 0.7333333333333333,
|
||||
price_per_unit: 999,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'in_1LntnKI67GP2qpb4VObu3NgV',
|
||||
number: '8D53267B-0005',
|
||||
create_at: 1664584986000,
|
||||
total: 733,
|
||||
tax: 0,
|
||||
status: 'paid',
|
||||
description: '',
|
||||
period_start: 1661990400000,
|
||||
period_end: 1664582400000,
|
||||
subscription_id: 'sub_K0AxuWCDoDD9Qq',
|
||||
line_items: [
|
||||
{
|
||||
price_id: 'price_1KLUZ2I67GP2qpb45uTS89eb',
|
||||
total: 733,
|
||||
quantity: 0.7333333333333333,
|
||||
price_per_unit: 999,
|
||||
description: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
current_product_name: 'Cloud Professional',
|
||||
},
|
||||
],
|
||||
});
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 'sub_test1',
|
||||
is_free_trial: 'true',
|
||||
customer_id: '5zqhakmibpgyix9juiwwkpfnmr',
|
||||
product_id: 'prod_K0AxuWCDoDD9Qq',
|
||||
seats: 25,
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
cy.intercept('GET', '**/api/v4/cloud/products**', {
|
||||
statusCode: 200,
|
||||
body:
|
||||
[
|
||||
{
|
||||
id: 'prod_LSBESgGXq9KlLj',
|
||||
sku: 'cloud-starter',
|
||||
price_per_seat: 0,
|
||||
name: 'Cloud Free',
|
||||
},
|
||||
{
|
||||
id: 'prod_K0AxuWCDoDD9Qq',
|
||||
sku: 'cloud-professional',
|
||||
price_per_seat: 10,
|
||||
name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'prod_Jh6tBLcgWWOOog',
|
||||
sku: 'cloud-enterprise',
|
||||
price_per_seat: 30,
|
||||
name: 'Cloud Enterprise',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('System Console - Empty Billing Screen', () => {
|
||||
|
@ -10,45 +10,6 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @cloud_only @cloud_trial
|
||||
|
||||
function simulateSubscription() {
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 'sub_test1',
|
||||
is_free_trial: 'true',
|
||||
customer_id: '5zqhakmibpgyix9juiwwkpfnmr',
|
||||
product_id: 'prod_Jh6tBLcgWWOOog',
|
||||
seats: 25,
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
cy.intercept('GET', '**/api/v4/cloud/products**', {
|
||||
statusCode: 200,
|
||||
body:
|
||||
[
|
||||
{
|
||||
id: 'prod_LSBESgGXq9KlLj',
|
||||
sku: 'cloud-starter',
|
||||
price_per_seat: 0,
|
||||
name: 'Cloud Free',
|
||||
},
|
||||
{
|
||||
id: 'prod_K0AxuWCDoDD9Qq',
|
||||
sku: 'cloud-professional',
|
||||
price_per_seat: 10,
|
||||
name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'prod_Jh6tBLcgWWOOog',
|
||||
sku: 'cloud-enterprise',
|
||||
price_per_seat: 30,
|
||||
name: 'Cloud Enterprise',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
describe('System Console - Payment Information section', () => {
|
||||
before(() => {
|
||||
// * Check if server has license for Cloud
|
||||
@ -116,5 +77,43 @@ describe('System Console - Payment Information section', () => {
|
||||
cy.get('span.savedFeedback__text').should('be.visible').should('have.text', 'Thanks for sharing feedback!');
|
||||
cy.get('button#feedbackSubmitedDone').should('be.enabled').click();
|
||||
});
|
||||
});
|
||||
|
||||
function simulateSubscription() {
|
||||
cy.intercept('GET', '**/api/v4/cloud/subscription', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 'sub_test1',
|
||||
is_free_trial: 'true',
|
||||
customer_id: '5zqhakmibpgyix9juiwwkpfnmr',
|
||||
product_id: 'prod_Jh6tBLcgWWOOog',
|
||||
seats: 25,
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
cy.intercept('GET', '**/api/v4/cloud/products**', {
|
||||
statusCode: 200,
|
||||
body:
|
||||
[
|
||||
{
|
||||
id: 'prod_LSBESgGXq9KlLj',
|
||||
sku: 'cloud-starter',
|
||||
price_per_seat: 0,
|
||||
name: 'Cloud Free',
|
||||
},
|
||||
{
|
||||
id: 'prod_K0AxuWCDoDD9Qq',
|
||||
sku: 'cloud-professional',
|
||||
price_per_seat: 10,
|
||||
name: 'Cloud Professional',
|
||||
},
|
||||
{
|
||||
id: 'prod_Jh6tBLcgWWOOog',
|
||||
sku: 'cloud-enterprise',
|
||||
price_per_seat: 30,
|
||||
name: 'Cloud Enterprise',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -10,37 +10,6 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @enterprise @not_cloud @system_console
|
||||
|
||||
// # Goes to the System Scheme page as System Admin
|
||||
const goToSessionLengths = () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.visit('/admin_console/environment/session_lengths');
|
||||
};
|
||||
|
||||
// # Wait's until the Saving text becomes Save
|
||||
const waitUntilConfigSave = () => {
|
||||
cy.waitUntil(() => cy.get('#saveSetting').then((el) => {
|
||||
return el[0].innerText === 'Save';
|
||||
}));
|
||||
};
|
||||
|
||||
// Clicks the save button in the system console page.
|
||||
// waitUntilConfigSaved: If we need to wait for the save button to go from saving -> save.
|
||||
// Usually we need to wait unless we are doing this in team override scheme
|
||||
const saveConfig = (waitUntilConfigSaved = true, clickConfirmationButton = false) => {
|
||||
// # Save if possible (if previous test ended abruptly all permissions may already be enabled)
|
||||
cy.get('#saveSetting').then((btn) => {
|
||||
if (btn.is(':enabled')) {
|
||||
btn.click();
|
||||
}
|
||||
});
|
||||
if (clickConfirmationButton) {
|
||||
cy.get('#confirmModalButton').click();
|
||||
}
|
||||
if (waitUntilConfigSaved) {
|
||||
waitUntilConfigSave();
|
||||
}
|
||||
};
|
||||
|
||||
describe('MM-T2574 Session Lengths', () => {
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
@ -125,4 +94,35 @@ describe('MM-T2574 Session Lengths', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// # Goes to the System Scheme page as System Admin
|
||||
const goToSessionLengths = () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.visit('/admin_console/environment/session_lengths');
|
||||
};
|
||||
|
||||
// # Wait's until the Saving text becomes Save
|
||||
const waitUntilConfigSave = () => {
|
||||
cy.waitUntil(() => cy.get('#saveSetting').then((el) => {
|
||||
return el[0].innerText === 'Save';
|
||||
}));
|
||||
};
|
||||
|
||||
// Clicks the save button in the system console page.
|
||||
// waitUntilConfigSaved: If we need to wait for the save button to go from saving -> save.
|
||||
// Usually we need to wait unless we are doing this in team override scheme
|
||||
const saveConfig = (waitUntilConfigSaved = true, clickConfirmationButton = false) => {
|
||||
// # Save if possible (if previous test ended abruptly all permissions may already be enabled)
|
||||
cy.get('#saveSetting').then((btn) => {
|
||||
if (btn.is(':enabled')) {
|
||||
btn.click();
|
||||
}
|
||||
});
|
||||
if (clickConfirmationButton) {
|
||||
cy.get('#confirmModalButton').click();
|
||||
}
|
||||
if (waitUntilConfigSaved) {
|
||||
waitUntilConfigSave();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -232,9 +232,9 @@ context('ldap', () => {
|
||||
cy.visit(`/${testTeam.name}`);
|
||||
cy.uiBrowseOrCreateChannel('Browse channels').click();
|
||||
|
||||
// * Search private channel name and make sure it isn't there in public channel directory
|
||||
// * Search private channel name and make sure it is still visible
|
||||
cy.get('#searchChannelsTextbox').type(testChannel.display_name);
|
||||
cy.get('#moreChannelsList').should('include.text', 'No results for');
|
||||
cy.get('#moreChannelsList').should('include.text', testChannel.display_name);
|
||||
});
|
||||
|
||||
it('MM-T2629 - Private to public - More....', () => {
|
||||
|
@ -9,26 +9,6 @@
|
||||
|
||||
// Group: @channels @enterprise @not_cloud
|
||||
|
||||
function withTrialBefore(trialed: string) {
|
||||
cy.intercept('GET', '**/api/v4/trial-license/prev', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
IsLicensed: trialed,
|
||||
IsTrial: trialed,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function withTrialLicense(trial: string) {
|
||||
cy.intercept('GET', '**/api/v4/license/client?format=old', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
IsLicensed: 'true',
|
||||
IsTrial: trial,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Self hosted Pricing modal', () => {
|
||||
let urlL: string | undefined;
|
||||
let createdUser: Cypress.UserProfile | undefined;
|
||||
@ -191,4 +171,24 @@ describe('Self hosted Pricing modal', () => {
|
||||
|
||||
cy.findByText('https://mattermost.com/pl/pricing/#self-hosted').last().should('exist');
|
||||
});
|
||||
|
||||
function withTrialBefore(trialed: string) {
|
||||
cy.intercept('GET', '**/api/v4/trial-license/prev', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
IsLicensed: trialed,
|
||||
IsTrial: trialed,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function withTrialLicense(trial: string) {
|
||||
cy.intercept('GET', '**/api/v4/license/client?format=old', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
IsLicensed: 'true',
|
||||
IsTrial: trial,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -119,7 +119,7 @@ describe('Channel members test', () => {
|
||||
cy.get('#addChannelMembers').click();
|
||||
|
||||
// # Enter user1 and user2 emails
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).typeWithForce(`${user1.email}{enter}${user2.email}{enter}`);
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).typeWithForce(`${user1.email}{enter}${user2.email}{enter}`);
|
||||
|
||||
// # Confirm add the users
|
||||
cy.get('#addUsersToChannelModal #saveItems').click();
|
||||
|
@ -148,7 +148,7 @@ describe('Compliance Export', () => {
|
||||
cy.findByTestId('enableComplianceExportfalse').click();
|
||||
|
||||
// * Verify that exported button is disabled
|
||||
cy.findByRole('button', {name: /run compliance export job now/i}).should('be.disabled');
|
||||
cy.findByRole('button', {name: /run compliance export job now/i}).should('be.enabled');
|
||||
});
|
||||
|
||||
it('MM-T1167 - Compliance Export job can be canceled', () => {
|
||||
|
@ -140,6 +140,7 @@ describe('Upload Files - Settings', () => {
|
||||
},
|
||||
}],
|
||||
types: [],
|
||||
getData: () => {},
|
||||
}});
|
||||
|
||||
// * An error should be visible saying 'File attachments are disabled'
|
||||
@ -165,6 +166,7 @@ describe('Upload Files - Settings', () => {
|
||||
},
|
||||
}],
|
||||
types: [],
|
||||
getData: () => {},
|
||||
}});
|
||||
|
||||
// * An error should be visible saying 'File attachments are disabled'
|
||||
|
@ -41,6 +41,7 @@ describe('Paste Image', () => {
|
||||
},
|
||||
}],
|
||||
types: [],
|
||||
getData: () => {},
|
||||
}});
|
||||
|
||||
cy.uiWaitForFileUploadPreview();
|
||||
|
@ -84,16 +84,18 @@ describe('Interactive Dialog', () => {
|
||||
if (index === 0) {
|
||||
expect(element.name).to.equal('someuserselector');
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('be.visible');
|
||||
cy.wrap($elForm).find('.form-control').type('{downarrow}'.repeat(10));
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}', {force: true});
|
||||
cy.wrap($elForm).find('.form-control').type('{downarrow}'.repeat(10), {force: true});
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('not.be.visible');
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}'.repeat(10));
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}'.repeat(10), {force: true});
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('be.visible');
|
||||
} else if (index === 1) {
|
||||
expect(element.name).to.equal('somechannelselector');
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('be.visible');
|
||||
cy.wrap($elForm).find('.form-control').type('{downarrow}'.repeat(10));
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}', {force: true});
|
||||
cy.wrap($elForm).find('.form-control').type('{downarrow}'.repeat(10), {force: true});
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('not.be.visible');
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}'.repeat(10));
|
||||
cy.wrap($elForm).find('.form-control').type('{uparrow}'.repeat(10), {force: true});
|
||||
cy.wrap($elForm).find('.suggestion-list__item').first().should('be.visible');
|
||||
}
|
||||
|
||||
|
@ -29,12 +29,14 @@ describe('Keyboard Shortcuts', () => {
|
||||
cy.get('body').cmdOrCtrlShortcut('K');
|
||||
cy.get('#quickSwitchInput').type('T');
|
||||
|
||||
// * Confirm the town-square channel is selected in the suggestion list
|
||||
cy.get('#suggestionList').findByTestId('town-square').should('be.visible').and('have.class', 'suggestion--selected');
|
||||
|
||||
// # Press down arrow
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('body').type('{downarrow}');
|
||||
cy.get('body').type('{downarrow}');
|
||||
|
||||
// * Confirm the offtopic channel is selected in the suggestion list
|
||||
// * Confirm the off-topic channel is selected in the suggestion list
|
||||
cy.get('#suggestionList').findByTestId('off-topic').should('be.visible').and('have.class', 'suggestion--selected');
|
||||
|
||||
// # Press ENTER
|
||||
|
@ -94,10 +94,8 @@ describe('Keyboard Shortcuts', () => {
|
||||
});
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * verify Save not shown in webview
|
||||
cy.findByText('Save').should('not.exist');
|
||||
|
||||
cy.viewport('iphone-6');
|
||||
// * verify Saved not shown in webview
|
||||
cy.findByText('Saved').should('not.exist');
|
||||
|
||||
// # Save Post
|
||||
cy.uiPostDropdownMenuShortcut(postId, 'Save', 'S', 'RHS_ROOT');
|
||||
|
@ -42,6 +42,7 @@ describe('Messaging', () => {
|
||||
cy.apiCreateGroupChannel([firstUser.id, secondUser.id, thirdUser.id]).then(({channel}) => {
|
||||
// # Visit the newly created group message
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Go to off-topic
|
||||
cy.get('#sidebarItem_off-topic').click();
|
||||
|
@ -36,11 +36,11 @@ describe('Show GIF images properly', () => {
|
||||
cy.url().should('include', offtopiclink);
|
||||
|
||||
// # Post tenor GIF
|
||||
cy.postMessage('https://c.tenor.com/Ztva2YFROSkAAAAi/duck-searching.gif');
|
||||
cy.postMessage('https://media.tenor.com/yCFHzEvKa9MAAAAi/hello.gif');
|
||||
|
||||
cy.getLastPostId().as('postId').then((postId) => {
|
||||
// * Validate image size
|
||||
cy.get(`#post_${postId}`).find('.attachment__image').should('have.css', 'width', '137px');
|
||||
cy.get(`#post_${postId}`).find('.attachment__image').should('have.css', 'width', '189px');
|
||||
});
|
||||
|
||||
// # Post giphy GIF
|
||||
|
@ -23,12 +23,12 @@ describe('Notifications', () => {
|
||||
|
||||
// # Open 'Settings' modal
|
||||
cy.uiOpenSettingsModal().within(() => {
|
||||
// # Open 'Words That Trigger Mentions' setting and uncheck all the checkboxes
|
||||
cy.findByRole('heading', {name: 'Words That Trigger Mentions'}).should('be.visible').click();
|
||||
// # Open 'Keywords that trigger Notifications' setting and uncheck all the checkboxes
|
||||
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
|
||||
cy.findByRole('checkbox', {name: `Your case-sensitive first name "${otherUser.first_name}"`}).should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: 'Channel-wide mentions "@channel", "@all", "@here"'}).click().should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: 'Other non case-sensitive words, separated by commas:'}).should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: 'Other non case-sensitive words, press Tab or use commas to separate keywords:'}).should('not.be.checked');
|
||||
|
||||
// # Save then close the modal
|
||||
cy.uiSaveAndClose();
|
||||
|
@ -243,14 +243,16 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
|
||||
// Navigate to settings modal
|
||||
cy.uiOpenSettingsModal();
|
||||
|
||||
// Notifications header should be visible
|
||||
cy.get('#notificationSettingsTitle').
|
||||
scrollIntoView().
|
||||
// # Click on notifications tab
|
||||
cy.findByRoleExtended('tab', {name: 'Notifications'}).
|
||||
should('be.visible').
|
||||
and('contain', 'Notifications');
|
||||
click();
|
||||
|
||||
// Notifications header should be visible
|
||||
cy.findAllByText('Notifications').should('be.visible');
|
||||
|
||||
// Open up 'Words that trigger mentions' sub-section
|
||||
cy.get('#keysTitle').
|
||||
cy.findByText('Keywords that trigger Notifications').
|
||||
scrollIntoView().
|
||||
click();
|
||||
|
||||
@ -270,8 +272,8 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
|
||||
// Set Custom field
|
||||
if (desiredSettings.custom && desiredSettings.customText) {
|
||||
cy.get('#notificationTriggerCustomText').
|
||||
clear().
|
||||
type(desiredSettings.customText);
|
||||
type(desiredSettings.customText, {force: true}).
|
||||
tab();
|
||||
}
|
||||
|
||||
// Click “Save” and close modal
|
||||
|
@ -29,14 +29,16 @@ describe('Notifications', () => {
|
||||
|
||||
// # Open 'Settings' modal
|
||||
cy.uiOpenSettingsModal().within(() => {
|
||||
cy.get('#keysEdit').should('be.visible').click();
|
||||
// # Open 'Keywords that trigger Notifications' setting
|
||||
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
|
||||
|
||||
// * As otherUser, ensure that 'Your non-case sensitive username' is not checked
|
||||
cy.get('#notificationTriggerUsername').should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('#accountSettingsHeader').find('button').should('be.visible').click();
|
||||
// # Save then close the modal
|
||||
cy.uiSaveAndClose();
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Login as sysadmin
|
||||
|
@ -17,6 +17,7 @@ describe('Plugin Marketplace', () => {
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHaveFeatureFlag('StreamlinedMarketplace', 'false'); // https://mattermost.atlassian.net/browse/MM-54230
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
@ -72,6 +73,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.get('#marketplaceTabs-pane-allListing').should('be.visible');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('MM-T2001 autofocus on search plugin input box', () => {
|
||||
cy.uiClose();
|
||||
|
||||
@ -82,6 +84,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.findByPlaceholderText('Search Marketplace').should('be.focused');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('render the list of all plugins by default', () => {
|
||||
// * Verify all plugins tab should be active
|
||||
cy.get('#marketplaceTabs-tab-allListing').should('be.visible').parent().should('have.class', 'active');
|
||||
@ -92,6 +95,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.get('#marketplaceTabs-pane-installed').should('not.exist');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
// this test uses exist, not visible, due to issues with Cypress
|
||||
it('render the list of installed plugins on demand', () => {
|
||||
// # Click on installed plugins tab
|
||||
@ -117,6 +121,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.get('#modal_marketplace').should('not.exist');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('should filter all on search', () => {
|
||||
// # Load all plugins before searching
|
||||
cy.get('.more-modal__row').should('have.length', 15);
|
||||
@ -136,6 +141,7 @@ describe('Plugin Marketplace', () => {
|
||||
should('have.length', 1);
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('should show an error bar on failing to filter', () => {
|
||||
// # Enable Plugin Marketplace
|
||||
cy.apiUpdateConfig({
|
||||
@ -168,6 +174,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.get('#marketplace-plugin-com\\.mattermost\\.webex').find('.btn.btn-outline', {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().should('be.visible').and('have.text', 'Configure');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('should install a plugin from search results on demand', () => {
|
||||
// # Uninstall any existing webex plugin
|
||||
cy.apiRemovePluginById('com.mattermost.webex');
|
||||
@ -226,6 +233,7 @@ describe('Plugin Marketplace', () => {
|
||||
cy.get('#marketplace-plugin-github').should('be.visible');
|
||||
});
|
||||
|
||||
// https://mattermost.atlassian.net/browse/MM-54230
|
||||
it('MM-T1986 change tab to "All Plugins" when "Install Plugins" link is clicked', () => {
|
||||
cy.get('#marketplaceTabs').scrollIntoView().should('be.visible').within(() => {
|
||||
// # Switch tab to installed plugin
|
||||
|
@ -26,7 +26,7 @@ describe('channels > App Bar', {testIsolation: true}, () => {
|
||||
|
||||
describe('App Bar disabled', () => {
|
||||
it('should not show the Playbook App Bar icon', () => {
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {DisableAppBar: true}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
@ -41,7 +41,7 @@ describe('channels > App Bar', {testIsolation: true}, () => {
|
||||
|
||||
describe('App Bar enabled', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {DisableAppBar: false}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
@ -47,7 +47,7 @@ describe('channels > channel header', {testIsolation: true}, () => {
|
||||
describe('App Bar enabled', () => {
|
||||
it('webapp should hide the Playbook channel header button', () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {DisableAppBar: false}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
@ -65,7 +65,7 @@ describe('channels > channel header', {testIsolation: true}, () => {
|
||||
describe('App Bar disabled', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
|
||||
cy.apiUpdateConfig({ExperimentalSettings: {DisableAppBar: true}});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
@ -164,7 +164,7 @@ describe('channels > rhs > checklist', {testIsolation: true}, () => {
|
||||
});
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Failed to find slash command /invalid');
|
||||
cy.verifyEphemeralMessage('Failed to execute slash command /invalid');
|
||||
});
|
||||
|
||||
it('successfully runs a valid slash command', () => {
|
||||
|
@ -84,7 +84,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook check ');
|
||||
|
||||
// * Verify suggestions number: a single run with 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 5);
|
||||
cy.get('.slash-command__info').should('have.length', 5);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -295,7 +295,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook check ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 9);
|
||||
cy.get('.slash-command__info').should('have.length', 9);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -337,7 +337,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook checkadd ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 2 checklists + 1 title
|
||||
cy.get('.slash-command').should('have.length', 5);
|
||||
cy.get('.slash-command__info').should('have.length', 5);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -380,7 +380,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook checkremove ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 9);
|
||||
cy.get('.slash-command__info').should('have.length', 9);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -423,7 +423,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook owner ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
cy.get('.slash-command__info').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -465,7 +465,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook finish ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
cy.get('.slash-command__info').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
@ -528,7 +528,7 @@ describe('channels > slash command > owner', {testIsolation: true}, () => {
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook update ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
cy.get('.slash-command__info').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
@ -43,7 +43,7 @@ declare namespace Cypress {
|
||||
* @example
|
||||
* cy.apiGetBots();
|
||||
*/
|
||||
apiGetBots(page: number, perPage: number, includeDeleted: boolean): Chainable<{bots: Bot[]}>;
|
||||
apiGetBots(page?: number, perPage?: number, includeDeleted?: boolean): Chainable<{bots: Bot[]}>;
|
||||
|
||||
/**
|
||||
* Disable bot.
|
||||
|
@ -1,39 +1,117 @@
|
||||
{
|
||||
"ServiceSettings": {
|
||||
"SiteURL": "http://localhost:8065",
|
||||
"WebsocketURL": "",
|
||||
"LicenseFileLocation": "",
|
||||
"ListenAddress": ":8065",
|
||||
"ConnectionSecurity": "",
|
||||
"TLSCertFile": "",
|
||||
"TLSKeyFile": "",
|
||||
"TLSMinVer": "1.2",
|
||||
"TLSStrictTransport": false,
|
||||
"TLSStrictTransportMaxAge": 63072000,
|
||||
"TLSOverwriteCiphers": [],
|
||||
"UseLetsEncrypt": false,
|
||||
"LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
|
||||
"Forward80To443": false,
|
||||
"TrustedProxyIPHeader": [],
|
||||
"ReadTimeout": 300,
|
||||
"WriteTimeout": 300,
|
||||
"IdleTimeout": 300,
|
||||
"MaximumLoginAttempts": 10,
|
||||
"GoroutineHealthThreshold": -1,
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnableOutgoingWebhooks": true,
|
||||
"EnableCommands": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableLinkPreviews": false,
|
||||
"EnablePermalinkPreviews": true,
|
||||
"RestrictLinkPreviews": "",
|
||||
"EnableTesting": false,
|
||||
"EnableDeveloper": false,
|
||||
"DeveloperFlags": "",
|
||||
"EnableClientPerformanceDebugging": false,
|
||||
"EnableOpenTracing": false,
|
||||
"EnableSecurityFixAlert": true,
|
||||
"EnableInsecureOutgoingConnections": false,
|
||||
"AllowedUntrustedInternalConnections": "localhost",
|
||||
"EnableMultifactorAuthentication": false,
|
||||
"EnforceMultifactorAuthentication": false,
|
||||
"EnableUserAccessTokens": false,
|
||||
"AllowCorsFrom": "",
|
||||
"CorsExposedHeaders": "",
|
||||
"CorsAllowCredentials": false,
|
||||
"CorsDebug": false,
|
||||
"AllowCookiesForSubdomains": false,
|
||||
"ExtendSessionLengthWithActivity": true,
|
||||
"SessionLengthWebInDays": 30,
|
||||
"SessionLengthWebInHours": 720,
|
||||
"SessionLengthMobileInDays": 30,
|
||||
"SessionLengthMobileInHours": 720,
|
||||
"SessionLengthSSOInDays": 30,
|
||||
"SessionLengthSSOInHours": 720,
|
||||
"SessionCacheInMinutes": 10,
|
||||
"SessionIdleTimeoutInMinutes": 43200,
|
||||
"WebsocketSecurePort": 443,
|
||||
"WebsocketPort": 80,
|
||||
"WebserverMode": "gzip",
|
||||
"EnableGifPicker": false,
|
||||
"GiphySdkKey": "s0glxvzVg9azvPipKxcPLpXV0q1x1fVP",
|
||||
"EnableCustomEmoji": false,
|
||||
"EnableEmojiPicker": true,
|
||||
"EnableGifPicker": false,
|
||||
"GfycatAPIKey": "2_KtH_W5",
|
||||
"GfycatAPISecret": "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof",
|
||||
"PostEditTimeLimit": -1,
|
||||
"TimeBetweenUserTypingUpdatesMilliseconds": 5000,
|
||||
"EnablePostSearch": true,
|
||||
"EnableFileSearch": true,
|
||||
"MinimumHashtagLength": 3,
|
||||
"EnableUserTypingMessages": true,
|
||||
"EnableChannelViewedMessages": true,
|
||||
"EnableUserStatuses": true,
|
||||
"ExperimentalEnableAuthenticationTransfer": true,
|
||||
"ClusterLogTimeoutMilliseconds": 2000,
|
||||
"EnablePreviewFeatures": true,
|
||||
"EnableTutorial": true,
|
||||
"EnableOnboardingFlow": false,
|
||||
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
|
||||
"ExperimentalGroupUnreadChannels": "disabled",
|
||||
"EnableAPITeamDeletion": true,
|
||||
"EnableAPITriggerAdminNotifications": false,
|
||||
"EnableAPIUserDeletion": false,
|
||||
"ExperimentalEnableHardenedMode": false,
|
||||
"ExperimentalStrictCSRFEnforcement": false,
|
||||
"EnableEmailInvitations": true,
|
||||
"DisableBotsWhenOwnerIsDeactivated": true,
|
||||
"EnableBotAccountCreation": true,
|
||||
"EnableSVGs": true,
|
||||
"EnableLatex": false,
|
||||
"EnableLegacySidebar": false,
|
||||
"EnableInlineLatex": true,
|
||||
"PostPriority": true,
|
||||
"AllowPersistentNotifications": true,
|
||||
"AllowPersistentNotificationsForGuests": false,
|
||||
"PersistentNotificationIntervalMinutes": 5,
|
||||
"PersistentNotificationMaxCount": 6,
|
||||
"PersistentNotificationMaxRecipients": 5,
|
||||
"EnableAPIChannelDeletion": false,
|
||||
"EnableLocalMode": false,
|
||||
"LocalModeSocketLocation": "/var/tmp/mattermost_local.socket",
|
||||
"EnableAWSMetering": false,
|
||||
"SplitKey": "",
|
||||
"FeatureFlagSyncIntervalSeconds": 30,
|
||||
"DebugSplit": false,
|
||||
"ThreadAutoFollow": true,
|
||||
"CollapsedThreads": "disabled"
|
||||
"CollapsedThreads": "disabled",
|
||||
"ManagedResourcePaths": "",
|
||||
"EnableCustomGroups": true,
|
||||
"SelfHostedPurchase": true,
|
||||
"AllowSyncedDrafts": true
|
||||
},
|
||||
"TeamSettings": {
|
||||
"SiteName": "Mattermost",
|
||||
"MaxUsersPerTeam": 2000,
|
||||
"EnableJoinLeaveMessageByDefault": true,
|
||||
"EnableUserCreation": true,
|
||||
"EnableOpenServer": true,
|
||||
"EnableUserDeactivation": false,
|
||||
@ -43,6 +121,7 @@
|
||||
"CustomBrandText": "",
|
||||
"CustomDescriptionText": "",
|
||||
"RestrictDirectMessage": "any",
|
||||
"EnableLastActiveTime": true,
|
||||
"UserStatusAwayTimeout": 300,
|
||||
"MaxChannelsPerTeam": 2000,
|
||||
"MaxNotificationsPerChannel": 1000,
|
||||
@ -54,12 +133,19 @@
|
||||
"ExperimentalPrimaryTeam": "",
|
||||
"ExperimentalDefaultChannels": []
|
||||
},
|
||||
"ClientRequirements": {
|
||||
"AndroidLatestVersion": "",
|
||||
"AndroidMinVersion": "",
|
||||
"IosLatestVersion": "",
|
||||
"IosMinVersion": ""
|
||||
},
|
||||
"PasswordSettings": {
|
||||
"MinimumLength": 5,
|
||||
"Lowercase": false,
|
||||
"Number": false,
|
||||
"Uppercase": false,
|
||||
"Symbol": false
|
||||
"Symbol": false,
|
||||
"EnableForgotLink": true
|
||||
},
|
||||
"EmailSettings": {
|
||||
"EnableSignUpWithEmail": true,
|
||||
@ -69,14 +155,27 @@
|
||||
"UseChannelInEmailNotifications": false,
|
||||
"RequireEmailVerification": false,
|
||||
"FeedbackName": "",
|
||||
"FeedbackEmail": "test@example.com",
|
||||
"ReplyToAddress": "test@example.com",
|
||||
"FeedbackOrganization": "",
|
||||
"EnableSMTPAuth": false,
|
||||
"SMTPUsername": "",
|
||||
"SMTPPassword": "",
|
||||
"SMTPServer": "localhost",
|
||||
"SMTPPort": "10025",
|
||||
"SMTPServerTimeout": 10,
|
||||
"ConnectionSecurity": "",
|
||||
"SendPushNotifications": true,
|
||||
"PushNotificationServer": "https://push-test.mattermost.com",
|
||||
"PushNotificationServerType": "custom",
|
||||
"PushNotificationServerLocation": "us",
|
||||
"PushNotificationContents": "generic",
|
||||
"PushNotificationBuffer": 1000,
|
||||
"EnableEmailBatching": false,
|
||||
"EmailBatchingBufferSize": 256,
|
||||
"EmailBatchingInterval": 30,
|
||||
"EnablePreviewModeBanner": true,
|
||||
"SkipServerCertificateVerification": false,
|
||||
"EmailNotificationContentsType": "full",
|
||||
"LoginButtonColor": "#0000",
|
||||
"LoginButtonBorderColor": "#2389D7",
|
||||
@ -87,6 +186,12 @@
|
||||
"ShowFullName": true
|
||||
},
|
||||
"SupportSettings": {
|
||||
"TermsOfServiceLink": "https://mattermost.com/pl/terms-of-use/",
|
||||
"PrivacyPolicyLink": "https://mattermost.com/pl/privacy-policy/",
|
||||
"AboutLink": "https://mattermost.com/pl/about-mattermost",
|
||||
"HelpLink": "https://mattermost.com/pl/help/",
|
||||
"ReportAProblemLink": "https://mattermost.com/pl/report-a-bug",
|
||||
"ForgotPasswordLink": "",
|
||||
"SupportEmail": "",
|
||||
"CustomTermsOfServiceEnabled": false,
|
||||
"CustomTermsOfServiceReAcceptancePeriod": 365,
|
||||
@ -99,7 +204,10 @@
|
||||
"BannerTextColor": "#333333",
|
||||
"AllowBannerDismissal": true,
|
||||
"AdminNoticesEnabled": false,
|
||||
"UserNoticesEnabled": false
|
||||
"UserNoticesEnabled": false,
|
||||
"NoticesURL": "https://notices.mattermost.com/",
|
||||
"NoticesFetchFrequency": 3600,
|
||||
"NoticesSkipCache": false
|
||||
},
|
||||
"ThemeSettings": {
|
||||
"EnableThemeSelection": true,
|
||||
@ -114,7 +222,10 @@
|
||||
"Scope": "",
|
||||
"AuthEndpoint": "",
|
||||
"TokenEndpoint": "",
|
||||
"UserAPIEndpoint": ""
|
||||
"UserAPIEndpoint": "",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": ""
|
||||
},
|
||||
"GoogleSettings": {
|
||||
"Enable": false,
|
||||
@ -123,7 +234,10 @@
|
||||
"Scope": "profile email",
|
||||
"AuthEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"TokenEndpoint": "https://www.googleapis.com/oauth2/v4/token",
|
||||
"UserAPIEndpoint": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata"
|
||||
"UserAPIEndpoint": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": ""
|
||||
},
|
||||
"Office365Settings": {
|
||||
"Enable": false,
|
||||
@ -133,8 +247,21 @@
|
||||
"AuthEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
"TokenEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
"UserAPIEndpoint": "https://graph.microsoft.com/v1.0/me",
|
||||
"DiscoveryEndpoint": "",
|
||||
"DirectoryId": ""
|
||||
},
|
||||
"OpenIdSettings": {
|
||||
"Enable": false,
|
||||
"Secret": "",
|
||||
"Id": "",
|
||||
"Scope": "profile openid email",
|
||||
"AuthEndpoint": "",
|
||||
"TokenEndpoint": "",
|
||||
"UserAPIEndpoint": "",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": "#145DBF"
|
||||
},
|
||||
"LdapSettings": {
|
||||
"Enable": true,
|
||||
"EnableSync": false,
|
||||
@ -162,6 +289,8 @@
|
||||
"PictureAttribute": "",
|
||||
"SyncIntervalMinutes": 10000,
|
||||
"SkipCertificateVerification": true,
|
||||
"PublicCertificateFile": "",
|
||||
"PrivateKeyFile": "",
|
||||
"QueryTimeout": 60,
|
||||
"MaxPageSize": 500,
|
||||
"LoginFieldName": "",
|
||||
@ -172,7 +301,9 @@
|
||||
},
|
||||
"ComplianceSettings": {
|
||||
"Enable": false,
|
||||
"EnableDaily": false
|
||||
"Directory": "./data/",
|
||||
"EnableDaily": false,
|
||||
"BatchSize": 30000
|
||||
},
|
||||
"LocalizationSettings": {
|
||||
"DefaultServerLocale": "en",
|
||||
@ -183,12 +314,14 @@
|
||||
"Enable": false,
|
||||
"EnableSyncWithLdap": false,
|
||||
"EnableSyncWithLdapIncludeAuth": false,
|
||||
"IgnoreGuestsLdapSync": false,
|
||||
"Verify": true,
|
||||
"Encrypt": true,
|
||||
"SignRequest": false,
|
||||
"IdpURL": "",
|
||||
"IdpDescriptorURL": "",
|
||||
"IdpMetadataURL": "",
|
||||
"ServiceProviderIdentifier": "",
|
||||
"AssertionConsumerServiceURL": "",
|
||||
"SignatureAlgorithm": "RSAwithSHA1",
|
||||
"CanonicalAlgorithm": "Canonical1.0",
|
||||
@ -213,18 +346,32 @@
|
||||
"LoginButtonBorderColor": "#2389D7",
|
||||
"LoginButtonTextColor": "#ffffff"
|
||||
},
|
||||
"NativeAppSettings": {
|
||||
"AppCustomURLSchemes": ["mmauth://", "mmauthbeta://"],
|
||||
"AppDownloadLink": "https://mattermost.com/pl/download-apps",
|
||||
"AndroidAppDownloadLink": "https://mattermost.com/pl/android-app/",
|
||||
"IosAppDownloadLink": "https://mattermost.com/pl/ios-app/"
|
||||
},
|
||||
"ClusterSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"ExperimentalSettings": {
|
||||
"RestrictSystemAdmin": true
|
||||
},
|
||||
"AnalyticsSettings": {
|
||||
"MaxUsersForStatistics": 2500
|
||||
},
|
||||
"DataRetentionSettings": {
|
||||
"EnableMessageDeletion": false,
|
||||
"EnableFileDeletion": false,
|
||||
"EnableBoardsDeletion": false,
|
||||
"MessageRetentionDays": 365,
|
||||
"FileRetentionDays": 365,
|
||||
"DeletionJobStartTime": "02:00"
|
||||
"BoardsRetentionDays": 365,
|
||||
"DeletionJobStartTime": "02:00",
|
||||
"BatchSize": 3000,
|
||||
"TimeBetweenBatchesMilliseconds": 100,
|
||||
"RetentionIdsBatchSize": 100
|
||||
},
|
||||
"MessageExportSettings": {
|
||||
"EnableExport": false,
|
||||
@ -232,37 +379,51 @@
|
||||
"DailyRunTime": "01:00",
|
||||
"ExportFromTimestamp": 0,
|
||||
"BatchSize": 10000,
|
||||
"DownloadExportResults": false,
|
||||
"GlobalRelaySettings": {
|
||||
"CustomerType": "A9",
|
||||
"SMTPUsername": "",
|
||||
"SMTPPassword": "",
|
||||
"EmailAddress": ""
|
||||
"EmailAddress": "",
|
||||
"SMTPServerTimeout": 1800
|
||||
}
|
||||
},
|
||||
"ProductSettings": {},
|
||||
"PluginSettings": {
|
||||
"Enable": true,
|
||||
"EnableUploads": true,
|
||||
"AllowInsecureDownloadURL": false,
|
||||
"EnableHealthCheck": true,
|
||||
"Directory": "./plugins",
|
||||
"ClientDirectory": "./client/plugins",
|
||||
"Plugins": {},
|
||||
"PluginStates": {
|
||||
"com.mattermost.calls": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.nps": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.plugin-incident-response": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.plugin-incident-management": {
|
||||
"Enable": false
|
||||
},
|
||||
"focalboard": {
|
||||
"Enable": false
|
||||
"playbooks": {
|
||||
"Enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnableMarketplace": true,
|
||||
"EnableRemoteMarketplace": true,
|
||||
"AutomaticPrepackagedPlugins": true,
|
||||
"RequirePluginSignature": false,
|
||||
"MarketplaceURL": "https://api.integrations.mattermost.com",
|
||||
"SignaturePublicKeyFiles": [],
|
||||
"ChimeraOAuthProxyURL": ""
|
||||
},
|
||||
"DisplaySettings": {
|
||||
"CustomURLSchemes": [],
|
||||
"MaxMarkdownNodes": 0,
|
||||
"ExperimentalTimezone": false
|
||||
},
|
||||
"GuestAccountsSettings": {
|
||||
"Enable": true,
|
||||
"HideTags": false,
|
||||
"AllowEmailAccounts": true,
|
||||
"EnforceMultifactorAuthentication": false,
|
||||
"RestrictCreationToDomains": ""
|
||||
@ -272,5 +433,13 @@
|
||||
"ImageProxyType": "local",
|
||||
"RemoteImageProxyURL": "",
|
||||
"RemoteImageProxyOptions": ""
|
||||
},
|
||||
"ImportSettings": {
|
||||
"Directory": "./import",
|
||||
"RetentionDays": 30
|
||||
},
|
||||
"ExportSettings": {
|
||||
"Directory": "./export",
|
||||
"RetentionDays": 30
|
||||
}
|
||||
}
|
||||
|
45
e2e-tests/cypress/tests/support/api/group.ts
Normal file
45
e2e-tests/cypress/tests/support/api/group.ts
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// *****************************************************************************
|
||||
// Groups
|
||||
// https://api.mattermost.com/#tag/groups
|
||||
// *****************************************************************************
|
||||
|
||||
import {ChainableT} from '../../types';
|
||||
|
||||
function apiCreateCustomUserGroup(displayName: string, name: string, userIds: string[]): ChainableT<Cypress.Response<any>> {
|
||||
return cy.request({
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
url: '/api/v4/groups',
|
||||
method: 'POST',
|
||||
body: {
|
||||
display_name: displayName,
|
||||
name,
|
||||
source: 'custom',
|
||||
allow_reference: true,
|
||||
user_ids: userIds,
|
||||
},
|
||||
}).then((response) => {
|
||||
expect(response.status).to.equal(201);
|
||||
return cy.wrap(response);
|
||||
});
|
||||
}
|
||||
|
||||
Cypress.Commands.add('apiCreateCustomUserGroup', apiCreateCustomUserGroup);
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
|
||||
/**
|
||||
* Create custom user group
|
||||
* @param {string} displayName - the display name of the group
|
||||
* @param {string} name - the @ mentionable name of the group
|
||||
* @param {string[]} userIds - users to add to the group
|
||||
*/
|
||||
apiCreateCustomUserGroup: typeof apiCreateCustomUserGroup;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import './cloud';
|
||||
import './cluster';
|
||||
import './common';
|
||||
import './data_retention';
|
||||
import './group';
|
||||
import './keycloak';
|
||||
import './ldap';
|
||||
import './playbooks';
|
||||
|
@ -12,22 +12,28 @@
|
||||
"TLSStrictTransportMaxAge": 63072000,
|
||||
"TLSOverwriteCiphers": [],
|
||||
"UseLetsEncrypt": false,
|
||||
"LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
|
||||
"Forward80To443": false,
|
||||
"TrustedProxyIPHeader": [],
|
||||
"ReadTimeout": 300,
|
||||
"WriteTimeout": 300,
|
||||
"IdleTimeout": 300,
|
||||
"MaximumLoginAttempts": 10,
|
||||
"GoroutineHealthThreshold": -1,
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnableOutgoingWebhooks": true,
|
||||
"EnableCommands": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableLinkPreviews": false,
|
||||
"EnablePermalinkPreviews": true,
|
||||
"RestrictLinkPreviews": "",
|
||||
"EnableTesting": false,
|
||||
"EnableDeveloper": false,
|
||||
"DeveloperFlags": "",
|
||||
"EnableClientPerformanceDebugging": false,
|
||||
"EnableOpenTracing": false,
|
||||
"EnableSecurityFixAlert": true,
|
||||
"EnableInsecureOutgoingConnections": false,
|
||||
@ -41,22 +47,25 @@
|
||||
"CorsDebug": false,
|
||||
"AllowCookiesForSubdomains": false,
|
||||
"ExtendSessionLengthWithActivity": true,
|
||||
"SessionLengthWebInDays": 30,
|
||||
"SessionLengthWebInHours": 720,
|
||||
"SessionLengthMobileInDays": 30,
|
||||
"SessionLengthMobileInHours": 720,
|
||||
"SessionLengthSSOInDays": 30,
|
||||
"SessionLengthSSOInHours": 720,
|
||||
"SessionCacheInMinutes": 10,
|
||||
"SessionIdleTimeoutInMinutes": 43200,
|
||||
"WebsocketSecurePort": 443,
|
||||
"WebsocketPort": 80,
|
||||
"WebserverMode": "gzip",
|
||||
"EnableGifPicker": false,
|
||||
"GiphySdkKey": "s0glxvzVg9azvPipKxcPLpXV0q1x1fVP",
|
||||
"EnableCustomEmoji": false,
|
||||
"EnableEmojiPicker": true,
|
||||
"EnableGifPicker": false,
|
||||
"GfycatAPIKey": "2_KtH_W5",
|
||||
"GfycatAPISecret": "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof",
|
||||
"PostEditTimeLimit": -1,
|
||||
"TimeBetweenUserTypingUpdatesMilliseconds": 5000,
|
||||
"EnablePostSearch": true,
|
||||
"EnableFileSearch": true,
|
||||
"MinimumHashtagLength": 3,
|
||||
"EnableUserTypingMessages": true,
|
||||
"EnableChannelViewedMessages": true,
|
||||
@ -69,6 +78,8 @@
|
||||
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
|
||||
"ExperimentalGroupUnreadChannels": "disabled",
|
||||
"EnableAPITeamDeletion": true,
|
||||
"EnableAPITriggerAdminNotifications": false,
|
||||
"EnableAPIUserDeletion": false,
|
||||
"ExperimentalEnableHardenedMode": false,
|
||||
"ExperimentalStrictCSRFEnforcement": false,
|
||||
"EnableEmailInvitations": true,
|
||||
@ -76,13 +87,31 @@
|
||||
"EnableBotAccountCreation": true,
|
||||
"EnableSVGs": true,
|
||||
"EnableLatex": false,
|
||||
"EnableLegacySidebar": false,
|
||||
"EnableInlineLatex": true,
|
||||
"PostPriority": true,
|
||||
"AllowPersistentNotifications": true,
|
||||
"AllowPersistentNotificationsForGuests": false,
|
||||
"PersistentNotificationIntervalMinutes": 5,
|
||||
"PersistentNotificationMaxCount": 6,
|
||||
"PersistentNotificationMaxRecipients": 5,
|
||||
"EnableAPIChannelDeletion": false,
|
||||
"EnableLocalMode": false,
|
||||
"LocalModeSocketLocation": "/var/tmp/mattermost_local.socket",
|
||||
"EnableAWSMetering": false,
|
||||
"SplitKey": "",
|
||||
"FeatureFlagSyncIntervalSeconds": 30,
|
||||
"DebugSplit": false,
|
||||
"ThreadAutoFollow": true,
|
||||
"CollapsedThreads": "disabled"
|
||||
"CollapsedThreads": "disabled",
|
||||
"ManagedResourcePaths": "",
|
||||
"EnableCustomGroups": true,
|
||||
"SelfHostedPurchase": true,
|
||||
"AllowSyncedDrafts": true
|
||||
},
|
||||
"TeamSettings": {
|
||||
"SiteName": "Mattermost",
|
||||
"MaxUsersPerTeam": 2000,
|
||||
"EnableJoinLeaveMessageByDefault": true,
|
||||
"EnableUserCreation": true,
|
||||
"EnableOpenServer": true,
|
||||
"EnableUserDeactivation": false,
|
||||
@ -92,6 +121,7 @@
|
||||
"CustomBrandText": "",
|
||||
"CustomDescriptionText": "",
|
||||
"RestrictDirectMessage": "any",
|
||||
"EnableLastActiveTime": true,
|
||||
"UserStatusAwayTimeout": 300,
|
||||
"MaxChannelsPerTeam": 2000,
|
||||
"MaxNotificationsPerChannel": 1000,
|
||||
@ -110,26 +140,37 @@
|
||||
"IosMinVersion": ""
|
||||
},
|
||||
"SqlSettings": {
|
||||
"DriverName": "postgres",
|
||||
"DataSource": "postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\u0026connect_timeout=10\u0026binary_parameters=yes",
|
||||
"DataSourceReplicas": [],
|
||||
"DataSourceSearchReplicas": [],
|
||||
"MaxIdleConns": 20,
|
||||
"ConnMaxLifetimeMilliseconds": 3600000,
|
||||
"ConnMaxIdleTimeMilliseconds": 300000,
|
||||
"MaxOpenConns": 300,
|
||||
"Trace": false,
|
||||
"AtRestEncryptKey": "",
|
||||
"QueryTimeout": 30
|
||||
"QueryTimeout": 30,
|
||||
"DisableDatabaseSearch": false,
|
||||
"MigrationsStatementTimeoutSeconds": 100000,
|
||||
"ReplicaLagSettings": [],
|
||||
"ReplicaMonitorIntervalSeconds": 5
|
||||
},
|
||||
"LogSettings": {
|
||||
"EnableConsole": true,
|
||||
"ConsoleLevel": "DEBUG",
|
||||
"ConsoleJson": true,
|
||||
"EnableColor": false,
|
||||
"EnableFile": true,
|
||||
"FileLevel": "INFO",
|
||||
"FileJson": true,
|
||||
"FileLocation": "",
|
||||
"EnableWebhookDebugging": true,
|
||||
"EnableDiagnostics": true,
|
||||
"EnableSentry": false
|
||||
"VerboseDiagnostics": false,
|
||||
"EnableSentry": false,
|
||||
"AdvancedLoggingJSON": {},
|
||||
"AdvancedLoggingConfig": ""
|
||||
},
|
||||
"ExperimentalAuditSettings": {
|
||||
"FileEnabled": false,
|
||||
@ -138,16 +179,21 @@
|
||||
"FileMaxAgeDays": 0,
|
||||
"FileMaxBackups": 0,
|
||||
"FileCompress": false,
|
||||
"FileMaxQueueSize": 1000
|
||||
"FileMaxQueueSize": 1000,
|
||||
"AdvancedLoggingJSON": {},
|
||||
"AdvancedLoggingConfig": ""
|
||||
},
|
||||
"NotificationLogSettings": {
|
||||
"EnableConsole": true,
|
||||
"ConsoleLevel": "DEBUG",
|
||||
"ConsoleJson": true,
|
||||
"EnableColor": false,
|
||||
"EnableFile": true,
|
||||
"FileLevel": "INFO",
|
||||
"FileJson": true,
|
||||
"FileLocation": ""
|
||||
"FileLocation": "",
|
||||
"AdvancedLoggingJSON": {},
|
||||
"AdvancedLoggingConfig": ""
|
||||
},
|
||||
"PasswordSettings": {
|
||||
"MinimumLength": 5,
|
||||
@ -155,27 +201,48 @@
|
||||
"Number": false,
|
||||
"Uppercase": false,
|
||||
"Symbol": false,
|
||||
"Enable": false
|
||||
"EnableForgotLink": true
|
||||
},
|
||||
"FileSettings": {
|
||||
"EnableFileAttachments": true,
|
||||
"EnableMobileUpload": true,
|
||||
"EnableMobileDownload": true,
|
||||
"MaxFileSize": 104857600,
|
||||
"MaxImageResolution": 33177600,
|
||||
"MaxImageDecoderConcurrency": -1,
|
||||
"DriverName": "local",
|
||||
"Directory": "./data/",
|
||||
"EnablePublicLink": false,
|
||||
"ExtractContent": true,
|
||||
"ArchiveRecursion": false,
|
||||
"PublicLinkSalt": "",
|
||||
"InitialFont": "nunito-bold.ttf",
|
||||
"AmazonS3AccessKeyId": "",
|
||||
"AmazonS3SecretAccessKey": "",
|
||||
"AmazonS3Bucket": "",
|
||||
"AmazonS3PathPrefix": "",
|
||||
"AmazonS3Region": "",
|
||||
"AmazonS3Endpoint": "s3.amazonaws.com",
|
||||
"AmazonS3SSL": true,
|
||||
"AmazonS3SignV2": false,
|
||||
"AmazonS3SSE": false,
|
||||
"AmazonS3Trace": false
|
||||
"AmazonS3Trace": false,
|
||||
"AmazonS3RequestTimeoutMilliseconds": 30000,
|
||||
"DedicatedExportStore": false,
|
||||
"ExportDriverName": "local",
|
||||
"ExportDirectory": "./data/",
|
||||
"ExportAmazonS3AccessKeyId": "",
|
||||
"ExportAmazonS3SecretAccessKey": "",
|
||||
"ExportAmazonS3Bucket": "",
|
||||
"ExportAmazonS3PathPrefix": "",
|
||||
"ExportAmazonS3Region": "",
|
||||
"ExportAmazonS3Endpoint": "s3.amazonaws.com",
|
||||
"ExportAmazonS3SSL": true,
|
||||
"ExportAmazonS3SignV2": false,
|
||||
"ExportAmazonS3SSE": false,
|
||||
"ExportAmazonS3Trace": false,
|
||||
"ExportAmazonS3RequestTimeoutMilliseconds": 30000,
|
||||
"ExportAmazonS3PresignExpiresSeconds": 21600
|
||||
},
|
||||
"EmailSettings": {
|
||||
"EnableSignUpWithEmail": true,
|
||||
@ -197,7 +264,10 @@
|
||||
"ConnectionSecurity": "",
|
||||
"SendPushNotifications": true,
|
||||
"PushNotificationServer": "https://push-test.mattermost.com",
|
||||
"PushNotificationServerType": "custom",
|
||||
"PushNotificationServerLocation": "us",
|
||||
"PushNotificationContents": "generic",
|
||||
"PushNotificationBuffer": 1000,
|
||||
"EnableEmailBatching": false,
|
||||
"EmailBatchingBufferSize": 256,
|
||||
"EmailBatchingInterval": 30,
|
||||
@ -224,9 +294,10 @@
|
||||
"SupportSettings": {
|
||||
"TermsOfServiceLink": "https://mattermost.com/pl/terms-of-use/",
|
||||
"PrivacyPolicyLink": "https://mattermost.com/pl/privacy-policy/",
|
||||
"AboutLink": "https://docs.mattermost.com/pl/about-mattermost",
|
||||
"AboutLink": "https://mattermost.com/pl/about-mattermost",
|
||||
"HelpLink": "https://mattermost.com/pl/help/",
|
||||
"ReportAProblemLink": "https://mattermost.com/pl/report-a-bug",
|
||||
"ForgotPasswordLink": "",
|
||||
"SupportEmail": "",
|
||||
"CustomTermsOfServiceEnabled": false,
|
||||
"CustomTermsOfServiceReAcceptancePeriod": 365,
|
||||
@ -239,7 +310,10 @@
|
||||
"BannerTextColor": "#333333",
|
||||
"AllowBannerDismissal": true,
|
||||
"AdminNoticesEnabled": false,
|
||||
"UserNoticesEnabled": false
|
||||
"UserNoticesEnabled": false,
|
||||
"NoticesURL": "https://notices.mattermost.com/",
|
||||
"NoticesFetchFrequency": 3600,
|
||||
"NoticesSkipCache": false
|
||||
},
|
||||
"ThemeSettings": {
|
||||
"EnableThemeSelection": true,
|
||||
@ -254,7 +328,10 @@
|
||||
"Scope": "",
|
||||
"AuthEndpoint": "",
|
||||
"TokenEndpoint": "",
|
||||
"UserAPIEndpoint": ""
|
||||
"UserAPIEndpoint": "",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": ""
|
||||
},
|
||||
"GoogleSettings": {
|
||||
"Enable": false,
|
||||
@ -263,7 +340,10 @@
|
||||
"Scope": "profile email",
|
||||
"AuthEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"TokenEndpoint": "https://www.googleapis.com/oauth2/v4/token",
|
||||
"UserAPIEndpoint": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata"
|
||||
"UserAPIEndpoint": "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": ""
|
||||
},
|
||||
"Office365Settings": {
|
||||
"Enable": false,
|
||||
@ -273,8 +353,21 @@
|
||||
"AuthEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
"TokenEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
"UserAPIEndpoint": "https://graph.microsoft.com/v1.0/me",
|
||||
"DiscoveryEndpoint": "",
|
||||
"DirectoryId": ""
|
||||
},
|
||||
"OpenIdSettings": {
|
||||
"Enable": false,
|
||||
"Secret": "",
|
||||
"Id": "",
|
||||
"Scope": "profile openid email",
|
||||
"AuthEndpoint": "",
|
||||
"TokenEndpoint": "",
|
||||
"UserAPIEndpoint": "",
|
||||
"DiscoveryEndpoint": "",
|
||||
"ButtonText": "",
|
||||
"ButtonColor": "#145DBF"
|
||||
},
|
||||
"LdapSettings": {
|
||||
"Enable": true,
|
||||
"EnableSync": false,
|
||||
@ -302,6 +395,8 @@
|
||||
"PictureAttribute": "",
|
||||
"SyncIntervalMinutes": 10000,
|
||||
"SkipCertificateVerification": true,
|
||||
"PublicCertificateFile": "",
|
||||
"PrivateKeyFile": "",
|
||||
"QueryTimeout": 60,
|
||||
"MaxPageSize": 500,
|
||||
"LoginFieldName": "",
|
||||
@ -313,7 +408,8 @@
|
||||
"ComplianceSettings": {
|
||||
"Enable": false,
|
||||
"Directory": "./data/",
|
||||
"EnableDaily": false
|
||||
"EnableDaily": false,
|
||||
"BatchSize": 30000
|
||||
},
|
||||
"LocalizationSettings": {
|
||||
"DefaultServerLocale": "en",
|
||||
@ -324,12 +420,14 @@
|
||||
"Enable": false,
|
||||
"EnableSyncWithLdap": false,
|
||||
"EnableSyncWithLdapIncludeAuth": false,
|
||||
"IgnoreGuestsLdapSync": false,
|
||||
"Verify": true,
|
||||
"Encrypt": true,
|
||||
"SignRequest": false,
|
||||
"IdpURL": "",
|
||||
"IdpDescriptorURL": "",
|
||||
"IdpMetadataURL": "",
|
||||
"ServiceProviderIdentifier": "",
|
||||
"AssertionConsumerServiceURL": "",
|
||||
"SignatureAlgorithm": "RSAwithSHA1",
|
||||
"CanonicalAlgorithm": "Canonical1.0",
|
||||
@ -355,10 +453,28 @@
|
||||
"LoginButtonTextColor": "#ffffff"
|
||||
},
|
||||
"NativeAppSettings": {
|
||||
"AppCustomURLSchemes": ["mmauth://", "mmauthbeta://"],
|
||||
"AppDownloadLink": "https://mattermost.com/pl/download-apps",
|
||||
"AndroidAppDownloadLink": "https://mattermost.com/pl/android-app/",
|
||||
"IosAppDownloadLink": "https://mattermost.com/pl/ios-app/"
|
||||
},
|
||||
"ClusterSettings": {
|
||||
"Enable": false,
|
||||
"ClusterName": "",
|
||||
"OverrideHostname": "",
|
||||
"NetworkInterface": "",
|
||||
"BindAddress": "",
|
||||
"AdvertiseAddress": "",
|
||||
"UseIPAddress": true,
|
||||
"EnableGossipCompression": true,
|
||||
"EnableExperimentalGossipEncryption": false,
|
||||
"ReadOnlyConfig": true,
|
||||
"GossipPort": 8074,
|
||||
"StreamingPort": 8075,
|
||||
"MaxIdleConns": 100,
|
||||
"MaxIdleConnsPerHost": 128,
|
||||
"IdleConnTimeoutMilliseconds": 90000
|
||||
},
|
||||
"MetricsSettings": {
|
||||
"Enable": false,
|
||||
"BlockProfileRate": 0,
|
||||
@ -370,7 +486,11 @@
|
||||
"LinkMetadataTimeoutMilliseconds": 5000,
|
||||
"RestrictSystemAdmin": false,
|
||||
"UseNewSAMLLibrary": false,
|
||||
"DisableAppBar": false
|
||||
"EnableSharedChannels": false,
|
||||
"EnableRemoteClusterService": false,
|
||||
"DisableAppBar": false,
|
||||
"DisableRefetchingOnBrowserFocus": false,
|
||||
"DelayChannelAutocomplete": false
|
||||
},
|
||||
"AnalyticsSettings": {
|
||||
"MaxUsersForStatistics": 2500
|
||||
@ -393,17 +513,33 @@
|
||||
"PostsAggregatorJobStartTime": "03:00",
|
||||
"IndexPrefix": "",
|
||||
"LiveIndexingBatchSize": 1,
|
||||
"BulkIndexingTimeWindowSeconds": 3600,
|
||||
"BatchSize": 10000,
|
||||
"RequestTimeoutSeconds": 30,
|
||||
"SkipTLSVerification": false,
|
||||
"Trace": ""
|
||||
"CA": "",
|
||||
"ClientCert": "",
|
||||
"ClientKey": "",
|
||||
"Trace": "",
|
||||
"IgnoredPurgeIndexes": ""
|
||||
},
|
||||
"BleveSettings": {
|
||||
"IndexDir": "",
|
||||
"EnableIndexing": false,
|
||||
"EnableSearching": false,
|
||||
"EnableAutocomplete": false,
|
||||
"BatchSize": 10000
|
||||
},
|
||||
"DataRetentionSettings": {
|
||||
"EnableMessageDeletion": false,
|
||||
"EnableFileDeletion": false,
|
||||
"EnableBoardsDeletion": false,
|
||||
"MessageRetentionDays": 365,
|
||||
"FileRetentionDays": 365,
|
||||
"DeletionJobStartTime": "02:00"
|
||||
"BoardsRetentionDays": 365,
|
||||
"DeletionJobStartTime": "02:00",
|
||||
"BatchSize": 3000,
|
||||
"TimeBetweenBatchesMilliseconds": 100,
|
||||
"RetentionIdsBatchSize": 100
|
||||
},
|
||||
"MessageExportSettings": {
|
||||
"EnableExport": false,
|
||||
@ -411,17 +547,22 @@
|
||||
"DailyRunTime": "01:00",
|
||||
"ExportFromTimestamp": 0,
|
||||
"BatchSize": 10000,
|
||||
"DownloadExportResults": false,
|
||||
"GlobalRelaySettings": {
|
||||
"CustomerType": "A9",
|
||||
"SMTPUsername": "",
|
||||
"SMTPPassword": "",
|
||||
"EmailAddress": ""
|
||||
"EmailAddress": "",
|
||||
"SMTPServerTimeout": 1800
|
||||
}
|
||||
},
|
||||
"JobSettings": {
|
||||
"RunJobs": true,
|
||||
"RunScheduler": true
|
||||
"RunScheduler": true,
|
||||
"CleanupJobsThresholdDays": -1,
|
||||
"CleanupConfigThresholdDays": -1
|
||||
},
|
||||
"ProductSettings": {},
|
||||
"PluginSettings": {
|
||||
"Enable": true,
|
||||
"EnableUploads": true,
|
||||
@ -431,17 +572,14 @@
|
||||
"ClientDirectory": "./client/plugins",
|
||||
"Plugins": {},
|
||||
"PluginStates": {
|
||||
"com.mattermost.calls": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.nps": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.plugin-incident-response": {
|
||||
"Enable": false
|
||||
},
|
||||
"com.mattermost.plugin-incident-management": {
|
||||
"Enable": false
|
||||
},
|
||||
"focalboard": {
|
||||
"Enable": false
|
||||
"playbooks": {
|
||||
"Enable": true
|
||||
}
|
||||
},
|
||||
"EnableMarketplace": true,
|
||||
@ -449,14 +587,17 @@
|
||||
"AutomaticPrepackagedPlugins": true,
|
||||
"RequirePluginSignature": false,
|
||||
"MarketplaceURL": "https://api.integrations.mattermost.com",
|
||||
"SignaturePublicKeyFiles": []
|
||||
"SignaturePublicKeyFiles": [],
|
||||
"ChimeraOAuthProxyURL": ""
|
||||
},
|
||||
"DisplaySettings": {
|
||||
"CustomURLSchemes": [],
|
||||
"MaxMarkdownNodes": 0,
|
||||
"ExperimentalTimezone": false
|
||||
},
|
||||
"GuestAccountsSettings": {
|
||||
"Enable": true,
|
||||
"HideTags": false,
|
||||
"AllowEmailAccounts": true,
|
||||
"EnforceMultifactorAuthentication": false,
|
||||
"RestrictCreationToDomains": ""
|
||||
@ -466,5 +607,13 @@
|
||||
"ImageProxyType": "local",
|
||||
"RemoteImageProxyURL": "",
|
||||
"RemoteImageProxyOptions": ""
|
||||
},
|
||||
"ImportSettings": {
|
||||
"Directory": "./import",
|
||||
"RetentionDays": 30
|
||||
},
|
||||
"ExportSettings": {
|
||||
"Directory": "./export",
|
||||
"RetentionDays": 30
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Client4 from 'mattermost-redux/client/client4';
|
||||
import {Client4} from '@mattermost/client';
|
||||
|
||||
import clientRequest from '../plugins/client_request';
|
||||
|
||||
|
1
e2e-tests/cypress/tests/support/index.d.ts
vendored
1
e2e-tests/cypress/tests/support/index.d.ts
vendored
@ -33,6 +33,7 @@ declare namespace Cypress {
|
||||
type UserCustomStatus = import('@mattermost/types/users').UserCustomStatus;
|
||||
type UserAccessToken = import('@mattermost/types/users').UserAccessToken;
|
||||
type DeepPartial = import('@mattermost/types/utilities').DeepPartial;
|
||||
type Group = import('@mattermost/types/groups').Group;
|
||||
interface Chainable {
|
||||
tab: (options?: {shift?: boolean}) => Chainable<JQuery>;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ Cypress.Commands.add('createPlaybook', (teamName, playbookName) => {
|
||||
// Select the playbook from the dropdown menu
|
||||
Cypress.Commands.add('selectPlaybookFromDropdown', (playbookName) => {
|
||||
cy.findByTestId('autoCompleteSelector').should('exist').within(() => {
|
||||
cy.get('input').click().type(playbookName.toLowerCase());
|
||||
cy.get('input').click().type(playbookName.toLowerCase(), {force: true});
|
||||
cy.get('#suggestionList').contains(playbookName).click({force: true});
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@
|
||||
# Typically run the local server with:
|
||||
cd server && make run
|
||||
|
||||
# Or build and distribute webapp including channels, boards and playbooks
|
||||
# Or build and distribute webapp including channels and playbooks
|
||||
# so that their product URLs do not rely on Webpack dev server.
|
||||
# Especially important when running test inside the Playwright's docker container.
|
||||
cd webapp && make dist
|
||||
@ -45,7 +45,7 @@ npm run test
|
||||
Change to root directory, run docker container
|
||||
|
||||
```
|
||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.36.0-focal /bin/bash
|
||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.38.0-jammy /bin/bash
|
||||
```
|
||||
|
||||
#### 2. Inside the docker container
|
||||
|
@ -48,7 +48,7 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
|
||||
await client.createTeam(createRandomTeam(defaultTeam.name, defaultTeam.displayName, 'O', false));
|
||||
} else if (myDefaultTeam && testConfig.resetBeforeTest) {
|
||||
await Promise.all(
|
||||
myTeams.filter((team) => team.name !== defaultTeam.name).map((team) => client.deleteTeam(team.id))
|
||||
myTeams.filter((team) => team.name !== defaultTeam.name).map((team) => client.deleteTeam(team.id)),
|
||||
);
|
||||
|
||||
const myChannels = await client.getMyChannels(myDefaultTeam.id);
|
||||
@ -61,7 +61,7 @@ async function sysadminSetup(client: Client, user: UserProfile | null) {
|
||||
channel.name !== 'off-topic'
|
||||
);
|
||||
})
|
||||
.map((channel) => client.deleteChannel(channel.id))
|
||||
.map((channel) => client.deleteChannel(channel.id)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ async function ensureServerDeployment(client: Client) {
|
||||
sameClusterName,
|
||||
sameClusterName
|
||||
? ''
|
||||
: `Should have cluster name set and as expected. Got "${ClusterName}" but expected "${haClusterName}"`
|
||||
: `Should have cluster name set and as expected. Got "${ClusterName}" but expected "${haClusterName}"`,
|
||||
).toBe(true);
|
||||
|
||||
const clusterInfo = await client.getClusterStatus();
|
||||
@ -179,12 +179,12 @@ async function ensureServerDeployment(client: Client) {
|
||||
sameCount,
|
||||
sameCount
|
||||
? ''
|
||||
: `Should match number of nodes in a cluster as expected. Got "${clusterInfo?.length}" but expected "${haClusterNodeCount}"`
|
||||
: `Should match number of nodes in a cluster as expected. Got "${clusterInfo?.length}" but expected "${haClusterNodeCount}"`,
|
||||
).toBe(true);
|
||||
|
||||
clusterInfo.forEach((info) =>
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`hostname: ${info.hostname}, version: ${info.version}, config_hash: ${info.config_hash}`)
|
||||
console.log(`hostname: ${info.hostname}, version: ${info.version}, config_hash: ${info.config_hash}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
950
e2e-tests/playwright/package-lock.json
generated
950
e2e-tests/playwright/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,34 +4,36 @@
|
||||
"percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=iphone --project=ipad",
|
||||
"tsc": "tsc -b",
|
||||
"lint": "eslint . --ext .js,.ts",
|
||||
"prettier": "prettier --write .",
|
||||
"prettier": "prettier . --check",
|
||||
"prettier:fix": "prettier --write .",
|
||||
"check": "npm run tsc && npm run lint && npm run prettier",
|
||||
"codegen": "cross-env playwright codegen $PW_BASE_URL",
|
||||
"playwright-ui": "playwright test --ui",
|
||||
"test-slomo": "cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
|
||||
"show-report": "npx playwright show-report"
|
||||
"show-report": "npx playwright show-report",
|
||||
"postinstall": "npx playwright install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@axe-core/playwright": "4.7.3",
|
||||
"@percy/cli": "1.26.2",
|
||||
"@percy/cli": "1.27.1",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.36.1",
|
||||
"@playwright/test": "1.38.0",
|
||||
"async-wait-until": "2.0.12",
|
||||
"axe-core": "4.7.2",
|
||||
"axe-core": "4.8.1",
|
||||
"chalk": "4.1.2",
|
||||
"deepmerge": "4.3.1",
|
||||
"dotenv": "16.3.1",
|
||||
"form-data": "4.0.0",
|
||||
"isomorphic-unfetch": "4.0.2",
|
||||
"uuid": "9.0.0"
|
||||
"uuid": "9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.1.0",
|
||||
"@typescript-eslint/parser": "6.1.0",
|
||||
"@types/uuid": "9.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.0",
|
||||
"@typescript-eslint/parser": "6.7.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.45.0",
|
||||
"prettier": "2.8.7",
|
||||
"typescript": "5.0.4"
|
||||
"eslint": "8.49.0",
|
||||
"prettier": "3.0.3",
|
||||
"typescript": "5.2.2"
|
||||
}
|
||||
}
|
||||
|
@ -52,13 +52,6 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'iphone',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
...devices['iPhone 13 Pro'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
use: {
|
||||
|
@ -16,41 +16,38 @@
|
||||
# 5. PW_ADMIN_EMAIL
|
||||
# - Default to "sysadmin@sample.mattermost.com" if not set.
|
||||
|
||||
# 6. PW_BOARDS_PRODUCT_ENABLED
|
||||
# - Default to "true" if not set. Used to correctly set server config.
|
||||
|
||||
# 7. PW_HA_CLUSTER_ENABLED
|
||||
# 6. PW_HA_CLUSTER_ENABLED
|
||||
# - Default to "false" if not set. Set to true if the test server is with HA enabled.
|
||||
|
||||
# 8. PW_HA_CLUSTER_NODE_COUNT
|
||||
# 7. PW_HA_CLUSTER_NODE_COUNT
|
||||
# - Default to "2" if not set.
|
||||
|
||||
# 9. PW_HA_CLUSTER_NAME
|
||||
# 8. PW_HA_CLUSTER_NAME
|
||||
# - Default to "mm_dev_cluster" if not set.
|
||||
|
||||
# 10. PW_RESET_BEFORE_TEST
|
||||
# 9. PW_RESET_BEFORE_TEST
|
||||
# - Default to "false" if not set. If true, the setup deletes all teams and channels other than the default team which is "ad-1".
|
||||
|
||||
# 11. CI
|
||||
# 10. CI
|
||||
# - Default to "false" if not set.
|
||||
|
||||
# 12. PW_HEADLESS
|
||||
# 11. PW_HEADLESS
|
||||
# - Default to "false" or headless mode if not set. Set to true to run test in headed mode.
|
||||
|
||||
# 13. PW_SLOWMO
|
||||
# 12. PW_SLOWMO
|
||||
# - Default to "0" if not set which means normal test speed run. Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
|
||||
# 14. PW_WORKERS
|
||||
# 13. PW_WORKERS
|
||||
# - Default to "1" if not set. The maximum number of concurrent worker processes to use for parallelizing tests.
|
||||
|
||||
# 15. PW_SNAPSHOT_ENABLE
|
||||
# 14. PW_SNAPSHOT_ENABLE
|
||||
# - Default to "false" if not set. Set to true to enable snapshot testing.
|
||||
# Note that, snapshot testing should be done in Playwright docker image only.
|
||||
# This is to ensure that, there's a common base platform for all contributors
|
||||
# regardless of each local development platform.
|
||||
|
||||
# 16. PW_PERCY_ENABLE
|
||||
# 15. PW_PERCY_ENABLE
|
||||
# - Default to "false" if not set. Use to save and compare results via https://percy.io/.
|
||||
|
||||
# 17. PERCY_TOKEN
|
||||
# 16. PERCY_TOKEN
|
||||
# - A token required by https://percy.io/.
|
||||
|
@ -2,6 +2,5 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const appsPluginId = 'com.mattermost.apps';
|
||||
export const boardsPluginId = 'focalboard';
|
||||
export const callsPluginId = 'com.mattermost.calls';
|
||||
export const playbooksPluginId = 'playbooks';
|
||||
|
@ -3,11 +3,10 @@
|
||||
|
||||
import os from 'node:os';
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {expect} from '@playwright/test';
|
||||
|
||||
import {callsPluginId} from './constant';
|
||||
import {getAdminClient} from './server/init';
|
||||
import {isSmallScreen} from './util';
|
||||
|
||||
export async function shouldHaveCallsEnabled(enabled = true) {
|
||||
const {adminClient} = await getAdminClient();
|
||||
@ -26,14 +25,10 @@ export async function shouldHaveFeatureFlag(name: string, value: string | boolea
|
||||
const matched = config.FeatureFlags[name] === value;
|
||||
expect(
|
||||
matched,
|
||||
matched ? '' : `FeatureFlags["${name}'] expect "${value}" but actual "${config.FeatureFlags[name]}"`
|
||||
matched ? '' : `FeatureFlags["${name}'] expect "${value}" but actual "${config.FeatureFlags[name]}"`,
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
export function shouldSkipInSmallScreen() {
|
||||
test.skip(({viewport}) => isSmallScreen(viewport), 'Not applicable to mobile device');
|
||||
}
|
||||
|
||||
export async function shouldRunInLinux() {
|
||||
const platform = os.platform();
|
||||
await expect(platform, 'Run in Linux or Playwright docker image only').toBe('linux');
|
||||
|
@ -35,9 +35,6 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
||||
Enable: testConfig.haClusterEnabled,
|
||||
ClusterName: testConfig.haClusterName,
|
||||
},
|
||||
ExperimentalSettings: {
|
||||
DisableAppBar: false,
|
||||
},
|
||||
PasswordSettings: {
|
||||
MinimumLength: 5,
|
||||
Lowercase: false,
|
||||
@ -48,9 +45,15 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
||||
},
|
||||
PluginSettings: {
|
||||
EnableUploads: true,
|
||||
Plugins: {
|
||||
PluginStates: {
|
||||
'com.mattermost.calls': {
|
||||
defaultenabled: true,
|
||||
Enable: false,
|
||||
},
|
||||
'com.mattermost.nps': {
|
||||
Enable: false,
|
||||
},
|
||||
playbooks: {
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -65,7 +68,7 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
||||
};
|
||||
|
||||
// Should be based only from the generated default config from ./server via "make config-reset"
|
||||
// Based on v7.10 server
|
||||
// Based on v9.1 server
|
||||
const defaultServerConfig: AdminConfig = {
|
||||
ServiceSettings: {
|
||||
SiteURL: '',
|
||||
@ -127,8 +130,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
WebsocketPort: 80,
|
||||
WebserverMode: 'gzip',
|
||||
EnableGifPicker: true,
|
||||
GfycatAPIKey: '2_KtH_W5',
|
||||
GfycatAPISecret: '3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof',
|
||||
GiphySdkKey: 's0glxvzVg9azvPipKxcPLpXV0q1x1fVP',
|
||||
EnableCustomEmoji: true,
|
||||
EnableEmojiPicker: true,
|
||||
PostEditTimeLimit: -1,
|
||||
@ -158,6 +160,11 @@ const defaultServerConfig: AdminConfig = {
|
||||
EnableLatex: false,
|
||||
EnableInlineLatex: true,
|
||||
PostPriority: true,
|
||||
AllowPersistentNotifications: true,
|
||||
AllowPersistentNotificationsForGuests: false,
|
||||
PersistentNotificationIntervalMinutes: 5,
|
||||
PersistentNotificationMaxCount: 6,
|
||||
PersistentNotificationMaxRecipients: 5,
|
||||
EnableAPIChannelDeletion: false,
|
||||
EnableLocalMode: false,
|
||||
LocalModeSocketLocation: '/var/tmp/mattermost_local.socket',
|
||||
@ -171,15 +178,11 @@ const defaultServerConfig: AdminConfig = {
|
||||
EnableCustomGroups: true,
|
||||
SelfHostedPurchase: true,
|
||||
AllowSyncedDrafts: true,
|
||||
AllowPersistentNotifications: true,
|
||||
PersistentNotificationMaxCount: 6,
|
||||
PersistentNotificationMaxRecipients: 5,
|
||||
PersistentNotificationIntervalMinutes: 5,
|
||||
AllowPersistentNotificationsForGuests: false,
|
||||
},
|
||||
TeamSettings: {
|
||||
SiteName: 'Mattermost',
|
||||
MaxUsersPerTeam: 50,
|
||||
EnableJoinLeaveMessageByDefault: true,
|
||||
EnableUserCreation: true,
|
||||
EnableOpenServer: false,
|
||||
EnableUserDeactivation: false,
|
||||
@ -223,6 +226,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
DisableDatabaseSearch: false,
|
||||
MigrationsStatementTimeoutSeconds: 100000,
|
||||
ReplicaLagSettings: [],
|
||||
ReplicaMonitorIntervalSeconds: 5,
|
||||
},
|
||||
LogSettings: {
|
||||
EnableConsole: true,
|
||||
@ -237,6 +241,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
EnableDiagnostics: true,
|
||||
VerboseDiagnostics: false,
|
||||
EnableSentry: true,
|
||||
AdvancedLoggingJSON: {},
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
ExperimentalAuditSettings: {
|
||||
@ -247,6 +252,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
FileMaxBackups: 0,
|
||||
FileCompress: false,
|
||||
FileMaxQueueSize: 1000,
|
||||
AdvancedLoggingJSON: {},
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
NotificationLogSettings: {
|
||||
@ -258,6 +264,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
FileLevel: 'INFO',
|
||||
FileJson: true,
|
||||
FileLocation: '',
|
||||
AdvancedLoggingJSON: {},
|
||||
AdvancedLoggingConfig: '',
|
||||
},
|
||||
PasswordSettings: {
|
||||
@ -293,6 +300,21 @@ const defaultServerConfig: AdminConfig = {
|
||||
AmazonS3SSE: false,
|
||||
AmazonS3Trace: false,
|
||||
AmazonS3RequestTimeoutMilliseconds: 30000,
|
||||
DedicatedExportStore: false,
|
||||
ExportDriverName: 'local',
|
||||
ExportDirectory: './data/',
|
||||
ExportAmazonS3AccessKeyId: '',
|
||||
ExportAmazonS3SecretAccessKey: '',
|
||||
ExportAmazonS3Bucket: '',
|
||||
ExportAmazonS3PathPrefix: '',
|
||||
ExportAmazonS3Region: '',
|
||||
ExportAmazonS3Endpoint: 's3.amazonaws.com',
|
||||
ExportAmazonS3SSL: true,
|
||||
ExportAmazonS3SignV2: false,
|
||||
ExportAmazonS3SSE: false,
|
||||
ExportAmazonS3Trace: false,
|
||||
ExportAmazonS3RequestTimeoutMilliseconds: 30000,
|
||||
ExportAmazonS3PresignExpiresSeconds: 21600,
|
||||
},
|
||||
EmailSettings: {
|
||||
EnableSignUpWithEmail: true,
|
||||
@ -344,7 +366,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
SupportSettings: {
|
||||
TermsOfServiceLink: 'https://mattermost.com/pl/terms-of-use/',
|
||||
PrivacyPolicyLink: 'https://mattermost.com/pl/privacy-policy/',
|
||||
AboutLink: 'https://docs.mattermost.com/pl/about-mattermost',
|
||||
AboutLink: 'https://mattermost.com/pl/about-mattermost',
|
||||
HelpLink: 'https://mattermost.com/pl/help/',
|
||||
ReportAProblemLink: 'https://mattermost.com/pl/report-a-bug',
|
||||
ForgotPasswordLink: '',
|
||||
@ -539,7 +561,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
UseNewSAMLLibrary: false,
|
||||
EnableSharedChannels: false,
|
||||
EnableRemoteClusterService: false,
|
||||
DisableAppBar: true,
|
||||
DisableAppBar: false,
|
||||
DisableRefetchingOnBrowserFocus: false,
|
||||
DelayChannelAutocomplete: false,
|
||||
},
|
||||
@ -589,6 +611,8 @@ const defaultServerConfig: AdminConfig = {
|
||||
BoardsRetentionDays: 365,
|
||||
DeletionJobStartTime: '02:00',
|
||||
BatchSize: 3000,
|
||||
TimeBetweenBatchesMilliseconds: 100,
|
||||
RetentionIdsBatchSize: 100,
|
||||
},
|
||||
MessageExportSettings: {
|
||||
EnableExport: false,
|
||||
@ -627,6 +651,9 @@ const defaultServerConfig: AdminConfig = {
|
||||
'com.mattermost.nps': {
|
||||
Enable: true,
|
||||
},
|
||||
playbooks: {
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
EnableMarketplace: true,
|
||||
EnableRemoteMarketplace: true,
|
||||
@ -638,6 +665,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
},
|
||||
DisplaySettings: {
|
||||
CustomURLSchemes: [],
|
||||
MaxMarkdownNodes: 0,
|
||||
ExperimentalTimezone: true,
|
||||
},
|
||||
GuestAccountsSettings: {
|
||||
@ -656,32 +684,24 @@ const defaultServerConfig: AdminConfig = {
|
||||
CloudSettings: {
|
||||
CWSURL: 'https://customers.mattermost.com',
|
||||
CWSAPIURL: 'https://portal.internal.prod.cloud.mattermost.com',
|
||||
CWSMock: false,
|
||||
},
|
||||
FeatureFlags: {
|
||||
TestFeature: 'off',
|
||||
TestBoolFeature: false,
|
||||
EnableRemoteClusterService: false,
|
||||
AppsEnabled: true,
|
||||
PluginPlaybooks: '',
|
||||
PluginApps: '',
|
||||
PluginFocalboard: '',
|
||||
PluginCalls: '',
|
||||
PermalinkPreviews: true,
|
||||
PermalinkPreviews: false,
|
||||
CallsEnabled: true,
|
||||
BoardsFeatureFlags: '',
|
||||
BoardsDataRetention: false,
|
||||
NormalizeLdapDNs: false,
|
||||
GraphQL: false,
|
||||
CommandPalette: false,
|
||||
SendWelcomePost: true,
|
||||
PostPriority: true,
|
||||
PostPriority: false,
|
||||
WysiwygEditor: false,
|
||||
PeopleProduct: false,
|
||||
ReduceOnBoardingTaskList: false,
|
||||
ThreadsEverywhere: false,
|
||||
OnboardingTourTips: true,
|
||||
DeprecateCloudFree: false,
|
||||
CloudReverseTrial: false,
|
||||
EnableExportDirectDownload: false,
|
||||
StreamlinedMarketplace: true,
|
||||
},
|
||||
ImportSettings: {
|
||||
Directory: './import',
|
||||
|
@ -24,7 +24,7 @@ export async function initSetup({
|
||||
const {adminClient, adminUser} = await getAdminClient();
|
||||
if (!adminClient) {
|
||||
throw new Error(
|
||||
"Failed to setup admin: Check that you're able to access the server using the same admin credential."
|
||||
"Failed to setup admin: Check that you're able to access the server using the same admin credential.",
|
||||
);
|
||||
}
|
||||
|
||||
@ -83,8 +83,8 @@ export async function initSetup({
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
chalk.green(
|
||||
`This failed due to the experimental fetch support in Node.js starting v18.0.0.\nYou may set environment variable: "export NODE_OPTIONS='--no-experimental-fetch'", then try again.'`
|
||||
)
|
||||
`This failed due to the experimental fetch support in Node.js starting v18.0.0.\nYou may set environment variable: "export NODE_OPTIONS='--no-experimental-fetch'", then try again.'`,
|
||||
),
|
||||
);
|
||||
}
|
||||
expect(err, 'Should not throw an error').toBeFalsy();
|
||||
|
@ -16,6 +16,6 @@ export async function hideDynamicChannelsContent(page: Page) {
|
||||
|
||||
export async function waitForAnimationEnd(locator: Locator) {
|
||||
return locator.evaluate((element) =>
|
||||
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished))
|
||||
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished)),
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import {test as base, Browser, Page, ViewportSize} from '@playwright/test';
|
||||
import {test as base, Browser, Page} from '@playwright/test';
|
||||
import {AxeResults} from 'axe-core';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
|
||||
import {TestBrowser} from './browser_context';
|
||||
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
|
||||
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldRunInLinux} from './flag';
|
||||
import {initSetup, getAdminClient} from './server';
|
||||
import {isSmallScreen} from './util';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||
import {pages} from './ui/pages';
|
||||
import {matchSnapshot} from './visual';
|
||||
@ -29,8 +28,8 @@ export const test = base.extend<ExtendedFixtures>({
|
||||
const ab = new AxeBuilderExtended();
|
||||
await use(ab);
|
||||
},
|
||||
pw: async ({browser, viewport}, use) => {
|
||||
const pw = new PlaywrightExtended(browser, viewport);
|
||||
pw: async ({browser}, use) => {
|
||||
const pw = new PlaywrightExtended(browser);
|
||||
await use(pw);
|
||||
await pw.testBrowser.close();
|
||||
},
|
||||
@ -47,7 +46,6 @@ class PlaywrightExtended {
|
||||
// ./flag
|
||||
readonly shouldHaveCallsEnabled;
|
||||
readonly shouldHaveFeatureFlag;
|
||||
readonly shouldSkipInSmallScreen;
|
||||
readonly shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
@ -62,20 +60,16 @@ class PlaywrightExtended {
|
||||
// ./ui/pages
|
||||
readonly pages;
|
||||
|
||||
// ./util
|
||||
readonly isSmallScreen;
|
||||
|
||||
// ./visual
|
||||
readonly matchSnapshot;
|
||||
|
||||
constructor(browser: Browser, viewport: ViewportSize | null) {
|
||||
constructor(browser: Browser) {
|
||||
// ./browser_context
|
||||
this.testBrowser = new TestBrowser(browser);
|
||||
|
||||
// ./flag
|
||||
this.shouldHaveCallsEnabled = shouldHaveCallsEnabled;
|
||||
this.shouldHaveFeatureFlag = shouldHaveFeatureFlag;
|
||||
this.shouldSkipInSmallScreen = shouldSkipInSmallScreen;
|
||||
this.shouldRunInLinux = shouldRunInLinux;
|
||||
|
||||
// ./server
|
||||
@ -90,9 +84,6 @@ class PlaywrightExtended {
|
||||
// ./ui/pages
|
||||
this.pages = pages;
|
||||
|
||||
// ./util
|
||||
this.isSmallScreen = () => isSmallScreen(viewport);
|
||||
|
||||
// ./visual
|
||||
this.matchSnapshot = matchSnapshot;
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class BoardsCreateModal {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly productSwitchMenu;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.productSwitchMenu = container.getByRole('button', {name: 'Product switch menu'});
|
||||
}
|
||||
|
||||
async switchProduct(name: string) {
|
||||
await this.productSwitchMenu.click();
|
||||
await this.container.getByRole('link', {name: ` ${name}`}).click();
|
||||
}
|
||||
|
||||
async toBeVisible(name: string) {
|
||||
await expect(this.container.getByRole('heading', {name})).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {BoardsCreateModal};
|
@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator} from '@playwright/test';
|
||||
|
||||
export default class BoardsSidebar {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly plusButton;
|
||||
readonly createNewBoardMenuItem;
|
||||
readonly createNewCategoryMenuItem;
|
||||
readonly titles;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.plusButton = container.locator('.add-board-icon');
|
||||
this.createNewBoardMenuItem = container.getByRole('button', {name: 'Create new board'});
|
||||
this.createNewCategoryMenuItem = container.getByRole('button', {name: 'Create New Category'});
|
||||
this.titles = container.locator('.SidebarBoardItem > .octo-sidebar-title');
|
||||
}
|
||||
|
||||
async waitForTitle(name: string) {
|
||||
await this.container.getByRole('button', {name: ` ${name}`}).waitFor({state: 'visible'});
|
||||
}
|
||||
}
|
||||
|
||||
export {BoardsSidebar};
|
@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
import {waitUntil} from '@e2e-support/test_action';
|
||||
import {duration} from '@e2e-support/util';
|
||||
|
||||
export default class ChannelsCenterView {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly header;
|
||||
readonly headerMobile;
|
||||
readonly postCreate;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.header = new components.ChannelsHeader(this.container.locator('.channel-header'));
|
||||
this.headerMobile = new components.ChannelsHeaderMobile(this.container.locator('.navbar'));
|
||||
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('post-create'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
await this.postCreate.toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first post in the Center
|
||||
*/
|
||||
async getFirstPost() {
|
||||
const firstPost = this.container.getByTestId('postView').first();
|
||||
await firstPost.waitFor();
|
||||
return new components.ChannelsPost(firstPost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last post in the Center
|
||||
*/
|
||||
async getLastPost() {
|
||||
const lastPost = this.container.getByTestId('postView').last();
|
||||
await lastPost.waitFor();
|
||||
return new components.ChannelsPost(lastPost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Nth post in the Center from the top
|
||||
* @param index
|
||||
* @returns
|
||||
*/
|
||||
async getNthPost(index: number) {
|
||||
const nthPost = this.container.getByTestId('postView').nth(index);
|
||||
await nthPost.waitFor();
|
||||
return new components.ChannelsPost(nthPost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Center post by post's id
|
||||
* @param postId Just the ID without the prefix
|
||||
*/
|
||||
async getPostById(id: string) {
|
||||
const postById = this.container.locator(`[id="post_${id}"]`);
|
||||
await postById.waitFor();
|
||||
return new components.ChannelsPost(postById);
|
||||
}
|
||||
|
||||
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getLastPost();
|
||||
const content = await post.container.textContent();
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout},
|
||||
);
|
||||
}
|
||||
|
||||
async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getPostById(id);
|
||||
const content = await post.container.textContent();
|
||||
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsCenterView};
|
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class EmojiGifPicker {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly gifTab: Locator;
|
||||
readonly gifSearchInput: Locator;
|
||||
readonly gifPickerItems: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.gifTab = container.getByText('GIFs');
|
||||
this.gifSearchInput = container.getByPlaceholder('Search GIPHY');
|
||||
this.gifPickerItems = container.locator('.gif-picker__items');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async openGifTab() {
|
||||
await expect(this.gifTab).toBeVisible();
|
||||
|
||||
await this.gifTab.click({force: true});
|
||||
|
||||
await expect(this.gifSearchInput).toBeVisible();
|
||||
await expect(this.gifPickerItems).toBeVisible();
|
||||
}
|
||||
|
||||
async searchGif(name: string) {
|
||||
await this.gifSearchInput.fill(name);
|
||||
await expect(this.gifSearchInput).toHaveValue(name);
|
||||
}
|
||||
|
||||
async getNthGif(n: number) {
|
||||
await expect(this.gifPickerItems).toBeVisible();
|
||||
|
||||
await this.gifPickerItems.locator('img').nth(n).waitFor();
|
||||
const nthGif = this.gifPickerItems.locator('img').nth(n);
|
||||
await expect(nthGif).toBeVisible();
|
||||
|
||||
const nthGifSrc = await nthGif.getAttribute('src');
|
||||
const nthGifAlt = await nthGif.getAttribute('alt');
|
||||
|
||||
if (!nthGifSrc || !nthGifAlt) {
|
||||
throw new Error('Gif src or alt is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
src: nthGifSrc,
|
||||
alt: nthGifAlt,
|
||||
img: nthGif,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {EmojiGifPicker};
|
@ -10,13 +10,13 @@ export default class ChannelsHeaderMobile {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toggleSidebar() {
|
||||
await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click();
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async toggleSidebar() {
|
||||
await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click();
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsHeaderMobile};
|
||||
|
@ -7,30 +7,76 @@ export default class ChannelsPostCreate {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly input;
|
||||
|
||||
readonly attachmentButton;
|
||||
readonly emojiButton;
|
||||
readonly sendMessageButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, isRHS = false) {
|
||||
this.container = container;
|
||||
|
||||
this.input = container.getByTestId('post_textbox');
|
||||
if (!isRHS) {
|
||||
this.input = container.getByTestId('post_textbox');
|
||||
} else {
|
||||
this.input = container.getByTestId('reply_textbox');
|
||||
}
|
||||
|
||||
this.attachmentButton = container.getByLabel('attachment');
|
||||
this.emojiButton = container.getByLabel('select an emoji');
|
||||
this.sendMessageButton = container.getByTestId('SendMessageButton');
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
|
||||
await this.input.waitFor();
|
||||
await expect(this.input).toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* It just writes the message in the input and doesn't send it
|
||||
* @param message : Message to be written in the input
|
||||
*/
|
||||
async writeMessage(message: string) {
|
||||
await this.input.waitFor();
|
||||
await expect(this.input).toBeVisible();
|
||||
|
||||
await this.input.fill(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the message input
|
||||
*/
|
||||
async getInputValue() {
|
||||
await expect(this.input).toBeVisible();
|
||||
return await this.input.inputValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message already written in the input
|
||||
*/
|
||||
async sendMessage() {
|
||||
await expect(this.input).toBeVisible();
|
||||
const messageInputValue = await this.getInputValue();
|
||||
expect(messageInputValue).not.toBe('');
|
||||
|
||||
await expect(this.sendMessageButton).toBeVisible();
|
||||
await expect(this.sendMessageButton).toBeEnabled();
|
||||
|
||||
await this.sendMessageButton.click();
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
await expect(this.input).toBeVisible();
|
||||
/**
|
||||
* Composes and sends a message
|
||||
*/
|
||||
async postMessage(message: string) {
|
||||
await this.writeMessage(message);
|
||||
await this.sendMessage();
|
||||
}
|
||||
|
||||
async openEmojiPicker() {
|
||||
await expect(this.emojiButton).toBeVisible();
|
||||
await this.emojiButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,43 @@ import {expect, Locator} from '@playwright/test';
|
||||
export default class PostDotMenu {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly replyMenuItem;
|
||||
readonly forwardMenuItem;
|
||||
readonly followMessageMenuItem;
|
||||
readonly markAsUnreadMenuItem;
|
||||
readonly remindMenuItem;
|
||||
readonly saveMenuItem;
|
||||
readonly removeFromSavedMenuItem;
|
||||
readonly pinToChannelMenuItem;
|
||||
readonly unpinFromChannelMenuItem;
|
||||
readonly copyLinkMenuItem;
|
||||
readonly editMenuItem;
|
||||
readonly copyTextMenuItem;
|
||||
readonly deleteMenuItem;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.deleteMenuItem = this.container.getByText('Delete', {exact: true});
|
||||
const getMenuItem = (hasText: string) => container.getByRole('menuitem').filter({hasText});
|
||||
|
||||
this.replyMenuItem = getMenuItem('Reply');
|
||||
this.forwardMenuItem = getMenuItem('Forward');
|
||||
this.followMessageMenuItem = getMenuItem('Follow message');
|
||||
this.markAsUnreadMenuItem = getMenuItem('Mark as Unread');
|
||||
this.remindMenuItem = getMenuItem('Remind');
|
||||
this.saveMenuItem = getMenuItem('Save');
|
||||
this.removeFromSavedMenuItem = getMenuItem('Remove from Saved');
|
||||
this.pinToChannelMenuItem = getMenuItem('Pin to Channel');
|
||||
this.unpinFromChannelMenuItem = getMenuItem('Unpin from Channel');
|
||||
this.copyLinkMenuItem = getMenuItem('Copy Link');
|
||||
this.editMenuItem = getMenuItem('Edit');
|
||||
this.copyTextMenuItem = getMenuItem('Copy Text');
|
||||
this.deleteMenuItem = getMenuItem('Delete');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async delete() {
|
||||
await this.deleteMenuItem.waitFor();
|
||||
await this.deleteMenuItem.click();
|
||||
}
|
||||
}
|
||||
|
||||
export {PostDotMenu};
|
||||
|
@ -6,12 +6,24 @@ import {expect, Locator} from '@playwright/test';
|
||||
export default class PostMenu {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly plusOneEmojiButton;
|
||||
readonly grinningEmojiButton;
|
||||
readonly whiteCheckMarkEmojiButton;
|
||||
readonly addReactionButton;
|
||||
readonly saveButton;
|
||||
readonly replyButton;
|
||||
readonly actionsButton;
|
||||
readonly dotMenuButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.plusOneEmojiButton = container.getByRole('button', {name: '+1 emoji'});
|
||||
this.grinningEmojiButton = container.getByRole('button', {name: 'grinning emoji'});
|
||||
this.whiteCheckMarkEmojiButton = container.getByRole('button', {name: 'white check mark emoji'});
|
||||
this.addReactionButton = container.getByRole('button', {name: 'add reaction'});
|
||||
this.saveButton = container.getByRole('button', {name: 'save'});
|
||||
this.actionsButton = container.getByRole('button', {name: 'actions'});
|
||||
this.replyButton = container.getByRole('button', {name: 'reply'});
|
||||
this.dotMenuButton = container.getByRole('button', {name: 'more'});
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
export default class PostReminderMenu {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly thirtyMinsMenuItem;
|
||||
readonly oneHourMenuItem;
|
||||
readonly twoHoursMenuItem;
|
||||
readonly tomorrowMenuItem;
|
||||
readonly customMenuItem;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
const getMenuItem = (hasText: string) => container.getByRole('menuitem').filter({hasText});
|
||||
|
||||
this.thirtyMinsMenuItem = getMenuItem('30 mins');
|
||||
this.oneHourMenuItem = getMenuItem('1 hour');
|
||||
this.twoHoursMenuItem = getMenuItem('2 hours');
|
||||
this.tomorrowMenuItem = getMenuItem('Tomorrow');
|
||||
this.customMenuItem = getMenuItem('Custom');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {PostReminderMenu};
|
@ -8,16 +8,13 @@ import {components} from '@e2e-support/ui/components';
|
||||
export default class ChannelsSidebarRight {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly input;
|
||||
readonly sendMessageButton;
|
||||
readonly postCreate;
|
||||
readonly closeButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.input = container.getByTestId('reply_textbox');
|
||||
this.sendMessageButton = container.getByTestId('SendMessageButton');
|
||||
|
||||
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('comment-create'), true);
|
||||
this.closeButton = container.locator('#rhsCloseButton');
|
||||
}
|
||||
|
||||
@ -25,29 +22,23 @@ export default class ChannelsSidebarRight {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
await this.input.fill(message);
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
await this.sendMessageButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the textbox in RHS
|
||||
*/
|
||||
async getInputValue() {
|
||||
return await this.input.inputValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RHS post by post id
|
||||
* @param postId Just the ID without the prefix
|
||||
*/
|
||||
async getRHSPostById(postId: string) {
|
||||
const rhsPostId = `rhsPost_${postId}`;
|
||||
const postLocator = this.container.locator(`#${rhsPostId}`);
|
||||
return new components.ChannelsPost(postLocator);
|
||||
async getPostById(postId: string) {
|
||||
const post = this.container.locator(`[id="rhsPost_${postId}"]`);
|
||||
await post.waitFor();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last post in the RHS
|
||||
*/
|
||||
async getLastPost() {
|
||||
const post = this.container.getByTestId('rhsPostView').last();
|
||||
await post.waitFor();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,56 +1,60 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BoardsSidebar} from './boards/sidebar';
|
||||
import {ChannelsHeader} from './channels/header';
|
||||
import {ChannelsHeaderMobile} from './channels/header_mobile';
|
||||
import {ChannelsAppBar} from './channels/app_bar';
|
||||
import {ChannelsPostCreate} from './channels/post_create';
|
||||
import {ChannelsPost} from './channels/post';
|
||||
import {ChannelsCenterView} from './channels/center_view';
|
||||
import {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 {Footer} from './footer';
|
||||
import {GlobalHeader} from './global_header';
|
||||
import {MainHeader} from './main_header';
|
||||
import {PostDotMenu} from './channels/post_dot_menu';
|
||||
import {DeletePostModal} from './channels/delete_post_modal';
|
||||
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';
|
||||
|
||||
const components = {
|
||||
BoardsSidebar,
|
||||
GlobalHeader,
|
||||
ChannelsCenterView,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsHeaderMobile,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
FindChannelsModal,
|
||||
Footer,
|
||||
GlobalHeader,
|
||||
MainHeader,
|
||||
PostDotMenu,
|
||||
DeletePostModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
ThreadFooter,
|
||||
Footer,
|
||||
MainHeader,
|
||||
PostReminderMenu,
|
||||
EmojiGifPicker,
|
||||
};
|
||||
|
||||
export {
|
||||
components,
|
||||
BoardsSidebar,
|
||||
GlobalHeader,
|
||||
ChannelsCenterView,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsHeaderMobile,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPost,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
FindChannelsModal,
|
||||
GlobalHeader,
|
||||
PostDotMenu,
|
||||
DeletePostModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
ThreadFooter,
|
||||
};
|
||||
|
@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
|
||||
export default class BoardsCreatePage {
|
||||
readonly boards = 'Boards';
|
||||
readonly page: Page;
|
||||
|
||||
readonly globalHeader;
|
||||
|
||||
readonly createBoardHeading;
|
||||
readonly createEmptyBoardButton;
|
||||
readonly useTemplateButton;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
|
||||
this.createBoardHeading = page.getByRole('heading', {name: 'Create a board'});
|
||||
this.createEmptyBoardButton = page.getByRole('button', {name: ' Create an empty board'});
|
||||
this.useTemplateButton = page.getByRole('button', {name: 'Use this template'});
|
||||
}
|
||||
|
||||
async goto(teamId = '') {
|
||||
let boardsUrl = '/boards';
|
||||
if (teamId) {
|
||||
boardsUrl += `/team/${teamId}`;
|
||||
}
|
||||
|
||||
await this.page.goto(boardsUrl);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.globalHeader.toBeVisible(this.boards);
|
||||
await expect(this.createEmptyBoardButton).toBeVisible();
|
||||
await expect(this.useTemplateButton).toBeVisible();
|
||||
await expect(this.createBoardHeading).toBeVisible();
|
||||
}
|
||||
|
||||
async createEmptyBoard() {
|
||||
await this.createEmptyBoardButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
export {BoardsCreatePage};
|
@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
|
||||
export default class BoardsViewPage {
|
||||
readonly boards = 'Boards';
|
||||
readonly page: Page;
|
||||
|
||||
readonly sidebar;
|
||||
readonly globalHeader;
|
||||
|
||||
readonly topHead;
|
||||
readonly editableTitle;
|
||||
readonly shareButton;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.sidebar = new components.BoardsSidebar(page.locator('.octo-sidebar'));
|
||||
this.globalHeader = new components.GlobalHeader(this.page.locator('#global-header'));
|
||||
this.topHead = page.locator('.top-head');
|
||||
this.editableTitle = this.topHead.getByPlaceholder('Untitled board');
|
||||
this.shareButton = page.getByRole('button', {name: ' Share'});
|
||||
}
|
||||
|
||||
async goto(teamId = '', boardId = '', viewId = '', cardId = '') {
|
||||
let boardsUrl = '/boards';
|
||||
if (teamId) {
|
||||
boardsUrl += `/team/${teamId}`;
|
||||
if (boardId) {
|
||||
boardsUrl += `/${boardId}`;
|
||||
if (viewId) {
|
||||
boardsUrl += `/${viewId}`;
|
||||
if (cardId) {
|
||||
boardsUrl += `/${cardId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.page.goto(boardsUrl);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await this.globalHeader.toBeVisible(this.boards);
|
||||
await expect(this.shareButton).toBeVisible();
|
||||
await expect(this.topHead).toBeVisible();
|
||||
}
|
||||
|
||||
async shouldHaveUntitledBoard() {
|
||||
await this.editableTitle.isVisible();
|
||||
expect(await this.editableTitle.getAttribute('value')).toBe('');
|
||||
await expect(this.page.getByTitle('(Untitled Board)')).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {BoardsViewPage};
|
@ -3,36 +3,51 @@
|
||||
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
import {waitUntil} from '@e2e-support/test_action';
|
||||
import {components} from '@e2e-support/ui/components';
|
||||
import {duration, isSmallScreen} from '@e2e-support/util';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
|
||||
readonly page: Page;
|
||||
readonly postCreate;
|
||||
readonly findChannelsModal;
|
||||
|
||||
readonly globalHeader;
|
||||
readonly header;
|
||||
readonly headerMobile;
|
||||
readonly appBar;
|
||||
readonly centerView;
|
||||
readonly sidebarLeft;
|
||||
readonly sidebarRight;
|
||||
readonly postDotMenu;
|
||||
readonly appBar;
|
||||
|
||||
readonly findChannelsModal;
|
||||
readonly deletePostModal;
|
||||
|
||||
readonly postDotMenu;
|
||||
readonly postReminderMenu;
|
||||
|
||||
readonly emojiGifPickerPopup;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create'));
|
||||
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
||||
|
||||
// The main areas of the app
|
||||
this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
|
||||
this.header = new components.ChannelsHeader(page.locator('.channel-header'));
|
||||
this.headerMobile = new components.ChannelsHeaderMobile(page.locator('.navbar'));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'));
|
||||
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||
this.postDotMenu = new components.PostDotMenu(page.getByRole('menu', {name: 'Post extra options'}));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
|
||||
// Modals
|
||||
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
||||
this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal'));
|
||||
|
||||
// Menus
|
||||
this.postDotMenu = new components.PostDotMenu(page.getByRole('menu', {name: 'Post extra options'}));
|
||||
this.postReminderMenu = new components.PostReminderMenu(page.getByRole('menu', {name: 'Set a reminder for:'}));
|
||||
|
||||
// Popovers
|
||||
this.emojiGifPickerPopup = new components.EmojiGifPicker(page.locator('#emojiGifPicker'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.centerView.toBeVisible();
|
||||
}
|
||||
|
||||
async goto(teamName = '', channelName = '') {
|
||||
@ -46,75 +61,6 @@ export default class ChannelsPage {
|
||||
|
||||
await this.page.goto(channelsUrl);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
if (!isSmallScreen(this.page.viewportSize())) {
|
||||
await this.globalHeader.toBeVisible(this.channels);
|
||||
}
|
||||
await this.postCreate.toBeVisible();
|
||||
}
|
||||
|
||||
async postMessage(message: string) {
|
||||
await this.postCreate.input.waitFor();
|
||||
await this.postCreate.postMessage(message);
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
await this.postCreate.sendMessage();
|
||||
}
|
||||
|
||||
async getFirstPost() {
|
||||
await this.page.getByTestId('postView').first().waitFor();
|
||||
const post = await this.page.getByTestId('postView').first();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getLastPost() {
|
||||
await this.page.getByTestId('postView').last().waitFor();
|
||||
const post = await this.page.getByTestId('postView').last();
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getNthPost(index: number) {
|
||||
await this.page.getByTestId('postView').nth(index).waitFor();
|
||||
const post = await this.page.getByTestId('postView').nth(index);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getPostById(id: string) {
|
||||
await this.page.locator(`[id="post_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="post_${id}"]`);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async getRHSPostById(id: string) {
|
||||
await this.page.locator(`[id="rhsPost_${id}"]`).waitFor();
|
||||
const post = await this.page.locator(`[id="rhsPost_${id}"]`);
|
||||
return new components.ChannelsPost(post);
|
||||
}
|
||||
|
||||
async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getLastPost();
|
||||
const content = await post.container.textContent();
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
|
||||
async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.getPostById(id);
|
||||
const content = await post.container.textContent();
|
||||
|
||||
return content?.includes(text);
|
||||
},
|
||||
{timeout}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPage};
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BoardsCreatePage} from './boards_create';
|
||||
import {BoardsViewPage} from './boards_view';
|
||||
import {ChannelsPage} from './channels';
|
||||
import {LandingLoginPage} from './landing_login';
|
||||
import {LoginPage} from './login';
|
||||
@ -10,8 +8,6 @@ import {ResetPasswordPage} from './reset_password';
|
||||
import {SignupPage} from './signup';
|
||||
|
||||
const pages = {
|
||||
BoardsCreatePage,
|
||||
BoardsViewPage,
|
||||
ChannelsPage,
|
||||
LandingLoginPage,
|
||||
LoginPage,
|
||||
@ -19,4 +15,4 @@ const pages = {
|
||||
SignupPage,
|
||||
};
|
||||
|
||||
export {pages, BoardsCreatePage, BoardsViewPage, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};
|
||||
export {pages, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user