-
Notifications
You must be signed in to change notification settings - Fork 972
test(e2e): Notifications, focus, Calls, menu bar, and permissions #3862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
db20b56
E2E: Playwright harness, fixtures, and CI wiring (1/10).
yasserfaraazkhan f46ebbc
E2E: Main-process test hooks and shared launch helpers (2/10).
yasserfaraazkhan c1a0bea
E2E: Notifications, focus, and Calls specs (8/10).
yasserfaraazkhan 28ce357
Merge origin/master into e2e/08-notifications-focus-calls
yasserfaraazkhan d9cc1ba
Rename triggerFlashEffects to triggerNotificationEffects in specs
yasserfaraazkhan 081a3b5
E2E: Menu bar and permissions IPC specs (9/10). (#3863)
yasserfaraazkhan 3c11505
Address CodeRabbit review on notifications, menu bar, and calls E2E s…
yasserfaraazkhan dd9ae3a
fix(e2e): fix duplicate cross-PR bugs, unsafe private-field cast, bar…
yasserfaraazkhan bad91be
fix(e2e): restore downloadsDropdown helper and address CodeRabbit nit…
yasserfaraazkhan 19a1f77
fix(e2e): restore missing mattermostShell helper for window_menu tests
yasserfaraazkhan cdba900
fix(e2e): fail CI status when Playwright collects zero tests
yasserfaraazkhan 300ab3c
Merge origin/master into e2e/08-notifications-focus-calls
yasserfaraazkhan a52c702
clean up
yasserfaraazkhan cff4fce
clean up
yasserfaraazkhan dd1e922
remove unwanted artifacts and fix failures
yasserfaraazkhan 487b5cf
fix badge tests
yasserfaraazkhan c3c1dc3
fix tests
yasserfaraazkhan 411e3c5
clean up hooks
yasserfaraazkhan 17077d0
move hookes to e2e/hooks
yasserfaraazkhan ca1c50c
move hookes to e2e/hooks
yasserfaraazkhan 8181131
clean up label
yasserfaraazkhan e309707
revert production app changes
yasserfaraazkhan 2d2bb51
clean up label
yasserfaraazkhan 1422f63
clean up label
yasserfaraazkhan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. | ||
| // See LICENSE.txt for license information. | ||
|
|
||
| import {expect} from '@playwright/test'; | ||
| import type {ElectronApplication} from 'playwright'; | ||
|
|
||
| import {evaluateInMainProcess, evaluateInMainProcessWithArg} from './testRefs'; | ||
|
|
||
| export type OsBadgeState = { | ||
| count: number; | ||
| symbol: 'mention' | 'unread' | 'expired' | 'none'; | ||
| hasOverlay: boolean; | ||
| }; | ||
|
|
||
| export async function waitForBadgeInfrastructure(app: ElectronApplication): Promise<void> { | ||
| await expect.poll( | ||
| async () => app.evaluate(() => { | ||
| const refs = (global as any).__e2eTestRefs; | ||
| return Boolean(refs?.AppState && refs?.ServerManager); | ||
| }), | ||
| {timeout: 30_000, message: 'AppState and ServerManager must be exposed on __e2eTestRefs'}, | ||
| ).toBe(true); | ||
| } | ||
|
|
||
| export async function setUnreadBadgeSetting(app: ElectronApplication, enabled: boolean): Promise<void> { | ||
| await evaluateInMainProcessWithArg(app, (_electron, showUnreadBadge) => { | ||
| const refs = (global as any).__e2eTestRefs; | ||
| if (!refs?.setUnreadBadgeSetting) { | ||
| throw new Error('setUnreadBadgeSetting missing from __e2eTestRefs'); | ||
| } | ||
| refs.Config?.set?.('showUnreadBadge', showUnreadBadge); | ||
| refs.setUnreadBadgeSetting(showUnreadBadge); | ||
| }, enabled); | ||
| } | ||
|
|
||
| export async function updateServerBadgeViaAppState( | ||
| app: ElectronApplication, | ||
| serverName: string, | ||
| mentions: number, | ||
| unreads: boolean, | ||
| ): Promise<void> { | ||
| await evaluateInMainProcessWithArg(app, (_electron, {serverName: name, mentions: mentionCount, unreads: hasUnreads}) => { | ||
| const refs = (global as any).__e2eTestRefs; | ||
| const AppState = refs?.AppState; | ||
| const ServerManager = refs?.ServerManager; | ||
| if (!AppState || !ServerManager) { | ||
| throw new Error('AppState or ServerManager missing from __e2eTestRefs'); | ||
| } | ||
| const server = ServerManager.getAllServers().find((s: {name: string}) => s.name === name); | ||
| if (!server) { | ||
| throw new Error(`Server not found: ${name}`); | ||
| } | ||
| AppState.updateUnreadsAndMentionsPerServer(server.id, mentionCount, hasUnreads); | ||
| }, {serverName, mentions, unreads}); | ||
| } | ||
|
|
||
| export async function setServerExpiredViaAppState( | ||
| app: ElectronApplication, | ||
| serverName: string, | ||
| expired: boolean, | ||
| ): Promise<void> { | ||
| await evaluateInMainProcessWithArg(app, (_electron, {serverName: name, expired: isExpired}) => { | ||
| const refs = (global as any).__e2eTestRefs; | ||
| const AppState = refs?.AppState; | ||
| const ServerManager = refs?.ServerManager; | ||
| if (!AppState || !ServerManager) { | ||
| throw new Error('AppState or ServerManager missing from __e2eTestRefs'); | ||
| } | ||
| const server = ServerManager.getAllServers().find((s: {name: string}) => s.name === name); | ||
| if (!server) { | ||
| throw new Error(`Server not found: ${name}`); | ||
| } | ||
| AppState.updateExpired(server.id, isExpired); | ||
| }, {serverName, expired}); | ||
| } | ||
|
|
||
| export async function clearAllBadgesViaAppState(app: ElectronApplication): Promise<void> { | ||
| await evaluateInMainProcess(app, () => { | ||
| const refs = (global as any).__e2eTestRefs; | ||
| const AppState = refs?.AppState; | ||
| const ServerManager = refs?.ServerManager; | ||
| if (!AppState || !ServerManager) { | ||
| throw new Error('AppState or ServerManager missing from __e2eTestRefs'); | ||
| } | ||
| for (const server of ServerManager.getAllServers()) { | ||
| AppState.updateUnreadsAndMentionsPerServer(server.id, 0, false); | ||
| AppState.updateExpired(server.id, false); | ||
| } | ||
| refs.Config?.set?.('showUnreadBadge', false); | ||
| refs.setUnreadBadgeSetting?.(false); | ||
| }); | ||
| } | ||
|
|
||
| export async function readOsBadge(electronApp: ElectronApplication): Promise<OsBadgeState> { | ||
| return evaluateInMainProcess(electronApp, ({app}) => { | ||
| const testState = (global as any).__testBadgeState; | ||
|
|
||
| if (process.platform === 'darwin') { | ||
| const badge = app.dock?.getBadge() ?? ''; | ||
| if (badge === '•') { | ||
| return {count: 0, symbol: 'unread' as const, hasOverlay: false}; | ||
| } | ||
| if (badge === '!') { | ||
| return {count: 0, symbol: 'expired' as const, hasOverlay: false}; | ||
| } | ||
| if (badge === '') { | ||
| return {count: 0, symbol: 'none' as const, hasOverlay: false}; | ||
| } | ||
| return {count: parseInt(badge, 10), symbol: 'mention' as const, hasOverlay: false}; | ||
| } | ||
|
|
||
| if (process.platform === 'linux') { | ||
| // app.getBadgeCount()/setBadgeCount() are no-ops without a running Unity | ||
| // desktop (true in headless CI), so fall back to re-deriving the count | ||
| // from the same inputs showBadgeLinux() would have passed to | ||
| // setBadgeCount() — mentionCount plus 1 for an expired session. | ||
| let count: number; | ||
| if (app.isUnityRunning()) { | ||
| count = app.getBadgeCount(); | ||
| } else if (testState) { | ||
| count = testState.mentionCount + (testState.sessionExpired ? 1 : 0); | ||
| } else { | ||
| count = 0; | ||
| } | ||
| const symbol = testState?.resolvedType ?? (count > 0 ? 'mention' : 'none'); | ||
| return {count, symbol, hasOverlay: false}; | ||
| } | ||
|
|
||
| // Electron exposes setOverlayIcon() but no getOverlayIcon(); __testBadgeState | ||
| // records what showBadgeWindows() decided to pass to setOverlayIcon(). | ||
| const symbol = testState?.resolvedType ?? 'none'; | ||
| return { | ||
| count: testState?.mentionCount ?? 0, | ||
| symbol, | ||
| hasOverlay: testState?.hasOverlay ?? false, | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| export async function readBadgeCount(app: ElectronApplication): Promise<number> { | ||
| const state = await readOsBadge(app); | ||
| return state.count; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. | ||
| // See LICENSE.txt for license information. | ||
|
|
||
| import type {Page} from '@playwright/test'; | ||
| import type {ElectronApplication} from 'playwright'; | ||
|
|
||
| export function findCallsWidgetWindow(electronApp: ElectronApplication): Page | null { | ||
| return electronApp.windows().find((w) => { | ||
| try { | ||
| const url = w.url(); | ||
| return url.includes('/plugins/com.mattermost.calls/standalone/widget.html'); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }) ?? null; | ||
| } | ||
|
|
||
| export async function waitForCallsWidgetWindow( | ||
| electronApp: ElectronApplication, | ||
| timeoutMs = 20_000, | ||
| ): Promise<Page | null> { | ||
| const existing = findCallsWidgetWindow(electronApp); | ||
| if (existing) { | ||
| return existing; | ||
| } | ||
|
|
||
| return electronApp.waitForEvent('window', { | ||
| predicate: (w) => { | ||
| try { | ||
| return w.url().includes('/plugins/com.mattermost.calls/standalone/widget.html'); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }, | ||
| timeout: timeoutMs, | ||
| }).catch(() => null); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. | ||
| // See LICENSE.txt for license information. | ||
|
|
||
| import type {ElectronApplication} from 'playwright'; | ||
|
|
||
| import {CLOSE_DOWNLOADS_DROPDOWN, CLOSE_DOWNLOADS_DROPDOWN_MENU} from './ipcChannels'; | ||
|
|
||
| import {evaluateInMainProcessWithArg, isTransientNavigationError} from './testRefs'; | ||
|
|
||
| /** | ||
| * Close the downloads dropdown WebContentsView if it is open. | ||
| * Parallel download specs can leave this overlay focused and block other UI flows. | ||
| */ | ||
| export async function closeDownloadsDropdownIfOpen(app: ElectronApplication): Promise<void> { | ||
| await evaluateInMainProcessWithArg(app, ({ipcMain}, channels) => { | ||
| ipcMain.emit(channels.menu); | ||
| ipcMain.emit(channels.dropdown); | ||
| }, {dropdown: CLOSE_DOWNLOADS_DROPDOWN, menu: CLOSE_DOWNLOADS_DROPDOWN_MENU}, { | ||
| timeoutMs: 15_000, | ||
| isRetryable: isTransientNavigationError, | ||
| }); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curios why need to remove data dir. Maybe would be good to add comment.