E2

e2e-tests-studio

Mandatory tool for testing React UI changes by focusing on product workflows and user-centric behavior.

Install

mkdir -p .claude/skills/e2e-tests-studio && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4429" && unzip -o skill.zip -d .claude/skills/e2e-tests-studio && rm skill.zip

Installs to .claude/skills/e2e-tests-studio

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

REQUIRED when modifying any file in packages/playground-ui or packages/playground. Triggers on: React component creation/modification/refactoring, UI changes, new playground features, bug fixes affecting studio UI. Generates Playwright E2E tests that validate PRODUCT BEHAVIOR, not just UI states.
297 chars · catalog description✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Generate Playwright E2E tests for UI modifications
  • Validate product behavior instead of UI states
  • Enforce BDD structure with precondition groups
  • Verify data persistence after page reloads
  • Intercept API calls to verify payload integrity

How it works

It enforces a strict BDD structure where every test is nested within a 'when' precondition group and asserts a single observable product outcome.

Inputs & outputs

You give it
A UI component or feature modification
You get back
A Playwright E2E test file following BDD structure

When to use e2e-tests-studio

  • Testing agent provider configuration
  • Validating tool execution workflows
  • Verifying persistent state after UI changes

About this skill

E2E Behavior Validation for Frontend Modifications

Core Principle: Test Product Behavior, Not UI States

CRITICAL: Tests must verify that product features WORK correctly, not just that UI elements render.

What NOT to test (UI States):

  • ❌ "Dropdown opens when clicked"
  • ❌ "Modal appears after button click"
  • ❌ "Loading spinner shows during request"
  • ❌ "Form fields are visible"
  • ❌ "Sidebar collapses"

What TO test (Product Behavior):

  • ✅ "Selecting an LLM provider configures the agent to use that provider"
  • ✅ "Creating a new agent persists it and shows in the agents list"
  • ✅ "Running a tool with parameters returns the expected output"
  • ✅ "Chat messages stream correctly and maintain conversation context"
  • ✅ "Workflow execution triggers tools in the correct order"

BDD Structure (REQUIRED)

Every E2E spec MUST follow the same BDD shape as the MSW tests. In packages/playground, e2e-bdd/test-needs-when-describe enforces this shape.

The structure has exactly three levels:

  1. Outer test.describe = the unit under test (one page or feature per file).
  2. Inner test.describe('when …') = exactly ONE precondition. The title MUST start with when.
  3. Each test = exactly ONE observable outcome.
import { test, expect } from '@playwright/test';
import { resetStorage } from '../__utils__/reset-storage';

test.describe('Tools list page', () => {
  // the unit
  test.afterEach(async () => {
    await resetStorage();
  });

  test.describe('when a registered tool is clicked', () => {
    // ONE precondition (starts with "when")
    test('navigates to that tool detail page', async ({ page }) => {
      // ONE outcome
      await page.goto('/tools');
      await page.locator('text=Get current weather for a location').click();
      await expect(page).toHaveURL(/\/tools\/weatherInfo$/);
    });

    test('shows the tool name as the page heading', async ({ page }) => {
      // ONE outcome
      await page.goto('/tools');
      await page.locator('text=Get current weather for a location').click();
      await expect(page.locator('h2')).toHaveText('weatherInfo');
    });
  });
});

Rules:

  • One outer test.describe per file naming the unit.
  • Every leaf test lives inside a test.describe('when …') precondition group. No top-level flat test().
  • Split a multi-assertion test() only where assertions represent distinct outcomes; keep tightly-coupled assertions that prove a single outcome together. Never drop an assertion.
  • Place beforeEach/afterEach in the narrowest describe scope that needs them.

Prerequisites

Requires Playwright MCP server. If the browser_navigate tool is unavailable, instruct the user to add it:

claude mcp add playwright -- npx @playwright/mcp@latest

Step 1: Understand the Feature Intent

Before writing ANY test, answer these questions:

  1. What user problem does this feature solve?
  2. What is the expected outcome when the feature works correctly?
  3. What data flows through the system? (user input → API → state → UI)
  4. What should persist after page reload?
  5. What downstream effects should this action have?

Document these answers as comments in your test file.

Step 2: Build and Start

pnpm build:cli
cd packages/playground/e2e/kitchen-sink && pnpm dev

Verify server at http://localhost:4111

Step 3: Map Feature to Behavior Tests

Feature-to-Test Mapping Guide

Feature CategoryWhat to TestExample Assertion
Agent ConfigurationConfig changes affect agent behaviorSend message → verify response uses selected model
LLM Provider SelectionSelected provider is used in requestsIntercept API call → verify provider in request payload
Tool ExecutionTool runs with correct params & returns resultExecute tool → verify output matches expected transformation
Workflow ExecutionSteps execute in order, data flows between stepsRun workflow → verify each step's output feeds next step
Chat/StreamingMessages persist, context maintained across turnsMulti-turn conversation → verify context awareness
MCP Server ToolsServer tools are callable and return dataCall MCP tool → verify response structure and content
Memory/PersistenceData survives page reloadCreate item → reload → verify item exists
Error HandlingErrors surface correctly to userTrigger error condition → verify error message + recovery

Step 4: Write Behavior-Focused Tests

Test Structure Template

