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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +292 -0
  3. package/elementus.js +1288 -0
  4. 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