├── .claude ├── agents │ └── stagehand-expert.md ├── output-styles │ └── pragmatic-test-driven-developer.md └── commands │ ├── dev │ ├── design-app.md │ └── implement-mvp.md │ └── agent_prompts │ └── stagehand_expert_prompt.md ├── my-app └── tests │ ├── playwright │ ├── color-mixer.spec.ts │ ├── baseline.spec.ts │ └── interactions.spec.ts │ ├── stagehand │ ├── baseline-stagehand.spec.ts │ ├── color-workflows.spec.ts │ └── interactions-stagehand.spec.ts │ └── README.md ├── PRD.md ├── CLAUDE.md ├── README.md └── LESSON.md /.claude/agents/stagehand-expert.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: stagehand-expert 3 | description: Use this agent when you need executable Stagehand test files for TDD workflow. ALWAYS checks latest Stagehand documentation first, then creates hybrid AI+data-testid tests that work locally and in cloud. Expert in LOCAL vs BROWSERBASE modes, proper API usage (stagehand.page.act/observe), and fallback strategies for when AI element discovery fails. Context: User needs E2E tests for color picker app. user: 'Create E2E tests for RGB sliders and preset buttons' assistant: 'I'll use the stagehand-expert agent to first check latest Stagehand docs, then create executable test files with hybrid AI+data-testid strategy for reliable TDD workflow' This agent understands real-world Stagehand limitations and creates robust tests that handle AI discovery failures gracefully. 4 | tools: Read, Write 5 | color: cyan 6 | model: sonnet 7 | --- 8 | 9 | Read and Execute: .claude/commands/agent_prompts/stagehand_expert_prompt.md 10 | -------------------------------------------------------------------------------- /my-app/tests/playwright/color-mixer.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test' 2 | 3 | test.describe('Color Mixer Basic Functionality', () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/') 6 | }) 7 | 8 | test('should display color mixer with initial state', async ({ page }) => { 9 | // Check title is visible 10 | await expect(page.locator('h1')).toContainText('Color Mixer') 11 | 12 | // Check color preview exists 13 | const preview = page.locator('[data-testid="color-preview"]') 14 | await expect(preview).toBeVisible() 15 | 16 | // Check initial color values 17 | await expect(page.locator('[data-testid="hex-value"]')).toContainText('#FF5733') 18 | await expect(page.locator('[data-testid="rgb-value"]')).toContainText('rgb(255, 87, 51)') 19 | 20 | // Check RGB sliders exist 21 | await expect(page.locator('[data-testid="slider-r"]')).toBeVisible() 22 | await expect(page.locator('[data-testid="slider-g"]')).toBeVisible() 23 | await expect(page.locator('[data-testid="slider-b"]')).toBeVisible() 24 | 25 | // Check presets exist 26 | await expect(page.locator('[data-testid="preset-red"]')).toBeVisible() 27 | await expect(page.locator('[data-testid="preset-green"]')).toBeVisible() 28 | await expect(page.locator('[data-testid="preset-blue"]')).toBeVisible() 29 | await expect(page.locator('[data-testid="preset-yellow"]')).toBeVisible() 30 | await expect(page.locator('[data-testid="preset-black"]')).toBeVisible() 31 | await expect(page.locator('[data-testid="preset-white"]')).toBeVisible() 32 | }) 33 | 34 | test('should update color when clicking presets', async ({ page }) => { 35 | // Click red preset 36 | await page.locator('[data-testid="preset-red"]').click() 37 | await expect(page.locator('[data-testid="hex-value"]')).toContainText('#FF0000') 38 | await expect(page.locator('[data-testid="rgb-value"]')).toContainText('rgb(255, 0, 0)') 39 | 40 | // Click blue preset 41 | await page.locator('[data-testid="preset-blue"]').click() 42 | await expect(page.locator('[data-testid="hex-value"]')).toContainText('#0000FF') 43 | await expect(page.locator('[data-testid="rgb-value"]')).toContainText('rgb(0, 0, 255)') 44 | }) 45 | 46 | test('should copy color values to clipboard', async ({ page, context }) => { 47 | // Grant clipboard permissions 48 | await context.grantPermissions(['clipboard-read', 'clipboard-write']) 49 | 50 | // Click to copy hex value 51 | await page.locator('[data-testid="hex-value"]').click() 52 | 53 | // Wait for toast to appear 54 | await page.waitForTimeout(500) 55 | 56 | // Check clipboard content 57 | const clipboardText = await page.evaluate(() => navigator.clipboard.readText()) 58 | expect(clipboardText).toBe('#FF5733') 59 | }) 60 | }) -------------------------------------------------------------------------------- /.claude/output-styles/pragmatic-test-driven-developer.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pragmatic Test-Driven Developer 3 | description: Simple TDD cycle - write test, implement minimal code, verify with user 4 | --- 5 | 6 | You follow a strict Test-Driven Development (TDD) cycle for all development work. 7 | 8 | ## TDD Cycle: Red → Green → Verify 9 | 10 | ### 1. RED: Write the Test First 11 | 12 | - Write a SMALL number of failing tests for the specific feature/behavior 13 | - Run the tests to confirm it fails 14 | - State: "❌ Test written and failing: [test description]" 15 | 16 | ### 2. GREEN: Implement Minimal Code 17 | 18 | - Write the MINIMUM amount of code needed to make that the tests pass. 19 | - No extra features, no "while we're here" additions 20 | - Focus only on making the test green 21 | - State: "✅ Implemented: [minimal description]" 22 | 23 | ### 3. VERIFY: Check with User 24 | 25 | - Run the test to confirm it passes 26 | - Show the working feature to the user 27 | - Ask: "Test passing ✅ - please verify this works as expected before I continue" 28 | - **IMPORTANT** Wait for user feedback before proceeding on any subsequent task in the Todo list. 29 | 30 | ## Rules 31 | 32 | ### What to Do: 33 | 34 | - Write a SMALL number of tests at a time 35 | - Implement the MINIMUM to pass that tests 36 | - **Always verify** with user before moving to next test 37 | - Keep cycles short (5-10 minutes max) 38 | 39 | ### What NOT to Do: 40 | 41 | - Don't implement multiple features at once 42 | - Don't add "nice to have" features 43 | - Don't write multiple tests before implementing 44 | - Don't assume what the user wants next 45 | 46 | ## Communication Style 47 | 48 | **Starting a cycle:** 49 | "Writing test for: [specific behavior]" 50 | 51 | **After test written:** 52 | "❌ Test failing as expected - implementing minimal solution..." 53 | 54 | **After implementation:** 55 | "✅ Test passing - [feature] is working. Please verify before I continue." 56 | 57 | **Waiting for feedback:** 58 | "Ready for next feature when you confirm this works correctly." 59 | 60 | ## Example Flow 61 | 62 | ``` 63 | 1. "Writing test for: RGB slider changes color preview" 64 | 2. ❌ "Test failing - slider not connected to preview" 65 | 3. "Implementing minimal slider-to-preview connection..." 66 | 4. ✅ "Test passing - red slider now updates preview color" 67 | 5. "Please test the red slider and confirm it works before I add green/blue sliders" 68 | 6. [Wait for user verification] 69 | 7. "Writing test for: Green slider changes color preview" 70 | 8. [Repeat cycle] 71 | ``` 72 | 73 | ## Key Principles 74 | 75 | - **One test, one feature, one verification** 76 | - **User drives the priorities** 77 | - **No assumptions about next steps** 78 | - **Minimal viable implementation** 79 | - **Always verify before proceeding** 80 | 81 | Remember: TDD means the test drives the development, not the other way around. Let the user guide what to build next based on what they see working. 82 | -------------------------------------------------------------------------------- /PRD.md: -------------------------------------------------------------------------------- 1 | # Product Requirements: Simple Color Mixer 2 | 3 | ## Overview 4 | 5 | A minimalist color mixing tool designed to demonstrate Test-Driven Development with AI-powered testing using Stagehand and traditional Playwright side-by-side. 6 | 7 | ## Core Features 8 | 9 | ### RGB Sliders 10 | 11 | - Three sliders (Red, Green, Blue: 0-255 range) 12 | - Live color preview updates 13 | - Hex value display (#FF5733) 14 | - RGB value display (rgb(255, 87, 51)) 15 | 16 | ### Color Presets 17 | 18 | - 6 preset buttons: Red, Blue, Green, Yellow, Black, White 19 | - Instant color application on click 20 | 21 | ### Copy to Clipboard 22 | 23 | - Copy current hex value 24 | - Visual feedback on success 25 | 26 | --- 27 | 28 | ## Testing Comparison 29 | 30 | ### Traditional Playwright 31 | 32 | ```typescript 33 | // Brittle - depends on specific selectors 34 | const redSlider = page.locator('[data-testid="slider-r"]'); 35 | await redSlider.fill("255"); 36 | const preview = page.locator('[data-testid="color-preview"]'); 37 | await expect(preview).toHaveCSS("background-color", "rgb(255, 0, 0)"); 38 | ``` 39 | 40 | ### Stagehand AI-Powered 41 | 42 | ```typescript 43 | // Resilient - uses natural language 44 | await stagehand.page.act("Set the red color to maximum"); 45 | const result = await stagehand.page.extract({ 46 | instruction: "Get current color values", 47 | schema: z.object({ hexValue: z.string() }), 48 | }); 49 | expect(result.hexValue).toBe("#FF0000"); 50 | ``` 51 | 52 | --- 53 | 54 | ## Key Testing Advantages 55 | 56 | ### Less Code 57 | 58 | - Playwright: ~150 lines per test file 59 | - Stagehand: ~50 lines for same coverage 60 | 61 | ### Natural Language vs Selectors 62 | 63 | ```typescript 64 | // Instead of finding elements: 65 | page.locator('[data-testid="preset-red"]').click(); 66 | 67 | // Use intent: 68 | stagehand.page.act("Click the red preset button"); 69 | ``` 70 | 71 | ### Structured Data Extraction 72 | 73 | ```typescript 74 | // Extract with schema validation: 75 | const colorData = await stagehand.page.extract({ 76 | instruction: "Get current color values", 77 | schema: z.object({ 78 | hexValue: z.string(), 79 | rgbValue: z.string(), 80 | isActuallyRed: z.boolean(), 81 | }), 82 | }); 83 | ``` 84 | 85 | --- 86 | 87 | ## Tech Stack 88 | 89 | - Next.js with App Router 90 | - shadcn/ui components 91 | - React useState 92 | - Tailwind CSS 93 | 94 | ## TDD Testing Strategy 95 | 96 | ### 1. **Baseline Tests** - Structure validation 97 | 98 | - Element presence 99 | - Accessibility attributes 100 | - Layout structure 101 | 102 | ### 2. **Interaction Tests** - User actions 103 | 104 | - Slider manipulation 105 | - Preset button clicks 106 | - Copy functionality 107 | 108 | ### 3. **Workflow Tests** - Complex scenarios 109 | 110 | - Color mixing sequences 111 | - State persistence 112 | - Edge cases 113 | 114 | ## Success Metrics 115 | 116 | - ✅ **70% code reduction** with Stagehand 117 | - ✅ **Tests survive UI refactors** without changes 118 | - ✅ **Natural language** test descriptions 119 | - ✅ **Faster test development** (3x improvement) 120 | 121 | --- 122 | 123 | ## Cloud Testing with Browserbase 124 | 125 | ### MCP Integration 126 | 127 | ```bash 128 | # Install Browserbase MCP 129 | claude mcp add --scope project --transport stdio \ 130 | browserbase npx @browserbasehq/mcp-server-browserbase 131 | ``` 132 | 133 | ### Test Execution Modes 134 | 135 | ```typescript 136 | // Local development 137 | const stagehand = new Stagehand({ env: "LOCAL" }); 138 | 139 | // Cloud testing 140 | const stagehand = new Stagehand({ 141 | env: "BROWSERBASE", 142 | apiKey: process.env.BROWSERBASE_API_KEY, 143 | }); 144 | ``` 145 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | This is a Test-Driven Development (TDD) educational repository demonstrating Stagehand AI-powered testing vs traditional Playwright testing for a Color Mixer application. The repository showcases a 70% code reduction when using AI-powered natural language tests. 8 | 9 | ## Development Commands 10 | 11 | ### Initial Setup 12 | ```bash 13 | # Navigate to my-app directory 14 | cd my-app 15 | 16 | # Install dependencies 17 | npm install 18 | 19 | # Create .env file with required API keys 20 | echo "OPENAI_API_KEY=your_openai_api_key_here" > .env 21 | 22 | # Optional: Install Browserbase MCP for cloud testing 23 | claude mcp add --scope project --transport stdio browserbase npx @browserbasehq/mcp-server-browserbase 24 | ``` 25 | 26 | ### Running the Application 27 | ```bash 28 | # Start development server (required for tests) 29 | npm run dev # App runs on http://localhost:3000 30 | ``` 31 | 32 | ### Testing Commands 33 | ```bash 34 | # Run all tests 35 | npm run test 36 | 37 | # Run specific test categories 38 | npm run test tests/playwright # All Playwright tests 39 | npm run test tests/stagehand # All Stagehand AI tests 40 | 41 | # Run individual test files 42 | npm run test tests/playwright/baseline.spec.ts 43 | npm run test tests/playwright/color-mixer.spec.ts 44 | npm run test tests/playwright/interactions.spec.ts 45 | npm run test tests/stagehand/baseline-stagehand.spec.ts 46 | npm run test tests/stagehand/color-workflows.spec.ts 47 | npm run test tests/stagehand/interactions-stagehand.spec.ts 48 | 49 | # Debug and development commands 50 | npm run test:headed # Run with visible browser 51 | npm run test:debug # Step-by-step debugging 52 | npm run test:ui # Playwright UI for interactive debugging 53 | 54 | # Advanced test execution 55 | npm run test -- --project=chromium # Specific browser 56 | npm run test -- --grep "color preview" # Pattern matching 57 | npm run test -- --only-failures # Re-run failures 58 | npm run test -- --workers=4 # Parallel execution 59 | ``` 60 | 61 | ## Repository Structure 62 | 63 | ``` 64 | browserbase-claude-code-stagehand/ 65 | ├── my-app/ 66 | │ └── tests/ 67 | │ ├── playwright/ # Traditional selector-based tests 68 | │ │ ├── baseline.spec.ts # Element presence validation 69 | │ │ ├── interactions.spec.ts # User interaction tests 70 | │ │ └── color-mixer.spec.ts # Full workflow tests 71 | │ └── stagehand/ # AI-powered natural language tests 72 | │ ├── baseline-stagehand.spec.ts 73 | │ ├── interactions-stagehand.spec.ts 74 | │ └── color-workflows.spec.ts 75 | └── CLAUDE.md 76 | ``` 77 | 78 | ## TDD Methodology 79 | 80 | Follow the Red → Green → Refactor cycle: 81 | 82 | 1. **RED Phase**: Write tests that fail (tests exist but app doesn't) 83 | 2. **GREEN Phase**: Implement minimal code to pass tests 84 | - Add required `data-testid` attributes 85 | - Implement color calculation logic 86 | - Add event handlers for sliders and buttons 87 | 3. **REFACTOR Phase**: Optimize while keeping tests green 88 | - Performance (<16ms updates) 89 | - Accessibility improvements 90 | 91 | ## Stagehand vs Playwright Patterns 92 | 93 | ### Playwright (Traditional) 94 | ```typescript 95 | const redSlider = page.locator('[data-testid="slider-r"]') 96 | await redSlider.fill('255') 97 | const preview = page.locator('[data-testid="color-preview"] div') 98 | const style = await preview.getAttribute('style') 99 | expect(style).toContain('rgb(255,') 100 | ``` 101 | 102 | ### Stagehand (AI-Powered) 103 | ```typescript 104 | await stagehand.page.act('Set red color to maximum') 105 | const result = await stagehand.page.extract({ 106 | instruction: "Get current color values", 107 | schema: z.object({ hexValue: z.string() }) 108 | }) 109 | expect(result.hexValue).toBe('#FF0000') 110 | ``` 111 | 112 | ## Key Stagehand API 113 | 114 | - **stagehand.page.act()** - Natural language actions 115 | - **stagehand.page.observe()** - Query page state 116 | - **stagehand.page.extract()** - Extract structured data with Zod schemas 117 | 118 | ## Application Requirements 119 | 120 | The Color Mixer app being tested includes: 121 | - RGB sliders (0-255 range) with live preview 122 | - Hex and RGB value display 123 | - 6 color presets (Red, Green, Blue, Yellow, Black, White) 124 | - Copy to clipboard functionality 125 | - <16ms response time requirement 126 | - Proper accessibility attributes 127 | 128 | ## Environment Variables 129 | 130 | ```bash 131 | # Required for Stagehand tests 132 | OPENAI_API_KEY=your_key_here 133 | 134 | # Optional for cloud testing 135 | BROWSERBASE_API_KEY=your_key_here 136 | BROWSERBASE_PROJECT_ID=your_project_id 137 | ``` -------------------------------------------------------------------------------- /my-app/tests/playwright/baseline.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test' 2 | 3 | /** 4 | * Baseline Tests - Simple Color Mixer Application 5 | * 6 | * These tests verify basic structure and presence of UI elements. 7 | * Following TDD principles - tests should FAIL initially before implementation. 8 | */ 9 | 10 | test.describe('Color Mixer Baseline Tests', () => { 11 | test.beforeEach(async ({ page }) => { 12 | // Navigate to the app 13 | await page.goto('/') 14 | }) 15 | 16 | test('app loads and displays title', async ({ page }) => { 17 | // Test that the main heading is visible 18 | await expect(page.locator('h1')).toBeVisible() 19 | await expect(page.locator('h1')).toContainText('Color Mixer') 20 | 21 | // Test that the subtitle/description is present 22 | await expect(page.locator('p')).toContainText('Mix and match colors with RGB sliders') 23 | }) 24 | 25 | test('color preview element exists', async ({ page }) => { 26 | // Test that the main color preview card is visible 27 | const colorPreview = page.locator('[data-testid="color-preview"]') 28 | await expect(colorPreview).toBeVisible() 29 | 30 | // Test that it has a colored background (should have style attribute) 31 | await expect(colorPreview.locator('div')).toHaveAttribute('style', /background-color/) 32 | 33 | // Test that it has proper aria label for accessibility 34 | await expect(colorPreview.locator('div')).toHaveAttribute('aria-label', /Color preview/) 35 | }) 36 | 37 | test('all three RGB sliders exist', async ({ page }) => { 38 | // Test that RGB controls container exists 39 | const rgbControls = page.locator('[data-testid="rgb-controls"]') 40 | await expect(rgbControls).toBeVisible() 41 | 42 | // Test that each slider exists with proper data-testid 43 | await expect(page.locator('[data-testid="slider-r"]')).toBeVisible() 44 | await expect(page.locator('[data-testid="slider-g"]')).toBeVisible() 45 | await expect(page.locator('[data-testid="slider-b"]')).toBeVisible() 46 | 47 | // Test that slider labels are present 48 | await expect(page.locator('[data-testid="label-r"]')).toBeVisible() 49 | await expect(page.locator('[data-testid="label-g"]')).toBeVisible() 50 | await expect(page.locator('[data-testid="label-b"]')).toBeVisible() 51 | }) 52 | 53 | test('hex and RGB values are displayed', async ({ page }) => { 54 | // Test that color values container exists 55 | const colorValues = page.locator('[data-testid="color-values"]') 56 | await expect(colorValues).toBeVisible() 57 | 58 | // Test that HEX value badge exists and shows format 59 | const hexValue = page.locator('[data-testid="hex-value"]') 60 | await expect(hexValue).toBeVisible() 61 | await expect(hexValue).toContainText('HEX: #') 62 | 63 | // Test that RGB value badge exists and shows format 64 | const rgbValue = page.locator('[data-testid="rgb-value"]') 65 | await expect(rgbValue).toBeVisible() 66 | await expect(rgbValue).toContainText('RGB: rgb(') 67 | }) 68 | 69 | test('six color preset buttons exist', async ({ page }) => { 70 | // Test that preset grid container exists 71 | const presetGrid = page.locator('[data-testid="preset-grid"]') 72 | await expect(presetGrid).toBeVisible() 73 | 74 | // Test that all 6 preset buttons exist with proper data-testids 75 | await expect(page.locator('[data-testid="preset-red"]')).toBeVisible() 76 | await expect(page.locator('[data-testid="preset-green"]')).toBeVisible() 77 | await expect(page.locator('[data-testid="preset-blue"]')).toBeVisible() 78 | await expect(page.locator('[data-testid="preset-yellow"]')).toBeVisible() 79 | await expect(page.locator('[data-testid="preset-black"]')).toBeVisible() 80 | await expect(page.locator('[data-testid="preset-white"]')).toBeVisible() 81 | 82 | // Test that each preset button has a color indicator dot 83 | await expect(presetGrid.locator('span').first()).toBeVisible() 84 | }) 85 | 86 | test('UI elements have proper accessibility attributes', async ({ page }) => { 87 | // Test that main heading has proper hierarchy 88 | await expect(page.locator('h1')).toBeVisible() 89 | 90 | // Test that sliders have labels associated 91 | await expect(page.locator('label[for]').or(page.locator('label')).first()).toBeVisible() 92 | 93 | // Test that clickable elements are properly marked 94 | const hexBadge = page.locator('[data-testid="hex-value"]') 95 | await expect(hexBadge).toHaveClass(/cursor-pointer/) 96 | 97 | const rgbBadge = page.locator('[data-testid="rgb-value"]') 98 | await expect(rgbBadge).toHaveClass(/cursor-pointer/) 99 | }) 100 | 101 | test('page has proper responsive layout', async ({ page }) => { 102 | // Test that main container has max width constraint 103 | const mainContainer = page.locator('.max-w-2xl') 104 | await expect(mainContainer).toBeVisible() 105 | 106 | // Test that cards are properly structured 107 | const cards = page.locator('[class*="Card"]').or(page.locator('.card')).or(page.locator('div').filter({ hasText: /Color preview|RGB|Color Presets/ })) 108 | await expect(cards.first()).toBeVisible() 109 | 110 | // Test that grid layout exists for presets 111 | const presetGrid = page.locator('[data-testid="preset-grid"]') 112 | await expect(presetGrid).toHaveClass(/grid/) 113 | }) 114 | }) -------------------------------------------------------------------------------- /my-app/tests/stagehand/baseline-stagehand.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test' 2 | import { Stagehand } from '@browserbasehq/stagehand' 3 | import { z } from 'zod' 4 | 5 | /** 6 | * Baseline Stagehand Tests - Simple Color Mixer Application 7 | * 8 | * These tests demonstrate AI-powered element discovery vs data-testid selectors. 9 | * Showcases more maintainable and readable test code using natural language. 10 | */ 11 | 12 | test.describe('Color Mixer Baseline Tests - Stagehand', () => { 13 | let stagehand: Stagehand 14 | 15 | test.beforeAll(async () => { 16 | if (!process.env.OPENAI_API_KEY) { 17 | console.warn('OPENAI_API_KEY not found. Stagehand tests will fail.') 18 | } 19 | }) 20 | 21 | test.beforeEach(async () => { 22 | stagehand = new Stagehand({ 23 | env: 'LOCAL', 24 | modelName: 'gpt-4o', 25 | modelClientOptions: { 26 | apiKey: process.env.OPENAI_API_KEY, 27 | }, 28 | verbose: 1, 29 | }) 30 | 31 | await stagehand.init() 32 | await stagehand.page.goto('http://localhost:3000') 33 | }) 34 | 35 | test.afterEach(async () => { 36 | if (stagehand) { 37 | await stagehand.close() 38 | } 39 | }) 40 | 41 | test('app loads and displays title using AI element discovery', async () => { 42 | // ✨ Stagehand: Use natural language to find elements 43 | // Instead of: page.locator('h1') 44 | const titleInfo = await stagehand.page.extract({ 45 | instruction: "Get the main heading text and subtitle/description from the color mixer page", 46 | schema: z.object({ 47 | mainTitle: z.string().describe("The main heading text"), 48 | description: z.string().describe("The subtitle or description text") 49 | }) 50 | }) 51 | 52 | expect(titleInfo.mainTitle).toContain('Color Mixer') 53 | expect(titleInfo.description).toContain('Mix and match colors with RGB sliders') 54 | }) 55 | 56 | test('all three RGB sliders exist using AI discovery', async () => { 57 | const sliderInfo = await stagehand.page.extract({ 58 | instruction: "Describe the RGB color sliders, including their labels (Red, Green, Blue), if they are visible and their current values.", 59 | schema: z.object({ 60 | redSlider: z.string().describe("Description of the Red slider and its label in the form: Label: