// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {expect, getAdminClient, 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 setAnonymousUrls(adminClient, true); // Wait until the server confirms UseAnonymousURLs=true. // expect.poll gives reliable retry semantics vs. a manual break-loop. await expect .poll( async () => { const cfg = await adminClient.getConfig(); return cfg.PrivacySettings?.UseAnonymousURLs; }, {timeout: 15000, intervals: [500, 1000, 2000]}, ) .toBe(true); // Final re-apply immediately before the UI action to close the race window // between the polling confirmation and the channel-creation POST reaching // the server (the modal open + fill + submit adds ~200–500 ms of latency). await setAnonymousUrls(adminClient, true); 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', () => { // initSetup patches only the on-prem overrides, so PrivacySettings survives from whichever // test ran last. Start every test from the default (off); tests needing it on enable it. test.beforeEach(async () => { const {adminClient} = await getAdminClient({skipLog: true}); await setAnonymousUrls(adminClient, false); }); // Reset PrivacySettings.UseAnonymousURLs to its default (off) at the end // of this file so leftover state does not affect other suites. Tests within // this file explicitly set the value they need at the start of each test. test.afterAll(async () => { try { const {adminClient} = await getAdminClient({skipLog: true}); await adminClient.patchConfig({ PrivacySettings: { UseAnonymousURLs: false, }, }); } catch { // Best-effort cleanup; do not fail the suite if admin client is unavailable. } }); /** * @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'); await expect .poll(async () => (await adminClient.getConfig()).PrivacySettings?.UseAnonymousURLs === true, { timeout: 30_000, intervals: [500, 1500, 3000], }) .toBe(true); // # Navigate away and come back await systemConsolePage.gotoNotificationsSettings(); await systemConsolePage.notifications.toBeVisible(); // Re-apply guard: a concurrent initSetup() → updateConfig(defaultConfig) may have // reset PrivacySettings.UseAnonymousURLs=false while we were navigating away. // Re-patch and confirm propagation before navigating back. await adminClient.patchConfig({PrivacySettings: {UseAnonymousURLs: true}}); await expect .poll(async () => (await adminClient.getConfig()).PrivacySettings?.UseAnonymousURLs === true, { timeout: 20_000, intervals: [500, 1000, 2000], }) .toBe(true); await systemConsolePage.sidebar.siteConfiguration.usersAndTeams.click(); await systemConsolePage.usersAndTeams.toBeVisible(); // Re-apply one more time after the page renders and confirm, so the radio reads the // fresh config (a WebSocket CONFIG_CHANGED event from a concurrent initSetup() can // reset the in-memory Redux state between the poll above and the page rendering). await adminClient.patchConfig({PrivacySettings: {UseAnonymousURLs: true}}); await expect .poll(async () => (await adminClient.getConfig()).PrivacySettings?.UseAnonymousURLs === true, { timeout: 10_000, intervals: [500, 1000], }) .toBe(true); // * 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, 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 adminClient.addToTeam(team.id, adminUser.id); await setAnonymousUrls(adminClient, true); await expect .poll( async () => { const cfg = await adminClient.getConfig(); return cfg.PrivacySettings?.UseAnonymousURLs === true; }, {timeout: 30000, intervals: [500, 1500, 3000]}, ) .toBe(true); // # Log in and go to channels — navigate to the team explicitly so the // # webapp loads its config after UseAnonymousURLs is already true const {channelsPage} = await pw.testBrowser.login(adminUser); await channelsPage.goto(team.name); await channelsPage.toBeVisible(); // Re-apply anonymous URLs immediately before the UI interaction: a // concurrent initSetup() → patchConfig(defaultConfig) resets // UseAnonymousURLs: false between the initial setAnonymousUrls call and here. await setAnonymousUrls(adminClient, true); // # 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 (wait for client config to apply) await expect .poll(async () => !(await channelsPage.newChannelModal.urlSection.isVisible()), { timeout: 30000, intervals: [500, 1500], }) .toBe(true); // # 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 setAnonymousUrls(adminClient, true); 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(); // Re-apply anonymous URLs immediately before the UI interaction: a // concurrent initSetup() → patchConfig(defaultConfig) resets // UseAnonymousURLs: false between the initial setAnonymousUrls call and here. await setAnonymousUrls(adminClient, true); // # 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(); // # Re-apply config in case a concurrent shard reset it during login/navigation await setAnonymousUrls(adminClient, true); const channelDisplayName = `Archived Anonymous ${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-${pw.random.id()}`; const legacyChannelDisplayName = `Legacy Channel ${pw.random.id()}`; const legacyChannel = await adminClient.createChannel({ team_id: team.id, name: legacyChannelSlug, display_name: legacyChannelDisplayName, type: 'O', }); const legacyTeamSlug = `legacy-team-${pw.random.id()}`; const legacyTeamDisplayName = `Legacy Team ${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. // Re-apply config immediately before the UI action: a concurrent initSetup() // on another shard can reset UseAnonymousURLs between the patch above and here. const anonymousChannelDisplayName = `Anonymous Channel ${pw.random.id()}`; await channelsPage.goto(team.name); await channelsPage.toBeVisible(); await setAnonymousUrls(adminClient, true); await pw.waitUntil(async () => { const cfg = await adminClient.getConfig(); return (cfg as any).PrivacySettings?.UseAnonymousURLs === true; }); 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. // Re-apply again: team creation is a separate UI flow and config may have drifted. const anonymousTeamDisplayName = `Anonymous Team ${pw.random.id()}`; await setAnonymousUrls(adminClient, true); 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 ${pw.random.id()}`; const gmMessage = `Anonymous URL GM ${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 ${pw.random.id()}`; // Re-apply guard: concurrent initSetup() may reset UseAnonymousURLs before channel creation await setAnonymousUrls(adminClient, true); await pw.waitUntil(async () => { const cfg = await adminClient.getConfig(); return (cfg as any).PrivacySettings?.UseAnonymousURLs === true; }); 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 ${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 ${pw.random.id()}`; // Re-apply guard: concurrent initSetup() may reset UseAnonymousURLs: false // between the initial setAnonymousUrls call above and the team creation UI flow. await setAnonymousUrls(adminClient, true); 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 ${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 ${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 ${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 navigate to the test team so the webapp loads config with // # UseAnonymousURLs already true; explicit addToTeam guards against race resets await adminClient.addToTeam(team.id, user.id); const {channelsPage} = await pw.testBrowser.login(user); await channelsPage.goto(team.name); await channelsPage.toBeVisible(); // # Re-apply config immediately before channel creation to guard against // # concurrent shard initSetup calls resetting UseAnonymousURLs to false await setAnonymousUrls(adminClient, true); const createdChannels = []; for (let i = 1; i <= 3; i++) { const displayName = `Search Test Channel ${i} ${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 ${pw.random.id()}`; const channel = await createAnonymousUrlChannel(channelsPage, adminClient, team.name, team.id, displayName); const message = `AnonymousSearchableMessage${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} ${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 🚀 ${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}`)); }, ); });