# Web Tests

> Complete browser automation with Playwright. **ALWAYS use when user needs browser testing, E2E testing, screenshots, form testing, or responsive design validation.** Auto-detects dev servers, saves test scripts to working directory. Examples - "test this page", "take screenshots of responsive design", "test login flow", "check for broken links", "validate form submission".

- **Type:** Skill
- **Install:** `agentstack add skill-marcioaltoe-claude-craftkit-web-tests`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [marcioaltoe](https://agentstack.voostack.com/s/marcioaltoe)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [marcioaltoe](https://github.com/marcioaltoe)
- **Source:** https://github.com/marcioaltoe/claude-craftkit/tree/main/plugins/ui-tests/skills/web-tests

## Install

```sh
agentstack add skill-marcioaltoe-claude-craftkit-web-tests
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

**IMPORTANT - Path Resolution:**
This skill is installed globally but saves outputs to the user's working directory. Always pass `CWD` environment variable when executing commands to ensure outputs go to the correct location.

Common installation paths:

- Plugin system: `~/.claude/plugins/marketplaces/claude-craftkit/plugins/ui-tests/skills/web-tests`
- Manual global: `~/.claude/skills/web-tests`
- Project-specific: `/.claude/skills/web-tests`

# Web Testing & Browser Automation

General-purpose browser automation skill. I'll write custom Playwright code for any automation task you request and execute it via the universal executor.

**CRITICAL WORKFLOW - Follow these steps in order:**

1. **Auto-detect dev servers** - For localhost testing, ALWAYS run server detection FIRST:

   ```bash
   cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
   ```

   - If **1 server found**: Use it automatically, inform user
   - If **multiple servers found**: Ask user which one to test
   - If **no servers found**: Ask for URL or offer to help start dev server

2. **Write scripts to user's working directory** - Save to `.web-tests/scripts/test-*.js` in user's repo

3. **Use visible browser by default** - Always use `headless: false` unless user specifically requests headless mode

4. **Parameterize URLs** - Always make URLs configurable via constant at top of script

## How It Works

1. You describe what you want to test/automate
2. I auto-detect running dev servers (or ask for URL if testing external site)
3. I write custom Playwright code in `.web-tests/scripts/test-*.js` (in user's working directory)
4. I execute it via: `CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-*.js`
5. Results displayed in real-time, browser window visible for debugging
6. Screenshots automatically saved to `.web-tests/screenshots/`

## Setup (First Time)

```bash
cd $SKILL_DIR
npm run setup
```

This installs Playwright and Chromium browser. Only needed once.

## Execution Pattern

**Step 1: Detect dev servers (for localhost testing)**

```bash
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"
```

**Step 2: Write test script to user's .web-tests/scripts/ with URL parameter**

```javascript
// .web-tests/scripts/test-page.js
const { chromium } = require("playwright");

// Parameterized URL (detected or user-provided)
const TARGET_URL = "http://localhost:3001"; //  {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(TARGET_URL);
  console.log("Page loaded:", await page.title());

  await page.screenshot({
    path: ".web-tests/screenshots/page.png",
    fullPage: true,
  });
  console.log("📸 Screenshot saved to .web-tests/screenshots/page.png");

  await browser.close();
})();
```

**Step 3: Execute from skill directory with CWD**

```bash
CWD=$(pwd) cd $SKILL_DIR && node run.js $(pwd)/.web-tests/scripts/test-page.js
```

## Common Patterns

### Test a Page (Multiple Viewports)

```javascript
// .web-tests/scripts/test-responsive.js
const { chromium } = require("playwright");
const helpers = require("$SKILL_DIR/lib/helpers"); // Path will be resolved

const TARGET_URL = "http://localhost:3001"; // Auto-detected

(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 100 });
  const page = await browser.newPage();

  // Desktop test
  await page.setViewportSize({ width: 1920, height: 1080 });
  await page.goto(TARGET_URL);
  console.log("Desktop - Title:", await page.title());
  await helpers.takeScreenshot(page, "desktop"); // Saves to .web-tests/screenshots/

  // Mobile test
  await page.setViewportSize({ width: 375, height: 667 });
  await helpers.takeScreenshot(page, "mobile");

  await browser.close();
})();
```

### Test Login Flow

```javascript
// .web-tests/scripts/test-login.js
const { chromium } = require("playwright");

