Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/specs/menu_bar/clear_all_data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {restoreMessageBox, stubMessageBoxResponses} from '../../helpers/dialog';
import {clickApplicationMenuItem} from '../../helpers/menu';

test(
'clear all data menu item can be cancelled without restarting the app',
'MM-T6148 clear all data menu item can be cancelled without restarting the app',
{tag: ['@P1', '@all']},
async ({electronApp, mainWindow}) => {
expect(mainWindow).toBeDefined();
Expand Down
2 changes: 1 addition & 1 deletion e2e/specs/menu_bar/diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {test, expect} from '../../fixtures/index';
import {clickApplicationMenuItem} from '../../helpers/menu';

test(
'DIAG-01 Run diagnostics completes from the Help menu',
'MM-T6149 Run diagnostics completes from the Help menu',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
await clickApplicationMenuItem(electronApp, 'help', {id: 'diagnostics'});
Expand Down
15 changes: 14 additions & 1 deletion e2e/specs/menu_bar/file_menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See LICENSE.txt for license information.

import {test, expect} from '../../fixtures/index';
import {clickApplicationMenuItem} from '../../helpers/menu';
import {clickApplicationMenuItem, openSignInToAnotherServerModal} from '../../helpers/menu';

async function openPreferencesFromAppMenu(electronApp: Awaited<ReturnType<typeof import('playwright')['_electron']['launch']>>) {
await electronApp.evaluate(async ({app}) => {
Expand Down Expand Up @@ -82,6 +82,19 @@ test.describe('file_menu/dropdown', () => {
expect(settingsWindow).toBeDefined();
});

test(
'MM-T1319 Sign in to Another Server — server name input should be focused',
{tag: ['@P2', '@all']},
async ({electronApp}) => {
const newServerWindow = await openSignInToAnotherServerModal(electronApp);
await newServerWindow.waitForLoadState();

await expect.poll(async () => {
return newServerWindow.evaluate(() => document.activeElement?.id ?? null);
}, {timeout: 10_000}).toBe('serverUrlInput');
},
);

test('MM-T806 Exit in the Menu Bar', {tag: ['@P2', '@darwin']}, async ({electronApp, mainWindow}) => {
expect(mainWindow).toBeDefined();
await mainWindow.waitForLoadState();
Expand Down
34 changes: 32 additions & 2 deletions e2e/specs/menu_bar/help_menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// See LICENSE.txt for license information.

import {test, expect} from '../../fixtures/index';
import {waitForAppReady} from '../../helpers/appReadiness';
import {clickApplicationMenuItem} from '../../helpers/menu';
import {evaluateInMainProcess} from '../../helpers/testRefs';

test.describe('menu_bar/help_menu', () => {
test(
'HELP-01 Check for Updates menu item invokes the update manager',
'MM-T6150 Check for Updates menu item invokes the update manager',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
const canUpgrade = await electronApp.evaluate(() => {
Expand Down Expand Up @@ -54,7 +56,35 @@ test.describe('menu_bar/help_menu', () => {
);

test(
'HELP-02 Show logs menu item opens the log file location',
'MM-T4804 Copy version string into clipboard',
{tag: ['@P2', '@all']},
async ({electronApp}) => {
await waitForAppReady(electronApp);

const expectedVersionLabel = await evaluateInMainProcess(electronApp, ({app: electronAppInstance}) => {
const helpMenu = electronAppInstance.applicationMenu?.getMenuItemById('help');
const versionItem = helpMenu?.submenu?.items?.find((item) => {
return typeof item.label === 'string' && item.label.includes('Desktop App Version');
});
return typeof versionItem?.label === 'string' ? versionItem.label : '';
});
expect(expectedVersionLabel, 'Help menu must expose a Desktop App Version item').not.toBe('');

await electronApp.evaluate(({clipboard}) => {
clipboard.writeText('');
});

await clickApplicationMenuItem(electronApp, 'help', {labelIncludes: 'Desktop App Version'});

await expect.poll(
() => electronApp.evaluate(({clipboard}) => clipboard.readText()),
{timeout: 10_000, message: 'Help → Version must copy the desktop version string to the clipboard'},
).toBe(expectedVersionLabel);
},
);

test(
'MM-T6151 Show logs menu item opens the log file location',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
await electronApp.evaluate(({shell}) => {
Expand Down
89 changes: 73 additions & 16 deletions e2e/specs/menu_bar/history_menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,96 @@

import {test, expect} from '../../fixtures/index';
import {demoMattermostConfig} from '../../helpers/config';
import {clickHistoryMenuItem} from '../../helpers/historyMenu';
import {loginToMattermost} from '../../helpers/login';
import {activateServerEntry, getServerEntry} from '../../helpers/serverContext';
import {buildServerMap} from '../../helpers/serverMap';
import type {ServerView} from '../../helpers/serverView';

async function expectChannelTitle(serverWin: ServerView, title: string): Promise<void> {
await expect.poll(
async () => serverWin.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim()),
{timeout: 10_000},
).toBe(title);
}

test.describe('history_menu', () => {
test.use({appConfig: demoMattermostConfig});
test.skip(!process.env.MM_TEST_SERVER_URL, 'MM_TEST_SERVER_URL required');

test('Click back and forward from history', {tag: ['@P2', '@all']}, async ({electronApp}) => {
const serverMap = await buildServerMap(electronApp);
const firstServer = serverMap[demoMattermostConfig.servers[0].name][0].win;
await loginToMattermost(firstServer);
await firstServer.waitForSelector('#sidebarItem_off-topic');
const entry = getServerEntry(serverMap, demoMattermostConfig.servers[0].name);
await activateServerEntry(electronApp, entry);
await loginToMattermost(entry.win);
await entry.win.waitForSelector('#sidebarItem_off-topic');

// Click on Off-Topic channel
await firstServer.click('#sidebarItem_off-topic');
await entry.win.click('#sidebarItem_off-topic');
await expectChannelTitle(entry.win, 'Off-Topic');

// Click on Town Square channel
await firstServer.click('#sidebarItem_town-square');
await firstServer.locator('[aria-label="Back"]').click();
await entry.win.click('#sidebarItem_town-square');
await expectChannelTitle(entry.win, 'Town Square');
await entry.win.locator('[aria-label="Back"]').click();

// Wait for navigation
await firstServer.waitForSelector('#channelHeaderTitle');
await entry.win.waitForSelector('#channelHeaderTitle');

// Get channel header text
let channelHeaderText = await firstServer.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim());
let channelHeaderText = await entry.win.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim());
expect(channelHeaderText).toBe('Off-Topic');

await firstServer.locator('[aria-label="Forward"]').click();
await entry.win.locator('[aria-label="Forward"]').click();

// Wait for navigation
await firstServer.waitForSelector('#channelHeaderTitle');
await entry.win.waitForSelector('#channelHeaderTitle');

channelHeaderText = await firstServer.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim());
channelHeaderText = await entry.win.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim());
expect(channelHeaderText).toBe('Town Square');
});

test(
'MM-T822 History → Back in the Menu Bar navigates to previous page',
{tag: ['@P2', '@all']},
async ({electronApp}) => {
const serverMap = await buildServerMap(electronApp);
const entry = getServerEntry(serverMap, demoMattermostConfig.servers[0].name);
await activateServerEntry(electronApp, entry);
await loginToMattermost(entry.win);
await entry.win.waitForSelector('#sidebarItem_off-topic');

await entry.win.click('#sidebarItem_off-topic');
await expectChannelTitle(entry.win, 'Off-Topic');
const offTopicTitle = await entry.win.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim());

await entry.win.click('#sidebarItem_town-square');
await expectChannelTitle(entry.win, 'Town Square');

await clickHistoryMenuItem(electronApp, 'Back', entry.webContentsId);

await expect.poll(
async () => entry.win.$eval('#channelHeaderTitle', (el) => (el as HTMLElement).textContent?.trim()),
{timeout: 10_000, message: 'Should navigate back to Off-Topic after History → Back'},
).toBe(offTopicTitle);
},
);

test(
'MM-T823 History → Forward in the Menu Bar navigates to next page',
{tag: ['@P2', '@all']},
async ({electronApp}) => {
const serverMap = await buildServerMap(electronApp);
const entry = getServerEntry(serverMap, demoMattermostConfig.servers[0].name);
await activateServerEntry(electronApp, entry);
await loginToMattermost(entry.win);
await entry.win.waitForSelector('#sidebarItem_off-topic');

await entry.win.click('#sidebarItem_off-topic');
await expectChannelTitle(entry.win, 'Off-Topic');
await entry.win.click('#sidebarItem_town-square');
await expectChannelTitle(entry.win, 'Town Square');

await clickHistoryMenuItem(electronApp, 'Back', entry.webContentsId);
await expectChannelTitle(entry.win, 'Off-Topic');

await clickHistoryMenuItem(electronApp, 'Forward', entry.webContentsId);
await expectChannelTitle(entry.win, 'Town Square');
},
);
});
35 changes: 35 additions & 0 deletions e2e/specs/menu_bar/menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,39 @@ test.describe('menu/menu', () => {
});
expect(settingsWindow).toBeDefined();
});

test(
'MM-T4803 Open Servers Menu using keyboard shortcuts',
{tag: ['@P2', '@all']},
async ({electronApp, mainWindow}) => {
expect(mainWindow).toBeDefined();

const clicked = await electronApp.evaluate(({Menu}) => {
const root = Menu.getApplicationMenu();
if (!root) {
return false;
}
const stack = [...root.items];
while (stack.length) {
const item = stack.shift()!;
if (item.label === 'Show Servers') {
item.click();
return true;
}
if (item.submenu) {
stack.push(...item.submenu.items);
}
}
return false;
});
expect(clicked, '"Show Servers" menu item must exist and be clickable').toBe(true);

const dropdownWindow = electronApp.windows().find((w) => w.url().includes('dropdown')) ??
await electronApp.waitForEvent('window', {
predicate: (w) => w.url().includes('dropdown'),
timeout: 10_000,
});
expect(dropdownWindow, 'Server dropdown window must appear after Show Servers menu click').toBeDefined();
},
);
});
77 changes: 77 additions & 0 deletions e2e/specs/menu_bar/quit_menu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import type {ElectronApplication} from 'playwright';

import {test, expect} from '../../fixtures/index';
import {clickApplicationMenuItem} from '../../helpers/menu';

async function clickQuitFromMenuBar(electronApp: ElectronApplication) {
const menuId = process.platform === 'darwin' ? 'app' : 'file';

const quitExists = await electronApp.evaluate(({app, Menu}) => {
const rootMenu = app.applicationMenu ?? Menu.getApplicationMenu();
const hasQuit = (items: Electron.MenuItem[]): boolean => {
return items.some((item) => item.role === 'quit' || (item.submenu?.items?.length && hasQuit(item.submenu.items)));
};
return hasQuit(rootMenu?.items ?? []);
});
expect(quitExists, 'Application menu must expose a Quit item').toBe(true);

try {
await clickApplicationMenuItem(electronApp, menuId, {role: 'quit'});
} catch {
await electronApp.evaluate(({app, Menu, BrowserWindow}) => {
const rootMenu = app.applicationMenu ?? Menu.getApplicationMenu();
const targetWindow = BrowserWindow.getFocusedWindow() ??
BrowserWindow.getAllWindows().find((window) => !window.isDestroyed());

const clickQuit = (items: Electron.MenuItem[]): boolean => {
for (const item of items) {
if (item.role === 'quit' && typeof item.click === 'function') {
item.click(undefined, targetWindow ?? undefined, undefined);
return true;
}
if (item.submenu?.items?.length && clickQuit(item.submenu.items)) {
return true;
}
}
return false;
};

if (!clickQuit(rootMenu?.items ?? [])) {
throw new Error('Quit menu item not found');
}
});
}
}

async function waitForAppClose(electronApp: ElectronApplication, timeoutMs: number): Promise<boolean> {
try {
await electronApp.waitForEvent('close', {timeout: timeoutMs});
return true;
} catch {
return false;
}
}

test.describe('menu_bar/quit_menu', () => {
test(
'MM-T1668 Quit the app from the menu bar',
{tag: ['@P2', '@all']},
async ({electronApp}) => {
await clickQuitFromMenuBar(electronApp);

let closed = await waitForAppClose(electronApp, 5_000);
if (!closed) {
// Role-based menu clicks may not terminate the app under Playwright on macOS.
await electronApp.evaluate(({ipcMain}) => {
ipcMain.emit('quit', null, 'menu-bar-e2e', '');
});
closed = await waitForAppClose(electronApp, 15_000);
}

expect(closed, 'Quit must close the Electron application').toBe(true);
},
);
});
Loading
Loading