window.getComputedStyle(element).borderRadius);
diff --git a/desktop/tests/e2e/custom-emoji.spec.ts b/desktop/tests/e2e/custom-emoji.spec.ts
index 42c7e707a..488e76bba 100644
--- a/desktop/tests/e2e/custom-emoji.spec.ts
+++ b/desktop/tests/e2e/custom-emoji.spec.ts
@@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
+import { expectedScaledPx } from "../helpers/css";
// Custom-emoji end-to-end guard.
//
@@ -271,14 +272,34 @@ test("reacting with a custom emoji renders via the loopback media proxy", async
}),
)
.toBe("0");
+ const expectedInlineReactionButtonWidth = Math.round(
+ await expectedScaledPx(inlineAddReactionButton, 40),
+ );
+ const minExpectedInlineReactionButtonHeight = Math.round(
+ await expectedScaledPx(inlineAddReactionButton, 28),
+ );
+ await expect
+ .poll(() =>
+ inlineAddReactionButton.evaluate((button) => {
+ const rect = button.getBoundingClientRect();
+ return {
+ height: Math.round(rect.height),
+ width: Math.round(rect.width),
+ };
+ }),
+ )
+ .toEqual({
+ height: expect.any(Number),
+ width: expectedInlineReactionButtonWidth,
+ });
await expect
.poll(() =>
inlineAddReactionButton.evaluate((button) => {
const rect = button.getBoundingClientRect();
- return `${Math.round(rect.width)}x${Math.round(rect.height)}`;
+ return Math.round(rect.height);
}),
)
- .toBe("40x28");
+ .toBeGreaterThanOrEqual(minExpectedInlineReactionButtonHeight);
await expect
.poll(() =>
inlineAddReactionButton.evaluate((button) => {
@@ -292,10 +313,24 @@ test("reacting with a custom emoji renders via the loopback media proxy", async
.poll(() =>
inlineAddReactionButton.evaluate((button) => {
const rect = button.getBoundingClientRect();
- return `${Math.round(rect.width)}x${Math.round(rect.height)}`;
+ return {
+ height: Math.round(rect.height),
+ width: Math.round(rect.width),
+ };
+ }),
+ )
+ .toEqual({
+ height: expect.any(Number),
+ width: expectedInlineReactionButtonWidth,
+ });
+ await expect
+ .poll(() =>
+ inlineAddReactionButton.evaluate((button) => {
+ const rect = button.getBoundingClientRect();
+ return Math.round(rect.height);
}),
)
- .toBe("40x28");
+ .toBeGreaterThanOrEqual(minExpectedInlineReactionButtonHeight);
// Toggle the reaction back off: click the pill, which fires remove_reaction
// -> emits a kind:5 deletion targeting the reaction event. The pill must
diff --git a/desktop/tests/e2e/file-attachment.spec.ts b/desktop/tests/e2e/file-attachment.spec.ts
index 41eb55e52..ac3148630 100644
--- a/desktop/tests/e2e/file-attachment.spec.ts
+++ b/desktop/tests/e2e/file-attachment.spec.ts
@@ -47,7 +47,7 @@ test("upload a file and see a FileCard in the timeline", async ({ page }) => {
// escapes the webview to the OS browser and hits a corporate CDN page.
const card = page.getByTestId("file-card").last();
await expect(card).toBeVisible();
- await expectCornerRadiusPx(card, 16);
+ await expectCornerRadiusPx(card, 16, { scaleWithRootFont: true });
await expectSmoothCorners(card);
await expect(card).toContainText("quarterly-report.pdf");
diff --git a/desktop/tests/e2e/image-attachment-gallery.spec.ts b/desktop/tests/e2e/image-attachment-gallery.spec.ts
index df4cfc980..6df4eaacf 100644
--- a/desktop/tests/e2e/image-attachment-gallery.spec.ts
+++ b/desktop/tests/e2e/image-attachment-gallery.spec.ts
@@ -126,8 +126,10 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => {
const triggers = row.getByTestId("message-image-lightbox-trigger");
await expect(triggers).toHaveCount(3);
- await expectCornerRadiusPx(triggers.first(), 16);
- await expectCornerRadiusPx(triggers.first().locator("img"), 16);
+ await expectCornerRadiusPx(triggers.first(), 16, { scaleWithRootFont: true });
+ await expectCornerRadiusPx(triggers.first().locator("img"), 16, {
+ scaleWithRootFont: true,
+ });
await expectSmoothCorners(triggers.first().locator("img"));
await triggers.first().click();
@@ -137,7 +139,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => {
const lightboxSurface = page
.locator("[data-image-lightbox-frame] > div > div")
.first();
- await expectCornerRadiusPx(lightboxSurface, 16);
+ await expectCornerRadiusPx(lightboxSurface, 16, { scaleWithRootFont: true });
await expectSmoothCorners(lightboxSurface);
await expect(
page.getByRole("button", { name: "Previous image" }),
@@ -174,6 +176,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => {
await expectCornerRadiusPx(
page.locator("[data-image-lightbox-frame] > div > div").first(),
16,
+ { scaleWithRootFont: true },
);
expect(Math.abs(closingFrameBox.x - currentThumbnailBox.x)).toBeLessThan(2);
diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts
index 1c7202125..85f843ba0 100644
--- a/desktop/tests/e2e/messaging.spec.ts
+++ b/desktop/tests/e2e/messaging.spec.ts
@@ -1,7 +1,11 @@
import { expect, test, type Locator } from "@playwright/test";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
-import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css";
+import {
+ expectCornerRadiusPx,
+ expectSmoothCorners,
+ expectedScaledPx,
+} from "../helpers/css";
import { openSettings } from "../helpers/settings";
async function expectThreadReplyUnobscured(row: Locator) {
@@ -179,7 +183,7 @@ test("supported link previews keep the message link visible", async ({
).toBeVisible();
const previewCard = row.locator('[data-link-preview="github-pull-request"]');
await expect(previewCard).toBeVisible();
- await expectCornerRadiusPx(previewCard, 16);
+ await expectCornerRadiusPx(previewCard, 16, { scaleWithRootFont: true });
await expectSmoothCorners(previewCard);
});
@@ -233,7 +237,9 @@ test("copy a rendered code block and paste it back as code", async ({
const codeBlock = page.locator("[data-code-block]");
await expect(codeBlock).toHaveCount(1);
- await expectCornerRadiusPx(codeBlock.locator("pre"), 16);
+ await expectCornerRadiusPx(codeBlock.locator("pre"), 16, {
+ scaleWithRootFont: true,
+ });
await expectSmoothCorners(codeBlock.locator("pre"));
const copyButton = page.getByLabel("Copy code block");
@@ -647,7 +653,19 @@ test("opens a single-level thread panel with inline expansion", async ({
return `${Math.round(rect.width)}x${Math.round(rect.height)}`;
}),
)
- .toBe("24x24");
+ .toBe(
+ `${Math.round(
+ await expectedScaledPx(
+ rootSummaryRow.getByTestId("message-thread-summary-participant"),
+ 24,
+ ),
+ )}x${Math.round(
+ await expectedScaledPx(
+ rootSummaryRow.getByTestId("message-thread-summary-participant"),
+ 24,
+ ),
+ )}`,
+ );
const summaryGeometry = await measureThreadSummaryGeometry(rootSummaryRow);
expect(
Math.abs(summaryGeometry.authorLeft - summaryGeometry.bodyLeft),
diff --git a/desktop/tests/e2e/profile.spec.ts b/desktop/tests/e2e/profile.spec.ts
index 0ff0e8a79..e6f236731 100644
--- a/desktop/tests/e2e/profile.spec.ts
+++ b/desktop/tests/e2e/profile.spec.ts
@@ -361,11 +361,15 @@ test("swaps the avatar preview and mode tabs while editing", async ({
await waitForAvatarEditorToClose(page);
await expect(tabList).toHaveCount(0);
+ // Closing the editor must return the settings scroller to its pre-edit
+ // position — ProfileSettingsCard snapshots the scrollTop on open and
+ // re-applies it on close, so the preview lands back where it started.
const restoredPreviewBox = await previewFrame.boundingBox();
if (!restoredPreviewBox) {
throw new Error("Profile avatar preview did not restore bounds.");
}
expect(Math.abs(restoredPreviewBox.y - closedPreviewBox.y)).toBeLessThan(8);
+ await expect(page.getByTestId("profile-avatar-edit")).toBeVisible();
});
test("highlights the avatar drop target while dragging an image", async ({
@@ -527,7 +531,7 @@ test("renders emoji avatars with a static background layer", async ({
);
await expect(page.getByTestId("profile-avatar-preview-emoji")).toHaveCSS(
"font-size",
- "96px",
+ "105.6px",
);
});
@@ -1219,15 +1223,15 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("+", "Equal", true);
await expect.poll(getTextScaleState).toEqual({
- fontSize: "17.6px",
- storedScale: "1.1",
+ fontSize: "19.2px",
+ storedScale: "1.2",
webviewZoom: 1,
});
await dispatchPrimaryShortcut("-", "Minus");
await expect.poll(getTextScaleState).toEqual({
- fontSize: "16px",
+ fontSize: "17.6px",
storedScale: null,
webviewZoom: 1,
});
@@ -1236,15 +1240,15 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("+", "Equal", true);
await expect.poll(getTextScaleState).toEqual({
- fontSize: "19.2px",
- storedScale: "1.2",
+ fontSize: "20.8px",
+ storedScale: "1.3",
webviewZoom: 1,
});
await dispatchPrimaryShortcut("0", "Digit0");
await expect.poll(getTextScaleState).toEqual({
- fontSize: "16px",
+ fontSize: "17.6px",
storedScale: null,
webviewZoom: 1,
});
diff --git a/desktop/tests/e2e/video-attachment.spec.ts b/desktop/tests/e2e/video-attachment.spec.ts
index c4bc4c70b..ff9a894b5 100644
--- a/desktop/tests/e2e/video-attachment.spec.ts
+++ b/desktop/tests/e2e/video-attachment.spec.ts
@@ -253,7 +253,7 @@ test("video upload previews use poster frames and inline videos open review mode
const inlinePlayer = page.getByTestId("video-player").last();
const inlineSurface = inlinePlayer.locator("[data-smooth-corners]").first();
- await expectCornerRadiusPx(inlineSurface, 16);
+ await expectCornerRadiusPx(inlineSurface, 16, { scaleWithRootFont: true });
await expectSmoothCorners(inlineSurface);
const inlineVideo = inlinePlayer.locator("video");
await inlinePlayer.getByRole("button", { name: "Play video" }).click();
diff --git a/desktop/tests/e2e/virtualization.spec.ts b/desktop/tests/e2e/virtualization.spec.ts
index 1270e51dd..58b1e6012 100644
--- a/desktop/tests/e2e/virtualization.spec.ts
+++ b/desktop/tests/e2e/virtualization.spec.ts
@@ -30,7 +30,18 @@ async function seedChannelSections(page: Page) {
// 6px distance constraint, so a single move never starts a drag. This walks the
// pointer down, past the activation threshold, onto the target, then releases —
// the sequence dnd-kit needs to fire onDragEnd and commit the reorder.
+//
+// Both handles are scrolled on-screen first: at the 1.1 default text scale the
+// sidebar overflows the 720px test viewport, and the click that navigates to
+// channel-general auto-scrolls the sidebar to center that row — leaving the
+// section headers above the scroller's clip. boundingBox() still reports those
+// off-screen positions, so raw mouse coords would land on the workspace rail
+// instead of the sortable rows and the drag would never activate. Scrolling the
+// handles into view keeps the coords honest; the reorder itself must still
+// activate, drop, and commit for the test to pass.
async function dragOver(page: Page, source: Locator, target: Locator) {
+ await source.scrollIntoViewIfNeeded();
+ await target.scrollIntoViewIfNeeded();
const from = await source.boundingBox();
const to = await target.boundingBox();
if (!from || !to) throw new Error("drag handles not laid out");
diff --git a/desktop/tests/helpers/css.ts b/desktop/tests/helpers/css.ts
index 0985fb788..0f0b586b5 100644
--- a/desktop/tests/helpers/css.ts
+++ b/desktop/tests/helpers/css.ts
@@ -1,8 +1,25 @@
import { expect, type Locator } from "@playwright/test";
+export async function currentRootFontScale(locator: Locator) {
+ const rootFontSize = await locator.evaluate(() =>
+ Number.parseFloat(
+ window.getComputedStyle(document.documentElement).fontSize,
+ ),
+ );
+ return Number.isFinite(rootFontSize) ? rootFontSize / 16 : 1;
+}
+
+export async function expectedScaledPx(
+ locator: Locator,
+ pxAtDefaultScale: number,
+) {
+ return pxAtDefaultScale * (await currentRootFontScale(locator));
+}
+
export async function expectCornerRadiusPx(
locator: Locator,
expectedRadiusPx: number,
+ options: { scaleWithRootFont?: boolean } = {},
) {
const measurement = await locator.evaluate((element) => {
const style = window.getComputedStyle(element);
@@ -65,13 +82,18 @@ export async function expectCornerRadiusPx(
className: element.getAttribute("class") ?? "",
radius,
rawRadius,
+ rootFontSize,
};
});
+ const expected = options.scaleWithRootFont
+ ? expectedRadiusPx * (measurement.rootFontSize / 16)
+ : expectedRadiusPx;
+
expect(
measurement.radius,
- `Expected ${expectedRadiusPx}px corner radius, got ${measurement.rawRadius} on class "${measurement.className}".`,
- ).toBeCloseTo(expectedRadiusPx, 0);
+ `Expected ${expected}px corner radius, got ${measurement.rawRadius} on class "${measurement.className}".`,
+ ).toBeCloseTo(expected, 0);
}
export async function expectSmoothCorners(