Playwright MCP Server

Playwright MCP servers enable AI models to perform cross-browser automation, modern web testing, accessibility testing, and end-to-end testing workflows using Playwright's powerful browser automation capabilities.

October 19, 2026
MCP ServerDevelopment Tools & DevOpsPlaywright MCP Server
GitHub starsnpm versionnpm downloads

Overview

The Playwright MCP Server enables AI models to perform browser automation using Microsoft's Playwright framework through the Model Context Protocol. Unlike traditional screenshot-based approaches, it uses structured accessibility snapshots for fast, deterministic, and LLM-friendly browser interactions.

Official & Community Implementations:

Official: Microsoft Playwright MCP - Fast, accessibility-tree based automation

Community: @executeautomation/playwright-mcp-server - Enhanced with code generation and API testing

Key Features

🌐

Cross-Browser Testing

Test across Chromium, Firefox, WebKit, and Microsoft Edge browsers

β™Ώ

Accessibility-First

Uses structured accessibility trees instead of pixel-based input

⚑

Fast & Lightweight

No vision models needed, operates on structured data

🎯

Test Code Generation

Record actions and generate Playwright test scripts automatically

Available Tools

Quick Reference

Official server (Microsoft)

ToolPurpose
browser_navigateNavigate to a URL
browser_snapshotCapture an accessibility snapshot of the current page
browser_clickPerform a click on a web page
browser_typeType text into an editable element
browser_fill_formFill multiple form fields
browser_evaluateEvaluate a JavaScript expression on the page or an element
browser_run_code_unsafeRun a Playwright code snippet (RCE-equivalent)
browser_take_screenshotTake a screenshot of the current page
browser_wait_forWait for text to appear or disappear, or a specified time to pass
browser_tabsList, create, close, or select a browser tab
browser_console_messagesReturn all console messages
browser_network_requestsReturn a numbered list of network requests since loading the page
browser_file_uploadUpload one or multiple files
browser_findSearch the accessibility snapshot for text or a regular expression

Community server (Execute Automation)

ToolPurposeCategory
playwright_navigateNavigate to URLNavigation
playwright_clickClick elementsInteraction
playwright_fillFill input fieldsForms
playwright_selectSelect dropdown optionsForms
playwright_screenshotCapture screenshotsTesting
playwright_evaluateExecute JavaScriptScripting
playwright_hoverHover over elementsInteraction
playwright_press_keyPress keyboard keysInteraction
playwright_dragDrag and drop elementsInteraction
playwright_upload_fileUpload filesForms
playwright_get_visible_textExtract visible textContent
playwright_get_visible_htmlGet page HTMLContent
playwright_console_logsRetrieve console logsDebugging
playwright_expect_responseMonitor HTTP responsesAPI Testing
start_codegen_sessionStart test recordingCode Gen
end_codegen_sessionGenerate test fileCode Gen

Detailed Usage

Community server tool names:

The examples in this section use the Execute Automation community server's playwright_* tool names. The official Microsoft server exposes the browser_* tools listed in Quick Reference above.

playwright_navigateβ–Ά

Navigate to a URL with configurable browser and viewport options.

// Basic navigation
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com"
  }
});

// Navigate with specific browser
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com",
    browserType: "firefox"  // chromium, firefox, webkit, msedge
  }
});

// Navigate with custom viewport
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com",
    width: 1920,
    height: 1080,
    headless: false
  }
});

Returns navigation status and page load confirmation.

playwright_clickβ–Ά

Click elements on the page using CSS selectors.

// Single click
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: "button.submit"
  }
});

// Double click
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: ".expandable-item",
    doubleClick: true
  }
});

// Click and switch to new tab
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click_and_switch_tab",
  arguments: {
    selector: "a[target='_blank']"
  }
});

// Click inside iframe
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_iframe_click",
  arguments: {
    iframeSelector: "#payment-iframe",
    selector: "button.pay-now"
  }
});

Returns click confirmation and element state.

playwright_screenshotβ–Ά

Capture screenshots of pages or specific elements.