const TARGET_URL = "http://localhost:3001"; // Auto-detected

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(`${TARGET_URL}/login`);

  await page.fill('input[name="email"]', "test@example.com");
  await page.fill('input[name="password"]', "password123");
  await page.click('button[type="submit"]');

  // Wait for redirect
  await page.waitForURL("**/dashboard");
  console.log("✅ Login successful, redirected to dashboard");

  await browser.close();
})();
```

### Fill and Submit Form

```javascript
// .web-tests/scripts/test-form.js
const { chromium } = require("playwright");
const helpers = require("$SKILL_DIR/lib/helpers");

const TARGET_URL = "http://localhost:3001"; // Auto-detected

(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 50 });
  const page = await browser.newPage();

  await page.goto(`${TARGET_URL}/contact`);

  await page.fill('input[name="name"]', "John Doe");
  await page.fill('input[name="email"]', "john@example.com");
  await page.fill('textarea[name="message"]', "Test message");

  await helpers.takeScreenshot(page, "form-filled");
  await page.click('button[type="submit"]');

  // Verify submission
  await page.waitForSelector(".success-message");
  console.log("✅ Form submitted successfully");
  await helpers.takeScreenshot(page, "form-success");

  await browser.close();
})();
```

### Check for Broken Links

```javascript
// .web-tests/scripts/test-broken-links.js
const { chromium } = require("playwright");

const TARGET_URL = "http://localhost:3001";

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(TARGET_URL);

  const links = await page.locator('a[href^="http"]').all();
  const results = { working: 0, broken: [] };

  for (const link of links) {
    const href = await link.getAttribute("href");
    try {
      const response = await page.request.head(href);
      if (response.ok()) {
        results.working++;
      } else {
        results.broken.push({ url: href, status: response.status() });
      }
    } catch (e) {
      results.broken.push({ url: href, error: e.message });
    }
  }

  console.log(`✅ Working links: ${results.working}`);
  console.log(`❌ Broken links:`, results.broken);

  await browser.close();
})();
```

### Take Screenshot with Error Handling

```javascript
// .web-tests/scripts/screenshot-with-error-handling.js
const { chromium } = require("playwright");
const helpers = require("$SKILL_DIR/lib/helpers");

const TARGET_URL = "http://localhost:3001";

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  try {
    await page.goto(TARGET_URL, {
      waitUntil: "networkidle",
      timeout: 10000,
    });

    await helpers.takeScreenshot(page, "page-success");
    console.log("📸 Screenshot saved successfully");
  } catch (error) {
    console.error("❌ Error:", error.message);
    await helpers.takeScreenshot(page, "page-error");
  } finally {
    await browser.close();
  }
})();
```

### Test Responsive Design (Full)

```javascript
// .web-tests/scripts/test-responsive-full.js
const { chromium } = require("playwright");
const helpers = require("$SKILL_DIR/lib/helpers");

const TARGET_URL = "http://localhost:3001"; // Auto-detected

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  const viewports = [
    { name: "Desktop", width: 1920, height: 1080 },
    { name: "Tablet", width: 768, height: 1024 },
    { name: "Mobile", width: 375, height: 667 },
  ];

  for (const viewport of viewports) {
    console.log(
      `Testing ${viewport.name} (${viewport.width}x${viewport.height})`
    );

    await page.setViewportSize({
      width: viewport.width,
      height: viewport.height,
    });

    await page.goto(TARGET_URL);
    await page.waitForTimeout(1000);

    await helpers.takeScreenshot(page, viewport.name.toLowerCase());
  }

  console.log("✅ All viewports tested");
  await browser.close();
})();
```

## Inline Execution (Simple Tasks)

For quick one-off tasks, you can execute code inline without creating files:

```bash
# Take a quick screenshot
cd $SKILL_DIR && CWD=$(pwd) node run.js "
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3001');
await helpers.takeScreenshot(page, 'quick-test');
console.log('Screenshot saved');
await browser.close();
"
```

**When to use inline vs files:**

- **Inline**: Quick one-off tasks (screenshot, check if element exists, get page title)
- **Files**: Complex tests, responsive design checks, anything user might want to re-run

## Available Helpers

Optional utility functions in `lib/helpers.js`:

```javascript
const helpers = require("./lib/helpers");

