playwright-genie 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +404 -347
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,435 +1,492 @@
1
- # playwright-nlp-locator
1
+ # playwright-genie
2
2
 
3
- > 🎯 Find Playwright locators using natural language descriptions powered by LLM
3
+ > Find and interact with Playwright elements using natural language powered by any LLM
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/playwright-nlp-locator.svg)](https://www.npmjs.com/package/playwright-nlp-locator)
5
+ [![npm version](https://img.shields.io/npm/v/playwright-genie.svg)](https://www.npmjs.com/package/playwright-genie)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **playwright-nlp-locator** enables you to write browser automation scripts using plain English instead of manually inspecting elements and writing complex selectors. Simply describe the element you want to interact with, and the library will find the best Playwright locator for it.
8
+ **playwright-genie** lets you write Playwright tests in plain English. No more hunting for selectors just describe the element and the genie finds it.
9
9
 
10
- ## Features
10
+ ## Features
11
11
 
12
- - 🗣️ **Natural Language Descriptions** - Describe elements in plain English
13
- - 🎯 **Smart Locator Generation** - Returns optimal locators (role-based > text-based > CSS)
14
- - 📊 **Confidence Scores** - Know how confident the match is
15
- - 🔄 **Alternative Suggestions** - Get multiple locator options
16
- - 🛠️ **TypeScript Support** - Full type definitions included
17
- - 🤖 **LLM-Powered** - Uses Abacus.AI APIs for intelligent matching
18
- - 📦 **Production Ready** - Error handling, edge cases, and best practices
12
+ - **Natural Language** describe elements in plain English, no selectors needed
13
+ - **40+ Playwright Actions** click, fill, check, hover, drag, wait, screenshot and more
14
+ - **Any LLM Provider** OpenAI, Claude, Ollama, Azure, or any OpenAI-compatible API
15
+ - **Smart Caching** — persistent disk cache + in-memory cache to minimize LLM calls
16
+ - **Action-Aware** `fill('username')` targets the input, not the label
17
+ - **Auto-Retry** stale cached locators are automatically re-resolved
18
+ - **TypeScript Support** full type definitions included
19
+ - **Single Page Object** — one `createSmartLocator(page)` works across all navigations
19
20
 
20
- ## 📦 Installation
21
+ ## Installation
21
22
 
22
23
  ```bash
23
- npm install playwright-nlp-locator playwright
24
+ npm install playwright-genie
24
25
  ```
25
26
 
26
27
  ### Prerequisites
27
28
 
28
- 1. **Playwright** - The library works with Playwright
29
- 2. **Python 3** - Required for LLM API calls
30
- 3. **Abacus.AI Python SDK** - Install with `pip install abacusai`
31
- 4. **Abacus.AI API Key** - Set the `ABACUS_API_KEY` environment variable
29
+ - **Node.js** >= 18
30
+ - **Playwright** >= 1.40
31
+ - An **LLM API key** (OpenAI, Anthropic, or any OpenAI-compatible provider)
32
32
 
33
- ```bash
34
- pip install abacusai
35
- export ABACUS_API_KEY="your-api-key-here"
33
+ ## Setup
34
+
35
+ Create a `.env` file in your project root:
36
+
37
+ ```env
38
+ # Option 1: OpenAI
39
+ LLM_API_KEY=sk-your-openai-key
40
+ LLM_MODEL=gpt-4o-mini
41
+
42
+ # Option 2: Anthropic (via OpenAI-compatible proxy)
43
+ LLM_API_KEY=your-anthropic-key
44
+ LLM_BASE_URL=https://your-proxy.com/v1
45
+ LLM_MODEL=claude-sonnet-4-20250514
46
+
47
+ # Option 3: Ollama (local, free)
48
+ LLM_API_KEY=ollama
49
+ LLM_BASE_URL=http://localhost:11434/v1
50
+ LLM_MODEL=llama3
51
+
52
+ # Option 4: Azure OpenAI
53
+ LLM_API_KEY=your-azure-key
54
+ LLM_BASE_URL=https://your-resource.openai.azure.com/openai/deployments/your-deployment
55
+ LLM_MODEL=gpt-4o-mini
56
+ ```
57
+
58
+ Also supports `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or `ROUTELLM_API_KEY` as fallbacks.
59
+
60
+ ## Quick Start
61
+
62
+ ### With Playwright Test
63
+
64
+ ```js
65
+ import { test } from '@playwright/test';
66
+ import { createSmartLocator } from 'playwright-genie';
67
+
68
+ test('login flow', async ({ page }) => {
69
+ const smart = createSmartLocator(page);
70
+
71
+ await page.goto('https://myapp.com/login');
72
+ await smart.fill('username', 'admin');
73
+ await smart.fill('password', 'secret123');
74
+ await smart.click('login button');
75
+ await smart.waitForVisible('welcome heading');
76
+ });
77
+ ```
78
+
79
+ ### Standalone Script
80
+
81
+ ```js
82
+ import { chromium } from 'playwright';
83
+ import { createSmartLocator } from 'playwright-genie';
84
+
85
+ const browser = await chromium.launch();
86
+ const page = await browser.newPage();
87
+ const smart = createSmartLocator(page);
88
+
89
+ await page.goto('https://myapp.com');
90
+ await smart.click('sign in link');
91
+ await smart.fill('email field', 'user@example.com');
92
+ await smart.fill('password field', 'secret');
93
+ await smart.click('submit button');
94
+
95
+ await browser.close();
36
96
  ```
37
97
 
38
- ## 🚀 Quick Start
98
+ ## API Reference
99
+
100
+ ### `createSmartLocator(page, options?)`
101
+
102
+ Creates a smart locator instance bound to a Playwright page. Works across navigations — no need to recreate it.
103
+
104
+ ```js
105
+ const smart = createSmartLocator(page, { verbose: true });
106
+ ```
107
+
108
+ **Options:**
109
+
110
+ | Option | Type | Default | Description |
111
+ |--------|------|---------|-------------|
112
+ | `verbose` | `boolean` | `false` | Log resolved locators to console |
113
+ | `debug` | `boolean` | `false` | Enable detailed debug logging |
114
+ | `model` | `string` | env var | Override LLM model |
115
+ | `temperature` | `number` | `0` | LLM temperature |
116
+ | `maxTokens` | `number` | `1024` | Max response tokens |
39
117
 
40
- ```javascript
41
- const { chromium } = require('playwright');
42
- const { findLocator, createNLPHelper } = require('playwright-nlp-locator');
118
+ ---
43
119
 
44
- async function main() {
45
- const browser = await chromium.launch();
46
- const page = await browser.newPage();
47
- await page.goto('https://example.com');
120
+ ### Interaction Actions
48
121
 
49
- // Option 1: Get the locator string
50
- const result = await findLocator(page, 'Login Button');
51
- console.log(result.locator); // e.g., "page.getByRole('button', { name: 'Login' })"
52
- console.log(result.confidence); // e.g., 0.95
122
+ ```js
123
+ await smart.click('login button');
124
+ await smart.click('submit', { force: true });
53
125
 
54
- // Option 2: Use the NLP Helper for direct interaction
55
- const nlp = createNLPHelper(page);
56
- await nlp.click('Submit button');
57
- await nlp.type('Email input field', 'user@example.com');
58
- await nlp.type('Password field', 'secret123');
126
+ await smart.dblclick('editable cell');
59
127
 
60
- await browser.close();
61
- }
128
+ await smart.fill('username', 'Admin');
129
+ await smart.fill('email field', 'a@b.com', { timeout: 5000 });
62
130
 
63
- main();
131
+ await smart.type('search box', 'hello');
132
+
133
+ await smart.pressSequentially('otp input', '123456', { delay: 100 });
134
+
135
+ await smart.press('search box', 'Enter');
136
+
137
+ await smart.clear('email field');
138
+
139
+ await smart.hover('profile menu');
140
+
141
+ await smart.focus('first input');
142
+
143
+ await smart.tap('mobile menu icon');
144
+
145
+ await smart.select('country dropdown', 'India');
146
+
147
+ await smart.selectText('paragraph content');
64
148
  ```
65
149
 
66
- ## 📖 API Reference
150
+ ---
151
+
152
+ ### Checkbox & Radio
153
+
154
+ ```js
155
+ await smart.check('remember me checkbox');
156
+
157
+ await smart.uncheck('newsletter opt-in');
158
+
159
+ await smart.setChecked('terms checkbox', true);
160
+ ```
67
161
 
68
- ### `findLocator(page, description, options?)`
162
+ ---
69
163
 
70
- Find a Playwright locator using natural language description.
164
+ ### File Upload
71
165
 
72
- **Parameters:**
73
- - `page` - Playwright Page object
74
- - `description` - Natural language description of the element
75
- - `options` - Optional configuration object
166
+ ```js
167
+ await smart.setInputFiles('file upload', '/path/to/file.pdf');
168
+ await smart.setInputFiles('avatar input', ['/img1.png', '/img2.png']);
169
+ ```
76
170
 
77
- **Returns:** `Promise<FindLocatorResult>`
171
+ ---
78
172
 
79
- ```javascript
80
- const result = await findLocator(page, 'email input field');
173
+ ### Drag & Drop
81
174
 
82
- // Result object:
83
- {
84
- found: true,
85
- locator: "page.getByPlaceholder('Enter your email')",
86
- locatorType: 'placeholder',
87
- confidence: 0.92,
88
- explanation: 'Input field with placeholder text indicating email entry',
89
- alternatives: [
90
- { locator: "page.locator('#email')", confidence: 0.85 }
91
- ],
92
- elementIndex: 3
93
- }
175
+ ```js
176
+ const { source, target } = await smart.dragTo('card item', 'drop zone');
94
177
  ```
95
178
 
96
- ### `getLocator(page, description, options?)`
179
+ ---
180
+
181
+ ### Wait Actions
182
+
183
+ ```js
184
+ await smart.waitForVisible('success toast');
185
+ await smart.waitForVisible('modal', 10000);
186
+
187
+ await smart.waitForHidden('loading spinner');
97
188
 
98
- Get an actual Playwright Locator object ready for interaction.
189
+ await smart.waitForAttached('dynamic table');
99
190
 
100
- ```javascript
101
- const { locator, result } = await getLocator(page, 'Submit button');
191
+ await smart.waitForDetached('old modal');
102
192
 
103
- // Use the locator directly
104
- await locator.click();
105
- await locator.fill('text');
106
- const text = await locator.textContent();
193
+ await smart.waitFor('element', { state: 'visible', timeout: 5000 });
107
194
  ```
108
195
 
109
- ### `findAllLocators(page, description, options?)`
196
+ ---
197
+
198
+ ### State Queries
199
+
200
+ ```js
201
+ const visible = await smart.isVisible('error message');
202
+ const hidden = await smart.isHidden('loading spinner');
203
+ const enabled = await smart.isEnabled('submit button');
204
+ const disabled = await smart.isDisabled('locked field');
205
+ const checked = await smart.isChecked('terms checkbox');
206
+ const editable = await smart.isEditable('readonly field');
207
+ const found = await smart.exists('optional element');
208
+ ```
110
209
 
111
- Find multiple matching elements for a description.
210
+ ---
112
211
 
113
- ```javascript
114
- const matches = await findAllLocators(page, 'navigation links');
212
+ ### Content Retrieval
115
213
 
116
- // Returns array of matches:
117
- [
118
- { locator: "page.getByRole('link', { name: 'Home' })", confidence: 0.90, type: 'role' },
119
- { locator: "page.getByRole('link', { name: 'About' })", confidence: 0.88, type: 'role' }
120
- ]
214
+ ```js
215
+ const text = await smart.getText('welcome heading');
216
+ const inner = await smart.getInnerText('article body');
217
+ const html = await smart.getInnerHTML('rich content area');
218
+ const value = await smart.getInputValue('email field');
219
+ const attr = await smart.getAttribute('link', 'href');
220
+ const box = await smart.getBoundingBox('hero image');
221
+ const num = await smart.count('list items');
121
222
  ```
122
223
 
123
- ### `createNLPHelper(page, options?)`
224
+ ---
124
225
 
125
- Create a helper object with natural language methods.
226
+ ### Scroll & Visual
126
227
 
127
- ```javascript
128
- const nlp = createNLPHelper(page);
228
+ ```js
229
+ await smart.scrollIntoView('footer section');
129
230
 
130
- // Available methods:
131
- await nlp.click('Login button');
132
- await nlp.type('Username field', 'john_doe');
133
- await nlp.select('Country dropdown', 'USA');
134
- await nlp.hover('Profile menu');
135
- await nlp.waitFor('Loading spinner to disappear');
136
- const text = await nlp.getText('Welcome message');
137
- const exists = await nlp.exists('Error message');
231
+ const buffer = await smart.screenshot('chart area', { path: 'chart.png' });
232
+
233
+ await smart.highlight('target element');
138
234
  ```
139
235
 
140
- ## ⚙️ Configuration Options
141
-
142
- ```javascript
143
- const options = {
144
- // Maximum elements to analyze (default: 100)
145
- maxElements: 100,
146
-
147
- // Include hidden elements (default: false)
148
- includeHiddenElements: false,
149
-
150
- // Minimum confidence to accept (default: 0.5)
151
- confidenceThreshold: 0.5,
152
-
153
- // LLM model to use (default: 'claude-3-5-sonnet')
154
- model: 'claude-3-5-sonnet',
155
-
156
- // Locator preference order
157
- preferredLocatorOrder: ['role', 'text', 'testId', 'css', 'xpath']
158
- };
159
-
160
- const result = await findLocator(page, 'Submit button', options);
236
+ ---
237
+
238
+ ### `smart.locate()` — Resolve Once, Act Many Times
239
+
240
+ When you need multiple actions on the same element, use `locate()` to resolve the locator once:
241
+
242
+ ```js
243
+ const el = await smart.locate('username');
244
+ await el.clear();
245
+ await el.fill('NewAdmin');
246
+ await el.press('Tab');
247
+ console.log(await el.inputValue());
248
+ console.log(await el.isEnabled());
249
+
250
+ // Access the raw Playwright locator
251
+ const loc = el.rawLocator;
252
+
253
+ // SmartAction has 40+ methods matching Playwright's Locator API
254
+ await el.click();
255
+ await el.hover();
256
+ await el.screenshot({ path: 'element.png' });
257
+ await el.waitForVisible();
258
+ await el.evaluate((node) => node.style.border = '2px solid red');
161
259
  ```
162
260
 
163
- ## 🎯 Best Practices for Descriptions
261
+ ---
164
262
 
165
- ### Good Descriptions
263
+ ### `smart.prefetch()` Batch Resolve
166
264
 
167
- ```javascript
168
- // Be specific about the element type
169
- await nlp.click('Login button');
170
- await nlp.type('Email input field', 'user@example.com');
171
- await nlp.click('Submit form button');
265
+ Pre-resolve multiple locators in a single LLM call to save time and cost:
172
266
 
173
- // Include context when needed
174
- await nlp.click('Add to cart button for the first product');
175
- await nlp.type('Search box in the header', 'shoes');
267
+ ```js
268
+ await smart.prefetch('username', 'password', 'login button');
176
269
 
177
- // Use visual or functional descriptions
178
- await nlp.click('Red cancel button');
179
- await nlp.click('Hamburger menu icon');
180
- await nlp.click('Close modal X button');
270
+ // These now hit the cache — no LLM calls
271
+ await smart.fill('username', 'Admin');
272
+ await smart.fill('password', 'secret');
273
+ await smart.click('login button');
181
274
  ```
182
275
 
183
- ### ❌ Avoid Vague Descriptions
276
+ ---
184
277
 
185
- ```javascript
186
- // Too vague - could match multiple elements
187
- await nlp.click('button');
188
- await nlp.type('input', 'text');
278
+ ### Cache Management
189
279
 
190
- // Better alternatives:
191
- await nlp.click('primary submit button');
192
- await nlp.type('username input', 'text');
280
+ ```js
281
+ smart.clearCache(); // clear in-memory cache only
282
+ smart.clearAllCaches(); // clear both memory + disk (.locator-cache.json)
193
283
  ```
194
284
 
195
- ## 📚 Complete Examples
285
+ ## Caching
196
286
 
197
- ### Example 1: Login Form Automation
287
+ **playwright-genie** uses a two-level cache to minimize LLM calls:
198
288
 
199
- ```javascript
200
- const { chromium } = require('playwright');
201
- const { createNLPHelper } = require('playwright-nlp-locator');
289
+ 1. **Memory cache** — instant lookups within the same test run
290
+ 2. **Disk cache** (`.locator-cache.json`) persists across runs
202
291
 
203
- async function loginAutomation() {
204
- const browser = await chromium.launch();
205
- const page = await browser.newPage();
206
-
292
+ Cache keys are scoped by **URL pathname + action + query**, so `fill('username')` on `/login` won't collide with `click('username')` on `/dashboard`.
293
+
294
+ If a cached locator becomes stale (element no longer exists), it's automatically invalidated and re-resolved via LLM.
295
+
296
+ Set `LOCATOR_CACHE_FILE` env var to customize the cache file path.
297
+
298
+ ## LLM Provider Configuration
299
+
300
+ | Provider | `LLM_API_KEY` | `LLM_BASE_URL` | `LLM_MODEL` |
301
+ |----------|---------------|-----------------|-------------|
302
+ | OpenAI | `sk-...` | *(default)* | `gpt-4o-mini` |
303
+ | Anthropic | `sk-ant-...` | proxy URL | `claude-sonnet-4-20250514` |
304
+ | Ollama | `ollama` | `http://localhost:11434/v1` | `llama3` |
305
+ | Azure OpenAI | Azure key | deployment URL | `gpt-4o-mini` |
306
+ | RouteLLM | key | proxy URL | model name |
307
+
308
+ ## Complete Examples
309
+
310
+ ### Login Flow
311
+
312
+ ```js
313
+ import { test, expect } from '@playwright/test';
314
+ import { createSmartLocator } from 'playwright-genie';
315
+
316
+ test('complete login flow', async ({ page }) => {
317
+ const smart = createSmartLocator(page);
207
318
  await page.goto('https://myapp.com/login');
208
-
209
- const nlp = createNLPHelper(page);
210
-
211
- // Fill login form using natural language
212
- await nlp.type('Username or email input', 'john@example.com');
213
- await nlp.type('Password field', 'secretPassword123');
214
-
215
- // Check "Remember me" if it exists
216
- if (await nlp.exists('Remember me checkbox')) {
217
- await nlp.click('Remember me checkbox');
319
+
320
+ await smart.fill('username', 'admin');
321
+ await smart.fill('password', 'secret123');
322
+
323
+ if (await smart.exists('remember me checkbox')) {
324
+ await smart.check('remember me checkbox');
218
325
  }
219
-
220
- // Submit the form
221
- await nlp.click('Sign in button');
222
-
223
- // Wait for dashboard to load
224
- await nlp.waitFor('Dashboard header');
225
-
226
- console.log('Login successful!');
227
-
228
- await browser.close();
229
- }
326
+
327
+ await smart.click('sign in button');
328
+ await smart.waitForVisible('dashboard heading');
329
+
330
+ const welcome = await smart.getText('welcome message');
331
+ expect(welcome).toContain('admin');
332
+ });
230
333
  ```
231
334
 
232
- ### Example 2: E-commerce Shopping Flow
335
+ ### E-commerce Flow
233
336
 
234
- ```javascript
235
- async function shoppingFlow() {
236
- const browser = await chromium.launch();
237
- const page = await browser.newPage();
238
- const nlp = createNLPHelper(page);
239
-
337
+ ```js
338
+ test('shopping flow', async ({ page }) => {
339
+ const smart = createSmartLocator(page);
240
340
  await page.goto('https://shop.example.com');
241
-
242
- // Search for a product
243
- await nlp.type('Search bar', 'wireless headphones');
244
- await nlp.click('Search button');
245
-
246
- // Filter results
247
- await nlp.click('Price filter dropdown');
248
- await nlp.click('Under $100 option');
249
-
250
- // Select a product
251
- await nlp.click('First product in the list');
252
-
253
- // Add to cart
254
- await nlp.select('Size selector', 'Medium');
255
- await nlp.click('Add to cart button');
256
-
257
- // Proceed to checkout
258
- await nlp.click('Shopping cart icon');
259
- await nlp.click('Proceed to checkout button');
260
-
261
- // Fill shipping info
262
- await nlp.type('First name field', 'John');
263
- await nlp.type('Last name field', 'Doe');
264
- await nlp.type('Address line 1', '123 Main St');
265
- await nlp.type('City input', 'New York');
266
- await nlp.select('State dropdown', 'NY');
267
- await nlp.type('Zip code', '10001');
268
-
269
- await nlp.click('Continue to payment button');
270
-
271
- await browser.close();
272
- }
273
- ```
274
341
 
275
- ### Example 3: Form Validation Testing
342
+ await smart.fill('search bar', 'wireless headphones');
343
+ await smart.press('search bar', 'Enter');
344
+ await smart.waitForVisible('product list');
276
345
 
277
- ```javascript
278
- const { findLocator, getLocator } = require('playwright-nlp-locator');
346
+ await smart.click('first product card');
347
+ await smart.select('size dropdown', 'Medium');
348
+ await smart.click('add to cart button');
279
349
 
280
- async function testFormValidation() {
281
- const browser = await chromium.launch();
282
- const page = await browser.newPage();
283
-
284
- await page.goto('https://myapp.com/signup');
285
-
286
- // Submit empty form to trigger validation
287
- const { locator: submitBtn } = await getLocator(page, 'Submit button');
288
- await submitBtn.click();
289
-
290
- // Check for validation errors
291
- const emailError = await findLocator(page, 'Email validation error message');
292
- if (emailError.found && emailError.confidence > 0.7) {
293
- console.log('Email validation working:', emailError.locator);
294
- }
295
-
296
- const passwordError = await findLocator(page, 'Password error message');
297
- if (passwordError.found) {
298
- const { locator } = await getLocator(page, 'Password error');
299
- const errorText = await locator.textContent();
300
- console.log('Password error:', errorText);
301
- }
302
-
303
- await browser.close();
304
- }
350
+ await smart.waitForVisible('cart badge');
351
+ const count = await smart.getText('cart badge');
352
+ expect(count).toBe('1');
353
+ });
305
354
  ```
306
355
 
307
- ### Example 4: Dynamic Content Handling
356
+ ### Dynamic Content & Modals
308
357
 
309
- ```javascript
310
- async function handleDynamicContent() {
311
- const browser = await chromium.launch();
312
- const page = await browser.newPage();
313
- const nlp = createNLPHelper(page);
314
-
358
+ ```js
359
+ test('handle dynamic content', async ({ page }) => {
360
+ const smart = createSmartLocator(page);
315
361
  await page.goto('https://app.example.com');
316
-
317
- // Wait for dynamic content to load
318
- await nlp.waitFor('Content loaded indicator');
319
-
320
- // Handle modals
321
- if (await nlp.exists('Cookie consent popup')) {
322
- await nlp.click('Accept cookies button');
323
- }
324
-
325
- if (await nlp.exists('Newsletter subscription modal')) {
326
- await nlp.click('Close modal button');
362
+
363
+ if (await smart.exists('cookie consent popup')) {
364
+ await smart.click('accept cookies button');
365
+ await smart.waitForHidden('cookie consent popup');
327
366
  }
328
-
329
- // Interact with lazy-loaded content
330
- await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
331
- await nlp.waitFor('Load more button');
332
- await nlp.click('Load more button');
333
-
334
- await browser.close();
335
- }
367
+
368
+ await smart.scrollIntoView('footer section');
369
+ await smart.waitForVisible('load more button');
370
+ await smart.click('load more button');
371
+ await smart.waitForHidden('loading spinner');
372
+ });
336
373
  ```
337
374
 
338
- ## 🔧 Locator Types
339
-
340
- The library returns different types of locators based on element attributes:
341
-
342
- | Type | Example | Priority |
343
- |------|---------|----------|
344
- | Role | `page.getByRole('button', { name: 'Submit' })` | 1 (Highest) |
345
- | Text | `page.getByText('Click here')` | 2 |
346
- | Label | `page.getByLabel('Email address')` | 3 |
347
- | Placeholder | `page.getByPlaceholder('Enter email')` | 4 |
348
- | TestId | `page.getByTestId('submit-btn')` | 5 |
349
- | CSS | `page.locator('button.primary')` | 6 |
350
- | XPath | `page.locator('//button[@id="submit"]')` | 7 (Lowest) |
351
-
352
- ## 🐛 Error Handling
353
-
354
- ```javascript
355
- const { findLocator, getLocator } = require('playwright-nlp-locator');
356
-
357
- // Method 1: Check result
358
- const result = await findLocator(page, 'Missing element');
359
- if (!result.found) {
360
- console.log('Element not found:', result.error);
361
- }
362
-
363
- if (result.confidence < 0.5) {
364
- console.log('Low confidence match - may be incorrect');
365
- }
366
-
367
- // Method 2: Try-catch with getLocator
368
- try {
369
- const { locator } = await getLocator(page, 'Element description');
370
- await locator.click();
371
- } catch (error) {
372
- console.error('Failed to find element:', error.message);
373
- }
374
-
375
- // Method 3: Fallback locators
376
- const result = await findLocator(page, 'Submit button');
377
- if (result.confidence < 0.7 && result.alternatives?.length > 0) {
378
- console.log('Using alternative locator:', result.alternatives[0].locator);
379
- }
375
+ ### Form Validation
376
+
377
+ ```js
378
+ test('form validation', async ({ page }) => {
379
+ const smart = createSmartLocator(page);
380
+ await page.goto('https://myapp.com/signup');
381
+
382
+ await smart.click('submit button');
383
+ await smart.waitForVisible('email error message');
384
+
385
+ const error = await smart.getText('email error message');
386
+ expect(error).toContain('required');
387
+
388
+ await smart.fill('email field', 'user@example.com');
389
+ const enabled = await smart.isEnabled('submit button');
390
+ expect(enabled).toBe(true);
391
+ });
380
392
  ```
381
393
 
382
- ## 🔒 Security Notes
383
-
384
- - The library sends page DOM structure to the LLM API for analysis
385
- - Sensitive data in element values may be visible to the API
386
- - Consider using this in development/testing environments
387
- - For production, ensure compliance with your data policies
388
-
389
- ## 📄 TypeScript Usage
390
-
391
- ```typescript
392
- import { chromium, Page } from 'playwright';
393
- import {
394
- findLocator,
395
- getLocator,
396
- createNLPHelper,
397
- FindLocatorResult,
398
- NLPHelper,
399
- NLPLocatorConfig
400
- } from 'playwright-nlp-locator';
401
-
402
- async function typedExample(): Promise<void> {
403
- const browser = await chromium.launch();
404
- const page: Page = await browser.newPage();
405
-
406
- const config: NLPLocatorConfig = {
407
- confidenceThreshold: 0.7,
408
- maxElements: 50
409
- };
410
-
411
- const result: FindLocatorResult = await findLocator(page, 'Login button', config);
412
-
413
- if (result.found && result.confidence >= 0.7) {
414
- console.log(`Found: ${result.locator}`);
415
- }
416
-
417
- const nlp: NLPHelper = createNLPHelper(page, config);
418
- await nlp.click('Submit button');
419
-
420
- await browser.close();
421
- }
394
+ ### Drag & Drop
395
+
396
+ ```js
397
+ test('kanban board', async ({ page }) => {
398
+ const smart = createSmartLocator(page);
399
+ await page.goto('https://app.example.com/board');
400
+
401
+ await smart.dragTo('first task card', 'done column');
402
+ await smart.waitForVisible('task moved toast');
403
+ });
404
+ ```
405
+
406
+ ## Best Practices
407
+
408
+ **Be specific about element type:**
409
+ ```js
410
+ await smart.click('login button'); // good
411
+ await smart.click('button'); // too vague
422
412
  ```
423
413
 
424
- ## 🤝 Contributing
414
+ **Include context when needed:**
415
+ ```js
416
+ await smart.click('delete button in first row');
417
+ await smart.fill('search box in header', 'shoes');
418
+ ```
419
+
420
+ **Use action-appropriate descriptions:**
421
+ ```js
422
+ await smart.fill('username', 'Admin'); // finds the input
423
+ await smart.click('username label'); // finds the label
424
+ ```
425
+
426
+ **Reuse the same instance across navigations:**
427
+ ```js
428
+ const smart = createSmartLocator(page);
429
+ await page.goto('/login');
430
+ await smart.fill('username', 'Admin');
431
+ await smart.click('login button');
432
+ // navigated to /dashboard — same smart object works
433
+ await smart.click('settings tab');
434
+ ```
435
+
436
+ **Use locate() for multiple actions on same element:**
437
+ ```js
438
+ const el = await smart.locate('search box');
439
+ await el.fill('query');
440
+ await el.press('Enter');
441
+ // 1 LLM call instead of 2
442
+ ```
443
+
444
+ ## TypeScript
445
+
446
+ ```ts
447
+ import { test } from '@playwright/test';
448
+ import { createSmartLocator, SmartAction, SmartLocator } from 'playwright-genie';
449
+
450
+ test('typed example', async ({ page }) => {
451
+ const smart: SmartLocator = createSmartLocator(page);
452
+
453
+ const el: SmartAction = await smart.locate('username');
454
+ await el.fill('Admin');
455
+
456
+ const visible: boolean = await smart.isVisible('dashboard');
457
+ const text: string | null = await smart.getText('heading');
458
+ });
459
+ ```
460
+
461
+ ## Debug Mode
462
+
463
+ ```bash
464
+ LLM_LOCATOR_DEBUG=true npx playwright test
465
+ ```
466
+
467
+ This logs:
468
+ - LLM queries and responses
469
+ - Cache hits/misses (memory and disk)
470
+ - Page structure payload sizes
471
+ - Stale cache invalidations
472
+
473
+ ## Security Notes
474
+
475
+ - The library sends the page's accessibility tree to your configured LLM API
476
+ - Sensitive data visible in the DOM may be sent to the API
477
+ - Use environment variables for API keys — never hardcode them
478
+ - For sensitive environments, use a local LLM (e.g., Ollama)
479
+
480
+ ## Contributing
425
481
 
426
482
  Contributions are welcome! Please feel free to submit a Pull Request.
427
483
 
428
- ## 📝 License
484
+ ## License
429
485
 
430
- MIT License - see the [LICENSE](LICENSE) file for details.
486
+ MIT License see the [LICENSE](LICENSE) file for details.
431
487
 
432
- ## 🙏 Acknowledgments
488
+ ## Acknowledgments
433
489
 
434
- - [Playwright](https://playwright.dev/) - The underlying browser automation framework
435
- - [Abacus.AI](https://abacus.ai/) - LLM API powering the natural language understanding
490
+ - [Playwright](https://playwright.dev/) browser automation framework
491
+ - [OpenAI](https://openai.com/) LLM API support
492
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) — AI-powered code generation and architecture design
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-genie",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Find and interact with Playwright elements using natural language descriptions powered by LLM",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",