Attach image e2e tests (#301)

This commit is contained in:
Will Chen
2025-06-01 00:44:19 -07:00
committed by GitHub
parent 440482356b
commit c0adf8d3f2
10 changed files with 137 additions and 25 deletions

View File

@@ -0,0 +1,30 @@
import { test } from "./helpers/test_helper";
// attach image is implemented in two separate components
// - HomeChatInput
// - ChatInput
// so we need to test both
test("attach image - home chat", async ({ po }) => {
await po.setUp();
await po
.getHomeChatInputContainer()
.locator("input[type='file']")
.setInputFiles("e2e-tests/fixtures/images/logo.png");
await po.sendPrompt("[dump]");
await po.snapshotServerDump({ onlyLastMessage: true });
await po.snapshotMessages({ replaceDumpPath: true });
});
test("attach image - chat", async ({ po }) => {
await po.setUp({ autoApprove: true });
await po.sendPrompt("basic");
await po
.getChatInputContainer()
.locator("input[type='file']")
.setInputFiles("e2e-tests/fixtures/images/logo.png");
await po.sendPrompt("[dump]");
await po.snapshotServerDump({ onlyLastMessage: true });
await po.snapshotMessages({ replaceDumpPath: true });
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -20,7 +20,25 @@ class PageObject {
await this.selectTestModel();
}
async snapshotMessages() {
async snapshotMessages({
replaceDumpPath = false,
}: { replaceDumpPath?: boolean } = {}) {
if (replaceDumpPath) {
// Update page so that "[[dyad-dump-path=*]]" is replaced with a placeholder path
// which is stable across runs.
await this.page.evaluate(() => {
const messagesList = document.querySelector(
"[data-testid=messages-list]",
);
if (!messagesList) {
throw new Error("Messages list not found");
}
messagesList.innerHTML = messagesList.innerHTML.replace(
/\[\[dyad-dump-path=([^\]]+)\]\]/,
"[[dyad-dump-path=*]]",
);
});
}
await expect(this.page.getByTestId("messages-list")).toMatchAriaSnapshot();
}
@@ -41,7 +59,9 @@ class PageObject {
await expect(iframe.contentFrame().locator("body")).toMatchAriaSnapshot();
}
async snapshotServerDump() {
async snapshotServerDump({
onlyLastMessage = false,
}: { onlyLastMessage?: boolean } = {}) {
// Get the text content of the messages list
const messagesListText = await this.page
.getByTestId("messages-list")
@@ -62,7 +82,9 @@ class PageObject {
const dumpContent = fs.readFileSync(dumpFilePath, "utf-8");
// Perform snapshot comparison
expect(prettifyDump(dumpContent)).toMatchSnapshot("server-dump.txt");
expect(prettifyDump(dumpContent, { onlyLastMessage })).toMatchSnapshot(
"server-dump.txt",
);
}
async waitForChatCompletion() {
@@ -85,13 +107,21 @@ class PageObject {
return this.page.getByRole("button", { name: "Undo" });
}
getHomeChatInputContainer() {
return this.page.getByTestId("home-chat-input-container");
}
getChatInputContainer() {
return this.page.getByTestId("chat-input-container");
}
getChatInput() {
return this.page.getByRole("textbox", { name: "Ask Dyad to build..." });
}
async sendPrompt(prompt: string) {
await this.page
.getByRole("textbox", { name: "Ask Dyad to build..." })
.click();
await this.page
.getByRole("textbox", { name: "Ask Dyad to build..." })
.fill(prompt);
await this.getChatInput().click();
await this.getChatInput().fill(prompt);
await this.page.getByRole("button", { name: "Send message" }).click();
await this.waitForChatCompletion();
}
@@ -310,15 +340,22 @@ export const test = base.extend<{
],
});
function prettifyDump(dumpContent: string) {
function prettifyDump(
dumpContent: string,
{ onlyLastMessage = false }: { onlyLastMessage?: boolean } = {},
) {
const parsedDump = JSON.parse(dumpContent) as Array<{
role: string;
content: string;
}>;
return parsedDump
const messages = onlyLastMessage ? parsedDump.slice(-1) : parsedDump;
return messages
.map((message) => {
const content = message.content
const content = Array.isArray(message.content)
? JSON.stringify(message.content)
: message.content
// We remove package.json because it's flaky.
// Depending on whether pnpm install is run, it will be modified,
// and the contents and timestamp (thus affecting order) will be affected.

View File

@@ -0,0 +1,25 @@
- paragraph: basic
- 'button "Thinking `dyad-write>`: I''ll think about the problem and write a bug report. dyad-write> dyad-write path=\"file1.txt\"> Fake dyad write /dyad-write>"':
- img
- img
- paragraph:
- code: "`dyad-write>`"
- text: ": I'll think about the problem and write a bug report."
- paragraph: dyad-write>
- paragraph: dyad-write path="file1.txt"> Fake dyad write /dyad-write>
- img
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM
- img
- text: Approved
- paragraph: "[dump]"
- paragraph: "Attachments:"
- list:
- listitem: logo.png (image/png)
- paragraph: "[[dyad-dump-path=*]]"
- img
- text: Approved
- button "Retry":
- img

View File

@@ -0,0 +1,7 @@
- paragraph: "[dump]"
- paragraph: "Attachments:"
- list:
- listitem: logo.png (image/png)
- paragraph: "[[dyad-dump-path=*]]"
- button "Retry":
- img

View File

@@ -0,0 +1,3 @@
===
role: user
message: [{"type":"text","text":"[dump]\n\nAttachments:\n- logo.png (image/png)"},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAACbElEQVQ4EY1TTWgTQRR+M7NpQoKmPVTPQi/qoaDWQAsxIigqRS/JzYNHxVz0oAcrG6hQoV68ePNQECUpeBFBIqQiVBrbaqvgoXjRSzVJf8zfzu7MG2eWbGywFN8e3ux73/fNm/dmAP7TFCgSQHeurSC4t1eEAFET4+WjzPIq5AX5ZURMjO5GNEk7VbJKKWU9Or8WBg2cvPRxlLXiX7arVtFWNjVkzSX/VJBPK0YKRMIciI6477kjhsDrAxfJoebVUwd0bl1vBD0ChpzR5JkrKzGvjum2B8MtrphLRXGTY5RKCaioi7wr/lcgID+//Omsu4EzERg4GIIoWAygxrezwOvNhmiAoCrkCtZtqF9BPp33d86nl46jw173aWKtXWtzWi21kELDgzOo+mJCCRBIFIowBr3rOQK4bDJC90PVrf5Ayi/efDP62QBvn14Y5m0sKogOCoWy/nvL55syqOl4ppCRT6+9GxAKjgmtLYg3ldVkM4Hs0Fr4QSmxwi28T1gUHE+J9rpGdozqRvoWcmKWUMpyEXWH2IYJiq0KtQYr/qgRWc3T4lJ/IbNVx/xmmCrMXJ+ML3+wZP+Jmre5MN8/NVYoFKTB2XbJ+vYyPi9l/4hLq+XZpZMJ0BxzP3z1XGpO99qUDg897VFGEkd+3lm9lVy8e2NsceL7q/iqwvCIohIYU9MGm+pwuuOwbUVtm+D0uXIO5b57noxCWzJoSQJNIaAhm8BxMze7PGbboLFA/El0BYxqcJTJC+Vkg5PrLU4PO1KBg/jVo87jZ++Tb4PSDX5XMxdq14QOpvfI9XDMxTKPKQim9DqtY8H/Tv8HGFE+AZtzYdAAAAAASUVORK5CYII="}}]

View File

@@ -28,7 +28,7 @@ const config: PlaywrightTestConfig = {
},
webServer: {
command: `cd testing/fake-llm-server && npm start`,
command: `cd testing/fake-llm-server && npm run build && npm start`,
url: "http://localhost:3500/health",
},
};

View File

@@ -253,7 +253,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
Error loading proposal: {proposalError}
</div>
)}
<div className="p-4">
<div className="p-4" data-testid="chat-input-container">
<div
className={`relative flex flex-col border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""

View File

@@ -79,7 +79,7 @@ export function HomeChatInput({
return (
<>
<div className="p-4">
<div className="p-4" data-testid="home-chat-input-container">
<div
className={`relative flex flex-col space-y-2 border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""

View File

@@ -7,7 +7,8 @@ import path from "path";
// Create Express app
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: true, limit: "50mb" }));
const PORT = 3500;
@@ -205,9 +206,17 @@ function chatCompletionHandler(req: Request, res: Response) {
}
let messageContent = CANNED_MESSAGE;
console.error("LASTMESSAGE", lastMessage);
// Check if the last message is "[dump]" to write messages to file and return path
if (lastMessage && lastMessage.content === "[dump]") {
if (
lastMessage &&
(Array.isArray(lastMessage.content)
? lastMessage.content.some(
(part: { type: string; text: string }) =>
part.type === "text" && part.text.includes("[dump]"),
)
: lastMessage.content.includes("[dump]"))
) {
const timestamp = Date.now();
const generatedDir = path.join(__dirname, "generated");
@@ -241,6 +250,7 @@ function chatCompletionHandler(req: Request, res: Response) {
if (
lastMessage &&
lastMessage.content &&
typeof lastMessage.content === "string" &&
lastMessage.content.startsWith("tc=")
) {
const testCaseName = lastMessage.content.slice(3); // Remove "tc=" prefix