The image E2E now runs in the main conversation, which is a group thread, so the browser test covers group uploads and reads from the artifact route as well as bot threads. Bot file attachments keep their run's operation id for tracing, and the non-streaming attach cap is the inline cap directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { expect, test } from "@playwright/test";
|
|
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
|
|
|
|
test.describe.configure({ mode: "serial" });
|
|
|
|
// VP9 in MP4: Playwright's Chromium has no H.264 decoder, real browsers play both.
|
|
const clip = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "media-clip.mp4");
|
|
const pixel = Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
|
"base64",
|
|
);
|
|
|
|
test("an uploaded video streams from the artifact route and plays inline", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `media-video-${stamp}@rakazo.test`, "password12", "Media Video");
|
|
await completeOnboarding(page);
|
|
|
|
const upload = page.waitForResponse(
|
|
(response) =>
|
|
response.request().method() === "POST" && /\/api\/artifacts\?/.test(response.url()),
|
|
);
|
|
await page.locator('input[type="file"]').setInputFiles(clip);
|
|
await expect(page.getByRole("button", { name: "Remove media-clip.mp4" })).toBeVisible();
|
|
await page.getByRole("combobox", { name: /Message/ }).fill("here is the clip");
|
|
await page.keyboard.press("Enter");
|
|
expect((await upload).status()).toBe(201);
|
|
|
|
const video = page.getByTestId("media-video").locator("video");
|
|
await expect(video).toBeVisible({ timeout: 30_000 });
|
|
await expect(video).toHaveAttribute("src", /^\/api\/artifacts\/[^/?]+$/);
|
|
await expect
|
|
.poll(() => video.evaluate((element: HTMLVideoElement) => element.readyState), {
|
|
timeout: 15_000,
|
|
})
|
|
.toBeGreaterThanOrEqual(1);
|
|
expect(await video.evaluate((element: HTMLVideoElement) => element.duration)).toBeCloseTo(1, 0);
|
|
|
|
await video.evaluate(async (element: HTMLVideoElement) => {
|
|
element.muted = true;
|
|
await element.play();
|
|
});
|
|
await expect
|
|
.poll(() => video.evaluate((element: HTMLVideoElement) => element.currentTime))
|
|
.toBeGreaterThan(0);
|
|
await video.evaluate((element: HTMLVideoElement) => {
|
|
element.pause();
|
|
element.currentTime = 0.5;
|
|
element.scrollIntoView({ block: "center" });
|
|
});
|
|
await captureScreenshot(page, testInfo, "media-video-block");
|
|
});
|
|
|
|
test("an image in the main conversation opens and downloads from the artifact route", async ({
|
|
page,
|
|
}) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `media-image-${stamp}@rakazo.test`, "password12", "Media Image");
|
|
// The main conversation is a group thread, so this covers group uploads and reads.
|
|
await completeOnboarding(page, undefined, { mainConversation: true });
|
|
|
|
await page
|
|
.locator('input[type="file"]')
|
|
.setInputFiles({ name: "pixel.png", mimeType: "image/png", buffer: pixel });
|
|
await page.getByRole("combobox", { name: /Message/ }).fill("a pixel");
|
|
await page.keyboard.press("Enter");
|
|
|
|
const thumbnail = page.getByTestId("transcript").getByRole("img", { name: "pixel.png" });
|
|
await expect(thumbnail).toBeVisible({ timeout: 30_000 });
|
|
await expect(thumbnail).toHaveAttribute("src", /^\/api\/artifacts\/[^/?]+$/);
|
|
await thumbnail.click();
|
|
|
|
const dialog = page.getByRole("dialog", { name: "pixel.png" });
|
|
const download = dialog.getByRole("link", { name: "Download pixel.png" });
|
|
await expect(download).toHaveAttribute("href", /\/api\/artifacts\/[^/?]+\?download=1$/);
|
|
const saved = page.waitForEvent("download");
|
|
await download.click();
|
|
expect((await saved).suggestedFilename()).toBe("pixel.png");
|
|
});
|