mattermost/e2e-tests
Scott Bishel 53aa05d8c6
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
Interactive Dialog - DateTime manual entry and timezone support (#34932)
* Respect user display preferences for date and time formatting

User Preference Support:
- Read isUseMilitaryTime preference from user settings
- Apply 24-hour format when enabled (14:00 instead of 2:00 PM)
- Apply 12-hour format when disabled (2:00 PM instead of 14:00)
- Pass useTime prop to Timestamp component with correct hourCycle/hour12

Date Formatting Consistency:
- Create formatDateForDisplay() utility in date_utils.ts
- Centralize date formatting logic (month: 'short', day/year: 'numeric')
- Use consistent "Jan 15, 2025" format across all date/datetime fields
- Replace DateTime.fromJSDate().toLocaleString() which varies by browser

Components Updated:
- DateTimeInput: Use isMilitaryTime for dropdown and selected time display
- DateTimeInput: Use formatDateForDisplay for date display
- AppsFormDateField: Use formatDateForDisplay instead of inline Intl code

Tests Added:
- 4 tests for user preference handling (military time, locale)
- 5 tests for formatDateForDisplay utility
- Updated snapshot for Timestamp changes

Benefits:
- Single source of truth for date formatting
- Easy to change format globally by updating one function
- Respects user preferences consistently
- Fixes inconsistency where datetime showed "1/1/2026" vs date showing "Jan 1, 2026"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Add timezone and manual time entry support for datetime fields

Object Model:
- Create DialogDateTimeConfig with TimeInterval, LocationTimezone, AllowManualTimeEntry
- Add DateTimeConfig field to DialogElement
- Keep legacy MinDate, MaxDate, TimeInterval for fallback

Timezone Support (location_timezone):
- Display datetime in specific IANA timezone (e.g., "Europe/London", "Asia/Tokyo")
- Show timezone indicator: "🌍 Times in GMT"
- Preserve timezone through all operations
- Fix momentToString to clone before converting to UTC (prevents mutation)
- Use moment.tz array syntax for timezone-safe moment creation
- Generate time intervals starting at midnight in display timezone

Manual Time Entry (allow_manual_time_entry):
- Add parseTimeString() function supporting multiple formats:
  - 12-hour: 12a, 12:30p, 3:45pm
  - 24-hour: 14:30, 9:15
- Add TimeInputManual component with text input
- Conditional rendering: manual input OR dropdown
- No rounding for manual entry (exact minutes preserved)
- No auto-advance (validation only, show error for invalid format)
- Respects user's 12h/24h preference for placeholder

Critical Bug Fixes:
- Fix getTimeInIntervals to return Moment[] instead of Date[] (preserves timezone)
- Fix momentToString mutation: use .clone() before .utc()
- Use .clone() when calling .startOf('day') to preserve timezone
- Use moment.tz([...], timezone) array syntax instead of .tz().hour() mutation
- Display selected time using .format() instead of Timestamp component
- Fix null handling: optional fields start empty, show '--:--'
- Manual entry gets exact current time, dropdown gets rounded time

Component Updates:
- DateTimeInput: Add TimeInputManual component, parseTimeString, timezone handling
- AppsFormDateTimeField: Extract config, timezone indicator, pass timezone to child
- Modal components: Handle Moment | null signatures
- CSS: Add manual entry input styles with error states

Features:
- Timezone-aware time generation (dropdown starts at midnight in display TZ)
- Manual entry works with timezones (creates moments in correct TZ)
- Optional fields start empty (null value, no display default)
- Required datetime fields get rounded default from apps_form_component

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Fix momentToString mutation - clone before converting to UTC

Prevents .utc() from mutating the original moment object.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Add E2E test for 12h/24h time preference support

Test MM-T2530H verifies that datetime fields respect user's display preference:
- Sets preference to 24-hour format
- Verifies dropdown shows times as 14:00, 15:00, etc.
- Verifies selected time displays in 24-hour format
- Changes preference to 12-hour format
- Verifies dropdown shows times as 2:00 PM, 3:00 PM, etc.

Uses cy.apiSaveClockDisplayModeTo24HourPreference() to set user preference.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Auto-round time to interval boundaries in DateTimeInput

Automatically rounds displayed time to timePickerInterval to ensure
consistent behavior across all callers.

Problem:
- DND modal and Custom Status modal showed unrounded times (e.g., 13:47)
- Should show rounded times (e.g., 14:00) to match dropdown intervals
- Some callers pre-rounded, others didn't (inconsistent)

Solution:
- Add useEffect in DateTimeInput that auto-rounds on mount
- Only calls handleChange if time needs rounding
- Uses timePickerInterval prop or 30-minute default
- Harmless for callers that already pre-round (no change triggered)

Behavior:
- DND modal: Now shows 14:00 instead of 13:47
- Custom Status: Still works (already pre-rounded, so no-op)
- Post Reminder: Still works (already pre-rounded, so no-op)
- Interactive Dialog: Still works (uses custom intervals)

Added 3 unit tests for auto-rounding behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* lint fix

* Add deferred login cleanup to post_test.go 'not logged in' test

Ensures the test helper is logged back in after the logout test completes, preventing test state issues for subsequent tests.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Add unit tests for parseTimeString and timezone handling

parseTimeString tests (9 test cases):
- 12-hour format with AM/PM (12a, 3:30pm, etc.)
- 24-hour format (14:30, 23:59, etc.)
- Time without minutes (defaults to :00)
- Invalid hours, minutes, and formats
- Edge cases (midnight 12:00am, noon 12:00pm)

Timezone handling tests (3 test cases):
- Preserve timezone in getTimeInIntervals
- Generate intervals starting at midnight in timezone
- Timezone conversion pattern verification

Total: 12 new tests added (32 total in file)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Add E2E tests and webhook support for timezone/manual entry

E2E Tests Added (MM-T2530O through MM-T2530S):
- MM-T2530O: Manual time entry basic functionality
- MM-T2530P: Manual time entry multiple formats (12a, 14:30, 9pm)
- MM-T2530Q: Manual time entry invalid format handling
- MM-T2530R: Timezone support dropdown (London GMT)
- MM-T2530S: Timezone support manual entry (London GMT)

Webhook Server Support:
- Added getTimezoneManualDialog() to webhook_utils.js
- Added 'timezone-manual' case to webhook_serve.js
- Dialog with 3 fields: local manual, London dropdown, London manual

Bug Fixes:
- Skip auto-rounding for allowManualTimeEntry fields (preserve exact minutes)
- Generate dropdown options even when displayTime is null (use currentTime fallback)
- Scope Cypress selectors with .within() to avoid duplicate ID issues

All tests passing (13 total datetime tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Fix ESLint no-multi-spaces in apps.ts

Remove extra spacing before comments to comply with ESLint rules.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* Fix gofmt formatting in integration_action.go

Align Options, MultiSelect, and Refresh field spacing to match Go formatting standards.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* more lint fixes

* css lint fix

* i18n-extract

* lint fixes

* update snapshot

* Fix modal scroll containment for datetime fields

The .modal-overflow class was applying overflow: visible to .modal-body,
which broke scroll containment when datetime fields were present. This
caused the entire form to scroll instead of scrolling within the modal-body
viewport.

Changes:
- Remove .modal-body overflow override from .modal-overflow class to
  preserve scroll containment while still allowing date/time popups to
  display correctly via z-index
- Remove italic styling from timezone indicator for cleaner appearance
- Remove redundant "Time" label from manual time entry input (aria-label
  is sufficient for accessibility)
- Add CSS rule to ensure "(optional)" label text is not bold

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>

* fixes for cypress tests

* fix for using timezone crossing dates

* fix dateonly strings parse failures

* regex fix

* linter fix

---------

Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-02-16 13:32:31 -07:00
..
.ci (chore): upgrade playwright and its dependencies (#35175) 2026-02-09 21:30:07 +08:00
cypress Interactive Dialog - DateTime manual entry and timezone support (#34932) 2026-02-16 13:32:31 -07:00
playwright MM-66937 Fix broken IME handling in Find Channels modal (#35264) 2026-02-16 11:00:59 +08:00
.gitignore upgrade playwright dependencies, screenshots and tests (#29466) 2024-12-04 19:52:18 +08:00
Makefile MM-66362 feat: run e2e full tests after successful smoke tests both in cypress and playwright (#34868) 2026-02-02 08:37:55 +08:00
README.md Support E2E stress tests in run_tests.js (#29682) 2025-01-23 14:07:38 +01:00

E2E testing for the Mattermost web client

This directory contains the E2E testing code for the Mattermost web client.

How to run locally

For test case development

Please refer to the dedicated developer documentation 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, node, 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. (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:
  • SERVER: either onprem (default) or cloud.
  • CWS_URL (mandatory when SERVER=cloud, only used in such case): when spinning up a cloud-like test server that communicates with a test instance of a customer web server.
  • TEST: either cypress (default), playwright, or none (to avoid creating the cypress/playwright sidecar containers, e.g. if you only want to launch a server instance)
  • ENABLED_DOCKER_SERVICES: a space-separated list of services to start alongside the server. Default to postgres inbucket, for smoke test purposes 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 all services to be running: postgres inbucket minio openldap elasticsearch keycloak.
    • Cypress smoke tests require only the following: postgres inbucket.
    • Playwright full tests require only the following: postgres inbucket.
  • The following variables, will be passed over to the server container: MM_LICENSE (no enterprise features will be available if this is unset; required when SERVER=cloud), and the exploded MM_ENV (a comma-separated list of env var specifications)
  • The following variables, which 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/Playwright to run. If not specified, only the smoke tests will run
    • Its format depends on which tool is used: for Cypress, please check the e2e-tests/cypress/run_tests.js file for details. For Playwright, it can simply be populated with arguments you want to give to the playwright test command.
  • More variables may be required to configure reporting and cloud interactions. Check the content of the .ci/report.*.sh and .ci/server.cloud_*.sh scripts for reference.
  1. (optional) make start-dashboard && make generate-test-cycle: start the automation dashboard in the background, and initiate a test cycle on it, for the given BUILD_ID
  • NB: the BUILD_ID value should stay the same across the make generate-test-cycle command, and the subsequent make (see next step). If you need to initiate a new test cycle on the same dashboard, you'll need to change the BUILD_ID value and rerun both make generate-test-cycle and make.
  • Note that part of the dashboard functionality assumes the BUILD_ID to have a certain format (see here for details). This is not relevant for local running, but it's important to note in the testing pipelines.
  • This also automatically 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.
  • The dashboard is used for orchestrating specs with parallel test runs and is typically used in CI.
  • Only Cypress is currently using the dashboard; Playwright is not.
  1. make: start and prepare the server, then run the Cypress smoke tests
  • You can track the progress of the run in the http://localhost:4000/cycles dashboard if you launched it locally
  • For SERVER=cloud runs, you'll need to first create a cloud customer against the specified CWS_URL service by running make cloud-init. The user isn't automatically removed, and may be reused across multiple runs until you run make cloud-teardown to delete it.
  • If you want to run the Playwright tests instead of the Cypress ones, you can run TEST=playwright make
  • If you just want to run a local server instance, without any further testing, you can run TEST=none make
  • If you're using the automation dashboard, you have the option of sharding the E2E test run: you can launch the make command in parallel on different machines (NB: you must use the same BUILD_ID and BRANCH values that you used for make generate-test-cycle) to distribute running the test cases across them. When doing this, you should also set on each machine the CI_BASE_URL variable to a value that uniquely identifies the instance where make is running.
  • This script will also parse the local test results, and write a e2e-tests/${TEST}/results/summary.json file containing the following keys: passed, failed and failed_expected (the total number of testcases that were run is the sum of these three numbers)
  1. make stop: tears down the server (and the dashboard, if running)
  • This will stop and cleanup all of the E2E testing containers, including the database and its persistent volume.
  • This also implicitly runs make clean, which also removes any generated environment or docker-compose 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.
  • All of the variables in .ci/env must be set before the make generate-server command is run (or, if using the dashboard, before the make generate-test-cycle command). Modifying that file afterward 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 new variables need to be passed to any of the containers, here are the general principles to follow when deciding where to populate it:
    • 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.
    • Exceptions are of course accepted wherever it makes sense (e.g. if you need to group variables based on some common functionality)
  • The report Make target is meant for internal usage. Usage and variables are documented in the respective scripts.
  • make start-server won't cleanup containers that don't change across runs. This means that you can use it to emulate a Mattermost server upgrade while retaining your database data by simply changing the SERVER_IMAGE variable on your machine, and then re-running make start-server. But this also means that if you want to run a clean local environment, you may have to manually run make stop to cleanup any running containers and their volumes, which include e.g. the database.
For code changes:
  • make fmt-ci to format and check yaml files and shell scripts.
For test stressing an E2E testcase

For Cypress:

  1. Enter the cypress/ subdirectory
  2. Identify which test files you want to run, and how many times each. For instance: suppose you want to run create_a_team_spec.js and demoted_user_spec.js (which you can locate with the find command, under cypress/tests/), each run 3 times
  3. Run the chosen testcases the desired amount of times: node run_tests.js --include-file=create_a_team_spec.js,demoted_user_spec.js --invert --stress-test-count=3
  1. The cypress/results/testPasses.json file will count, for each of the testfiles, how many times it was run, and how many times each of the testcases contained in it passed. If the attempts and passes numbers do not match, that specific testcase may be flaky.

For Playwright: WIP