Endpoint: https://playwright.val.run
AI-powered service that fetches any URL's DOM and generates Playwright-native locators. The AI model does all selector generation — there is zero algorithmic/semantic logic in the code itself. The code only fetches HTML, extracts raw DOM context, and passes it to the model.
Every POST request requires the X-Api-Key header matching the X_API_KEY environment variable set on the val.
curl -X POST https://playwright.val.run \ -H "Content-Type: application/json" \ -H "X-Api-Key: your_endpoint_key" \ -d '{"url": "https://example.com"}'
Requests without a valid key receive 401 Unauthorized.
Set these on the val (all required):
| Env Var | Purpose |
|---|---|
X_API_KEY | Protects the endpoint from unauthorized access |
ANTHROPIC_API_KEY | Z.AI / Anthropic API key for the model |
ANTHROPIC_URL | API endpoint (default: https://api.z.ai/api/anthropic/v1/messages) |
ANTHROPIC_MODEL | Model identifier (default: anthropic/claude-sonnet-4-20250514) |
Health check.
{ "status": "ok", "service": "playwright-selector-gen" }
Headers:
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | Endpoint access key |
Content-Type | Yes | application/json |
Body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Page URL to analyze |
filter | string[] | No | Only return elements matching these tags |
{ "url": "https://example.com", "totalElements": 5, "elements": [ { "index": 0, "tag": "h1", "locators": [ { "method": "getByRole", "playwrightCode": "page.getByRole('heading', { name: 'Example Domain' })", "confidence": "high" }, { "method": "getByText", "playwrightCode": "page.getByText('Example Domain', { exact: true })", "confidence": "medium" } ] }, { "index": 1, "tag": "a", "locators": [ { "method": "getByRole", "playwrightCode": "page.getByRole('link', { name: 'Learn more' })", "confidence": "high" }, { "method": "getByText", "playwrightCode": "page.getByText('Learn more', { exact: true })", "confidence": "medium" }, { "method": "locator:css", "playwrightCode": "page.locator('a[href=\"https://iana.org/domains/example\"]')", "confidence": "medium" } ] } ] }
The AI model follows the official Playwright locator recommendation:
| Rank | Method | Used For |
|---|---|---|
| 1 | getByRole() | ARIA role + accessible name |
| 2 | getByLabel() | Form fields with associated <label> |
| 3 | getByPlaceholder() | Inputs with placeholder text |
| 4 | getByText() | Visible text content |
| 5 | getByAltText() | Images with alt attribute |
| 6 | getByTitle() | Elements with title attribute |
| 7 | getByTestId() | data-testid / data-cy |
| 8 | locator() CSS | Short CSS only — absolute last resort |
| 9 | locator() XPath | Only for truly complex DOM traversal |
Deep nested selectors are never generated. Each element gets up to 3 locators ranked best → fallback.
POST { url } + X-Api-Key
│
▼
┌─ Fetch target URL HTML ─┐
│ │
├─ Parse DOM (deno_dom) │
│ │
├─ Extract raw elements: │
│ tag, attributes, │
│ outerHTML snippet, │
│ text content │
│ │
│ (NO selector logic) │
└──────────┬───────────────┘
│
▼
┌─ AI Model (Z.AI) ───────┐
│ │
│ Receives raw DOM data. │
│ Generates ALL locators. │
│ Ranks by Playwright │
│ priority. Returns JSON. │
│ │
└──────────┬───────────────┘
│
▼
JSON response
The code contains no selector generation logic whatsoever. It extracts raw DOM context (tag names, attributes, outer HTML snippets, text content) and sends it to the AI model. The model produces every locator.
Interactive: a, button, input, select, textarea, form, label, details, summary, dialog
Landmarks: nav, header, footer, main, aside, section, article
Content: img, h1–h6, table, th, td, tr, ul, ol, li, span, p, div
By attribute: Any element with role, data-testid, data-test-id, data-cy, aria-label, tabindex, or contenteditable
Capped at 300 elements per request. Elements are batched in groups of 30 for the AI call.
| Status | Meaning |
|---|---|
401 | Missing or invalid X-Api-Key header |
400 | Missing url in request body |
405 | Method not allowed (use POST) |
500 | ANTHROPIC_API_KEY not configured / AI error |
502 | Target URL could not be fetched |