// Viewport screenshot
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "homepage-view"
  }
});

// Full page screenshot
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "full-page",
    fullPage: true
  }
});

// Element screenshot
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "header",
    selector: "header.main-nav"
  }
});

// Save as PNG file
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "dashboard",
    savePng: true,
    downloadsDir: "./screenshots"
  }
});

Returns screenshot data or file path.

playwright_fillβ–Ά

Fill input fields, textareas, and contenteditable elements.

// Fill text input
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_fill",
  arguments: {
    selector: "input[name='email']",
    value: "[email protected]"
  }
});

// Fill password field
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_fill",
  arguments: {
    selector: "input[type='password']",
    value: "SecurePass123!"
  }
});

// Fill inside iframe
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_iframe_fill",
  arguments: {
    iframeSelector: "#contact-form-iframe",
    selector: "textarea#message",
    value: "Hello, this is a test message"
  }
});

Clears existing value before filling.

playwright_selectβ–Ά

Select options from dropdown menus.

// Select by value
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_select",
  arguments: {
    selector: "select[name='country']",
    value: "US"
  }
});

// Select by label
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_select",
  arguments: {
    selector: "select#language",
    value: "English"
  }
});

Returns selected option value.

playwright_evaluateβ–Ά

Execute JavaScript code in the browser context.

// Get page title
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_evaluate",
  arguments: {
    script: "document.title"
  }
});

// Extract data
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_evaluate",
  arguments: {
    script: `
      Array.from(document.querySelectorAll('.product'))
        .map(p => ({
          name: p.querySelector('.name').textContent,
          price: p.querySelector('.price').textContent
        }))
    `
  }
});

// Modify page
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_evaluate",
  arguments: {
    script: `
      document.querySelector('.modal').style.display = 'none';
      return 'Modal hidden';
    `
  }
});

Returns the result of the JavaScript expression.

playwright_upload_fileβ–Ά

Upload files to file input elements.

// Single file upload
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_upload_file",
  arguments: {
    selector: "input[type='file']",
    filePath: "/path/to/document.pdf"
  }
});

// Multiple file upload
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_upload_file",
  arguments: {
    selector: "input[type='file'][multiple]",
    filePath: [
      "/path/to/image1.jpg",
      "/path/to/image2.jpg"
    ]
  }
});

Returns upload confirmation.

playwright_get_visible_textβ–Ά

Extract all visible text from the page (excludes hidden elements).

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_get_visible_text",
  arguments: {}
});

Returns plain text content visible to users.

playwright_get_visible_htmlβ–Ά

Retrieve page HTML with filtering and cleaning options.

// Get clean HTML
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_get_visible_html",
  arguments: {
    removeScripts: true,
    removeComments: true,
    cleanHtml: true
  }
});

// Get minified HTML
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_get_visible_html",
  arguments: {
    minify: true
  }
});

Returns filtered HTML source code.

playwright_console_logsβ–Ά

Retrieve browser console logs with filtering options.

// Get all console logs
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_console_logs",
  arguments: {}
});

// Search for specific logs
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_console_logs",
  arguments: {
    search: "error",
    type: "error"
  }
});

// Get limited logs and clear
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_console_logs",
  arguments: {
    limit: 50,
    clear: true
  }
});

Returns array of console messages with types and timestamps.

start_codegen_sessionβ–Ά

Start recording browser actions to generate Playwright test scripts.

use_mcp_tool({
  server_name: "playwright",
  tool_name: "start_codegen_session",
  arguments: {
    outputPath: "./tests",
    testNamePrefix: "user-login",
    includeComments: true
  }
});

Returns session ID for tracking the recording.

end_codegen_sessionβ–Ά

Finalize recording and generate test file.

use_mcp_tool({
  server_name: "playwright",
  tool_name: "end_codegen_session",
  arguments: {
    sessionId: "session-123abc"
  }
});

Returns generated test file path and content.

playwright_expect_responseβ–Ά

Monitor and validate HTTP responses during browser interactions.

