Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions e2e-tests/restore_to_message.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { testSkipIfWindows, Timeout } from "./helpers/test_helper";
import { expect } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";

/**
* E2E test for the per-message "restore" arrow on user messages.
*
* Clicking the arrow on a user message should:
* 1. Create a NEW chat containing only the messages before that message
* (the original chat stays intact).
* 2. Restore the app's code to the version that existed right before that
* message was sent.
* 3. Navigate the user to the new chat.
*/
testSkipIfWindows(
"restore to message - forks chat and reverts code",
async ({ po }) => {
await po.setUp({ autoApprove: true });
await po.importApp("minimal");

const indexPath = async () =>
path.join(
await po.appManagement.getCurrentAppPath(),
"src",
"pages",
"Index.tsx",
);

// Turn A: writes src/pages/Index.tsx -> creates a version.
await po.sendPrompt("tc=write-index");
expect(fs.readFileSync(await indexPath(), "utf-8")).toContain(
"Testing:write-index!",
);

// Turn B: overwrites src/pages/Index.tsx -> creates a newer version.
await po.sendPrompt("tc=write-index-2");
expect(fs.readFileSync(await indexPath(), "utf-8")).toContain(
"Testing:write-index(2)!",
);

const originalChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(originalChatId).toBeTruthy();

// Importing the "minimal" fixture triggers an auto-generated AI_RULES.md
// user message, so the chat has three user messages total: AI_RULES, turn
// A, turn B. Each gets a restore button.
const restoreButtons = po.page.getByTestId("restore-to-message-button");
await expect(restoreButtons).toHaveCount(3);

// Click the undo icon on the LAST user message (turn B), then confirm in
// the dialog. This should create a new chat with [AI_RULES, userA,
// assistantA] and revert the app to the state after turn A (i.e. before
// turn B).
await restoreButtons.nth(2).click();
await po.page.getByTestId("confirm-restore-to-message-button").click();

// We should navigate to a brand-new chat.
await expect(async () => {
const newChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(newChatId).toBeTruthy();
expect(newChatId).not.toBe(originalChatId);
}).toPass({ timeout: Timeout.LONG });

// The new chat contains only the messages before turn B: the AI_RULES
// user message and turn A, so two restore buttons.
await expect(restoreButtons).toHaveCount(2);

const messagesList = po.page.getByTestId("messages-list");
await expect(messagesList).toContainText("tc=write-index");
await expect(messagesList).not.toContainText("tc=write-index-2");

// The app code is reverted to the state right before turn B.
await expect(async () => {
const content = fs.readFileSync(await indexPath(), "utf-8");
expect(content).toContain("Testing:write-index!");
expect(content).not.toContain("Testing:write-index(2)!");
}).toPass({ timeout: Timeout.LONG });

// The original chat must be left intact: the confirmation dialog promises
// "Your current chat will not be changed". Navigate back to it and verify
// both turns are still present.
await po.page.getByRole("link", { name: "Apps" }).hover();
await expect(po.page.getByTestId("chat-list-container")).toBeVisible({
timeout: Timeout.MEDIUM,
});
await po.page.getByTestId(`chat-list-item-${originalChatId}`).click();
await expect(async () => {
const currentChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(currentChatId).toBe(originalChatId);
}).toPass({ timeout: Timeout.MEDIUM });
await expect(messagesList).toContainText("tc=write-index");
await expect(messagesList).toContainText("tc=write-index-2");
},
);
Comment thread
azizmejri1 marked this conversation as resolved.

/**
* "Fork chat only" forks the conversation into a new chat but leaves the app's
* code untouched.
*/
testSkipIfWindows(
"restore to message - fork chat only leaves code untouched",
async ({ po }) => {
await po.setUp({ autoApprove: true });
await po.importApp("minimal");

const indexPath = async () =>
path.join(
await po.appManagement.getCurrentAppPath(),
"src",
"pages",
"Index.tsx",
);

await po.sendPrompt("tc=write-index");
await po.sendPrompt("tc=write-index-2");
expect(fs.readFileSync(await indexPath(), "utf-8")).toContain(
"Testing:write-index(2)!",
);

const originalChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(originalChatId).toBeTruthy();

const restoreButtons = po.page.getByTestId("restore-to-message-button");
await expect(restoreButtons).toHaveCount(3);

// Open the dialog on the last user message (turn B) and choose to only fork
// the chat.
await restoreButtons.nth(2).click();
await po.page.getByTestId("fork-chat-button").click();

// We should navigate to a brand-new chat with only the messages before
// turn B.
await expect(async () => {
const newChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(newChatId).toBeTruthy();
expect(newChatId).not.toBe(originalChatId);
}).toPass({ timeout: Timeout.LONG });
await expect(restoreButtons).toHaveCount(2);

const messagesList = po.page.getByTestId("messages-list");
await expect(messagesList).toContainText("tc=write-index");
await expect(messagesList).not.toContainText("tc=write-index-2");

// The app code must NOT be reverted: forking only touches the chat.
expect(fs.readFileSync(await indexPath(), "utf-8")).toContain(
"Testing:write-index(2)!",
);
},
);

