diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cd32661..dd795a94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -35,3 +38,32 @@ jobs: - run: npm test - run: npm run prettier + + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + run: npx playwright test --reporter=list + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-results + path: test-results/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index dc9ad995..1d78df59 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ bun.lockb .vscode/ docs/api/ tmp/ +intermediate-findings/ + +# Playwright +playwright-report/ +test-results/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 310bc209..81f776ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,45 @@ Or build and run examples: npm run examples:start ``` +## Testing + +### Unit Tests + +Run unit tests with Bun: + +```bash +npm test +``` + +### E2E Tests + +E2E tests use Playwright to verify all example servers work correctly with screenshot comparisons. + +```bash +# Run all E2E tests +npm run test:e2e + +# Run a specific server's tests +npm run test:e2e -- --grep "Budget Allocator" + +# Run tests in interactive UI mode +npm run test:e2e:ui +``` + +### Updating Golden Screenshots + +When UI changes are intentional, update the golden screenshots: + +```bash +# Update all screenshots +npm run test:e2e:update + +# Update screenshots for a specific server +npm run test:e2e:update -- --grep "Three.js" +``` + +**Note**: Golden screenshots are platform-agnostic. Tests use canvas masking and tolerance thresholds to handle minor cross-platform rendering differences. + ## Code of Conduct This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing. diff --git a/examples/basic-host/src/index.tsx b/examples/basic-host/src/index.tsx index 1288a340..afe51e5e 100644 --- a/examples/basic-host/src/index.tsx +++ b/examples/basic-host/src/index.tsx @@ -1,9 +1,30 @@ +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo } from "./implementation"; import styles from "./index.module.css"; +/** + * Extract default values from a tool's JSON Schema inputSchema. + * Returns a formatted JSON string with defaults, or "{}" if none found. + */ +function getToolDefaults(tool: Tool | undefined): string { + if (!tool?.inputSchema?.properties) return "{}"; + + const defaults: Record = {}; + for (const [key, prop] of Object.entries(tool.inputSchema.properties)) { + if (prop && typeof prop === "object" && "default" in prop) { + defaults[key] = prop.default; + } + } + + return Object.keys(defaults).length > 0 + ? JSON.stringify(defaults, null, 2) + : "{}"; +} + + // Host passes serversPromise to CallToolPanel interface HostProps { serversPromise: Promise; @@ -74,6 +95,14 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) { setSelectedServer(server); const [firstTool] = server.tools.keys(); setSelectedTool(firstTool ?? ""); + // Set input JSON to tool defaults (if any) + setInputJson(getToolDefaults(server.tools.get(firstTool ?? ""))); + }; + + const handleToolSelect = (toolName: string) => { + setSelectedTool(toolName); + // Set input JSON to tool defaults (if any) + setInputJson(getToolDefaults(selectedServer?.tools.get(toolName))); }; const handleSubmit = () => { @@ -96,7 +125,7 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) {