// Monitor API endpoint
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_expect_response",
  arguments: {
    id: "api-check-1",
    url: "https://api.example.com/users"
  }
});

// Assert response value
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_assert_response",
  arguments: {
    id: "api-check-1",
    url: "https://api.example.com/users",
    value: { status: 200 }
  }
});

Returns response data including status, headers, and body.

Installation

Microsoft Playwright MCP

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

With Browser Selection:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--browser", "firefox"
      ]
    }
  }
}

With Custom Configuration:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--browser", "chrome",
        "--viewport-size", "1280x720",
        "--user-data-dir", "./playwright-data"
      ]
    }
  }
}

Persistent Browser Profile:

Use --user-data-dir to persist login sessions and cookies across runs.

Configuration Options

Browser Selection

--browser <browser>    # chrome, firefox, webkit, msedge

Viewport Configuration

--viewport-size <size>       # e.g. "1280x720"

Device Emulation

--device <device>      # For example: "iPhone 15"

Network Settings

--allowed-origins <origins>  # Semicolon-separated trusted origins
--blocked-origins <origins>  # Semicolon-separated blocked origins
--proxy-server <url>         # Use proxy server

Session Management

--user-data-dir <path>       # Persistent browser profile
--isolated                   # Ephemeral sessions
--storage-state <file>       # Load cookies/localStorage

Recording & Debugging

--save-session               # Save the session into the output directory
--timeout-action <ms>        # Action timeout, default 5000
--timeout-navigation <ms>    # Navigation timeout, default 60000
--timeout-settle <ms>        # Settle wait after each action, default 500

Trace and video recording:

Trace and video recording are tools rather than CLI flags (--caps=devtools): browser_start_tracing / browser_stop_tracing and browser_start_video / browser_stop_video.

Common Use Cases

Community server tool names:

The examples in this section use the Execute Automation community server's playwright_* tool names.

1. End-to-End Testing

Automate complete user workflows:

// Navigate to login page
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://app.example.com/login"
  }
});

// Fill credentials
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_fill",
  arguments: {
    selector: "input[name='email']",
    value: "[email protected]"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_fill",
  arguments: {
    selector: "input[name='password']",
    value: "TestPass123!"
  }
});

// Submit form
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: "button[type='submit']"
  }
});

// Verify login success
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "dashboard-logged-in",
    fullPage: true
  }
});

2. Cross-Browser Testing

Test across multiple browsers:

// Test in Chromium
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com",
    browserType: "chromium"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "chromium-homepage"
  }
});

// Test in Firefox
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com",
    browserType: "firefox"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "firefox-homepage"
  }
});

// Test in WebKit (Safari)
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com",
    browserType: "webkit"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "webkit-homepage"
  }
});

3. Test Code Generation

Record actions and generate test scripts:

// Start recording
use_mcp_tool({
  server_name: "playwright",
  tool_name: "start_codegen_session",
  arguments: {
    outputPath: "./tests",
    testNamePrefix: "checkout-flow",
    includeComments: true
  }
});

// Perform actions (automatically recorded)
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://shop.example.com"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: ".add-to-cart"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: ".checkout-button"
  }
});

// Generate test file
use_mcp_tool({
  server_name: "playwright",
  tool_name: "end_codegen_session",
  arguments: {
    sessionId: "session-123"
  }
});

4. API Testing During Browser Interactions

Monitor network requests and validate responses:

// Navigate to page
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com/dashboard"
  }
});

// Monitor API call
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_expect_response",
  arguments: {
    id: "user-api",
    url: "https://api.example.com/users/me"
  }
});

// Trigger action that makes API call
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_click",
  arguments: {
    selector: ".refresh-data"
  }
});

// Assert API response
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_assert_response",
  arguments: {
    id: "user-api",
    value: { status: 200 }
  }
});

5. Visual Regression Testing

Capture screenshots for visual comparison:

// Baseline screenshot
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_navigate",
  arguments: {
    url: "https://example.com"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "baseline-homepage",
    fullPage: true,
    savePng: true,
    downloadsDir: "./screenshots/baseline"
  }
});

// Component screenshots
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "header",
    selector: "header",
    savePng: true,
    downloadsDir: "./screenshots/components"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_screenshot",
  arguments: {
    name: "footer",
    selector: "footer",
    savePng: true,
    downloadsDir: "./screenshots/components"
  }
});

6. Accessibility Testing

Test keyboard navigation and screen reader compatibility:

// Navigate using keyboard
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_press_key",
  arguments: {
    key: "Tab"
  }
});

use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_press_key",
  arguments: {
    key: "Enter"
  }
});

// Extract accessibility tree
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_evaluate",
  arguments: {
    script: `
      const snapshot = await window.accessibility.snapshot();
      return snapshot;
    `
  }
});

// Check for accessibility violations
use_mcp_tool({
  server_name: "playwright",
  tool_name: "playwright_console_logs",
  arguments: {
    search: "accessibility",
    type: "warning"
  }
});

Beyond testing: a signed-in browser as a general-purpose MCP

Most write-ups treat Playwright MCP as a testing tool. In daily use with several coding agents, it ended up doing more. Sign in to a web service once, and the agent can work with that service the same way you do. You don't need an MCP server, API key or OAuth app for each service.

How it works. By default, Playwright MCP keeps a persistent browser profile. Logins, cookies and site settings carry over from one session to the next. Sign in by hand once, change any one-time settings, and the agent picks up where you left off.

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest", "--user-data-dir", "~/.mcp-browser-profile"]
    }
  }
}

Patterns that held up in practice

1. Read data from pages you already have open. browser_evaluate runs JavaScript on the page, so you can pull out the content as structured JSON instead of a full snapshot. Some sites' terms forbid calling their unpublished internal endpoints, even from a page where you're signed in. Check the terms before making fetch() calls beyond what the page itself loads.

2. Extract structured data instead of reading snapshots. Return only the fields you need:

() => [...document.querySelectorAll('article')].map(a => ({
  title: a.querySelector('h2')?.textContent?.trim(),
  link: a.querySelector('a')?.href
}))

3. Run several steps in one call. browser_run_code_unsafe runs a Playwright function with page. A loop with waits, scrolling and downloads becomes one tool call instead of twenty clicks, each with its own snapshot.

async (page) => {
  const results = [];
  for (let i = 0; i < 5; i++) {
    await page.mouse.wheel(0, 2000);
    await page.waitForTimeout(800);
    results.push(await page.locator('article').count());
  }
  return results;
}

4. Drive web apps that have no API. Use browser_file_upload, browser_fill_form and browser_type to put work in, browser_wait_for while it runs, and then an evaluate call to collect the result.

5. Check your own dev servers. Open the page on localhost, then read browser_console_messages and browser_network_requests and take a screenshot. That catches errors that unit tests don't.

What we learned the hard way

  • In real use, most calls ran scripts. browser_evaluate and browser_run_code_unsafe made up about half of all calls; clicks and typing were a small share.
  • One profile can only be open in one browser at a time. Agents running at once can't share a signed-in profile. Give each its own --user-data-dir, or use --isolated, which doesn't keep your logins.
  • --isolated means you're signed out. That's good for testing, but this whole approach depends on being signed in.
  • Scripts against the page break without an error. When a site changes its markup, querySelectorAll returns an empty list instead of failing. Return counts, and treat an empty result as an error.
  • The agent can do anything you can do on those sites. browser_run_code_unsafe is, in the README's own words, "RCE-equivalent." A page that injects instructions into the agent's context can trigger actions with your logins. Keep the profile limited to services you're willing to give the agent.
  • Prefer small evaluate results over full snapshots. A short JSON result costs far fewer tokens than an accessibility snapshot of the same page.

Stay within the terms of service

