> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stably.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Locator

> AI-assisted element location using Stably's Playwright SDK.

<Snippet file="ai-rules/install-sdk-rules.mdx" />

Writing reliable locators is often painful—users must inspect HTML, guess selectors, and tweak through trial and error. This becomes especially hard when dealing with dynamic lists or repeating elements.

`page.getLocatorsByAI(prompt)` lets you describe what you want in natural language (e.g., "all product cards in the carousel") and automatically returns stable locators.

<Note>
  **Why not vanilla Playwright?** Low-level `page.locator()` requires precise selectors that break when the DOM changes. On the other extreme, fully agentic [`agent.act()`](/stably-sdk/agent) is powerful but heavyweight for simple element selection. `getLocatorsByAI()` bridges the gap—giving you AI-assisted, self-healing locators that feel native to Playwright.
</Note>

## Built-in Autofix: The Key Advantage

The biggest pain point with traditional Playwright locators is **maintenance**. When developers refactor the UI, selectors break and tests fail—even though the functionality still works. With `getLocatorsByAI`, your tests **automatically adapt** to DOM changes because they target semantic intent, not brittle selectors.

<Tabs>
  <Tab title="Traditional Playwright">
    ```ts theme={null}
    // ❌ Fragile: breaks when class names or structure change
    test('add item to cart', async ({ page }) => {
      await page.goto('/products');

      // These selectors WILL break when:
      // - Designer renames .product-card to .item-card
      // - Dev wraps cards in a new container div
      // - Class becomes .ProductCard (React component)
      await page.locator('.product-grid .product-card').first().click();
      await page.locator('button.add-to-cart-btn').click();

      // Test fails → Manual fix required → Repeat forever
    });
    ```
  </Tab>

  <Tab title="With getLocatorsByAI">
    ```ts theme={null}
    // ✅ Resilient: adapts automatically to DOM changes
    test('add item to cart', async ({ page }) => {
      await page.goto('/products');

      // Describes WHAT you want, not WHERE it is
      const { locator: products } = await page.getLocatorsByAI(
        'product cards in the product listing'
      );
      await products.first().click();

      const { locator: addBtn } = await page.getLocatorsByAI(
        'the Add to Cart button'
      );
      await addBtn.click();

      // Class renamed? Structure changed? Still works.
    });
    ```
  </Tab>
</Tabs>

<Note>
  **Why does this work?** `getLocatorsByAI` uses the page's accessibility tree (aria snapshot) to understand elements semantically. As long as your UI remains accessible, the AI can locate elements by their **meaning** rather than their **implementation details**.

  Because aria snapshots are non-visual, prompts referencing colors, sizes, or positions (e.g., "the blue button" or "the large header") won't work. Describe elements by their role, label, or text content instead.
</Note>

## Installation

Install the Stably Playwright test integration and import it in place of the Playwright test runner:

```ts theme={null}
import { test, expect } from '@stablyai/playwright-test';
```

The `getLocatorsByAI` method is automatically available on all `page` objects.

You can also import types for TypeScript:

```ts theme={null}
import type {
  AIModel,
  GetLocatorsByAIOptions,
  GetLocatorsByAIResult
} from '@stablyai/playwright-test';
```

## getLocatorsByAI

Use prompt-based locator generation to find elements without writing fragile selectors.

### Basic Usage

```ts theme={null}
import { test, expect } from '@stablyai/playwright-test';

test('interact with product cards', async ({ page }) => {
  await page.goto('/products');

  const { locator, count, reason } = await page.getLocatorsByAI(
    'all product cards in the featured section'
  );

  // Use like any Playwright locator
  await expect(locator).toHaveCount(count);

  // Get all matching elements
  const cards = await locator.all();
  await cards[0].click();
});
```

### How It Works

`getLocatorsByAI` performs intelligent element location in three steps:

1. **Aria Snapshot**: Captures the page's accessibility tree to understand the semantic structure of the DOM.
2. **AI Analysis**: Evaluates your prompt against the aria snapshot to identify matching elements.
3. **Locator Generation**: Returns Playwright-native locators that can be used like any standard locator.

### Method Signature

```ts theme={null}
await page.getLocatorsByAI(
  prompt: string,
  options?: GetLocatorsByAIOptions
): Promise<GetLocatorsByAIResult>
```

**Parameters:**