/**
* Restoring to the very first message is allowed: it restores to "version 1"
* (the commit before the first message's changes) and forks a new, empty chat.
*/
testSkipIfWindows(
"restore to message - first message forks an empty chat",
async ({ po }) => {
await po.setUp({ autoApprove: true });
await po.importApp("minimal");

await po.sendPrompt("tc=write-index");

const originalChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(originalChatId).toBeTruthy();

// The imported "minimal" fixture adds an auto-generated AI_RULES.md user
// message followed by turn A, so there are two restore buttons.
const restoreButtons = po.page.getByTestId("restore-to-message-button");
await expect(restoreButtons).toHaveCount(2);

// Restore to the FIRST user message. Previously this failed with "Cannot
// restore before the first message"; now it forks an empty chat.
await restoreButtons.nth(0).click();
await po.page.getByTestId("confirm-restore-to-message-button").click();

// We navigate to a brand-new chat with no prior messages, so no restore
// buttons are shown.
await expect(async () => {
const newChatId = po.page.url().match(/[?&]id=(\d+)/)?.[1];
expect(newChatId).toBeTruthy();
expect(newChatId).not.toBe(originalChatId);
}).toPass({ timeout: Timeout.LONG });
await expect(restoreButtons).toHaveCount(0);
},
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now...
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now...
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
- button "Restore to this point"
- paragraph: basic
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Restore to this point"
- paragraph: "[[UPLOAD_IMAGE_TO_CODEBASE]]"
- 'button "Expand image: logo.png"'
- paragraph: Uploading image to codebase
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
- button "Restore to this point"
- paragraph: basic
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Restore to this point"
- paragraph: "[dump]"
- 'button "Expand image: logo.png"'
- paragraph: "[[dyad-dump-path=*]]"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[dump]"
- 'button "Expand image: logo.png"'
- paragraph: "[[dyad-dump-path=*]]"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
- button "Restore to this point"
- paragraph: basic
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Restore to this point"
- paragraph: "[dump]"
- 'button "Expand image: logo.png"'
- paragraph: "[[dyad-dump-path=*]]"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now...
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=basic
- paragraph: This is a simple basic response
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=chat2
- paragraph: chat2
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- button "Restore to this point"
- paragraph: tc=chat1 [sleep=medium]
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[dump] tc=turbo-edits"
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[dump] tc=turbo-edits"
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[dump] tc=turbo-edits"
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=create-multiple-errors
- paragraph: I will intentionally add multiple errors to test the Fix All Errors button
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: intentionally add first error"'
Expand All @@ -9,6 +10,7 @@
- button "Fix All Errors (3)"
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Restore to this point"
- paragraph: "Fix all of the following errors:"
- list:
- listitem: First error in Index
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
- button "Restore to this point"
- paragraph: tc=create-error
- paragraph: I will intentionally add an error
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: intentionally add an error"'
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Restore to this point"
- paragraph:
- text: "Fix error: Error Line 6 error Stack trace: Index ("
- link "http://localhost:[[port]]/src/pages/Index.tsx:6:6":
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/snapshots/import.spec.ts_import-app-2.aria.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: "[dump]"
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: hi
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/connection-drop
- paragraph: I'll create a file for you.
- 'button "recovered.ts src/recovered.ts Edit Summary: File created after connection recovery"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: tc=local-agent/explore-code
- paragraph: I'll inspect the TypeScript symbols around the app component.
- 'button "CODE \"App component render flow\" markdown Copy ## explore_code report Query: \"App component render flow\" | Intent: explain | Confidence: low | Action: read_targets Flow: 1. src/App.tsx:1-4 (observed) - compiler-backed symbol window Missing: submit_report was not called Read targets: flow 1 - observed fallback target ```json {\"action\":\"read_targets\",\"confidence\":\"low\",\"paths\":[{\"path\":\"src/App.tsx\",\"range\":\"1-4\"}]} ```" [expanded]'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/upload-to-codebase
- 'button "Expand image: logo.png"'
- paragraph: I'll upload your file to the codebase.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/generate-image
- paragraph: I'll generate a hero image for your landing page.
- button "Image Generation A modern, minimal hero illustration of a rocket launching from a laptop screen, flat design style, blue and purple gradient background, clean lines View generated image"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/persistent-todos
- paragraph: I'll create a task list to track the work.Let me create the utility module first.
- 'button "utils.ts src/lib/utils.ts Edit Summary: Create utility module"'
- paragraph: Marking the first task as done.I've completed the utility module. I'll continue with the remaining tasks.I see there are remaining tasks. I'll pick these up in the next turn.
- button "Copy"
- text: "[[Version 3: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/persistent-todos-resume
- paragraph: I see there are unfinished todos from last time. Let me continue.Adding error handling to the utility module.
- 'button "utils.ts src/lib/utils.ts Edit Summary: Add error handling utility"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- button "Restore to this point"
- paragraph: Summarize from chat-id=1
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/todo-followup-loop
- paragraph: I'll create a todo list to track these tasks.Let me create the utility function first.
- 'button "helper.ts src/utils/helper.ts Edit Summary: Create helper utility function"'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
- button "Restore to this point"
- paragraph: "[[AI_RULES_GENERATION_PROMPT]]"
- button "file1.txt file1.txt Edit"
- paragraph: More EOM
- button "Copy"
- text: "[[Version 2: files changed]]"
- button "Copy Request ID"
- button "Restore to this point"
- paragraph: tc=local-agent/web-fetch
- paragraph: I'll fetch the content of that page for you.
- text: Web Fetch
Expand Down
Loading
Loading