fix(e2e): repair master flakes from the channel intro user limit and tour-tip pulse rings (#38986)

* fix(e2e): repair file_permissions_edge_cases.spec.ts on master

MM-T5828 built its permalink from testConfig.internalBaseURL and assumed the
server's SiteURL still matched. pw.ensureSiteUrl() restarts the Mattermost
container with MM_SERVICESETTINGS_SITEURL pointing at the host-reachable
baseURL, and because that is an env override, initSetup()'s patchConfig cannot
put it back. On a reused server, every spec that runs after an auth/claim or
oidc spec therefore sees a SiteURL the permalink no longer matches, the server
emits no permalink embed, and .post-preview never renders.

Read SiteURL off the running server instead of assuming it.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): share permalinkUrl helper across SiteURL-sensitive specs

Build /pl/ permalinks from the SiteURL the running server reports, via
one helper used by MM-T5828 and the post_list specs that had the same
internalBaseURL assumption.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* fix(e2e): reject empty SiteURL in permalinkUrl helper

Fail fast instead of building a relative /pl/ path when the server
reports no SiteURL.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* fix(e2e): converge SiteURL for demo_dialog interactive dialogs

demo_dialog.spec.ts's two bare-/dialog tests fail on any CI worker that
earlier ran a spec calling pw.ensureSiteUrl(). The demo plugin registers
its dialog callback as <SiteURL>/plugins/<id>/dialog/1, and
App.DoActionRequest only dispatches in-process for relative URLs, so an
absolute one is a real outbound POST. Once SiteURL points at the
host-mapped baseURL the server cannot reach that address from inside its
own container: the submit returns 400, leaving the dialog open, and the
cancel posts no notification.

Add pw.ensureInternalSiteUrl(), which converges SiteURL back to
internalBaseURL and is a no-op on a stack booted with the default, and
call it from the two tests whose callback URL is absolute.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): repair channel_headers.spec.ts MM-T880 on master

A channel intro replaces its action buttons -- favorite, "Set header" and
notification preferences -- with just the invite button while the server has
fewer users than the webapp's usersLimit of 10. On a reused Testcontainers
server that count depends on how many specs the worker ran before, so
MM-T880's click on the intro's "Set header" button only found it by luck.

Add ensureChannelIntroActions(), which tops the server up to that many users,
and call it from the test.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(webapp): stop tour-tip pulse rings from swallowing clicks

PulsatingDot's ::before/::after rings animate to scale(2.5), 30px across a
12px dot, and were hit-testable. The burn-on-read tour tip puts that dot on
top of the burn-on-read button, so for ~77% of every 2s pulse cycle the
element at the button's own centre is the dot, not the button.

A click whose mousedown and mouseup land on different elements is delivered
to their nearest common ancestor, so when a ring crosses the cursor between
the two, `click` fires on .BurnOnReadControl and neither the button's toggle
nor the tip's open handler runs. That is the intermittent
dm_gm_flow.spec.ts "MM-66742_1 BoR toggle is available in DM channel"
failure on master: the click reports success and the BURN ON READ label
never appears.

The rings are decoration; the dot itself stays clickable.

https://github.com/mattermost/mattermost/actions/runs/37978315300

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): drop the permalinkUrl helper now that master resets SiteURL per spec

#39091's ensureDefaultServerEnv() restores the default SiteURL before each
spec file, so the permalink specs pass with master's own copies (12/12 and
210/210 on a server with a leaked SiteURL). Revert them and remove the helper.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-21 03:44:05 +05:30
1 parent 926f347bd5
commit 4d716b67f9
5 files changed
+33 -1

No files matched your search

@@ -16,6 +16,7 @@ export {createNewTeam, createRandomTeam} from './team';
export {
createNewUserProfile,
createRandomUser,
ensureChannelIntroActions,
getDefaultAdminUser,
isOutsideRemoteUserHour,
setDefaultOnboardingPreferences,
@@ -43,6 +43,26 @@ export async function createNewUserProfile(
return newUser;
}
/**
* `usersLimit` in webapp/channels/src/components/post_view/channel_intro_message/index.ts.
* Below it, a channel intro shows only the invite button and drops the favorite, "Set header"
* and notification preferences buttons.
*/
const CHANNEL_INTRO_ACTIONS_MIN_USERS = 10;
/**
* Creates throwaway users until the server has enough of them for a channel intro to render its
* action buttons. Specs that drive those buttons must call this: on a reused server the user
* count depends on how many specs ran before, so the buttons are otherwise there only by luck.
*/
export async function ensureChannelIntroActions(client: Client4) {
const {total_users_count: totalUsers} = await client.getTotalUsersStats();
for (let i = totalUsers; i < CHANNEL_INTRO_ACTIONS_MIN_USERS; i++) {
await client.createUser(await createRandomUser('introfill'), '', '');
}
}
/**
* Marks the tutorial and first-time onboarding task list as already seen for the given
* user, so they don't cover the UI on their first visit to a channel. Useful for users
@@ -36,6 +36,7 @@ import {
elasticsearchServerConfig,
enableAIBridgeTestMode,
ensureAzurite,
ensureChannelIntroActions,
ensureDemoPlugin,
ensureElasticsearch,
ensureFeatureFlag,
@@ -200,6 +201,7 @@ export class PlaywrightExtended {
readonly getFileFromAsset;
// ./server
readonly ensureChannelIntroActions;
readonly ensurePluginsLoaded;
readonly getAdminClient;
readonly resetConfig;
@@ -330,6 +332,7 @@ export class PlaywrightExtended {
this.getFileFromAsset = getFileFromAsset;
// ./server
this.ensureChannelIntroActions = ensureChannelIntroActions;
this.ensurePluginsLoaded = ensurePluginsLoaded;
this.initSetup = initSetup;
this.getAdminClient = getAdminClient;
@@ -57,7 +57,11 @@ test(
* that cancelling the modal makes no change, and that the saved header appears in the channel info.
*/
test('MM-T880 adds a channel header from the intro Set header link', {tag: '@channel_settings'}, async ({pw}) => {
const {user, team} = await pw.initSetup();
const {adminClient, user, team} = await pw.initSetup();
// # Make sure the channel intro offers its action buttons instead of only the invite button
await pw.ensureChannelIntroActions(adminClient);
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
@@ -18,6 +18,9 @@
border-radius: 50%;
}
// The pulse rings grow to 2.5x the dot and are purely decorative. Left hit-testable they
// cover whatever the dot is placed over -- e.g. the burn-on-read button it sits on -- and
// swallow clicks meant for it.
&::before,
&::after {
position: absolute;
@@ -25,6 +28,7 @@
left: 0;
display: block;
content: "";
pointer-events: none;
}
&::after {