Files

1018 lines
48 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, getAdminClient, mergeWithOnPremServerConfig, 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) {
const merged = mergeWithOnPremServerConfig({
PrivacySettings: {UseAnonymousURLs: enabled},
} as unknown as Parameters<typeof mergeWithOnPremServerConfig>[0]);
await adminClient.patchConfig({PrivacySettings: merged.PrivacySettings});
}
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', () => {
// 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}`));
},
);
});