Files
f8f3abc50b Anonymous URLs (#35493) (#35576)
* COmposing messages with redacted URLs

* Handled non member channels

* Some refinements

* Optimizations

* lint fixes

* cleaned up hasObfuscatedSlug test

* Fixed a test

* Added system console setting

* WIP

* fixed channel seelection double selection bug

* LInt fixes

* i18n fixes

* fixed test

* CI

* renamed setting

* lint fixes

* lint fixes

* WIP

* Combined TeamSignupDisplayNamePage and TeamUrl component into a single CreateTeamForm component

* Converted CreateTeamForm to functional component

* Refactored to mnake code cleaner

* Handle team creation with setting enabled

* Skipped team URL step if secure URL feature is enabled

* Managed button text and steps in team creation flow

* lint fixes

* Don't register team URL path when using secure URL

* Display team display name instead of name in system console top nav bar

* Fixed tests

* Fixed coderabbit issues

* Fixed type errors

* Optimization

* improvements

* Handled API errors during team creation when using secure URL setting

* Some refinements

* Added test

* Updaetd tests, and trimming when reading instead of writing

* Added tests for new components

* Added BackstageNavbar tests

* Restored package lock

* lint fix

* Updaetd plugin API

* Updated team creation tests

* Added tests for ChannelNameFormField

* Added plugin API tests

* Updated API terst

* Review fixes

* Added test for ConvertGmToChannelModal component

* Added EA license check for secure urls feature

* restored package lock

* Fixed GM conversion test

* Fixed team creation tests

* remove message composition changes

* remove message composition changes

* remove message composition changes

* restored a file

* lint fix

* renamed feature

* used model.SafeDereference

* Added E2E tests

* add secure URL Playwright coverage

Expand the secure URLs Playwright coverage to validate creation, routing, rename flows, system console configuration, and search/navigation behavior while adding the page objects needed to keep the tests maintainable.

Made-with: Cursor

* rename secure URLs copy to anonymous URLs

Align the admin console and Playwright coverage with the Anonymous URLs feature name while preserving the existing UseAnonymousURLs config behavior and validating the renamed test surfaces.

Made-with: Cursor

* Update team creation CTA for anonymous URLs

Show Create in the single-step anonymous URL flow while preserving Next and Finish in the standard team creation flow. Update unit and Playwright coverage to match the revised create-team UX.

Made-with: Cursor

---------



(cherry picked from commit d81e1722cf)

Co-authored-by: Harshil Sharma <[email protected]>
Co-authored-by: maria.nunez <[email protected]>
2026-03-12 17:12:07 -04:00

901 lines
42 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
const OBFUSCATED_SLUG_RE = /^[a-z0-9]{26}$/;
async function skipIfNoAdvancedLicense(adminClient: any) {
const license = await adminClient.getClientLicenseOld();
test.skip(license.SkuShortName !== 'advanced', 'Skipping test - server does not have enterprise advanced license');
}
async function setAnonymousUrls(adminClient: any, enabled: boolean) {
await adminClient.patchConfig({
PrivacySettings: {
UseAnonymousURLs: enabled,
},
});
}
function expectObfuscatedSlug(slug: string) {
expect(slug).toMatch(OBFUSCATED_SLUG_RE);
}
function expectReadableSlug(slug: string, expectedSlug?: string) {
if (expectedSlug) {
expect(slug).toBe(expectedSlug);
}
expect(slug).not.toMatch(OBFUSCATED_SLUG_RE);
}
async function createChannelFromUI(channelsPage: any, displayName: string) {
const newChannelModal = await channelsPage.openNewChannelModal();
await newChannelModal.fillDisplayName(displayName);
await newChannelModal.create();
await channelsPage.toBeVisible();
}
async function createTeamFromUI(channelsPage: any, displayName: string) {
const createTeamForm = await channelsPage.openCreateTeamForm();
await createTeamForm.fillTeamName(displayName);
await createTeamForm.submitDisplayName();
await channelsPage.toBeVisible();
}
async function getChannelByDisplayName(adminClient: any, teamId: string, displayName: string) {
const channels = await adminClient.getChannels(teamId);
const channel = channels.find((candidate: any) => candidate.display_name === displayName);
expect(channel).toBeDefined();
return channel!;
}
async function getTeamByDisplayName(adminClient: any, displayName: string) {
const teams = await adminClient.getMyTeams();
const team = teams.find((candidate: any) => candidate.display_name === displayName);
expect(team).toBeDefined();
return team!;
}
async function createAnonymousUrlChannel(
channelsPage: any,
adminClient: any,
teamName: string,
teamId: string,
displayName: string,
) {
await createChannelFromUI(channelsPage, displayName);
await channelsPage.centerView.header.toHaveTitle(displayName);
const channel = await getChannelByDisplayName(adminClient, teamId, displayName);
expectObfuscatedSlug(channel.name);
await expect(channelsPage.page).toHaveURL(new RegExp(`/${teamName}/channels/${channel.name}$`));
return channel;
}
test.describe('Anonymous URLs', () => {
/**
* @objective Verify that the anonymous URLs setting can be toggled on from System Console and persists after navigation
*
* @precondition
* Server must have an Enterprise Advanced license
*/
test(
'enables anonymous URLs setting from System Console and verifies it persists',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and check license
const {adminUser, adminClient} = await pw.initSetup({withDefaultProfileImage: false});
const license = await adminClient.getClientLicenseOld();
test.skip(
license.SkuShortName !== 'advanced',
'Skipping test - server does not have enterprise advanced license',
);
// # Log in as admin
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Visit System Console
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
// # Navigate to Users and Teams
await systemConsolePage.sidebar.siteConfiguration.usersAndTeams.click();
await systemConsolePage.usersAndTeams.toBeVisible();
// * Verify the anonymous URLs radio group is visible
await systemConsolePage.usersAndTeams.useAnonymousURLs.toBeVisible();
// * Verify the setting is initially false
await systemConsolePage.usersAndTeams.useAnonymousURLs.toBeFalse();
// # Enable anonymous URLs by clicking the True radio
await systemConsolePage.usersAndTeams.useAnonymousURLs.selectTrue();
// * Verify it is now true
await systemConsolePage.usersAndTeams.useAnonymousURLs.toBeTrue();
// # Save settings
await systemConsolePage.usersAndTeams.save();
await pw.waitUntil(async () => (await systemConsolePage.usersAndTeams.saveButton.textContent()) === 'Save');
// # Navigate away and come back
await systemConsolePage.sidebar.siteConfiguration.notifications.click();
await systemConsolePage.notifications.toBeVisible();
await systemConsolePage.sidebar.siteConfiguration.usersAndTeams.click();
await systemConsolePage.usersAndTeams.toBeVisible();
// * Verify the setting is still enabled
await systemConsolePage.usersAndTeams.useAnonymousURLs.toBeTrue();
// # Reset to false for cleanup
await systemConsolePage.usersAndTeams.useAnonymousURLs.selectFalse();
await systemConsolePage.usersAndTeams.save();
await pw.waitUntil(async () => (await systemConsolePage.usersAndTeams.saveButton.textContent()) === 'Save');
},
);
/**
* @objective Verify that the channel URL editor is hidden when creating a new channel with anonymous URLs enabled
*
* @precondition
* Server must have an Enterprise Advanced license
*/
test(
'hides channel URL editor when creating new channel with anonymous URLs enabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and configure anonymous URLs
const {adminUser, adminClient} = await pw.initSetup({withDefaultProfileImage: false});
const license = await adminClient.getClientLicenseOld();
test.skip(
license.SkuShortName !== 'advanced',
'Skipping test - server does not have enterprise advanced license',
);
await setAnonymousUrls(adminClient, true);
// # Log in and go to channels
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open new channel modal
await channelsPage.sidebarLeft.browseOrCreateChannelButton.click();
await channelsPage.page.locator('#createNewChannelMenuItem').click();
await channelsPage.newChannelModal.toBeVisible();
// # Fill in a channel name
await channelsPage.newChannelModal.fillDisplayName('Anonymous Test Channel');
// * Verify the URL editor section is not visible
await expect(channelsPage.newChannelModal.urlSection).not.toBeVisible();
// # Cancel modal
await channelsPage.newChannelModal.cancel();
},
);
/**
* @objective Verify that a channel created with anonymous URLs enabled has an obfuscated slug that does not match the display name
*
* @precondition
* Server must have an Enterprise Advanced license
*/
test(
'creates channel with obfuscated URL slug when anonymous URLs enabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
const license = await adminClient.getClientLicenseOld();
test.skip(
license.SkuShortName !== 'advanced',
'Skipping test - server does not have enterprise advanced license',
);
await setAnonymousUrls(adminClient, true);
// # Add admin to the test team so they can create channels there
await adminClient.addToTeam(team.id, adminUser.id);
// # Log in and go to the test team
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto(team.name);
await channelsPage.toBeVisible();
// # Create a new channel via UI
const channelDisplayName = 'Obfuscated Channel ' + Date.now();
await channelsPage.sidebarLeft.browseOrCreateChannelButton.click();
await channelsPage.page.locator('#createNewChannelMenuItem').click();
await channelsPage.newChannelModal.toBeVisible();
await channelsPage.newChannelModal.fillDisplayName(channelDisplayName);
await channelsPage.newChannelModal.create();
// # Wait for channel to be created and navigated to
await channelsPage.toBeVisible();
await pw.wait(pw.duration.two_sec);
// # Fetch all channels for the team to find the newly created one by display name
const allChannels = await adminClient.getChannels(team.id);
const createdChannel = allChannels.find((ch) => ch.display_name === channelDisplayName);
// * Verify channel was created
expect(createdChannel).toBeDefined();
// * Verify the slug does not match a cleaned version of the display name
const humanReadableSlug = channelDisplayName.toLowerCase().replace(/\s+/g, '-');
expect(createdChannel!.name).not.toBe(humanReadableSlug);
// * Verify the slug looks like a model.NewId (26 chars, alphanumeric)
expect(createdChannel!.name).toMatch(/^[a-z0-9]{26}$/);
// * Verify display name is preserved
expect(createdChannel!.display_name).toBe(channelDisplayName);
},
);
/**
* @objective Verify that the channel URL editor is visible when creating a new channel with anonymous URLs disabled (default)
*/
test(
'shows channel URL editor when creating new channel with anonymous URLs disabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup
const {adminUser} = await pw.initSetup({withDefaultProfileImage: false});
// # Log in and go to channels
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open new channel modal
await channelsPage.sidebarLeft.browseOrCreateChannelButton.click();
await channelsPage.page.locator('#createNewChannelMenuItem').click();
await channelsPage.newChannelModal.toBeVisible();
// # Type a display name to trigger URL generation
await channelsPage.newChannelModal.fillDisplayName('Test Channel URL');
// * Verify the URL editor section is visible
await expect(channelsPage.newChannelModal.urlSection).toBeVisible();
// # Cancel modal
await channelsPage.newChannelModal.cancel();
},
);
/**
* @objective Verify that the team URL step is skipped when creating a team with anonymous URLs enabled and the team is created directly after entering display name
*
* @precondition
* Server must have an Enterprise Advanced license
*/
test(
'skips team URL step when creating team with anonymous URLs enabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup
const {adminUser, adminClient} = await pw.initSetup({withDefaultProfileImage: false});
const license = await adminClient.getClientLicenseOld();
test.skip(
license.SkuShortName !== 'advanced',
'Skipping test - server does not have enterprise advanced license',
);
await setAnonymousUrls(adminClient, true);
// # Log in and go to channels
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open team menu and click Create a team
await channelsPage.sidebarLeft.teamMenuButton.click();
await channelsPage.teamMenu.toBeVisible();
await channelsPage.teamMenu.clickCreateTeam();
// # Wait for create team form
await channelsPage.createTeamForm.toBeVisible();
// * Verify the display name input is visible
await expect(channelsPage.createTeamForm.teamNameInput).toBeVisible();
// * Verify the submit button text says "Create" because team creation stays on a single step
await expect(channelsPage.createTeamForm.teamNameSubmitButton).toContainText('Create');
// # Enter team name and submit
const teamName = 'Anonymous Team ' + Date.now();
await channelsPage.createTeamForm.fillTeamName(teamName);
await channelsPage.createTeamForm.submitDisplayName();
// * Verify the team is created and user is redirected (no URL step shown)
await channelsPage.toBeVisible();
// # Verify the team has an obfuscated slug
const teams = await adminClient.getMyTeams();
const createdTeam = teams.find((t) => t.display_name === teamName);
expect(createdTeam).toBeDefined();
expect(createdTeam!.name).toMatch(/^[a-z0-9]{26}$/);
},
);
/**
* @objective Verify that the team URL step is shown when creating a team with anonymous URLs disabled (default)
*/
test(
'shows team URL step when creating team with anonymous URLs disabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup
const {adminUser} = await pw.initSetup({withDefaultProfileImage: false});
// # Log in and go to channels
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open team menu and click Create a team
await channelsPage.sidebarLeft.teamMenuButton.click();
await channelsPage.teamMenu.toBeVisible();
await channelsPage.teamMenu.clickCreateTeam();
// # Wait for create team form
await channelsPage.createTeamForm.toBeVisible();
// * Verify the submit button says "Next" on the first step because the team URL step follows
await expect(channelsPage.createTeamForm.teamNameSubmitButton).toContainText('Next');
// # Enter team name and click Next
await channelsPage.createTeamForm.fillTeamName('Test Team URL Step');
await channelsPage.createTeamForm.submitDisplayName();
// * Verify the team URL step is now visible
await expect(channelsPage.createTeamForm.teamURLInput).toBeVisible();
await expect(channelsPage.createTeamForm.teamURLSubmitButton).toBeVisible();
await expect(channelsPage.createTeamForm.teamURLSubmitButton).toContainText('Finish');
},
);
/**
* @objective Verify that an archived channel created with anonymous URLs keeps its obfuscated route and becomes usable again after unarchiving.
*/
test(
'preserves archived anonymous channel routes and restores channel access after unarchive',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
await adminClient.addToTeam(team.id, adminUser.id);
// # Log in as admin and create a channel with an obfuscated slug
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto(team.name);
await channelsPage.toBeVisible();
const channelDisplayName = `Archived Anonymous ${await pw.random.id()}`;
await createChannelFromUI(channelsPage, channelDisplayName);
const createdChannel = await getChannelByDisplayName(adminClient, team.id, channelDisplayName);
expectObfuscatedSlug(createdChannel.name);
// # Archive the channel and preserve the anonymous URL slug
await adminClient.deleteChannel(createdChannel.id);
const archivedChannel = await adminClient.getChannel(createdChannel.id);
// * Verify archiving does not rotate the anonymous URL route slug
expect(archivedChannel.name).toBe(createdChannel.name);
// # Restore the archived channel and verify the anonymous URL slug is preserved
const restoredChannel = await adminClient.unarchiveChannel(createdChannel.id);
expect(restoredChannel.name).toBe(createdChannel.name);
// # Open the restored channel again from the sidebar
await channelsPage.page.reload();
await channelsPage.sidebarLeft.goToItem(createdChannel.name);
// * Verify the restored channel still uses the original anonymous URL route
await channelsPage.centerView.header.toHaveTitle(channelDisplayName);
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${createdChannel.name}`);
},
);
/**
* @objective Verify that enabling anonymous URLs does not rewrite existing readable slugs and only affects channels and teams created afterward.
*/
test(
'keeps existing readable routes unchanged and obfuscates only newly created channels and teams',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup with anonymous URLs disabled by default
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await adminClient.addToTeam(team.id, adminUser.id);
// # Create a legacy channel and team before enabling anonymous URLs
const legacyChannelSlug = `legacy-channel-${await pw.random.id()}`;
const legacyChannelDisplayName = `Legacy Channel ${await pw.random.id()}`;
const legacyChannel = await adminClient.createChannel({
team_id: team.id,
name: legacyChannelSlug,
display_name: legacyChannelDisplayName,
type: 'O',
});
const legacyTeamSlug = `legacy-team-${await pw.random.id()}`;
const legacyTeamDisplayName = `Legacy Team ${await pw.random.id()}`;
const legacyTeam = await adminClient.createTeam({
name: legacyTeamSlug,
display_name: legacyTeamDisplayName,
type: 'O',
} as any);
expectReadableSlug(legacyChannel.name, legacyChannelSlug);
expectReadableSlug(legacyTeam.name, legacyTeamSlug);
// # Enable anonymous URLs after the legacy channel and team already exist
await setAnonymousUrls(adminClient, true);
const legacyChannelAfterToggle = await adminClient.getChannel(legacyChannel.id);
const legacyTeamAfterToggle = await adminClient.getTeam(legacyTeam.id);
// * Verify the pre-existing slugs remain readable and unchanged
expectReadableSlug(legacyChannelAfterToggle.name, legacyChannelSlug);
expectReadableSlug(legacyTeamAfterToggle.name, legacyTeamSlug);
// # Log in and verify the original readable channel route still works
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto(team.name, legacyChannelSlug);
await channelsPage.toBeVisible();
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${legacyChannelSlug}`);
// # Create a new channel after the anonymous URL toggle
const anonymousChannelDisplayName = `Anonymous Channel ${await pw.random.id()}`;
await channelsPage.goto(team.name);
await channelsPage.toBeVisible();
await createChannelFromUI(channelsPage, anonymousChannelDisplayName);
const anonymousChannel = await getChannelByDisplayName(adminClient, team.id, anonymousChannelDisplayName);
// * Verify only the new channel receives an obfuscated slug
expectObfuscatedSlug(anonymousChannel.name);
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${anonymousChannel.name}`);
// # Create a new team after the anonymous URL toggle
const anonymousTeamDisplayName = `Anonymous Team ${await pw.random.id()}`;
await createTeamFromUI(channelsPage, anonymousTeamDisplayName);
const anonymousTeam = await getTeamByDisplayName(adminClient, anonymousTeamDisplayName);
// * Verify only the new team receives an obfuscated slug
expectObfuscatedSlug(anonymousTeam.name);
await expect(channelsPage.page).toHaveURL(new RegExp(`/${anonymousTeam.name}/`));
},
);
/**
* @objective Verify that direct and group messages continue using message routes and are excluded from anonymous URL slug obfuscation.
*/
test(
'keeps direct and group message routes readable when anonymous URLs are enabled',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup, create message participants, and enable anonymous URLs
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
await adminClient.addToTeam(team.id, adminUser.id);
const secondUser = await pw.createNewUserProfile(adminClient, {prefix: 'anonymousurlsdm'});
const thirdUser = await pw.createNewUserProfile(adminClient, {prefix: 'anonymousurlsgm'});
await adminClient.addToTeam(team.id, secondUser.id);
await adminClient.addToTeam(team.id, thirdUser.id);
const dmChannel = await adminClient.createDirectChannel([adminUser.id, secondUser.id]);
const gmChannel = await adminClient.createGroupChannel([adminUser.id, secondUser.id, thirdUser.id]);
const dmMessage = `Anonymous URL DM ${await pw.random.id()}`;
const gmMessage = `Anonymous URL GM ${await pw.random.id()}`;
await adminClient.createPost({channel_id: dmChannel.id, message: dmMessage});
await adminClient.createPost({channel_id: gmChannel.id, message: gmMessage});
// * Verify DM and GM channel identifiers are not replaced with obfuscated slugs
expect(dmChannel.type).toBe('D');
expect(gmChannel.type).toBe('G');
expectReadableSlug(dmChannel.name);
expectReadableSlug(gmChannel.name);
expect(dmChannel.name).toContain(adminUser.id);
expect(dmChannel.name).toContain(secondUser.id);
// # Log in as admin and open the DM route
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto(team.name, `@${secondUser.username}`);
await channelsPage.toBeVisible();
// * Verify the DM still uses the standard message route
await expect(channelsPage.page).toHaveURL(`/${team.name}/messages/@${secondUser.username}`);
await channelsPage.centerView.waitUntilLastPostContains(dmMessage);
// # Open the GM route
await channelsPage.gotoMessage(team.name, gmChannel.name);
await channelsPage.toBeVisible();
// * Verify the GM still uses the standard message route
await expect(channelsPage.page).toHaveURL(`/${team.name}/messages/${gmChannel.name}`);
await channelsPage.centerView.waitUntilLastPostContains(gmMessage);
},
);
/**
* @objective Verify that renaming an anonymous URL channel changes the display name without rewriting its obfuscated channel slug.
*/
test(
'renames an anonymous URL channel without changing its obfuscated route',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and create an anonymous URL channel
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
await adminClient.addToTeam(team.id, adminUser.id);
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto(team.name);
await channelsPage.toBeVisible();
const originalDisplayName = `Original Channel ${await pw.random.id()}`;
await createChannelFromUI(channelsPage, originalDisplayName);
const createdChannel = await getChannelByDisplayName(adminClient, team.id, originalDisplayName);
const originalSlug = createdChannel.name;
expectObfuscatedSlug(originalSlug);
// # Rename the channel from channel settings
const renamedDisplayName = `Renamed Channel ${await pw.random.id()}`;
const channelSettingsModal = await channelsPage.openChannelSettings();
const infoTab = await channelSettingsModal.openInfoTab();
await infoTab.updateName(renamedDisplayName);
await channelSettingsModal.save();
await pw.waitUntil(
async () => (await adminClient.getChannel(createdChannel.id)).display_name === renamedDisplayName,
);
await channelSettingsModal.close();
const renamedChannel = await adminClient.getChannel(createdChannel.id);
// * Verify the channel name changes without rotating the anonymous URL slug
expect(renamedChannel.display_name).toBe(renamedDisplayName);
expect(renamedChannel.name).toBe(originalSlug);
expectObfuscatedSlug(renamedChannel.name);
// # Reopen the channel using its original obfuscated route
await channelsPage.goto(team.name, originalSlug);
await channelsPage.toBeVisible();
// * Verify the obfuscated route still resolves to the renamed channel
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${originalSlug}`);
await channelsPage.centerView.header.toHaveTitle(renamedDisplayName);
},
);
/**
* @objective Verify that renaming an anonymous URL team changes the display name without rewriting its obfuscated team slug.
*/
test(
'renames an anonymous URL team without changing its obfuscated route',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {adminUser, adminClient} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
// # Log in as admin and create a team with an obfuscated slug
const {channelsPage} = await pw.testBrowser.login(adminUser);
await channelsPage.goto();
await channelsPage.toBeVisible();
const originalTeamDisplayName = `Original Team ${await pw.random.id()}`;
await createTeamFromUI(channelsPage, originalTeamDisplayName);
const createdTeam = await getTeamByDisplayName(adminClient, originalTeamDisplayName);
const originalTeamSlug = createdTeam.name;
expectObfuscatedSlug(originalTeamSlug);
// # Rename the team from team settings
const renamedTeamDisplayName = `Renamed Team ${await pw.random.id()}`;
const teamSettingsModal = await channelsPage.openTeamSettings();
const infoTab = await teamSettingsModal.openInfoTab();
await infoTab.updateName(renamedTeamDisplayName);
await teamSettingsModal.save();
await pw.waitUntil(
async () => (await adminClient.getTeam(createdTeam.id)).display_name === renamedTeamDisplayName,
);
await teamSettingsModal.close();
const renamedTeam = await adminClient.getTeam(createdTeam.id);
// * Verify the team name changes without rotating the anonymous URL slug
expect(renamedTeam.display_name).toBe(renamedTeamDisplayName);
expect(renamedTeam.name).toBe(originalTeamSlug);
expectObfuscatedSlug(renamedTeam.name);
// # Reopen the team using its original obfuscated route
await channelsPage.goto(originalTeamSlug);
await channelsPage.toBeVisible();
// * Verify the obfuscated team route still resolves to the renamed team
await expect(channelsPage.page).toHaveURL(new RegExp(`/${originalTeamSlug}/`));
const reopenedTeamSettings = await channelsPage.openTeamSettings();
await expect(reopenedTeamSettings.infoSettings.nameInput).toHaveValue(renamedTeamDisplayName);
await reopenedTeamSettings.close();
},
);
/**
* @objective Verify that post permalinks created in anonymous URL channels continue to resolve after the feature is turned off.
*/
test(
'opens anonymous channel permalinks before and after disabling anonymous URLs',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
// # Log in and create an anonymous URL channel
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
const displayName = `Permalink Channel ${await pw.random.id()}`;
const channel = await createAnonymousUrlChannel(channelsPage, adminClient, team.name, team.id, displayName);
// # Publish a post that will be opened via permalink
const message = `Anonymous permalink message ${await pw.random.id()}`;
await channelsPage.postMessage(message);
const lastPost = await channelsPage.getLastPost();
const postId = await lastPost.getId();
const permalink = `/${team.name}/pl/${postId}`;
// # Open the permalink while anonymous URLs are enabled
await channelsPage.page.goto(permalink);
// * Verify the permalink resolves to the channel's obfuscated route
await channelsPage.centerView.header.toHaveTitle(displayName);
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${channel.name}`);
await channelsPage.centerView.waitUntilPostWithIdContains(postId, message);
// # Disable anonymous URLs and reopen the same permalink
await setAnonymousUrls(adminClient, false);
await channelsPage.page.goto(permalink);
// * Verify the permalink still resolves to the existing obfuscated route
await channelsPage.centerView.header.toHaveTitle(displayName);
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${channel.name}`);
await channelsPage.centerView.waitUntilPostWithIdContains(postId, message);
},
);
/**
* @objective Verify that channel search finds anonymous URL channels by display name and navigates to their obfuscated routes.
*/
test('channel search finds channels with obfuscated URLs', {tag: '@anonymous_urls'}, async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
// # Log in and create anonymous URL channels
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
const createdChannels = [];
for (let i = 1; i <= 3; i++) {
const displayName = `Search Test Channel ${i} ${await pw.random.id()}`;
const channel = await createAnonymousUrlChannel(channelsPage, adminClient, team.name, team.id, displayName);
createdChannels.push({channel, displayName});
}
const targetChannel = createdChannels[0];
// # Open Find Channels and search by display name
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
await channelsPage.sidebarLeft.findChannelButton.click();
await channelsPage.findChannelsModal.toBeVisible();
await channelsPage.findChannelsModal.input.fill(targetChannel.displayName.substring(0, 15));
// * Verify the anonymous URL channel appears in results
const result = channelsPage.findChannelsModal.getResult(targetChannel.channel.name);
await expect(result).toBeVisible();
await expect(result).toContainText(targetChannel.displayName);
// # Select the matching anonymous URL channel
await channelsPage.findChannelsModal.selectChannel(targetChannel.channel.name);
// * Verify navigation lands on the obfuscated route
await channelsPage.centerView.header.toHaveTitle(targetChannel.displayName);
await expect(channelsPage.page).toHaveURL(`/${team.name}/channels/${targetChannel.channel.name}`);
});
/**
* @objective Verify that post search results navigate back to the correct anonymous URL channel route.
*/
test('navigates post search results back to anonymous URL channels', {tag: '@anonymous_urls'}, async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
// # Log in and create an anonymous URL channel with a searchable post
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
const displayName = `Search Channel ${await pw.random.id()}`;
const channel = await createAnonymousUrlChannel(channelsPage, adminClient, team.name, team.id, displayName);
const message = `AnonymousSearchableMessage${await pw.random.id()}`;
await channelsPage.postMessage(message);
const lastPost = await channelsPage.getLastPost();
const postId = await lastPost.getId();
// # Open message search from another channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
await channelsPage.globalHeader.openSearch();
await channelsPage.searchBox.searchInput.fill(message);
await channelsPage.searchBox.searchInput.press('Enter');
const searchItem = channelsPage.page.getByTestId('search-item-container').first();
// * Verify the post appears in results
await expect(searchItem).toContainText(message, {timeout: 15000});
// # Jump from the search result back to the anonymous URL channel
await searchItem.getByRole('link', {name: 'Jump'}).click();
// * Verify navigation returns to the anonymous URL route and highlights the expected post
await channelsPage.centerView.header.toHaveTitle(displayName);
await expect(channelsPage.page).toHaveURL(new RegExp(`/${team.name}/channels/${channel.name}`));
await channelsPage.centerView.waitUntilPostWithIdContains(postId, message);
});
/**
* @objective Verify that anonymous URLs preserve edge-case channel display names while keeping channels discoverable and fully usable.
*/
test(
'creates anonymous URL channels from unicode and emoji display names and keeps them discoverable',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and enable anonymous URLs
const {user, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
await setAnonymousUrls(adminClient, true);
// # Log in and open the test team
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Create channels with edge-case display names
const channelTemplates = ['Design review 🚀', 'Roadmap 中文', 'Support & Ops عربى'];
const createdChannels = [];
for (const template of channelTemplates) {
const displayName = `${template} ${(await pw.random.id()).slice(0, 6)}`;
const channel = await createAnonymousUrlChannel(
channelsPage,
adminClient,
team.name,
team.id,
displayName,
);
createdChannels.push({channel, displayName});
// # Post in the new anonymous URL channel
const message = `anonymous-url edge case ${displayName}`;
await channelsPage.postMessage(message);
// * Verify the channel keeps the exact display name and remains usable
const lastPost = await channelsPage.getLastPost();
await expect(lastPost.body).toContainText(message);
await channelsPage.centerView.header.toHaveTitle(displayName);
await expect(channelsPage.page).toHaveURL(new RegExp(`/${team.name}/channels/${channel.name}$`));
}
const searchableChannel = createdChannels[1];
// # Reopen the channel from Find Channels using its display name
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
await channelsPage.sidebarLeft.findChannelButton.click();
await channelsPage.findChannelsModal.toBeVisible();
await channelsPage.findChannelsModal.input.fill(searchableChannel.displayName);
// * Verify search results use the display name and navigate back to the obfuscated slug
await expect(channelsPage.findChannelsModal.getResult(searchableChannel.channel.name)).toBeVisible();
await channelsPage.findChannelsModal.selectChannel(searchableChannel.channel.name);
await channelsPage.centerView.header.toHaveTitle(searchableChannel.displayName);
await expect(channelsPage.page).toHaveURL(
new RegExp(`/${team.name}/channels/${searchableChannel.channel.name}$`),
);
},
);
/**
* @objective Verify that anonymous URL channels with special-character names still expose the Calls entry point after direct navigation.
*
* @precondition
* Calls plugin must be installed in the test environment.
*/
test(
'shows calls entry point in anonymous URL channels with special-character names and preserves the obfuscated route',
{tag: '@anonymous_urls'},
async ({pw}) => {
// # Initialize setup and validate Calls prerequisites
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
await skipIfNoAdvancedLicense(adminClient);
const pluginStatuses = await adminClient.getPluginStatuses();
test.skip(
!pluginStatuses.some((plugin: {plugin_id: string}) => plugin.plugin_id === 'com.mattermost.calls'),
'Skipping test - Calls plugin is not installed',
);
await pw.ensurePluginsLoaded(['com.mattermost.calls']);
await pw.shouldHaveCallsEnabled();
await setAnonymousUrls(adminClient, true);
await adminClient.addToTeam(team.id, adminUser.id);
// # Create an anonymous URL channel with a special-character display name
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
const displayName = `Calls & Planning 🚀 ${(await pw.random.id()).slice(0, 6)}`;
const channel = await createAnonymousUrlChannel(channelsPage, adminClient, team.name, team.id, displayName);
await adminClient.addToChannel(adminUser.id, channel.id);
// * Verify the current anonymous URL channel shows the Calls entry point
await channelsPage.centerView.header.toHaveTitle(displayName);
await expect(channelsPage.centerView.header.callButton).toBeVisible();
// # Open the anonymous URL directly as another member
const {channelsPage: otherChannelsPage} = await pw.testBrowser.login(adminUser);
await otherChannelsPage.goto(team.name, channel.name);
await otherChannelsPage.toBeVisible();
// * Verify direct navigation keeps the obfuscated slug and still exposes Calls
await otherChannelsPage.centerView.header.toHaveTitle(displayName);
await expect(otherChannelsPage.page).toHaveURL(new RegExp(`/${team.name}/channels/${channel.name}$`));
await expect(otherChannelsPage.centerView.header.callButton).toBeVisible();
// # Open the Calls entry point from the anonymous URL channel
await otherChannelsPage.centerView.header.openCalls();
// * Verify opening Calls does not break the anonymous URL route context
await otherChannelsPage.centerView.header.toHaveTitle(displayName);
await expect(otherChannelsPage.page).toHaveURL(new RegExp(`/${team.name}/channels/${channel.name}`));
},
);
});