elementus-ai 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +292 -0
- package/elementus.js +1288 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Elementus
|
|
2
|
+
|
|
3
|
+
Self-healing element resolution for **Playwright**, **WebDriverIO** & **Appium**.
|
|
4
|
+
|
|
5
|
+
When a selector breaks, Elementus uses AI to find the element by natural-language description. Works with any action (`click`, `fill`, `hover`) and any assertion (`toHaveText`, `toBeVisible`). Supports local LLMs via LM Studio and cloud LLMs via Google Gemini API.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install elementus
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## One-Prompt Setup
|
|
14
|
+
|
|
15
|
+
Copy this prompt to your AI coding agent (Claude, Cursor, Copilot, etc.) and it will analyze your project and integrate Elementus automatically:
|
|
16
|
+
|
|
17
|
+
<details>
|
|
18
|
+
<summary><strong>Click to copy the setup prompt</strong></summary>
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
I just installed the npm package "elementus" — a self-healing element resolution library for test automation. Analyze my project and integrate it. Follow these steps:
|
|
22
|
+
|
|
23
|
+
1. DETECT MY FRAMEWORK
|
|
24
|
+
- Search for: playwright.config, wdio.conf, appium config files
|
|
25
|
+
- Check package.json for: @playwright/test, playwright, webdriverio, wdio, appium
|
|
26
|
+
- Read a few existing test files to understand the test structure
|
|
27
|
+
- If none found, tell me you can't detect a supported framework and stop
|
|
28
|
+
|
|
29
|
+
2. CHOOSE THE LLM PROVIDER
|
|
30
|
+
- Ask me: "Do you want to use a local LLM (LM Studio, free, private) or Google Gemini (cloud, fast, ~$0.01/500 tests)?"
|
|
31
|
+
- If Gemini: ask for API key or check for GEMINI_API_KEY env var
|
|
32
|
+
- If LM Studio: use defaults (localhost:1234, gemma model)
|
|
33
|
+
|
|
34
|
+
3. INTEGRATE BASED ON MY FRAMEWORK
|
|
35
|
+
|
|
36
|
+
For Playwright:
|
|
37
|
+
- Create or update a fixtures file that wraps page with el.wrapPage(page)
|
|
38
|
+
- Make sure all tests import from the fixtures file instead of @playwright/test
|
|
39
|
+
- Set actionTimeout: 10000 in playwright config (Elementus respects framework timeouts)
|
|
40
|
+
|
|
41
|
+
For WebDriverIO:
|
|
42
|
+
- Add el.wrapBrowser(browser) in the before hook in wdio.conf.js
|
|
43
|
+
|
|
44
|
+
For Appium:
|
|
45
|
+
- Add el.wrapBrowser(driver) in the before hook
|
|
46
|
+
|
|
47
|
+
4. EXAMPLE TEST
|
|
48
|
+
Ask me one of these three options:
|
|
49
|
+
a) "Which test case would you like me to add { ai } to as an example?"
|
|
50
|
+
b) "Or should I pick one test from your repo that has fragile selectors?"
|
|
51
|
+
c) "Or do you prefer no changes to existing tests — just the setup/config?"
|
|
52
|
+
|
|
53
|
+
If (a): apply { ai } to 1-2 fragile locators in the test I specify
|
|
54
|
+
If (b): find one test with fragile selectors (auto-generated IDs, deep CSS paths, nth-child), apply { ai } to 1-2 locators in that single test, explain why you chose it
|
|
55
|
+
If (c): skip this step — just confirm the setup is ready and show a standalone code snippet of how { ai } would look with my framework
|
|
56
|
+
|
|
57
|
+
IMPORTANT: Never modify more than one test file. This is an example — the user decides where to apply { ai } going forward.
|
|
58
|
+
|
|
59
|
+
5. VERIFY
|
|
60
|
+
- If a test was modified: run that single test to confirm it passes
|
|
61
|
+
- If no test was modified: confirm Elementus loads without errors by running a quick import check
|
|
62
|
+
|
|
63
|
+
Rules:
|
|
64
|
+
- Only modify ONE test file maximum, and only 1-2 locators in it — this is a demo, not a migration
|
|
65
|
+
- Do NOT add { ai } to every locator — only to ones with fragile selectors
|
|
66
|
+
- Stable selectors (data-testid, explicit IDs, aria labels) should NOT get { ai } — zero overhead matters
|
|
67
|
+
- The { ai } description should use words that appear in or near the element's visible text
|
|
68
|
+
- Never add runtime dependencies — Elementus has zero deps by design
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
</details>
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const { createElementus } = require('elementus')
|
|
77
|
+
|
|
78
|
+
const el = createElementus({
|
|
79
|
+
provider: 'gemini',
|
|
80
|
+
geminiApiKey: process.env.GEMINI_API_KEY,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Wrap your page — add { ai } to any locator that might break:
|
|
84
|
+
const p = el.wrapPage(page)
|
|
85
|
+
await p.locator('#submit-btn', { ai: 'Submit order button' }).click()
|
|
86
|
+
|
|
87
|
+
// Locators WITHOUT { ai } work normally — zero overhead:
|
|
88
|
+
await p.locator('#stable-element').click()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## LLM Provider Setup
|
|
92
|
+
|
|
93
|
+
### Option A: Local LLM via LM Studio (free, private)
|
|
94
|
+
|
|
95
|
+
1. Download [LM Studio](https://lmstudio.ai)
|
|
96
|
+
2. Load a vision-capable model (e.g., `gemma-4-26b-a4b-it`)
|
|
97
|
+
3. Start the local server (default: `http://localhost:1234`)
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
const el = createElementus({
|
|
101
|
+
provider: 'lmstudio',
|
|
102
|
+
lmStudioUrl: 'http://localhost:1234/v1/chat/completions',
|
|
103
|
+
model: 'gemma-4-26b-a4b-it',
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Option B: Google Gemini API (cloud, fast, better vision)
|
|
108
|
+
|
|
109
|
+
1. Get an API key from [Google AI Studio](https://aistudio.google.com/apikey)
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const el = createElementus({
|
|
113
|
+
provider: 'gemini',
|
|
114
|
+
geminiApiKey: 'AIza...', // or set GEMINI_API_KEY env var
|
|
115
|
+
geminiModel: 'gemini-2.5-flash',
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Framework Setup
|
|
120
|
+
|
|
121
|
+
### Playwright
|
|
122
|
+
|
|
123
|
+
Wrap page once, add `{ ai }` to any locator:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const p = el.wrapPage(page)
|
|
127
|
+
await p.locator('#btn', { ai: 'Submit order button' }).click()
|
|
128
|
+
await p.locator('#email', { ai: 'Email input field' }).fill('test@test.com')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Recommended: Playwright fixture** (wrap once for all tests):
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// fixtures.js
|
|
135
|
+
const { test: base } = require('@playwright/test')
|
|
136
|
+
const { createElementus } = require('elementus')
|
|
137
|
+
const el = createElementus({ provider: 'gemini', geminiApiKey: '...' })
|
|
138
|
+
|
|
139
|
+
module.exports = base.extend({
|
|
140
|
+
page: async ({ page }, use) => {
|
|
141
|
+
await use(el.wrapPage(page))
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// In tests — page is already wrapped:
|
|
146
|
+
test('example', async ({ page }) => {
|
|
147
|
+
await page.locator('#btn', { ai: 'Submit button' }).click()
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### WebDriverIO
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
const b = el.wrapBrowser(browser)
|
|
155
|
+
await b.$('#btn', { ai: 'Submit order button' }).click()
|
|
156
|
+
await b.$('#email', { ai: 'Email input field' }).setValue('test@test.com')
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Appium (Native Android / iOS / Flutter)
|
|
160
|
+
|
|
161
|
+
Same `wrapBrowser` pattern. Elementus auto-detects native apps and parses the element tree from `driver.getPageSource()` (XML) instead of DOM scanning.
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const d = el.wrapBrowser(driver)
|
|
165
|
+
await d.$('~loginButton', { ai: 'Login button on welcome screen' }).click()
|
|
166
|
+
await d.$('~emailField', { ai: 'Email input' }).setValue('test@test.com')
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Works with Flutter, React Native, native Android/iOS — any Appium driver.
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
### `el.wrapPage(page)`
|
|
174
|
+
|
|
175
|
+
Wraps a Playwright page. Returns a proxy where `page.locator(selector, { ai: 'description' })` auto-creates AI-fallback locators. Locators without `{ ai }` pass through unchanged.
|
|
176
|
+
|
|
177
|
+
### `el.wrapBrowser(browser)`
|
|
178
|
+
|
|
179
|
+
Wraps a WDIO/Appium browser. Returns a proxy where `browser.$(selector, { ai: 'description' })` auto-creates AI-fallback elements.
|
|
180
|
+
|
|
181
|
+
### `el.find(context, description)`
|
|
182
|
+
|
|
183
|
+
Find element by description only (no locator needed). Returns a framework-native locator/element.
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const found = await el.find(page, 'Submit order button')
|
|
187
|
+
await found.click()
|
|
188
|
+
await expect(found).toHaveText('Submit')
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `el.locate(context, locator, description)`
|
|
192
|
+
|
|
193
|
+
Try locator first, fall back to AI if it fails. Returns a framework-native locator/element. Respects your framework's configured action timeout.
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
const found = await el.locate(page, page.locator('#btn'), 'Submit button')
|
|
197
|
+
await found.click()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `el.click(context, locator, description)`
|
|
201
|
+
|
|
202
|
+
Click with optimized fallback: uses `page.goto()` for links (avoids hover/overlay issues) and JS click for buttons (no mouse movement). Best for navigation clicks. Respects your framework's configured action timeout.
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
await el.click(page, page.locator('#nav-blog'), 'Blog page link')
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `el.wrap(context, locator, description)`
|
|
209
|
+
|
|
210
|
+
Low-level: wraps any single locator/element with AI fallback. Prefer `wrapPage`/`wrapBrowser` for cleaner code.
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
createElementus({
|
|
216
|
+
// LLM Provider
|
|
217
|
+
provider: 'lmstudio', // 'lmstudio' | 'gemini'
|
|
218
|
+
|
|
219
|
+
// LM Studio
|
|
220
|
+
lmStudioUrl: 'http://localhost:1234/v1/chat/completions',
|
|
221
|
+
model: 'gemma-4-26b-a4b-it',
|
|
222
|
+
|
|
223
|
+
// Gemini
|
|
224
|
+
geminiApiKey: null, // or GEMINI_API_KEY env var
|
|
225
|
+
geminiModel: 'gemini-2.5-flash',
|
|
226
|
+
|
|
227
|
+
// Behavior
|
|
228
|
+
maxCandidates: 20, // max elements sent to LLM for disambiguation
|
|
229
|
+
visionMaxWidth: 1280, // max screenshot width (px) sent to vision LLM
|
|
230
|
+
|
|
231
|
+
// Debugging
|
|
232
|
+
debug: false, // save screenshots to debugDir
|
|
233
|
+
debugDir: './debug', // directory for debug screenshots
|
|
234
|
+
|
|
235
|
+
// Custom stop words
|
|
236
|
+
stopWords: null, // Set of words to ignore in descriptions
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Timeouts
|
|
241
|
+
|
|
242
|
+
Elementus respects your framework's configured timeouts. It does **not** override or race against them. Set appropriate action timeouts in your framework config:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// Playwright: playwright.config.js or test.use()
|
|
246
|
+
test.use({ actionTimeout: 10_000 }) // 10s before locator fails, then AI takes over
|
|
247
|
+
|
|
248
|
+
// WDIO: wdio.conf.js
|
|
249
|
+
waitforTimeout: 10000
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
If a selector works, it returns immediately (zero overhead). If it fails after your configured timeout, Elementus AI fallback kicks in.
|
|
253
|
+
|
|
254
|
+
## How It Works
|
|
255
|
+
|
|
256
|
+
When a selector fails, Elementus runs a 3-step pipeline:
|
|
257
|
+
|
|
258
|
+
**Step 1: Locator** — Try the original selector. If it works, done (zero overhead).
|
|
259
|
+
|
|
260
|
+
**Step 2: DOM/Element Tree Scoring** — Scan all interactive elements on the page (DOM for web, XML source for native apps). Score each by keyword and phrase relevance to the description. If one clear winner, use it. If multiple tied, send candidates to LLM. If all identical (e.g., 10x "Edit" buttons), use positional LLM with coordinates.
|
|
261
|
+
|
|
262
|
+
**Step 3: Vision** — Take a screenshot with a labeled grid overlay. Ask the vision LLM which region contains the target. Scroll there, re-scan. If still unresolved, ask for precise pixel coordinates.
|
|
263
|
+
|
|
264
|
+
## Tips for Writing Descriptions
|
|
265
|
+
|
|
266
|
+
**Good** — use words that appear in or near the element:
|
|
267
|
+
- `'Submit order button'` matches `<button>Submit</button>`
|
|
268
|
+
- `'Email input field'` matches `<input>` near "Email" label
|
|
269
|
+
- `'Privacy Policy footer link'` matches `<a>Privacy Policy</a>`
|
|
270
|
+
|
|
271
|
+
**For identical elements**, add positional context:
|
|
272
|
+
- `'first Edit button near the top'`
|
|
273
|
+
- `'Delete button in the third row'`
|
|
274
|
+
|
|
275
|
+
**Avoid vague descriptions:**
|
|
276
|
+
- `'the button'` matches every button
|
|
277
|
+
- `'Save Changes button'` is specific and matchable
|
|
278
|
+
|
|
279
|
+
## Platform Support
|
|
280
|
+
|
|
281
|
+
| Platform | Element scan | Vision | Status |
|
|
282
|
+
|----------|-------------|--------|--------|
|
|
283
|
+
| Playwright (web) | DOM | Screenshot + LLM | Full support |
|
|
284
|
+
| WDIO (web) | DOM | Screenshot + LLM | Full support |
|
|
285
|
+
| Appium (mobile web) | DOM | Screenshot + LLM | Full support |
|
|
286
|
+
| Appium (native Android/iOS) | XML source | Screenshot + LLM | Full support |
|
|
287
|
+
| Appium (Flutter) | XML source | Screenshot + LLM | Full support |
|
|
288
|
+
| Appium (React Native) | XML source | Screenshot + LLM | Full support |
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|