feat: show call participants in room list (Discord-style)
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled

This commit is contained in:
sorB
2026-05-10 14:25:35 +02:00
parent b797925316
commit 3da363517f
4610 changed files with 827237 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
/test-results/
/html-report/
# Only commit snapshots from Linux
/snapshots/**/*.png
!/snapshots/**/*-linux.png
+19
View File
@@ -0,0 +1,19 @@
FROM mcr.microsoft.com/playwright:v1.59.1-jammy@sha256:8a0360d39d1973be506dd59002904a774f6d697d4946c94063b3fd006461c8ff
WORKDIR /work
RUN apt-get update && \
apt-get -y install xvfb dbus-x11 && \
apt-get purge -y --auto-remove && \
rm -rf /var/lib/apt/lists/* && \
corepack enable
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
ENV GITHUB_ACTIONS=1
ENV DEBUG=pw:browser
# switch to node user
USER 1000:1000
COPY apps/desktop/playwright/docker-entrypoint.sh /opt/docker-entrypoint.sh
ENTRYPOINT ["bash", "/opt/docker-entrypoint.sh"]
@@ -0,0 +1,12 @@
#!/bin/bash
set -e
echo "Starting Xvfb"
Xvfb :99 -ac &
sleep 2
export DISPLAY=:99
PLAYWRIGHT_HTML_OPEN=never ELEMENT_DESKTOP_EXECUTABLE="./dist/linux-unpacked/element-desktop" \
exec pnpm -C apps/desktop exec playwright test --update-snapshots --reporter line,html "$1"
@@ -0,0 +1,44 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { test, expect } from "../../element-desktop-test.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
test.describe("App config options", () => {
test.describe("Should load custom config via env", () => {
test.slow();
test.use({
extraEnv: {
ELEMENT_DESKTOP_CONFIG_JSON: resolve(__dirname, "../..", "fixtures/custom-config.json"),
},
});
test("should launch and use configured homeserver", async ({ page }) => {
await page.locator("#matrixchat").waitFor();
await page.locator(".mx_Welcome").waitFor();
await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
await page.getByText("Sign in").click();
await page.getByText("matrix.example.org", { exact: true }).waitFor();
});
});
test.describe("Should load custom config via argument", () => {
test.slow();
test.use({
extraArgs: ["--config", resolve(__dirname, "../..", "fixtures/custom-config.json")],
});
test("should launch and use configured homeserver", async ({ page }) => {
await page.locator("#matrixchat").waitFor();
await page.locator(".mx_Welcome").waitFor();
await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
await page.getByText("Sign in").click();
await page.getByText("matrix.example.org", { exact: true }).waitFor();
});
});
});
@@ -0,0 +1,75 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-desktop-test.js";
declare global {
interface ElectronPlatform {
getEventIndexingManager():
| {
supportsEventIndexing(): Promise<boolean>;
}
| undefined;
getPickleKey(userId: string, deviceId: string): Promise<string | null>;
createPickleKey(userId: string, deviceId: string): Promise<string | null>;
}
interface Window {
mxPlatformPeg: {
get(): ElectronPlatform;
};
}
}
test.describe("App launch", () => {
test.slow();
test.beforeEach(async ({ page }) => {
await page.locator("#matrixchat").waitFor();
await page.locator(".mx_Welcome").waitFor();
});
test("should launch and render the welcome view successfully", async ({ page }) => {
await expect(page).toHaveURL("vector://vector/webapp/#/welcome");
await expect(page).toHaveScreenshot();
});
test("should launch and render the welcome view successfully and support seshat", async ({ page }) => {
await expect(
page.evaluate<boolean>(async () => {
return window.mxPlatformPeg.get().getEventIndexingManager()?.supportsEventIndexing();
}),
).resolves.toBeTruthy();
});
test.describe("safeStorage", () => {
const userId = "@user:server";
const deviceId = "ABCDEF";
test("should be supported", async ({ page }) => {
await expect(
page.evaluate(
([userId, deviceId]) => window.mxPlatformPeg.get().createPickleKey(userId, deviceId),
[userId, deviceId],
),
).resolves.not.toBeNull();
});
});
test.describe("--no-update", () => {
test.use({
extraArgs: ["--no-update"],
});
// XXX: this test works fine locally but in CI the app start races with the test plumbing up the stdout/stderr pipes
// which means the logs are missed, disabling for now.
test.skip("should respect option", async ({ page, stdout }) => {
expect(stdout.data.toString()).toContain("Auto update disabled via command line flag");
});
});
});
@@ -0,0 +1,36 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-desktop-test.js";
declare global {
interface ElectronPlatform {
getOidcCallbackUrl(): URL;
}
interface Window {
mxPlatformPeg: {
get(): ElectronPlatform;
};
}
}
test.describe("OIDC Native", () => {
test.slow();
test.beforeEach(async ({ page }) => {
await page.locator(".mx_Welcome").waitFor();
});
test("should use OIDC callback URL without authority component", async ({ page }) => {
await expect(
page.evaluate<string>(() => {
return window.mxPlatformPeg.get().getOidcCallbackUrl().toString();
}),
).resolves.toMatch(/io\.element\.(desktop|nightly):\/vector\/webapp\//);
});
});
@@ -0,0 +1,126 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { _electron as electron, test as base, expect as baseExpect, type ElectronApplication } from "@playwright/test";
import fs from "node:fs/promises";
import path, { dirname } from "node:path";
import os from "node:os";
import { fileURLToPath } from "node:url";
import { PassThrough } from "node:stream";
/**
* A PassThrough stream that captures all data written to it.
*/
class CapturedPassThrough extends PassThrough {
private _chunks: any[] = [];
public constructor() {
super();
super.on("data", this.onData);
}
private onData = (chunk: any): void => {
this._chunks.push(chunk);
};
public get data(): Buffer {
return Buffer.concat(this._chunks);
}
}
interface Fixtures {
app: ElectronApplication;
tmpDir: string;
extraEnv: Record<string, string>;
extraArgs: string[];
// Utilities to capture stdout and stderr for tests to make assertions against
stdout: CapturedPassThrough;
stderr: CapturedPassThrough;
}
const __dirname = dirname(fileURLToPath(import.meta.url));
export const test = base.extend<Fixtures>({
extraEnv: {},
extraArgs: [],
// eslint-disable-next-line no-empty-pattern
stdout: async ({}, use) => {
await use(new CapturedPassThrough());
},
// eslint-disable-next-line no-empty-pattern
stderr: async ({}, use) => {
await use(new CapturedPassThrough());
},
// eslint-disable-next-line no-empty-pattern
tmpDir: async ({}, use) => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "element-desktop-tests-"));
await use(tmpDir);
await fs.rm(tmpDir, { recursive: true });
},
app: async ({ tmpDir, extraEnv, extraArgs, stdout, stderr }, use) => {
const args = ["--profile-dir", tmpDir, ...extraArgs];
if (process.env.GITHUB_ACTIONS) {
args.push("--disable-gpu");
if (process.platform === "linux") {
if (process.getuid() === 0) {
args.push("--no-sandbox");
}
// GitHub Actions hosted runner lacks dbus and a compatible keyring, so we need to force plaintext storage
args.push("--storage-mode", "force-plaintext");
} else if (process.platform === "darwin") {
// GitHub Actions hosted runner has no working default keychain, so allow plaintext storage
args.push("--storage-mode", "allow-plaintext");
}
}
const executablePath = process.env["ELEMENT_DESKTOP_EXECUTABLE"];
if (!executablePath) {
// Unpackaged mode testing
args.unshift(path.join(__dirname, "..", "lib", "electron-main.js"));
}
console.log(`Launching '${executablePath || "electron"}' with args ${args.join(" ")}`);
const app = await electron.launch({
env: {
...process.env,
...extraEnv,
},
executablePath,
args,
});
app.process().stdout.pipe(stdout).pipe(process.stdout);
app.process().stderr.pipe(stderr).pipe(process.stderr);
await app.firstWindow();
// Block matrix.org access to ensure consistent tests
const context = app.context();
await context.route("https://matrix.org/**", async (route) => {
await route.abort();
});
await use(app);
},
page: async ({ app }, use) => {
const window = await app.firstWindow();
await use(window);
await app.close().catch((e) => {
console.error(e);
});
},
});
export const expect = baseExpect;
@@ -0,0 +1,10 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.org"
},
"m.identity_server": {
"base_url": "https://identity.example.org"
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"resolveJsonModule": true,
"moduleResolution": "bundler",
"esModuleInterop": true,
"target": "es2022",
"module": "ESNext",
"lib": ["es2024", "dom", "dom.iterable"],
"strictNullChecks": false,
"types": ["node"]
},
"include": ["**/*.ts"]
}