// Detect running dev servers (CRITICAL - use this first!)
const servers = await helpers.detectDevServers();
console.log("Found servers:", servers);

// Safe click with retry
await helpers.safeClick(page, "button.submit", { retries: 3 });

// Safe type with clear
await helpers.safeType(page, "#username", "testuser");

// Take timestamped screenshot (auto-saves to .web-tests/screenshots/)
await helpers.takeScreenshot(page, "test-result");

// Handle cookie banners
await helpers.handleCookieBanner(page);

// Extract table data
const data = await helpers.extractTableData(page, "table.results");
```

See `lib/helpers.js` for full list.

## Directory Structure

When testing, the skill creates this structure in the user's working directory:

```
user-repo/
└── .web-tests/
    ├── scripts/          # Test scripts (reusable)
    │   ├── test-login.js
    │   ├── test-form.js
    │   ├── test-responsive.js
    │   └── test-broken-links.js
    └── screenshots/      # Screenshots with timestamps
        ├── desktop-2025-10-23T12-30-45.png
        ├── mobile-2025-10-23T12-30-51.png
        └── form-success-2025-10-23T12-31-05.png
```

## Tips

- **CRITICAL: Detect servers FIRST** - Always run `detectDevServers()` before writing test code for localhost testing
- **Save to .web-tests/** - Write scripts to `.web-tests/scripts/`, screenshots auto-save to `.web-tests/screenshots/`
- **Parameterize URLs** - Put detected/provided URL in a `TARGET_URL` constant at the top of every script
- **DEFAULT: Visible browser** - Always use `headless: false` unless user explicitly asks for headless mode
- **Headless mode** - Only use `headless: true` when user specifically requests "headless" or "background" execution
- **Slow down:** Use `slowMo: 100` to make actions visible and easier to follow
- **Wait strategies:** Use `waitForURL`, `waitForSelector`, `waitForLoadState` instead of fixed timeouts
- **Error handling:** Always use try-catch for robust automation
- **Console output:** Use `console.log()` to track progress and show what's happening
- **Use helpers:** The `helpers.takeScreenshot()` automatically saves to `.web-tests/screenshots/`

## Troubleshooting

**Playwright not installed:**

```bash
cd $SKILL_DIR && npm run setup
```

**Module not found:**
Ensure running from skill directory via `run.js` wrapper with CWD set

**Browser doesn't open:**
Check `headless: false` and ensure display available

**Element not found:**
Add wait: `await page.waitForSelector('.element', { timeout: 10000 })`

**Screenshots not in .web-tests/:**
Make sure `CWD` environment variable is set when executing

## Example Usage

```
User: "Test if the marketing page looks good"

Claude: I'll test the marketing page across multiple viewports. Let me first detect running servers...
[Runs: detectDevServers()]
[Output: Found server on port 3001]
I found your dev server running on http://localhost:3001

[Writes custom automation script to .web-tests/scripts/test-marketing.js with URL parameterized]
[Runs: CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-marketing.js]
[Shows results with screenshots from .web-tests/screenshots/]
```

```
User: "Check if login redirects correctly"

Claude: I'll test the login flow. First, let me check for running servers...
[Runs: detectDevServers()]
[Output: Found servers on ports 3000 and 3001]
I found 2 dev servers. Which one should I test?
- http://localhost:3000
- http://localhost:3001

User: "Use 3001"

[Writes login automation to .web-tests/scripts/test-login.js]
[Runs: CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-login.js]
[Reports: ✅ Login successful, redirected to /dashboard]
```

## Notes

- Each automation is custom-written for your specific request
- Not limited to pre-built scripts - any browser task possible
- Auto-detects running dev servers to eliminate hardcoded URLs
- Test scripts saved to `.web-tests/scripts/` for reusability
- Screenshots automatically saved to `.web-tests/screenshots/` with timestamps
- Code executes reliably with proper module resolution via `run.js`
- Works as global tool - no per-repo installation needed

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [marcioaltoe](https://github.com/marcioaltoe)
- **Source:** [marcioaltoe/claude-craftkit](https://github.com/marcioaltoe/claude-craftkit)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-marcioaltoe-claude-craftkit-web-tests
- Seller: https://agentstack.voostack.com/s/marcioaltoe
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
