Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fe9514e
fix(cmt): POST directly to matterwick instead of relying on workflow_…
May 21, 2026
c41490b
fix(cmt): drop all GITHUB_TOKEN scopes for cmt-provisioner
May 25, 2026
0ce5d27
fix(cmt): quote ${RUN_ID} expansion in jq invocation
May 25, 2026
e2e49b9
fix(e2e): drop push trigger from e2e-nightly-trigger.yml
May 25, 2026
cd36811
fix(e2e): fix macOS e2e failures from crash dialogs and teardown
yasserfaraazkhan May 26, 2026
ddcc6c0
fix(e2e): add blank line before comment to satisfy eslint lines-aroun…
yasserfaraazkhan May 26, 2026
8298749
fix(e2e): use execFileSync instead of execSync, fix misleading comment
yasserfaraazkhan May 26, 2026
a926985
fix(e2e): unblock macOS tests and report per-OS results
yasserfaraazkhan May 26, 2026
0b1facd
fix(ci): pin policy-tests-windows to windows-2022 + add intercom tests
yasserfaraazkhan May 26, 2026
bb174ef
fix(e2e): address remaining macOS/Linux/Windows failures
yasserfaraazkhan May 26, 2026
e7e0dc9
fix(e2e): split inline try/catch onto multiple lines for eslint
yasserfaraazkhan May 26, 2026
b3d7682
fix(e2e): address CI failures across Linux, macOS, and Windows
yasserfaraazkhan May 26, 2026
de6d112
fix tests
yasserfaraazkhan May 26, 2026
7508c9f
Merge remote-tracking branch 'origin/master' into fix/cmt-direct-disp…
yasserfaraazkhan May 26, 2026
090b52d
fix(e2e): fix bad_servers expired-cert reload and copy_link contextmenu
yasserfaraazkhan May 26, 2026
25e40fc
fix(e2e): fix Windows CI failures for deeplink, fullscreen, minimize,…
yasserfaraazkhan May 26, 2026
47fe037
fix(e2e): fix ESLint lines-around-comment and no-multiple-empty-lines…
yasserfaraazkhan May 26, 2026
2cb6e9c
fix(e2e): add blank line before comment in deeplink test (ESLint)
yasserfaraazkhan May 26, 2026
45c8a99
fix(ci): show ran/passed/failed in status checks, drop skipped count
yasserfaraazkhan May 26, 2026
2561cd3
fix(test): add missing on mock to MainWindow in popoutManager unit test
yasserfaraazkhan May 26, 2026
4d8521e
fix: address CodeRabbit inline review findings
yasserfaraazkhan May 26, 2026
8c410ed
revert: restore hardcoded S3 bucket name and URL in e2e workflow
yasserfaraazkhan May 26, 2026
8b865eb
test(popoutManager): add unit tests for registerMainWindowCloseHandle…
yasserfaraazkhan May 27, 2026
9d891fb
Desktop qa agent (#3834)
yasserfaraazkhan May 27, 2026
4d077d0
qa: fix intercom.test.js electron mock — app.once is not a function
cursoragent May 27, 2026
4bfe9d0
qa: add QA report for PR #3829
cursoragent May 27, 2026
912ee16
ci(e2e): scheduled CMT trigger + drop in-workflow cleanup call
yasserfaraazkhan Jun 1, 2026
52a3f0c
ci(e2e): harden Actions permissions + make electron install resilient
yasserfaraazkhan Jun 1, 2026
4b6764e
test: mock electron in unit suites that import it (fix flaky CI failure)
yasserfaraazkhan Jun 1, 2026
f46c763
chore: remove accidentally committed qa-report.md
yasserfaraazkhan Jun 2, 2026
cd735c8
revert(e2e): remove Cursor-automation PR server-URL feature
yasserfaraazkhan Jun 2, 2026
5cde967
revert(e2e): remove Cursor-automation logic (workflows, setup, helper…
yasserfaraazkhan Jun 2, 2026
4a44c88
docs(e2e): drop redundant AGENTS.md note; leave docs unchanged
yasserfaraazkhan Jun 2, 2026
40bbaaa
docs(cmt): correct stale cmt_run_id input description
yasserfaraazkhan Jun 2, 2026
1ddad60
Merge remote-tracking branch 'origin/master' into fix/cmt-direct-disp…
yasserfaraazkhan Jun 3, 2026
473fdf3
fix(e2e): address review — drop popout-close change, gate E2E readiness
yasserfaraazkhan Jun 3, 2026
155039f
fix(e2e): fix Linux/macOS/Windows E2E failures (test fixes)
yasserfaraazkhan Jun 4, 2026
986d7e8
revert(test): drop electron mocks from UserActivityMonitor/diagnostic…
yasserfaraazkhan Jun 4, 2026
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
71 changes: 62 additions & 9 deletions .github/workflows/cmt-provisioner.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
name: CMT Provisioner

# This is a stub workflow. Its only purpose is to fire a workflow_run webhook
# to Matterwick, which provisions cloud servers for each server version and
# then dispatches compatibility-matrix-testing.yml with the CMT_MATRIX input.
# Trigger workflow for Compatibility Matrix Testing (CMT).
#
# Usage: Go to Actions → CMT Provisioner → Run workflow
# Previous design: this workflow just echoed the inputs and relied on GitHub
# firing a workflow_run webhook to matterwick, which would then read the
# server_versions input from the webhook payload. That path is broken --
# GitHub's workflow_run payload does not include workflow_dispatch inputs,
# so matterwick never received server_versions and never dispatched
# compatibility-matrix-testing.yml.
#
# New design: POST directly to matterwick's /cmt_dispatch endpoint with
# everything it needs (server_versions, run_id, sha, ref, repo, owner).
# Matterwick returns 202 immediately and provisions instances + dispatches
# compatibility-matrix-testing.yml asynchronously.
#
# Usage: Go to Actions -> CMT Provisioner -> Run workflow
# server_versions: comma-separated Mattermost server versions
# Example: "v11.1.0, v11.2.0, v12.0.0"

Expand All @@ -16,14 +26,57 @@ on:
required: true
type: string

# This workflow does not use GITHUB_TOKEN. It does not check out the repo,
# does not call the GitHub API, and does not interact with workflow files;
# its single step is an outbound curl to matterwick authenticated by a
# separate shared secret. Drop all GITHUB_TOKEN scopes so the runner-issued
# token cannot accidentally be used to modify the repo if a step is added
# carelessly later.
permissions: {}

jobs:
notify:
trigger-matterwick:
runs-on: ubuntu-22.04
steps:
- name: Log CMT request
- name: Request CMT provisioning from matterwick
env:
MATTERWICK_URL: ${{ vars.MATTERWICK_URL }}
CMT_TRIGGER_TOKEN: ${{ secrets.MATTERWICK_CMT_TRIGGER_SECRET }}
SERVER_VERSIONS: ${{ inputs.server_versions }}
REPO: ${{ github.event.repository.name }}
OWNER: ${{ github.repository_owner }}
SHA: ${{ github.sha }}
REF: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
run: |
echo "CMT Provisioner triggered"
echo "Server versions: ${SERVER_VERSIONS}"
echo "Matterwick will provision cloud instances and dispatch compatibility-matrix-testing.yml"
if [ -z "${MATTERWICK_URL}" ]; then
echo "ERROR: vars.MATTERWICK_URL is not set on this repository" >&2
exit 1
fi
if [ -z "${CMT_TRIGGER_TOKEN}" ]; then
echo "ERROR: secrets.MATTERWICK_CMT_TRIGGER_SECRET is not set on this repository" >&2
exit 1
fi

payload="$(jq -nc \
--arg owner "${OWNER}" \
--arg repo "${REPO}" \
--arg sha "${SHA}" \
--arg ref "${REF}" \
--argjson run_id "${RUN_ID}" \
--arg versions "${SERVER_VERSIONS}" \
'{owner:$owner, repo:$repo, sha:$sha, ref:$ref, run_id:$run_id, server_versions:$versions}')"

echo "Requesting CMT provisioning for server versions: ${SERVER_VERSIONS}"

curl -sS -X POST "${MATTERWICK_URL}/cmt_dispatch" \
-H "Content-Type: application/json" \
-H "X-Trigger-Token: ${CMT_TRIGGER_TOKEN}" \
--data-binary "${payload}" \
--fail-with-body \
--retry 3 --retry-delay 5 --retry-connrefused \
--connect-timeout 10 --max-time 30

echo "matterwick accepted the CMT dispatch request."
echo "Provisioning will run asynchronously (~30 min) and then"
echo "compatibility-matrix-testing.yml will be dispatched automatically."
12 changes: 12 additions & 0 deletions .github/workflows/e2e-functional-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@ jobs:
npm ci
cd e2e && npm ci

- name: e2e/suppress-macos-dialogs
if: runner.os == 'macOS'
run: |
# Suppress the "Reopen windows" dialog that blocks Electron startup
# when a previous test instance was killed by SIGTERM/SIGKILL.
# The global-setup.ts also does this, but running it here as well ensures
# the setting is applied even if the Playwright globalSetup step is skipped.
defaults write com.github.Electron NSQuitAlwaysKeepsWindows -bool false || true
defaults write com.github.Electron ApplePersistenceIgnoreState -bool YES || true
defaults write com.apple.LaunchServices LSQuarantine -bool false || true
defaults write com.apple.CrashReporter DialogType none || true

- name: e2e/run-playwright-tests-linux
if: runner.os == 'Linux'
run: |
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/e2e-functional.yml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,16 @@ jobs:
npm ci
cd e2e && npm ci

- name: e2e/suppress-macos-dialogs
if: runner.os == 'macOS'
run: |
# Suppress the "Reopen windows" dialog that blocks Electron startup
# when a previous test instance was killed by SIGTERM/SIGKILL.
defaults write com.github.Electron NSQuitAlwaysKeepsWindows -bool false || true
defaults write com.github.Electron ApplePersistenceIgnoreState -bool YES || true
defaults write com.apple.LaunchServices LSQuarantine -bool false || true
defaults write com.apple.CrashReporter DialogType none || true

- name: e2e/build-test-bundle
run: npm run build-test

Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/e2e-nightly-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ name: E2E Nightly Trigger
# Matterwick receives the workflow_run:requested event, provisions 3 Mattermost cloud
# instances (linux/macos/windows), and dispatches e2e-functional.yml with instance details.
# When e2e-functional.yml completes, Matterwick destroys the provisioned instances.
#
# Schedule-only. Pushes to master and release-* are handled by Matterwick's push-event
# handler directly (E2EAutoTriggerOnMaster / E2EAutoTriggerOnRelease in the deployed
# config). Previously this workflow also fired on push to those branches, which caused
# both Matterwick paths to fire concurrently for every push -- producing 2x cloud
# instances and 2x e2e-functional.yml runs per commit. Dropping the push trigger here
# leaves Matterwick's native push handler as the single canonical driver for push runs.
on:
push:
branches:
- master
- 'release-*'
schedule:
- cron: "0 0 * * 4,5" # Thursday and Friday midnight UTC

Expand Down
6 changes: 6 additions & 0 deletions e2e/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ export const test = base.extend<Fixtures>({
'--disable-features=CrossOriginOpenerPolicy',
'--disable-renderer-backgrounding',

// Dialogs & first-run
'--no-first-run',
'--no-default-browser-check',
'--disable-default-apps',
'--disable-crash-reporter',

// Consistency
'--force-color-profile=srgb',
'--mute-audio',
Expand Down
33 changes: 30 additions & 3 deletions e2e/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,38 @@ export default async function globalSetup() {
}

if (process.platform === 'darwin') {
// Multiple bundle IDs may be involved: com.github.Electron (Electron binary
// launched directly) and the app's own bundle ID (when running signed builds).
const bundleIDs = ['com.github.Electron'];

for (const bundleID of bundleIDs) {
try {
execSync(`defaults write ${bundleID} NSQuitAlwaysKeepsWindows -bool false`, {stdio: 'pipe'});
} catch {
// Non-fatal — tests still run, just potentially with the Resume dialog
}
try {
execSync(`defaults write ${bundleID} ApplePersistenceIgnoreState -bool YES`, {stdio: 'pipe'});
} catch {
// Non-fatal
}
}

// Verify at least one bundle ID got the settings applied.
// Also suppress the "verification of developer" dialog that can appear
// on first launch of unsigned Electron builds.
try {
execSync('defaults write com.apple.LaunchServices LSQuarantine -bool false', {stdio: 'pipe'});
} catch {
// Non-fatal
}

// Suppress the macOS crash dialog ("Electron quit unexpectedly") that
// appears when a process is killed by SIGKILL in global-teardown.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
try {
execSync('defaults write com.github.Electron NSQuitAlwaysKeepsWindows -bool false', {stdio: 'ignore'});
execSync('defaults write com.github.Electron ApplePersistenceIgnoreState -bool YES', {stdio: 'ignore'});
execSync('defaults write com.apple.CrashReporter DialogType none', {stdio: 'pipe'});
} catch {
// Non-fatal — tests still run, just potentially with the Resume dialog
// Non-fatal
}
}
}
19 changes: 14 additions & 5 deletions e2e/global-teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ export default async function globalTeardown() {
continue;
}

const deadline = Date.now() + 5_000;
// Give the process time to exit gracefully.
// On macOS, use a longer wait since Electron shutdown can be slow.
const waitMs = process.platform === 'darwin' ? 10_000 : 5_000;
const deadline = Date.now() + waitMs;
while (Date.now() < deadline) {
if (!isProcessAlive(pid)) {
break;
Expand All @@ -60,10 +63,16 @@ export default async function globalTeardown() {
}

if (isProcessAlive(pid)) {
try {
process.kill(pid, 'SIGKILL');
} catch {
// already exited
// On macOS, SIGKILL triggers the "quit unexpectedly" crash dialog
// which blocks subsequent Electron launches. Skip SIGKILL and let
// the process linger — global-setup clears the registry, and each
// test uses a unique userDataDir so orphans never block new tests.
if (process.platform !== 'darwin') {
try {
process.kill(pid, 'SIGKILL');
} catch {
// already exited
}
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions e2e/helpers/appReadiness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import type {ElectronApplication} from 'playwright';
* We read the global directly, not via IPC.
*/
export async function waitForAppReady(app: ElectronApplication): Promise<void> {
// macOS CI runners are slower and may show Resume dialogs that delay startup.
// Use a longer timeout to accommodate this.
const timeout = process.platform === 'darwin' ? 60_000 : 30_000;

await expect.poll(
async () => {
try {
Expand All @@ -32,8 +36,14 @@ export async function waitForAppReady(app: ElectronApplication): Promise<void> {
}
},
{
message: 'Timed out waiting for __e2eAppReady. Check that initialize.ts sets it after handleMainWindowIsShown().',
timeout: 30_000,
message: [
'Timed out waiting for __e2eAppReady.',
`Timeout: ${timeout}ms.`,
'Check that initialize.ts sets __e2eAppReady after handleMainWindowIsShown().',
'On macOS, verify that global-setup.ts successfully wrote NSQuitAlwaysKeepsWindows=false',
'to prevent the "Reopen windows" dialog from blocking startup.',
].join(' '),
timeout,
intervals: [200, 500, 1000, 2000],
},
).toBe(true);
Expand Down
31 changes: 30 additions & 1 deletion e2e/utils/analyze-flaky-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,39 @@
}

if (suite.failures !== undefined || suite.errors !== undefined) {
// These are aggregated counts. Playwright includes retried failures in them.
// If the exit code is 0, all tests ultimately passed — only count definitive failures.
const exitCode = toNumber(process.env.PLAYWRIGHT_EXIT_CODE || '0');
if (exitCode === 0) {
return 0;
}
return toNumber(suite.failures) + toNumber(suite.errors);
}

return asArray(suite.testcase).filter((testcase) => testcase.failure || testcase.error).length;
// When no aggregated counts exist, inspect individual testcases.
// Filter out failures that were later retried and passed.
const cases = asArray(suite.testcase);
const definitiveFailures = cases.filter((testcase) => {
if (!testcase.failure && !testcase.error) {
return false;
}
// If this test name ends with a retry suffix like " (retry #1)",

Check failure on line 46 in e2e/utils/analyze-flaky-test.js

View workflow job for this annotation

GitHub Actions / build-mac-no-dmg

Expected line before comment

Check failure on line 46 in e2e/utils/analyze-flaky-test.js

View workflow job for this annotation

GitHub Actions / build-linux

Expected line before comment

Check failure on line 46 in e2e/utils/analyze-flaky-test.js

View workflow job for this annotation

GitHub Actions / build-win-no-installer

Expected line before comment
// and the base test (without suffix) also appears as a passing case,
// this failure was retried and resolved — don't count it.
const name = testcase.name || '';
const retryMatch = name.match(/^(.*) \(retry #\d+\)$/);
if (retryMatch) {
const baseName = retryMatch[1];
const hasPassingRetry = cases.some(
(c) => c.name === baseName && !c.failure && !c.error,
);
if (hasPassingRetry) {
return false;
}
}
return true;
});
return definitiveFailures.length;
}

function getFailureCountFromReport(report) {
Expand Down
Loading