claude-skills/engineering-team/playwright-pro/skills/pw/reference/fixtures.md
Reza Rezvani 1851c8fb09 fix(plugins): restructure 9 multi-skill domain plugins into ./skills/ layout
Same root cause as #587/#591 — Claude Code's runtime loader rejects
array-form skills paths like ["./content-production", "./ai-seo", ...]
even when each entry is a valid subdirectory containing SKILL.md.
`claude plugin validate` accepts them but the loader does not.

The proven canonical layout (used by self-improving-agent in #536):

  <plugin>/
  ├── .claude-plugin/plugin.json    skills: "./skills"
  └── skills/
      ├── <skill-1>/SKILL.md
      ├── <skill-2>/SKILL.md
      └── ...

Restructured 9 multi-skill domain plugins:
- business-growth (4 skills moved)
- c-level-advisor (28)
- engineering (36)
- engineering-team (32)
- finance (2)
- marketing-skill (43)
- product-team (12)
- project-management (8)
- ra-qm-team (13)

Also fixed standalone plugins that had root SKILL.md + ./skills/ subdir
(agenthub, autoresearch-agent, executive-mentor, playwright-pro). The
loader rejected them despite skills="./skills" because of the conflicting
root SKILL.md (compare self-improving-agent which works because PR #536
moved its root SKILL.md). Moved each root SKILL.md into ./skills/<name>/.

Restored standalone plugin folders to their original paths after the
multi-skill restructure swept them into parent skills/ directories
(marketplace.json source paths require original locations).

Removed 7 orphaned marketplace entries that pointed to skill folders
without their own plugin.json (content-creator, demand-gen,
fullstack-engineer, aws-architect, product-manager, scrum-master,
skill-security-auditor) — these were already non-functional.

Bumped patch versions on every changed plugin and synced
marketplace.json. Marketplace now lists 29 working plugins (down
from 36).

After merge: users run `/plugin marketplace update claude-code-skills`
followed by `/plugin update --all` to pick up the working layout.
2026-05-02 22:51:20 +02:00

3.1 KiB

Fixtures Reference

What Are Fixtures

Fixtures provide setup/teardown for each test. They replace beforeEach/afterEach for shared state and are composable, type-safe, and lazy (only run when used).

Creating Custom Fixtures

// fixtures.ts
import { test as base, expect } from '@playwright/test';

// Define fixture types
type MyFixtures = {
  authenticatedPage: Page;
  testUser: { email: string; password: string };
  apiClient: APIRequestContext;
};

export const test = base.extend<MyFixtures>({
  // Simple value fixture
  testUser: async ({}, use) => {
    await use({
      email: `test-${Date.now()}@example.com`,
      password: 'Test123!',
    });
  },

  // Fixture with setup and teardown
  authenticatedPage: async ({ page, testUser }, use) => {
    // Setup: log in
    await page.goto('/login');
    await page.getByLabel('Email').fill(testUser.email);
    await page.getByLabel('Password').fill(testUser.password);
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page).toHaveURL('/dashboard');

    // Provide the authenticated page to the test
    await use(page);

    // Teardown: clean up (optional)
    await page.goto('/logout');
  },

  // API client fixture
  apiClient: async ({ playwright }, use) => {
    const context = await playwright.request.newContext({
      baseURL: 'http://localhost:3000',
      extraHTTPHeaders: {
        Authorization: `Bearer ${process.env.API_TOKEN}`,
      },
    });
    await use(context);
    await context.dispose();
  },
});

export { expect };

Using Fixtures in Tests

import { test, expect } from './fixtures';

test('should show dashboard for logged in user', async ({ authenticatedPage }) => {
  // authenticatedPage is already logged in
  await expect(authenticatedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

test('should create item via API', async ({ apiClient }) => {
  const response = await apiClient.post('/api/items', {
    data: { name: 'Test Item' },
  });
  expect(response.ok()).toBeTruthy();
});

Shared Auth State (storageState)

For performance, authenticate once and reuse:

// auth.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('admin@example.com');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('/dashboard');
  await page.context().storageState({ path: '.auth/user.json' });
});
// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: {
        storageState: '.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

When to Use What

Need Use
Shared login state storageState + setup project
Per-test data creation Custom fixture with API calls
Reusable page helpers Custom fixture returning page
Test data cleanup Fixture teardown (after use())
Config values Simple value fixture