# Project export: Couch

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Manual website testing is tedious-especially when teams are iterating rapidly. We wanted to reimagine QA: combining LLMs with browser automation to create an intelligent, autonomous QA engineer.
- Devpost: https://devpost.com/software/couch-rcegqt
- GitHub: https://github.com/dwang88/calhacks2025
- Team: 4 GitHub contributor(s) — Srikar Eranky (17 commits), matthewjlu (7 commits), Aarav Navani (5 commits), dwang88 (5 commits)

## Devpost submission (written by the team)

### Inspiration

Imagine if testing your website was as simple as pasting a URL. Our platform uses Claude AI and Stagehand to automatically explore your site, generate user flows (eg. "Login → Checkout" or "Signup → Browse"), and test them end-to-end with no manual scripting needed. It identifies bugs in real-time, flags broken steps with full context and screenshots, and gives you a clean dashboard to monitor, debug, and re-run tests instantly. Think of it as an AI-powered QA engineer that never sleeps.

### What it does

Our platform uses Claude AI and Stagehand to automatically explore your site, generate user flows (eg. "Login → Checkout" or "Signup → Browse"), and test them end-to-end with no manual scripting needed. It identifies bugs in real-time, flags broken steps with full context and screenshots, and gives you a clean dashboard to monitor, debug, and re-run tests instantly. It works like this: Generating test steps: Each flow is broken down into atomic actions (clicks, form entries, validations), using the Stagehand and Playwright framework. Executing flows: Stagehand performs actions (act()) and validations (extract()/observe()), logging each step with success/failure status and context. Autonomous Error detection: Errors (like missing elements or failed assertions) trigger alerts, and an agent autonomously creates a Github issue on the website's repository indicating the incorrect user flow. Continuous feedback: Developers can re-run flows, annotate failures (false alarm or real bug), and iterate instantly. Accurate Summarization: We utilize AI to summarize the results of the automatic testing framework in a sleek, interactive chatbot interface.

### How we built it

Frontend: Next.js + Tailwind UI + ShadCN to render flows, logs, screenshots, and error history. Stagehand Integration: We used Stagehand’s page.goto, act, observe, and extract APIs for page control and assertions. LLM Integration: We utilized Anthropic's API to create a function that is called when Stagehand detects failed tests in the DOM - this trigger the LLM to create a title and description for a Github issue and create a Github issue on the repo. We also utilized the Gemini API for a summarization of the battle testing.

### Challenges we ran into

One big challenge was trying to figure out which framework to use to integrate multiple AI agents. We got over this hump by utilizing Langchain and Langgraph's support for autonomous agents.

### Accomplishments we're proud of

We are proud of being able to build a feature that autonomously created Github issues, as that was one main functionality we wanted to integrate.

### What we learned

We learned a lot about AI agents as well as different frameworks for integrating multiple agents into software applications. We also learned a lot about browser automation and testing.

### What's next

We plan on incorporating a fully agentic system that can read and understand HTML DOM, and identify potential issues within the code. We also hope to allow AI to make decisions about creating Github issues/PRs, as well as new frontend designs. Srikar Eranky Hacker ID: 98A

## README (from the GitHub repository)

# Website Testing Assistant

An AI-powered website testing system that automatically scrapes websites, generates Playwright tests, runs them, and creates GitHub issues for test failures.

## Features

- 🤖 **AI-Powered Test Generation**: Uses Claude to generate comprehensive Playwright tests
- 🌐 **Website Scraping**: Extracts HTML, JavaScript, and CSS from any website
- 🧪 **Automated Testing**: Runs generated tests and reports results
- 🐛 **GitHub Integration**: Automatically creates GitHub issues for test failures
- 💬 **Chatbot Interface**: Natural language interface for testing workflows
- 🔧 **FastMCP Server**: Anthropic's FastMCP server with @tool decorators for AI agent integration

## Architecture

```
Frontend (Next.js) → API Server (FastAPI) → Anthropic API → FastMCP Server → Tools
                                                                    ↓
                                                              GitHub Issues
```

## Setup

### Prerequisites

- Python 3.8+
- Node.js 18+
- Playwright browsers
- Anthropic API key
- GitHub token (optional, for issue creation)

### Backend Setup

1. **Install Python dependencies:**

   ```bash
   cd backend
   pip install -r requirements.txt
   ```

2. **Install Playwright browsers:**

   ```bash
   playwright install
   ```

3. **Set up environment variables:**
   Create a `.env` file in the `backend` directory:

   ```env
   ANTHROPIC_API_KEY=your_anthropic_api_key_here
   GITHUB_TOKEN=your_github_token_here
   GITHUB_REPO=owner/repo_name
   ```

4. **Start the servers:**

   ```bash
   ./start.sh
   ```

   This will start:

   - FastMCP server on `http://localhost:8001`
   - API server on `http://localhost:8000`
   - Frontend on `http://localhost:3000`

### Frontend Setup

1. **Install dependencies:**

   ```bash
   cd frontend
   npm install --legacy-peer-deps
   ```

2. **Start the development server:**
   ```bash
   npm run dev
   ```
   The frontend will run on `http://localhost:3000`

## Usage

### Chatbot Interface

1. Open `http://localhost:3000` in your browser
2. Enter a website URL in the URL field
3. Use natural language to interact with the assistant:

   **Examples:**

   - "Test the website at https://example.com"
   - "Scrape https://example.com"
   - "Generate tests for https://example.com"
   - "Run test_google_com.py"

### Quick Actions

The interface provides quick action buttons for common tasks:

- **Test Website Integrity**: Complete workflow (scrape → generate → run → create issue)
- **Scrape Website**: Extract HTML, JavaScript, and CSS
- **Generate Tests**: Create Playwright test files
- **Run Tests**: Execute existing test files

### FastMCP Server

The FastMCP server can be used directly by AI agents:

```bash
cd backend
python fastmcp_server.py
```

**Available Tools:**

- `scrape_website(url)`: Extract website content
- `generate_playwright_test(url, html_content)`: Generate test files
- `run_playwright_test(filename)`: Execute tests
- `create_github_issue(title, body, labels)`: Create GitHub issues
- `test_website_integrity(url)`: Complete workflow

## API Endpoints

### POST `/api/chat`

Handle chat messages and route to appropriate tools via Anthropic.

**Request:**

```json
{
  "messages": [
    { "role": "user", "content": "Test the website at https://example.com" }
  ],
  "url": "https://example.com"
}
```

**Response:**

```json
{
  "message": "✅ Website integrity test completed successfully!",
  "success": true,
  "data": {...}
}
```

### GET `/api/tools`

List available MCP tools.

## Workflow

1. **User Input**: User provides a URL and command via chatbot
2. **Anthropic Processing**: API server sends request to Anthropic with tool definitions
3. **Tool Decision**: Anthropic decides which tools to use
4. **FastMCP Execution**: API server calls FastMCP server to execute tools
5. **Results**: Returns detailed results to the user

## Configuration

### Environment Variables

| Variable            | Description                            | Required |
| ------------------- | -------------------------------------- | -------- |
| `ANTHROPIC_API_KEY` | Anthropic API key for Claude           | Yes      |
| `GITHUB_TOKEN`      | GitHub personal access token           | No       |
| `GITHUB_REPO`       | GitHub repository (format: owner/repo) | No       |

### GitHub Token Setup

1. Go to GitHub Settings → Developer settings → Personal access tokens
2. Generate a new token with `repo` scope
3. Add the token to your `.env` file

## Development

### Project Structure

```
├── backend/
│   ├── fastmcp_server.py      # FastMCP server with @tool decorators
│   ├── api_server.py          # FastAPI server with Anthropic integration
│   ├── fastmcp_client.py      # Client for communicating with FastMCP server
│   ├── generate_tests.py      # Original test generator
│   ├── requirements.txt       # Python dependencies
│   └── .env                   # Environment variables
├── frontend/
│   ├── app/
│   │   └── page.tsx           # Chatbot interface
│   ├── components/
│   │   └── ui/                # UI components
│   └── package.json           # Node dependencies
└── README.md
```

