feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled

Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
2026-08-13 18:34:25 +08:00
parent 61c3fa1df9
commit 9dfa06ffee
3785 changed files with 1085458 additions and 2 deletions
+653
View File
@@ -0,0 +1,653 @@
import { expect, test } from "@playwright/test";
import type { Page } from "@playwright/test";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css";
async function openMoreActionsMenu(page: Page, messageId: string) {
const row = page.locator(`[data-message-id="${messageId}"]`);
await row.hover();
await page.getByTestId(`more-actions-${messageId}`).click();
await expect(page.locator('[role="menuitem"]').first()).toBeVisible({
timeout: 5_000,
});
}
// Exercises the generic file-attachment UI contract end-to-end through the
// mock Tauri bridge: paperclip upload → composer chip → send → FileCard in the
// timeline. This guards the frontend wiring (the riskiest, previously
// untested path). It does NOT prove the real relay store/serve round-trip —
// that lives in the Rust media + relay tests.
test.beforeEach(async ({ page }) => {
await installMockBridge(page, {
deferredComposerUploads: true,
uploadDescriptors: [
{
url: `https://mock.relay/media/${"a".repeat(64)}.pdf`,
sha256: "a".repeat(64),
size: 12345,
type: "application/pdf",
uploaded: Math.floor(Date.now() / 1000),
filename: "quarterly-report.pdf",
},
],
});
});
async function chooseQuarterlyReport(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.from("quarterly report"),
mimeType: "application/pdf",
name: "quarterly-report.pdf",
});
}
async function chooseLargeVideo(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.alloc(16 * 1024 * 1024, 1),
mimeType: "video/mp4",
name: "large-video.mp4",
});
}
async function choosePhoto(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.from("photo"),
mimeType: "image/png",
name: "photo.png",
});
}
test("photos upload before Send without a queued spoiler control", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 1_000;
});
await page.getByTestId("channel-general").click();
await choosePhoto(page);
await expect(page.getByTestId("upload-progress")).toBeVisible();
await expect(page.getByTestId("composer-queued-video-spoiler")).toHaveCount(
0,
);
// Photos upload immediately, so they are in neither `pendingImeta` nor the
// queued list until the upload lands: Send stays blocked so the message
// cannot publish without the attachment.
await expect(page.getByTestId("send-message")).toBeDisabled();
await expect(page.getByTestId("upload-progress")).toHaveCount(0, {
timeout: 5_000,
});
await expect(page.getByTestId("composer-upload-progress")).toHaveCount(0);
await expect(page.getByTestId("send-message")).toBeEnabled();
await expect
.poll(() =>
page.evaluate(
() =>
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
.__BUZZ_E2E_COMMANDS__ ?? [],
),
)
.toContain("upload_media_bytes_raw");
});
test("opening edit during an immediate photo upload preserves the draft", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 1_000;
});
await page.getByTestId("channel-general").click();
await choosePhoto(page);
await expect(page.getByTestId("upload-progress")).toBeVisible();
await openMoreActionsMenu(page, "mock-general-welcome");
await page.getByTestId("edit-message-mock-general-welcome").click();
// Edit entry is rejected while the compacted draft cannot represent the
// reserved upload slot. The upload remains current and lands in the draft.
await expect(page.getByTestId("edit-target")).toHaveCount(0);
await expect(page.getByTestId("upload-progress")).toBeVisible();
await expect(page.getByTestId("upload-progress")).toHaveCount(0, {
timeout: 5_000,
});
await expect(page.getByTestId("message-composer")).toContainText(
"quarterly-report.pdf",
);
// Once settled, the same edit action enters edit mode normally.
await openMoreActionsMenu(page, "mock-general-welcome");
await page.getByTestId("edit-message-mock-general-welcome").click();
await expect(page.getByTestId("edit-target")).toBeVisible();
});
test("upload a file and see a FileCard in the timeline", async ({ page }) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
// Non-video files keep the established immediate-upload behavior.
await chooseQuarterlyReport(page);
// The composer shows a chip with the original filename.
await expect(page.getByTestId("message-composer")).toContainText(
"quarterly-report.pdf",
);
// Send the (attachment-only) message.
await page.getByTestId("send-message").click();
await expect(page.getByText("Sending")).toHaveCount(0);
// A FileCard renders in the timeline: a button carrying the filename. It
// downloads via the native `download_file` command (HTTP inside the app's
// tunnel + save dialog), NOT a plain `<a download>` link — a bare link
// 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 expectSmoothCorners(card);
await expect(card).toContainText("quarterly-report.pdf");
await card.click();
await expect
.poll(() =>
page.evaluate(
() =>
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
.__BUZZ_E2E_COMMANDS__ ?? [],
),
)
.toContain("download_file");
});
test("sends immediately and keeps upload progress across channels", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 1_000;
});
await page.getByTestId("channel-general").click();
await chooseLargeVideo(page);
await expect(page.getByTestId("composer-upload-progress")).toHaveCount(0);
await expect(page.getByTestId("composer-video-spoiler")).toHaveCount(0);
const queuedSpoiler = page.getByTestId("composer-queued-video-spoiler");
// Revealed on hover rather than removed from the DOM: the control stays
// focusable so keyboard users can reach it, but is transparent and
// click-through until the thumbnail is hovered or focused.
await expect(queuedSpoiler).toHaveCSS("opacity", "0");
await expect(queuedSpoiler).toHaveCSS("pointer-events", "none");
await page.getByTestId("composer-queued-media-attachment").hover();
await expect(queuedSpoiler).toBeVisible();
await expect(queuedSpoiler).toHaveCSS("opacity", "1");
await page.getByTestId("send-message").click();
await expect(page.getByTestId("message-composer")).not.toContainText(
"large-video.mp4",
);
await expect(page.getByTestId("composer-upload-progress")).toBeVisible();
await page.getByTestId("channel-random").click();
await expect(page.getByTestId("chat-title")).toHaveText("random");
await expect(page.getByTestId("composer-upload-progress")).toBeVisible();
await expect(page.getByTestId("composer-upload-progress")).toHaveCount(0, {
timeout: 5_000,
});
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("file-card").last()).toContainText(
"quarterly-report.pdf",
);
});
test("shows upload feedback before transferring a large file", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 5_000;
});
await page.getByTestId("channel-general").click();
await chooseLargeVideo(page);
const progress = page.getByTestId("composer-upload-progress");
await Promise.all([
page.getByTestId("send-message").click(),
expect(progress).toBeVisible({ timeout: 800 }),
]);
await expect(progress).toHaveAttribute("aria-label", "Preparing");
await expect(page.getByTestId("composer-upload-spinner")).toBeVisible();
await expect(page.getByTestId("composer-upload-percentage")).toHaveCount(0);
await expect
.poll(() =>
page.evaluate(
() =>
(
window as Window & {
__BUZZ_E2E_COMMAND_PAYLOADS__?: Array<{
command: string;
payload: { rawByteLength?: number } | null;
}>;
}
).__BUZZ_E2E_COMMAND_PAYLOADS__ ?? [],
),
)
.toContainEqual({
command: "upload_media_bytes_raw",
payload: { rawByteLength: 16 * 1024 * 1024 },
});
const uploadId = "background-media-upload-0-0";
await page.evaluate(async (id) => {
await window.__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PHASE__?.({
id,
phase: "processing-video",
});
}, uploadId);
await expect(progress).toHaveAttribute("aria-label", "Processing");
await waitForAnimations(page);
const processingPhaseBox = await page
.getByTestId("composer-upload-phase")
.boundingBox();
const processingStatusBox = await page
.getByTestId("composer-upload-status")
.boundingBox();
expect(processingPhaseBox).not.toBeNull();
expect(processingStatusBox).not.toBeNull();
expect(
(processingStatusBox?.x ?? 0) -
((processingPhaseBox?.x ?? 0) + (processingPhaseBox?.width ?? 0)),
).toBeGreaterThanOrEqual(3);
await expect(page.getByTestId("composer-upload-spinner")).toBeVisible();
await expect(page.getByTestId("composer-upload-percentage")).toHaveCount(0);
await page.evaluate(async (id) => {
await window.__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PHASE__?.({
id,
phase: "uploading",
});
await window.__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PROGRESS__?.({
id,
sent: 42,
total: 100,
});
}, uploadId);
await expect(progress).toHaveAttribute("aria-label", "Uploading 42%");
await waitForAnimations(page);
await expect(page.getByTestId("composer-upload-spinner")).toHaveCount(0);
await expect(page.getByTestId("composer-upload-percentage")).toHaveText(
"42%",
);
await page.getByTestId("composer-upload-cancel").click();
});
test("canceling a background upload prevents the message from publishing", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 1_000;
});
await page.getByTestId("channel-general").click();
await chooseLargeVideo(page);
await page.getByTestId("send-message").click();
await page.getByTestId("composer-upload-cancel").click();
await expect(page.getByTestId("composer-upload-progress")).toHaveCount(0);
await page.waitForTimeout(1_100);
await expect(page.getByTestId("file-card")).toHaveCount(0);
});
test("upload progress floats above the dock and lifts Jump to latest", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
const e2e = (
window as Window & {
__BUZZ_E2E__?: { mock?: { uploadDelayMs?: number } };
}
).__BUZZ_E2E__;
if (e2e?.mock) e2e.mock.uploadDelayMs = 2_000;
});
await page.getByTestId("channel-deep-history").click();
const timeline = page.getByTestId("message-timeline");
await expect(timeline.locator("[data-message-id]").first()).toBeVisible();
await timeline.evaluate((element) => {
element.scrollTop = Math.max(500, element.scrollHeight / 2);
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
const jumpToLatest = page.getByTestId("message-scroll-to-latest");
await expect(jumpToLatest).toBeVisible();
const restingBox = await jumpToLatest.boundingBox();
await chooseLargeVideo(page);
await page.getByTestId("send-message").click();
const uploadMotion = page.getByTestId("composer-upload-progress-motion");
await expect(uploadMotion).toBeVisible();
await timeline.evaluate((element) => {
element.scrollTop = Math.max(500, element.scrollHeight / 2);
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
await expect(jumpToLatest).toBeVisible();
await page.waitForTimeout(250);
const [uploadBox, dockBackdropBox, liftedBox] = await Promise.all([
uploadMotion.boundingBox(),
page.getByTestId("composer-dock-backdrop").boundingBox(),
jumpToLatest.boundingBox(),
]);
expect(restingBox).not.toBeNull();
expect(uploadBox).not.toBeNull();
expect(dockBackdropBox).not.toBeNull();
expect(liftedBox).not.toBeNull();
expect((dockBackdropBox?.y ?? 0) + 1).toBeGreaterThanOrEqual(
(uploadBox?.y ?? 0) + (uploadBox?.height ?? 0),
);
expect((liftedBox?.y ?? 0) + (liftedBox?.height ?? 0)).toBeLessThanOrEqual(
uploadBox?.y ?? 0,
);
expect(liftedBox?.y ?? 0).toBeLessThan((restingBox?.y ?? 0) - 10);
await page.getByTestId("composer-upload-cancel").click();
});
test("dropping a file on the channel column attaches it to the composer", async ({
page,
}) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
const dataTransfer = await page.evaluateHandle(() => {
const transfer = new DataTransfer();
transfer.items.add(
new File(["quarterly report"], "quarterly-report.pdf", {
type: "application/pdf",
}),
);
return transfer;
});
const dropZone = page.getByTestId("channel-drop-zone");
await dropZone.dispatchEvent("dragenter", { dataTransfer });
const overlay = dropZone.getByTestId("drop-zone-overlay");
const label = dropZone.getByTestId("drop-zone-label");
await expect(overlay).toBeVisible();
await expect(label).toContainText("Drop files to upload");
const [dropZoneBox, overlayBox, overlayStyles, stacking] = await Promise.all([
dropZone.boundingBox(),
overlay.boundingBox(),
page.evaluate(() => {
const overlayElement = document.querySelector<HTMLElement>(
'[data-testid="drop-zone-overlay"]',
);
const contentSurface = document.querySelector<HTMLElement>(
"[data-buzz-content-surface]",
);
if (!(overlayElement && contentSurface)) return null;
const overlayStyle = getComputedStyle(overlayElement);
return {
backdropFilter: overlayStyle.backdropFilter,
containerRadius: getComputedStyle(contentSurface).borderRadius,
overlayRadius: overlayStyle.borderRadius,
};
}),
page.evaluate(() => {
const overlayElement = document.querySelector<HTMLElement>(
'[data-testid="drop-zone-overlay"]',
);
const composerOverlayElement = document.querySelector<HTMLElement>(
'[data-testid="channel-composer-overlay"]',
);
if (!(overlayElement && composerOverlayElement)) return null;
return {
composer: Number.parseInt(
getComputedStyle(composerOverlayElement).zIndex,
10,
),
dropZone: Number.parseInt(getComputedStyle(overlayElement).zIndex, 10),
};
}),
]);
expect(overlayBox).toEqual(dropZoneBox);
expect(overlayStyles).not.toBeNull();
expect(overlayStyles?.overlayRadius).toBe(overlayStyles?.containerRadius);
expect(overlayStyles?.backdropFilter).toContain("blur");
expect(stacking).not.toBeNull();
expect(stacking?.dropZone).toBeGreaterThan(stacking?.composer ?? 0);
await dropZone.dispatchEvent("drop", { dataTransfer });
await expect(page.getByTestId("message-composer")).toContainText(
"quarterly-report.pdf",
);
});
for (const theme of ["buzz", "buzz-dark", "github-light", "github-dark"]) {
test(`drop prompt has accessible text contrast in ${theme}`, async ({
page,
}) => {
await page.goto("/");
await page.evaluate((selectedTheme) => {
window.localStorage.setItem("buzz-theme", selectedTheme);
}, theme);
await page.reload();
await page.getByTestId("channel-general").click();
const dataTransfer = await page.evaluateHandle(() => {
const transfer = new DataTransfer();
transfer.items.add(
new File(["contrast check"], "contrast-check.txt", {
type: "text/plain",
}),
);
return transfer;
});
const dropZone = page.getByTestId("channel-drop-zone");
await dropZone.dispatchEvent("dragenter", { dataTransfer });
const contrastRatio = await dropZone
.getByTestId("drop-zone-label")
.evaluate((element) => {
const parseRgb = (value: string) =>
(value.match(/[\d.]+/g) ?? []).slice(0, 3).map(Number);
const luminance = (color: number[]) =>
color
.map((channel) => {
const value = channel / 255;
return value <= 0.04045
? value / 12.92
: ((value + 0.055) / 1.055) ** 2.4;
})
.reduce(
(sum, channel, index) =>
sum + channel * [0.2126, 0.7152, 0.0722][index],
0,
);
const style = getComputedStyle(element);
const foreground = luminance(parseRgb(style.color));
const background = luminance(parseRgb(style.backgroundColor));
return (
(Math.max(foreground, background) + 0.05) /
(Math.min(foreground, background) + 0.05)
);
});
expect(contrastRatio).toBeGreaterThanOrEqual(4.5);
});
}
test("forum posts emit a FileCard for generic attachments, not a broken image", async ({
page,
}) => {
// Regression guard for the ForumComposer bug: it used to hand-build content
// as `![image](url)` for every non-video attachment (and omit the `filename`
// imeta tag), so a PDF posted in a forum rendered as a broken inline image
// and lost its label. The fix routes forum/notes posts through the same
// `buildOutgoingMessage` builder as chat. This test would fail (no FileCard)
// if ForumComposer ever drifts back to hand-building media markdown.
await page.goto("/");
// "watercooler" is a seeded forum the mock identity is a member of.
await page.getByTestId("channel-watercooler").click();
// Open the new-post composer ("Start a new post...").
await page.getByRole("button", { name: "Start a new post..." }).click();
// Paperclip → mocked pick_and_upload_media returns the PDF descriptor.
await page.getByRole("button", { name: "Attach file" }).click();
// Submit the (attachment-only) forum post.
await page.getByTestId("send-message").click();
// The post renders through the shared Markdown component as a FileCard —
// a button carrying the filename that downloads via the native
// `download_file` command — NOT an inline image and NOT a bare link.
const card = page.getByTestId("file-card");
await expect(card).toBeVisible();
await expect(card).toContainText("quarterly-report.pdf");
await card.click();
await expect
.poll(() =>
page.evaluate(
() =>
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
.__BUZZ_E2E_COMMANDS__ ?? [],
),
)
.toContain("download_file");
});
test("a queued attachment can be removed without a mouse", async ({ page }) => {
// Regression: the queued remove badge is revealed on hover, but hiding it
// with `display: none` made it unfocusable, leaving keyboard-only users no
// way to drop a queued video before sending.
await page.goto("/");
await page.getByTestId("channel-general").click();
await chooseLargeVideo(page);
const queued = page.getByTestId("composer-queued-media-attachment");
await expect(queued).toBeVisible();
const remove = queued.getByRole("button", { name: "Remove attachment" });
// Focusable while transparent — this is what `display: none` prevented.
await remove.focus();
await expect(remove).toBeFocused();
// Focus reveals it, so the user can see what they are about to activate.
await expect(remove).toHaveCSS("opacity", "1");
await page.keyboard.press("Enter");
await expect(queued).toHaveCount(0);
await expect(page.getByTestId("message-composer")).not.toContainText(
"large-video.mp4",
);
});
test("an uploaded attachment's remove button is named and keyboard-operable", async ({
page,
}) => {
// Companion to the queued case: these badges are now in the tab order, so
// every icon-only remove button needs an accessible name a screen reader can
// read. Images and non-media files render through different branches, so
// both are checked here.
await installMockBridge(page, {
deferredComposerUploads: true,
uploadDescriptors: [
{
url: `https://mock.relay/media/${"b".repeat(64)}.png`,
sha256: "b".repeat(64),
size: 2048,
type: "image/png",
uploaded: Math.floor(Date.now() / 1000),
dim: "320x200",
filename: "photo.png",
},
],
});
await page.goto("/");
await page.getByTestId("channel-general").click();
const composer = page.getByTestId("message-composer");
const remove = composer.getByRole("button", { name: "Remove attachment" });
// Image attachment (MediaAttachmentItem).
await choosePhoto(page);
await expect(composer.getByTestId("composer-media-attachment")).toBeVisible();
await expect(remove).toHaveCount(1);
await remove.focus();
await expect(remove).toBeFocused();
await expect(remove).toHaveCSS("opacity", "1");
await page.keyboard.press("Enter");
await expect(composer.getByTestId("composer-media-attachment")).toHaveCount(
0,
);
});
test("a non-media attachment's remove button is named and keyboard-operable", async ({
page,
}) => {
// The file-card branch renders its own remove badge, so it needs the same
// accessible name as the image and queued ones.
await page.goto("/");
await page.getByTestId("channel-general").click();
await chooseQuarterlyReport(page);
const composer = page.getByTestId("message-composer");
await expect(composer).toContainText("quarterly-report.pdf");
const remove = composer.getByRole("button", { name: "Remove attachment" });
await expect(remove).toHaveCount(1);
await remove.focus();
await expect(remove).toBeFocused();
await expect(remove).toHaveCSS("opacity", "1");
await page.keyboard.press("Enter");
await expect(composer).not.toContainText("quarterly-report.pdf");
});