Many web services ban automated access in their terms, even when it's your own account doing it. If they catch it, you can lose the account, not just get rate-limited. Before you point an agent at a service:

  • Read the terms first. Look for "automated means," "scraping," "bots" or "unofficial clients." If the terms forbid it, don't automate that service.
  • Use the official API or MCP when there is one. Use the browser only where no supported route exists.
  • Keep it to your own account and your own data. Don't collect other users' data in bulk, and don't share a signed-in profile with other people.
  • Work at human pace and volume. Leave delays between actions, keep sessions short, and don't run anything around the clock. If something would take a person hours of clicking, it's probably too much.
  • Don't get around protections. If you hit a CAPTCHA, a rate limit, a "suspicious activity" warning or a block, stop. Don't solve or bypass it with automation.
  • Leave paid limits and access controls alone. Don't use automation to exceed usage caps, unlock paid features or reach content you couldn't open by hand.
  • Don't automate account-level actions. Sign up, sign in, change security settings and complete any verification yourself.
  • Watch for warnings. An email about unusual activity, a forced logout or a new verification step means stop and back off.

Browser Support

BrowserEnginePlatformsMobile
ChromiumBlinkWindows, macOS, Linuxβœ“
FirefoxGeckoWindows, macOS, Linuxβœ—
WebKitWebKitmacOS, Linuxβœ“
Microsoft EdgeBlinkWindows, macOS, Linuxβœ—

Best Practices

  1. Use Accessibility Selectors: Leverage role, aria-label, and text selectors for robust tests
  2. Wait for Elements: Playwright auto-waits, but explicit waits improve reliability
  3. Isolate Tests: Use --isolated mode to prevent state leakage between tests
  4. Record Traces: Enable tracing for debugging failed tests
  5. Test Across Browsers: Run tests on Chromium, Firefox, and WebKit
  6. Screenshot on Failure: Capture screenshots when assertions fail
  7. Monitor Console: Check console logs for JavaScript errors
  8. Use Code Generation: Record complex flows to generate test code

Performance Tips

  1. Headless Mode: Use headless browsers for faster test execution
  2. Parallel Execution: Run tests in parallel across multiple browsers
  3. Reuse Browser Context: Share browser context across tests when possible
  4. Disable Resources: Block images/fonts for faster page loads in tests
  5. Set Timeouts: Configure appropriate timeouts for different operations
  6. Cache User Data: Use persistent profiles to avoid repeated logins

Troubleshooting

Common Issues

Issue: Browser fails to launch

  • Install Playwright browsers: npx playwright install
  • Check system dependencies: npx playwright install-deps
  • Verify Node.js version (18+ required)

Issue: Elements not found

  • Wait for element to be visible
  • Check selector syntax (CSS, text, role-based)
  • Verify element isn't in iframe or shadow DOM

Issue: Screenshots are blank

  • Ensure page has loaded completely
  • Check viewport size is appropriate
  • Verify element selector is correct

Issue: Timeouts occurring

  • Increase timeout with --timeout-action or --timeout-navigation
  • Check network connectivity
  • Verify page isn't stuck loading resources

Issue: Cross-browser differences

  • Test on actual target browsers
  • Use browser-specific selectors if needed
  • Check for browser-specific CSS/JS issues

Security Considerations

Security Warning:

Playwright can execute arbitrary JavaScript and access local files. Only use with trusted URLs and scripts.

Security Best Practices

  1. Validate URLs: Only navigate to trusted, verified URLs
  2. Sanitize Inputs: Validate all user-provided selectors and scripts
  3. Use Isolated Mode: Enable --isolated for untrusted content
  4. Don't Rely on Origin Filters: --allowed-origins / --blocked-origins only filter browser requests; per the README they are not a security boundary and do not affect redirects
  5. Review Scripts: Audit JavaScript executed via evaluate
  6. Secure Storage: Protect session storage and cookie files
  7. Container Isolation: Run in Docker for additional security

Limitations

  • Docker: Only supports headless Chromium in containers
  • File Uploads: Requires local file system access
  • Browser Extensions: Limited support for extensions
  • WebRTC: May have issues with real-time communication
  • Download Handling: Requires configuration for file downloads
  • iOS Safari: No direct iOS Safari testing (use WebKit as proxy)

Resources

Sources