### Adding New Tools

1. Add the tool function with `@mcp.tool()` decorator in `fastmcp_server.py`
2. Add tool definition to the API server's tool list
3. Update the system message in `api_server.py` if needed

### FastMCP Tool Example

```python
@mcp.tool()
async def my_new_tool(param1: str, param2: int) -> str:
    """Description of what this tool does.

    Args:
        param1: Description of param1
        param2: Description of param2
    """
    # Tool implementation
    return "Tool result"
```

## Troubleshooting

### Common Issues

1. **Playwright browsers not installed:**

   ```bash
   playwright install
   ```

2. **CORS errors:**

   - Ensure the frontend is running on `http://localhost:3000`
   - Check CORS configuration in `api_server.py`

3. **API key errors:**

   - Verify your `.env` file is in the backend directory
   - Check that `ANTHROPIC_API_KEY` is set correctly

4. **FastMCP server not responding:**

   - Ensure FastMCP server is running on port 8001
   - Check that FastMCP dependencies are installed

5. **GitHub integration not working:**
   - Ensure `GITHUB_TOKEN` and `GITHUB_REPO` are set
   - Verify the token has `repo` scope

### Debug Mode

Run the API server with debug logging:

```bash
uvicorn api_server:app --reload --log-level debug
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## License

MIT License - see LICENSE file for details.


## Detected evidence (automated analysis)

Indexed codebase: 83 recognized source files, 282 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (92 of 92)

```
.gitignore
backend/.gitignore
backend/api_server.py
backend/fastmcp_server.py
backend/generate_tests.py
backend/package.json
backend/requirements.txt
backend/test_google_com.py
backend/test_localhost_3001_.py
components/error-boundary.tsx
frontend/.gitignore
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/components.json
frontend/components/area-chart.tsx
frontend/components/charts-section.tsx
frontend/components/filters.tsx
frontend/components/header.tsx
frontend/components/icons.tsx
frontend/components/json-data-viewer.tsx
frontend/components/loading-spinner.tsx
frontend/components/map-visualization.tsx
frontend/components/metric-card.tsx
frontend/components/stats-card.tsx
frontend/components/status-indicator.tsx
frontend/components/theme-provider.tsx
frontend/components/time-period-selector.tsx
frontend/components/ui/accordion.tsx
frontend/components/ui/alert-dialog.tsx
frontend/components/ui/alert.tsx
frontend/components/ui/aspect-ratio.tsx
frontend/components/ui/avatar.tsx
frontend/components/ui/badge.tsx
frontend/components/ui/breadcrumb.tsx
frontend/components/ui/button.tsx
frontend/components/ui/calendar.tsx
frontend/components/ui/card.tsx
frontend/components/ui/carousel.tsx
frontend/components/ui/chart.tsx
frontend/components/ui/checkbox.tsx
frontend/components/ui/collapsible.tsx
frontend/components/ui/command.tsx
frontend/components/ui/context-menu.tsx
frontend/components/ui/dialog.tsx
frontend/components/ui/drawer.tsx
frontend/components/ui/dropdown-menu.tsx
frontend/components/ui/form.tsx
frontend/components/ui/hover-card.tsx
frontend/components/ui/input-otp.tsx
frontend/components/ui/input.tsx
frontend/components/ui/label.tsx
frontend/components/ui/menubar.tsx
frontend/components/ui/navigation-menu.tsx
frontend/components/ui/pagination.tsx
frontend/components/ui/popover.tsx
frontend/components/ui/progress.tsx
frontend/components/ui/radio-group.tsx
frontend/components/ui/resizable.tsx
frontend/components/ui/scroll-area.tsx
frontend/components/ui/select.tsx
frontend/components/ui/separator.tsx
frontend/components/ui/sheet.tsx
frontend/components/ui/sidebar.tsx
frontend/components/ui/skeleton.tsx
frontend/components/ui/slider.tsx
frontend/components/ui/sonner.tsx
frontend/components/ui/switch.tsx
frontend/components/ui/table.tsx
frontend/components/ui/tabs.tsx
frontend/components/ui/textarea.tsx
frontend/components/ui/toast.tsx
frontend/components/ui/toaster.tsx
frontend/components/ui/toggle-group.tsx
frontend/components/ui/toggle.tsx
frontend/components/ui/tooltip.tsx
frontend/components/ui/use-mobile.tsx
frontend/components/ui/use-toast.ts
frontend/dashboard.tsx
frontend/hooks/use-mobile.tsx
frontend/hooks/use-toast.ts
frontend/lib/utils.ts
frontend/next.config.mjs
frontend/package.json
frontend/postcss.config.mjs
frontend/styles/globals.css
frontend/tailwind.config.ts
frontend/tsconfig.json
next-env.d.ts
README.md
test.js
utils.ts
```

### Dependencies

- backend/package.json: @browserbasehq/stagehand@^2.3.1, @playwright/test@^1.53.1, dotenv@^16.5.0, zod@^3.25.67
- backend/requirements.txt: aiohttp@==3.9.1, anthropic@==0.7.8, fastapi@==0.104.1, fastmcp@==0.1.0, langchain-anthropic@==0.2.0, langchain-mcp-adapters@==0.0.1, langgraph@==0.2.0, openai@==1.3.0, playwright@==1.40.0, pydantic@==2.5.0, python-dotenv@==1.0.0, requests@==2.31.0, uvicorn@==0.24.0
- frontend/package.json: @browserbasehq/stagehand@^2.3.1, @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@1.1.1, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @types/node@^22, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@4.1.0, embla-carousel-react@8.5.1, input-otp@1.4.1, lucide-react@^0.454.0, next@15.2.4, next-themes@^0.4.4, postcss@^8.5, react@^19.1.0, react-day-picker@8.10.1, react-dom@^19.1.0, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@latest, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5, vaul@^0.9.6, zod@^3.24.1

### Recent commits (newest first)

- _
- Merge pull request #11 from dwang88/srikar-updates
- working demo
- Merge pull request #10 from dwang88/srikar-updates
- claude integration
- Merge pull request #9 from dwang88/srikar-updates
- github integrations
- Merge branch 'main' of https://github.com/dwang88/calhacks2025 into srikar-updates
- updates
- Merge pull request #8 from dwang88/ai-test
- added stagehand
- more fixes
- Merge branch 'main' of https://github.com/dwang88/calhacks2025 into ai-test
- Merge pull request #7 from dwang88/srikar-updates
- fixes
- Merge pull request #6 from dwang88/srikar-updates
- agentic flow
- Merge branch 'main' of https://github.com/dwang88/calhacks2025 into ai-test
- Remove .env from tracking and add to gitignore
- full ai test pipeline

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
fastapi==0.104.1
uvicorn==0.24.0
playwright==1.40.0
anthropic==0.7.8
python-dotenv==1.0.0
requests==2.31.0
pydantic==2.5.0
fastmcp==0.1.0
aiohttp==3.9.1
langchain-mcp-adapters==0.0.1
langgraph==0.2.0
langchain-anthropic==0.2.0
openai==1.3.0 
```

### backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@browserbasehq/stagehand": "^2.3.1",
    "@playwright/test": "^1.53.1",
    "dotenv": "^16.5.0",
    "zod": "^3.25.67"
  }
}

```

### frontend/package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "next lint",
    "start": "next start"
  },
  "dependencies": {
    "@browserbasehq/stagehand": "^2.3.1",
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "1.1.1",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "4.1.0",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "lucide-react": "^0.454.0",
    "next": "15.2.4",
    "next-themes": "^0.4.4",
    "react": "^19.1.0",
    "react-day-picker": "8.10.1",
    "react-dom": "^19.1.0",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "latest",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.6",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "^5"
  }
}

```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { ThemeProvider } from '@/components/theme-provider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <head>
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
        <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap" rel="stylesheet" />
      </head>
      <body className={inter.className}>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

```

### frontend/app/page.tsx

```typescript
"use client";

import { useState, useRef, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { SystemStatus } from "@/components/status-indicator";
import {
  Send,
  Globe,
  TestTube,
  User,
  Sparkles,
  Clock,
  Code,
  Activity,
  Bot,
  Plus,
  ArrowUp,
} from "lucide-react";
import { LoadingPulse } from "@/components/loading-spinner";
import JsonDataViewer from "@/components/json-data-viewer";

interface Message {
  id: string;
  role: "user" | "assistant";
  content: string;
  timestamp: Date;
  data?: any;
}

export default function ChatbotPage() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [isChatExpanded, setIsChatExpanded] = useState(false);
  const scrollRef = useRef<HTMLDivElement>(null);
  const messagesRef = useRef(messages);

  useEffect(() => {
    messagesRef.current = messages;
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [messages]);

  const sendMessage = async () => {
    if (!input.trim()) return;

    const currentInput = input;
    const isFirstMessage = messages.length === 0;

    // Expand the chat view on the first message
    if (isFirstMessage && !isChatExpanded) {
      setIsChatExpanded(true);
    }
    
    const userMessage: Message = {
      id: Date.now().toString(),
      role: "user",
      content: currentInput,
      timestamp: new Date(),
    };

    const newMessages = [...messagesRef.current, userMessage];
    
    setMessages(newMessages);
    setInput("");
    setIsLoading(true);

    try {
      const response = await fetch("http://127.0.0.1:8000/chat", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          messages: newMessages.map((msg) => ({
            role: msg.role,
            content: msg.content,
          })),
        }),
      });

      const data = await response.json();

      const assistantMessage: Message = {
        id: (Date.now() + 1).toString(),
        role: "assistant",
        content: data.message,
        timestamp: new Date(),
        data: data.data,
      };

      setMessages((prev) => [...prev, assistantMessage]);
    } catch (error) {
      const errorMessage: Message = {
        id: (Date.now() + 1).toString(),
        role: "assistant",
        content: "❌ Sorry, I encountered an error. Please try again.",
        timestamp: new Date(),
      };
      setMessages((prev) => [...prev, errorMessage]);
    } finally {
      setIsLoading(false);
    }
  };
  
  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  };

  const getMessageIcon = (role: "user" | "assistant") => {
    if (role === "user") return <User className="w-4 h-4" />;
    return <Bot className="w-4 h-4" />;
  };

  const formatMessage = (content: string) => {
    return content.split("\n").map((line, index) => {
      if (line.startsWith("**") && line.endsWith("**")) {
        return (
          <strong key={index} className="font-semibold">
            {line.slice(2, -2)}
          </strong>
        );
      }
      if (line.startsWith("*") && line.endsWith("*")) {
        return (
          <em key={index} className="italic">
            {line.slice(1, -1)}
          </em>
        );
      }
      return <p key={index} className="mb-2 last:mb-0">{line}</p>;
    });
  };

  return (
    <div className="min-h-screen bg-[#f7f7f8] flex items-center justify-center p-4">
      {/* Subtle background pattern */}
      <div className="fixed inset-0 opacity-50 -z-10">
        <div className="absolute inset-0 bg-[radial-gradient(circle_at_1px_1px,rgba(156,146,172,0.03)_1px,transparent_0)] bg-[length:20px_20px]"></div>
      </div>
      
      <div className="absolute top-6">
        <SystemStatus />
      </div>

      <div className="relative z-10 w-full max-w-5xl mx-auto">
        {!isChatExpanded ? (
          <div className="text-center">
            <h1 className="text-6xl font-bold text-gray-800 mb-8" style={{ fontFamily: "'Poppins', sans-serif" }}>Couch</h1>
            <div
              className="bg-white border border-gray-200/80 shadow-md rounded-2xl p-4 w-full max-w-3xl mx-auto text-left"
            >
              <textarea
                placeholder="Ask me to battle test a website..."
                className="w-full bg-transparent outline-none resize-none text-lg text-gray-700 placeholder:text-gray-400"
                rows={3}
                value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyPress={handleKeyPress}
              />
              <div className="flex justify-between items-center mt-3">
                <div className="flex gap-2 items-center">
                  <Button
                    variant="ghost"
                    size="icon"
                    className="rounded-full text-gray-500 hover:bg-gray-100 hover:text-gray-800"
                  >
                    <Plus className="h-5 w-5" />
                  </Button>
                  <Button
                    variant="ghost"
                    className="rounded-full text-gray-500 hover:bg-gray-100 hover:text-gray-800 px-4"
                  >
                    <Globe className="h-4 w-4 mr-2" />
                    Public
                  </Button>
                </div>
                <Button
                  size="icon"
                  className="rounded-full bg-gray-800 hover:bg-gray-900 text-white w-9 h-9"
                  onClick={sendMessage}
                  disabled={isLoading}
                >
                
[truncated — 6480 more characters]
```

### next-env.d.ts

```typescript
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

```

### utils.ts

```typescript
import { AgentAction, AgentResult } from "@browserbasehq/stagehand";
import { exec } from "child_process";
import fs from "fs/promises";

export async function replay(result: AgentResult) {
  const history = result.actions;
  const replay = history
    .map((action: AgentAction) => {
      switch (action.type) {
        case "act":
          if (!action.playwrightArguments) {
            throw new Error("No playwright arguments provided");
          }
          return `await page.act(${JSON.stringify(
            action.playwrightArguments
          )})`;
        case "extract":
          return `await page.extract("${action.parameters}")`;
        case "goto":
          return `await page.goto("${action.parameters}")`;
        case "wait":
          return `await page.waitForTimeout(${parseInt(
            action.parameters as string
          )})`;
        case "navback":
          return `await page.goBack()`;
        case "refresh":
          return `await page.reload()`;
        case "close":
          return `await stagehand.close()`;
        default:
          return `await stagehand.oops()`;
      }
    })
    .join("\n");

  console.log("Replay:");
  const boilerplate = `
import { Page, BrowserContext, Stagehand } from "@browserbasehq/stagehand";

export async function main(stagehand: Stagehand) {
    const page = stagehand.page
	${replay}
}
  `;
  await fs.writeFile("replay.ts", boilerplate);

  // Format the replay file with prettier
  await new Promise((resolve, reject) => {
    exec(
      "npx prettier --write replay.ts",
      (error: any, stdout: any, stderr: any) => {
        if (error) {
          console.error(`Error formatting replay.ts: ${error}`);
          reject(error);
          return;
        }
        resolve(stdout);
      }
    );
  });
}
```

### test.js

```javascript
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
let url = "https://www.amazon.com/";

// Initialize Stagehand with OpenAI configuration
let stagehand = new Stagehand({
	env: "LOCAL",
	modelName: "claude-3-5-sonnet-20240620",
	modelClientOptions: {
		apiKey: process.env.ANTHROPIC_API_KEY,
	},
	verbose: 1,
});
await stagehand.init();

let page = stagehand.page;

console.log("🤖 AI-Powered Website Bug Testing System");
console.log("========================================");

try {
	// Step 1: Navigate and scrape the website
	console.log("\n📍 Step 1: Navigating and scraping website...");
	await page.goto(url);
	await page.waitForTimeout(3000);
	
	// Get the full HTML content
	const htmlContent = await page.evaluate(() => {
		return document.documentElement.outerHTML;
	});
	
	// Get visible text content for context
	const visibleContent = await page.evaluate(() => {
		return document.body.innerText;
	});
	
	console.log("✅ Website content scraped successfully");
	console.log(`📄 HTML length: ${htmlContent.length} characters`);
	console.log(`📝 Visible text length: ${visibleContent.length} characters`);

	// Step 2: Analyze with LLM to generate test cases
	console.log("\n🧠 Step 2: Analyzing website with LLM to generate test cases...");
	
	const testPlan = await page.extract({
		instruction: `Analyze this website's HTML and visible content to identify ALL interactive elements and functionalities that should be tested. 
		
		HTML Content: ${htmlContent.substring(0, 10000)}
		
		Visible Content: ${visibleContent.substring(0, 2000)}
		
		Generate comprehensive test cases for every button, link, form, input, and interactive element. Include both positive tests (should work) and negative tests (might break).`,
		
		schema: z.object({
			websiteType: z.string().describe("What type of website this is"),
			criticalFunctionalities: z.array(z.string()).describe("Most important features that must work"),
			testCases: z.array(z.object({
				testName: z.string().describe("Name of the test"),
				action: z.string().describe("Specific action to perform (e.g., 'click on Login button', 'fill form with test data')"),
				expectedBehavior: z.string().describe("What should happen when this action is performed"),
				riskLevel: z.enum(["low", "medium", "high"]).describe("Risk level if this functionality breaks"),
				elementSelector: z.string().describe("How to find this element (button text, CSS selector, etc.)"),
			})).describe("List of all test cases to execute"),
			potentialIssues: z.array(z.string()).describe("Potential issues or bugs that might exist based on the code structure"),
		}),
	});
	
	console.log("🎯 LLM Analysis Complete!");
	console.log(`📊 Website Type: ${testPlan.websiteType}`);
	console.log(`🔧 Critical Functionalities: ${testPlan.criticalFunctionalities.join(", ")}`);
	console.log(`🧪 Generated ${testPlan.testCases.length} test cases`);
	console.log(`⚠️  Potential Issues: ${testPlan.potentialIssues.join(", ")}`);

	// Step 3: Execute all test cases with Stagehand
	console.log("\n🚀 Step 3: Executing automated test cases...");
	
	const testResults = [];
	
	for (let i = 0; i < testPlan.testCases.length; i++) {
		const testCase = testPlan.testCases[i];
		console.log(`\n🧪 Test ${i + 1}/${testPlan.testCases.length}: ${testCase.testName}`);
		console.log(`   Action: ${testCase.action}`);
		console.log(`   Expected: ${testCase.expectedBehavior}`);
		console.log(`   Risk Level: ${testCase.riskLevel.toUpperCase()}`);
		
		let testResult = {
			testName: testCase.testName,
			action: testCase.action,
			status: "unknown",
			error: null,
			actualBehavior: "",
			riskLevel: testCase.riskLevel
		};
		
		try {
			// Check if browser/page is still alive, restart if necessary
			try {
				await page.evaluate(() => true);
			} catch (browserError) {
				console.log(`   🔄 Browser crashed, restarting...`);
				await stagehand.close();
				const newStagehand = new Stagehand({
					env: "LOCAL",
					modelName: "gpt-4o-2024-08-06",
					modelClientOptions: {
						apiKey: process.env.OPENAI_API_KEY,
					},
					verbose: 1,
				});
				await newStagehand.init();
				
				// Update our references
				stagehand = newStagehand;
				page = stagehand.page;
				console.log(`   ✅ New browser instance started`);
			}
			
			// Navigate to fresh page for each test to avoid contamination
			console.log("   🔄 Loading fresh page...");
			await page.goto(url);
			await page.waitForTimeout(2000);
			
			// AGGRESSIVE CLEANUP: Clear all browser state that might carry errors
			await page.evaluate(() => {
				// Clear console errors
				if (window.console && window.console.clear) {
					window.console.clear();
				}
				
				// Clear any stored console errors
				if (window.console && window.console._errors) {
					window.console._errors = [];
				}
				
				// Force garbage collection if available
				if (window.gc) {
					window.gc();
				}
				
				// Clear any React error boundary states by forcing a refresh
				if (window.location.reload && Math.random() < 0.1) { // Occasionally do hard refresh
					window.location.reload(true);
				}
			});
			
			// Remove any existing listeners to prevent contamination
			page.removeAllListeners('console');
			page.removeAllListeners('pageerror');
			
			// Wait a bit more after cleanup
			await page.waitForTimeout(500);
			
			// Set up fresh error monitoring for this test only
			const testConsoleErrors = [];
			const testPageErrors = [];
			
			const consoleHandler = (msg) => {
				if (msg.type() === 'error') {
					const errorText = msg.text();
					console.log(`   🔍 Raw console message: ${errorText}`);
					// Only catch critical JavaScript runtime errors that happen DURING this specific test
					if (errorText.includes('TypeError:') || 
					    errorText.includes('ReferenceError:') || 
					    errorText.includes('Cannot read properties of undefined') ||
					    (errorText.includes('Cannot read property') && errorText.includes('of undefin
[truncated — 10786 more characters]
```

### frontend/dashboard.tsx

```typescript
import { Header } from "./components/header"
import { FilterBar } from "./components/filters"
import { MetricCard } from "./components/metric-card"
import { StatsCard } from "./components/stats-card"
import { ChartsSection } from "./components/charts-section"
import { MapVisualization } from "./components/map-visualization"
import {
  OrdersIcon,
  OpenOrdersIcon,
  ClosedOrdersIcon,
  StockIcon,
  ReturnOrdersIcon,
  InwardsIcon,
} from "./components/icons"

export default function Dashboard() {
  const metrics = [
    { title: "Total Orders", value: "57", icon: <OrdersIcon />, color: "green" as const },
    { title: "Open Orders", value: "24", icon: <OpenOrdersIcon />, color: "blue" as const },
    { title: "Closed Orders", value: "33", icon: <ClosedOrdersIcon />, color: "green" as const },
    { title: "Stock Value", value: "₹15.23k", icon: <StockIcon />, color: "purple" as const },
    { title: "Return Orders", value: "05", icon: <ReturnOrdersIcon />, color: "yellow" as const },
    { title: "Inwards", value: "01", icon: <InwardsIcon />, color: "blue" as const },
  ]

  const stats = [
    { title: "Total Hospitals", value: "28,900" },
    { title: "Near Expiry Products", value: "250" },
    { title: "Total Outstanding Amount", value: "₹10,20,000" },
  ]

  return (
    <div className="p-6">
      <Header />
      <FilterBar />

      <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
        {metrics.map((metric) => (
          <MetricCard key={metric.title} {...metric} />
        ))}
      </div>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
        {stats.map((stat) => (
          <StatsCard key={stat.title} {...stat} />
        ))}
      </div>

      <ChartsSection />
      <MapVisualization />
    </div>
  )
}

```

### components/error-boundary.tsx

```typescript
'use client'

import React from 'react'

interface ErrorBoundaryState {
  hasError: boolean
  error?: Error
}

interface ErrorBoundaryProps {
  children: React.ReactNode
}

export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('🚨🚨🚨 WEBSITE CRASHED! 🚨🚨🚨', error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="min-h-screen bg-red-50 flex items-center justify-center p-4">
          <div className="max-w-2xl mx-auto text-center">
            <div className="bg-red-600 text-white p-8 rounded-lg shadow-2xl">
              <div className="text-6xl mb-4">💥</div>
              <h1 className="text-3xl font-bold mb-4">WEBSITE CRASHED!</h1>
              <p className="text-xl mb-6">The distributor system has experienced a critical failure!</p>
              
              <div className="bg-red-700 p-4 rounded mb-6 text-left">
                <p className="font-mono text-sm">
                  ERROR: {this.state.error?.message}
                </p>
              </div>
              
              <div className="space-y-2 text-sm">
                <p>🔥 Database connection: LOST</p>
                <p>⚠️  Distributor network: DOWN</p>
                <p>❌ Emergency protocols: ACTIVATED</p>
              </div>
              
              <button 
                onClick={() => window.location.reload()}
                className="mt-6 bg-white text-red-600 px-6 py-3 rounded-lg font-bold hover:bg-gray-100 transition-colors"
              >
                🔄 Restart System
              </button>
            </div>
          </div>
        </div>
      )
    }

    return this.props.children
  }
} 
```

[74 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]