diff --git a/e2e-tests/.ci/.e2erc b/e2e-tests/.ci/.e2erc index e08232620b..a6c8efe301 100644 --- a/e2e-tests/.ci/.e2erc +++ b/e2e-tests/.ci/.e2erc @@ -1,7 +1,7 @@ +# shellcheck disable=SC2148,SC2155 + # Utility variables # NB: these assume you `source` them from the directory this file is in -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 }}') @@ -34,17 +34,18 @@ mme2e_get_current_shopt_arg () { esac } mme2e_load_env_file () { - # This loads the ./env file. Variables are automatically exported - [ -f ./env ] || return 0 + # This loads the ./.env file. Variables are automatically exported + [ -f ./.env ] || return 0 MME2E_PREVIOUS_ALLEXPORT=$(mme2e_get_current_shopt_arg allexport) set -o allexport - mme2e_log "Loading env file" - . ./env + mme2e_log "Loading .env file" + . ./.env + # shellcheck disable=SC2086 set ${MME2E_PREVIOUS_ALLEXPORT} } mme2e_generate_envfile_from_var_names () { # Read var names from stdin, one per line - while read VARIABLE; do + while read -r VARIABLE; do [ -z "${!VARIABLE:-}" ] || echo "${VARIABLE}=${!VARIABLE}"; done } @@ -59,8 +60,9 @@ mme2e_wait_command_success () { RETRIES_LEFT=$((RETRIES_LEFT - 1)) [ "$RETRIES_LEFT" -le "0" ] && break mme2e_log "${RETRY_MESSAGE} ($RETRIES_LEFT retries left, sleeping $RETRIES_INTERVAL seconds)" - sleep $RETRIES_INTERVAL + sleep "$RETRIES_INTERVAL" done + # shellcheck disable=SC2086 set ${MME2E_PREVIOUS_PIPEFAIL} if [ "$RETRIES_LEFT" = "0" ]; then exit 1 @@ -92,11 +94,6 @@ mme2e_legacy_setup () { 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 -# NB: this won't work in the script -alias docker-compose-mmserver="${MME2E_DC_SERVER}" -alias docker-compose-mmdashboard="${MME2E_DC_DASHBOARD}" - # Call prerequisite utility functions mme2e_load_env_file mme2e_legacy_setup # Temporary diff --git a/e2e-tests/.ci/.e2erc_setup b/e2e-tests/.ci/.e2erc_setup new file mode 100644 index 0000000000..e032f570a7 --- /dev/null +++ b/e2e-tests/.ci/.e2erc_setup @@ -0,0 +1,22 @@ +# shellcheck disable=SC2148 + +# Set up base docker compose file and export +DC_FILE="docker-compose.server.override.yml" +if [ ! -f docker-compose.server.override.yml ]; then + echo "No './.ci/docker-compose.server.override.yml' found. You may need to run 'make generate-docker-compose'" + exit 1 +fi + +if [ "$TEST" = "cypress" ]; then + MME2E_DC_SERVER="docker compose -p mmserver -f ./$DC_FILE -f ./docker-compose.cypress.yml" +elif [ "$TEST" = "playwright" ]; then + MME2E_DC_SERVER="docker compose -p mmserver -f ./$DC_FILE -f ./docker-compose.playwright.yml" +else + MME2E_DC_SERVER="docker compose -p mmserver -f ./$DC_FILE" +fi + +export MME2E_DC_SERVER=$MME2E_DC_SERVER +alias docker-compose-mmserver='${MME2E_DC_SERVER}' + +export MME2E_DC_DASHBOARD="docker compose -p mmdashboard -f ./dashboard/docker/docker-compose.yml -f ./dashboard.override.yml" +alias docker-compose-mmdashboard='${MME2E_DC_DASHBOARD}' diff --git a/e2e-tests/.ci/.env.server.cloud b/e2e-tests/.ci/.env.server.cloud new file mode 100644 index 0000000000..64c0275276 --- /dev/null +++ b/e2e-tests/.ci/.env.server.cloud @@ -0,0 +1 @@ +# NB: this file is just a placeholder required by docker-compose. It's supposed to be modified by the CI pipeline and should remain empty in git diff --git a/e2e-tests/.ci/.gitignore b/e2e-tests/.ci/.gitignore index cc1baff8fd..855154b663 100644 --- a/e2e-tests/.ci/.gitignore +++ b/e2e-tests/.ci/.gitignore @@ -1,5 +1,6 @@ dashboard -env +.env !.env.cypress !.env.server +!.env.server.cloud !.env.dashboard diff --git a/e2e-tests/.ci/.shellcheckrc b/e2e-tests/.ci/.shellcheckrc new file mode 100644 index 0000000000..53cca9fc7c --- /dev/null +++ b/e2e-tests/.ci/.shellcheckrc @@ -0,0 +1,4 @@ +# SC1091: Not following: +# https://www.shellcheck.net/wiki/SC1091 +disable=SC1091 +# Used to ignore for sourcing ".e2erc*" diff --git a/e2e-tests/.ci/dashboard.entrypoint.sh b/e2e-tests/.ci/dashboard.entrypoint.sh index 49ccfe40a9..712b07d0a8 100755 --- a/e2e-tests/.ci/dashboard.entrypoint.sh +++ b/e2e-tests/.ci/dashboard.entrypoint.sh @@ -7,14 +7,14 @@ npm install MIGRATION_ATTEMPTS_LEFT=10 MIGRATION_ATTEMPTS_INTERVAL=10 until npm run migrate:latest; do - MIGRATION_ATTEMPTS_LEFT=$((MIGRATION_ATTEMPTS_LEFT - 1)) - [ "$MIGRATION_ATTEMPTS_LEFT" -gt 0 ] || break - echo "Migration script failed, sleeping $MIGRATION_ATTEMPTS_INTERVAL" - sleep $MIGRATION_ATTEMPTS_INTERVAL + MIGRATION_ATTEMPTS_LEFT=$((MIGRATION_ATTEMPTS_LEFT - 1)) + [ "$MIGRATION_ATTEMPTS_LEFT" -gt 0 ] || break + echo "Migration script failed, sleeping $MIGRATION_ATTEMPTS_INTERVAL" + sleep $MIGRATION_ATTEMPTS_INTERVAL done [ "$MIGRATION_ATTEMPTS_LEFT" -gt 0 ] || { - echo "Migration script failed, exhausted attempts. Giving up." - exit 1 + echo "Migration script failed, exhausted attempts. Giving up." + exit 1 } # Launch the dashboard diff --git a/e2e-tests/.ci/dashboard.start.sh b/e2e-tests/.ci/dashboard.start.sh index d21092dc99..7a2429973e 100755 --- a/e2e-tests/.ci/dashboard.start.sh +++ b/e2e-tests/.ci/dashboard.start.sh @@ -1,7 +1,8 @@ #!/bin/bash set -e -u -o pipefail -cd $(dirname $0) +cd "$(dirname "$0")" . .e2erc +. .e2erc_setup MME2E_DASHBOARD_REF_DEFAULT="origin/main" MME2E_DASHBOARD_REF=${MME2E_DASHBOARD_REF:-$MME2E_DASHBOARD_REF_DEFAULT} @@ -9,26 +10,34 @@ MME2E_DASHBOARD_REF=${MME2E_DASHBOARD_REF:-$MME2E_DASHBOARD_REF_DEFAULT} mme2e_log "Cloning the automation-dashboard project" if [ ! -d dashboard ]; then git clone https://github.com/saturninoabril/automation-dashboard.git dashboard - . .e2erc # Must reinitialize some variables that depend on the dashboard repo being checked out + # Must reinitialize some variables that depend on the dashboard repo being checked out + . .e2erc + . .e2erc_setup fi git -C dashboard fetch -git -C dashboard checkout $MME2E_DASHBOARD_REF +git -C dashboard checkout "$MME2E_DASHBOARD_REF" mme2e_log "Starting the dashboard" ${MME2E_DC_DASHBOARD} up -d db dashboard mme2e_log "Generating the dashboard's local URL" 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') +darwin) + # shellcheck disable=SC2046 + AUTOMATION_DASHBOARD_IP=$(ifconfig $(route get 1.1.1.1 | grep interface | awk '{print $2}') | grep -w inet | awk '{print $2}') + ;; +*) + # shellcheck disable=SC2046 + 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 +# shellcheck disable=SC2034 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 dashboard npm i +${MME2E_DC_DASHBOARD} exec -T dashboard bash -c "rm -rf node_modules && npm install --cache /tmp/empty-cache" +# shellcheck disable=SC2034 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" diff --git a/e2e-tests/.ci/dashboard.stop.sh b/e2e-tests/.ci/dashboard.stop.sh index 97bab72f4c..61bcbea0e6 100755 --- a/e2e-tests/.ci/dashboard.stop.sh +++ b/e2e-tests/.ci/dashboard.stop.sh @@ -1,7 +1,8 @@ #!/bin/bash set -e -u -o pipefail -cd $(dirname $0) +cd "$(dirname "$0")" . .e2erc +. .e2erc_setup if [ -d dashboard ]; then mme2e_log "Stopping the dashboard containers" diff --git a/e2e-tests/.ci/docker-compose.cypress.yml b/e2e-tests/.ci/docker-compose.cypress.yml new file mode 100644 index 0000000000..11cb4472b7 --- /dev/null +++ b/e2e-tests/.ci/docker-compose.cypress.yml @@ -0,0 +1,61 @@ +# Image hashes in this file are for amd64 systems + +version: "2.4" +services: + cypress: + image: "cypress/browsers:node-18.16.1-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1" + ### Temporarily 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" + # Cypress configuration + HEADLESS: "true" + CYPRESS_baseUrl: "http://server:8065" + CYPRESS_dbConnection: "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10" + CYPRESS_smtpUrl: "http://inbucket:9001" + CYPRESS_webhookBaseUrl: "http://webhook-interactions:3000" + CYPRESS_chromeWebSecurity: "false" + CYPRESS_firstTest: "true" + CYPRESS_resetBeforeTest: "true" + CYPRESS_allowedUntrustedInternalConnections: "localhost webhook-interactions" + TM4J_ENABLE: "false" + # disable shared memory X11 affecting Cypress v4 and Chrome + # https://github.com/cypress-io/cypress-docker-images/issues/270 + QT_X11_NO_MITSHM: "1" + _X11_NO_MITSHM: "1" + _MITSHM: "0" + # avoid too many progress messages + # https://github.com/cypress-io/cypress/issues/1243 + CI: "1" + # Ensure we're independent from the global node environment + 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: /cypress + volumes: + - "../../e2e-tests/cypress/:/cypress" + + webhook-interactions: + image: mattermostdevelopment/mirrored-node:${NODE_VERSION_REQUIRED} + command: sh -c "npm install --legacy-peer-deps && 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"] + interval: 10s + timeout: 15s + retries: 12 + working_dir: /cypress + volumes: + - "../../e2e-tests/cypress/:/cypress:ro" + networks: + default: + aliases: + - webhook-interactions diff --git a/e2e-tests/.ci/docker-compose.playwright.yml b/e2e-tests/.ci/docker-compose.playwright.yml new file mode 100644 index 0000000000..78001da5f1 --- /dev/null +++ b/e2e-tests/.ci/docker-compose.playwright.yml @@ -0,0 +1,32 @@ +# Image hashes in this file are for amd64 systems + +version: "2.4" +services: + playwright: + image: mcr.microsoft.com/playwright:v1.38.1-jammy + entrypoint: ["/bin/bash", "-c"] + command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"] + env_file: + - "./.env.playwright" + environment: + CI: "true" + NODE_OPTIONS: --no-experimental-fetch + PW_BASE_URL: http://server:8065 + PW_ADMIN_USERNAME: sysadmin + PW_ADMIN_PASSWORD: Sys@dmin-sample1 + PW_ADMIN_EMAIL: sysadmin@sample.mattermost.com + PW_ENSURE_PLUGINS_INSTALLED: "" + PW_HA_CLUSTER_ENABLED: "false" + PW_RESET_BEFORE_TEST: "false" + PW_HEADLESS: "true" + PW_SLOWMO: 0 + PW_WORKERS: 2 + PW_SNAPSHOT_ENABLE: "false" + PW_PERCY_ENABLE: "false" + ulimits: + nofile: + soft: 8096 + hard: 1048576 + working_dir: /mattermost + volumes: + - "../../:/mattermost" diff --git a/e2e-tests/.ci/docker_compose_generator.sh b/e2e-tests/.ci/docker_compose_generator.sh new file mode 100755 index 0000000000..865a2bb15c --- /dev/null +++ b/e2e-tests/.ci/docker_compose_generator.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# SC2034: appears unused. +# https://www.shellcheck.net/wiki/SC2034 +# shellcheck disable=SC2034 +# Note: Variables are dynamically used depending on usage input (ENABLED_DOCKER_SERVICES) + +set -e -u -o pipefail +cd "$(dirname "$0")" +. .e2erc + +# File to be used for overriding docker compose +CONFIG_FILE="docker-compose.server.override.yml" + +# Check if ENABLED_DOCKER_SERVICES is set or not +if [ -n "${ENABLED_DOCKER_SERVICES-}" ] && [ -n "$ENABLED_DOCKER_SERVICES" ]; then + mme2e_log "ENABLED_DOCKER_SERVICES: $ENABLED_DOCKER_SERVICES" +else + # If not set, then remove the override file if it exists and exit + mme2e_log "ENABLED_DOCKER_SERVICES is empty or unset." + if [ -f $CONFIG_FILE ]; then + rm -f $CONFIG_FILE + mme2e_log "$CONFIG_FILE removed." + else + mme2e_log "No override to docker compose." + fi + exit 0 +fi + +# Define list of valid services +validServices=("postgres:5432" "minio:9000" "inbucket:9001" "openldap:389" "elasticsearch:9200" "keycloak:8080") + +# Read the enabled docker services and split them into an array +enabled_docker_services="$ENABLED_DOCKER_SERVICES" +read -ra docker_services <<<"$enabled_docker_services" + +# Initialize variables for required services +postgres_found=false +inbucket_found=false + +# Get service and post of valid services +services=() +for service in "${docker_services[@]}"; do + port="" + for svcPort in "${validServices[@]}"; do + svc=${svcPort%%:*} + if [ "$service" == "$svc" ]; then + port=${svcPort#*:} + + # Find the required services + if [ "$service" == "$svc" ]; then + if [ "$service" == "postgres" ]; then + postgres_found=true + elif [ "$service" == "inbucket" ]; then + inbucket_found=true + fi + fi + break + fi + done + + if [ -z "$port" ]; then + mme2e_log "Unknown service $svc" + exit 1 + fi + + services+=("$service:$port") +done + +# Check if the required services such as postgres and inbucket are found +# Do not continue if any of the required services are not found. +if [ "$postgres_found" != true ] || [ "$inbucket_found" != true ]; then + mme2e_log "When overriding docker compose via ENABLED_DOCKER_SERVICES, postgres and inbucket are both required." + exit 1 +fi + +# Define each service values +postgres=' + postgres: + image: mattermostdevelopment/mirrored-postgres:12 + restart: always + environment: + POSTGRES_USER: mmuser + POSTGRES_PASSWORD: mostest + POSTGRES_DB: mattermost_test + command: postgres -c "config_file=/etc/postgresql/postgresql.conf" + volumes: + - ../../server/build/docker/postgres.conf:/etc/postgresql/postgresql.conf + healthcheck: + test: ["CMD", "pg_isready", "-h", "localhost"] + interval: 10s + timeout: 15s + retries: 12 + networks: + default: + aliases: + - postgres' + +inbucket=' + inbucket: + restart: "no" + container_name: mattermost-inbucket + ports: + - "9001:9001" + - "10025:10025" + - "10110:10110" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: inbucket' + +minio=' + minio: + restart: "no" + container_name: mattermost-minio + ports: + - "9000:9000" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: minio' + +openldap=' + openldap: + restart: "no" + container_name: mattermost-openldap + ports: + - "389:389" + - "636:636" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: openldap' + +elasticsearch=' + elasticsearch: + restart: "no" + container_name: mattermost-elasticsearch + ports: + - "9200:9200" + - "9300:9300" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: elasticsearch' + +elasticsearch_arm64=' + elasticsearch: + image: mattermostdevelopment/mattermost-elasticsearch:7.17.10 + platform: linux/arm64/v8 + restart: "no" + container_name: mattermost-elasticsearch + ports: + - "9200:9200" + - "9300:9300" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: elasticsearch' + +keycloak=' + keycloak: + restart: "no" + container_name: mattermost-keycloak + ports: + - "8484:8080" + extends: + file: ../../server/build/gitlab-dc.common.yml + service: keycloak' + +# Function to get the service value based on the key +get_service_val_by_key() { + local key="$1" + + if [ "$MME2E_ARCHTYPE" = "arm64" ] && [ "$key" = "elasticsearch" ]; then + # Use arm64 version of elasticsearch + key="elasticsearch_arm64" + fi + + # Use variable indirection to retrieve the value + local service_val="${!key}" + + echo "$service_val" +} + +# Generate the docker compose override file +cat <"$CONFIG_FILE" +# Image hashes in this file are for amd64 systems +# NB: May include paths relative to the "server/build" directory, which contains the original compose file that this yaml is overriding + +version: "2.4" +services: + server: + image: \${SERVER_IMAGE} + restart: always + env_file: + - "./.env.server" + - "./.env.server.cloud" + environment: + MM_LICENSE: \${MM_LICENSE} + MM_SERVICESETTINGS_SITEURL: http://server:8065 + MM_SERVICESETTINGS_ENABLELOCALMODE: "true" + MM_PLUGINSETTINGS_ENABLED: "true" + MM_PLUGINSETTINGS_ENABLEUPLOADS: "true" + MM_PLUGINSETTINGS_AUTOMATICPREPACKAGEDPLUGINS: "true" + MM_TEAMSETTINGS_ENABLEOPENSERVER: "true" + MM_SQLSETTINGS_DATASOURCE: "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10&binary_parameters=yes" + MM_SQLSETTINGS_DRIVERNAME: "postgres" + MM_EMAILSETTINGS_SMTPSERVER: "inbucket" + MM_CLUSTERSETTINGS_READONLYCONFIG: "false" + MM_SERVICESETTINGS_ENABLEONBOARDINGFLOW: "false" + MM_FEATUREFLAGS_ONBOARDINGTOURTIPS: "false" + MM_SERVICEENVIRONMENT: "test" + ports: + - "8065:8065" + depends_on: +$(for service in "${docker_services[@]}"; do + echo " $service:" + echo " condition: service_healthy" +done) +$(for service in "${docker_services[@]}"; do + service_val=$(get_service_val_by_key "$service") + + if [ -n "$service_val" ]; then + echo "$service_val" + fi +done) + + start_dependencies: + image: mattermost/mattermost-wait-for-dep:latest + depends_on: +$(for service in "${docker_services[@]}"; do + echo " - $service" +done) + command: $( + IFS=' ' + echo "${services[*]}" +) + networks: + default: + +networks: + default: + name: \${COMPOSE_PROJECT_NAME} + external: true +EOL + +mme2e_log "Configuration generated in $CONFIG_FILE" diff --git a/e2e-tests/.ci/server.cloud_customer_create.sh b/e2e-tests/.ci/server.cloud_customer_create.sh new file mode 100755 index 0000000000..aeb69564ad --- /dev/null +++ b/e2e-tests/.ci/server.cloud_customer_create.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e -u -o pipefail +cd "$(dirname "$0")" +. .e2erc + +if [ "$SERVER" != "cloud" ]; then + mme2e_log "Not applicable to SERVER='$SERVER'. For cloud only." + exit 0 +fi + +# Check if CWS_URL is set or not +if [ -n "${CWS_URL-}" ] && [ -n "$CWS_URL" ]; then + mme2e_log "CWS_URL is set." +else + mme2e_log "Environment variable CWS_URL is empty or unset. It must be set to create test cloud customer." + exit 1 +fi + +response=$(curl -X POST "${CWS_URL}/api/v1/internal/tests/create-customer?sku=cloud-enterprise&is_paid=true") +MM_CUSTOMER_ID=$(echo "$response" | jq -r .customer_id) +MM_CLOUD_API_KEY=$(echo "$response" | jq -r .api_key) +MM_CLOUD_INSTALLATION_ID=$(echo "$response" | jq -r .installation_id) + +export MM_CUSTOMER_ID=$MM_CUSTOMER_ID +export MM_CLOUD_API_KEY=$MM_CLOUD_API_KEY +export MM_CLOUD_INSTALLATION_ID=$MM_CLOUD_INSTALLATION_ID +export MM_CLOUDSETTINGS_CWSURL=$CWS_URL +export MM_CLOUDSETTINGS_CWSAPIURL=$CWS_URL + +mme2e_generate_envfile_from_var_names >.env.server.cloud </dev/null -${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.9.0/com.mattermost.demo-plugin-0.9.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.9.0.tar.gz >/dev/null -${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.8.0/com.mattermost.demo-plugin-0.8.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.8.0.tar.gz >/dev/null -${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.8.0/com.mattermost.demo-plugin-0.8.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.8.0.tar.gz >/dev/null -${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress tee tests/fixtures/keycloak.crt >/dev/null <../../server/build/docker/keycloak/keycloak.crt +${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-gitlab/releases/download/v1.3.0/com.github.manland.mattermost-plugin-gitlab-1.3.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress tee tests/fixtures/com.github.manland.mattermost-plugin-gitlab-1.3.0.tar.gz >/dev/null +${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.9.0/com.mattermost.demo-plugin-0.9.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.9.0.tar.gz >/dev/null +${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.8.0/com.mattermost.demo-plugin-0.8.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.8.0.tar.gz >/dev/null +${MME2E_DC_SERVER} exec -T -- server curl -L --silent https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.8.0/com.mattermost.demo-plugin-0.8.0.tar.gz | ${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress tee tests/fixtures/com.mattermost.demo-plugin-0.8.0.tar.gz >/dev/null +${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress tee tests/fixtures/keycloak.crt >/dev/null <../../server/build/docker/keycloak/keycloak.crt + +if [[ $ENABLED_DOCKER_SERVICES == *"openldap"* ]]; then + ${MME2E_DC_SERVER} exec -T -- openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest' <../../server/tests/test-data.ldif +fi + +if [[ $ENABLED_DOCKER_SERVICES == *"minio"* ]]; then + ${MME2E_DC_SERVER} exec -T -- minio sh -c 'mkdir -p /data/mattermost-test' +fi -# Inject test data, prepare for E2E tests -mme2e_log "Prepare Server: injecting E2E test data" -${MME2E_DC_SERVER} exec -T -- server mmctl config set TeamSettings.MaxUsersPerTeam 100 --local -${MME2E_DC_SERVER} exec -T -- server mmctl sampledata -u 60 --local -${MME2E_DC_SERVER} exec -T -- openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest' <../../server/tests/test-data.ldif -${MME2E_DC_SERVER} exec -T -- minio sh -c 'mkdir -p /data/mattermost-test' mme2e_log "Mattermost is running and ready for E2E testing" -mme2e_log "You can use the test data credentials for logging in (username=sysadmin password=Sys@dmin-sample1)" diff --git a/e2e-tests/.ci/server.run_cypress.sh b/e2e-tests/.ci/server.run_cypress.sh index d5b48089a8..3c5914dd85 100755 --- a/e2e-tests/.ci/server.run_cypress.sh +++ b/e2e-tests/.ci/server.run_cypress.sh @@ -1,7 +1,8 @@ #!/bin/bash set -e -u -o pipefail -cd $(dirname $0) +cd "$(dirname "$0")" . .e2erc +. .e2erc_setup # Set required variables TEST_FILTER_DEFAULT='--stage=@prod --group=@smoke' @@ -9,8 +10,8 @@ TEST_FILTER=${TEST_FILTER:-$TEST_FILTER_DEFAULT} # Print run information mme2e_log "Printing Cypress container informations" -${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress node -p 'module.paths' -${MME2E_DC_SERVER} exec -T -u $MME2E_UID -- cypress bash <<"EOF" +${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress node -p 'module.paths' +${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- cypress bash <<"EOF" cat <../playwright/logs/mattermost.log 2>&1 diff --git a/e2e-tests/.ci/server.start.sh b/e2e-tests/.ci/server.start.sh index 67836f3e18..57619d56d3 100755 --- a/e2e-tests/.ci/server.start.sh +++ b/e2e-tests/.ci/server.start.sh @@ -1,31 +1,48 @@ #!/bin/bash set -e -u -o pipefail -cd $(dirname $0) +cd "$(dirname "$0")" . .e2erc +. .e2erc_setup -export BRANCH_DEFAULT=$(git branch --show-current) +if [ "$TEST" != "cypress" ] && [ "$TEST" != "playwright" ] && [ "$TEST" != "server" ]; then + mme2e_log "Invalid TEST='$TEST', expected: 'cypress', 'playwright' or 'server'" + exit 1 +fi + +BRANCH_DEFAULT=$(git branch --show-current) export BRANCH=${BRANCH:-$BRANCH_DEFAULT} -export BUILD_ID_DEFAULT=$(date +%s) +BUILD_ID_DEFAULT=$(date +%s) export BUILD_ID=${BUILD_ID:-$BUILD_ID_DEFAULT} export CI_BASE_URL="${CI_BASE_URL:-localhost}" +export SITE_URL="${SITE_URL:-http://server:8065}" # Cleanup old containers, if any mme2e_log "Stopping leftover E2E containers, if any are running" -${MME2E_DC_SERVER} down -v +${MME2E_DC_SERVER} down -v --remove-orphans # Generate .env.server mme2e_log "Generating .env.server" +if [ "$SERVER" = "cloud" ]; then + export "MM_NOTIFY_ADMIN_COOL_OFF_DAYS=0.00000001" + export 'MM_FEATUREFLAGS_ANNUALSUBSCRIPTION="true"' +fi mme2e_generate_envfile_from_var_names >.env.server <> .env.server + echo "$env" >>.env.server done -# Generate .env.cypress -mme2e_log "Generating .env.cypress" -mme2e_generate_envfile_from_var_names >.env.cypress <.env.cypress <>.env.cypress + echo "CYPRESS_runLDAPSync=true" >>.env.cypress + fi + + if [[ $ENABLED_DOCKER_SERVICES == *"minio"* ]]; then + echo "CYPRESS_minioS3Endpoint=minio:9000" >>.env.cypress + fi + + if [[ $ENABLED_DOCKER_SERVICES == *"keycloak"* ]]; then + echo "CYPRESS_keycloakBaseUrl=http://keycloak:8484" >>.env.cypress + fi + + if [[ $ENABLED_DOCKER_SERVICES == *"elasticsearch"* ]]; then + echo "CYPRESS_elasticsearchConnectionURL=http://elasticsearch:9200" >>.env.cypress + fi + + if [ "$SERVER" = "cloud" ]; then + echo "CYPRESS_serverEdition=Cloud" >>.env.cypress + else + echo "CYPRESS_serverEdition=E20" >>.env.cypress + fi +elif [ "$TEST" = "playwright" ]; then + # Generate .env.playwright + mme2e_log "Playwright: Generating .env.playwright" + mme2e_generate_envfile_from_var_names >.env.playwright </opt/server-config/config.json -echo "### Generated server config:" -cat /opt/server-config/config.json -EOF +mme2e_wait_image "$SERVER_IMAGE" 30 60 # Launch mattermost-server, and wait for it to be healthy mme2e_log "Starting E2E containers" +${MME2E_DC_SERVER} create ${MME2E_DC_SERVER} up -d if ! mme2e_wait_service_healthy server 60 10; then mme2e_log "Mattermost container not healthy, retry attempts exhausted. Giving up." >&2 diff --git a/e2e-tests/.ci/server.stop.sh b/e2e-tests/.ci/server.stop.sh index cd7d7f83e1..2e1e559848 100755 --- a/e2e-tests/.ci/server.stop.sh +++ b/e2e-tests/.ci/server.stop.sh @@ -1,7 +1,8 @@ #!/bin/bash set -e -u -o pipefail -cd $(dirname $0) +cd "$(dirname "$0")" . .e2erc +. .e2erc_setup mme2e_log "Stopping E2E containers" -${MME2E_DC_SERVER} down -v +${MME2E_DC_SERVER} down -v --remove-orphans diff --git a/e2e-tests/.gitignore b/e2e-tests/.gitignore index ff56c51f94..c3faaf0050 100644 --- a/e2e-tests/.gitignore +++ b/e2e-tests/.gitignore @@ -9,3 +9,6 @@ # node *.lock + +# dev/ci +.ci/docker-compose.server.override.yml diff --git a/e2e-tests/Makefile b/e2e-tests/Makefile index 0e26fba3f5..915f92160e 100644 --- a/e2e-tests/Makefile +++ b/e2e-tests/Makefile @@ -1,17 +1,39 @@ +# Load environment variables from .env file +-include .ci/.env +export + SHELL := /bin/bash +TEST ?= cypress +SERVER ?= self-hosted +ENABLED_DOCKER_SERVICES ?= postgres inbucket + +define log_message + @echo "[$(shell date '+%Y-%m-%dT%H:%M:%S%Z')]$1" +endef .PHONY: all run stop all: run -run: start-server prepare-server run-cypress +run: info generate-docker-compose create-cloud-customer start-server-test prepare-server run-test delete-cloud-customer stop: stop-server stop-dashboard clean-env-placeholders -.PHONY: start-server prepare-server run-cypress stop-server restart-server -start-server: +.PHONY: info generate-docker-compose start-server-test run-test start-server prepare-server stop-server restart-server +generate-docker-compose: + bash ./.ci/docker_compose_generator.sh +start-server-test: bash ./.ci/server.start.sh prepare-server: bash ./.ci/server.prepare.sh -run-cypress: - bash ./.ci/server.run_cypress.sh +run-test: + @if [ "$(TEST)" = "cypress" ]; then bash ./.ci/server.run_cypress.sh; \ + elif [ "$(TEST)" = "playwright" ]; then bash ./.ci/server.run_playwright.sh; \ + else $(call log_message, "TEST: $(TEST)"); fi +info: + $(call log_message, "TEST: $(TEST)") + $(call log_message, "SERVER: $(SERVER)") + $(call log_message, "ENABLED_DOCKER_SERVICES: $(ENABLED_DOCKER_SERVICES)") + +start-server: generate-docker-compose + TEST=server bash ./.ci/server.start.sh stop-server: bash ./.ci/server.stop.sh restart-server: stop-server start-server @@ -26,5 +48,29 @@ stop-dashboard: print-env-placeholders: find .ci -maxdepth 1 -name '.env.*' | xargs --verbose -l cat clean-env-placeholders: - git reset .ci/.env.{cypress,server,dashboard} - git checkout .ci/.env.{cypress,server,dashboard} + git reset .ci/.env.{cypress,server,server.cloud,dashboard} + git checkout .ci/.env.{cypress,server,server.cloud,dashboard} + +.PHONY: create-cloud-customer delete-cloud-customer +create-cloud-customer: + @if [ "$(SERVER)" = "cloud" ]; then bash ./.ci/server.cloud_customer_create.sh; fi +delete-cloud-customer: + @if [ "$(SERVER)" = "cloud" ]; then bash ./.ci/server.cloud_customer_delete.sh; fi + +.PHONY: prettier fmt-ci +# Install with https://webinstall.dev/shellcheck/ +HAVE_SHELLCHECK ?= $(shell command -v shellcheck >/dev/null 2>&1 && echo yes) + +# Install with https://webinstall.dev/shfmt/ +HAVE_SHFMT ?= $(shell command -v shfmt >/dev/null 2>&1 && echo yes) + +prettier: + # formats yaml files + npx prettier ./.ci "!./.ci/dashboard" --write --cache +fmt-ci: prettier +ifeq ($(HAVE_SHFMT),yes) + shfmt -w -s -i 2 ./.ci/*.sh +endif +ifeq ($(HAVE_SHELLCHECK),yes) + shellcheck ./.ci/*.sh ./.ci/.e2erc* +endif diff --git a/e2e-tests/README.md b/e2e-tests/README.md index 96119b3576..58405c3e15 100644 --- a/e2e-tests/README.md +++ b/e2e-tests/README.md @@ -17,15 +17,34 @@ Instructions, tl;dr: create a local branch with your E2E test changes, then open Instructions, detailed: 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) + * `TEST` either `cypress` (default) or `playwright`. + * `SERVER` either `self-hosted` (default) or `cloud`. + * The `ENABLED_DOCKER_SERVICES` is by default set to `postgres` and `inbucket` for smoke tests purpose, and for lightweight and faster start up time. Depending on the test requirement being worked on, you may want to override as needed, as such: + * Cypress full tests require full services with `postgres`, `inbucket`, `minio`, `openldap`, `elasticsearch` and `keycloak`. + * Cypress smoke tests require `postgres` and `inbucket` only. + * Playwright full tests require `postgres` and `inbucket` only. * 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. + * Set `CWS_URL` when spinning up a cloud-like test server that communicates with a test instance of customer web server. 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 `.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 + * 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. + * The dashboard is used for orchestrating specs with parallel test run and is typically used in CI. + * Only Cypress is currently using the dashboard; Playwright is not. +3. Run the following commands to run with cypress: + * `TEST=cypress make`: start and prepare the server, then run the cypress tests against self-hosted test server. + * `TEST=cypress SERVER=cloud make`: create test customer for cloud, start and prepare the server, run the cypress tests against cloud-like test server, then delete the test customer. When anticipating a licensed test server, make sure the loaded license via `MM_LICENSE` is for cloud. + * When anticipating a licensed test server, make sure the loaded license via `MM_LICENSE` does match either for self-hosted or cloud. + * `CWS_URL` is required to be set for cloud test. + * You can track the progress of the run in the `http://localhost:4000/cycles` dashboard, if you launched it locally. +4. Run the following commands to run with playwright: + * `TEST=playwright make`: start and prepare the server, then run the playwright tests against self-hosted test server. + * `TEST=playwright SERVER=cloud `: generate test customer for cloud, start and prepare the server, run the playwright tests against cloud-like test server, then delete the initially generated test customer. + * When anticipating a licensed test server, make sure the loaded license via `MM_LICENSE` does match either for self-hosted or cloud. + * `CWS_URL` is required to be set for cloud test. +5. `TEST=server make`: starts local server. +6. `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. @@ -34,5 +53,8 @@ Notes: - 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) - 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 their value is fixed (e.g. a static server configuration), these may be simply added to the `docker_compose_generator.sh` 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. + +##### For code changes: +* `make fmt-ci` to format and check yaml files and shell scripts. diff --git a/e2e-tests/playwright/README.md b/e2e-tests/playwright/README.md index 867af490e1..59d5ff4ba1 100644 --- a/e2e-tests/playwright/README.md +++ b/e2e-tests/playwright/README.md @@ -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.38.0-jammy /bin/bash +docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.38.1-jammy /bin/bash ``` #### 2. Inside the docker container diff --git a/e2e-tests/playwright/package-lock.json b/e2e-tests/playwright/package-lock.json index 10d7ea1c10..6ba387440c 100644 --- a/e2e-tests/playwright/package-lock.json +++ b/e2e-tests/playwright/package-lock.json @@ -10,7 +10,7 @@ "@axe-core/playwright": "4.7.3", "@percy/cli": "1.27.1", "@percy/playwright": "1.0.4", - "@playwright/test": "1.38.0", + "@playwright/test": "1.38.1", "async-wait-until": "2.0.12", "axe-core": "4.8.1", "chalk": "4.1.2", @@ -488,11 +488,11 @@ } }, "node_modules/@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", "dependencies": { - "playwright": "1.38.0" + "playwright": "1.38.1" }, "bin": { "playwright": "cli.js" @@ -2015,11 +2015,11 @@ } }, "node_modules/playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", "dependencies": { - "playwright-core": "1.38.0" + "playwright-core": "1.38.1" }, "bin": { "playwright": "cli.js" @@ -2032,9 +2032,9 @@ } }, "node_modules/playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", "bin": { "playwright-core": "cli.js" }, @@ -2781,11 +2781,11 @@ } }, "@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", "requires": { - "playwright": "1.38.0" + "playwright": "1.38.1" } }, "@types/json-schema": { @@ -3846,18 +3846,18 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", "requires": { "fsevents": "2.3.2", - "playwright-core": "1.38.0" + "playwright-core": "1.38.1" } }, "playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==" + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==" }, "prelude-ls": { "version": "1.2.1", diff --git a/e2e-tests/playwright/package.json b/e2e-tests/playwright/package.json index 5f9aac389b..eaf8210eed 100644 --- a/e2e-tests/playwright/package.json +++ b/e2e-tests/playwright/package.json @@ -1,15 +1,16 @@ { "scripts": { - "test": "cross-env PW_SNAPSHOT_ENABLE=true playwright test", - "percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=iphone --project=ipad", + "test": "cross-env NODE_OPTIONS='--no-experimental-fetch' PW_SNAPSHOT_ENABLE=true playwright test", + "test:update-snapshots": "cross-env NODE_OPTIONS='--no-experimental-fetch' PW_SNAPSHOT_ENABLE=true playwright test --update-snapshots", + "percy": "cross-env NODE_OPTIONS='--no-experimental-fetch' PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=ipad", "tsc": "tsc -b", "lint": "eslint . --ext .js,.ts", "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", + "playwright-ui": "cross-env NODE_OPTIONS='--no-experimental-fetch' playwright test --ui", + "test-slomo": "cross-env NODE_OPTIONS='--no-experimental-fetch' PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test", "show-report": "npx playwright show-report", "postinstall": "npx playwright install" }, @@ -17,7 +18,7 @@ "@axe-core/playwright": "4.7.3", "@percy/cli": "1.27.1", "@percy/playwright": "1.0.4", - "@playwright/test": "1.38.0", + "@playwright/test": "1.38.1", "async-wait-until": "2.0.12", "axe-core": "4.8.1", "chalk": "4.1.2", diff --git a/e2e-tests/playwright/playwright.config.ts b/e2e-tests/playwright/playwright.config.ts index 7b687cabea..0bdc2dae54 100644 --- a/e2e-tests/playwright/playwright.config.ts +++ b/e2e-tests/playwright/playwright.config.ts @@ -17,9 +17,10 @@ export default defineConfig({ workers: testConfig.workers, expect: { timeout: duration.ten_sec, - toMatchSnapshot: { + toHaveScreenshot: { threshold: 0.4, maxDiffPixelRatio: 0.0001, + animations: 'disabled', }, }, use: { diff --git a/e2e-tests/playwright/support/server/default_config.ts b/e2e-tests/playwright/support/server/default_config.ts index 524b196325..5c99cce938 100644 --- a/e2e-tests/playwright/support/server/default_config.ts +++ b/e2e-tests/playwright/support/server/default_config.ts @@ -63,6 +63,7 @@ const onPremServerConfig = (): Partial => { }, TeamSettings: { EnableOpenServer: true, + MaxUsersPerTeam: 2000, }, }; }; diff --git a/e2e-tests/playwright/support/ui/components/channels/center_view.ts b/e2e-tests/playwright/support/ui/components/channels/center_view.ts index ffd91d0ac0..13e8408dc9 100644 --- a/e2e-tests/playwright/support/ui/components/channels/center_view.ts +++ b/e2e-tests/playwright/support/ui/components/channels/center_view.ts @@ -11,14 +11,12 @@ 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')); } diff --git a/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts b/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts deleted file mode 100644 index 50b39e6218..0000000000 --- a/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts +++ /dev/null @@ -1,22 +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 ChannelsHeaderMobile { - readonly container: Locator; - - constructor(container: Locator) { - this.container = container; - } - - async toBeVisible() { - await expect(this.container).toBeVisible(); - } - - async toggleSidebar() { - await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click(); - } -} - -export {ChannelsHeaderMobile}; diff --git a/e2e-tests/playwright/support/ui/components/index.ts b/e2e-tests/playwright/support/ui/components/index.ts index ca2074c108..d05d1705b9 100644 --- a/e2e-tests/playwright/support/ui/components/index.ts +++ b/e2e-tests/playwright/support/ui/components/index.ts @@ -2,7 +2,6 @@ // See LICENSE.txt for license information. 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'; @@ -27,7 +26,6 @@ const components = { ChannelsSidebarRight, ChannelsAppBar, ChannelsHeader, - ChannelsHeaderMobile, ChannelsPostCreate, ChannelsPost, FindChannelsModal, @@ -49,7 +47,6 @@ export { ChannelsSidebarRight, ChannelsAppBar, ChannelsHeader, - ChannelsHeaderMobile, ChannelsPostCreate, ChannelsPost, FindChannelsModal, diff --git a/e2e-tests/playwright/support/visual/index.ts b/e2e-tests/playwright/support/visual/index.ts index 0728aaa331..b69e725094 100644 --- a/e2e-tests/playwright/support/visual/index.ts +++ b/e2e-tests/playwright/support/visual/index.ts @@ -7,11 +7,12 @@ import chalk from 'chalk'; import {expect, TestInfo} from '@playwright/test'; import {illegalRe} from '@e2e-support/util'; -import testConfig, {TestArgs} from '@e2e-test.config'; +import testConfig from '@e2e-test.config'; +import {ScreenshotOptions, TestArgs} from '@e2e-types'; import snapshotWithPercy from './percy'; -export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs) { +export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs, options: ScreenshotOptions = {}) { if (os.platform() !== 'linux') { // eslint-disable-next-line no-console console.log( @@ -25,7 +26,7 @@ export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs) { if (testConfig.snapshotEnabled) { // Visual test with built-in snapshot const filename = testInfo.title.replace(illegalRe, '').replace(/\s/g, '-').trim().toLowerCase(); - expect(await testArgs.page.screenshot({fullPage: true})).toMatchSnapshot(`${filename}.png`); + await expect(testArgs.page).toHaveScreenshot(`${filename}.png`, {fullPage: true, ...options}); } if (testConfig.percyEnabled) { diff --git a/e2e-tests/playwright/support/visual/percy.ts b/e2e-tests/playwright/support/visual/percy.ts index f4892ef7a5..fc14454ae1 100644 --- a/e2e-tests/playwright/support/visual/percy.ts +++ b/e2e-tests/playwright/support/visual/percy.ts @@ -3,7 +3,8 @@ import percySnapshot from '@percy/playwright'; -import testConfig, {TestArgs} from '@e2e-test.config'; +import testConfig from '@e2e-test.config'; +import {TestArgs} from '@e2e-types'; export default async function snapshotWithPercy(name: string, testArgs: TestArgs) { if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) { diff --git a/e2e-tests/playwright/test.config.ts b/e2e-tests/playwright/test.config.ts index 7dc2bc23a5..5d24bbd759 100644 --- a/e2e-tests/playwright/test.config.ts +++ b/e2e-tests/playwright/test.config.ts @@ -1,40 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. - -import {Page, ViewportSize} from '@playwright/test'; import * as dotenv from 'dotenv'; +import {TestConfig} from '@e2e-types'; + dotenv.config(); -export type TestArgs = { - page: Page; - browserName: string; - viewport?: ViewportSize | null; -}; - -export type TestConfig = { - // Server - baseURL: string; - adminUsername: string; - adminPassword: string; - adminEmail: string; - ensurePluginsInstalled: string[]; - resetBeforeTest: boolean; - haClusterEnabled: boolean; - haClusterNodeCount: number; - haClusterName: string; - // CI - isCI: boolean; - // Playwright - headless: boolean; - slowMo: number; - workers: number; - // Visual tests - snapshotEnabled: boolean; - percyEnabled: boolean; - percyToken?: string; -}; - // All process.env should be defined here const config: TestConfig = { // Server @@ -44,7 +15,7 @@ const config: TestConfig = { adminEmail: process.env.PW_ADMIN_EMAIL || 'sysadmin@sample.mattermost.com', ensurePluginsInstalled: typeof process.env?.PW_ENSURE_PLUGINS_INSTALLED === 'string' - ? process.env.PW_ENSURE_PLUGINS_INSTALLED.split(',') + ? process.env.PW_ENSURE_PLUGINS_INSTALLED.split(',').filter((plugin) => Boolean(plugin)) : [], haClusterEnabled: parseBool(process.env.PW_HA_CLUSTER_ENABLED, false), haClusterNodeCount: parseNumber(process.env.PW_HA_CLUSTER_NODE_COUNT, 2), diff --git a/e2e-tests/playwright/tests/accessibility/common/login.spec.ts b/e2e-tests/playwright/tests/accessibility/common/login.spec.ts index ad8e36fc37..bd5bd8d6dc 100644 --- a/e2e-tests/playwright/tests/accessibility/common/login.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/common/login.spec.ts @@ -61,19 +61,19 @@ test('/login accessibility tab support', async ({pw, pages, page}) => { await loginPage.footer.termsLink.press('Tab'); expect(await loginPage.footer.helpLink).toBeFocused(); - // * Should move focus to header logo after tab - await loginPage.footer.helpLink.press('Tab'); - expect(await loginPage.header.logo).toBeFocused(); + // # Move focus to login input + await loginPage.loginInput.focus(); + expect(await loginPage.loginInput).toBeFocused(); - // * Should move focus to create account link after tab - await loginPage.header.logo.press('Tab'); - expect(await loginPage.createAccountLink).toBeFocused(); - - // * Should move focus to create account link after tab - await loginPage.createAccountLink.press('Tab'); + // * Should move focus to login body after shift+tab + await loginPage.loginInput.press('Shift+Tab'); expect(await loginPage.bodyCard).toBeFocused(); - // * Then, should move focus to login body after tab - await loginPage.bodyCard.press('Tab'); - expect(await loginPage.loginInput).toBeFocused(); + // * Should move focus to create account link after shift+tab + await loginPage.bodyCard.press('Shift+Tab'); + expect(await loginPage.createAccountLink).toBeFocused(); + + // * Should move focus to login body after tab + await loginPage.createAccountLink.press('Shift+Tab'); + expect(await loginPage.header.logo).toBeFocused(); }); diff --git a/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts b/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts index 32176afe16..604a96015d 100644 --- a/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts @@ -45,11 +45,11 @@ test('/reset_password accessibility tab support', async ({pages, page}) => { await resetPasswordPage.footer.termsLink.press('Tab'); expect(await resetPasswordPage.footer.helpLink).toBeFocused(); - // * Should move focus to header logo after tab - await resetPasswordPage.footer.helpLink.press('Tab'); - expect(await resetPasswordPage.header.backButton).toBeFocused(); - - // * Then, should move focus to email input after tab - await resetPasswordPage.header.backButton.press('Tab'); + // # Move focus to email input + await resetPasswordPage.emailInput.focus(); expect(await resetPasswordPage.emailInput).toBeFocused(); + + // * Should move focus to back button after shift+tab + await resetPasswordPage.emailInput.press('Shift+Tab'); + expect(await resetPasswordPage.header.backButton).toBeFocused(); }); diff --git a/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts b/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts index cf3431e766..dc68f4926c 100644 --- a/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts @@ -18,7 +18,7 @@ test('/signup_user_complete accessibility quick check', async ({pages, page, axe expect(accessibilityScanResults.violations).toHaveLength(0); }); -test('/signup_user_complete accessibility tab support', async ({pages, page}) => { +test('/signup_user_complete accessibility tab support', async ({pages, page}, testInfo) => { // # Go to reset password page const signupPage = new pages.SignupPage(page); await signupPage.goto(); @@ -71,23 +71,41 @@ test('/signup_user_complete accessibility tab support', async ({pages, page}) => await signupPage.footer.termsLink.press('Tab'); expect(await signupPage.footer.helpLink).toBeFocused(); - // * Should move focus to header logo after tab - await signupPage.footer.helpLink.press('Tab'); - expect(await signupPage.header.logo).toBeFocused(); + // # Move focus to email input + await signupPage.emailInput.focus(); + expect(await signupPage.emailInput).toBeFocused(); - // * Should move focus to header back button after tab - await signupPage.header.logo.press('Tab'); - expect(await signupPage.header.backButton).toBeFocused(); - - // * Should move focus to log in link after tab - await signupPage.header.backButton.press('Tab'); - expect(await signupPage.loginLink).toBeFocused(); - - // * Should move focus to sign up body after tab - await signupPage.loginLink.press('Tab'); + // * Should move focus to sign up body after shift+tab + await signupPage.emailInput.press('Shift+Tab'); expect(await signupPage.bodyCard).toBeFocused(); - // * Then, should move focus to email input after tab - await signupPage.bodyCard.press('Tab'); - expect(await signupPage.emailInput).toBeFocused(); + // * Should move focus to sign up body after shift+tab + await signupPage.emailInput.press('Shift+Tab'); + expect(await signupPage.bodyCard).toBeFocused(); + + if (testInfo.project.name === 'ipad') { + // * Should move focus to header back button after shift+tab + await signupPage.bodyCard.press('Shift+Tab'); + expect(await signupPage.header.backButton).toBeFocused(); + + // * Should move focus to log in link after shift+tab + await signupPage.header.backButton.press('Shift+Tab'); + expect(await signupPage.loginLink).toBeFocused(); + + // * Should move focus to header logo after shift+tab + await signupPage.loginLink.press('Shift+Tab'); + expect(await signupPage.header.logo).toBeFocused(); + } else { + // * Should move focus to log in link after shift+tab + await signupPage.bodyCard.press('Shift+Tab'); + expect(await signupPage.loginLink).toBeFocused(); + + // * Should move focus to header back button after shift+tab + await signupPage.loginLink.press('Shift+Tab'); + expect(await signupPage.header.backButton).toBeFocused(); + + // * Should move focus to header logo after shift+tab + await signupPage.header.backButton.press('Shift+Tab'); + expect(await signupPage.header.logo).toBeFocused(); + } }); diff --git a/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts b/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts index 13611b4b7c..8ddc8d374b 100644 --- a/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts +++ b/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts @@ -37,7 +37,6 @@ test('MM-T5424 Find channel search returns only 50 results when there are more t await channelsPage.toBeVisible(); // # Click on "Find channel" and type "test_channel" - await channelsPage.centerView.headerMobile.toggleSidebar(); await channelsPage.sidebarLeft.findChannelButton.click(); await channelsPage.findChannelsModal.toBeVisible(); diff --git a/e2e-tests/playwright/tests/visual/channels/intro_channel.spec.ts-snapshots/intro-to-channel-as-regular-user-ipad-linux.png b/e2e-tests/playwright/tests/visual/channels/intro_channel.spec.ts-snapshots/intro-to-channel-as-regular-user-ipad-linux.png index 5bbe761878..e8021bf749 100644 Binary files a/e2e-tests/playwright/tests/visual/channels/intro_channel.spec.ts-snapshots/intro-to-channel-as-regular-user-ipad-linux.png and b/e2e-tests/playwright/tests/visual/channels/intro_channel.spec.ts-snapshots/intro-to-channel-as-regular-user-ipad-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-chrome-linux.png b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-chrome-linux.png index 1c5a0cdadd..c3829d3eb5 100644 Binary files a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-chrome-linux.png and b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-chrome-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-firefox-linux.png b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-firefox-linux.png index 1856c983ef..13eef56023 100644 Binary files a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-firefox-linux.png and b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-firefox-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-ipad-linux.png b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-ipad-linux.png index c67b985dfb..4dd3d8f2c2 100644 Binary files a/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-ipad-linux.png and b/e2e-tests/playwright/tests/visual/common/landing_page.spec.ts-snapshots/landing-login-ipad-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png index dd9cbaad55..1559290d13 100644 Binary files a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png and b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png index 3e7e4791ac..d8902bc0c6 100644 Binary files a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png and b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png index b66f7a50cb..2d7fdc1d90 100644 Binary files a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png and b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-ipad-linux.png b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-ipad-linux.png index a3b93c2012..ff12316cc5 100644 Binary files a/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-ipad-linux.png and b/e2e-tests/playwright/tests/visual/common/login.spec.ts-snapshots/login-ipad-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png index c1ddad54e6..79010f1c7c 100644 Binary files a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png and b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png index cfeb0670e6..fd536204ed 100644 Binary files a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png and b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png index 29b0460680..85b2b68c5b 100644 Binary files a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png and b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png index 1c245f0353..a751719131 100644 Binary files a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png and b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png differ diff --git a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png index 4e4e5976d5..2e0c00388a 100644 Binary files a/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png and b/e2e-tests/playwright/tests/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png differ diff --git a/e2e-tests/playwright/tsconfig.json b/e2e-tests/playwright/tsconfig.json index 3c136644ba..e9d33d2052 100644 --- a/e2e-tests/playwright/tsconfig.json +++ b/e2e-tests/playwright/tsconfig.json @@ -10,7 +10,8 @@ "@mattermost/client/*": ["../../webapp/platform/client/lib/*"], "@mattermost/types/*": ["../../webapp/platform/types/lib/*"], "@e2e-support/*": ["support/*"], - "@e2e-test.config": ["test.config.ts"] + "@e2e-test.config": ["test.config.ts"], + "@e2e-types": ["types.ts"] } }, "include": ["./**/*"], diff --git a/e2e-tests/playwright/types.ts b/e2e-tests/playwright/types.ts new file mode 100644 index 0000000000..cbb0c88e4d --- /dev/null +++ b/e2e-tests/playwright/types.ts @@ -0,0 +1,134 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Locator, Page, ViewportSize} from '@playwright/test'; + +export type TestArgs = { + page: Page; + browserName: string; + viewport?: ViewportSize | null; +}; + +export type TestConfig = { + // Server + baseURL: string; + adminUsername: string; + adminPassword: string; + adminEmail: string; + ensurePluginsInstalled: string[]; + resetBeforeTest: boolean; + haClusterEnabled: boolean; + haClusterNodeCount: number; + haClusterName: string; + // CI + isCI: boolean; + // Playwright + headless: boolean; + slowMo: number; + workers: number; + // Visual tests + snapshotEnabled: boolean; + percyEnabled: boolean; + percyToken?: string; +}; + +// Based on https://github.com/microsoft/playwright/blob/d6ec1ae3994f127e38b866a231a34efc6a4cac0d/packages/playwright/types/test.d.ts#L5692-L5759 +export type ScreenshotOptions = { + /** + * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different + * treatment depending on their duration: + * - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event. + * - infinite animations are canceled to initial state, and then played over after the screenshot. + * + * Defaults to `"disabled"` that disables animations. + */ + animations?: 'disabled' | 'allow'; + + /** + * When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be + * changed. Defaults to `"hide"`. + */ + caret?: 'hide' | 'initial'; + + /** + * An object which specifies clipping of the resulting image. + */ + clip?: { + /** + * x-coordinate of top-left corner of clip area + */ + x: number; + + /** + * y-coordinate of top-left corner of clip area + */ + y: number; + + /** + * width of clipping area + */ + width: number; + + /** + * height of clipping area + */ + height: number; + }; + + /** + * When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to + * `false`. + */ + fullPage?: boolean; + + /** + * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink + * box `#FF00FF` (customized by `maskColor`) that completely covers its bounding box. + */ + mask?: Array; + + /** + * Specify the color of the overlay box for masked elements, in + * [CSS color format](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Default color is pink `#FF00FF`. + */ + maskColor?: string; + + /** + * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is + * configurable with `TestConfig.expect`. Unset by default. + */ + maxDiffPixelRatio?: number; + + /** + * An acceptable amount of pixels that could be different. Default is configurable with `TestConfig.expect`. Unset by + * default. + */ + maxDiffPixels?: number; + + /** + * Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. + * Defaults to `false`. + */ + omitBackground?: boolean; + + /** + * When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this + * will keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so + * screenshots of high-dpi devices will be twice as large or even larger. + * + * Defaults to `"css"`. + */ + scale?: 'css' | 'device'; + + /** + * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the + * same pixel in compared images, between zero (strict) and one (lax), default is configurable with + * `TestConfig.expect`. Defaults to `0.2`. + */ + threshold?: number; + + /** + * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; +}; diff --git a/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx b/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx index d8170237d7..0180405376 100644 --- a/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx +++ b/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx @@ -71,6 +71,7 @@ const AppBarPluginComponent = (props: PluginComponentProps) => { > {component.pluginId}