claude-skills/engineering-team/playwright-pro/skills/pw/reference/common-pitfalls.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

Common Pitfalls (Top 10)

1. waitForTimeout

Symptom: Slow, flaky tests.

// BAD
await page.waitForTimeout(3000);

// GOOD
await expect(page.getByTestId('result')).toBeVisible();

2. Non-Web-First Assertions

Symptom: Assertions fail on dynamic content.

// BAD — checks once, no retry
const text = await page.textContent('.msg');
expect(text).toBe('Done');

// GOOD — retries until timeout
await expect(page.getByText('Done')).toBeVisible();

3. Missing await

Symptom: Random passes/failures, tests seem to skip steps.

// BAD
page.goto('/dashboard');
expect(page.getByText('Welcome')).toBeVisible();

// GOOD
await page.goto('/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();

4. Hardcoded URLs

Symptom: Tests break in different environments.

// BAD
await page.goto('http://localhost:3000/login');

// GOOD — uses baseURL from config
await page.goto('/login');

5. CSS Selectors Instead of Roles

Symptom: Tests break after CSS refactors.

// BAD
await page.click('#submit-btn');

// GOOD
await page.getByRole('button', { name: 'Submit' }).click();

6. Shared State Between Tests

Symptom: Tests pass alone, fail in suite.

// BAD — test B depends on test A
let userId: string;
test('create user', async () => { userId = '123'; });
test('edit user', async () => { /* uses userId */ });

// GOOD — each test is independent
test('edit user', async ({ request }) => {
  const res = await request.post('/api/users', { data: { name: 'Test' } });
  const { id } = await res.json();
  // ...
});

7. Using networkidle

Symptom: Tests hang or timeout unpredictably.

// BAD — waits for all network activity to stop
await page.goto('/dashboard', { waitUntil: 'networkidle' });

// GOOD — wait for specific content
await page.goto('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();

8. Not Waiting for Navigation

Symptom: Assertions run on wrong page.

// BAD — click navigates but we don't wait
await page.getByRole('link', { name: 'Settings' }).click();
await expect(page.getByRole('heading')).toHaveText('Settings');

// GOOD — wait for URL change
await page.getByRole('link', { name: 'Settings' }).click();
await expect(page).toHaveURL('/settings');
await expect(page.getByRole('heading')).toHaveText('Settings');

9. Testing Implementation, Not Behavior

Symptom: Tests break on every refactor.

// BAD — tests CSS class (implementation detail)
await expect(page.locator('.btn')).toHaveClass('btn-primary active');

// GOOD — tests what the user sees
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();

10. No Error Case Tests

Symptom: App breaks on errors but all tests pass.

// Missing: what happens when the API fails?
test('should handle API error', async ({ page }) => {
  await page.route('**/api/data', (route) =>
    route.fulfill({ status: 500 })
  );
  await page.goto('/dashboard');
  await expect(page.getByText(/error|try again/i)).toBeVisible();
});