From 647fd0169e9b07e32fc4585704f3cbad42277602 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Thu, 29 May 2025 00:03:51 -0700 Subject: [PATCH] make it easy to write multiple e2e tests (#280) --- e2e-tests/fixtures/basic.md | 1 + e2e-tests/helpers/test_helper.ts | 89 ++++++++++++++++++- e2e-tests/main.spec.ts | 51 +++-------- ...ic-message-to-custom-test-model-1.aria.yml | 4 + playwright.config.ts | 2 +- src/app/TitleBar.tsx | 2 + src/lib/schemas.ts | 5 ++ src/main.ts | 5 ++ src/pages/app-details.tsx | 5 +- src/pages/home.tsx | 4 +- src/paths/paths.ts | 3 + testing/fake-llm-server/index.ts | 49 ++++++++-- 12 files changed, 169 insertions(+), 51 deletions(-) create mode 100644 e2e-tests/fixtures/basic.md create mode 100644 e2e-tests/snapshots/main.spec.ts_basic-message-to-custom-test-model-1.aria.yml diff --git a/e2e-tests/fixtures/basic.md b/e2e-tests/fixtures/basic.md new file mode 100644 index 0000000..668dfa6 --- /dev/null +++ b/e2e-tests/fixtures/basic.md @@ -0,0 +1 @@ +This is a simple basic response diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index a3618b9..f913db5 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -1,9 +1,86 @@ -import { test as base } from "@playwright/test"; +import { test as base, Page, expect } from "@playwright/test"; import { findLatestBuild, parseElectronApp } from "electron-playwright-helpers"; import { ElectronApplication, _electron as electron } from "playwright"; const showDebugLogs = process.env.DEBUG_LOGS === "true"; +class PageObject { + constructor(private page: Page) {} + + async setUp() { + await this.goToSettingsTab(); + await this.setUpTestProvider(); + await this.setUpTestModel(); + await this.goToAppsTab(); + await this.selectTestModel(); + } + + async dumpMessages() { + await expect(this.page.getByTestId("messages-list")).toMatchAriaSnapshot(); + } + + async waitForChatCompletion() { + await expect( + this.page.getByRole("button", { name: "Retry" }), + ).toBeVisible(); + } + + async sendPrompt(prompt: string) { + await this.page + .getByRole("textbox", { name: "Ask Dyad to build..." }) + .click(); + await this.page + .getByRole("textbox", { name: "Ask Dyad to build..." }) + .fill(prompt); + await this.page.getByRole("button", { name: "Start new chat" }).click(); + await this.waitForChatCompletion(); + } + + async selectTestModel() { + await this.page.getByRole("button", { name: "Model: Auto" }).click(); + await this.page.getByText("test-provider").click(); + await this.page.getByText("test-model").click(); + } + + async setUpTestProvider() { + await this.page.getByText("Add custom providerConnect to").click(); + // Fill out provider dialog + await this.page + .getByRole("textbox", { name: "Provider ID" }) + .fill("testing"); + await this.page.getByRole("textbox", { name: "Display Name" }).click(); + await this.page + .getByRole("textbox", { name: "Display Name" }) + .fill("test-provider"); + await this.page.getByText("API Base URLThe base URL for").click(); + await this.page + .getByRole("textbox", { name: "API Base URL" }) + .fill("http://localhost:3500/v1"); + await this.page.getByRole("button", { name: "Add Provider" }).click(); + } + + async setUpTestModel() { + await this.page + .getByRole("heading", { name: "test-provider Needs Setup" }) + .click(); + await this.page.getByRole("button", { name: "Add Custom Model" }).click(); + await this.page + .getByRole("textbox", { name: "Model ID*" }) + .fill("test-model"); + await this.page.getByRole("textbox", { name: "Model ID*" }).press("Tab"); + await this.page.getByRole("textbox", { name: "Name*" }).fill("test-model"); + await this.page.getByRole("button", { name: "Add Model" }).click(); + } + + async goToSettingsTab() { + await this.page.getByRole("link", { name: "Settings" }).click(); + } + + async goToAppsTab() { + await this.page.getByRole("link", { name: "Apps" }).click(); + } +} + // From https://github.com/microsoft/playwright/issues/8208#issuecomment-1435475930 // // Note how we mark the fixture as { auto: true }. @@ -11,7 +88,17 @@ const showDebugLogs = process.env.DEBUG_LOGS === "true"; export const test = base.extend<{ attachScreenshotsToReport: void; electronApp: ElectronApplication; + po: PageObject; }>({ + po: [ + async ({ electronApp }, use) => { + const page = await electronApp.firstWindow(); + + const po = new PageObject(page); + await use(po); + }, + { auto: true }, + ], attachScreenshotsToReport: [ async ({ page }, use, testInfo) => { await use(); diff --git a/e2e-tests/main.spec.ts b/e2e-tests/main.spec.ts index 224abc2..148bb45 100644 --- a/e2e-tests/main.spec.ts +++ b/e2e-tests/main.spec.ts @@ -8,45 +8,14 @@ test("renders the first page", async ({ electronApp }) => { expect(text).toBe("Build your dream app"); }); -test("simple message to custom test model", async ({ electronApp }) => { - const page = await electronApp.firstWindow(); - await page.getByRole("link", { name: "Settings" }).click(); - await page.getByText("Add custom providerConnect to").click(); - - // Fill out provider dialog - await page.getByRole("textbox", { name: "Provider ID" }).fill("testing"); - await page.getByRole("textbox", { name: "Display Name" }).click(); - await page - .getByRole("textbox", { name: "Display Name" }) - .fill("test-provider"); - await page.getByText("API Base URLThe base URL for").click(); - await page - .getByRole("textbox", { name: "API Base URL" }) - .fill("http://localhost:3500/v1"); - await page.getByRole("button", { name: "Add Provider" }).click(); - - // Create custom model - await page - .getByRole("heading", { name: "test-provider Needs Setup" }) - .click(); - await page.getByRole("button", { name: "Add Custom Model" }).click(); - await page.getByRole("textbox", { name: "Model ID*" }).fill("test-model"); - await page.getByRole("textbox", { name: "Model ID*" }).press("Tab"); - await page.getByRole("textbox", { name: "Name*" }).fill("test-model"); - await page.getByRole("button", { name: "Add Model" }).click(); - - // Go to apps page and select custom model - await page.getByRole("link", { name: "Apps" }).click(); - await page.getByRole("button", { name: "Model: Auto" }).click(); - await page.getByText("test-provider").click(); - await page.getByText("test-model").click(); - - // Enter prompt and send - await page.getByRole("textbox", { name: "Ask Dyad to build..." }).click(); - await page.getByRole("textbox", { name: "Ask Dyad to build..." }).fill("hi"); - await page.getByRole("button", { name: "Start new chat" }).click(); - - // Make sure it's done - await expect(page.getByRole("button", { name: "Retry" })).not.toBeVisible(); - await expect(page.getByTestId("messages-list")).toMatchAriaSnapshot(); +test("simple message to custom test model", async ({ po }) => { + await po.setUp(); + await po.sendPrompt("hi"); + await po.dumpMessages(); +}); + +test("basic message to custom test model", async ({ po }) => { + await po.setUp(); + await po.sendPrompt("tc=basic"); + await po.dumpMessages(); }); diff --git a/e2e-tests/snapshots/main.spec.ts_basic-message-to-custom-test-model-1.aria.yml b/e2e-tests/snapshots/main.spec.ts_basic-message-to-custom-test-model-1.aria.yml new file mode 100644 index 0000000..ff9ddd9 --- /dev/null +++ b/e2e-tests/snapshots/main.spec.ts_basic-message-to-custom-test-model-1.aria.yml @@ -0,0 +1,4 @@ +- paragraph: tc=basic +- paragraph: This is a simple basic response +- button "Retry": + - img \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts index 2bbf85f..fe6d36c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,7 +3,7 @@ import { PlaywrightTestConfig } from "@playwright/test"; const config: PlaywrightTestConfig = { testDir: "./e2e-tests", maxFailures: 1, - timeout: process.env.CI ? 30_000 : 10_000, + timeout: process.env.CI ? 30_000 : 15_000, // Use a custom snapshot path template because Playwright's default // is platform-specific which isn't necessary for Dyad e2e tests // which should be platform agnostic (we don't do screenshots; only textual diffs). diff --git a/src/app/TitleBar.tsx b/src/app/TitleBar.tsx index 1cbfc39..38fffef 100644 --- a/src/app/TitleBar.tsx +++ b/src/app/TitleBar.tsx @@ -72,6 +72,7 @@ export const TitleBar = () => {
Dyad Logo {isDyadPro && (