diff --git a/e2e/specs/menu_bar/clear_all_data.test.ts b/e2e/specs/menu_bar/clear_all_data.test.ts new file mode 100644 index 00000000000..f6280b03f4f --- /dev/null +++ b/e2e/specs/menu_bar/clear_all_data.test.ts @@ -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); + } + }, +); diff --git a/e2e/specs/menu_bar/devtools_current_server.test.ts b/e2e/specs/menu_bar/devtools_current_server.test.ts new file mode 100644 index 00000000000..7c57a355c20 --- /dev/null +++ b/e2e/specs/menu_bar/devtools_current_server.test.ts @@ -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); + }, + ); +}); diff --git a/e2e/specs/menu_bar/diagnostics.test.ts b/e2e/specs/menu_bar/diagnostics.test.ts new file mode 100644 index 00000000000..d531546d617 --- /dev/null +++ b/e2e/specs/menu_bar/diagnostics.test.ts @@ -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); + }, +); diff --git a/e2e/specs/menu_bar/edit_menu.test.ts b/e2e/specs/menu_bar/edit_menu.test.ts index 976a46aa78f..7f80fe2912e 100644 --- a/e2e/specs/menu_bar/edit_menu.test.ts +++ b/e2e/specs/menu_bar/edit_menu.test.ts @@ -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'; @@ -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; @@ -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 () => { diff --git a/e2e/specs/menu_bar/file_menu.test.ts b/e2e/specs/menu_bar/file_menu.test.ts index 523318baeeb..fa16f8ff822 100644 --- a/e2e/specs/menu_bar/file_menu.test.ts +++ b/e2e/specs/menu_bar/file_menu.test.ts @@ -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>) { await electronApp.evaluate(async ({app}) => { @@ -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, @@ -89,11 +76,6 @@ 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); @@ -101,11 +83,6 @@ test.describe('file_menu/dropdown', () => { }); 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(); diff --git a/e2e/specs/menu_bar/full_screen.test.ts b/e2e/specs/menu_bar/full_screen.test.ts index aff14c7af0a..fb66b69aa6f 100644 --- a/e2e/specs/menu_bar/full_screen.test.ts +++ b/e2e/specs/menu_bar/full_screen.test.ts @@ -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; diff --git a/e2e/specs/menu_bar/help_menu.test.ts b/e2e/specs/menu_bar/help_menu.test.ts new file mode 100644 index 00000000000..62f4c998f77 --- /dev/null +++ b/e2e/specs/menu_bar/help_menu.test.ts @@ -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; + }); + } + }, + ); +}); diff --git a/e2e/specs/menu_bar/menu.test.ts b/e2e/specs/menu_bar/menu.test.ts index 8378ebd1279..8835872e848 100644 --- a/e2e/specs/menu_bar/menu.test.ts +++ b/e2e/specs/menu_bar/menu.test.ts @@ -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'); diff --git a/e2e/specs/menu_bar/view_menu.test.ts b/e2e/specs/menu_bar/view_menu.test.ts index 3f183ca5e15..421db759b30 100644 --- a/e2e/specs/menu_bar/view_menu.test.ts +++ b/e2e/specs/menu_bar/view_menu.test.ts @@ -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 {clickApplicationMenuItem} from '../../helpers/menu'; import {buildServerMap} from '../../helpers/serverMap'; @@ -117,39 +117,8 @@ test.describe('menu/view', () => { test.beforeAll(async () => { userDataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mm-view-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, - }); + electronApp = await launchDirectTestApp(userDataDir, demoMattermostConfig); - await waitForAppReady(electronApp); mainWindow = await waitForWindow(electronApp, 'index'); const serverMap = await buildServerMap(electronApp); const firstServer = serverMap[demoMattermostConfig.servers[0].name][0].win; @@ -163,7 +132,7 @@ test.describe('menu/view', () => { }); test.afterAll(async () => { - await closeElectronApp(electronApp, userDataDir); + await closeElectronAppFast(electronApp, userDataDir); }); test('MM-T813 Control+F should focus the search bar in Mattermost', {tag: ['@P2', '@all']}, async () => { @@ -264,11 +233,6 @@ test.describe('menu/view', () => { }); test('MM-T820 should open Developer Tools For Application Wrapper for main window', {tag: ['@P2', '@darwin', '@win32']}, async () => { - if (process.platform === 'linux') { - test.skip(true, 'Linux not supported'); - return; - } - const browserWindow = await electronApp.browserWindow(mainWindow); let isDevToolsOpen = await browserWindow.evaluate((window) => { diff --git a/e2e/specs/menu_bar/window_menu.test.ts b/e2e/specs/menu_bar/window_menu.test.ts index 51facdb2d57..af9befe171b 100644 --- a/e2e/specs/menu_bar/window_menu.test.ts +++ b/e2e/specs/menu_bar/window_menu.test.ts @@ -7,10 +7,12 @@ import * as path from 'path'; import {test, expect} from '../../fixtures/index'; import {waitForAppReady} from '../../helpers/appReadiness'; -import {waitForLockFileRelease} from '../../helpers/cleanup'; -import {buildServerMap} from '../../helpers/serverMap'; import {appDir, demoMattermostConfig, electronBinaryPath, writeConfigFile} from '../../helpers/config'; +import {closeDownloadsDropdownIfOpen} from '../../helpers/downloadsDropdown'; +import {closeElectronAppFast, registerElectronMainProcess, waitForWindow} from '../../helpers/electronApp'; import {loginToMattermost} from '../../helpers/login'; +import {waitForMattermostShell} from '../../helpers/mattermostShell'; +import {buildServerMap} from '../../helpers/serverMap'; const windowMenuConfig = { ...demoMattermostConfig, @@ -35,56 +37,6 @@ let mainWindow: ElectronPage; let serverMap: Awaited>; let userDataDir: string; -async function waitForWindow(app: ElectronApplication, pattern: string, timeout = 30_000) { - const timeoutAt = Date.now() + timeout; - while (Date.now() < timeoutAt) { - const win = app.windows().find((window) => { - try { - return window.url().includes(pattern); - } catch { - return false; - } - }); - - if (win) { - await win.waitForLoadState().catch(() => {}); - return win; - } - - await new Promise((resolve) => setTimeout(resolve, 200)); - } - - throw new Error(`Timed out waiting for window matching "${pattern}"`); -} - -async function closeElectronApp(app: ElectronApplication, dataDir: string) { - let pid: number | undefined; - try { - pid = app.process()?.pid; - } catch { - pid = undefined; - } - - let cleanClosed = false; - await Promise.race([ - app.close().catch(() => {}).then(() => { - cleanClosed = true; - }), - new Promise((resolve) => setTimeout(resolve, 10_000)), - ]); - - if (!cleanClosed && pid) { - try { - process.kill(pid, 'SIGTERM'); - } catch { - // already exited - } - return; - } - - await waitForLockFileRelease(dataDir).catch(() => {}); -} - async function clickWindowMenuItem( app: ElectronApplication, matcher: {label?: string; labelIncludes?: string; accelerator?: string; role?: string}, @@ -242,6 +194,7 @@ async function focusMainWindow() { } async function resetWindowMenuState() { + await closeDownloadsDropdownIfOpen(electronApp); await focusMainWindow(); const resetResult = await evaluateWithRetry(electronApp, () => { const refs = (global as any).__e2eTestRefs; @@ -277,17 +230,42 @@ async function createExtraTabs() { await mainWindow.click('#newTabButton'); await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(3)', {timeout: 15_000}); - // Wait until WebContentsManager has registered all 3 views const serverName = windowMenuConfig.servers[0].name; let map = await buildServerMap(electronApp); - const deadline = Date.now() + 15_000; + const deadline = Date.now() + 30_000; while ((map[serverName]?.length ?? 0) < 3 && Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 200)); map = await buildServerMap(electronApp); } + expect(map[serverName]?.length, 'Three Mattermost tabs should be registered').toBeGreaterThanOrEqual(3); return map; } +async function getTabView(tabIndex: number) { + const serverName = windowMenuConfig.servers[0].name; + const deadline = Date.now() + 30_000; + while (Date.now() < deadline) { + const map = await buildServerMap(electronApp); + const view = map[serverName]?.[tabIndex]?.win; + if (view) { + return view; + } + await new Promise((resolve) => setTimeout(resolve, 200)); + } + + throw new Error(`Mattermost tab view at index ${tabIndex} should exist`); +} + +async function switchToTab(tabNumber: number) { + const tab = await mainWindow.waitForSelector( + `.TabBar li.serverTabItem:nth-child(${tabNumber})`, + {timeout: 15_000}, + ); + await tab.click(); + await focusMainWindow(); + return getTabView(tabNumber - 1); +} + test.describe('Menu/window_menu', () => { test.beforeAll(async () => { if (!process.env.MM_TEST_SERVER_URL) { @@ -328,6 +306,8 @@ test.describe('Menu/window_menu', () => { timeout: 90_000, }); + registerElectronMainProcess(electronApp.process()?.pid); + await waitForAppReady(electronApp); mainWindow = await waitForWindow(electronApp, 'index'); serverMap = await buildServerMap(electronApp); @@ -341,7 +321,10 @@ test.describe('Menu/window_menu', () => { }); test.afterAll(async () => { - await closeElectronApp(electronApp, userDataDir); + if (!electronApp) { + return; + } + await closeElectronAppFast(electronApp, userDataDir); }); test.describe('MM-T826 should switch to servers when keyboard shortcuts are pressed', () => { @@ -368,21 +351,15 @@ test.describe('Menu/window_menu', () => { test.describe('MM-T4385 select tab from menu', () => { test('MM-T4385_1 should show the second tab', {tag: ['@P2', '@all']}, async () => { - const updatedServerMap = await createExtraTabs(); - - const secondTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(2)', {timeout: 15_000}); - await secondTab.click(); - const secondView = updatedServerMap[windowMenuConfig.servers[0].name]?.[1]?.win; - expect(secondView, 'Second Mattermost tab should exist').toBeTruthy(); - await secondView!.waitForSelector('#sidebarItem_off-topic', {timeout: 15_000}); - await secondView!.click('#sidebarItem_off-topic'); - - const thirdTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(3)', {timeout: 15_000}); - await thirdTab.click(); - const thirdView = updatedServerMap[windowMenuConfig.servers[0].name]?.[2]?.win; - expect(thirdView, 'Third Mattermost tab should exist').toBeTruthy(); - await thirdView!.waitForSelector('#sidebarItem_town-square', {timeout: 15_000}); - await thirdView!.click('#sidebarItem_town-square'); + await createExtraTabs(); + + const secondView = await switchToTab(2); + await waitForMattermostShell(secondView, {channelItem: '#sidebarItem_off-topic'}); + await secondView.click('#sidebarItem_off-topic'); + + const thirdView = await switchToTab(3); + await waitForMattermostShell(thirdView); + await thirdView.click('#sidebarItem_town-square'); // Tab title updates asynchronously after channel navigation — poll for it. await expect(mainWindow.locator('.active')).toContainText('Town Square', {timeout: 10_000}); @@ -392,21 +369,15 @@ test.describe('Menu/window_menu', () => { }); test('MM-T4385_2 should show the third tab', {tag: ['@P2', '@all']}, async () => { - const updatedServerMap = await createExtraTabs(); - - const secondTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(2)', {timeout: 15_000}); - await secondTab.click(); - const secondView = updatedServerMap[windowMenuConfig.servers[0].name]?.[1]?.win; - expect(secondView, 'Second Mattermost tab should exist').toBeTruthy(); - await secondView!.waitForSelector('#sidebarItem_off-topic', {timeout: 15_000}); - await secondView!.click('#sidebarItem_off-topic'); - - const thirdTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(3)', {timeout: 15_000}); - await thirdTab.click(); - const thirdView = updatedServerMap[windowMenuConfig.servers[0].name]?.[2]?.win; - expect(thirdView, 'Third Mattermost tab should exist').toBeTruthy(); - await thirdView!.waitForSelector('#sidebarItem_town-square', {timeout: 15_000}); - await thirdView!.click('#sidebarItem_town-square'); + await createExtraTabs(); + + const secondView = await switchToTab(2); + await waitForMattermostShell(secondView, {channelItem: '#sidebarItem_off-topic'}); + await secondView.click('#sidebarItem_off-topic'); + + const thirdView = await switchToTab(3); + await waitForMattermostShell(thirdView); + await thirdView.click('#sidebarItem_town-square'); await clickWindowMenuItem(electronApp, {accelerator: 'CmdOrCtrl+2'}); await clickWindowMenuItem(electronApp, {accelerator: 'CmdOrCtrl+3'}); @@ -414,21 +385,15 @@ test.describe('Menu/window_menu', () => { }); test('MM-T4385_3 should show the first tab', {tag: ['@P2', '@all']}, async () => { - const updatedServerMap = await createExtraTabs(); - - const secondTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(2)', {timeout: 15_000}); - await secondTab.click(); - const secondView = updatedServerMap[windowMenuConfig.servers[0].name]?.[1]?.win; - expect(secondView, 'Second Mattermost tab should exist').toBeTruthy(); - await secondView!.waitForSelector('#sidebarItem_off-topic', {timeout: 15_000}); - await secondView!.click('#sidebarItem_off-topic'); - - const thirdTab = await mainWindow.waitForSelector('.TabBar li.serverTabItem:nth-child(3)', {timeout: 15_000}); - await thirdTab.click(); - const thirdView = updatedServerMap[windowMenuConfig.servers[0].name]?.[2]?.win; - expect(thirdView, 'Third Mattermost tab should exist').toBeTruthy(); - await thirdView!.waitForSelector('#sidebarItem_town-square', {timeout: 15_000}); - await thirdView!.click('#sidebarItem_town-square'); + await createExtraTabs(); + + const secondView = await switchToTab(2); + await waitForMattermostShell(secondView, {channelItem: '#sidebarItem_off-topic'}); + await secondView.click('#sidebarItem_off-topic'); + + const thirdView = await switchToTab(3); + await waitForMattermostShell(thirdView); + await thirdView.click('#sidebarItem_town-square'); await clickWindowMenuItem(electronApp, {accelerator: 'CmdOrCtrl+2'}); await clickWindowMenuItem(electronApp, {accelerator: 'CmdOrCtrl+1'}); @@ -457,10 +422,6 @@ test.describe('Menu/window_menu', () => { }); test('MM-T824 should be minimized when keyboard shortcuts are pressed', {tag: ['@P2', '@darwin', '@win32']}, async () => { - if (process.platform === 'linux') { - test.skip(true, 'Linux not supported'); - return; - } const browserWindow = await electronApp.browserWindow(mainWindow); // Both macOS and Windows: invoke minimize() directly on the BrowserWindow. @@ -481,10 +442,6 @@ test.describe('Menu/window_menu', () => { // Ctrl+Shift+W closes the window, and closing the main window with // minimizeToTray=false shows a quit confirmation dialog rather than hiding it. // So this behavior is only meaningful (and only passes) on macOS. - if (process.platform !== 'darwin') { - test.skip(true, 'App hide is macOS-only'); - return; - } const browserWindow = await electronApp.browserWindow(mainWindow); // macOS: app.hide() hides all windows without closing (Cmd+H behavior) diff --git a/e2e/specs/permissions/permissions_ipc.test.ts b/e2e/specs/permissions/permissions_ipc.test.ts index 5ce9e7b92cf..4e790550098 100644 --- a/e2e/specs/permissions/permissions_ipc.test.ts +++ b/e2e/specs/permissions/permissions_ipc.test.ts @@ -39,12 +39,7 @@ async function openSettingsWindow(electronApp: ElectronApplication) { } test.describe('permissions/ipc', () => { - test('E2E-P01: should return a valid media access status via GET_MEDIA_ACCESS_STATUS IPC', {tag: ['@P2', '@all']}, async ({electronApp}) => { - if (process.platform === 'linux') { - test.skip(true, 'systemPreferences.getMediaAccessStatus is not available on Linux'); - return; - } - + test('E2E-P01: should return a valid media access status via GET_MEDIA_ACCESS_STATUS IPC', {tag: ['@P2', '@darwin', '@win32']}, async ({electronApp}) => { const settingsWindow = await openSettingsWindow(electronApp); const status = await settingsWindow.evaluate( @@ -53,12 +48,7 @@ test.describe('permissions/ipc', () => { expect(['granted', 'denied', 'not-determined', 'restricted', 'unknown']).toContain(status); }); - test('E2E-P02: should open ms-settings:privacy-webcam for camera preferences (Windows only)', {tag: ['@P2', '@all', '@win32']}, async ({electronApp}) => { - if (process.platform !== 'win32') { - test.skip(true, 'Windows only'); - return; - } - + test('E2E-P02: should open ms-settings:privacy-webcam for camera preferences (Windows only)', {tag: ['@P2', '@win32']}, async ({electronApp}) => { const settingsWindow = await openSettingsWindow(electronApp); await electronApp.evaluate(({shell}) => { @@ -79,12 +69,7 @@ test.describe('permissions/ipc', () => { expect(capturedURL).toBe('ms-settings:privacy-webcam'); }); - test('E2E-P03: should open ms-settings:privacy-microphone for microphone preferences (Windows only)', {tag: ['@P2', '@all', '@win32']}, async ({electronApp}) => { - if (process.platform !== 'win32') { - test.skip(true, 'Windows only'); - return; - } - + test('E2E-P03: should open ms-settings:privacy-microphone for microphone preferences (Windows only)', {tag: ['@P2', '@win32']}, async ({electronApp}) => { const settingsWindow = await openSettingsWindow(electronApp); await electronApp.evaluate(({shell}) => {