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
27 changes: 27 additions & 0 deletions e2e/specs/menu_bar/clear_all_data.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

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

test(
'clear all data menu item can be cancelled without restarting the app',
{tag: ['@P1', '@all']},
async ({electronApp, mainWindow}) => {
expect(mainWindow).toBeDefined();

const serverButtonText = await mainWindow!.innerText('.ServerDropdownButton');

await stubMessageBoxResponses(electronApp, [{response: 1}]);
try {
await clickApplicationMenuItem(electronApp, 'view', {labelIncludes: 'Clear All Data'});
await expect.poll(
() => mainWindow!.innerText('.ServerDropdownButton'),
{timeout: 10_000, message: 'Canceling Clear All Data should leave the active server unchanged'},
).toBe(serverButtonText);
} finally {
await restoreMessageBox(electronApp);
}
},
);
90 changes: 90 additions & 0 deletions e2e/specs/menu_bar/devtools_current_server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

// ── MM-T821: Toggle Developer Tools for Current Server ────────────────
// Tests opening DevTools for the active server's WebContentsView (the
// embedded view that renders the Mattermost webapp).
//
// Sibling test: specs/menu_bar/view_menu.test.ts :: MM-T820 tests
// DevTools for the Application Wrapper (the main BrowserWindow).
// These are distinct: MM-T820 targets the chrome window, MM-T821
// targets the server content view.

import {test, expect} from '../../fixtures/index';
import {demoMattermostConfig} from '../../helpers/config';
import {loginToMattermost} from '../../helpers/login';
import {clickApplicationMenuItem} from '../../helpers/menu';
import {closeOverlayWindowsIfOpen} from '../../helpers/overlayWindows';
import {prepareMattermostServerView} from '../../helpers/prepareServerView';
import {getActiveServerWebContentsId} from '../../helpers/testRefs';

test.describe('menu_bar/devtools_current_server', () => {
test.use({appConfig: demoMattermostConfig});
test.setTimeout(120_000);

test('MM-T821 Toggle Developer Tools for Current Server in the Menu Bar',
{tag: ['@P2', '@all']},
async ({electronApp, serverMap}) => {
if (!process.env.MM_TEST_SERVER_URL) {
test.skip(true, 'MM_TEST_SERVER_URL required');
return;
}

const serverEntry = serverMap[demoMattermostConfig.servers[0].name]?.[0];
const firstServer = serverEntry?.win;
expect(firstServer, 'Mattermost server view should exist').toBeTruthy();

await closeOverlayWindowsIfOpen(electronApp);
await prepareMattermostServerView(electronApp, serverEntry!.webContentsId);
await loginToMattermost(firstServer!);
await firstServer!.waitForSelector('#sidebarItem_town-square', {timeout: 30_000});

const webContentsId = serverEntry!.webContentsId ?? await getActiveServerWebContentsId(electronApp);

const webContentsExists = await electronApp.evaluate(({webContents}, id) => {
const wc = webContents.fromId(id);
return wc !== undefined && !wc.isDestroyed();
}, webContentsId);
expect(webContentsExists, 'Server webContents should exist').toBe(true);

await clickApplicationMenuItem(
electronApp,
'view',
{label: 'Developer Tools for Current Tab'},
{webContentsId},
);
await expect.poll(
() => electronApp.evaluate(({webContents}, id) => {
const wc = webContents.fromId(id);
return Boolean(wc && !wc.isDestroyed() && wc.isDevToolsOpened());
}, webContentsId),
{timeout: 15_000, message: 'DevTools must open for the current server webContents after menu click'},
).toBe(true);

// Toggle closed instead of closeDevTools() evaluate, which can race with
// DevTools teardown and destabilize the app on Linux CI.
await electronApp.evaluate(({webContents}, id) => {
try {
const wc = webContents.fromId(id);
if (wc && !wc.isDestroyed() && wc.isDevToolsOpened()) {
wc.toggleDevTools();
}
} catch {
// DevTools may already be detaching.
}
}, webContentsId).catch(() => {});
await expect.poll(
() => electronApp.evaluate(({webContents}, id) => {
const wc = webContents.fromId(id);
return wc && !wc.isDestroyed() ? !wc.isDevToolsOpened() : true;
}, webContentsId).catch(() => true),
{timeout: 15_000, message: 'DevTools must close after toggle'},
).toBe(true);

const serverStillFunctional = await firstServer!.evaluate(() => {
return document.querySelector('#post_textbox') !== null;
});
expect(serverStillFunctional, 'Server view should still be functional after DevTools toggle').toBe(true);
},
);
});
33 changes: 33 additions & 0 deletions e2e/specs/menu_bar/diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

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

