Endpoint: https://playwright.val.run
AI-powered service that fetches any URL's DOM and generates Playwright-native locators following the official Playwright locator priority. Each element gets its top 3 ranked locators (best → fallback).
Follows the official Playwright recommendation:
| Rank | Method | When Used | Confidence |
|---|---|---|---|
| 1 | getByRole() | ARIA role + accessible name — always first | High |
| 2 | getByLabel() | Form fields with associated <label> | High |
| 3 | getByPlaceholder() | Inputs with placeholder text | High |
| 4 | getByText() | Visible text content | Medium |
| 5 | getByAltText() | Images with alt attribute | High |
| 6 | getByTitle() | Elements with title attribute | Medium |
| 7 | getByTestId() | data-testid / data-cy attributes | High |
| 8 | locator() CSS | Short CSS only — #id, [attr], max 2 levels | Low |
| 9 | locator() XPath | Absolute last resort | Low |
Deep nested selectors like html > body > div > div > div > ... are never generated.
Pass your API key to get model-analyzed, context-aware selectors:
curl -X POST https://playwright.val.run \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "apiKey": "your_zai_api_key", "apiUrl": "https://api.z.ai/api/anthropic/v1/messages", "model": "anthropic/claude-sonnet-4-20250514" }'
The AI analyzes each element's role, context, and surrounding DOM to pick the most resilient locators. It understands when getByRole('button', { name: 'Submit' }) is better than getByText('Submit') because the text might appear elsewhere.
If no apiKey is provided, the service uses deterministic heuristics — still following Playwright's priority order, but without contextual reasoning:
curl -X POST https://playwright.val.run \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com"}'
Instead of passing credentials in the body, you can set these env vars on the val:
| Env Var | Purpose | Default |
|---|---|---|
ANTHROPIC_API_KEY | Z.AI / Anthropic API key | — |
ANTHROPIC_URL | API endpoint | https://api.z.ai/api/anthropic/v1/messages |
ANTHROPIC_MODEL | Model identifier | anthropic/claude-sonnet-4-20250514 |
Body params override env vars.
Health check.
{ "status": "ok", "service": "playwright-selector-gen" }
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Page URL to analyze |
filter | string[] | No | Only return elements matching these tags |
apiKey | string | No | Anthropic / Z.AI API key (enables AI mode) |
apiUrl | string | No | API endpoint URL |
model | string | No | Model identifier |
{ "url": "https://example.com", "totalElements": 5, "elements": [ { "tag": "a", "text": "Learn more", "role": "link", "attributes": { "href": "https://iana.org/domains/example" }, "locators": [ { "method": "getByRole", "selector": "role=link[name=\"Learn more\"]", "playwrightCode": "page.getByRole('link', { name: 'Learn more' })", "confidence": "high" }, { "method": "getByText", "selector": "text=\"Learn more\"", "playwrightCode": "page.getByText('Learn more', { exact: true })", "confidence": "medium" }, { "method": "locator:css", "selector": "a[href=\"https://iana.org/domains/example\"]", "playwrightCode": "page.locator('a[href=\"https://iana.org/domains/example\"]')", "confidence": "medium" } ] } ] }
import { test, expect } from '@playwright/test';
test('example page has working link', async ({ page }) => {
await page.goto('https://example.com');
// Best: getByRole (rank 1)
await page.getByRole('link', { name: 'Learn more' }).click();
// Fallback: getByText (rank 4)
await page.getByText('Learn more', { exact: true }).click();
// Last resort: CSS locator (rank 8)
await page.locator('a[href="https://iana.org/domains/example"]').click();
});
- Fetch — The endpoint fetches the target URL's HTML with a browser-like User-Agent.
- Parse — DOM is parsed and walked. Interactive elements (
a,button,input,select, etc.), landmarks (nav,header,main), headings, images, and elements with ARIA/test attributes are extracted. - Enrich — Each element gets its implicit ARIA role, accessible name (from
aria-label, associated<label>,alt,placeholder, or text content), and relevant attributes. - Generate — If an API key is provided, the element data is sent to the AI model which produces the best 3 Playwright locators per element using full contextual understanding. Without an API key, a deterministic algorithm applies Playwright's priority chain.
- Return — Top 3 locators per element, ranked best → fallback, each with a ready-to-paste
playwrightCodestring.
Interactive: a, button, input, select, textarea, form, label, details, summary, dialog
Landmarks: nav, header, footer, main, aside, section, article
Content: img, h1–h6, table
By attribute: Any element with role, data-testid, data-test-id, data-cy, aria-label, tabindex, or contenteditable
Capped at 300 elements per request.
| Status | Meaning |
|---|---|
400 | Missing url in request body |
405 | Method not allowed (use POST) |
502 | Target URL could not be fetched |
500 | Internal / AI API error |