* `prompt` - Natural language description of the element(s) to locate
* `options` - Optional configuration
  * `model` - AI model to use (see [Model Selection](#model-selection))

**Types:**

```ts theme={null}
interface GetLocatorsByAIOptions {
  model?: AIModel;
}

type AIModel =
  | "openai/o4-mini"
  | "google/gemini-3.1-pro-preview"
  | "google/gemini-3-flash-preview";

interface GetLocatorsByAIResult {
  locator: Locator;  // Playwright locator for matched elements
  count: number;     // Number of elements found
  reason: string;    // AI's explanation of how it found the elements
}
```

The returned `locator` behaves exactly like Playwright's native `Locator` object. Call `.all()` to get an array of individual locators for each matched element.

### Common Use Cases

**Locate Dynamic Lists**

```ts theme={null}
const { locator } = await page.getLocatorsByAI('all items in the shopping cart');
const items = await locator.all();
```

**Find Specific Elements by Role**

```ts theme={null}
const { locator } = await page.getLocatorsByAI('the submit button in the checkout form');
await locator.click();
```

**Target Elements by Position**

```ts theme={null}
const { locator } = await page.getLocatorsByAI('the third row in the users table');
await expect(locator).toBeVisible();
```

### Model Selection

You can specify which AI model to use. If not specified, the backend default is used.

```ts theme={null}
// Use Google's fast model
const { locator } = await page.getLocatorsByAI('the submit button', {
  model: 'google/gemini-3-flash-preview'
});

// Use OpenAI's reasoning model
const { locator } = await page.getLocatorsByAI('the third product card', {
  model: 'openai/o4-mini'
});
```

| Model                           | Provider | Characteristics     |
| ------------------------------- | -------- | ------------------- |
| `google/gemini-3-flash-preview` | Google   | Fast, efficient     |
| `google/gemini-3.1-pro-preview` | Google   | Most capable        |
| `openai/o4-mini`                | OpenAI   | Efficient reasoning |

## Best Practices

### One Locator Type Per Prompt

<Warning>
  While you *can* request multiple unrelated locators in a single prompt (e.g., "get all the circle buttons and the first two headers"), this is **discouraged**. Playwright's [strictness model](https://playwright.dev/docs/locators#strictness) recommends locators that uniquely identify target elements. Mixing unrelated elements in one locator can lead to unexpected behavior when the page changes.
</Warning>

```ts theme={null}
// ❌ Discouraged: multiple unrelated element types
const { locator } = await page.getLocatorsByAI('all the add buttons and the page title');

// ✅ Recommended: separate calls for different element types
const { locator: addButtons } = await page.getLocatorsByAI('all Add to Cart buttons');
const { locator: title } = await page.getLocatorsByAI('the page title');
```

### Use the `reason` Field

The `reason` field explains how the AI interpreted your prompt. Use it for debugging when locators don't match as expected. It's also attached to the test report.

```ts theme={null}
const { locator, count, reason } = await page.getLocatorsByAI('all product cards');

console.log(`Found ${count} elements`);
console.log(`AI reason: ${reason}`);
```

## Troubleshooting

**No elements found**

The AI couldn't locate elements matching your description. Refine your prompt to use semantic identifiers.

```ts theme={null}
// ❌ Too vague or visual
const { count } = await page.getLocatorsByAI('the buttons');
// count: 0 (or unexpected results)

// ✅ More specific and semantic
const { count } = await page.getLocatorsByAI('buttons with role "button" in the form');
```

**Too many elements matched**

The prompt is matching more elements than intended. Add constraints to narrow the selection.

```ts theme={null}
// ❌ Matches all links on the page
const { locator } = await page.getLocatorsByAI('links');

// ✅ Scoped to specific section
const { locator } = await page.getLocatorsByAI('links in the footer navigation');
```

**Playwright version error**

If you see an error about `_snapshotForAI` not being a function, you need to upgrade Playwright:

```bash theme={null}
npm install @playwright/test@latest
```

`getLocatorsByAI` requires Playwright v1.54.1 or higher.

## When to Use

* **Use `getLocatorsByAI`** when:
  * You need to locate elements in dynamic lists or repeated structures
  * Writing precise selectors is difficult due to complex or changing DOM
  * You want self-healing locators that adapt to minor DOM changes
  * You're prototyping tests and want to describe elements naturally

* **Use standard Playwright locators** when:
  * You have stable, unique selectors (IDs, data-testid attributes)
  * Performance is critical and AI overhead is not acceptable
  * The element structure is simple and unlikely to change

## References

* Stably Playwright SDK: `@stablyai/playwright-test`
* Playwright Locators: [Playwright Locator API](https://playwright.dev/docs/api/class-locator)
* Playwright Strictness: [Locator Strictness](https://playwright.dev/docs/locators#strictness)