test(
'DIAG-01 Run diagnostics completes from the Help menu',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
await clickApplicationMenuItem(electronApp, 'help', {id: 'diagnostics'});

await expect.poll(async () => {
return electronApp.evaluate(() => {
const diagnostics = (global as any).__e2eTestRefs?.Diagnostics;
return diagnostics?.isRunning?.() ?? false;
});
}, {
timeout: 30_000,
message: 'Diagnostics.run should start after choosing Help → Run diagnostics',
}).toBe(true);

await expect.poll(async () => {
return electronApp.evaluate(() => {
const diagnostics = (global as any).__e2eTestRefs?.Diagnostics;
return diagnostics?.isRunning?.() ?? true;
});
}, {
timeout: 60_000,
message: 'Diagnostics.run should finish without staying in the running state',
}).toBe(false);
},
);
43 changes: 6 additions & 37 deletions e2e/specs/menu_bar/edit_menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import * as os from 'os';
import * as path from 'path';

import {test, expect} from '../../fixtures/index';
import {waitForAppReady} from '../../helpers/appReadiness';
import {appDir, demoMattermostConfig, electronBinaryPath, writeConfigFile} from '../../helpers/config';
import {waitForWindow, closeElectronApp} from '../../helpers/electronApp';
import {demoMattermostConfig} from '../../helpers/config';
import {launchDirectTestApp} from '../../helpers/directLaunch';
import {waitForWindow, closeElectronAppFast} from '../../helpers/electronApp';
import {loginToMattermost} from '../../helpers/login';
import {buildServerMap} from '../../helpers/serverMap';
import type {ServerView} from '../../helpers/serverView';
Expand Down Expand Up @@ -86,39 +86,8 @@ test.describe('edit_menu', () => {

test.beforeAll(async () => {
userDataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mm-edit-menu-e2e-'));
writeConfigFile(userDataDir, demoMattermostConfig);

const {_electron: electron} = await import('playwright');
electronApp = await electron.launch({
executablePath: electronBinaryPath,
args: [
appDir,
`--user-data-dir=${userDataDir}`,
'--no-sandbox',
'--disable-gpu',
'--disable-gpu-sandbox',
'--disable-dev-shm-usage',
'--no-zygote',
'--disable-software-rasterizer',
'--disable-breakpad',
'--disable-features=SpareRendererForSitePerProcess',
'--disable-features=CrossOriginOpenerPolicy',
'--disable-renderer-backgrounding',
'--force-color-profile=srgb',
'--mute-audio',
],
env: {
...process.env,
NODE_ENV: 'test',
RESOURCES_PATH: appDir,
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
ELECTRON_NO_ATTACH_CONSOLE: 'true',
NODE_OPTIONS: '--no-warnings',
},
timeout: 90_000,
});

await waitForAppReady(electronApp);
electronApp = await launchDirectTestApp(userDataDir, demoMattermostConfig);

mainWindow = await waitForWindow(electronApp, 'index');
const serverMap = await buildServerMap(electronApp);
firstServer = serverMap[demoMattermostConfig.servers[0].name][0].win;
Expand All @@ -134,7 +103,7 @@ test.describe('edit_menu', () => {
});

test.afterAll(async () => {
await closeElectronApp(electronApp, userDataDir);
await closeElectronAppFast(electronApp, userDataDir);
});

test('MM-T807 Undo in the post textbox', {tag: ['@P2', '@all']}, async () => {
Expand Down
33 changes: 5 additions & 28 deletions e2e/specs/menu_bar/file_menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE.txt for license information.

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

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

test('MM-T805 Sign in to Another Server Window opens using menu item', {tag: ['@P2', '@win32']}, async ({electronApp}) => {
if (process.platform !== 'win32') {
test.skip(true, 'Windows-only test');
return;
}

// Invoke the File menu item directly — keyboard presses sent via Playwright
// do not reliably reach popup menus in headless CI on Windows.
await electronApp.evaluate(({app}) => {
const fileMenu = (app as any).applicationMenu?.getMenuItemById('file');
const signInItem = fileMenu?.submenu?.items?.find(
(item: any) => typeof item.label === 'string' && item.label.includes('Sign in'),
);
if (!signInItem) {
throw new Error('Sign in to Another Server menu item not found');
}
signInItem.click();
});
// appReady ensures the application menu is built before clicking File items.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
test('MM-T805 Sign in to Another Server Window opens using menu item', {tag: ['@P2', '@win32']}, async ({electronApp, appReady: _appReady}) => {
await clickApplicationMenuItem(electronApp, 'file', {labelIncludes: 'Sign in'});
const signInToAnotherServerWindow = await electronApp.waitForEvent('window', {
predicate: (window) => window.url().includes('newServer'),
timeout: 15_000,
Expand All @@ -89,23 +76,13 @@ test.describe('file_menu/dropdown', () => {
});

test('MM-T804 Preferences in Menu Bar open the Settings page', {tag: ['@P2', '@win32']}, async ({electronApp}) => {
if (process.platform !== 'win32') {
test.skip(true, 'Windows-only test');
return;
}

// Reuse the existing direct-invocation helper instead of keyboard navigation.
await openPreferencesFromAppMenu(electronApp);
const settingsWindow = await waitForSettingsWindow(electronApp);
expect(settingsWindow).toBeDefined();
});

test('MM-T806 Exit in the Menu Bar', {tag: ['@P2', '@darwin']}, async ({electronApp, mainWindow}) => {
if (process.platform !== 'darwin') {
test.skip(true, 'macOS-only test');
return;
}

expect(mainWindow).toBeDefined();
await mainWindow.waitForLoadState();
await mainWindow.bringToFront();
Expand Down
4 changes: 0 additions & 4 deletions e2e/specs/menu_bar/full_screen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ test.describe('menu/view', () => {
test.use({appConfig: demoMattermostConfig});

test('MM-T816 Toggle Full Screen in the Menu Bar', {tag: ['@P2', '@win32']}, async ({electronApp}) => {
if (process.platform !== 'win32') {
test.skip(true, 'Windows only');
return;
}
if (!process.env.MM_TEST_SERVER_URL) {
test.skip(true, 'MM_TEST_SERVER_URL required');
return;
Expand Down
83 changes: 83 additions & 0 deletions e2e/specs/menu_bar/help_menu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

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

test.describe('menu_bar/help_menu', () => {
test(
'HELP-01 Check for Updates menu item invokes the update manager',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
const canUpgrade = await electronApp.evaluate(() => {
const refs = (global as any).__e2eTestRefs;
return Boolean(refs?.Config?.canUpgrade);
});

if (!canUpgrade) {
test.skip(true, 'Config.canUpgrade is false in this build');
return;
}

await electronApp.evaluate(() => {
const refs = (global as any).__e2eTestRefs;
refs.updateNotifier.__e2eCheckForUpdatesCalls = 0;
refs.updateNotifier.__e2eOriginalCheckForUpdates = refs.updateNotifier.checkForUpdates;
refs.updateNotifier.checkForUpdates = () => {
refs.updateNotifier.__e2eCheckForUpdatesCalls += 1;
};
});

try {
await clickApplicationMenuItem(electronApp, 'help', {labelIncludes: 'Check for Updates'});

await expect.poll(async () => {
return electronApp.evaluate(() => {
const refs = (global as any).__e2eTestRefs;
return refs?.updateNotifier?.__e2eCheckForUpdatesCalls ?? 0;
});
}, {timeout: 10_000}).toBeGreaterThan(0);
} finally {
await electronApp.evaluate(() => {
const refs = (global as any).__e2eTestRefs;
if (refs?.updateNotifier?.__e2eOriginalCheckForUpdates) {
refs.updateNotifier.checkForUpdates = refs.updateNotifier.__e2eOriginalCheckForUpdates;
delete refs.updateNotifier.__e2eOriginalCheckForUpdates;
}
});
}
},
);

test(
'HELP-02 Show logs menu item opens the log file location',
{tag: ['@P1', '@all']},
async ({electronApp}) => {
await electronApp.evaluate(({shell}) => {
(global as any).__e2eShownInFolder = [] as string[];
(global as any).__e2eOriginalShowItemInFolder = shell.showItemInFolder.bind(shell);
shell.showItemInFolder = (fullPath: string) => {
(global as any).__e2eShownInFolder.push(fullPath);
return (global as any).__e2eOriginalShowItemInFolder(fullPath);
};
});

try {
await clickApplicationMenuItem(electronApp, 'help', {id: 'Show logs'});

await expect.poll(async () => {
return electronApp.evaluate(() => ((global as any).__e2eShownInFolder as string[] | undefined)?.length ?? 0);
}, {timeout: 10_000}).toBeGreaterThan(0);
} finally {
await electronApp.evaluate(({shell}) => {
const original = (global as any).__e2eOriginalShowItemInFolder;
if (original) {
shell.showItemInFolder = original;
delete (global as any).__e2eOriginalShowItemInFolder;
}
delete (global as any).__e2eShownInFolder;
});
}
},
);
});
5 changes: 0 additions & 5 deletions e2e/specs/menu_bar/menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import {test, expect} from '../../fixtures/index';

test.describe('menu/menu', () => {
test('MM-T4404 should open the 3 dot menu with Alt', {tag: ['@P2', '@win32']}, async ({electronApp, mainWindow}) => {
if (process.platform === 'darwin') {
test.skip(true, 'No keyboard shortcut for macOS');
return;
}

expect(mainWindow).toBeDefined();

await mainWindow.waitForSelector('button.three-dot-menu');
Expand Down
Loading
Loading