Unverified 提交 dcf06ece authored 作者: wwwillchen-bot's avatar wwwillchen-bot 提交者: GitHub

refactor: expose PageObject component page objects publicly (#2582)

## Summary - Updated PageObject class to expose component page objects (chatActions, previewPanel, codeEditor, etc.) as public properties instead of private - Updated all e2e spec files to use explicit component access pattern (e.g., `po.chatActions.sendPrompt()` instead of `po.sendPrompt()`) - Makes the test code more modular and explicit about which component is being used ## Test plan - [x] All 784 unit tests pass - [x] TypeScript type checking passes - [x] Linting passes - [ ] E2E tests should be run via CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2582" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refactored the e2e PageObject to expose component objects publicly and added delegated helpers (sendPrompt, importApp) for shorter calls. Tests now use explicit component access with shorthand for common actions, making code clearer and easier to maintain. - **Refactors** - Exposed component objects on PageObject (chatActions, previewPanel, codeEditor, modelPicker, settings, navigation, toastNotifications, appManagement, securityReview, agentConsent, githubConnector). - Added PageObject.sendPrompt() and PageObject.importApp() delegates; updated specs to use them for brevity. - Removed monolithic wrappers; no functional app changes—test-only refactor. - **Migration** - Use component methods directly (e.g., po.previewPanel.selectPreviewMode(), po.settings.recordSettings(), po.toastNotifications.waitForToastWithText()). - Prefer po.sendPrompt() and po.importApp() for common actions; replace old PageObject methods with component or delegate equivalents. <sup>Written for commit 15cf1998d7f9b06e0fe6e53f8cc36f4f3c42e630. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarWill Chen <willchen90@gmail.com> Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 0f8ee17d
...@@ -6,7 +6,7 @@ test("add prompt via deep link with base64-encoded data", async ({ ...@@ -6,7 +6,7 @@ test("add prompt via deep link with base64-encoded data", async ({
electronApp, electronApp,
}) => { }) => {
await po.setUp(); await po.setUp();
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
// Verify library is empty initially // Verify library is empty initially
await expect(po.page.getByTestId("prompt-card")).not.toBeVisible(); await expect(po.page.getByTestId("prompt-card")).not.toBeVisible();
......
...@@ -11,15 +11,15 @@ testSkipIfWindows( ...@@ -11,15 +11,15 @@ testSkipIfWindows(
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// Click the annotator button to activate annotator mode // Click the annotator button to activate annotator mode
await po.clickPreviewAnnotatorButton(); await po.previewPanel.clickPreviewAnnotatorButton();
// Wait for annotator mode to be active // Wait for annotator mode to be active
await po.waitForAnnotatorMode(); await po.previewPanel.waitForAnnotatorMode();
// Submit the screenshot to chat // Submit the screenshot to chat
await po.clickAnnotatorSubmit(); await po.previewPanel.clickAnnotatorSubmit();
await expect(po.getChatInput()).toContainText( await expect(po.chatActions.getChatInput()).toContainText(
"Please update the UI based on these screenshots", "Please update the UI based on these screenshots",
); );
......
...@@ -6,7 +6,7 @@ test("configure app commands", async ({ po }) => { ...@@ -6,7 +6,7 @@ test("configure app commands", async ({ po }) => {
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
// Navigate to configure panel // Navigate to configure panel
await po.selectPreviewMode("configure"); await po.previewPanel.selectPreviewMode("configure");
// Verify default state - no custom commands // Verify default state - no custom commands
await expect( await expect(
...@@ -68,7 +68,7 @@ test("configure app commands", async ({ po }) => { ...@@ -68,7 +68,7 @@ test("configure app commands", async ({ po }) => {
await po.page.getByTestId("save-app-commands").click(); await po.page.getByTestId("save-app-commands").click();
// Verify success toast // Verify success toast
await po.waitForToastWithText("App commands saved"); await po.toastNotifications.waitForToastWithText("App commands saved");
// Verify the commands are displayed // Verify the commands are displayed
await expect(po.page.getByTestId("current-install-command")).toHaveText( await expect(po.page.getByTestId("current-install-command")).toHaveText(
......
...@@ -3,28 +3,28 @@ import { test } from "./helpers/test_helper"; ...@@ -3,28 +3,28 @@ import { test } from "./helpers/test_helper";
test("app search - basic search dialog functionality", async ({ po }) => { test("app search - basic search dialog functionality", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.page.getByTestId("search-apps-button").waitFor(); await po.page.getByTestId("search-apps-button").waitFor();
// Create some apps for testing // Create some apps for testing
await po.sendPrompt("create a todo application"); await po.sendPrompt("create a todo application");
// Go back to apps list // Go back to apps list
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.page.getByTestId("search-apps-button").waitFor(); await po.page.getByTestId("search-apps-button").waitFor();
// Create second app // Create second app
await po.sendPrompt("build a weather dashboard"); await po.sendPrompt("build a weather dashboard");
// Go back to apps list // Go back to apps list
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.page.getByTestId("search-apps-button").waitFor(); await po.page.getByTestId("search-apps-button").waitFor();
// Create third app // Create third app
await po.sendPrompt("create a blog system"); await po.sendPrompt("create a blog system");
// Go back to apps list // Go back to apps list
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.page.getByTestId("search-apps-button").waitFor(); await po.page.getByTestId("search-apps-button").waitFor();
// Test 1: Open search dialog using the search button // Test 1: Open search dialog using the search button
...@@ -60,13 +60,13 @@ test("app search - search functionality with different terms", async ({ ...@@ -60,13 +60,13 @@ test("app search - search functionality with different terms", async ({
// Create apps with specific content for testing // Create apps with specific content for testing
await po.sendPrompt("create a calculator application with advanced features"); await po.sendPrompt("create a calculator application with advanced features");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("build a task management system with priority levels"); await po.sendPrompt("build a task management system with priority levels");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create a weather monitoring dashboard"); await po.sendPrompt("create a weather monitoring dashboard");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Open search dialog // Open search dialog
await po.page.getByTestId("search-apps-button").click(); await po.page.getByTestId("search-apps-button").click();
...@@ -99,7 +99,7 @@ test("app search - keyboard shortcut functionality", async ({ po }) => { ...@@ -99,7 +99,7 @@ test("app search - keyboard shortcut functionality", async ({ po }) => {
// Create an app first // Create an app first
await po.sendPrompt("create sample application"); await po.sendPrompt("create sample application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Test keyboard shortcut (Ctrl+K) to open dialog // Test keyboard shortcut (Ctrl+K) to open dialog
await po.page.keyboard.press("Control+k"); await po.page.keyboard.press("Control+k");
...@@ -123,13 +123,13 @@ test("app search - navigation and selection", async ({ po }) => { ...@@ -123,13 +123,13 @@ test("app search - navigation and selection", async ({ po }) => {
// Create multiple apps // Create multiple apps
await po.sendPrompt("create first application"); await po.sendPrompt("create first application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create second application"); await po.sendPrompt("create second application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create third application"); await po.sendPrompt("create third application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Open search dialog // Open search dialog
await po.page.getByTestId("search-apps-button").click(); await po.page.getByTestId("search-apps-button").click();
...@@ -158,13 +158,13 @@ test("app search - empty search shows all apps", async ({ po }) => { ...@@ -158,13 +158,13 @@ test("app search - empty search shows all apps", async ({ po }) => {
// Create a few apps // Create a few apps
await po.sendPrompt("create alpha application"); await po.sendPrompt("create alpha application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create beta application"); await po.sendPrompt("create beta application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create gamma application"); await po.sendPrompt("create gamma application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Open search dialog // Open search dialog
await po.page.getByTestId("search-apps-button").click(); await po.page.getByTestId("search-apps-button").click();
...@@ -186,7 +186,7 @@ test("app search - case insensitive search", async ({ po }) => { ...@@ -186,7 +186,7 @@ test("app search - case insensitive search", async ({ po }) => {
// Create an app with mixed case content // Create an app with mixed case content
await po.sendPrompt("create a Test Application with Mixed Case Content"); await po.sendPrompt("create a Test Application with Mixed Case Content");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Open search dialog // Open search dialog
await po.page.getByTestId("search-apps-button").click(); await po.page.getByTestId("search-apps-button").click();
...@@ -213,7 +213,7 @@ test("app search - partial word matching", async ({ po }) => { ...@@ -213,7 +213,7 @@ test("app search - partial word matching", async ({ po }) => {
// Create an app with a long descriptive name // Create an app with a long descriptive name
await po.sendPrompt("create a comprehensive project management solution"); await po.sendPrompt("create a comprehensive project management solution");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Open search dialog // Open search dialog
await po.page.getByTestId("search-apps-button").click(); await po.page.getByTestId("search-apps-button").click();
...@@ -240,13 +240,13 @@ test("app search - search by app name", async ({ po }) => { ...@@ -240,13 +240,13 @@ test("app search - search by app name", async ({ po }) => {
// Create apps - note that app names are randomly generated // Create apps - note that app names are randomly generated
await po.sendPrompt("create a todo application"); await po.sendPrompt("create a todo application");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("build a weather dashboard"); await po.sendPrompt("build a weather dashboard");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("create a blog system"); await po.sendPrompt("create a blog system");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Get the actual app names from the UI (these are randomly generated) // Get the actual app names from the UI (these are randomly generated)
const appItems = await po.page.getByTestId(/^app-list-item-/).all(); const appItems = await po.page.getByTestId(/^app-list-item-/).all();
......
...@@ -8,10 +8,10 @@ test("move app to a custom storage location", async ({ po }) => { ...@@ -8,10 +8,10 @@ test("move app to a custom storage location", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hello"); await po.sendPrompt("hello");
const appName = await po.getCurrentAppName(); const appName = await po.appManagement.getCurrentAppName();
const originalPath = await po.getCurrentAppPath(); const originalPath = await po.appManagement.getCurrentAppPath();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
const newBasePath = path.join(po.userDataDir, "alt-app-storage"); const newBasePath = path.join(po.userDataDir, "alt-app-storage");
if (!fs.existsSync(newBasePath)) { if (!fs.existsSync(newBasePath)) {
......
...@@ -11,8 +11,8 @@ testSkipIfWindows("write to index, approve, check preview", async ({ po }) => { ...@@ -11,8 +11,8 @@ testSkipIfWindows("write to index, approve, check preview", async ({ po }) => {
await po.snapshotMessages(); await po.snapshotMessages();
// This can be pretty slow because it's waiting for the app to build. // This can be pretty slow because it's waiting for the app to build.
await expect(po.getPreviewIframeElement()).toBeVisible({ await expect(po.previewPanel.getPreviewIframeElement()).toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
...@@ -16,7 +16,7 @@ test("attach image - home chat", async ({ po }) => { ...@@ -16,7 +16,7 @@ test("attach image - home chat", async ({ po }) => {
await po.setUp(); await po.setUp();
// Open auxiliary actions menu // Open auxiliary actions menu
await po await po.chatActions
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -48,7 +48,7 @@ test("attach image - chat", async ({ po }) => { ...@@ -48,7 +48,7 @@ test("attach image - chat", async ({ po }) => {
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// Open auxiliary actions menu // Open auxiliary actions menu
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -80,7 +80,7 @@ test("attach image - chat - upload to codebase", async ({ po }) => { ...@@ -80,7 +80,7 @@ test("attach image - chat - upload to codebase", async ({ po }) => {
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// Open auxiliary actions menu // Open auxiliary actions menu
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -108,7 +108,7 @@ test("attach image - chat - upload to codebase", async ({ po }) => { ...@@ -108,7 +108,7 @@ test("attach image - chat - upload to codebase", async ({ po }) => {
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
// new/image/file.png // new/image/file.png
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const filePath = path.join(appPath, "new", "image", "file.png"); const filePath = path.join(appPath, "new", "image", "file.png");
expect(fs.existsSync(filePath)).toBe(true); expect(fs.existsSync(filePath)).toBe(true);
// check contents of filePath is equal in value to e2e-tests/fixtures/images/logo.png // check contents of filePath is equal in value to e2e-tests/fixtures/images/logo.png
...@@ -130,7 +130,10 @@ test("attach image via drag - chat", async ({ po }) => { ...@@ -130,7 +130,10 @@ test("attach image via drag - chat", async ({ po }) => {
"base64", "base64",
); );
// locate the inner drop target (first child div of the container) // locate the inner drop target (first child div of the container)
const dropTarget = po.getChatInputContainer().locator("div").first(); const dropTarget = po.chatActions
.getChatInputContainer()
.locator("div")
.first();
// simulate dragenter, dragover, and drop with a File // simulate dragenter, dragover, and drop with a File
await dropTarget.evaluate((element, fileBase64) => { await dropTarget.evaluate((element, fileBase64) => {
// convert base64 to Uint8Array // convert base64 to Uint8Array
......
...@@ -7,8 +7,8 @@ testSkipIfWindows("auto-approve", async ({ po }) => { ...@@ -7,8 +7,8 @@ testSkipIfWindows("auto-approve", async ({ po }) => {
await po.snapshotMessages(); await po.snapshotMessages();
// This can be pretty slow because it's waiting for the app to build. // This can be pretty slow because it's waiting for the app to build.
await expect(po.getPreviewIframeElement()).toBeVisible({ await expect(po.previewPanel.getPreviewIframeElement()).toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
...@@ -2,16 +2,16 @@ import { expect } from "@playwright/test"; ...@@ -2,16 +2,16 @@ import { expect } from "@playwright/test";
import { test } from "./helpers/test_helper"; import { test } from "./helpers/test_helper";
test("auto update - disable and enable", async ({ po }) => { test("auto update - disable and enable", async ({ po }) => {
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.toggleAutoUpdate(); await po.settings.toggleAutoUpdate();
await expect( await expect(
po.page.getByRole("button", { name: "Restart Dyad" }), po.page.getByRole("button", { name: "Restart Dyad" }),
).toBeVisible(); ).toBeVisible();
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await po.toggleAutoUpdate(); await po.settings.toggleAutoUpdate();
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
}); });
...@@ -3,7 +3,7 @@ import { test as testWithPo } from "./helpers/test_helper"; ...@@ -3,7 +3,7 @@ import { test as testWithPo } from "./helpers/test_helper";
testWithPo("Azure provider settings UI", async ({ po }) => { testWithPo("Azure provider settings UI", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
// Look for Azure OpenAI in the provider list // Look for Azure OpenAI in the provider list
await expect(po.page.getByText("Azure OpenAI")).toBeVisible(); await expect(po.page.getByText("Azure OpenAI")).toBeVisible();
......
...@@ -15,7 +15,7 @@ testAzure("send message through Azure OpenAI", async ({ po }) => { ...@@ -15,7 +15,7 @@ testAzure("send message through Azure OpenAI", async ({ po }) => {
await po.setUpAzure(); await po.setUpAzure();
// Select Azure model // Select Azure model
await po.selectTestAzureModel(); await po.modelPicker.selectTestAzureModel();
// Send a test prompt that returns a normal conversational response // Send a test prompt that returns a normal conversational response
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
......
...@@ -3,9 +3,9 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; ...@@ -3,9 +3,9 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper";
testSkipIfWindows("capacitor upgrade and sync works", async ({ po }) => { testSkipIfWindows("capacitor upgrade and sync works", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.clickAppUpgradeButton({ upgradeId: "capacitor" }); await po.appManagement.clickAppUpgradeButton({ upgradeId: "capacitor" });
await po.expectNoAppUpgrades(); await po.appManagement.expectNoAppUpgrades();
await po.snapshotAppFiles({ name: "upgraded-capacitor" }); await po.snapshotAppFiles({ name: "upgraded-capacitor" });
await po.page.getByTestId("capacitor-controls").waitFor({ state: "visible" }); await po.page.getByTestId("capacitor-controls").waitFor({ state: "visible" });
......
...@@ -6,13 +6,13 @@ test("should open, navigate, and select from history menu", async ({ po }) => { ...@@ -6,13 +6,13 @@ test("should open, navigate, and select from history menu", async ({ po }) => {
// Send messages to populate history // Send messages to populate history
await po.sendPrompt("First test message"); await po.sendPrompt("First test message");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.sendPrompt("Second test message"); await po.sendPrompt("Second test message");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Click on the chat input to focus it // Click on the chat input to focus it
const chatInput = po.getChatInput(); const chatInput = po.chatActions.getChatInput();
await chatInput.click(); await chatInput.click();
await chatInput.fill(""); await chatInput.fill("");
...@@ -54,7 +54,7 @@ test("should open, navigate, and select from history menu", async ({ po }) => { ...@@ -54,7 +54,7 @@ test("should open, navigate, and select from history menu", async ({ po }) => {
}); });
// Clear input for mouse click test // Clear input for mouse click test
await po.openChatHistoryMenu(); await po.chatActions.openChatHistoryMenu();
// Click the first item (oldest message, at top) // Click the first item (oldest message, at top)
await menuItems.nth(0).click(); await menuItems.nth(0).click();
...@@ -71,7 +71,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async ...@@ -71,7 +71,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async
}) => { }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
const chatInput = po.getChatInput(); const chatInput = po.chatActions.getChatInput();
const historyMenu = po.page.locator('[data-mentions-menu="true"]'); const historyMenu = po.page.locator('[data-mentions-menu="true"]');
// Test 1: Empty history guard - menu should not open with no history // Test 1: Empty history guard - menu should not open with no history
...@@ -85,7 +85,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async ...@@ -85,7 +85,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async
// Create some history // Create some history
await po.sendPrompt("History entry for testing"); await po.sendPrompt("History entry for testing");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Test 2: Non-empty input guard - menu should not open when input has content // Test 2: Non-empty input guard - menu should not open when input has content
await chatInput.click(); await chatInput.click();
...@@ -99,7 +99,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async ...@@ -99,7 +99,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async
await expect(historyMenu).not.toBeVisible(); await expect(historyMenu).not.toBeVisible();
// Test 3: Escape closes menu and clears input // Test 3: Escape closes menu and clears input
await po.openChatHistoryMenu(); await po.chatActions.openChatHistoryMenu();
await po.page.keyboard.press("Escape"); await po.page.keyboard.press("Escape");
await expect(historyMenu).not.toBeVisible(); await expect(historyMenu).not.toBeVisible();
...@@ -109,7 +109,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async ...@@ -109,7 +109,7 @@ test("should handle edge cases: guards, escape, and sending after cancel", async
await chatInput.fill("New test message after escape"); await chatInput.fill("New test message after escape");
await po.page.keyboard.press("Enter"); await po.page.keyboard.press("Enter");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Verify the message was sent // Verify the message was sent
await expect( await expect(
......
...@@ -11,7 +11,7 @@ test("send button disabled during pending proposal", async ({ po }) => { ...@@ -11,7 +11,7 @@ test("send button disabled during pending proposal", async ({ po }) => {
await expect(po.page.getByTestId("approve-proposal-button")).toBeVisible(); await expect(po.page.getByTestId("approve-proposal-button")).toBeVisible();
// Type something in the input to ensure it's not disabled due to empty input // Type something in the input to ensure it's not disabled due to empty input
await po.getChatInput().fill("test message"); await po.chatActions.getChatInput().fill("test message");
// Check send button is disabled due to pending changes // Check send button is disabled due to pending changes
const sendButton = po.page.getByRole("button", { name: "Send message" }); const sendButton = po.page.getByRole("button", { name: "Send message" });
...@@ -36,7 +36,7 @@ test("send button disabled during pending proposal - reject", async ({ ...@@ -36,7 +36,7 @@ test("send button disabled during pending proposal - reject", async ({
await expect(po.page.getByTestId("reject-proposal-button")).toBeVisible(); await expect(po.page.getByTestId("reject-proposal-button")).toBeVisible();
// Type something in the input to ensure it's not disabled due to empty input // Type something in the input to ensure it's not disabled due to empty input
await po.getChatInput().fill("test message"); await po.chatActions.getChatInput().fill("test message");
// Check send button is disabled due to pending changes // Check send button is disabled due to pending changes
const sendButton = po.page.getByRole("button", { name: "Send message" }); const sendButton = po.page.getByRole("button", { name: "Send message" });
......
...@@ -5,7 +5,7 @@ test("chat mode selector - default build mode", async ({ po }) => { ...@@ -5,7 +5,7 @@ test("chat mode selector - default build mode", async ({ po }) => {
await po.importApp("minimal"); await po.importApp("minimal");
await po.sendPrompt("[dump] hi"); await po.sendPrompt("[dump] hi");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
...@@ -15,9 +15,9 @@ test("chat mode selector - ask mode", async ({ po }) => { ...@@ -15,9 +15,9 @@ test("chat mode selector - ask mode", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectChatMode("ask"); await po.chatActions.selectChatMode("ask");
await po.sendPrompt("[dump] hi"); await po.sendPrompt("[dump] hi");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
...@@ -26,17 +26,17 @@ test("chat mode selector - ask mode", async ({ po }) => { ...@@ -26,17 +26,17 @@ test("chat mode selector - ask mode", async ({ po }) => {
test.skip("dyadwrite edit and save - basic flow", async ({ po }) => { test.skip("dyadwrite edit and save - basic flow", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt( await po.sendPrompt(
"Create a simple React component in src/components/Hello.tsx", "Create a simple React component in src/components/Hello.tsx",
); );
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.clickEditButton(); await po.codeEditor.clickEditButton();
await po.editFileContent("// Test modification\n"); await po.codeEditor.editFileContent("// Test modification\n");
await po.saveFile(); await po.codeEditor.saveFile();
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
}); });
...@@ -6,15 +6,15 @@ test.skip("chat search - basic search dialog functionality", async ({ po }) => { ...@@ -6,15 +6,15 @@ test.skip("chat search - basic search dialog functionality", async ({ po }) => {
// Create some chats with specific names for testing // Create some chats with specific names for testing
await po.sendPrompt("[dump] create a todo application"); await po.sendPrompt("[dump] create a todo application");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt("[dump] build a weather dashboard"); await po.sendPrompt("[dump] build a weather dashboard");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt("[dump] create a blog system"); await po.sendPrompt("[dump] create a blog system");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Test 1: Open search dialog using the search button // Test 1: Open search dialog using the search button
await po.page.getByTestId("search-chats-button").click(); await po.page.getByTestId("search-chats-button").click();
...@@ -49,20 +49,20 @@ test.skip("chat search - with named chats for easier testing", async ({ ...@@ -49,20 +49,20 @@ test.skip("chat search - with named chats for easier testing", async ({
// Create chats with descriptive names that will be useful for testing // Create chats with descriptive names that will be useful for testing
await po.sendPrompt("[dump] hello world app"); await po.sendPrompt("[dump] hello world app");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Use a timeout to ensure the UI has updated before trying to interact // Use a timeout to ensure the UI has updated before trying to interact
await po.page.waitForTimeout(1000); await po.page.waitForTimeout(1000);
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt("[dump] todo list manager"); await po.sendPrompt("[dump] todo list manager");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.page.waitForTimeout(1000); await po.page.waitForTimeout(1000);
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt("[dump] weather forecast widget"); await po.sendPrompt("[dump] weather forecast widget");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.page.waitForTimeout(1000); await po.page.waitForTimeout(1000);
...@@ -91,7 +91,7 @@ test.skip("chat search - keyboard shortcut functionality", async ({ po }) => { ...@@ -91,7 +91,7 @@ test.skip("chat search - keyboard shortcut functionality", async ({ po }) => {
// Create a chat // Create a chat
await po.sendPrompt("[dump] sample app"); await po.sendPrompt("[dump] sample app");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Test keyboard shortcut (Ctrl+K) // Test keyboard shortcut (Ctrl+K)
await po.page.keyboard.press("Control+k"); await po.page.keyboard.press("Control+k");
...@@ -108,11 +108,11 @@ test.skip("chat search - navigation and selection", async ({ po }) => { ...@@ -108,11 +108,11 @@ test.skip("chat search - navigation and selection", async ({ po }) => {
// Create multiple chats // Create multiple chats
await po.sendPrompt("[dump] first application"); await po.sendPrompt("[dump] first application");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.clickNewChat(); await po.chatActions.clickNewChat();
await po.sendPrompt("[dump] second application"); await po.sendPrompt("[dump] second application");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Test selecting a chat through search // Test selecting a chat through search
await po.page.getByTestId("search-chats-button").click(); await po.page.getByTestId("search-chats-button").click();
......
...@@ -9,10 +9,10 @@ test("concurrent chat", async ({ po }) => { ...@@ -9,10 +9,10 @@ test("concurrent chat", async ({ po }) => {
// Need a short wait otherwise the click on Apps tab is ignored. // Need a short wait otherwise the click on Apps tab is ignored.
await po.sleep(2_000); await po.sleep(2_000);
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("tc=chat2"); await po.sendPrompt("tc=chat2");
await po.snapshotMessages(); await po.snapshotMessages();
await po.clickChatActivityButton(); await po.chatActions.clickChatActivityButton();
// Chat #1 will be the last in the list // Chat #1 will be the last in the list
expect( expect(
......
...@@ -12,7 +12,7 @@ testSkipIfWindows( ...@@ -12,7 +12,7 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send first message with a fixture that returns 200k token usage. // Send first message with a fixture that returns 200k token usage.
// This exceeds the compaction threshold (min(80% of context window, 180k)) // This exceeds the compaction threshold (min(80% of context window, 180k))
......
...@@ -12,7 +12,7 @@ test("context limit banner shows 'running out' when near context limit", async ( ...@@ -12,7 +12,7 @@ test("context limit banner shows 'running out' when near context limit", async (
await po.sendPrompt("tc=context-limit-response [high-tokens=110000]"); await po.sendPrompt("tc=context-limit-response [high-tokens=110000]");
// Verify the context limit banner appears inside the chat input container // Verify the context limit banner appears inside the chat input container
const contextLimitBanner = po const contextLimitBanner = po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("context-limit-banner"); .getByTestId("context-limit-banner");
await expect(contextLimitBanner).toBeVisible({ timeout: Timeout.MEDIUM }); await expect(contextLimitBanner).toBeVisible({ timeout: Timeout.MEDIUM });
...@@ -26,7 +26,7 @@ test("context limit banner shows 'running out' when near context limit", async ( ...@@ -26,7 +26,7 @@ test("context limit banner shows 'running out' when near context limit", async (
await contextLimitBanner.getByRole("button", { name: "Summarize" }).click(); await contextLimitBanner.getByRole("button", { name: "Summarize" }).click();
// Wait for the new chat to load and message to complete // Wait for the new chat to load and message to complete
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Snapshot the messages in the new chat // Snapshot the messages in the new chat
await po.snapshotMessages(); await po.snapshotMessages();
...@@ -38,13 +38,13 @@ test("context limit banner shows 'costs extra' for long context", async ({ ...@@ -38,13 +38,13 @@ test("context limit banner shows 'costs extra' for long context", async ({
await po.setUp(); await po.setUp();
// Add a custom test model with a 1M context window so 250k tokens isn't "near limit" // Add a custom test model with a 1M context window so 250k tokens isn't "near limit"
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.addCustomTestModel({ await po.settings.addCustomTestModel({
name: "test-model-large-ctx", name: "test-model-large-ctx",
contextWindow: 1_000_000, contextWindow: 1_000_000,
}); });
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectModel({ await po.modelPicker.selectModel({
provider: "test-provider", provider: "test-provider",
model: "test-model-large-ctx", model: "test-model-large-ctx",
}); });
...@@ -54,7 +54,7 @@ test("context limit banner shows 'costs extra' for long context", async ({ ...@@ -54,7 +54,7 @@ test("context limit banner shows 'costs extra' for long context", async ({
await po.sendPrompt("tc=context-limit-response [high-tokens=250000]"); await po.sendPrompt("tc=context-limit-response [high-tokens=250000]");
// Verify the context limit banner appears inside the chat input container // Verify the context limit banner appears inside the chat input container
const contextLimitBanner = po const contextLimitBanner = po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("context-limit-banner"); .getByTestId("context-limit-banner");
await expect(contextLimitBanner).toBeVisible({ timeout: Timeout.MEDIUM }); await expect(contextLimitBanner).toBeVisible({ timeout: Timeout.MEDIUM });
...@@ -76,7 +76,7 @@ test("context limit banner does not appear when within limit", async ({ ...@@ -76,7 +76,7 @@ test("context limit banner does not appear when within limit", async ({
await po.sendPrompt("tc=context-limit-response [high-tokens=50000]"); await po.sendPrompt("tc=context-limit-response [high-tokens=50000]");
// Verify the context limit banner does NOT appear in the chat input container // Verify the context limit banner does NOT appear in the chat input container
const contextLimitBanner = po const contextLimitBanner = po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("context-limit-banner"); .getByTestId("context-limit-banner");
await expect(contextLimitBanner).not.toBeVisible(); await expect(contextLimitBanner).not.toBeVisible();
......
...@@ -20,7 +20,10 @@ test("manage context - default", async ({ po }) => { ...@@ -20,7 +20,10 @@ test("manage context - default", async ({ po }) => {
test("manage context - smart context", async ({ po }) => { test("manage context - smart context", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" }); await po.modelPicker.selectModel({
provider: "Google",
model: "Gemini 2.5 Pro",
});
await po.importApp("context-manage"); await po.importApp("context-manage");
let dialog = await po.openContextFilesPicker(); let dialog = await po.openContextFilesPicker();
...@@ -59,7 +62,10 @@ test("manage context - smart context", async ({ po }) => { ...@@ -59,7 +62,10 @@ test("manage context - smart context", async ({ po }) => {
test("manage context - smart context - auto-includes only", async ({ po }) => { test("manage context - smart context - auto-includes only", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" }); await po.modelPicker.selectModel({
provider: "Google",
model: "Gemini 2.5 Pro",
});
await po.importApp("context-manage"); await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker(); const dialog = await po.openContextFilesPicker();
...@@ -110,7 +116,10 @@ test("manage context - exclude paths", async ({ po }) => { ...@@ -110,7 +116,10 @@ test("manage context - exclude paths", async ({ po }) => {
test("manage context - exclude paths with smart context", async ({ po }) => { test("manage context - exclude paths with smart context", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" }); await po.modelPicker.selectModel({
provider: "Google",
model: "Gemini 2.5 Pro",
});
await po.importApp("context-manage"); await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker(); const dialog = await po.openContextFilesPicker();
......
...@@ -11,8 +11,8 @@ testSkipIfWindows("context window", async ({ po }) => { ...@@ -11,8 +11,8 @@ testSkipIfWindows("context window", async ({ po }) => {
await po.sendPrompt("[dump] tc=5"); await po.sendPrompt("[dump] tc=5");
await po.snapshotServerDump(); await po.snapshotServerDump();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.page await po.page
.getByRole("combobox", { name: "Maximum number of chat turns" }) .getByRole("combobox", { name: "Maximum number of chat turns" })
.click(); .click();
...@@ -20,7 +20,7 @@ testSkipIfWindows("context window", async ({ po }) => { ...@@ -20,7 +20,7 @@ testSkipIfWindows("context window", async ({ po }) => {
// close combobox // close combobox
// await po.page.keyboard.press("Escape"); // await po.page.keyboard.press("Escape");
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
await po.page.getByText("Go Back").click(); await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] tc=6"); await po.sendPrompt("[dump] tc=6");
......
...@@ -22,11 +22,11 @@ for (const { testName, newAppName, buttonName, expectedVersion } of tests) { ...@@ -22,11 +22,11 @@ for (const { testName, newAppName, buttonName, expectedVersion } of tests) {
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.snapshotAppFiles({ name: "app" }); await po.snapshotAppFiles({ name: "app" });
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
// Open the dropdown menu // Open the dropdown menu
await po.clickAppDetailsMoreOptions(); await po.appManagement.clickAppDetailsMoreOptions();
await po.clickAppDetailsCopyAppButton(); await po.appManagement.clickAppDetailsCopyAppButton();
await po.page.getByLabel("New app name").fill(newAppName); await po.page.getByLabel("New app name").fill(newAppName);
...@@ -46,10 +46,10 @@ for (const { testName, newAppName, buttonName, expectedVersion } of tests) { ...@@ -46,10 +46,10 @@ for (const { testName, newAppName, buttonName, expectedVersion } of tests) {
timeout: Timeout.MEDIUM, timeout: Timeout.MEDIUM,
}); });
const currentAppName = await po.getCurrentAppName(); const currentAppName = await po.appManagement.getCurrentAppName();
expect(currentAppName).toBe(newAppName); expect(currentAppName).toBe(newAppName);
await po.clickOpenInChatButton(); await po.appManagement.clickOpenInChatButton();
await expect(po.page.getByText(expectedVersion)).toBeVisible(); await expect(po.page.getByText(expectedVersion)).toBeVisible();
await po.snapshotAppFiles({ name: "app" }); await po.snapshotAppFiles({ name: "app" });
......
...@@ -14,7 +14,7 @@ testSkipIfWindows( ...@@ -14,7 +14,7 @@ testSkipIfWindows(
await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG }); await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG });
// Wait for iframe to load and app to render // Wait for iframe to load and app to render
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
await expect( await expect(
iframe.contentFrame().getByText("Console Logs Test App"), iframe.contentFrame().getByText("Console Logs Test App"),
).toBeVisible({ ).toBeVisible({
...@@ -97,7 +97,7 @@ testSkipIfWindows( ...@@ -97,7 +97,7 @@ testSkipIfWindows(
await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG }); await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG });
// Wait for iframe to load - wait for content to appear // Wait for iframe to load - wait for content to appear
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
const iframeFrame = iframe.contentFrame(); const iframeFrame = iframe.contentFrame();
await expect( await expect(
iframeFrame.getByText("Network Requests Test App"), iframeFrame.getByText("Network Requests Test App"),
...@@ -208,7 +208,7 @@ testSkipIfWindows( ...@@ -208,7 +208,7 @@ testSkipIfWindows(
await sendToChatButton.click({ timeout: Timeout.EXTRA_LONG }); await sendToChatButton.click({ timeout: Timeout.EXTRA_LONG });
// Check that the chat input now contains the log information // Check that the chat input now contains the log information
const chatInput = po.getChatInput(); const chatInput = po.chatActions.getChatInput();
const inputValue = await chatInput.textContent(); const inputValue = await chatInput.textContent();
// Verify the log was added to chat input // Verify the log was added to chat input
...@@ -262,7 +262,7 @@ testSkipIfWindows("clear logs button clears all logs", async ({ po }) => { ...@@ -262,7 +262,7 @@ testSkipIfWindows("clear logs button clears all logs", async ({ po }) => {
await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG }); await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG });
// Wait for iframe to load // Wait for iframe to load
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
await expect( await expect(
iframe.contentFrame().getByText("Console Logs Test App"), iframe.contentFrame().getByText("Console Logs Test App"),
).toBeVisible({ ).toBeVisible({
......
...@@ -8,20 +8,22 @@ test("default chat mode - pro user defaults and setting change applies to new ch ...@@ -8,20 +8,22 @@ test("default chat mode - pro user defaults and setting change applies to new ch
// Pro users should default to local-agent mode // Pro users should default to local-agent mode
await expect( await expect(
po.getHomeChatInputContainer().getByTestId("chat-mode-selector"), po.chatActions
.getHomeChatInputContainer()
.getByTestId("chat-mode-selector"),
).toHaveText("Agent"); ).toHaveText("Agent");
// Change default chat mode to "agent" (Build with MCP) in settings // Change default chat mode to "agent" (Build with MCP) in settings
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.page.getByLabel("Default Chat Mode").click(); await po.page.getByLabel("Default Chat Mode").click();
await po.page.getByRole("option", { name: "Build with MCP" }).click(); await po.page.getByRole("option", { name: "Build with MCP" }).click();
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
// Import an app and create a new chat to verify the default is applied // Import an app and create a new chat to verify the default is applied
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.importApp("minimal"); await po.importApp("minimal");
await po.clickNewChat(); await po.chatActions.clickNewChat();
// Verify the chat mode selector shows the new default mode // Verify the chat mode selector shows the new default mode
await expect(po.page.getByTestId("chat-mode-selector")).toContainText( await expect(po.page.getByTestId("chat-mode-selector")).toContainText(
...@@ -34,6 +36,8 @@ test("default chat mode - non-pro user defaults to build", async ({ po }) => { ...@@ -34,6 +36,8 @@ test("default chat mode - non-pro user defaults to build", async ({ po }) => {
// Non-pro users should default to build mode // Non-pro users should default to build mode
await expect( await expect(
po.getHomeChatInputContainer().getByTestId("chat-mode-selector"), po.chatActions
.getHomeChatInputContainer()
.getByTestId("chat-mode-selector"),
).toHaveText("Build"); ).toHaveText("Build");
}); });
...@@ -5,23 +5,23 @@ import { expect } from "@playwright/test"; ...@@ -5,23 +5,23 @@ import { expect } from "@playwright/test";
testSkipIfWindows("delete app", async ({ po }) => { testSkipIfWindows("delete app", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
const appName = await po.getCurrentAppName(); const appName = await po.appManagement.getCurrentAppName();
if (!appName) { if (!appName) {
throw new Error("App name not found"); throw new Error("App name not found");
} }
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await expect(po.getAppListItem({ appName })).toBeVisible(); await expect(po.appManagement.getAppListItem({ appName })).toBeVisible();
// Delete app // Delete app
await po.clickAppDetailsMoreOptions(); await po.appManagement.clickAppDetailsMoreOptions();
// Open delete dialog // Open delete dialog
await po.page.getByRole("button", { name: "Delete" }).click(); await po.page.getByRole("button", { name: "Delete" }).click();
// Confirm delete // Confirm delete
await po.page.getByRole("button", { name: "Delete App" }).click(); await po.page.getByRole("button", { name: "Delete App" }).click();
// Make sure the app is deleted // Make sure the app is deleted
await po.isCurrentAppNameNone(); await po.appManagement.isCurrentAppNameNone();
expect(fs.existsSync(appPath)).toBe(false); expect(fs.existsSync(appPath)).toBe(false);
await expect(po.getAppListItem({ appName })).not.toBeVisible(); await expect(po.appManagement.getAppListItem({ appName })).not.toBeVisible();
}); });
...@@ -2,9 +2,9 @@ import { test } from "./helpers/test_helper"; ...@@ -2,9 +2,9 @@ import { test } from "./helpers/test_helper";
test("delete custom provider should not freeze", async ({ po }) => { test("delete custom provider should not freeze", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByTestId("delete-custom-provider").click(); await po.page.getByTestId("delete-custom-provider").click();
await po.page.getByRole("button", { name: "Delete Provider" }).click(); await po.page.getByRole("button", { name: "Delete Provider" }).click();
// Make sure UI hasn't freezed // Make sure UI hasn't freezed
await po.goToAppsTab(); await po.navigation.goToAppsTab();
}); });
...@@ -6,11 +6,11 @@ import path from "path"; ...@@ -6,11 +6,11 @@ import path from "path";
test("edit code", async ({ po }) => { test("edit code", async ({ po }) => {
const editedFilePath = path.join("src", "components", "made-with-dyad.tsx"); const editedFilePath = path.join("src", "components", "made-with-dyad.tsx");
await po.sendPrompt("foo"); await po.sendPrompt("foo");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.selectPreviewMode("code"); await po.previewPanel.selectPreviewMode("code");
await po.page.getByText("made-with-dyad.tsx").click(); await po.page.getByText("made-with-dyad.tsx").click();
await po.page await po.page
.getByRole("code") .getByRole("code")
...@@ -41,15 +41,15 @@ test("edit code edits the right file", async ({ po }) => { ...@@ -41,15 +41,15 @@ test("edit code edits the right file", async ({ po }) => {
const editedFilePath = path.join("src", "components", "made-with-dyad.tsx"); const editedFilePath = path.join("src", "components", "made-with-dyad.tsx");
const robotsFilePath = path.join("public", "robots.txt"); const robotsFilePath = path.join("public", "robots.txt");
await po.sendPrompt("foo"); await po.sendPrompt("foo");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const originalRobotsFile = fs.readFileSync( const originalRobotsFile = fs.readFileSync(
path.join(appPath, robotsFilePath), path.join(appPath, robotsFilePath),
"utf8", "utf8",
); );
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.selectPreviewMode("code"); await po.previewPanel.selectPreviewMode("code");
await po.page.getByText("made-with-dyad.tsx").click(); await po.page.getByText("made-with-dyad.tsx").click();
await po.page await po.page
.getByRole("code") .getByRole("code")
......
...@@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; ...@@ -3,7 +3,7 @@ import { expect } from "@playwright/test";
test("edit custom model", async ({ po }) => { test("edit custom model", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByText("test-provider").click(); await po.page.getByText("test-provider").click();
// test edit model by double clicking the model panel // test edit model by double clicking the model panel
...@@ -51,5 +51,5 @@ test("edit custom model", async ({ po }) => { ...@@ -51,5 +51,5 @@ test("edit custom model", async ({ po }) => {
await po.page.getByRole("button", { name: "Cancel" }).click(); await po.page.getByRole("button", { name: "Cancel" }).click();
// Make sure UI hasn't freezed // Make sure UI hasn't freezed
await po.goToAppsTab(); await po.navigation.goToAppsTab();
}); });
...@@ -2,7 +2,7 @@ import { test } from "./helpers/test_helper"; ...@@ -2,7 +2,7 @@ import { test } from "./helpers/test_helper";
test("can edit custom provider", async ({ po }) => { test("can edit custom provider", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
// Create a provider first // Create a provider first
......
...@@ -2,7 +2,10 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -2,7 +2,10 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("send message to engine", async ({ po }) => { testSkipIfWindows("send message to engine", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" }); await po.modelPicker.selectModel({
provider: "Google",
model: "Gemini 2.5 Pro",
});
await po.sendPrompt("[dump] tc=turbo-edits"); await po.sendPrompt("[dump] tc=turbo-edits");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
...@@ -13,7 +16,7 @@ testSkipIfWindows("send message to engine - openai gpt-5", async ({ po }) => { ...@@ -13,7 +16,7 @@ testSkipIfWindows("send message to engine - openai gpt-5", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
// By default, it's using auto which points to Flash 2.5 and doesn't // By default, it's using auto which points to Flash 2.5 and doesn't
// use engine. // use engine.
await po.selectModel({ provider: "OpenAI", model: "GPT 5" }); await po.modelPicker.selectModel({ provider: "OpenAI", model: "GPT 5" });
await po.sendPrompt("[dump] tc=turbo-edits"); await po.sendPrompt("[dump] tc=turbo-edits");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
...@@ -25,7 +28,10 @@ testSkipIfWindows( ...@@ -25,7 +28,10 @@ testSkipIfWindows(
await po.setUpDyadPro(); await po.setUpDyadPro();
// By default, it's using auto which points to Flash 2.5 and doesn't // By default, it's using auto which points to Flash 2.5 and doesn't
// use engine. // use engine.
await po.selectModel({ provider: "Anthropic", model: "Claude Sonnet 4" }); await po.modelPicker.selectModel({
provider: "Anthropic",
model: "Claude Sonnet 4",
});
await po.sendPrompt("[dump] tc=turbo-edits"); await po.sendPrompt("[dump] tc=turbo-edits");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
......
...@@ -5,9 +5,9 @@ import fs from "fs"; ...@@ -5,9 +5,9 @@ import fs from "fs";
test("env var", async ({ po }) => { test("env var", async ({ po }) => {
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
await po.selectPreviewMode("configure"); await po.previewPanel.selectPreviewMode("configure");
// Create a new env var // Create a new env var
await po.page await po.page
......
...@@ -7,7 +7,7 @@ test.describe("Favorite App Tests", () => { ...@@ -7,7 +7,7 @@ test.describe("Favorite App Tests", () => {
// Create a test app // Create a test app
await po.sendPrompt("create a test app"); await po.sendPrompt("create a test app");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Get the app name from the UI (randomly generated) // Get the app name from the UI (randomly generated)
const appItems = await po.page.getByTestId(/^app-list-item-/).all(); const appItems = await po.page.getByTestId(/^app-list-item-/).all();
...@@ -43,7 +43,7 @@ test.describe("Favorite App Tests", () => { ...@@ -43,7 +43,7 @@ test.describe("Favorite App Tests", () => {
// Create a test app // Create a test app
await po.sendPrompt("create a test app"); await po.sendPrompt("create a test app");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// Get the app name from the UI // Get the app name from the UI
const appItems = await po.page.getByTestId(/^app-list-item-/).all(); const appItems = await po.page.getByTestId(/^app-list-item-/).all();
......
...@@ -6,8 +6,8 @@ test("file tree search finds content matches and surfaces line numbers", async ( ...@@ -6,8 +6,8 @@ test("file tree search finds content matches and surfaces line numbers", async (
}) => { }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.goToChatTab(); await po.navigation.goToChatTab();
await po.selectPreviewMode("code"); await po.previewPanel.selectPreviewMode("code");
// Wait for the code view to finish loading files // Wait for the code view to finish loading files
await expect( await expect(
po.page.getByText("Loading files...", { exact: false }), po.page.getByText("Loading files...", { exact: false }),
......
...@@ -5,20 +5,20 @@ testSkipIfWindows("fix error with AI", async ({ po }) => { ...@@ -5,20 +5,20 @@ testSkipIfWindows("fix error with AI", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=create-error"); await po.sendPrompt("tc=create-error");
await po.snapshotPreviewErrorBanner(); await po.previewPanel.snapshotPreviewErrorBanner();
await po.page.getByText("Error Line 6 error", { exact: true }).click(); await po.page.getByText("Error Line 6 error", { exact: true }).click();
await po.snapshotPreviewErrorBanner(); await po.previewPanel.snapshotPreviewErrorBanner();
await po.clickFixErrorWithAI(); await po.previewPanel.clickFixErrorWithAI();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
// TODO: this is an actual bug where the error banner should not // TODO: this is an actual bug where the error banner should not
// be shown, however there's some kind of race condition and // be shown, however there's some kind of race condition and
// we don't reliably detect when the HMR update has completed. // we don't reliably detect when the HMR update has completed.
// await po.locatePreviewErrorBanner().waitFor({ state: "hidden" }); // await po.previewPanel.locatePreviewErrorBanner().waitFor({ state: "hidden" });
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
testSkipIfWindows("copy error message from banner", async ({ po }) => { testSkipIfWindows("copy error message from banner", async ({ po }) => {
...@@ -29,7 +29,7 @@ testSkipIfWindows("copy error message from banner", async ({ po }) => { ...@@ -29,7 +29,7 @@ testSkipIfWindows("copy error message from banner", async ({ po }) => {
state: "visible", state: "visible",
}); });
await po.clickCopyErrorMessage(); await po.previewPanel.clickCopyErrorMessage();
const clipboardText = await po.getClipboardText(); const clipboardText = await po.getClipboardText();
expect(clipboardText).toContain("Error Line 6 error"); expect(clipboardText).toContain("Error Line 6 error");
...@@ -45,8 +45,8 @@ test("fix all errors button", async ({ po }) => { ...@@ -45,8 +45,8 @@ test("fix all errors button", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=create-multiple-errors"); await po.sendPrompt("tc=create-multiple-errors");
await po.clickFixAllErrors(); await po.previewPanel.clickFixAllErrors();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -31,7 +31,7 @@ testSkipIfWindows( ...@@ -31,7 +31,7 @@ testSkipIfWindows(
await po.page.keyboard.press("Escape"); await po.page.keyboard.press("Escape");
// 3. Select Basic Agent mode and verify it's selected // 3. Select Basic Agent mode and verify it's selected
await po.selectChatMode("basic-agent"); await po.chatActions.selectChatMode("basic-agent");
await expect(po.page.getByTestId("chat-mode-selector")).toContainText( await expect(po.page.getByTestId("chat-mode-selector")).toContainText(
"Basic Agent", "Basic Agent",
); );
...@@ -39,7 +39,7 @@ testSkipIfWindows( ...@@ -39,7 +39,7 @@ testSkipIfWindows(
// 4. Send 5 messages to exhaust quota (this will exhaust quota even if some was already used) // 4. Send 5 messages to exhaust quota (this will exhaust quota even if some was already used)
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
await po.sendPrompt(`tc=local-agent/simple-response message ${i + 1}`); await po.sendPrompt(`tc=local-agent/simple-response message ${i + 1}`);
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
} }
// 5. Verify quota exceeded banner appears with correct content // 5. Verify quota exceeded banner appears with correct content
...@@ -79,7 +79,7 @@ testSkipIfWindows( ...@@ -79,7 +79,7 @@ testSkipIfWindows(
// 9. Verify user can still send messages in Build mode // 9. Verify user can still send messages in Build mode
await po.sendPrompt("[dyad-qa=write] create a simple file"); await po.sendPrompt("[dyad-qa=write] create a simple file");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
}, },
); );
...@@ -91,7 +91,7 @@ testSkipIfWindows( ...@@ -91,7 +91,7 @@ testSkipIfWindows(
await po.importApp("minimal"); await po.importApp("minimal");
// 1. Select Basic Agent mode and send messages to use some quota // 1. Select Basic Agent mode and send messages to use some quota
await po.selectChatMode("basic-agent"); await po.chatActions.selectChatMode("basic-agent");
await expect(po.page.getByTestId("chat-mode-selector")).toContainText( await expect(po.page.getByTestId("chat-mode-selector")).toContainText(
"Basic Agent", "Basic Agent",
); );
...@@ -99,7 +99,7 @@ testSkipIfWindows( ...@@ -99,7 +99,7 @@ testSkipIfWindows(
// Send 3 messages to use some quota // Send 3 messages to use some quota
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
await po.sendPrompt(`tc=local-agent/simple-response message ${i + 1}`); await po.sendPrompt(`tc=local-agent/simple-response message ${i + 1}`);
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
} }
// 2. Verify quota decreased (exact count may vary due to setup messages) // 2. Verify quota decreased (exact count may vary due to setup messages)
...@@ -122,9 +122,9 @@ testSkipIfWindows( ...@@ -122,9 +122,9 @@ testSkipIfWindows(
// 4. Wait for React Query cache to become stale (staleTime is 500ms in test mode) // 4. Wait for React Query cache to become stale (staleTime is 500ms in test mode)
// then navigate to force a refetch with the updated timestamps // then navigate to force a refetch with the updated timestamps
await po.page.waitForTimeout(1000); await po.page.waitForTimeout(1000);
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.waitForTimeout(500); await po.page.waitForTimeout(500);
await po.goToChatTab(); await po.navigation.goToChatTab();
// Wait for the chat mode selector to be visible // Wait for the chat mode selector to be visible
await expect(po.page.getByTestId("chat-mode-selector")).toBeVisible({ await expect(po.page.getByTestId("chat-mode-selector")).toBeVisible({
timeout: Timeout.MEDIUM, timeout: Timeout.MEDIUM,
...@@ -138,9 +138,9 @@ testSkipIfWindows( ...@@ -138,9 +138,9 @@ testSkipIfWindows(
await po.page.keyboard.press("Escape"); await po.page.keyboard.press("Escape");
// 6. Verify we can send messages again in Basic Agent mode (proves reset worked) // 6. Verify we can send messages again in Basic Agent mode (proves reset worked)
await po.selectChatMode("basic-agent"); await po.chatActions.selectChatMode("basic-agent");
await po.sendPrompt("tc=local-agent/simple-response post-reset message"); await po.sendPrompt("tc=local-agent/simple-response post-reset message");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Successfully sending a message in Basic Agent mode after reset proves the quota was reset // Successfully sending a message in Basic Agent mode after reset proves the quota was reset
// and is usable again. No need to verify the exact quota count as that would require // and is usable again. No need to verify the exact quota count as that would require
// waiting for React Query cache to become stale again. // waiting for React Query cache to become stale again.
......
...@@ -12,7 +12,7 @@ test.describe("Git Collaboration", () => { ...@@ -12,7 +12,7 @@ test.describe("Git Collaboration", () => {
await po.setUp({ disableNativeGit: false }); await po.setUp({ disableNativeGit: false });
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
// Create a new repo to start fresh // Create a new repo to start fresh
...@@ -30,8 +30,8 @@ test.describe("Git Collaboration", () => { ...@@ -30,8 +30,8 @@ test.describe("Git Collaboration", () => {
const featureBranch = "feature-1"; const featureBranch = "feature-1";
// User instruction: Open chat and go to publish tab // User instruction: Open chat and go to publish tab
await po.goToChatTab(); await po.navigation.goToChatTab();
await po.getTitleBarAppNameButton().click(); // Open Publish Panel await po.appManagement.getTitleBarAppNameButton().click(); // Open Publish Panel
// Wait for BranchManager to appear // Wait for BranchManager to appear
await expect( await expect(
...@@ -76,7 +76,7 @@ test.describe("Git Collaboration", () => { ...@@ -76,7 +76,7 @@ test.describe("Git Collaboration", () => {
); );
{ {
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
if (!appPath) throw new Error("App path not found"); if (!appPath) throw new Error("App path not found");
const gitStatus = execSync("git status --porcelain", { const gitStatus = execSync("git status --porcelain", {
cwd: appPath, cwd: appPath,
...@@ -123,7 +123,7 @@ test.describe("Git Collaboration", () => { ...@@ -123,7 +123,7 @@ test.describe("Git Collaboration", () => {
// 4. Merge Branch // 4. Merge Branch
// First, create a file on feature-1 to verify merge actually works // First, create a file on feature-1 to verify merge actually works
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
if (!appPath) throw new Error("App path not found"); if (!appPath) throw new Error("App path not found");
// Switch to feature-1 and create a test file // Switch to feature-1 and create a test file
...@@ -138,7 +138,7 @@ test.describe("Git Collaboration", () => { ...@@ -138,7 +138,7 @@ test.describe("Git Collaboration", () => {
const featureContent = "Content from feature-1 branch"; const featureContent = "Content from feature-1 branch";
fs.writeFileSync(mergeTestFilePath, featureContent); fs.writeFileSync(mergeTestFilePath, featureContent);
// Configure git user for commit // Configure git user for commit
await po.configureGitUser(); await po.appManagement.configureGitUser();
execSync( execSync(
`git add ${mergeTestFile} && git commit -m "Add merge test file"`, `git add ${mergeTestFile} && git commit -m "Add merge test file"`,
{ {
...@@ -164,7 +164,7 @@ test.describe("Git Collaboration", () => { ...@@ -164,7 +164,7 @@ test.describe("Git Collaboration", () => {
await po.page.getByTestId("merge-branch-submit-button").click(); await po.page.getByTestId("merge-branch-submit-button").click();
// Wait for merge to complete // Wait for merge to complete
await po.waitForToast("success", 10000); await po.toastNotifications.waitForToast("success", 10000);
// Verify merge success: file should now exist on main // Verify merge success: file should now exist on main
await expect(async () => { await expect(async () => {
...@@ -206,7 +206,7 @@ test.describe("Git Collaboration", () => { ...@@ -206,7 +206,7 @@ test.describe("Git Collaboration", () => {
await po.setUp({ disableNativeGit: false }); await po.setUp({ disableNativeGit: false });
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
// Create a new repo to start fresh // Create a new repo to start fresh
...@@ -219,11 +219,11 @@ test.describe("Git Collaboration", () => { ...@@ -219,11 +219,11 @@ test.describe("Git Collaboration", () => {
timeout: Timeout.MEDIUM, timeout: Timeout.MEDIUM,
}); });
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
if (!appPath) throw new Error("App path not found"); if (!appPath) throw new Error("App path not found");
// Configure git user // Configure git user
await po.configureGitUser(); await po.appManagement.configureGitUser();
// Create a file locally // Create a file locally
const testFile = "pull-test.txt"; const testFile = "pull-test.txt";
...@@ -235,8 +235,8 @@ test.describe("Git Collaboration", () => { ...@@ -235,8 +235,8 @@ test.describe("Git Collaboration", () => {
}); });
// Go to publish panel // Go to publish panel
await po.goToChatTab(); await po.navigation.goToChatTab();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
// Open the branch actions dropdown // Open the branch actions dropdown
await expect( await expect(
...@@ -250,7 +250,7 @@ test.describe("Git Collaboration", () => { ...@@ -250,7 +250,7 @@ test.describe("Git Collaboration", () => {
await po.page.getByTestId("git-pull-button").click(); await po.page.getByTestId("git-pull-button").click();
// Wait for success toast // Wait for success toast
await po.waitForToast("success", 10000); await po.toastNotifications.waitForToast("success", 10000);
// Verify the file still exists (pull succeeded) // Verify the file still exists (pull succeeded)
expect(fs.existsSync(testFilePath)).toBe(true); expect(fs.existsSync(testFilePath)).toBe(true);
...@@ -267,7 +267,7 @@ test.describe("Git Collaboration", () => { ...@@ -267,7 +267,7 @@ test.describe("Git Collaboration", () => {
test("should invite and remove collaborators", async ({ po }) => { test("should invite and remove collaborators", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.selectPreviewMode("publish"); await po.previewPanel.selectPreviewMode("publish");
await po.githubConnector.connect(); await po.githubConnector.connect();
const repoName = "test-git-collab-invite-" + Date.now(); const repoName = "test-git-collab-invite-" + Date.now();
...@@ -290,7 +290,7 @@ test.describe("Git Collaboration", () => { ...@@ -290,7 +290,7 @@ test.describe("Git Collaboration", () => {
await po.page.getByTestId("collaborator-invite-input").fill(fakeUser); await po.page.getByTestId("collaborator-invite-input").fill(fakeUser);
await po.page.getByTestId("collaborator-invite-button").click(); await po.page.getByTestId("collaborator-invite-button").click();
// Let's check for a toast. // Let's check for a toast.
await po.waitForToast("success"); await po.toastNotifications.waitForToast("success");
// verify collaborator appears in the list // verify collaborator appears in the list
await expect( await expect(
...@@ -300,7 +300,7 @@ test.describe("Git Collaboration", () => { ...@@ -300,7 +300,7 @@ test.describe("Git Collaboration", () => {
// Delete collaborator // Delete collaborator
await po.page.getByTestId(`collaborator-remove-button-${fakeUser}`).click(); await po.page.getByTestId(`collaborator-remove-button-${fakeUser}`).click();
await po.page.getByTestId("confirm-remove-collaborator").click(); await po.page.getByTestId("confirm-remove-collaborator").click();
await po.waitForToast("success"); await po.toastNotifications.waitForToast("success");
await expect( await expect(
po.page.getByTestId(`collaborator-item-${fakeUser}`), po.page.getByTestId(`collaborator-item-${fakeUser}`),
).not.toBeVisible({ ).not.toBeVisible({
......
...@@ -5,7 +5,7 @@ test("should connect to GitHub using device flow", async ({ po }) => { ...@@ -5,7 +5,7 @@ test("should connect to GitHub using device flow", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
// Wait for device flow to start and show the code // Wait for device flow to start and show the code
...@@ -24,7 +24,7 @@ test("create and sync to new repo", async ({ po }) => { ...@@ -24,7 +24,7 @@ test("create and sync to new repo", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
// Verify "Create new repo" is selected by default // Verify "Create new repo" is selected by default
...@@ -61,7 +61,7 @@ test("create and sync to new repo - custom branch", async ({ po }) => { ...@@ -61,7 +61,7 @@ test("create and sync to new repo - custom branch", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
await po.githubConnector.fillCreateRepoName("test-new-repo-custom"); await po.githubConnector.fillCreateRepoName("test-new-repo-custom");
...@@ -90,7 +90,7 @@ test("create repo with spaces in name - should normalize to hyphens", async ({ ...@@ -90,7 +90,7 @@ test("create repo with spaces in name - should normalize to hyphens", async ({
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
// Enter a repo name with spaces - GitHub normalizes these to hyphens // Enter a repo name with spaces - GitHub normalizes these to hyphens
...@@ -122,7 +122,7 @@ test("disconnect from repo", async ({ po }) => { ...@@ -122,7 +122,7 @@ test("disconnect from repo", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
await po.githubConnector.fillCreateRepoName("test-new-repo-disconnect"); await po.githubConnector.fillCreateRepoName("test-new-repo-disconnect");
...@@ -139,7 +139,7 @@ test("create and sync to existing repo", async ({ po }) => { ...@@ -139,7 +139,7 @@ test("create and sync to existing repo", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
await po.githubConnector.getConnectToExistingRepoModeButton().click(); await po.githubConnector.getConnectToExistingRepoModeButton().click();
...@@ -158,7 +158,7 @@ test("create and sync to existing repo - custom branch", async ({ po }) => { ...@@ -158,7 +158,7 @@ test("create and sync to existing repo - custom branch", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
await po.githubConnector.getConnectToExistingRepoModeButton().click(); await po.githubConnector.getConnectToExistingRepoModeButton().click();
...@@ -182,19 +182,19 @@ test("github clear integration settings", async ({ po }) => { ...@@ -182,19 +182,19 @@ test("github clear integration settings", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.githubConnector.connect(); await po.githubConnector.connect();
await expect(po.githubConnector.getCreateNewRepoModeButton()).toBeVisible(); await expect(po.githubConnector.getCreateNewRepoModeButton()).toBeVisible();
// Capture settings before disconnecting // Capture settings before disconnecting
await po.clickOpenInChatButton(); await po.appManagement.clickOpenInChatButton();
// Make sure we are committing so that githubUser.email is getting set. // Make sure we are committing so that githubUser.email is getting set.
await po.sendPrompt("tc=write-index"); await po.sendPrompt("tc=write-index");
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
// Navigate to settings // Navigate to settings
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
// Verify the "Disconnect from GitHub" button is visible (meaning we're connected) // Verify the "Disconnect from GitHub" button is visible (meaning we're connected)
const disconnectButton = po.page.getByRole("button", { const disconnectButton = po.page.getByRole("button", {
...@@ -208,5 +208,5 @@ test("github clear integration settings", async ({ po }) => { ...@@ -208,5 +208,5 @@ test("github clear integration settings", async ({ po }) => {
await expect(disconnectButton).not.toBeVisible(); await expect(disconnectButton).not.toBeVisible();
// Snapshot only the settings that changed (GitHub token/user removed) // Snapshot only the settings that changed (GitHub token/user removed)
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
}); });
...@@ -14,18 +14,18 @@ testSkipIfWindows( ...@@ -14,18 +14,18 @@ testSkipIfWindows(
await po.sendPrompt("tc=multi-page"); await po.sendPrompt("tc=multi-page");
// Wait for the preview iframe to be visible and loaded // Wait for the preview iframe to be visible and loaded
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
// Wait for the Home Page content to be visible in the iframe // Wait for the Home Page content to be visible in the iframe
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Home Page" }), .getByRole("heading", { name: "Home Page" }),
).toBeVisible({ timeout: Timeout.LONG }); ).toBeVisible({ timeout: Timeout.LONG });
// Navigate to /about by clicking the link // Navigate to /about by clicking the link
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Go to About Page") .getByText("Go to About Page")
...@@ -33,7 +33,7 @@ testSkipIfWindows( ...@@ -33,7 +33,7 @@ testSkipIfWindows(
// Wait for About Page to be visible // Wait for About Page to be visible
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "About Page" }), .getByRole("heading", { name: "About Page" }),
...@@ -41,7 +41,7 @@ testSkipIfWindows( ...@@ -41,7 +41,7 @@ testSkipIfWindows(
// Navigate back to / by clicking the link (triggers pushState with pathname "/") // Navigate back to / by clicking the link (triggers pushState with pathname "/")
// This is the scenario that triggers the bug - pushState to "/" doesn't clear preserved URL // This is the scenario that triggers the bug - pushState to "/" doesn't clear preserved URL
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Go to Home Page") .getByText("Go to Home Page")
...@@ -49,7 +49,7 @@ testSkipIfWindows( ...@@ -49,7 +49,7 @@ testSkipIfWindows(
// Wait for Home Page to be visible // Wait for Home Page to be visible
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Home Page" }), .getByRole("heading", { name: "Home Page" }),
...@@ -61,7 +61,7 @@ testSkipIfWindows( ...@@ -61,7 +61,7 @@ testSkipIfWindows(
); );
// Get the app path to modify the Index.tsx file // Get the app path to modify the Index.tsx file
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
if (!appPath) { if (!appPath) {
throw new Error("No app path found"); throw new Error("No app path found");
} }
...@@ -83,13 +83,13 @@ testSkipIfWindows( ...@@ -83,13 +83,13 @@ testSkipIfWindows(
// After HMR, the page should still be on Home Page (/) // After HMR, the page should still be on Home Page (/)
// BUG: Due to the regression, it might incorrectly load /about // BUG: Due to the regression, it might incorrectly load /about
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Home Page" }), .getByRole("heading", { name: "Home Page" }),
).toBeVisible({ timeout: Timeout.MEDIUM }); ).toBeVisible({ timeout: Timeout.MEDIUM });
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "About Page" }), .getByRole("heading", { name: "About Page" }),
......
...@@ -17,7 +17,7 @@ testSkipIfWindows("import app", async ({ po }) => { ...@@ -17,7 +17,7 @@ testSkipIfWindows("import app", async ({ po }) => {
.fill("minimal-imported-app"); .fill("minimal-imported-app");
await po.page.getByRole("button", { name: "Import" }).click(); await po.page.getByRole("button", { name: "Import" }).click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -37,7 +37,7 @@ testSkipIfWindows("import app with AI rules", async ({ po }) => { ...@@ -37,7 +37,7 @@ testSkipIfWindows("import app with AI rules", async ({ po }) => {
.fill("minimal-imported-app"); .fill("minimal-imported-app");
await po.page.getByRole("button", { name: "Import" }).click(); await po.page.getByRole("button", { name: "Import" }).click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.sendPrompt("[dump]"); await po.sendPrompt("[dump]");
...@@ -96,6 +96,6 @@ testSkipIfWindows( ...@@ -96,6 +96,6 @@ testSkipIfWindows(
await po.page.getByRole("button", { name: "Import" }).click(); await po.page.getByRole("button", { name: "Import" }).click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}, },
); );
...@@ -36,5 +36,5 @@ testSkipIfWindows("import app without copying to dyad-apps", async ({ po }) => { ...@@ -36,5 +36,5 @@ testSkipIfWindows("import app without copying to dyad-apps", async ({ po }) => {
await po.page.getByRole("button", { name: "Import" }).click(); await po.page.getByRole("button", { name: "Import" }).click();
// Verify import succeeded // Verify import succeeded
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
import { test } from "./helpers/test_helper"; import { test } from "./helpers/test_helper";
test("send message to LM studio", async ({ po }) => { test("send message to LM studio", async ({ po }) => {
await po.selectTestLMStudioModel(); await po.modelPicker.selectTestLMStudioModel();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -7,11 +7,11 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -7,11 +7,11 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - security review fix", async ({ po }) => { testSkipIfWindows("local-agent - security review fix", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// First, trigger a security review // First, trigger a security review
await po.selectPreviewMode("security"); await po.previewPanel.selectPreviewMode("security");
await po.clickRunSecurityReview(); await po.securityReview.clickRunSecurityReview();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
}); });
...@@ -24,8 +24,8 @@ testSkipIfWindows("local-agent - mention apps", async ({ po }) => { ...@@ -24,8 +24,8 @@ testSkipIfWindows("local-agent - mention apps", async ({ po }) => {
// Import app and reference it. // Import app and reference it.
await po.importApp("minimal-with-ai-rules"); await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Use @app:minimal-with-ai-rules to reference the other app // Use @app:minimal-with-ai-rules to reference the other app
await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi"); await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi");
...@@ -38,7 +38,7 @@ testSkipIfWindows("local-agent - mention apps", async ({ po }) => { ...@@ -38,7 +38,7 @@ testSkipIfWindows("local-agent - mention apps", async ({ po }) => {
*/ */
testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => { testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click(); await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
// Configure the test MCP server // Configure the test MCP server
...@@ -57,9 +57,9 @@ testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => { ...@@ -57,9 +57,9 @@ testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => {
.fill(testMcpServerPath); .fill(testMcpServerPath);
await po.page.getByRole("button", { name: "Add Server" }).click(); await po.page.getByRole("button", { name: "Add Server" }).click();
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send prompt that triggers MCP tool call // Send prompt that triggers MCP tool call
await po.sendPrompt("tc=local-agent/mcp-calculator", { await po.sendPrompt("tc=local-agent/mcp-calculator", {
...@@ -67,11 +67,11 @@ testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => { ...@@ -67,11 +67,11 @@ testSkipIfWindows("local-agent - mcp tool call", async ({ po }) => {
}); });
// MCP tools require consent - wait for the consent banner // MCP tools require consent - wait for the consent banner
await po.waitForAgentConsentBanner(); await po.agentConsent.waitForAgentConsentBanner();
await po.clickAgentConsentAlwaysAllow(); await po.agentConsent.clickAgentConsentAlwaysAllow();
// Wait for chat to complete // Wait for chat to complete
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -10,7 +10,7 @@ testSkipIfWindows("local-agent ask mode", async ({ po }) => { ...@@ -10,7 +10,7 @@ testSkipIfWindows("local-agent ask mode", async ({ po }) => {
await po.importApp("minimal"); await po.importApp("minimal");
// Select ask mode - local agent will be used in read-only mode for Pro users // Select ask mode - local agent will be used in read-only mode for Pro users
await po.selectChatMode("ask"); await po.chatActions.selectChatMode("ask");
// Test read-only tools work // Test read-only tools work
await po.sendPrompt("tc=local-agent/ask-read-file"); await po.sendPrompt("tc=local-agent/ask-read-file");
......
...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - dump request", async ({ po }) => { testSkipIfWindows("local-agent - dump request", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("[dump]"); await po.sendPrompt("[dump]");
...@@ -19,7 +19,7 @@ testSkipIfWindows("local-agent - dump request", async ({ po }) => { ...@@ -19,7 +19,7 @@ testSkipIfWindows("local-agent - dump request", async ({ po }) => {
testSkipIfWindows("local-agent - read then edit", async ({ po }) => { testSkipIfWindows("local-agent - read then edit", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/read-then-edit"); await po.sendPrompt("tc=local-agent/read-then-edit");
await po.snapshotMessages(); await po.snapshotMessages();
...@@ -32,7 +32,7 @@ testSkipIfWindows("local-agent - read then edit", async ({ po }) => { ...@@ -32,7 +32,7 @@ testSkipIfWindows("local-agent - read then edit", async ({ po }) => {
testSkipIfWindows("local-agent - parallel tool calls", async ({ po }) => { testSkipIfWindows("local-agent - parallel tool calls", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/parallel-tools"); await po.sendPrompt("tc=local-agent/parallel-tools");
......
...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - code search", async ({ po }) => { testSkipIfWindows("local-agent - code search", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/code-search"); await po.sendPrompt("tc=local-agent/code-search");
......
...@@ -10,7 +10,7 @@ testSkipIfWindows( ...@@ -10,7 +10,7 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send prompt that triggers add_dependency (requires consent) // Send prompt that triggers add_dependency (requires consent)
await po.sendPrompt("tc=local-agent/add-dependency", { await po.sendPrompt("tc=local-agent/add-dependency", {
...@@ -18,19 +18,19 @@ testSkipIfWindows( ...@@ -18,19 +18,19 @@ testSkipIfWindows(
}); });
// Wait for consent banner to appear // Wait for consent banner to appear
await po.waitForAgentConsentBanner(); await po.agentConsent.waitForAgentConsentBanner();
// Click "Always allow" - should persist the consent // Click "Always allow" - should persist the consent
await po.clickAgentConsentAlwaysAllow(); await po.agentConsent.clickAgentConsentAlwaysAllow();
// Wait for chat to complete // Wait for chat to complete
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
// Send prompt that triggers add_dependency (should not require consent this time) // Send prompt that triggers add_dependency (should not require consent this time)
await po.sendPrompt("tc=local-agent/add-dependency"); await po.sendPrompt("tc=local-agent/add-dependency");
await expect(po.getAgentConsentBanner()).not.toBeVisible(); await expect(po.agentConsent.getAgentConsentBanner()).not.toBeVisible();
}, },
); );
...@@ -39,7 +39,7 @@ testSkipIfWindows( ...@@ -39,7 +39,7 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send prompt that triggers add_dependency (requires consent) // Send prompt that triggers add_dependency (requires consent)
await po.sendPrompt("tc=local-agent/add-dependency", { await po.sendPrompt("tc=local-agent/add-dependency", {
...@@ -47,13 +47,13 @@ testSkipIfWindows( ...@@ -47,13 +47,13 @@ testSkipIfWindows(
}); });
// Wait for consent banner to appear // Wait for consent banner to appear
await po.waitForAgentConsentBanner(); await po.agentConsent.waitForAgentConsentBanner();
// Click "Allow once" - should allow this execution only // Click "Allow once" - should allow this execution only
await po.clickAgentConsentAllowOnce(); await po.agentConsent.clickAgentConsentAllowOnce();
// Wait for chat to complete // Wait for chat to complete
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}, },
...@@ -64,7 +64,7 @@ testSkipIfWindows( ...@@ -64,7 +64,7 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send prompt that triggers add_dependency (requires consent) // Send prompt that triggers add_dependency (requires consent)
await po.sendPrompt("tc=local-agent/add-dependency", { await po.sendPrompt("tc=local-agent/add-dependency", {
...@@ -72,13 +72,13 @@ testSkipIfWindows( ...@@ -72,13 +72,13 @@ testSkipIfWindows(
}); });
// Wait for consent banner to appear // Wait for consent banner to appear
await po.waitForAgentConsentBanner(); await po.agentConsent.waitForAgentConsentBanner();
// Click "Decline" - should reject the tool execution // Click "Decline" - should reject the tool execution
await po.clickAgentConsentDecline(); await po.agentConsent.clickAgentConsentDecline();
// Wait for chat to complete (should show error about declined permission) // Wait for chat to complete (should show error about declined permission)
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}, },
......
...@@ -13,10 +13,10 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -13,10 +13,10 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - upload file to codebase", async ({ po }) => { testSkipIfWindows("local-agent - upload file to codebase", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Open auxiliary actions menu // Open auxiliary actions menu
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -42,7 +42,7 @@ testSkipIfWindows("local-agent - upload file to codebase", async ({ po }) => { ...@@ -42,7 +42,7 @@ testSkipIfWindows("local-agent - upload file to codebase", async ({ po }) => {
await po.sendPrompt("tc=local-agent/upload-to-codebase"); await po.sendPrompt("tc=local-agent/upload-to-codebase");
// Verify the file was written to the codebase // Verify the file was written to the codebase
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const filePath = path.join(appPath, "assets", "uploaded-file.png"); const filePath = path.join(appPath, "assets", "uploaded-file.png");
// The file should exist // The file should exist
......
...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - grep search", async ({ po }) => { testSkipIfWindows("local-agent - grep search", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/grep-search"); await po.sendPrompt("tc=local-agent/grep-search");
......
...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -8,7 +8,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - list_files", async ({ po }) => { testSkipIfWindows("local-agent - list_files", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/list-files-non-recursive"); await po.sendPrompt("tc=local-agent/list-files-non-recursive");
await po.sendPrompt("tc=local-agent/list-files-recursive"); await po.sendPrompt("tc=local-agent/list-files-recursive");
...@@ -24,7 +24,7 @@ testSkipIfWindows("local-agent - list_files", async ({ po }) => { ...@@ -24,7 +24,7 @@ testSkipIfWindows("local-agent - list_files", async ({ po }) => {
testSkipIfWindows("local-agent - list_files include_hidden", async ({ po }) => { testSkipIfWindows("local-agent - list_files include_hidden", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal-with-dyad"); await po.importApp("minimal-with-dyad");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/list-files-include-hidden"); await po.sendPrompt("tc=local-agent/list-files-include-hidden");
const listFiles = po.page.getByTestId("dyad-list-files").first(); const listFiles = po.page.getByTestId("dyad-list-files").first();
......
...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - read logs with filters", async ({ po }) => { testSkipIfWindows("local-agent - read logs with filters", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Send prompt that triggers read_logs with various filters // Send prompt that triggers read_logs with various filters
// The fixture tests multiple filter combinations: // The fixture tests multiple filter combinations:
......
...@@ -11,13 +11,13 @@ testSkipIfWindows( ...@@ -11,13 +11,13 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// Ensure pnpm install has run so TypeScript is available // Ensure pnpm install has run so TypeScript is available
await po.ensurePnpmInstall(); await po.appManagement.ensurePnpmInstall();
// Switch to Problems panel first to observe the update // Switch to Problems panel first to observe the update
await po.selectPreviewMode("problems"); await po.previewPanel.selectPreviewMode("problems");
// Initially there should be no problems // Initially there should be no problems
const fixButton = po.page.getByTestId("fix-all-button"); const fixButton = po.page.getByTestId("fix-all-button");
...@@ -41,6 +41,6 @@ testSkipIfWindows( ...@@ -41,6 +41,6 @@ testSkipIfWindows(
await expect(problemRows.first()).toBeVisible({ timeout: Timeout.MEDIUM }); await expect(problemRows.first()).toBeVisible({ timeout: Timeout.MEDIUM });
// Take a snapshot of the problems pane for regression testing // Take a snapshot of the problems pane for regression testing
await po.snapshotProblemsPane(); await po.previewPanel.snapshotProblemsPane();
}, },
); );
...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -9,7 +9,7 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("local-agent - search_replace edit", async ({ po }) => { testSkipIfWindows("local-agent - search_replace edit", async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
await po.sendPrompt("tc=local-agent/search-replace"); await po.sendPrompt("tc=local-agent/search-replace");
......
...@@ -14,7 +14,7 @@ testSkipIfWindows( ...@@ -14,7 +14,7 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectLocalAgentMode(); await po.chatActions.selectLocalAgentMode();
// First, send a message to create a chat with some content // First, send a message to create a chat with some content
// This simulates a chat with technical discussion // This simulates a chat with technical discussion
...@@ -27,7 +27,7 @@ testSkipIfWindows( ...@@ -27,7 +27,7 @@ testSkipIfWindows(
const originalChatId = chatIdMatch![1]; const originalChatId = chatIdMatch![1];
// Create a new chat by clicking the "New Chat" button // Create a new chat by clicking the "New Chat" button
await po.clickNewChat(); await po.chatActions.clickNewChat();
// Now trigger summarization by sending the summarize prompt // Now trigger summarization by sending the summarize prompt
// This is the same mechanism used by the "Summarize into new chat" button // This is the same mechanism used by the "Summarize into new chat" button
......
...@@ -5,7 +5,7 @@ import { expect } from "@playwright/test"; ...@@ -5,7 +5,7 @@ import { expect } from "@playwright/test";
testSkipIfWindows("mcp - call calculator", async ({ po }) => { testSkipIfWindows("mcp - call calculator", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click(); await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
await po.page await po.page
...@@ -29,8 +29,8 @@ testSkipIfWindows("mcp - call calculator", async ({ po }) => { ...@@ -29,8 +29,8 @@ testSkipIfWindows("mcp - call calculator", async ({ po }) => {
await po.page.getByRole("textbox", { name: "Key" }).fill("testKey1"); await po.page.getByRole("textbox", { name: "Key" }).fill("testKey1");
await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1"); await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1");
await po.page.getByRole("button", { name: "Save" }).click(); await po.page.getByRole("button", { name: "Save" }).click();
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectChatMode("agent"); await po.chatActions.selectChatMode("agent");
await po.sendPrompt("[call_tool=calculator_add]", { await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true, skipWaitForCompletion: true,
}); });
...@@ -89,7 +89,7 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => { ...@@ -89,7 +89,7 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => {
try { try {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click(); await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
// Fill in server name // Fill in server name
...@@ -112,10 +112,10 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => { ...@@ -112,10 +112,10 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => {
await po.page.getByRole("textbox", { name: "Key" }).fill("Authorization"); await po.page.getByRole("textbox", { name: "Key" }).fill("Authorization");
await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1"); await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1");
await po.page.getByRole("button", { name: "Save" }).click(); await po.page.getByRole("button", { name: "Save" }).click();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click(); await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectChatMode("agent"); await po.chatActions.selectChatMode("agent");
await po.sendPrompt("[call_tool=calculator_add]", { await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true, skipWaitForCompletion: true,
}); });
......
...@@ -4,7 +4,7 @@ test("mention app (without pro)", async ({ po }) => { ...@@ -4,7 +4,7 @@ test("mention app (without pro)", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.importApp("minimal-with-ai-rules"); await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi"); await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi");
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
...@@ -14,8 +14,8 @@ test("mention app (with pro)", async ({ po }) => { ...@@ -14,8 +14,8 @@ test("mention app (with pro)", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.importApp("minimal-with-ai-rules"); await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectChatMode("build"); await po.chatActions.selectChatMode("build");
await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi"); await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
......
...@@ -5,10 +5,10 @@ test("mention file", async ({ po }) => { ...@@ -5,10 +5,10 @@ test("mention file", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.importApp("minimal-with-ai-rules"); await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.getChatInput().click(); await po.chatActions.getChatInput().click();
// Use pressSequentially so the mention trigger (@) is properly detected by Lexical // Use pressSequentially so the mention trigger (@) is properly detected by Lexical
await po.getChatInput().pressSequentially("[dump] @"); await po.chatActions.getChatInput().pressSequentially("[dump] @");
// Wait for the mention menu to appear // Wait for the mention menu to appear
const menuItem = po.page.getByRole("menuitem", { const menuItem = po.page.getByRole("menuitem", {
name: "Choose AI_RULES.md", name: "Choose AI_RULES.md",
...@@ -16,7 +16,7 @@ test("mention file", async ({ po }) => { ...@@ -16,7 +16,7 @@ test("mention file", async ({ po }) => {
await expect(menuItem).toBeVisible({ timeout: Timeout.MEDIUM }); await expect(menuItem).toBeVisible({ timeout: Timeout.MEDIUM });
await menuItem.click(); await menuItem.click();
await po.page.getByRole("button", { name: "Send message" }).click(); await po.page.getByRole("button", { name: "Send message" }).click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
}); });
...@@ -10,7 +10,7 @@ newChatTestCases.forEach(({ name, clickOptions }) => { ...@@ -10,7 +10,7 @@ newChatTestCases.forEach(({ name, clickOptions }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=chat1"); await po.sendPrompt("tc=chat1");
await po.snapshotMessages(); await po.snapshotMessages();
await po.clickNewChat(clickOptions); await po.chatActions.clickNewChat(clickOptions);
// Make sure it's empty // Make sure it's empty
await po.snapshotMessages(); await po.snapshotMessages();
......
...@@ -4,7 +4,7 @@ import { expect } from "@playwright/test"; ...@@ -4,7 +4,7 @@ import { expect } from "@playwright/test";
test.describe("Node.js Path Configuration", () => { test.describe("Node.js Path Configuration", () => {
test("should browse and set custom Node.js path", async ({ po }) => { test("should browse and set custom Node.js path", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const browseButton = po.page.getByRole("button", { const browseButton = po.page.getByRole("button", {
name: /Browse for Node\.js/i, name: /Browse for Node\.js/i,
...@@ -19,7 +19,7 @@ test.describe("Node.js Path Configuration", () => { ...@@ -19,7 +19,7 @@ test.describe("Node.js Path Configuration", () => {
test("should reset custom path to system default", async ({ po }) => { test("should reset custom path to system default", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const resetButton = po.page.getByRole("button", { const resetButton = po.page.getByRole("button", {
name: /Reset to Default/i, name: /Reset to Default/i,
...@@ -35,7 +35,7 @@ test.describe("Node.js Path Configuration", () => { ...@@ -35,7 +35,7 @@ test.describe("Node.js Path Configuration", () => {
test("should show CheckCircle when Node.js is valid", async ({ po }) => { test("should show CheckCircle when Node.js is valid", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
// Wait for status check // Wait for status check
await po.page.waitForTimeout(2000); await po.page.waitForTimeout(2000);
......
import { test } from "./helpers/test_helper"; import { test } from "./helpers/test_helper";
test("send message to ollama", async ({ po }) => { test("send message to ollama", async ({ po }) => {
await po.selectTestOllamaModel(); await po.modelPicker.selectTestOllamaModel();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -31,7 +31,7 @@ testWithConfig({ ...@@ -31,7 +31,7 @@ testWithConfig({
"force-close detection shows dialog with performance data", "force-close detection shows dialog with performance data",
async ({ po }) => { async ({ po }) => {
// Wait for the home page to be visible first // Wait for the home page to be visible first
await expect(po.getHomeChatInputContainer()).toBeVisible({ await expect(po.chatActions.getHomeChatInputContainer()).toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
...@@ -97,7 +97,7 @@ testWithConfig({ ...@@ -97,7 +97,7 @@ testWithConfig({
}, },
})("no force-close dialog when app was properly shut down", async ({ po }) => { })("no force-close dialog when app was properly shut down", async ({ po }) => {
// Verify the home page loaded normally // Verify the home page loaded normally
await expect(po.getHomeChatInputContainer()).toBeVisible({ await expect(po.chatActions.getHomeChatInputContainer()).toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
...@@ -111,7 +111,7 @@ testWithConfig({})( ...@@ -111,7 +111,7 @@ testWithConfig({})(
"performance information is being captured during normal operation", "performance information is being captured during normal operation",
async ({ po, electronApp }) => { async ({ po, electronApp }) => {
// Wait for the app to load // Wait for the app to load
await expect(po.getHomeChatInputContainer()).toBeVisible({ await expect(po.chatActions.getHomeChatInputContainer()).toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
......
...@@ -9,10 +9,10 @@ test("plan mode - accept plan redirects to new chat and saves to disk", async ({ ...@@ -9,10 +9,10 @@ test("plan mode - accept plan redirects to new chat and saves to disk", async ({
test.setTimeout(180000); test.setTimeout(180000);
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectChatMode("plan"); await po.chatActions.selectChatMode("plan");
// Get app path before accepting (needed to check saved plan) // Get app path before accepting (needed to check saved plan)
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
// Trigger write_plan fixture // Trigger write_plan fixture
await po.sendPrompt("tc=local-agent/accept-plan"); await po.sendPrompt("tc=local-agent/accept-plan");
...@@ -56,7 +56,7 @@ test("plan mode - questionnaire flow", async ({ po }) => { ...@@ -56,7 +56,7 @@ test("plan mode - questionnaire flow", async ({ po }) => {
test.setTimeout(180000); test.setTimeout(180000);
await po.setUpDyadPro({ localAgent: true }); await po.setUpDyadPro({ localAgent: true });
await po.importApp("minimal"); await po.importApp("minimal");
await po.selectChatMode("plan"); await po.chatActions.selectChatMode("plan");
// Trigger questionnaire fixture // Trigger questionnaire fixture
await po.sendPrompt("tc=local-agent/questionnaire"); await po.sendPrompt("tc=local-agent/questionnaire");
...@@ -73,7 +73,7 @@ test("plan mode - questionnaire flow", async ({ po }) => { ...@@ -73,7 +73,7 @@ test("plan mode - questionnaire flow", async ({ po }) => {
await po.page.getByRole("button", { name: /Submit/ }).click(); await po.page.getByRole("button", { name: /Submit/ }).click();
// Wait for the LLM response to the submitted answers // Wait for the LLM response to the submitted answers
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Snapshot the messages // Snapshot the messages
await po.snapshotMessages(); await po.snapshotMessages();
......
...@@ -5,6 +5,6 @@ testSkipIfWindows("preview iframe has sandbox attributes", async ({ po }) => { ...@@ -5,6 +5,6 @@ testSkipIfWindows("preview iframe has sandbox attributes", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
expect( expect(
await po.getPreviewIframeElement().getAttribute("sandbox"), await po.previewPanel.getPreviewIframeElement().getAttribute("sandbox"),
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
...@@ -8,7 +8,7 @@ const MINIMAL_APP = "minimal-with-ai-rules"; ...@@ -8,7 +8,7 @@ const MINIMAL_APP = "minimal-with-ai-rules";
test("problems auto-fix - enabled", async ({ po }) => { test("problems auto-fix - enabled", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
await po.sendPrompt("tc=create-ts-errors"); await po.sendPrompt("tc=create-ts-errors");
...@@ -21,7 +21,7 @@ test("problems auto-fix - enabled", async ({ po }) => { ...@@ -21,7 +21,7 @@ test("problems auto-fix - enabled", async ({ po }) => {
test("problems auto-fix - gives up after 2 attempts", async ({ po }) => { test("problems auto-fix - gives up after 2 attempts", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
await po.sendPrompt("tc=create-unfixable-ts-errors"); await po.sendPrompt("tc=create-unfixable-ts-errors");
...@@ -38,7 +38,7 @@ test("problems auto-fix - gives up after 2 attempts", async ({ po }) => { ...@@ -38,7 +38,7 @@ test("problems auto-fix - gives up after 2 attempts", async ({ po }) => {
test("problems auto-fix - complex delete-rename-write", async ({ po }) => { test("problems auto-fix - complex delete-rename-write", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
await po.sendPrompt("tc=create-ts-errors-complex"); await po.sendPrompt("tc=create-ts-errors-complex");
...@@ -51,7 +51,7 @@ test("problems auto-fix - complex delete-rename-write", async ({ po }) => { ...@@ -51,7 +51,7 @@ test("problems auto-fix - complex delete-rename-write", async ({ po }) => {
test("problems auto-fix - disabled", async ({ po }) => { test("problems auto-fix - disabled", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: false }); await po.setUp({ enableAutoFixProblems: false });
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
await po.sendPrompt("tc=create-ts-errors"); await po.sendPrompt("tc=create-ts-errors");
...@@ -61,7 +61,7 @@ test("problems auto-fix - disabled", async ({ po }) => { ...@@ -61,7 +61,7 @@ test("problems auto-fix - disabled", async ({ po }) => {
testSkipIfWindows("problems - fix all", async ({ po }) => { testSkipIfWindows("problems - fix all", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const badFilePath = path.join(appPath, "src", "bad-file.tsx"); const badFilePath = path.join(appPath, "src", "bad-file.tsx");
fs.writeFileSync( fs.writeFileSync(
badFilePath, badFilePath,
...@@ -73,11 +73,12 @@ nonExistentFunction3(); ...@@ -73,11 +73,12 @@ nonExistentFunction3();
export default App; export default App;
`, `,
); );
await po.ensurePnpmInstall(); await po.appManagement.ensurePnpmInstall();
await po.sendPrompt("tc=create-ts-errors"); await po.sendPrompt("tc=create-ts-errors");
await po.selectPreviewMode("problems"); await po.previewPanel.selectPreviewMode("problems");
await po.clickFixAllProblems(); await po.previewPanel.clickFixAllProblems();
await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("last-message"); await po.snapshotServerDump("last-message");
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
...@@ -90,7 +91,7 @@ testSkipIfWindows( ...@@ -90,7 +91,7 @@ testSkipIfWindows(
await po.importApp(MINIMAL_APP); await po.importApp(MINIMAL_APP);
// Create multiple TS errors in one file // Create multiple TS errors in one file
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const badFilePath = path.join(appPath, "src", "bad-file.tsx"); const badFilePath = path.join(appPath, "src", "bad-file.tsx");
fs.writeFileSync( fs.writeFileSync(
badFilePath, badFilePath,
...@@ -103,12 +104,12 @@ export default App; ...@@ -103,12 +104,12 @@ export default App;
`, `,
); );
await po.ensurePnpmInstall(); await po.appManagement.ensurePnpmInstall();
// Trigger creation of problems and open problems panel // Trigger creation of problems and open problems panel
// await po.sendPrompt("tc=create-ts-errors"); // await po.sendPrompt("tc=create-ts-errors");
await po.selectPreviewMode("problems"); await po.previewPanel.selectPreviewMode("problems");
await po.clickRecheckProblems(); await po.previewPanel.clickRecheckProblems();
// Initially, all selected: button shows Fix X problems and Clear all is visible // Initially, all selected: button shows Fix X problems and Clear all is visible
const fixButton = po.page.getByTestId("fix-all-button"); const fixButton = po.page.getByTestId("fix-all-button");
...@@ -143,7 +144,7 @@ export default App; ...@@ -143,7 +144,7 @@ export default App;
await expect(fixButton).toContainText(/Fix 2 problems/); await expect(fixButton).toContainText(/Fix 2 problems/);
await fixButton.click(); await fixButton.click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("last-message"); await po.snapshotServerDump("last-message");
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
}, },
...@@ -153,7 +154,7 @@ testSkipIfWindows("problems - manual edit (react/vite)", async ({ po }) => { ...@@ -153,7 +154,7 @@ testSkipIfWindows("problems - manual edit (react/vite)", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const badFilePath = path.join(appPath, "src", "bad-file.tsx"); const badFilePath = path.join(appPath, "src", "bad-file.tsx");
fs.writeFileSync( fs.writeFileSync(
badFilePath, badFilePath,
...@@ -163,27 +164,27 @@ nonExistentFunction(); ...@@ -163,27 +164,27 @@ nonExistentFunction();
export default App; export default App;
`, `,
); );
await po.ensurePnpmInstall(); await po.appManagement.ensurePnpmInstall();
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.selectPreviewMode("problems"); await po.previewPanel.selectPreviewMode("problems");
const fixButton = po.page.getByTestId("fix-all-button"); const fixButton = po.page.getByTestId("fix-all-button");
await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG }); await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG });
await expect(fixButton).toContainText(/Fix 1 problem/); await expect(fixButton).toContainText(/Fix 1 problem/);
fs.unlinkSync(badFilePath); fs.unlinkSync(badFilePath);
await po.clickRecheckProblems(); await po.previewPanel.clickRecheckProblems();
await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG }); await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG });
await expect(fixButton).toContainText(/Fix 0 problems/); await expect(fixButton).toContainText(/Fix 0 problems/);
}); });
testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => { testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => {
await po.setUp({ enableAutoFixProblems: true }); await po.setUp({ enableAutoFixProblems: true });
await po.goToHubAndSelectTemplate("Next.js Template"); await po.navigation.goToHubAndSelectTemplate("Next.js Template");
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const badFilePath = path.join(appPath, "src", "bad-file.tsx"); const badFilePath = path.join(appPath, "src", "bad-file.tsx");
fs.writeFileSync( fs.writeFileSync(
badFilePath, badFilePath,
...@@ -193,17 +194,17 @@ testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => { ...@@ -193,17 +194,17 @@ testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => {
export default App; export default App;
`, `,
); );
await po.ensurePnpmInstall(); await po.appManagement.ensurePnpmInstall();
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.selectPreviewMode("problems"); await po.previewPanel.selectPreviewMode("problems");
const fixButton = po.page.getByTestId("fix-all-button"); const fixButton = po.page.getByTestId("fix-all-button");
await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG }); await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG });
await expect(fixButton).toContainText(/Fix 1 problem/); await expect(fixButton).toContainText(/Fix 1 problem/);
fs.unlinkSync(badFilePath); fs.unlinkSync(badFilePath);
await po.clickRecheckProblems(); await po.previewPanel.clickRecheckProblems();
await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG }); await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG });
await expect(fixButton).toContainText(/Fix 0 problems/); await expect(fixButton).toContainText(/Fix 0 problems/);
}); });
...@@ -3,9 +3,9 @@ import { expect } from "@playwright/test"; ...@@ -3,9 +3,9 @@ import { expect } from "@playwright/test";
test("create and edit prompt", async ({ po }) => { test("create and edit prompt", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Prompts" }).click(); await po.page.getByRole("link", { name: "Prompts" }).click();
await po.createPrompt({ await po.promptLibrary.createPrompt({
title: "title1", title: "title1",
description: "desc", description: "desc",
content: "prompt1content", content: "prompt1content",
...@@ -24,9 +24,9 @@ test("create and edit prompt", async ({ po }) => { ...@@ -24,9 +24,9 @@ test("create and edit prompt", async ({ po }) => {
test("delete prompt", async ({ po }) => { test("delete prompt", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Prompts" }).click(); await po.page.getByRole("link", { name: "Prompts" }).click();
await po.createPrompt({ await po.promptLibrary.createPrompt({
title: "title1", title: "title1",
description: "desc", description: "desc",
content: "prompt1content", content: "prompt1content",
...@@ -40,20 +40,20 @@ test("delete prompt", async ({ po }) => { ...@@ -40,20 +40,20 @@ test("delete prompt", async ({ po }) => {
test("use prompt", async ({ po }) => { test("use prompt", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Prompts" }).click(); await po.page.getByRole("link", { name: "Prompts" }).click();
await po.createPrompt({ await po.promptLibrary.createPrompt({
title: "title1", title: "title1",
description: "desc", description: "desc",
content: "prompt1content", content: "prompt1content",
}); });
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.getChatInput().click(); await po.chatActions.getChatInput().click();
await po.getChatInput().fill("[dump] @"); await po.chatActions.getChatInput().fill("[dump] @");
await po.page.getByRole("menuitem", { name: "Choose title1" }).click(); await po.page.getByRole("menuitem", { name: "Choose title1" }).click();
await po.page.getByRole("button", { name: "Send message" }).click(); await po.page.getByRole("button", { name: "Send message" }).click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotServerDump("last-message"); await po.snapshotServerDump("last-message");
}); });
...@@ -6,19 +6,19 @@ import path from "path"; ...@@ -6,19 +6,19 @@ import path from "path";
testSkipIfWindows("rebuild app", async ({ po }) => { testSkipIfWindows("rebuild app", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
const currentAppPath = await po.getCurrentAppPath(); const currentAppPath = await po.appManagement.getCurrentAppPath();
const testPath = path.join(currentAppPath, "node_modules", "test.txt"); const testPath = path.join(currentAppPath, "node_modules", "test.txt");
fs.writeFileSync(testPath, "test"); fs.writeFileSync(testPath, "test");
await po.clickRebuild(); await po.previewPanel.clickRebuild();
await expect(po.locateLoadingAppPreview()).toBeVisible(); await expect(po.previewPanel.locateLoadingAppPreview()).toBeVisible();
await expect(po.locateLoadingAppPreview()).not.toBeVisible({ await expect(po.previewPanel.locateLoadingAppPreview()).not.toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
// Check that the file is removed with the rebuild // Check that the file is removed with the rebuild
expect(fs.existsSync(testPath)).toBe(false); expect(fs.existsSync(testPath)).toBe(false);
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
...@@ -7,7 +7,7 @@ testSkipIfWindows("refresh app", async ({ po }) => { ...@@ -7,7 +7,7 @@ testSkipIfWindows("refresh app", async ({ po }) => {
// Drop the document.body inside the contentFrame to make // Drop the document.body inside the contentFrame to make
// sure refresh works. // sure refresh works.
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.locator("body") .locator("body")
...@@ -15,8 +15,8 @@ testSkipIfWindows("refresh app", async ({ po }) => { ...@@ -15,8 +15,8 @@ testSkipIfWindows("refresh app", async ({ po }) => {
body.remove(); body.remove();
}); });
await po.clickPreviewRefresh(); await po.previewPanel.clickPreviewRefresh();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
testSkipIfWindows("refresh preserves current route", async ({ po }) => { testSkipIfWindows("refresh preserves current route", async ({ po }) => {
...@@ -26,15 +26,18 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => { ...@@ -26,15 +26,18 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => {
await po.sendPrompt("tc=multi-page"); await po.sendPrompt("tc=multi-page");
// Wait for the preview iframe to be visible and loaded // Wait for the preview iframe to be visible and loaded
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
// Wait for the Home Page content to be visible in the iframe // Wait for the Home Page content to be visible in the iframe
await expect( await expect(
po.getPreviewIframeElement().contentFrame().getByText("Home Page"), po.previewPanel
.getPreviewIframeElement()
.contentFrame()
.getByText("Home Page"),
).toBeVisible({ timeout: Timeout.LONG }); ).toBeVisible({ timeout: Timeout.LONG });
// Click on the navigation link to go to /about (realistic user behavior) // Click on the navigation link to go to /about (realistic user behavior)
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Go to About Page") .getByText("Go to About Page")
...@@ -42,15 +45,21 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => { ...@@ -42,15 +45,21 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => {
// Wait for the About Page content to be visible // Wait for the About Page content to be visible
await expect( await expect(
po.getPreviewIframeElement().contentFrame().getByText("About Page"), po.previewPanel
.getPreviewIframeElement()
.contentFrame()
.getByText("About Page"),
).toBeVisible({ timeout: Timeout.MEDIUM }); ).toBeVisible({ timeout: Timeout.MEDIUM });
// Click refresh // Click refresh
await po.clickPreviewRefresh(); await po.previewPanel.clickPreviewRefresh();
// Verify the route is preserved after refresh - About Page should still be visible // Verify the route is preserved after refresh - About Page should still be visible
await expect( await expect(
po.getPreviewIframeElement().contentFrame().getByText("About Page"), po.previewPanel
.getPreviewIframeElement()
.contentFrame()
.getByText("About Page"),
).toBeVisible({ timeout: Timeout.MEDIUM }); ).toBeVisible({ timeout: Timeout.MEDIUM });
// Wait to see if the page stays on About Page (reproducing local issue with HMR) // Wait to see if the page stays on About Page (reproducing local issue with HMR)
...@@ -59,13 +68,13 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => { ...@@ -59,13 +68,13 @@ testSkipIfWindows("refresh preserves current route", async ({ po }) => {
// Verify it's STILL on About Page after waiting - check that About Page heading is visible // Verify it's STILL on About Page after waiting - check that About Page heading is visible
// and the Home Page heading is not (use getByRole to match the heading, not the link text) // and the Home Page heading is not (use getByRole to match the heading, not the link text)
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "About Page" }), .getByRole("heading", { name: "About Page" }),
).toBeVisible({ timeout: Timeout.MEDIUM }); ).toBeVisible({ timeout: Timeout.MEDIUM });
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Home Page" }), .getByRole("heading", { name: "Home Page" }),
...@@ -81,11 +90,14 @@ testSkipIfWindows( ...@@ -81,11 +90,14 @@ testSkipIfWindows(
await po.sendPrompt("tc=multi-page"); await po.sendPrompt("tc=multi-page");
// Wait for the preview iframe to be visible and loaded // Wait for the preview iframe to be visible and loaded
await po.expectPreviewIframeIsVisible(); await po.previewPanel.expectPreviewIframeIsVisible();
// Wait for the Home Page content to be visible in the iframe // Wait for the Home Page content to be visible in the iframe
await expect( await expect(
po.getPreviewIframeElement().contentFrame().getByText("Home Page"), po.previewPanel
.getPreviewIframeElement()
.contentFrame()
.getByText("Home Page"),
).toBeVisible({ timeout: Timeout.LONG }); ).toBeVisible({ timeout: Timeout.LONG });
// Verify back button is disabled initially (no history) // Verify back button is disabled initially (no history)
...@@ -94,7 +106,7 @@ testSkipIfWindows( ...@@ -94,7 +106,7 @@ testSkipIfWindows(
).toBeDisabled(); ).toBeDisabled();
// Click on the navigation link to go to /about // Click on the navigation link to go to /about
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Go to About Page") .getByText("Go to About Page")
...@@ -102,7 +114,7 @@ testSkipIfWindows( ...@@ -102,7 +114,7 @@ testSkipIfWindows(
// Wait for the About Page content to be visible // Wait for the About Page content to be visible
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "About Page" }), .getByRole("heading", { name: "About Page" }),
...@@ -114,11 +126,11 @@ testSkipIfWindows( ...@@ -114,11 +126,11 @@ testSkipIfWindows(
).toBeEnabled(); ).toBeEnabled();
// Click back button to go back to Home Page // Click back button to go back to Home Page
await po.clickPreviewNavigateBack(); await po.previewPanel.clickPreviewNavigateBack();
// Verify we're back on Home Page // Verify we're back on Home Page
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Home Page" }), .getByRole("heading", { name: "Home Page" }),
...@@ -130,11 +142,11 @@ testSkipIfWindows( ...@@ -130,11 +142,11 @@ testSkipIfWindows(
).toBeEnabled(); ).toBeEnabled();
// Click forward button to go back to About Page // Click forward button to go back to About Page
await po.clickPreviewNavigateForward(); await po.previewPanel.clickPreviewNavigateForward();
// Verify we're on About Page again // Verify we're on About Page again
await expect( await expect(
po po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "About Page" }), .getByRole("heading", { name: "About Page" }),
......
...@@ -10,5 +10,5 @@ test("reject", async ({ po }) => { ...@@ -10,5 +10,5 @@ test("reject", async ({ po }) => {
// Should be slightly different from above, because it will say "rejected" // Should be slightly different from above, because it will say "rejected"
await po.snapshotMessages(); await po.snapshotMessages();
await expect(po.getPreviewIframeElement()).not.toBeVisible(); await expect(po.previewPanel.getPreviewIframeElement()).not.toBeVisible();
}); });
...@@ -4,21 +4,21 @@ import { test } from "./helpers/test_helper"; ...@@ -4,21 +4,21 @@ import { test } from "./helpers/test_helper";
test("release channel - change from stable to beta and back", async ({ test("release channel - change from stable to beta and back", async ({
po, po,
}) => { }) => {
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
// Change to beta channel // Change to beta channel
const beforeSettings1 = po.recordSettings(); const beforeSettings1 = po.settings.recordSettings();
await po.changeReleaseChannel("beta"); await po.settings.changeReleaseChannel("beta");
await expect( await expect(
po.page.getByRole("button", { name: "Restart Dyad" }), po.page.getByRole("button", { name: "Restart Dyad" }),
).toBeVisible(); ).toBeVisible();
po.snapshotSettingsDelta(beforeSettings1); po.settings.snapshotSettingsDelta(beforeSettings1);
// Change back to stable channel // Change back to stable channel
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await po.changeReleaseChannel("stable"); await po.settings.changeReleaseChannel("stable");
await expect( await expect(
po.page.getByRole("button", { name: "Download Stable" }), po.page.getByRole("button", { name: "Download Stable" }),
).toBeVisible(); ).toBeVisible();
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
}); });
...@@ -6,10 +6,10 @@ test("rename app (including folder)", async ({ po }) => { ...@@ -6,10 +6,10 @@ test("rename app (including folder)", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.clickAppDetailsRenameAppButton(); await po.appManagement.clickAppDetailsRenameAppButton();
await po.page await po.page
.getByRole("textbox", { name: "Enter new app name" }) .getByRole("textbox", { name: "Enter new app name" })
.fill("new-app-name"); .fill("new-app-name");
...@@ -19,11 +19,11 @@ test("rename app (including folder)", async ({ po }) => { ...@@ -19,11 +19,11 @@ test("rename app (including folder)", async ({ po }) => {
.click(); .click();
await expect(async () => { await expect(async () => {
expect(await po.getCurrentAppName()).toBe("new-app-name"); expect(await po.appManagement.getCurrentAppName()).toBe("new-app-name");
}).toPass(); }).toPass();
expect(fs.existsSync(appPath)).toBe(false); expect(fs.existsSync(appPath)).toBe(false);
const newAppPath = po.getAppPath({ appName: "new-app-name" }); const newAppPath = po.appManagement.getAppPath({ appName: "new-app-name" });
expect(fs.existsSync(newAppPath)).toBe(true); expect(fs.existsSync(newAppPath)).toBe(true);
await expect(po.page.getByText(newAppPath)).toBeVisible(); await expect(po.page.getByText(newAppPath)).toBeVisible();
...@@ -33,10 +33,10 @@ test("rename app (without folder)", async ({ po }) => { ...@@ -33,10 +33,10 @@ test("rename app (without folder)", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.clickAppDetailsRenameAppButton(); await po.appManagement.clickAppDetailsRenameAppButton();
await po.page await po.page
.getByRole("textbox", { name: "Enter new app name" }) .getByRole("textbox", { name: "Enter new app name" })
.fill("new-app-name"); .fill("new-app-name");
...@@ -46,7 +46,7 @@ test("rename app (without folder)", async ({ po }) => { ...@@ -46,7 +46,7 @@ test("rename app (without folder)", async ({ po }) => {
.click(); .click();
await expect(async () => { await expect(async () => {
expect(await po.getCurrentAppName()).toBe("new-app-name"); expect(await po.appManagement.getCurrentAppName()).toBe("new-app-name");
}).toPass(); }).toPass();
expect(fs.existsSync(appPath)).toBe(true); expect(fs.existsSync(appPath)).toBe(true);
......
...@@ -6,10 +6,10 @@ testSkipIfWindows("restart app", async ({ po }) => { ...@@ -6,10 +6,10 @@ testSkipIfWindows("restart app", async ({ po }) => {
await po.sendPrompt("hi"); await po.sendPrompt("hi");
await po.clickRestart(); await po.clickRestart();
await expect(po.locateLoadingAppPreview()).toBeVisible(); await expect(po.previewPanel.locateLoadingAppPreview()).toBeVisible();
await expect(po.locateLoadingAppPreview()).not.toBeVisible({ await expect(po.previewPanel.locateLoadingAppPreview()).not.toBeVisible({
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
}); });
...@@ -5,9 +5,9 @@ test("retry - should work", async ({ po }) => { ...@@ -5,9 +5,9 @@ test("retry - should work", async ({ po }) => {
await po.sendPrompt("[increment]"); await po.sendPrompt("[increment]");
await po.snapshotMessages(); await po.snapshotMessages();
await po.dismissAllToasts(); await po.toastNotifications.dismissAllToasts();
await po.clickRetry(); await po.chatActions.clickRetry();
await po.expectNoToast(); await po.toastNotifications.expectNoToast();
// The counter should be incremented in the snapshotted messages. // The counter should be incremented in the snapshotted messages.
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -7,14 +7,14 @@ testSkipIfWindows("security review", async ({ po }) => { ...@@ -7,14 +7,14 @@ testSkipIfWindows("security review", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
await po.selectPreviewMode("security"); await po.previewPanel.selectPreviewMode("security");
await po.clickRunSecurityReview(); await po.securityReview.clickRunSecurityReview();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
await po.snapshotSecurityFindingsTable(); await po.securityReview.snapshotSecurityFindingsTable();
await po.page.getByRole("button", { name: "Fix Issue" }).first().click(); await po.page.getByRole("button", { name: "Fix Issue" }).first().click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -24,7 +24,7 @@ testSkipIfWindows( ...@@ -24,7 +24,7 @@ testSkipIfWindows(
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
await po.selectPreviewMode("security"); await po.previewPanel.selectPreviewMode("security");
await po.page.getByRole("button", { name: "Edit Security Rules" }).click(); await po.page.getByRole("button", { name: "Edit Security Rules" }).click();
await po.page await po.page
.getByRole("textbox", { name: "# SECURITY_RULES.md\\n\\" }) .getByRole("textbox", { name: "# SECURITY_RULES.md\\n\\" })
...@@ -34,7 +34,7 @@ testSkipIfWindows( ...@@ -34,7 +34,7 @@ testSkipIfWindows(
.fill("testing\nrules123"); .fill("testing\nrules123");
await po.page.getByRole("button", { name: "Save" }).click(); await po.page.getByRole("button", { name: "Save" }).click();
await po.clickRunSecurityReview(); await po.securityReview.clickRunSecurityReview();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
}, },
); );
...@@ -43,13 +43,13 @@ test("security review - multi-select and fix issues", async ({ po }) => { ...@@ -43,13 +43,13 @@ test("security review - multi-select and fix issues", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
await po.selectPreviewMode("security"); await po.previewPanel.selectPreviewMode("security");
await po.page await po.page
.getByRole("button", { name: "Run Security Review" }) .getByRole("button", { name: "Run Security Review" })
.first() .first()
.click(); .click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
// Select the first two issues using individual checkboxes // Select the first two issues using individual checkboxes
const checkboxes = po.page.getByRole("checkbox"); const checkboxes = po.page.getByRole("checkbox");
...@@ -65,6 +65,6 @@ test("security review - multi-select and fix issues", async ({ po }) => { ...@@ -65,6 +65,6 @@ test("security review - multi-select and fix issues", async ({ po }) => {
// Click the fix selected button // Click the fix selected button
await fixSelectedButton.click(); await fixSelectedButton.click();
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -4,21 +4,23 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -4,21 +4,23 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("select component", async ({ po }) => { testSkipIfWindows("select component", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
.click(); .click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
await po.sendPrompt("[dump] make it smaller"); await po.sendPrompt("[dump] make it smaller");
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible(); await expect(
po.previewPanel.getSelectedComponentsDisplay(),
).not.toBeVisible();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
...@@ -30,27 +32,29 @@ testSkipIfWindows("select component", async ({ po }) => { ...@@ -30,27 +32,29 @@ testSkipIfWindows("select component", async ({ po }) => {
testSkipIfWindows("select multiple components", async ({ po }) => { testSkipIfWindows("select multiple components", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
.click(); .click();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Made with Dyad") .getByText("Made with Dyad")
.click(); .click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
await po.sendPrompt("[dump] make both smaller"); await po.sendPrompt("[dump] make both smaller");
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible(); await expect(
po.previewPanel.getSelectedComponentsDisplay(),
).not.toBeVisible();
await po.snapshotServerDump("last-message"); await po.snapshotServerDump("last-message");
}); });
...@@ -58,23 +62,25 @@ testSkipIfWindows("select multiple components", async ({ po }) => { ...@@ -58,23 +62,25 @@ testSkipIfWindows("select multiple components", async ({ po }) => {
testSkipIfWindows("deselect component", async ({ po }) => { testSkipIfWindows("deselect component", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
.click(); .click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
// Deselect the component and make sure the state has reverted // Deselect the component and make sure the state has reverted
await po.clickDeselectComponent(); await po.previewPanel.clickDeselectComponent();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible(); await expect(
po.previewPanel.getSelectedComponentsDisplay(),
).not.toBeVisible();
// Send one more prompt to make sure it's a normal message. // Send one more prompt to make sure it's a normal message.
await po.sendPrompt("[dump] tc=basic"); await po.sendPrompt("[dump] tc=basic");
...@@ -86,49 +92,51 @@ testSkipIfWindows( ...@@ -86,49 +92,51 @@ testSkipIfWindows(
async ({ po }) => { async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
.click(); .click();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByText("Made with Dyad") .getByText("Made with Dyad")
.click(); .click();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
await po.clickDeselectComponent({ index: 0 }); await po.previewPanel.clickDeselectComponent({ index: 0 });
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
await expect(po.getSelectedComponentsDisplay()).toBeVisible(); await expect(po.previewPanel.getSelectedComponentsDisplay()).toBeVisible();
}, },
); );
testSkipIfWindows("upgrade app to select component", async ({ po }) => { testSkipIfWindows("upgrade app to select component", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.importApp("select-component"); await po.importApp("select-component");
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.clickAppUpgradeButton({ upgradeId: "component-tagger" }); await po.appManagement.clickAppUpgradeButton({
await po.expectAppUpgradeButtonIsNotVisible({ upgradeId: "component-tagger",
});
await po.appManagement.expectAppUpgradeButtonIsNotVisible({
upgradeId: "component-tagger", upgradeId: "component-tagger",
}); });
await po.snapshotAppFiles({ name: "app-upgraded" }); await po.snapshotAppFiles({ name: "app-upgraded" });
await po.clickOpenInChatButton(); await po.appManagement.clickOpenInChatButton();
// There should be another version from the upgrade being committed. // There should be another version from the upgrade being committed.
await expect(po.page.getByText("Version 2")).toBeVisible(); await expect(po.page.getByText("Version 2")).toBeVisible();
await po.clickRestart(); await po.clickRestart();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Launch Your Next Project" }) .getByRole("heading", { name: "Launch Your Next Project" })
...@@ -141,23 +149,23 @@ testSkipIfWindows("upgrade app to select component", async ({ po }) => { ...@@ -141,23 +149,23 @@ testSkipIfWindows("upgrade app to select component", async ({ po }) => {
testSkipIfWindows("select component next.js", async ({ po }) => { testSkipIfWindows("select component next.js", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.goToHubAndSelectTemplate("Next.js Template"); await po.navigation.goToHubAndSelectTemplate("Next.js Template");
await po.selectChatMode("build"); await po.chatActions.selectChatMode("build");
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Blank page" }) .getByRole("heading", { name: "Blank page" })
.click(); .click();
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotSelectedComponentsDisplay(); await po.previewPanel.snapshotSelectedComponentsDisplay();
await po.sendPrompt("[dump] make it smaller"); await po.sendPrompt("[dump] make it smaller");
await po.snapshotPreview(); await po.previewPanel.snapshotPreview();
await po.snapshotServerDump("all-messages"); await po.snapshotServerDump("all-messages");
}); });
...@@ -118,14 +118,14 @@ testSetup.describe("Setup Flow", () => { ...@@ -118,14 +118,14 @@ testSetup.describe("Setup Flow", () => {
await expect(po.page.getByRole("link", { name: "Settings" })).toBeVisible(); await expect(po.page.getByRole("link", { name: "Settings" })).toBeVisible();
// Now configure the test provider // Now configure the test provider
await po.setUpTestProvider(); await po.settings.setUpTestProvider();
// Set up API key so provider is considered configured // Set up API key so provider is considered configured
await po.page.getByRole("heading", { name: "test-provider" }).click(); await po.page.getByRole("heading", { name: "test-provider" }).click();
await po.setUpTestProviderApiKey(); await po.settings.setUpTestProviderApiKey();
await po.setUpTestModel(); await po.settings.setUpTestModel();
// Go back to apps tab // Go back to apps tab
await po.goToAppsTab(); await po.navigation.goToAppsTab();
// After configuring a provider, the setup banner should be gone // After configuring a provider, the setup banner should be gone
await expect( await expect(
......
...@@ -25,8 +25,8 @@ testSkipIfWindows( ...@@ -25,8 +25,8 @@ testSkipIfWindows(
// First, create an imported app. // First, create an imported app.
await po.importApp("minimal-with-ai-rules"); await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.selectChatMode("build"); await po.chatActions.selectChatMode("build");
const proModesDialog = await po.openProModesDialog({ const proModesDialog = await po.openProModesDialog({
location: "home-chat-input-container", location: "home-chat-input-container",
}); });
......
...@@ -6,15 +6,15 @@ test("switching smart context mode saves the right setting", async ({ po }) => { ...@@ -6,15 +6,15 @@ test("switching smart context mode saves the right setting", async ({ po }) => {
location: "home-chat-input-container", location: "home-chat-input-container",
}); });
const beforeSettings1 = po.recordSettings(); const beforeSettings1 = po.settings.recordSettings();
await proModesDialog.setSmartContextMode("balanced"); await proModesDialog.setSmartContextMode("balanced");
po.snapshotSettingsDelta(beforeSettings1); po.settings.snapshotSettingsDelta(beforeSettings1);
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await proModesDialog.setSmartContextMode("off"); await proModesDialog.setSmartContextMode("off");
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
const beforeSettings3 = po.recordSettings(); const beforeSettings3 = po.settings.recordSettings();
await proModesDialog.setSmartContextMode("deep"); await proModesDialog.setSmartContextMode("deep");
po.snapshotSettingsDelta(beforeSettings3); po.settings.snapshotSettingsDelta(beforeSettings3);
}); });
...@@ -8,19 +8,19 @@ testSkipIfWindows("supabase branch selection works", async ({ po }) => { ...@@ -8,19 +8,19 @@ testSkipIfWindows("supabase branch selection works", async ({ po }) => {
// Connect to Supabase // Connect to Supabase
await po.page.getByText("Set up supabase").click(); await po.page.getByText("Set up supabase").click();
await po.clickConnectSupabaseButton(); await po.appManagement.clickConnectSupabaseButton();
await po.clickBackButton(); await po.navigation.clickBackButton();
await po.toggleTokenBar(); await po.toggleTokenBar();
// The default branch has a small context. // The default branch has a small context.
await expect(po.page.getByTestId("token-bar")).toContainText("6% of 128K"); await expect(po.page.getByTestId("token-bar")).toContainText("6% of 128K");
// We hide the token bar so we re-open it later to refresh the token count. // We hide the token bar so we re-open it later to refresh the token count.
await po.toggleTokenBar(); await po.toggleTokenBar();
await po.getTitleBarAppNameButton().click(); await po.appManagement.getTitleBarAppNameButton().click();
await po.page.getByTestId("supabase-branch-select").click(); await po.page.getByTestId("supabase-branch-select").click();
await po.page.getByRole("option", { name: "Test Branch" }).click(); await po.page.getByRole("option", { name: "Test Branch" }).click();
await po.clickBackButton(); await po.navigation.clickBackButton();
// The test branch has a large context (200k tokens) so it'll hit the 100% limit. // The test branch has a large context (200k tokens) so it'll hit the 100% limit.
// This is to make sure we're connecting to the right supabase project for the branch. // This is to make sure we're connecting to the right supabase project for the branch.
await po.toggleTokenBar(); await po.toggleTokenBar();
......
...@@ -7,8 +7,8 @@ testSkipIfWindows("supabase client is generated", async ({ po }) => { ...@@ -7,8 +7,8 @@ testSkipIfWindows("supabase client is generated", async ({ po }) => {
// Connect to Supabase // Connect to Supabase
await po.page.getByText("Set up supabase").click(); await po.page.getByText("Set up supabase").click();
await po.clickConnectSupabaseButton(); await po.appManagement.clickConnectSupabaseButton();
await po.clickBackButton(); await po.navigation.clickBackButton();
await po.sendPrompt("tc=generate-supabase-client"); await po.sendPrompt("tc=generate-supabase-client");
await po.snapshotAppFiles({ name: "supabase-client-generated" }); await po.snapshotAppFiles({ name: "supabase-client-generated" });
......
...@@ -10,21 +10,21 @@ testSkipIfWindows("supabase migrations", async ({ po }) => { ...@@ -10,21 +10,21 @@ testSkipIfWindows("supabase migrations", async ({ po }) => {
// Connect to Supabase // Connect to Supabase
await po.page.getByText("Set up supabase").click(); await po.page.getByText("Set up supabase").click();
await po.clickConnectSupabaseButton(); await po.appManagement.clickConnectSupabaseButton();
await po.clickBackButton(); await po.navigation.clickBackButton();
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const migrationsDir = path.join(appPath, "supabase", "migrations"); const migrationsDir = path.join(appPath, "supabase", "migrations");
// --- SCENARIO 1: OFF BY DEFAULT --- // --- SCENARIO 1: OFF BY DEFAULT ---
await po.sendPrompt("tc=execute-sql-1"); await po.sendPrompt("tc=execute-sql-1");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
expect(fs.existsSync(migrationsDir)).toBe(false); expect(fs.existsSync(migrationsDir)).toBe(false);
// --- SCENARIO 2: TOGGLE ON --- // --- SCENARIO 2: TOGGLE ON ---
// Go to settings to find the Supabase integration // Go to settings to find the Supabase integration
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const migrationsSwitch = po.page.getByRole("switch", { const migrationsSwitch = po.page.getByRole("switch", {
name: "Write SQL migration files", name: "Write SQL migration files",
}); });
...@@ -32,11 +32,11 @@ testSkipIfWindows("supabase migrations", async ({ po }) => { ...@@ -32,11 +32,11 @@ testSkipIfWindows("supabase migrations", async ({ po }) => {
await migrationsSwitch.click(); await migrationsSwitch.click();
// Wait for the setting to be persisted // Wait for the setting to be persisted
await expect(migrationsSwitch).toBeChecked(); await expect(migrationsSwitch).toBeChecked();
await po.goToChatTab(); await po.navigation.goToChatTab();
// Send a prompt that triggers a migration // Send a prompt that triggers a migration
await po.sendPrompt("tc=execute-sql-1"); await po.sendPrompt("tc=execute-sql-1");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
let files: string[] = []; let files: string[] = [];
await expect(async () => { await expect(async () => {
...@@ -52,7 +52,7 @@ testSkipIfWindows("supabase migrations", async ({ po }) => { ...@@ -52,7 +52,7 @@ testSkipIfWindows("supabase migrations", async ({ po }) => {
// Send a prompt that triggers a migration // Send a prompt that triggers a migration
await po.sendPrompt("tc=execute-sql-no-description"); await po.sendPrompt("tc=execute-sql-no-description");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await expect(async () => { await expect(async () => {
// Check that one migration file was created // Check that one migration file was created
...@@ -76,21 +76,21 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => { ...@@ -76,21 +76,21 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => {
// Connect to Supabase // Connect to Supabase
await po.page.getByText("Set up supabase").click(); await po.page.getByText("Set up supabase").click();
await po.clickConnectSupabaseButton(); await po.appManagement.clickConnectSupabaseButton();
await po.clickBackButton(); await po.navigation.clickBackButton();
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
const migrationsDir = path.join(appPath, "supabase", "migrations"); const migrationsDir = path.join(appPath, "supabase", "migrations");
// --- SCENARIO 1: OFF BY DEFAULT --- // --- SCENARIO 1: OFF BY DEFAULT ---
await po.sendPrompt("tc=execute-sql-1"); await po.sendPrompt("tc=execute-sql-1");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
expect(fs.existsSync(migrationsDir)).toBe(false); expect(fs.existsSync(migrationsDir)).toBe(false);
// --- SCENARIO 2: TOGGLE ON --- // --- SCENARIO 2: TOGGLE ON ---
// Go to settings to find the Supabase integration // Go to settings to find the Supabase integration
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const migrationsSwitch = po.page.getByRole("switch", { const migrationsSwitch = po.page.getByRole("switch", {
name: "Write SQL migration files", name: "Write SQL migration files",
}); });
...@@ -98,11 +98,11 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => { ...@@ -98,11 +98,11 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => {
await migrationsSwitch.click(); await migrationsSwitch.click();
// Wait for the setting to be persisted // Wait for the setting to be persisted
await expect(migrationsSwitch).toBeChecked(); await expect(migrationsSwitch).toBeChecked();
await po.goToChatTab(); await po.navigation.goToChatTab();
// Send a prompt that triggers a migration // Send a prompt that triggers a migration
await po.sendPrompt("tc=execute-sql-1"); await po.sendPrompt("tc=execute-sql-1");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
let files: string[] = []; let files: string[] = [];
await expect(async () => { await expect(async () => {
...@@ -125,7 +125,7 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => { ...@@ -125,7 +125,7 @@ testSkipIfWindows("supabase migrations with native git", async ({ po }) => {
// Send a prompt that triggers a migration // Send a prompt that triggers a migration
await po.sendPrompt("tc=execute-sql-no-description"); await po.sendPrompt("tc=execute-sql-no-description");
await po.waitForChatCompletion(); await po.chatActions.waitForChatCompletion();
await expect(async () => { await expect(async () => {
// Check that one migration file was created // Check that one migration file was created
......
...@@ -8,18 +8,18 @@ testSkipIfWindows("supabase - stale ui", async ({ po }) => { ...@@ -8,18 +8,18 @@ testSkipIfWindows("supabase - stale ui", async ({ po }) => {
await po.page.getByText("Set up supabase").click(); await po.page.getByText("Set up supabase").click();
// On app details page: // On app details page:
await po.clickConnectSupabaseButton(); await po.appManagement.clickConnectSupabaseButton();
// TODO: for some reason on Windows this navigates to the main (apps) page, // TODO: for some reason on Windows this navigates to the main (apps) page,
// rather than the original chat page, so this test is skipped on Windows. // rather than the original chat page, so this test is skipped on Windows.
// However, the underlying issue is platform-agnostic, so it seems OK to test // However, the underlying issue is platform-agnostic, so it seems OK to test
// only on Mac. // only on Mac.
await po.clickBackButton(); await po.navigation.clickBackButton();
// On chat page: // On chat page:
await po.snapshotMessages(); await po.snapshotMessages();
// Create a second app; do NOT integrate it with Supabase, and make sure UI is correct. // Create a second app; do NOT integrate it with Supabase, and make sure UI is correct.
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("tc=add-supabase"); await po.sendPrompt("tc=add-supabase");
await po.snapshotMessages(); await po.snapshotMessages();
}); });
...@@ -4,10 +4,10 @@ import { expect } from "@playwright/test"; ...@@ -4,10 +4,10 @@ import { expect } from "@playwright/test";
test("switch apps", async ({ po }) => { test("switch apps", async ({ po }) => {
await po.setUp(); await po.setUp();
await po.sendPrompt("hi"); await po.sendPrompt("hi");
const firstAppName = await po.getCurrentAppName(); const firstAppName = await po.appManagement.getCurrentAppName();
await po.goToAppsTab(); await po.navigation.goToAppsTab();
await po.sendPrompt("second-app"); await po.sendPrompt("second-app");
const secondAppName = await po.getCurrentAppName(); const secondAppName = await po.appManagement.getCurrentAppName();
expect(secondAppName).not.toBe(firstAppName); expect(secondAppName).not.toBe(firstAppName);
}); });
...@@ -8,20 +8,20 @@ const runSwitchVersionTest = async ( ...@@ -8,20 +8,20 @@ const runSwitchVersionTest = async (
await po.setUp({ autoApprove: true, disableNativeGit }); await po.setUp({ autoApprove: true, disableNativeGit });
await po.sendPrompt("tc=write-index"); await po.sendPrompt("tc=write-index");
await po.snapshotPreview({ name: `v2` }); await po.previewPanel.snapshotPreview({ name: `v2` });
expect( expect(
await po.page.getByRole("button", { name: "Version" }).textContent(), await po.page.getByRole("button", { name: "Version" }).textContent(),
).toBe("Version 2"); ).toBe("Version 2");
await po.page.getByRole("button", { name: "Version" }).click(); await po.page.getByRole("button", { name: "Version" }).click();
await po.page.getByText("Init Dyad app Restore").click(); await po.page.getByText("Init Dyad app Restore").click();
await po.snapshotPreview({ name: `v1` }); await po.previewPanel.snapshotPreview({ name: `v1` });
await po.page await po.page
.getByRole("button", { name: "Restore to this version" }) .getByRole("button", { name: "Restore to this version" })
.click(); .click();
// Should be same as the previous snapshot, but just to be sure. // Should be same as the previous snapshot, but just to be sure.
await po.snapshotPreview({ name: `v1` }); await po.previewPanel.snapshotPreview({ name: `v1` });
await expect(po.page.getByText("Version 3")).toBeVisible({ await expect(po.page.getByText("Version 3")).toBeVisible({
timeout: Timeout.MEDIUM, timeout: Timeout.MEDIUM,
......
import { test } from "./helpers/test_helper"; import { test } from "./helpers/test_helper";
test("telemetry - accept", async ({ po }) => { test("telemetry - accept", async ({ po }) => {
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.clickTelemetryAccept(); await po.settings.clickTelemetryAccept();
// Expect telemetry settings to be set // Expect telemetry settings to be set
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
}); });
test("telemetry - reject", async ({ po }) => { test("telemetry - reject", async ({ po }) => {
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.clickTelemetryReject(); await po.settings.clickTelemetryReject();
// Expect telemetry settings to still NOT be set // Expect telemetry settings to still NOT be set
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
}); });
test("telemetry - later", async ({ po }) => { test("telemetry - later", async ({ po }) => {
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.clickTelemetryLater(); await po.settings.clickTelemetryLater();
// Expect telemetry settings to still NOT be set // Expect telemetry settings to still NOT be set
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
}); });
import { test } from "./helpers/test_helper"; import { test } from "./helpers/test_helper";
test("template - community", async ({ po }) => { test("template - community", async ({ po }) => {
await po.goToHubTab(); await po.navigation.goToHubTab();
// This is a community template, so we should see the consent dialog // This is a community template, so we should see the consent dialog
const beforeSettings1 = po.recordSettings(); const beforeSettings1 = po.settings.recordSettings();
await po.selectTemplate("Angular"); await po.navigation.selectTemplate("Angular");
await po.page.getByRole("button", { name: "Cancel" }).click(); await po.page.getByRole("button", { name: "Cancel" }).click();
po.snapshotSettingsDelta(beforeSettings1); po.settings.snapshotSettingsDelta(beforeSettings1);
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await po.selectTemplate("Angular"); await po.navigation.selectTemplate("Angular");
await po.page.getByRole("button", { name: "Accept" }).click(); await po.page.getByRole("button", { name: "Accept" }).click();
await po.page await po.page
.locator("section") .locator("section")
...@@ -17,5 +17,5 @@ test("template - community", async ({ po }) => { ...@@ -17,5 +17,5 @@ test("template - community", async ({ po }) => {
.locator("div") .locator("div")
.first() .first()
.click(); .click();
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
}); });
...@@ -3,10 +3,10 @@ import { expect } from "@playwright/test"; ...@@ -3,10 +3,10 @@ import { expect } from "@playwright/test";
test("create next.js app", async ({ po }) => { test("create next.js app", async ({ po }) => {
await po.setUp(); await po.setUp();
const beforeSettings = po.recordSettings(); const beforeSettings = po.settings.recordSettings();
await po.goToHubAndSelectTemplate("Next.js Template"); await po.navigation.goToHubAndSelectTemplate("Next.js Template");
await po.selectChatMode("build"); await po.chatActions.selectChatMode("build");
po.snapshotSettingsDelta(beforeSettings); po.settings.snapshotSettingsDelta(beforeSettings);
// Create an app // Create an app
await po.sendPrompt("tc=edit-made-with-dyad"); await po.sendPrompt("tc=edit-made-with-dyad");
...@@ -15,6 +15,8 @@ test("create next.js app", async ({ po }) => { ...@@ -15,6 +15,8 @@ test("create next.js app", async ({ po }) => {
await po.clickRestart(); await po.clickRestart();
// This can be pretty slow because it's waiting for the app to build. // This can be pretty slow because it's waiting for the app to build.
await expect(po.getPreviewIframeElement()).toBeVisible({ timeout: 100_000 }); await expect(po.previewPanel.getPreviewIframeElement()).toBeVisible({
await po.snapshotPreview(); timeout: 100_000,
});
await po.previewPanel.snapshotPreview();
}); });
...@@ -7,11 +7,11 @@ test("theme selection - dyad-wide default theme is persisted", async ({ ...@@ -7,11 +7,11 @@ test("theme selection - dyad-wide default theme is persisted", async ({
await po.setUp(); await po.setUp();
// Verify initial settings state // Verify initial settings state
const initialSettings = po.recordSettings(); const initialSettings = po.settings.recordSettings();
expect(initialSettings.selectedThemeId).toBe("default"); expect(initialSettings.selectedThemeId).toBe("default");
// Open menu and select "No Theme" // Open menu and select "No Theme"
await po await po.chatActions
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -21,10 +21,10 @@ test("theme selection - dyad-wide default theme is persisted", async ({ ...@@ -21,10 +21,10 @@ test("theme selection - dyad-wide default theme is persisted", async ({
await expect(po.page.getByTestId("theme-option-none")).not.toBeVisible(); await expect(po.page.getByTestId("theme-option-none")).not.toBeVisible();
// Verify settings file was updated // Verify settings file was updated
expect(po.recordSettings().selectedThemeId).toBe(""); expect(po.settings.recordSettings().selectedThemeId).toBe("");
// Re-open and verify UI shows "No Theme" selected, then select "Default Theme" back // Re-open and verify UI shows "No Theme" selected, then select "Default Theme" back
await po await po.chatActions
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -36,7 +36,7 @@ test("theme selection - dyad-wide default theme is persisted", async ({ ...@@ -36,7 +36,7 @@ test("theme selection - dyad-wide default theme is persisted", async ({
await expect(po.page.getByTestId("theme-option-default")).not.toBeVisible(); await expect(po.page.getByTestId("theme-option-default")).not.toBeVisible();
// Verify settings file was updated back to default // Verify settings file was updated back to default
expect(po.recordSettings().selectedThemeId).toBe("default"); expect(po.settings.recordSettings().selectedThemeId).toBe("default");
}); });
test("theme selection - app-specific theme is persisted", async ({ po }) => { test("theme selection - app-specific theme is persisted", async ({ po }) => {
...@@ -44,7 +44,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => { ...@@ -44,7 +44,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => {
await po.importApp("minimal"); await po.importApp("minimal");
// Open menu and select "Default Theme" for this app // Open menu and select "Default Theme" for this app
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -54,7 +54,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => { ...@@ -54,7 +54,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => {
await expect(po.page.getByTestId("theme-option-default")).not.toBeVisible(); await expect(po.page.getByTestId("theme-option-default")).not.toBeVisible();
// Re-open, verify selection, then switch to "No Theme" // Re-open, verify selection, then switch to "No Theme"
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -66,7 +66,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => { ...@@ -66,7 +66,7 @@ test("theme selection - app-specific theme is persisted", async ({ po }) => {
await expect(po.page.getByTestId("theme-option-none")).not.toBeVisible(); await expect(po.page.getByTestId("theme-option-none")).not.toBeVisible();
// Re-open and verify "No Theme" is selected // Re-open and verify "No Theme" is selected
await po await po.chatActions
.getChatInputContainer() .getChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
......
...@@ -5,7 +5,7 @@ test("themes management - CRUD operations", async ({ po }) => { ...@@ -5,7 +5,7 @@ test("themes management - CRUD operations", async ({ po }) => {
await po.setUp(); await po.setUp();
// Navigate to Themes page via Library sidebar // Navigate to Themes page via Library sidebar
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Themes" }).click(); await po.page.getByRole("link", { name: "Themes" }).click();
await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible(); await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible();
...@@ -102,7 +102,7 @@ test("themes management - create theme from chat input", async ({ po }) => { ...@@ -102,7 +102,7 @@ test("themes management - create theme from chat input", async ({ po }) => {
await po.setUp(); await po.setUp();
// Open the auxiliary actions menu // Open the auxiliary actions menu
await po await po.chatActions
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -138,7 +138,7 @@ test("themes management - create theme from chat input", async ({ po }) => { ...@@ -138,7 +138,7 @@ test("themes management - create theme from chat input", async ({ po }) => {
// Verify the newly created theme is auto-selected // Verify the newly created theme is auto-selected
// Re-open the menu to verify // Re-open the menu to verify
await po await po.chatActions
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu") .getByTestId("auxiliary-actions-menu")
.click(); .click();
...@@ -154,7 +154,7 @@ test("themes management - AI generator image upload limit", async ({ po }) => { ...@@ -154,7 +154,7 @@ test("themes management - AI generator image upload limit", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
// Navigate to Themes page via Library sidebar // Navigate to Themes page via Library sidebar
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Themes" }).click(); await po.page.getByRole("link", { name: "Themes" }).click();
await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible(); await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible();
...@@ -204,7 +204,7 @@ test("themes management - AI generator flow", async ({ po }) => { ...@@ -204,7 +204,7 @@ test("themes management - AI generator flow", async ({ po }) => {
await po.setUp(); await po.setUp();
// Navigate to Themes page via Library sidebar // Navigate to Themes page via Library sidebar
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Themes" }).click(); await po.page.getByRole("link", { name: "Themes" }).click();
await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible(); await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible();
...@@ -276,7 +276,7 @@ test("themes management - AI generator from website URL", async ({ po }) => { ...@@ -276,7 +276,7 @@ test("themes management - AI generator from website URL", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
// Navigate to Themes page via Library sidebar // Navigate to Themes page via Library sidebar
await po.goToLibraryTab(); await po.navigation.goToLibraryTab();
await po.page.getByRole("link", { name: "Themes" }).click(); await po.page.getByRole("link", { name: "Themes" }).click();
await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible(); await expect(po.page.getByRole("heading", { name: "Themes" })).toBeVisible();
......
...@@ -2,35 +2,38 @@ import { testSkipIfWindows } from "./helpers/test_helper"; ...@@ -2,35 +2,38 @@ import { testSkipIfWindows } from "./helpers/test_helper";
testSkipIfWindows("thinking budget", async ({ po }) => { testSkipIfWindows("thinking budget", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" }); await po.modelPicker.selectModel({
provider: "Google",
model: "Gemini 2.5 Pro",
});
await po.sendPrompt("tc=1"); await po.sendPrompt("tc=1");
// Low // Low
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings1 = po.recordSettings(); const beforeSettings1 = po.settings.recordSettings();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click(); await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "Low" }).click(); await po.page.getByRole("option", { name: "Low" }).click();
po.snapshotSettingsDelta(beforeSettings1); po.settings.snapshotSettingsDelta(beforeSettings1);
await po.page.getByText("Go Back").click(); await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi"); await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
// Medium // Medium
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click(); await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "Medium (default)" }).click(); await po.page.getByRole("option", { name: "Medium (default)" }).click();
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
await po.page.getByText("Go Back").click(); await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi"); await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
// High // High
await po.goToSettingsTab(); await po.navigation.goToSettingsTab();
const beforeSettings3 = po.recordSettings(); const beforeSettings3 = po.settings.recordSettings();
await po.page.getByRole("combobox", { name: "Thinking Budget" }).click(); await po.page.getByRole("combobox", { name: "Thinking Budget" }).click();
await po.page.getByRole("option", { name: "High" }).click(); await po.page.getByRole("option", { name: "High" }).click();
po.snapshotSettingsDelta(beforeSettings3); po.settings.snapshotSettingsDelta(beforeSettings3);
await po.page.getByText("Go Back").click(); await po.page.getByText("Go Back").click();
await po.sendPrompt("[dump] hi"); await po.sendPrompt("[dump] hi");
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
......
...@@ -6,7 +6,7 @@ test.describe("Toggle Screen Size Tests", () => { ...@@ -6,7 +6,7 @@ test.describe("Toggle Screen Size Tests", () => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("tc=write-index"); await po.sendPrompt("tc=write-index");
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
const frame = await iframe.contentFrame(); const frame = await iframe.contentFrame();
await expect(frame.getByText("Testing:write-index!")).toBeVisible({ await expect(frame.getByText("Testing:write-index!")).toBeVisible({
...@@ -96,9 +96,9 @@ test.describe("Toggle Screen Size Tests", () => { ...@@ -96,9 +96,9 @@ test.describe("Toggle Screen Size Tests", () => {
await expect(previewIframe).toHaveAttribute("style", /width:\s*375px/); await expect(previewIframe).toHaveAttribute("style", /width:\s*375px/);
// Trigger rebuild // Trigger rebuild
await po.clickRebuild(); await po.previewPanel.clickRebuild();
await expect(po.locateLoadingAppPreview()).toBeVisible(); await expect(po.previewPanel.locateLoadingAppPreview()).toBeVisible();
await expect(po.locateLoadingAppPreview()).not.toBeVisible({ await expect(po.previewPanel.locateLoadingAppPreview()).not.toBeVisible({
timeout: Timeout.EXTRA_LONG, timeout: Timeout.EXTRA_LONG,
}); });
......
...@@ -6,15 +6,15 @@ test("switching turbo edits saves the right setting", async ({ po }) => { ...@@ -6,15 +6,15 @@ test("switching turbo edits saves the right setting", async ({ po }) => {
location: "home-chat-input-container", location: "home-chat-input-container",
}); });
const beforeSettings1 = po.recordSettings(); const beforeSettings1 = po.settings.recordSettings();
await proModesDialog.setTurboEditsMode("classic"); await proModesDialog.setTurboEditsMode("classic");
po.snapshotSettingsDelta(beforeSettings1); po.settings.snapshotSettingsDelta(beforeSettings1);
const beforeSettings2 = po.recordSettings(); const beforeSettings2 = po.settings.recordSettings();
await proModesDialog.setTurboEditsMode("search-replace"); await proModesDialog.setTurboEditsMode("search-replace");
po.snapshotSettingsDelta(beforeSettings2); po.settings.snapshotSettingsDelta(beforeSettings2);
const beforeSettings3 = po.recordSettings(); const beforeSettings3 = po.settings.recordSettings();
await proModesDialog.setTurboEditsMode("off"); await proModesDialog.setTurboEditsMode("off");
po.snapshotSettingsDelta(beforeSettings3); po.settings.snapshotSettingsDelta(beforeSettings3);
}); });
...@@ -16,7 +16,7 @@ const runUncommittedFilesBannerTest = async ( ...@@ -16,7 +16,7 @@ const runUncommittedFilesBannerTest = async (
await po.setUp({ disableNativeGit: !nativeGit }); await po.setUp({ disableNativeGit: !nativeGit });
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
const appPath = await po.getCurrentAppPath(); const appPath = await po.appManagement.getCurrentAppPath();
if (!appPath) { if (!appPath) {
throw new Error("No app path found"); throw new Error("No app path found");
} }
...@@ -76,7 +76,7 @@ const runUncommittedFilesBannerTest = async ( ...@@ -76,7 +76,7 @@ const runUncommittedFilesBannerTest = async (
await po.page.getByTestId("commit-button").click(); await po.page.getByTestId("commit-button").click();
// Wait for success toast // Wait for success toast
await po.waitForToast("success"); await po.toastNotifications.waitForToast("success");
// The dialog should close // The dialog should close
await expect(po.page.getByTestId("commit-dialog")).not.toBeVisible(); await expect(po.page.getByTestId("commit-dialog")).not.toBeVisible();
......
...@@ -6,7 +6,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => { ...@@ -6,7 +6,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => {
await po.sendPrompt("tc=write-index"); await po.sendPrompt("tc=write-index");
await po.sendPrompt("tc=write-index-2"); await po.sendPrompt("tc=write-index-2");
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
await expect( await expect(
iframe.contentFrame().getByText("Testing:write-index(2)!"), iframe.contentFrame().getByText("Testing:write-index(2)!"),
).toBeVisible({ ).toBeVisible({
...@@ -14,7 +14,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => { ...@@ -14,7 +14,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => {
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
await po.clickUndo(); await po.chatActions.clickUndo();
await expect( await expect(
iframe.contentFrame().getByText("Testing:write-index!"), iframe.contentFrame().getByText("Testing:write-index!"),
...@@ -23,7 +23,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => { ...@@ -23,7 +23,7 @@ const runUndoTest = async (po: PageObject, nativeGit: boolean) => {
timeout: Timeout.LONG, timeout: Timeout.LONG,
}); });
await po.clickUndo(); await po.chatActions.clickUndo();
await expect( await expect(
iframe.contentFrame().getByText("Welcome to Your Blank App"), iframe.contentFrame().getByText("Welcome to Your Blank App"),
...@@ -50,7 +50,7 @@ testSkipIfWindows("undo after assistant with no code", async ({ po }) => { ...@@ -50,7 +50,7 @@ testSkipIfWindows("undo after assistant with no code", async ({ po }) => {
// Second prompt - generates code // Second prompt - generates code
await po.sendPrompt("tc=write-index"); await po.sendPrompt("tc=write-index");
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
await expect( await expect(
iframe.contentFrame().getByText("Testing:write-index!"), iframe.contentFrame().getByText("Testing:write-index!"),
).toBeVisible({ ).toBeVisible({
...@@ -58,7 +58,7 @@ testSkipIfWindows("undo after assistant with no code", async ({ po }) => { ...@@ -58,7 +58,7 @@ testSkipIfWindows("undo after assistant with no code", async ({ po }) => {
}); });
// Undo should work even though first assistant had no commit // Undo should work even though first assistant had no commit
await po.clickUndo(); await po.chatActions.clickUndo();
await expect( await expect(
iframe.contentFrame().getByText("Welcome to Your Blank App"), iframe.contentFrame().getByText("Welcome to Your Blank App"),
......
...@@ -6,11 +6,11 @@ const path = require("path"); ...@@ -6,11 +6,11 @@ const path = require("path");
testSkipIfWindows("edit style of one selected component", async ({ po }) => { testSkipIfWindows("edit style of one selected component", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
// Select a component // Select a component
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
...@@ -60,7 +60,9 @@ testSkipIfWindows("edit style of one selected component", async ({ po }) => { ...@@ -60,7 +60,9 @@ testSkipIfWindows("edit style of one selected component", async ({ po }) => {
await po.page.getByRole("button", { name: "Save Changes" }).click(); await po.page.getByRole("button", { name: "Save Changes" }).click();
// Wait for the success toast // Wait for the success toast
await po.waitForToastWithText("Visual changes saved to source files"); await po.toastNotifications.waitForToastWithText(
"Visual changes saved to source files",
);
// Verify that the changes are applied to codebase // Verify that the changes are applied to codebase
await po.snapshotAppFiles({ await po.snapshotAppFiles({
...@@ -72,11 +74,11 @@ testSkipIfWindows("edit style of one selected component", async ({ po }) => { ...@@ -72,11 +74,11 @@ testSkipIfWindows("edit style of one selected component", async ({ po }) => {
testSkipIfWindows("edit text of the selected component", async ({ po }) => { testSkipIfWindows("edit text of the selected component", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
// Click on component that contains static text // Click on component that contains static text
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
...@@ -88,7 +90,7 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => { ...@@ -88,7 +90,7 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => {
}); });
// Get the iframe and access the content // Get the iframe and access the content
const iframe = po.getPreviewIframeElement(); const iframe = po.previewPanel.getPreviewIframeElement();
const frame = iframe.contentFrame(); const frame = iframe.contentFrame();
// Find the heading element in the iframe // Find the heading element in the iframe
...@@ -127,7 +129,9 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => { ...@@ -127,7 +129,9 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => {
await po.page.getByRole("button", { name: "Save Changes" }).click(); await po.page.getByRole("button", { name: "Save Changes" }).click();
// Wait for the success toast // Wait for the success toast
await po.waitForToastWithText("Visual changes saved to source files"); await po.toastNotifications.waitForToastWithText(
"Visual changes saved to source files",
);
// Verify that the changes are applied to the codebase // Verify that the changes are applied to the codebase
await po.snapshotAppFiles({ await po.snapshotAppFiles({
...@@ -139,11 +143,11 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => { ...@@ -139,11 +143,11 @@ testSkipIfWindows("edit text of the selected component", async ({ po }) => {
testSkipIfWindows("discard changes", async ({ po }) => { testSkipIfWindows("discard changes", async ({ po }) => {
await po.setUpDyadPro(); await po.setUpDyadPro();
await po.sendPrompt("tc=basic"); await po.sendPrompt("tc=basic");
await po.clickTogglePreviewPanel(); await po.previewPanel.clickTogglePreviewPanel();
await po.clickPreviewPickElement(); await po.previewPanel.clickPreviewPickElement();
// Select a component // Select a component
await po await po.previewPanel
.getPreviewIframeElement() .getPreviewIframeElement()
.contentFrame() .contentFrame()
.getByRole("heading", { name: "Welcome to Your Blank App" }) .getByRole("heading", { name: "Welcome to Your Blank App" })
...@@ -194,7 +198,7 @@ testSkipIfWindows("discard changes", async ({ po }) => { ...@@ -194,7 +198,7 @@ testSkipIfWindows("discard changes", async ({ po }) => {
}); });
// Take a snapshot of the app files before discarding // Take a snapshot of the app files before discarding
const appPathBefore = await po.getCurrentAppPath(); const appPathBefore = await po.appManagement.getCurrentAppPath();
const appFileBefore = fs.readFileSync( const appFileBefore = fs.readFileSync(
path.join(appPathBefore, "src", "pages", "Index.tsx"), path.join(appPathBefore, "src", "pages", "Index.tsx"),
"utf-8", "utf-8",
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论