Files
upstream-mattermost/e2e-tests/playwright/specs/functional/system_console/concurrent_config_save.spec.ts
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

146 lines
6.8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {AdminConfig} from '@mattermost/types/config';
import {expect, test} from '@mattermost/playwright-lib';
// Wait for the GET /api/v4/config re-fetch that our config_changed websocket
// handler triggers. This is the signal that entities.admin.config in the
// browser's Redux store is now up-to-date with the server's current state.
async function waitForAdminConfigRefresh(page: import('@playwright/test').Page) {
return page.waitForResponse(
(resp) => resp.url().endsWith('/api/v4/config') && resp.request().method() === 'GET' && resp.status() === 200,
{timeout: 10000},
);
}
// Each test case opens a System Console page, makes a parallel API change to a
// completely separate config section (simulating another admin), then saves the
// open page and asserts both changes survived.
test.describe('System Console > Concurrent config saves', () => {
test('Emoji page: parallel change to TeamSettings is not clobbered', async ({pw}) => {
const {adminUser, adminClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
// # Set a known baseline for both sections under test
await adminClient.patchConfig({
ServiceSettings: {EnableCustomEmoji: false},
TeamSettings: {MaxUsersPerTeam: 50},
} as Partial<AdminConfig>);
// # Open the Emoji settings page
const {page} = await pw.testBrowser.login(adminUser);
await page.goto('/admin_console/site_config/emoji');
const emojiSection = page.getByTestId('sysconsole_section_EmojiSettings');
await expect(emojiSection).toBeVisible();
const saveButton = emojiSection.getByRole('button', {name: 'Save'});
// # Simulate another admin saving an unrelated section via API.
// Set up the response listener before triggering the change so we don't
// miss the GET /api/v4/config that our config_changed handler dispatches.
const configRefresh = waitForAdminConfigRefresh(page);
await adminClient.patchConfig({TeamSettings: {MaxUsersPerTeam: 99}} as Partial<AdminConfig>);
// # Wait for the browser to re-fetch the admin config before we save,
// confirming that this.props.config is now fresh. Then assert the
// toggle's current state to confirm the Redux store update has
// propagated to the UI before we interact.
await configRefresh;
const emojiToggle = emojiSection.getByTestId('ServiceSettings.EnableCustomEmojitrue');
await expect(emojiToggle).not.toBeChecked();
// # Change a setting on the open Emoji page and save
await emojiToggle.click();
await saveButton.click();
await pw.waitUntil(async () => (await saveButton.textContent()) === 'Save');
// * Both the UI change and the parallel API change must be present
const finalConfig = await adminClient.getConfig();
expect(finalConfig.ServiceSettings.EnableCustomEmoji).toBe(true);
expect(finalConfig.TeamSettings.MaxUsersPerTeam).toBe(99);
});
test('Announcement Banner page: parallel change to ServiceSettings is not clobbered', async ({pw}) => {
const {adminUser, adminClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
// # Set a known baseline for both sections under test
await adminClient.patchConfig({
AnnouncementSettings: {EnableBanner: false},
ServiceSettings: {EnableCustomEmoji: false},
} as Partial<AdminConfig>);
// # Open the Announcement Banner settings page
const {page} = await pw.testBrowser.login(adminUser);
await page.goto('/admin_console/site_config/announcement_banner');
const bannerSection = page.getByTestId('sysconsole_section_AnnouncementSettings');
await expect(bannerSection).toBeVisible();
const saveButton = bannerSection.getByRole('button', {name: 'Save'});
// # Simulate another admin saving an unrelated section via API
const configRefresh = waitForAdminConfigRefresh(page);
await adminClient.patchConfig({ServiceSettings: {EnableCustomEmoji: true}} as Partial<AdminConfig>);
await configRefresh;
const bannerToggle = bannerSection.getByTestId('AnnouncementSettings.EnableBannertrue');
await expect(bannerToggle).not.toBeChecked();
// # Change a setting on the open Announcement Banner page and save
await bannerToggle.click();
await saveButton.click();
await pw.waitUntil(async () => (await saveButton.textContent()) === 'Save');
// * Both changes must be present
const finalConfig = await adminClient.getConfig();
expect(finalConfig.AnnouncementSettings.EnableBanner).toBe(true);
expect(finalConfig.ServiceSettings.EnableCustomEmoji).toBe(true);
});
test('Users and Teams page: parallel change to EmailSettings is not clobbered', async ({pw}) => {
const {adminUser, adminClient} = await pw.initSetup();
if (!adminUser) {
throw new Error('Failed to create admin user');
}
// # Set a known baseline for both sections under test
await adminClient.patchConfig({
TeamSettings: {EnableJoinLeaveMessageByDefault: false},
EmailSettings: {EnableSignUpWithEmail: true},
} as Partial<AdminConfig>);
// # Open the Users and Teams settings page
const {page} = await pw.testBrowser.login(adminUser);
await page.goto('/admin_console/site_config/users_and_teams');
const teamSection = page.getByTestId('sysconsole_section_UserAndTeamsSettings');
await expect(teamSection).toBeVisible();
const saveButton = teamSection.getByRole('button', {name: 'Save'});
// # Simulate another admin saving EmailSettings via API
const configRefresh = waitForAdminConfigRefresh(page);
await adminClient.patchConfig({EmailSettings: {EnableSignUpWithEmail: false}} as Partial<AdminConfig>);
await configRefresh;
const joinLeaveToggle = teamSection.getByTestId('TeamSettings.EnableJoinLeaveMessageByDefaulttrue');
await expect(joinLeaveToggle).not.toBeChecked();
// # Toggle Enable Join/Leave messages on the Users and Teams page and save
await joinLeaveToggle.click();
await saveButton.click();
await pw.waitUntil(async () => (await saveButton.textContent()) === 'Save');
// * Both changes must be present
const finalConfig = await adminClient.getConfig();
expect(finalConfig.TeamSettings.EnableJoinLeaveMessageByDefault).toBe(true);
expect(finalConfig.EmailSettings.EnableSignUpWithEmail).toBe(false);
});
});