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