import { test, expect, Page } from '@playwright/test';
import { resetStorage } from '../__utils__/reset-storage';
import { selectFixture } from '../__utils__/select-fixture';
import { nanoid } from 'nanoid';

/**
 * FEATURE: [Name of feature]
 * USER STORY: As a user, I want to [action] so that [outcome]
 * BEHAVIOR UNDER TEST: [Specific behavior being validated]
 */

test.describe('[Feature Name] - Behavior Tests', () => {
  let page: Page;

  test.beforeEach(async ({ browser }) => {
    const context = await browser.newContext();
    page = await context.newPage();
  });

  test.afterEach(async () => {
    await resetStorage(page);
  });

  test.describe('when [the single precondition for these outcomes]', () => {
    test('[verb describing the single observable outcome]', async () => {
      // ARRANGE: Set up preconditions
      // - Navigate to the feature
      // - Configure any required state
      // ACT: Perform the user action that triggers the behavior
      // ASSERT: Verify the OUTCOME, not the UI state
      // - Check data persistence
      // - Verify downstream effects
      // - Confirm API calls made correctly
    });
  });
});

Behavior Test Patterns

Pattern 1: Configuration Affects Behavior

test.describe('when a different LLM provider is selected', () => {
  test('uses that provider for agent responses', async () => {
    // ARRANGE
    await page.goto('/agents/my-agent/chat');

    // Intercept API to verify provider
    let capturedProvider: string | null = null;
    await page.route('**/api/chat', route => {
      const body = JSON.parse(route.request().postData() || '{}');
      capturedProvider = body.provider;
      route.continue();
    });

    // ACT: Select a different provider
    await page.getByTestId('provider-selector').click();
    await page.getByRole('option', { name: 'OpenAI' }).click();

    // Send a message to trigger the agent
    await page.getByTestId('chat-input').fill('Hello');
    await page.getByTestId('send-button').click();

    // ASSERT: Verify the selected provider was used
    await expect.poll(() => capturedProvider).toBe('openai');
  });
});

Pattern 2: Data Persistence

test.describe('when a new agent is created', () => {
  test('persists after page reload', async () => {
    // ARRANGE
    await page.goto('/agents');
    const agentName = `Test Agent ${nanoid()}`;

    // ACT: Create new agent
    await page.getByTestId('create-agent-button').click();
    await page.getByTestId('agent-name-input').fill(agentName);
    await page.getByTestId('save-agent-button').click();

    // Wait for creation to complete
    await expect(page.getByText(agentName)).toBeVisible();

    // ASSERT: Verify persistence
    await page.reload();
    await expect(page.getByText(agentName)).toBeVisible({ timeout: 10000 });
  });
});

Pattern 3: Tool Execution Produces Correct Output

test.describe('when the weather tool is executed with a city', () => {
  test('returns formatted weather data for that city', async () => {
    // ARRANGE
    await selectFixture(page, 'weather-success');
    await page.goto('/tools/weather-tool');

    // ACT: Execute tool with parameters
    await page.getByTestId('param-city').fill('San Francisco');
    await page.getByTestId('execute-tool-button').click();

    // ASSERT: Verify OUTPUT content, not just that output appears
    const output = page.getByTestId('tool-output');
    await expect(output).toContainText('temperature');
    await expect(output).toContainText('San Francisco');

    // Verify structured data if applicable
    const outputText = await output.textContent();
    const outputData = JSON.parse(outputText || '{}');
    expect(outputData).toHaveProperty('temperature');
    expect(outputData).toHaveProperty('conditions');
  });
});

Pattern 4: Workflow Step Chaining

test.describe('when a multi-step workflow is run', () => {
  test('passes data between steps correctly', async () => {
    // ARRANGE
    await selectFixture(page, 'workflow-multi-step');
    const sessionId = nanoid();
    await page.goto(`/workflows/data-pipeline?session=${sessionId}`);

    // ACT: Trigger workflow execution
    await page.getByTestId('workflow-input').fill('test input data');
    await page.getByTestId('run-workflow-button').click();

    // ASSERT: Verify each step received correct input from previous step
    // Wait for completion
    await expect(page.getByTestId('workflow-status')).toHaveText('completed', { timeout: 30000 });

    // Check step outputs show data transformation chain
    const step1Output = await page.getByTestId('step-1-output').textContent();
    const step2Output = await page.getByTestId('step-2-output').textContent();

    // Verify step 2 received step 1's output as input
    expect(step2Output).toContain(step1Output);
  });
});

Pattern 5: Streaming Chat with Context

test.describe('when a multi-turn conversation is held', () 

---

*Content truncated.*

When not to use it

  • When testing simple UI visual states
  • When the feature does not involve product behavior

Prerequisites

Playwright MCP serverpackages/playground/e2e/kitchen-sink

Limitations

  • Requires Playwright MCP server availability
  • Strict BDD structure is mandatory for all tests

How it compares

It mandates testing product outcomes and data persistence, whereas standard E2E testing often focuses on verifying UI element visibility.

Compared to similar skills

e2e-tests-studio side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
e2e-tests-studio (this skill)16moReviewAdvanced
ui-ux-expert-skill919moReviewAdvanced
frontend-testing113moReviewIntermediate
feature-flags66moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry