Attach image e2e tests (#301)
This commit is contained in:
30
e2e-tests/attach_image.spec.ts
Normal file
30
e2e-tests/attach_image.spec.ts
Normal 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 });
|
||||
});
|
||||
BIN
e2e-tests/fixtures/images/logo.png
Normal file
BIN
e2e-tests/fixtures/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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,22 +340,29 @@ 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
|
||||
// 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.
|
||||
.replace(
|
||||
/\n<dyad-file path="package\.json">[\s\S]*?<\/dyad-file>\n/g,
|
||||
"",
|
||||
);
|
||||
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.
|
||||
.replace(
|
||||
/\n<dyad-file path="package\.json">[\s\S]*?<\/dyad-file>\n/g,
|
||||
"",
|
||||
);
|
||||
return `===\nrole: ${message.role}\nmessage: ${content}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
- paragraph: "[dump]"
|
||||
- paragraph: "Attachments:"
|
||||
- list:
|
||||
- listitem: logo.png (image/png)
|
||||
- paragraph: "[[dyad-dump-path=*]]"
|
||||
- button "Retry":
|
||||
- img
|
||||
3
e2e-tests/snapshots/attach_image.spec.ts_server-dump.txt
Normal file
3
e2e-tests/snapshots/attach_image.spec.ts_server-dump.txt
Normal 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="}}]
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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" : ""
|
||||
|
||||
@@ -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" : ""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user