E2E/Playwright: MM-T5424 Find channel limit to 50 results (#23248)

* fix aria-label of Find Channels modal

* add components

* add test for MM-T5424

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Saturnino Abril
2023-05-05 06:20:23 +08:00
коммит произвёл GitHub
родитель 30a053314b
Коммит 8fdbbf3d5b
11 изменённых файлов: 187 добавлений и 30 удалений

Просмотреть файл

@@ -4,25 +4,32 @@
import {getRandomId} from '@e2e-support/util'; import {getRandomId} from '@e2e-support/util';
import {Channel, ChannelType} from '@mattermost/types/channels'; import {Channel, ChannelType} from '@mattermost/types/channels';
export function createRandomChannel( type ChannelInput = {
teamId: string, teamId: string;
name: string, name: string;
displayName: string, displayName: string;
type: ChannelType = 'O', type?: ChannelType;
purpose = '', purpose?: string;
header = '', header?: string;
unique = true unique?: boolean;
): Channel { };
const randomSuffix = getRandomId();
export function createRandomChannel(channelInput: ChannelInput): Channel {
const channel = { const channel = {
team_id: teamId, team_id: channelInput.teamId,
name: unique ? `${name}-${randomSuffix}` : name, name: channelInput.name,
display_name: unique ? `${displayName} ${randomSuffix}` : displayName, display_name: channelInput.displayName,
type, type: channelInput.type || 'O',
purpose, purpose: channelInput.type || '',
header, header: channelInput.type || '',
}; };
if (channelInput.unique) {
const randomSuffix = getRandomId();
channel.name = `${channelInput.name}-${randomSuffix}`;
channel.display_name = `${channelInput.displayName} ${randomSuffix}`;
}
return channel as Channel; return channel as Channel;
} }

Просмотреть файл

@@ -1,8 +1,9 @@
import {test as base, Browser} from '@playwright/test'; import {test as base, Browser, ViewportSize} from '@playwright/test';
import {TestBrowser} from './browser_context'; import {TestBrowser} from './browser_context';
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag'; import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
import {initSetup, getAdminClient} from './server'; import {initSetup, getAdminClient} from './server';
import {isSmallScreen} from './util';
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action'; import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
import {pages} from './ui/pages'; import {pages} from './ui/pages';
import {matchSnapshot} from './visual'; import {matchSnapshot} from './visual';
@@ -15,8 +16,8 @@ type ExtendedFixtures = {
}; };
export const test = base.extend<ExtendedFixtures>({ export const test = base.extend<ExtendedFixtures>({
pw: async ({browser}, use) => { pw: async ({browser, viewport}, use) => {
const pw = new PlaywrightExtended(browser); const pw = new PlaywrightExtended(browser, viewport);
await use(pw); await use(pw);
await pw.testBrowser.close(); await pw.testBrowser.close();
}, },
@@ -48,10 +49,13 @@ class PlaywrightExtended {
// ./ui/pages // ./ui/pages
readonly pages; readonly pages;
// ./util
readonly isSmallScreen;
// ./visual // ./visual
readonly matchSnapshot; readonly matchSnapshot;
constructor(browser: Browser) { constructor(browser: Browser, viewport: ViewportSize | null) {
// ./browser_context // ./browser_context
this.testBrowser = new TestBrowser(browser); this.testBrowser = new TestBrowser(browser);
@@ -73,6 +77,9 @@ class PlaywrightExtended {
// ./ui/pages // ./ui/pages
this.pages = pages; this.pages = pages;
// ./util
this.isSmallScreen = () => isSmallScreen(viewport);
// ./visual // ./visual
this.matchSnapshot = matchSnapshot; this.matchSnapshot = matchSnapshot;
} }

Просмотреть файл

@@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class FindChannelsModal {
readonly container: Locator;
readonly input;
readonly searchList;
constructor(container: Locator) {
this.container = container;
this.input = container.getByRole('textbox', {name: 'quick switch input'});
this.searchList = container.locator('.suggestion-list__item');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {FindChannelsModal};

Просмотреть файл

@@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ChannelsHeaderMobile {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toggleSidebar() {
await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click();
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {ChannelsHeaderMobile};

Просмотреть файл

@@ -0,0 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ChannelsSidebarLeft {
readonly container: Locator;
readonly findChannelButton;
constructor(container: Locator) {
this.container = container;
this.findChannelButton = container.getByRole('button', {name: 'Find Channels'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {ChannelsSidebarLeft};

Просмотреть файл

@@ -3,19 +3,25 @@
import {BoardsSidebar} from './boards/sidebar'; import {BoardsSidebar} from './boards/sidebar';
import {ChannelsHeader} from './channels/header'; import {ChannelsHeader} from './channels/header';
import {ChannelsHeaderMobile} from './channels/header_mobile';
import {ChannelsAppBar} from './channels/app_bar'; import {ChannelsAppBar} from './channels/app_bar';
import {ChannelsPostCreate} from './channels/post_create'; import {ChannelsPostCreate} from './channels/post_create';
import {ChannelsPost} from './channels/post'; import {ChannelsPost} from './channels/post';
import {ChannelsSidebarLeft} from './channels/sidebar_left';
import {ChannelsSidebarRight} from './channels/sidebar_right'; import {ChannelsSidebarRight} from './channels/sidebar_right';
import {FindChannelsModal} from './channels/find_channels_modal';
import {GlobalHeader} from './global_header'; import {GlobalHeader} from './global_header';
const components = { const components = {
BoardsSidebar, BoardsSidebar,
ChannelsAppBar, ChannelsAppBar,
ChannelsHeader, ChannelsHeader,
ChannelsHeaderMobile,
ChannelsPostCreate, ChannelsPostCreate,
ChannelsPost, ChannelsPost,
ChannelsSidebarLeft,
ChannelsSidebarRight, ChannelsSidebarRight,
FindChannelsModal,
GlobalHeader, GlobalHeader,
}; };
@@ -24,8 +30,11 @@ export {
BoardsSidebar, BoardsSidebar,
ChannelsAppBar, ChannelsAppBar,
ChannelsHeader, ChannelsHeader,
ChannelsHeaderMobile,
ChannelsPostCreate, ChannelsPostCreate,
ChannelsPost, ChannelsPost,
ChannelsSidebarLeft,
ChannelsSidebarRight, ChannelsSidebarRight,
FindChannelsModal,
GlobalHeader, GlobalHeader,
}; };

Просмотреть файл

@@ -11,17 +11,23 @@ export default class ChannelsPage {
readonly channels = 'Channels'; readonly channels = 'Channels';
readonly page: Page; readonly page: Page;
readonly postCreate; readonly postCreate;
readonly findChannelsModal;
readonly globalHeader; readonly globalHeader;
readonly header; readonly header;
readonly headerMobile;
readonly appBar; readonly appBar;
readonly sidebarLeft;
readonly sidebarRight; readonly sidebarRight;
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create')); this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create'));
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
this.globalHeader = new components.GlobalHeader(page.locator('#global-header')); this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
this.header = new components.ChannelsHeader(page.locator('.channel-header')); this.header = new components.ChannelsHeader(page.locator('.channel-header'));
this.headerMobile = new components.ChannelsHeaderMobile(page.locator('.navbar'));
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar')); this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right')); this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
} }

Просмотреть файл

@@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture';
import {createRandomChannel} from '@e2e-support/server';
test('MM-T5424 Find channel search returns only 50 results when there are more than 50 channels with similar names', async ({
pw,
pages,
}) => {
const {adminClient, user, team} = await pw.initSetup();
const commonName = 'test_channel';
// # Create more than 50 channels with similar names
const channelsRes = [];
for (let i = 0; i < 100; i++) {
let suffix = i.toString();
if (i < 10) {
suffix = `0${i}`;
}
const channel = createRandomChannel({
teamId: team.id,
name: `${commonName}_${suffix}`,
displayName: `Test Channel ${suffix}`,
});
channelsRes.push(adminClient.createChannel(channel));
}
await Promise.all(channelsRes);
// # Log in a user in new browser context
const {page} = await pw.testBrowser.login(user);
// # Visit a default channel page
const channelsPage = new pages.ChannelsPage(page);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on "Find channel" and type "test_channel"
if (pw.isSmallScreen()) {
await channelsPage.headerMobile.toggleSidebar();
}
await channelsPage.sidebarLeft.findChannelButton.click();
await channelsPage.findChannelsModal.toBeVisible();
await channelsPage.findChannelsModal.input.fill(commonName);
const limitCount = 50;
// # Only 50 results for similar name should be displayed.
await expect(channelsPage.findChannelsModal.searchList).toHaveCount(limitCount);
for (let i = 0; i < limitCount; i++) {
let suffix = i.toString();
if (i < 10) {
suffix = `0${i}`;
}
await expect(channelsPage.findChannelsModal.container.getByTestId(`${commonName}_${suffix}`)).toBeVisible();
}
});

Просмотреть файл

@@ -2,7 +2,6 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture'; import {expect, test} from '@e2e-support/test_fixture';
import {isSmallScreen} from '@e2e-support/util';
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => { test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
// Create and sign in a new user // Create and sign in a new user
@@ -23,7 +22,7 @@ test('Intro to channel as regular user', async ({pw, pages, browserName, viewpor
// await wait(duration.one_sec); // await wait(duration.one_sec);
// Wait for Playbooks icon to be loaded in App bar, except in iphone // Wait for Playbooks icon to be loaded in App bar, except in iphone
if (!isSmallScreen(viewport)) { if (!pw.isSmallScreen()) {
await expect(channelsPage.appBar.playbooksIcon).toBeVisible(); await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
} }

Просмотреть файл

@@ -3,8 +3,8 @@
exports[`components/QuickSwitchModal should match snapshot 1`] = ` exports[`components/QuickSwitchModal should match snapshot 1`] = `
<Modal <Modal
animation={false} animation={false}
aria-describedby="quickSwitchHeader" aria-describedby="quickSwitchHeaderWithHint"
aria-labelledby="quickSwitchModalLabel" aria-labelledby="quickSwitchHeader"
autoFocus={true} autoFocus={true}
backdrop={true} backdrop={true}
bsClass="modal" bsClass="modal"
@@ -42,9 +42,11 @@ exports[`components/QuickSwitchModal should match snapshot 1`] = `
> >
<div <div
className="channel-switcher__header" className="channel-switcher__header"
id="quickSwitchHeader" id="quickSwitchHeaderWithHint"
> >
<h1> <h1
id="quickSwitchHeader"
>
<MemoizedFormattedMessage <MemoizedFormattedMessage
defaultMessage="Find Channels" defaultMessage="Find Channels"
id="quick_switch_modal.switchChannels" id="quick_switch_modal.switchChannels"

Просмотреть файл

@@ -157,7 +157,7 @@ export default class QuickSwitchModal extends React.PureComponent<Props, State>
const providers: SwitchChannelProvider[] = this.channelProviders; const providers: SwitchChannelProvider[] = this.channelProviders;
const header = ( const header = (
<h1> <h1 id='quickSwitchHeader'>
<FormattedMessage <FormattedMessage
id='quick_switch_modal.switchChannels' id='quick_switch_modal.switchChannels'
defaultMessage='Find Channels' defaultMessage='Find Channels'
@@ -190,8 +190,8 @@ export default class QuickSwitchModal extends React.PureComponent<Props, State>
enforceFocus={false} enforceFocus={false}
restoreFocus={false} restoreFocus={false}
role='dialog' role='dialog'
aria-labelledby='quickSwitchModalLabel' aria-labelledby='quickSwitchHeader'
aria-describedby='quickSwitchHeader' aria-describedby='quickSwitchHeaderWithHint'
animation={false} animation={false}
> >
<Modal.Header <Modal.Header
@@ -201,7 +201,7 @@ export default class QuickSwitchModal extends React.PureComponent<Props, State>
<Modal.Body> <Modal.Body>
<div <div
className='channel-switcher__header' className='channel-switcher__header'
id='quickSwitchHeader' id='quickSwitchHeaderWithHint'
> >
{header} {header}
<div <div