chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
test-results
|
||||
@@ -0,0 +1,156 @@
|
||||
# Examples E2E (Playwright) — Notes for Agents & Future Devs
|
||||
|
||||
This folder contains an end-to-end (e2e) smoke test harness for the repository’s `examples/`.
|
||||
|
||||
The goals are:
|
||||
|
||||
- Provide a consistent way to smoke-test examples locally and in CI.
|
||||
- Support multiple example “shapes” (Next-only vs hybrid examples that also have an agent).
|
||||
- Keep tests lightweight and stable (avoid flakiness, avoid requiring real API keys).
|
||||
|
||||
## How the harness works
|
||||
|
||||
### Selecting which example to run
|
||||
|
||||
This suite intentionally runs **one example at a time**.
|
||||
|
||||
- The active example is selected via the `EXAMPLE` environment variable.
|
||||
- If `EXAMPLE` is not set, it defaults to `form-filling`.
|
||||
|
||||
The Playwright config (`playwright.config.ts`) uses `EXAMPLE` to:
|
||||
|
||||
- Set the `webServer.cwd` to the chosen example directory (`examples/v1/${EXAMPLE}`).
|
||||
- Choose the `webServer.command` used to start the app.
|
||||
|
||||
### Why each spec has `const EXAMPLE = process.env.EXAMPLE ?? "form-filling";`
|
||||
|
||||
Each spec file is **gated** so that when you run the suite for one example:
|
||||
|
||||
- The matching spec runs.
|
||||
- All other specs skip.
|
||||
|
||||
This makes it easy to run a CI matrix (one job per example) while keeping all tests in one folder.
|
||||
|
||||
## Example types
|
||||
|
||||
### Next.js-only examples
|
||||
|
||||
These can be started with:
|
||||
|
||||
- `pnpm dev`
|
||||
|
||||
### Hybrid examples (UI + agent)
|
||||
|
||||
Some examples have a Python agent that can be run alongside the UI.
|
||||
|
||||
For e2e smoke tests we typically only need the UI to boot, so the Playwright config treats these as “hybrid”:
|
||||
|
||||
- `travel`
|
||||
- `research-canvas`
|
||||
|
||||
For hybrid examples the `webServer.command` is:
|
||||
|
||||
- `pnpm dev:ui`
|
||||
|
||||
This avoids starting the Python agent during UI-only smoke tests.
|
||||
|
||||
## Local setup
|
||||
|
||||
### Install Playwright harness deps
|
||||
|
||||
From `examples/e2e`:
|
||||
|
||||
- `pnpm install`
|
||||
- `pnpm exec playwright install --with-deps chromium`
|
||||
|
||||
### Install the example’s deps
|
||||
|
||||
Each example has its own `package.json`.
|
||||
|
||||
Install deps in the example directory you want to test, e.g.:
|
||||
|
||||
- `cd examples/v1/travel && pnpm install`
|
||||
|
||||
Notes:
|
||||
|
||||
- If an example has a `postinstall` that requires non-Node tooling (e.g. Python `uv`), you may want to run `pnpm install --ignore-scripts` for CI-like behavior.
|
||||
|
||||
### Run a single example
|
||||
|
||||
From `examples/e2e`:
|
||||
|
||||
- `EXAMPLE=form-filling pnpm test`
|
||||
- `EXAMPLE=travel pnpm test`
|
||||
- `EXAMPLE=research-canvas pnpm test`
|
||||
- `EXAMPLE=chat-with-your-data pnpm test`
|
||||
- `EXAMPLE=state-machine pnpm test`
|
||||
|
||||
When `EXAMPLE` is set, you should see `1 passed` and the other example specs `skipped`.
|
||||
|
||||
## Test layout
|
||||
|
||||
- Tests live under `tests/v1.x/`.
|
||||
- Each example gets a single smoke spec (minimal assertions).
|
||||
|
||||
Examples:
|
||||
|
||||
- `tests/v1.x/form-filling.spec.ts`
|
||||
- `tests/v1.x/travel.spec.ts`
|
||||
|
||||
## Writing smoke tests (guidelines)
|
||||
|
||||
Keep smoke tests:
|
||||
|
||||
- **Stable**: prefer `getByRole` selectors and obvious headings/buttons.
|
||||
- **Cheap**: do not rely on LLM outputs.
|
||||
- **Non-invasive**: avoid sending chat messages or triggering expensive background work.
|
||||
|
||||
Patterns used:
|
||||
|
||||
- Gate the spec:
|
||||
- `test.skip(EXAMPLE !== "<example>", ...)`
|
||||
- Prefer:
|
||||
- `await expect(page).toHaveTitle(/.../)`
|
||||
- `await expect(page.getByRole("heading", { name: "..." })).toBeVisible()`
|
||||
|
||||
If an example auto-opens Copilot UI / triggers calls, prefer adding a query param to disable it (e.g. `travel` uses `/?copilotOpen=false`).
|
||||
|
||||
## CI (GitHub Actions)
|
||||
|
||||
Workflow:
|
||||
|
||||
- `.github/workflows/test_e2e-legacy-v1.yml`
|
||||
|
||||
It runs a matrix of:
|
||||
|
||||
- `form-filling`
|
||||
- `travel`
|
||||
- `research-canvas`
|
||||
- `chat-with-your-data`
|
||||
- `state-machine`
|
||||
|
||||
Key CI behaviors:
|
||||
|
||||
- Installs `examples/e2e` deps and Playwright Chromium.
|
||||
- Installs the selected example’s deps.
|
||||
- Uses `pnpm install --frozen-lockfile` for deterministic installs.
|
||||
- For `research-canvas`, installs with `--ignore-scripts` to avoid requiring Python tooling just to run UI smoke tests.
|
||||
|
||||
Artifacts:
|
||||
|
||||
- Always uploads Playwright output (`test-results` and `playwright-report`) for debugging.
|
||||
|
||||
## Common issues / debugging
|
||||
|
||||
- "`next: command not found`": the selected example’s `node_modules` are missing; run `pnpm install` in that example directory.
|
||||
- "module not found" for a transitive dep (e.g. `shiki`): add it explicitly to the example’s dependencies and reinstall.
|
||||
- Next.js dev warnings about cross-origin (`allowedDevOrigins`): currently treated as warnings; tests can still pass.
|
||||
|
||||
## Adding a new example
|
||||
|
||||
1. Ensure the example can be started via `pnpm dev` (Next-only) or `pnpm dev:ui` (hybrid).
|
||||
2. Add a new spec under `tests/v1.x/<example>.spec.ts`.
|
||||
3. Run locally:
|
||||
- `EXAMPLE=<example> pnpm test`
|
||||
4. Add the example name to the CI matrix in:
|
||||
- `.github/workflows/test_e2e-legacy-v1.yml`
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "examples-e2e",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:headed": "playwright test --headed",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@types/node": "^22",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
const PORT = Number(process.env.PORT ?? "3000");
|
||||
|
||||
const HYBRID_EXAMPLES = new Set(["travel", "research-canvas"]);
|
||||
const webServerCommand = HYBRID_EXAMPLES.has(EXAMPLE)
|
||||
? "pnpm dev:ui"
|
||||
: "pnpm dev";
|
||||
|
||||
const exampleDir = path.resolve(__dirname, "../v1", EXAMPLE);
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
timeout: 60_000,
|
||||
expect: {
|
||||
timeout: 10_000,
|
||||
},
|
||||
use: {
|
||||
baseURL: `http://127.0.0.1:${PORT}`,
|
||||
trace: "retain-on-failure",
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
webServer: {
|
||||
command: webServerCommand,
|
||||
url: `http://127.0.0.1:${PORT}`,
|
||||
cwd: exampleDir,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 180_000,
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(PORT),
|
||||
NEXT_TELEMETRY_DISABLED: "1",
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "test",
|
||||
NEXT_PUBLIC_CPK_PUBLIC_API_KEY:
|
||||
process.env.NEXT_PUBLIC_CPK_PUBLIC_API_KEY ?? "",
|
||||
NEXT_PUBLIC_COPILOT_PUBLIC_API_KEY:
|
||||
process.env.NEXT_PUBLIC_COPILOT_PUBLIC_API_KEY ?? "",
|
||||
LANGSMITH_API_KEY: process.env.LANGSMITH_API_KEY ?? "",
|
||||
REMOTE_ACTION_URL:
|
||||
process.env.REMOTE_ACTION_URL ?? "http://127.0.0.1:8000/copilotkit",
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
reporter: process.env.CI ? "github" : "list",
|
||||
outputDir: "test-results",
|
||||
});
|
||||
Generated
+95
@@ -0,0 +1,95 @@
|
||||
lockfileVersion: "9.0"
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
.:
|
||||
devDependencies:
|
||||
"@playwright/test":
|
||||
specifier: ^1.50.0
|
||||
version: 1.57.0
|
||||
"@types/node":
|
||||
specifier: ^22
|
||||
version: 22.19.3
|
||||
typescript:
|
||||
specifier: ^5
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
"@playwright/test@1.57.0":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==,
|
||||
}
|
||||
engines: { node: ">=18" }
|
||||
hasBin: true
|
||||
|
||||
"@types/node@22.19.3":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==,
|
||||
}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==,
|
||||
}
|
||||
engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 }
|
||||
os: [darwin]
|
||||
|
||||
playwright-core@1.57.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==,
|
||||
}
|
||||
engines: { node: ">=18" }
|
||||
hasBin: true
|
||||
|
||||
playwright@1.57.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==,
|
||||
}
|
||||
engines: { node: ">=18" }
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==,
|
||||
}
|
||||
engines: { node: ">=14.17" }
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==,
|
||||
}
|
||||
|
||||
snapshots:
|
||||
"@playwright/test@1.57.0":
|
||||
dependencies:
|
||||
playwright: 1.57.0
|
||||
|
||||
"@types/node@22.19.3":
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
playwright-core@1.57.0: {}
|
||||
|
||||
playwright@1.57.0:
|
||||
dependencies:
|
||||
playwright-core: 1.57.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Regression test for the chat window flex layout broken in 1.55.
|
||||
*
|
||||
* Root cause: the drag-and-drop wrapper div introduced with attachments had
|
||||
* no CSS class when not dragging, so no flex rule applied and the chat body
|
||||
* collapsed to content height instead of filling the window.
|
||||
*/
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("chat window layout", () => {
|
||||
test.skip(EXAMPLE !== "form-filling", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/");
|
||||
// form-filling uses defaultOpen — window is already open after hydration
|
||||
await page.locator(".copilotKitWindow.open").waitFor({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("chat body fills the window", async ({ page }) => {
|
||||
const chatBody = page.locator(".copilotKitChatBody");
|
||||
const window = page.locator(".copilotKitWindow");
|
||||
|
||||
await expect(chatBody).toHaveCSS("flex", "1 1 0%");
|
||||
|
||||
const bodyBox = await chatBody.boundingBox();
|
||||
const windowBox = await window.boundingBox();
|
||||
expect(bodyBox).not.toBeNull();
|
||||
expect(windowBox).not.toBeNull();
|
||||
expect(bodyBox!.height).toBeGreaterThan(windowBox!.height * 0.8);
|
||||
});
|
||||
|
||||
test("messages area fills available space", async ({ page }) => {
|
||||
const messages = page.locator(".copilotKitMessages");
|
||||
const window = page.locator(".copilotKitWindow");
|
||||
|
||||
const messagesBox = await messages.boundingBox();
|
||||
const windowBox = await window.boundingBox();
|
||||
expect(messagesBox).not.toBeNull();
|
||||
expect(windowBox).not.toBeNull();
|
||||
expect(messagesBox!.height).toBeGreaterThan(windowBox!.height * 0.5);
|
||||
expect(messagesBox!.height).toBeLessThanOrEqual(windowBox!.height);
|
||||
});
|
||||
|
||||
test("input is pinned to the lower half of the window", async ({ page }) => {
|
||||
const input = page.locator(".copilotKitInput");
|
||||
const window = page.locator(".copilotKitWindow");
|
||||
|
||||
const inputBox = await input.boundingBox();
|
||||
const windowBox = await window.boundingBox();
|
||||
expect(inputBox).not.toBeNull();
|
||||
expect(windowBox).not.toBeNull();
|
||||
|
||||
const inputMidY = inputBox!.y + inputBox!.height / 2;
|
||||
const windowMidY = windowBox!.y + windowBox!.height / 2;
|
||||
expect(inputMidY).toBeGreaterThan(windowMidY);
|
||||
});
|
||||
|
||||
test("drag state does not break layout", async ({ page }) => {
|
||||
const chatBody = page.locator(".copilotKitChatBody");
|
||||
const messages = page.locator(".copilotKitMessages");
|
||||
const window = page.locator(".copilotKitWindow");
|
||||
|
||||
await page.evaluate(() => {
|
||||
const body = document.querySelector(".copilotKitChatBody");
|
||||
body?.dispatchEvent(new DragEvent("dragenter", { bubbles: true }));
|
||||
});
|
||||
|
||||
await expect(chatBody).toHaveCSS("flex", "1 1 0%");
|
||||
|
||||
const messagesBox = await messages.boundingBox();
|
||||
const windowBox = await window.boundingBox();
|
||||
expect(messagesBox).not.toBeNull();
|
||||
expect(windowBox).not.toBeNull();
|
||||
expect(messagesBox!.height).toBeGreaterThan(windowBox!.height * 0.5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("chat-with-your-data", () => {
|
||||
test.skip(EXAMPLE !== "chat-with-your-data", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test("loads", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page).toHaveTitle(/Chat with your data/i);
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Data Dashboard" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Regression test for #2920 — .dark CSS selectors must be scoped to CopilotKit
|
||||
* elements, not leak into the host application.
|
||||
*
|
||||
* Before the fix (main), selectors like `.dark, html.dark, body.dark` applied
|
||||
* `color: white` and `color: rgb(69, 69, 69)` directly to the .dark element
|
||||
* itself, cascading into every child — including host app content.
|
||||
*
|
||||
* After the fix (#3850), selectors are scoped:
|
||||
* `.dark .copilotKitDevConsole .copilotKitDebugMenuTriggerButton`
|
||||
* `.dark .poweredBy`
|
||||
* so only CopilotKit elements receive the dark mode overrides.
|
||||
*/
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("dark mode CSS scoping (#2920)", () => {
|
||||
// This test relies on an app that uses .dark class (e.g. form-filling)
|
||||
test.skip(EXAMPLE !== "form-filling", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.locator(".copilotKitWindow.open").waitFor({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("enabling .dark class does not leak CopilotKit styles to host elements", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Grab a host-app element that should never be styled by CopilotKit CSS.
|
||||
// In form-filling the <body> itself or a top-level container works.
|
||||
const body = page.locator("body");
|
||||
|
||||
// Toggle dark mode by adding .dark to <html>
|
||||
await page.evaluate(() => document.documentElement.classList.add("dark"));
|
||||
|
||||
// Wait a tick for styles to recompute
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
const colorAfter = await body.evaluate(
|
||||
(el) => window.getComputedStyle(el).color,
|
||||
);
|
||||
|
||||
// The bug on main: .dark { color: rgb(69, 69, 69) !important } would
|
||||
// override the body's color. With the fix, the body color should be
|
||||
// determined solely by the host app's own .dark styles, NOT by CopilotKit.
|
||||
//
|
||||
// We verify the body color is NOT rgb(69, 69, 69) — the specific value
|
||||
// that the broken CopilotKit input.css `.dark` rule forced.
|
||||
expect(colorAfter).not.toBe("rgb(69, 69, 69)");
|
||||
});
|
||||
|
||||
test("CopilotKit poweredBy gets correct dark mode color", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Toggle dark mode
|
||||
await page.evaluate(() => document.documentElement.classList.add("dark"));
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
const poweredBy = page.locator(".poweredBy");
|
||||
|
||||
// With the fix, .dark .poweredBy { color: rgb(69, 69, 69) !important }
|
||||
// should apply to the poweredBy element specifically.
|
||||
const count = await poweredBy.count();
|
||||
test.skip(count === 0, "No .poweredBy element found — skipping");
|
||||
|
||||
const color = await poweredBy
|
||||
.first()
|
||||
.evaluate((el) => window.getComputedStyle(el).color);
|
||||
expect(color).toBe("rgb(69, 69, 69)");
|
||||
});
|
||||
|
||||
test("screenshot: dark mode side-by-side comparison", async ({ page }) => {
|
||||
// Light mode screenshot
|
||||
await page.evaluate(() =>
|
||||
document.documentElement.classList.remove("dark"),
|
||||
);
|
||||
await page.waitForTimeout(200);
|
||||
const lightScreenshot = await page.screenshot({ fullPage: true });
|
||||
expect(lightScreenshot).toBeTruthy();
|
||||
|
||||
// Dark mode screenshot
|
||||
await page.evaluate(() => document.documentElement.classList.add("dark"));
|
||||
await page.waitForTimeout(200);
|
||||
const darkScreenshot = await page.screenshot({ fullPage: true });
|
||||
expect(darkScreenshot).toBeTruthy();
|
||||
|
||||
// Visual regression: dark mode screenshot should differ from light mode
|
||||
// (if they're identical, dark mode styles aren't applying at all)
|
||||
expect(Buffer.compare(lightScreenshot, darkScreenshot)).not.toBe(0);
|
||||
});
|
||||
|
||||
test("dark mode styles only target CopilotKit elements", async ({ page }) => {
|
||||
// Add a marker element outside CopilotKit to detect style leaks
|
||||
await page.evaluate(() => {
|
||||
const marker = document.createElement("div");
|
||||
marker.id = "leak-detector";
|
||||
marker.textContent = "Leak detector";
|
||||
document.body.prepend(marker);
|
||||
});
|
||||
|
||||
// Enable dark mode
|
||||
await page.evaluate(() => document.documentElement.classList.add("dark"));
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
const markerColorDark = await page
|
||||
.locator("#leak-detector")
|
||||
.evaluate((el) => window.getComputedStyle(el).color);
|
||||
|
||||
// The leak detector element should NOT have its color changed by
|
||||
// CopilotKit's .dark CSS rules. If it does, that's a style leak.
|
||||
//
|
||||
// Note: The host app's own .dark { --foreground: ... } will change colors
|
||||
// via CSS variables, which is fine. What we're checking is that
|
||||
// CopilotKit doesn't force rgb(69, 69, 69) or white via its own rules.
|
||||
expect(markerColorDark).not.toBe("rgb(69, 69, 69)");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("form-filling", () => {
|
||||
test.skip(EXAMPLE !== "form-filling", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test("loads", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Security Incident Report" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole("contentinfo")
|
||||
.filter({ hasText: /Powered by CopilotKit/i })
|
||||
.first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("research-canvas", () => {
|
||||
test.skip(EXAMPLE !== "research-canvas", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test("loads", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Research Helper" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("state-machine", () => {
|
||||
test.skip(EXAMPLE !== "state-machine", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test("loads", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("button", { name: "Orders" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "State Visualizer" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const EXAMPLE = process.env.EXAMPLE ?? "form-filling";
|
||||
|
||||
test.describe("travel", () => {
|
||||
test.skip(EXAMPLE !== "travel", `EXAMPLE=${EXAMPLE}`);
|
||||
|
||||
test("loads", async ({ page }) => {
|
||||
await page.goto("/?copilotOpen=false");
|
||||
await expect(page).toHaveTitle(/CopilotKit Travel/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user