Skip to content

test(e2e): Playwright harness and main-process hooks for migration stack - #3855

Merged
yasserfaraazkhan merged 12 commits into
masterfrom
e2e/01-harness-ci
Jun 30, 2026
Merged

test(e2e): Playwright harness and main-process hooks for migration stack#3855
yasserfaraazkhan merged 12 commits into
masterfrom
e2e/01-harness-ci

Conversation

@yasserfaraazkhan

@yasserfaraazkhan yasserfaraazkhan commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

This is a small set of changes to get CI running Playwright. This will put in the main hooks required by e2e tests specs to run tests from these pr. (#3857#3864).

Summary

Playwright’s Electron driver gives us a BrowserWindow pages for the app shell. Each Mattermost server renders in a WebContentsView, which is not a page we get from normal playwright execution. Finding and driving a specific server means going through main-process singletons like ViewManager, WebContentsManager, etc and not the page.click() alone.

To automate these flow we are using hooks. The app.evaluate() (main-process context) plus global.__e2e* refs to reach ViewManager, WebContentsManager, tray actions, etc.

Its the same case with tray menus and native certificate dialogs. They are OS/main-process and not in the DOM. We need the global.__e2e refs.


In master we already have __e2eTestRefs (MainWindow, ServerManager, TabManager, ViewManager, WebContentsManager), __e2eAppReady (intercom.ts), dock/taskbar badge hooks (badge.ts).

In this PR we are add more helper to test the scenarios mentioned in the beginning.

Why gate on NODE_ENV === 'test' (vs webapp-style always-on testdata)?

We do not want to push hooks to production app. In webapp we attache test fields to DOM. Here we're publishing handles on the Node main-process global. NODE_ENV === 'test' keeps normal user installs from getting those globals.

NONE

Change Impact: 🟡 Medium

Regression Risk: Refactors and expands the cross-platform Playwright/Electron E2E harness (worker/global Electron main-process lifecycle, PID-registry reaping, teardown/“fast teardown” termination behavior, and evaluation/timeout/retry logic), plus CI workflow/caching and report-tag generation. While production runtime behavior is gated to NODE_ENV=test via setTestField/test-only stubs (message box, tray/deep-link/menu click wiring, flash effects exposure), the teardown and main-process evaluation/timeout paths are sensitive and could cause flaky E2E behavior, orphaned processes, or teardown timing regressions—especially on macOS.

QA Recommendation: Rely primarily on automated CI E2E coverage; minimal manual QA. If any failures occur, manually validate on the failing OS job(s) that the existing specs run under Playwright, the failing scenario reproduces reliably, and Electron shuts down cleanly with no orphan processes (and that behavior differs as expected between normal vs “fast teardown”), paying extra attention to macOS defaults snapshot/restore.
Generated by CodeRabbitAI

Part 1 of splitting #3847 — core Playwright config, fixtures, electronApp
teardown, and GitHub Actions E2E workflow updates. No new specs yet.
Exposes __e2eTestRefs, message-box stub, tray/deep-link hooks (NODE_ENV=test
only) plus directLaunch, testRefs, and shared helper updates.
@mm-cloud-bot

Copy link
Copy Markdown

@yasserfaraazkhan: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

I understand the commands that are listed here

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR updates E2E test infrastructure, Playwright project selection, CI caching and setup, Electron process cleanup, helper utilities, app test hooks, and related guidance and dependencies.

Changes

E2E infrastructure and CI

Layer / File(s) Summary
Playwright config and flaky-test reporting
e2e/playwright.config.ts, .github/workflows/e2e-functional-template.yml, e2e/utils/analyze-flaky-test.js
Playwright config now builds platform-specific projects, rewrites CI report tags, and sets worker teardown timing; the workflow template updates CI environment tags and flaky-test reporting to consume testStatus.
CI setup, caching, and dependency updates
.github/actions/install-os-dependencies/action.yaml, .github/workflows/e2e-functional.yml, .github/workflows/e2e-functional-template.yml, e2e/package.json, package.json
Workflow setup disables setup-node package caching, splits node_modules and Electron gyp caches, adds macOS tar/pip preparation steps, pre-trusts Homebrew taps, updates Playwright tooling, and adds fast-xml-parser.
Electron process registry and teardown
e2e/helpers/electronApp.ts, e2e/helpers/config.ts, e2e/fixtures/index.ts, e2e/helpers/cleanup.ts, e2e/helpers/appReadiness.ts, e2e/global-setup.ts, e2e/global-teardown.ts, e2e/helpers/directLaunch.ts
Adds sharded PID registry helpers, clean-close and force-close teardown behavior, fixture registration and worker cleanup, lock-file handling updates, registry clearing in global setup, shared Electron-process cleanup in global teardown, and direct launch registration.
E2E helper modules for main-process and renderer interaction
e2e/helpers/testRefs.ts, e2e/helpers/login.ts, e2e/helpers/dialog.ts, e2e/helpers/notificationEffects.ts, e2e/helpers/overlayWindows.ts, e2e/helpers/prepareServerView.ts, e2e/helpers/serverMap.ts, e2e/helpers/serverView.ts, e2e/helpers/tray.ts
Adds main-process retry helpers, login and app-shell probing, dialog and notification test hooks, overlay-window closure, server-view preparation, server-map ordering, server-view renderer handling, tray interaction helpers, and supporting direct-launch logic.
App initialization test hooks and stubs
src/main/app/initialize.ts, src/main/testMessageBoxStub.ts, src/main/notifications/index.ts, src/main/app/initialize.test.js
Startup wiring exposes additional E2E references and helpers, installs message-box stubs and certificate cleanup in test mode, exposes notification flash effects, and adds test mocks for tray and popout manager modules.
E2E guidance and setup docs
e2e/AGENTS.md
Updates the E2E guide with declarative platform-tag guidance and points environment setup back to that section.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a Playwright E2E harness and main-process hooks for the migration stack.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch e2e/01-harness-ci

Comment @coderabbitai help to get the list of available commands.

@yasserfaraazkhan yasserfaraazkhan changed the title E2E: Playwright harness and CI wiring (1/10) E2E: Playwright harness and CI wiring Jun 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/fixtures/index.ts`:
- Around line 64-67: In the worker fixture teardown within the Promise.race
block, the setTimeout call creates a timeout handle that is never cleared when
cleanupRegisteredElectronProcesses() resolves first, causing the event loop to
remain active. Store the timeout ID returned by setTimeout, and after the
Promise.race completes, clear that timeout using clearTimeout to ensure the
Node.js event loop is not kept alive by the pending callback.

In `@e2e/helpers/cleanup.ts`:
- Around line 33-43: The code in the catch block unconditionally attempts to
unlink the SingletonLock file using fs.unlinkSync(lockFile), but on POSIX
systems (macOS/Linux) this removes the directory entry immediately even while a
live process holds the file descriptor, allowing premature relaunch and
violating Electron's singleton guard. Remove or skip the entire inner try-catch
block that calls fs.unlinkSync(lockFile) to prevent false cleanup success on
POSIX systems in the timeout path, ensuring the lock file persists until the
process truly releases the file descriptor.

In `@e2e/package.json`:
- Line 27: The playwright package is missing from the devDependencies section of
e2e/package.json while `@playwright/test` version 1.61.0 is present. Add
playwright as a devDependency entry in the devDependencies object with version
1.61.0 to match the `@playwright/test` version exactly, ensuring both packages are
version-locked as required by the coding guideline.

In `@e2e/playwright.config.ts`:
- Line 32: The workers constant assignment uses parseInt on the E2E_WORKERS
environment variable but does not validate the result, which can lead to NaN or
non-positive values being assigned. Add validation after parsing to ensure the
resulting value is a valid positive number, falling back to defaultWorkers if
parseInt returns NaN or if the parsed value is less than or equal to zero. This
can be done by checking if the parsed value is a valid positive integer before
assignment to the workers constant.
- Around line 68-72: The wayland project being pushed to the projects array when
E2E_WAYLAND is true is missing the policyFilter spread operator that is applied
to the main platform project. Add the policyFilter spread operator into the
wayland project configuration object (the one with name set to 'wayland' and
grep set to /@wayland/) to ensure policy test exclusion is properly applied when
policy runs are disabled.

In `@e2e/utils/analyze-flaky-test.js`:
- Around line 20-23: The bare catch block in the getXMLParserClass() function
suppresses all exceptions, which masks real errors like syntax errors or missing
peer dependencies. Modify the catch block to check the error's code property and
only suppress MODULE_NOT_FOUND (CommonJS) and ERR_MODULE_NOT_FOUND (ESM) errors.
For all other error types, rethrow the error immediately so actual failures are
properly surfaced rather than falling through to the generic error message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6f1b2683-b80e-4d90-9763-c657b001854b

📥 Commits

Reviewing files that changed from the base of the PR and between ef8d8e6 and db20b56.

⛔ Files ignored due to path filters (1)
  • e2e/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .github/actions/install-os-dependencies/action.yaml
  • .github/workflows/e2e-functional-template.yml
  • .github/workflows/e2e-functional.yml
  • e2e/AGENTS.md
  • e2e/fixtures/index.ts
  • e2e/global-setup.ts
  • e2e/global-teardown.ts
  • e2e/helpers/appReadiness.ts
  • e2e/helpers/cleanup.ts
  • e2e/helpers/config.ts
  • e2e/helpers/electronApp.ts
  • e2e/package.json
  • e2e/playwright.config.ts
  • e2e/utils/analyze-flaky-test.js

Comment thread e2e/fixtures/index.ts Outdated
Comment thread e2e/helpers/cleanup.ts
Comment thread e2e/package.json
Comment thread e2e/playwright.config.ts Outdated
Comment thread e2e/playwright.config.ts
Comment thread e2e/utils/analyze-flaky-test.js Outdated
@github-actions github-actions Bot removed the E2E/Run Run Desktop E2E Tests label Jun 19, 2026
@esarafianou
esarafianou requested review from NARSimoes and removed request for esarafianou June 19, 2026 10:18
@github-actions github-actions Bot removed the E2E/Run Run Desktop E2E Tests label Jun 19, 2026
Fixtures in PR 1 import overlayWindows and other shared helpers from PR 2;
merge them so the full E2E suite can run on #3855 before the stack continues.
@github-actions github-actions Bot added the E2E/Run Run Desktop E2E Tests label Jun 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
e2e/helpers/login.ts (2)

18-24: ⚖️ Poor tradeoff

Error swallowing in hasAppShell may hide real failures.

The .catch(() => false) on runInRenderer silently converts all errors (including execution context destruction, renderer crashes, or selector bugs) into false. While this provides robustness during navigation, it may mask legitimate test setup issues or app crashes.

Consider logging unexpected errors before returning false, or at minimum, check for known transient error messages (like "Execution context was destroyed") before suppressing them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/helpers/login.ts` around lines 18 - 24, In the hasAppShell function, the
catch block on the runInRenderer call currently silences all errors without
logging. Modify this catch handler to log the error before returning false, so
that unexpected errors like execution context destruction or renderer crashes
are captured for debugging. You may optionally check if the error is a known
transient error message (such as "Execution context was destroyed") and only log
non-transient errors to avoid noise in test output.

32-34: ⚖️ Poor tradeoff

Error swallowing in hasLoginForm may hide real failures.

The .catch(() => false) silently suppresses all errors from runInRenderer. This is the same pattern as in hasAppShell (lines 18-24). While it makes the polling resilient to navigation, it could hide legitimate bugs or crashes.

Consider adding selective error handling to distinguish transient navigation errors from genuine failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/helpers/login.ts` around lines 32 - 34, The catch handler in the
hasLoginForm function currently suppresses all errors returned from
runInRenderer by catching any error and returning false. Instead of catching all
errors indiscriminately, implement selective error handling that only catches
transient navigation-related errors that are expected to occur during polling
(such as navigation errors or timeout errors), and allows genuine failures or
unexpected errors to propagate or be logged. This way, the function remains
resilient to expected navigation transitions while still surfacing real bugs or
crashes.
src/main/app/initialize.ts (1)

315-333: 💤 Low value

Clicking disabled or invisible menu items may produce unexpected behavior.

The tray menu traversal (lines 315-333) finds items by label and invokes item.click() without checking item.enabled or item.visible properties. While this may be acceptable for E2E testing scenarios that need to verify behavior regardless of UI state, it could mask issues where tests should fail when interacting with disabled menu items.

Consider adding a validation step or documenting this behavior if intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/app/initialize.ts` around lines 315 - 333, In the
__e2eClickTrayMenuItem test field function, the code finds and clicks menu items
without verifying the item.enabled and item.visible properties. Before calling
item.click(), add a condition to check that the item is both enabled and visible
(ensure both properties are true or undefined since that typically means
enabled/visible by default). If the item is disabled or invisible, either skip
it and continue searching through the stack, or document why clicking disabled
items is intentional for this E2E testing scenario.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/helpers/testRefs.ts`:
- Around line 74-93: The transient error handling logic that checks for
"Execution context was destroyed" and "Target page, context or browser has been
closed" is partially re-implemented in the getMainWindowId function's catch
block and again elsewhere in the module around lines 106-113. This inconsistent
duplication can cause tests to fail on transient context churn that the module
intends to tolerate. Extract this error checking logic into a reusable helper
function and apply it consistently across all locations in the module where
app.evaluate() is called, ensuring all transient errors are uniformly handled
through the expect.poll retry mechanism instead of being partially bypassed in
different places.

---

Nitpick comments:
In `@e2e/helpers/login.ts`:
- Around line 18-24: In the hasAppShell function, the catch block on the
runInRenderer call currently silences all errors without logging. Modify this
catch handler to log the error before returning false, so that unexpected errors
like execution context destruction or renderer crashes are captured for
debugging. You may optionally check if the error is a known transient error
message (such as "Execution context was destroyed") and only log non-transient
errors to avoid noise in test output.
- Around line 32-34: The catch handler in the hasLoginForm function currently
suppresses all errors returned from runInRenderer by catching any error and
returning false. Instead of catching all errors indiscriminately, implement
selective error handling that only catches transient navigation-related errors
that are expected to occur during polling (such as navigation errors or timeout
errors), and allows genuine failures or unexpected errors to propagate or be
logged. This way, the function remains resilient to expected navigation
transitions while still surfacing real bugs or crashes.

In `@src/main/app/initialize.ts`:
- Around line 315-333: In the __e2eClickTrayMenuItem test field function, the
code finds and clicks menu items without verifying the item.enabled and
item.visible properties. Before calling item.click(), add a condition to check
that the item is both enabled and visible (ensure both properties are true or
undefined since that typically means enabled/visible by default). If the item is
disabled or invisible, either skip it and continue searching through the stack,
or document why clicking disabled items is intentional for this E2E testing
scenario.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a32daa94-1821-4304-8e45-69e396283de5

📥 Commits

Reviewing files that changed from the base of the PR and between ce0c87a and 9bc885e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • e2e/helpers/dialog.ts
  • e2e/helpers/directLaunch.ts
  • e2e/helpers/login.ts
  • e2e/helpers/notificationEffects.ts
  • e2e/helpers/overlayWindows.ts
  • e2e/helpers/prepareServerView.ts
  • e2e/helpers/serverMap.ts
  • e2e/helpers/serverView.ts
  • e2e/helpers/testRefs.ts
  • e2e/helpers/tray.ts
  • package.json
  • src/main/app/initialize.test.js
  • src/main/app/initialize.ts
  • src/main/notifications/index.ts
  • src/main/testMessageBoxStub.ts
✅ Files skipped from review due to trivial changes (2)
  • e2e/helpers/overlayWindows.ts
  • src/main/notifications/index.ts

Comment thread e2e/helpers/testRefs.ts
@github-actions github-actions Bot removed the E2E/Run Run Desktop E2E Tests label Jun 19, 2026
@github-actions github-actions Bot added the E2E/Run Run Desktop E2E Tests label Jun 19, 2026
@github-actions github-actions Bot removed the E2E/Run Run Desktop E2E Tests label Jun 19, 2026
@hmhealey
hmhealey removed their request for review June 26, 2026 17:02
@github-actions github-actions Bot added E2E/Run Run Desktop E2E Tests and removed E2E/Run Run Desktop E2E Tests labels Jun 30, 2026

@lieut-data lieut-data left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @yasserfaraazkhan! A few comments below, but not strictly blocking -- I don't need to re-review after you've decided next steps.

ServerManager.on(SERVER_URL_CHANGED, updateServerInfo);
ServerManager.on(SERVER_PRE_AUTH_SECRET_CHANGED, updateServerInfo);

if (process.env.NODE_ENV === 'test') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like setTestField already checks process.env.NODE_ENV === 'test' -- can we simplify this?

Comment thread src/main/app/initialize.ts Outdated
setTestField('__e2eClickTrayMenuItem', (label: string) => {
const menu = createTrayMenu();
const stack = [...menu.items];
while (stack.length > 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a while to realize what was going on here -- flattening the menu in a kind of manual depth-first-search.

Would we be open to just writing this recursively, ala something like:

setTestField('__e2eClickTrayMenuItem', (label: string) => {
    const truncated = label.length > 50 ? `${label.slice(0, 50)}...` : label;

    function clickItem(items: Electron.MenuItemConstructorOptions[]): boolean {
        for (const item of items) {
            const itemLabel = typeof item.label === 'string' ? item.label : '';
            if (
                (itemLabel === label || itemLabel === truncated) &&
                item.enabled !== false &&
                item.visible !== false &&
                typeof item.click === 'function'
            ) {
                item.click();
                return true;
            }
            if (item.submenu?.items && clickItem(item.submenu.items)) {
                return true;
            }
        }
        return false;
    }

    if (!clickItem(createTrayMenu().items)) {
        throw new Error(`Tray menu item not found: ${label}`);
    }
});

@github-actions github-actions Bot added the E2E/Run Run Desktop E2E Tests label Jun 30, 2026
@github-actions github-actions Bot added E2E/Run Run Desktop E2E Tests and removed E2E/Run Run Desktop E2E Tests labels Jun 30, 2026
@yasserfaraazkhan
yasserfaraazkhan merged commit 08129c4 into master Jun 30, 2026
36 of 39 checks passed
@yasserfaraazkhan
yasserfaraazkhan deleted the e2e/01-harness-ci branch June 30, 2026 23:13
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve conflicts by keeping merged harness/hook fixes from #3855
and retaining PR 3 Mattermost shell helpers and specs.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Align notification trigger tests with #3855 helper rename after master merge.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
yasserfaraazkhan added a commit that referenced this pull request Jun 30, 2026
Resolve harness/hook conflicts by keeping merged #3855 changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants