elementus-ai 1.1.1 → 1.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes are documented here. This project adheres to [Semantic Versioning](https://semver.org).
4
+
5
+ ## 1.4.0
6
+
7
+ ### Added
8
+
9
+ - `HealEvent.resolved` — `{ tag, text, role }` of the element a heal resolved to (when known), so a
10
+ consumer can compose a **suggested replacement locator** (e.g. `getByRole(role, { name: text })`) and
11
+ show "selector X failed → replace with Y" in reports. Absent on coordinate-only vision heals.
12
+
13
+ ### Changed
14
+
15
+ - Additive on the existing `onHeal` event; no behavior change.
16
+
17
+ ## 1.3.0
18
+
19
+ ### Added
20
+
21
+ - `onHeal(event)` config callback — fires once per heal with `{ description, selector, method, framework }`
22
+ so consumers can surface healed locators (report annotations, CI fail-on-heal). Framework-agnostic:
23
+ elementus emits the event; wiring it to a reporter is consumer-side (README "Detecting heals" has
24
+ Playwright + WebdriverIO/Appium recipes).
25
+ - `HealEvent` exported type (`index.d.ts`); `onHeal` added to `ElementusOptions`.
26
+ - Test `T47` covering onHeal; docs in README, `docs/API.md`, `docs/ARCHITECTURE.md`.
27
+
28
+ ### Changed
29
+
30
+ - No change to existing behavior. `onHeal` is opt-in; unset ⇒ byte-for-byte the same as 1.2.0
31
+ (zero-overhead path untouched, single export preserved).
32
+
33
+ ## 1.2.0
34
+
35
+ ### Added
36
+
37
+ - Bundled TypeScript type definitions (`index.d.ts`), exposed via the package `types` field — no `@types/...` package or `declare module` shim required.
38
+ - Exported types: `createElementus`, `ElementusOptions`, `Elementus`, `ElementusPage`, `AiLocatorOptions`.
39
+ - `ElementusPage` types `page.locator(selector, { ai })` by extending Playwright's own locator options, so the `{ ai }` hint type-checks while plain locators and native options keep working.
40
+ - `@playwright/test` types resolve as an optional peer — WDIO/Appium-only projects don't need Playwright installed.
41
+ - README: new **TypeScript** section (typed fixture + Page Object Model patterns); the One-Prompt Setup is now TypeScript-aware.
42
+
43
+ ### Changed
44
+
45
+ - No runtime changes. `elementus.js` is untouched; this release is type definitions and documentation only.
46
+
47
+ ## 1.1.1
48
+
49
+ - Previous release.
package/README.md CHANGED
@@ -24,6 +24,7 @@ I just installed the npm package "elementus-ai" — a self-healing element resol
24
24
  - Search for: playwright.config, wdio.conf, appium config files
25
25
  - Check package.json for: @playwright/test, playwright, webdriverio, wdio, appium
26
26
  - Read a few existing test files to understand the test structure
27
+ - Note whether the project is TypeScript (tsconfig.json or .ts test files) — this changes the fixture syntax (see step 3)
27
28
  - If none found, tell me you can't detect a supported framework and stop
28
29
 
29
30
  2. CHOOSE THE LLM PROVIDER
@@ -33,10 +34,24 @@ I just installed the npm package "elementus-ai" — a self-healing element resol
33
34
 
34
35
  3. INTEGRATE BASED ON MY FRAMEWORK
35
36
 
37
+ First create the elementus instance using the provider chosen in step 2:
38
+ const { createElementus } = require('elementus-ai') // ESM/TS: import { createElementus } from 'elementus-ai'
39
+ const el = createElementus({ provider: 'gemini', geminiApiKey: process.env.GEMINI_API_KEY })
40
+ // LM Studio instead: createElementus({ provider: 'lmstudio', lmStudioUrl: 'http://localhost:1234/v1/chat/completions', model: 'holo-3.1-9b' })
41
+ For exact, copy-pasteable code (TypeScript fixture, onHeal recipe), read the installed package's
42
+ node_modules/elementus-ai/README.md ("Quick Start", "TypeScript", "Detecting heals") and docs/API.md.
43
+
36
44
  For Playwright:
37
- - Create or update a fixtures file that wraps page with el.wrapPage(page)
45
+ - Create or update a fixtures file that overrides the page fixture with el.wrapPage(page)
38
46
  - Make sure all tests import from the fixtures file instead of @playwright/test
47
+ - TypeScript projects: use import/export and type the override as base.extend<{ page: ElementusPage }>({ ... }) (import ElementusPage from elementus-ai) so { ai } is autocompleted and documented. Types are bundled — do NOT add @types or a "declare module" shim
39
48
  - Set actionTimeout: 10000 in playwright config (Elementus respects framework timeouts)
49
+ - Optional heal visibility: only if the user wants healed locators surfaced, wire onHeal to the
50
+ framework's reporter. Build a suggested replacement from e.resolved ({ tag, text, role }), e.g.
51
+ getByRole(role, { name: text }). Playwright: push a testInfo "healed" annotation (HTML report) AND
52
+ testInfo.attach (so it also reaches Allure + custom reporters); optionally fail CI on heal via an
53
+ env-gated afterEach/fixture. WebdriverIO/Appium: collect in onHeal, fail in afterTest. See
54
+ "Detecting heals" in the README. Default off
40
55
 
41
56
  For WebDriverIO:
42
57
  - In wdio.conf.js before hook, wrap browser and override global $:
@@ -90,6 +105,8 @@ await p.locator('#submit-btn', { ai: 'Submit order button' }).click()
90
105
  await p.locator('#stable-element').click()
91
106
  ```
92
107
 
108
+ > **Using TypeScript or ESM?** `import { createElementus } from 'elementus-ai'` — type definitions are bundled. See [TypeScript](#typescript) for the typed fixture pattern.
109
+
93
110
  ## LLM Provider Setup
94
111
 
95
112
  ### Option A: Local LLM via LM Studio (free, private)
@@ -193,6 +210,55 @@ await d.$('~emailField', { ai: 'Email input' }).setValue('test@test.com')
193
210
 
194
211
  Works with Flutter, React Native, native Android/iOS — any Appium driver.
195
212
 
213
+ ## TypeScript
214
+
215
+ Type definitions are bundled — there is no `@types/elementus-ai` package to install and no `declare module` shim to write. Because `@playwright/test` is an *optional* peer, WDIO/Appium-only projects can use the types without installing Playwright.
216
+
217
+ ```ts
218
+ import { createElementus, type ElementusPage } from 'elementus-ai'
219
+ ```
220
+
221
+ **Typed Playwright fixture.** `wrapPage` changes the page's runtime value but not its static type, so override the `page` fixture's type with `ElementusPage` — then `{ ai }` is recognized and autocompleted (with docs) in your tests:
222
+
223
+ ```ts
224
+ // fixtures.ts
225
+ import { test as base, expect } from '@playwright/test'
226
+ import { createElementus, type ElementusPage } from 'elementus-ai'
227
+
228
+ const el = createElementus({ provider: 'gemini', geminiApiKey: process.env.GEMINI_API_KEY })
229
+
230
+ export const test = base.extend<{ page: ElementusPage }>({
231
+ page: async ({ page }, use) => {
232
+ await use(el.wrapPage(page))
233
+ },
234
+ })
235
+ export { expect }
236
+
237
+ // In tests — page is already wrapped and typed:
238
+ test('example', async ({ page }) => {
239
+ await page.locator('#btn', { ai: 'Submit button' }).click() // { ai } type-checks
240
+ await page.locator('#btn').click() // plain locator, zero overhead
241
+ })
242
+ ```
243
+
244
+ > The override is for editor support — IntelliSense and inline docs for `{ ai }`. It heals at runtime either way, and because Playwright's `locator()` options are permissive, `{ ai }` compiles with or without the override; the override just surfaces it as a documented option.
245
+
246
+ **Page Object Model.** Type the page your objects receive as `ElementusPage`:
247
+
248
+ ```ts
249
+ import { type ElementusPage } from 'elementus-ai'
250
+
251
+ abstract class BasePage {
252
+ constructor(protected readonly page: ElementusPage) {}
253
+ }
254
+
255
+ class LoginPage extends BasePage {
256
+ readonly submit = this.page.locator('#submit', { ai: 'Submit button' })
257
+ }
258
+ ```
259
+
260
+ **Exported types:** `ElementusOptions`, `Elementus`, `ElementusPage`, `AiLocatorOptions`. `AiLocatorOptions` is Playwright's own `locator()` option type plus `ai?: string`, derived from the installed Playwright version so it never drifts.
261
+
196
262
  ## API Reference
197
263
 
198
264
  ### `el.wrapPage(page)`
@@ -267,9 +333,91 @@ createElementus({
267
333
 
268
334
  // Custom stop words
269
335
  stopWords: null, // Set of words to ignore in descriptions
336
+
337
+ // Heal telemetry (opt-in) — called once per heal with { description, selector, method, framework, resolved }.
338
+ // Best-effort & isolated: a throwing callback never breaks a test. See "Detecting heals" below.
339
+ onHeal: null, // e.g. (e) => console.log('healed', e.selector, '→', e.description)
270
340
  })
271
341
  ```
272
342
 
343
+ ## Detecting heals (reporting & CI)
344
+
345
+ A heal is silent by default — the test still passes. To surface it, pass an `onHeal(event)` callback;
346
+ elementus calls it **once per heal** with `{ description, selector, method, framework, resolved }`. The library only
347
+ *emits* the event — turning it into a report annotation or a CI failure is framework-specific (a few lines
348
+ on your side). It fires when the AI resolves a replacement element, so it signals "the selector drifted and
349
+ AI stepped in", not "the action ultimately passed".
350
+
351
+ **Playwright** — annotate the test (shows in the Playwright HTML report), and optionally fail
352
+ CI on heal:
353
+
354
+ ```javascript
355
+ // fixtures.js
356
+ const { test: base } = require('@playwright/test')
357
+ const { createElementus } = require('elementus-ai')
358
+
359
+ const el = createElementus({
360
+ provider: 'gemini',
361
+ geminiApiKey: process.env.GEMINI_API_KEY,
362
+ onHeal: (e) => {
363
+ // Build a suggested replacement from the element elementus resolved to (e.resolved)
364
+ const r = e.resolved
365
+ const role = r && (r.role ?? { a: 'link', button: 'button' }[r.tag])
366
+ const suggestion = !r ? '(located visually — no DOM suggestion)'
367
+ : role && r.text ? `getByRole('${role}', { name: '${r.text}' })`
368
+ : r.text ? `getByText('${r.text}')`
369
+ : `locator('${r.tag}')`
370
+ const line = `${e.selector} → ${suggestion}` // failed locator → suggested replacement
371
+ try {
372
+ const info = base.info()
373
+ info.annotations.push({ type: 'healed', description: line }) // Playwright HTML report
374
+ void info.attach('healed', { body: line, contentType: 'text/plain' }) // Allure + custom reporters
375
+ } catch {} // ignored outside a running test
376
+ },
377
+ })
378
+
379
+ const test = base.extend({
380
+ page: async ({ page }, use) => { await use(el.wrapPage(page)) },
381
+ // Optional: fail CI when a selector only passed via self-healing (drift you should fix).
382
+ failOnHeal: [async ({}, use, testInfo) => {
383
+ await use()
384
+ const heals = testInfo.annotations.filter(a => a.type === 'healed')
385
+ if (process.env.ELEMENTUS_FAIL_ON_HEAL && heals.length) {
386
+ throw new Error('Passed only via self-healing — fix the selector(s):\n ' + heals.map(h => h.description).join('\n '))
387
+ }
388
+ }, { auto: true }],
389
+ })
390
+ module.exports = { test, expect: require('@playwright/test').expect }
391
+ ```
392
+
393
+ (TypeScript: import the `HealEvent` type from `elementus-ai`; the annotation/fixture code is identical.)
394
+
395
+ **WebdriverIO / Appium** — collect in `onHeal`, fail (or report) in `afterTest`:
396
+
397
+ ```javascript
398
+ // wdio.conf.js
399
+ const { createElementus } = require('elementus-ai')
400
+ let heals = []
401
+ const el = createElementus({ provider: 'gemini', geminiApiKey: '...', onHeal: (e) => heals.push(e) })
402
+
403
+ exports.config = {
404
+ beforeTest() { heals = [] },
405
+ afterTest() {
406
+ if (process.env.ELEMENTUS_FAIL_ON_HEAL && heals.length) {
407
+ throw new Error('healed: ' + heals.map(h => h.description).join(', '))
408
+ }
409
+ },
410
+ }
411
+ ```
412
+
413
+ **Where each report shows it:** the annotation appears in the **Playwright HTML report** (test → _Annotations_).
414
+ In **Allure**, the heal shows in the test's **step tree** (the broken locator → the healed `[data-elementus=…]`
415
+ element) and, with fail-on-heal on, the **failure message** — note `allure-playwright` does not convert
416
+ runtime annotations into labels/steps, so for a dedicated Allure marker also
417
+ `testInfo.attach('healed', { body: \`${e.selector} → ${e.description}\`, contentType: 'text/plain' })`
418
+ (attachments surface in Allure and most custom reporters). A custom HTML reporter surfaces the heal via the
419
+ failure message and captured stdout.
420
+
273
421
  ## Security Notes
274
422
 
275
423
  - **Debug screenshots** capture the full page — including any sensitive data visible on it. Keep `debugDir` out of version control.
package/elementus.js CHANGED
@@ -377,6 +377,7 @@ const REGION_LABELS = [
377
377
  * @param {number} [userConfig.visionMaxWidth=1280] - max screenshot width (px) sent to vision LLM
378
378
  * @param {string|null} [userConfig.cacheFile=null] - opt-in fingerprint cache file (e.g. './elementus-cache.json')
379
379
  * @param {string|null} [userConfig.embeddingModel=null] - opt-in embedding model for semantic matching
380
+ * @param {(event: { description: string, selector: string, method: string, framework: string, resolved?: { tag: string, text: string, role: string|null } }) => void} [userConfig.onHeal] - called after a heal resolves a replacement element (original selector failed, AI found it). `resolved` is the element it healed to (when known), for suggesting a replacement. Best-effort; throwing never breaks a test.
380
381
  * @returns {{ wrap, wrapPage, wrapBrowser, locate, find, click }}
381
382
  */
382
383
  function createElementus(userConfig = {}) {
@@ -1988,6 +1989,31 @@ function createElementus(userConfig = {}) {
1988
1989
  return { record: null, somCandidates: out.somCandidates || null }
1989
1990
  }
1990
1991
 
1992
+ // Facts about the element the last heal resolved to (tag/text/role) — set during resolution,
1993
+ // read once by _notifyHeal. Framework-agnostic; consumers format a suggested replacement locator.
1994
+ let _lastResolved = null
1995
+ function _resolvedFacts(record) {
1996
+ if (!record) return null
1997
+ return { tag: record.tag || '', text: (record.text || '').trim(), role: record.role || null }
1998
+ }
1999
+
2000
+ // Fire the user's onHeal(event) after a real heal — the original selector failed and the AI
2001
+ // pipeline resolved a replacement. Best-effort and fully isolated: a throwing or rejecting
2002
+ // callback must never break a test. No-op with zero overhead when onHeal is not configured.
2003
+ function _notifyHeal(ctx, description, selectorKey, method) {
2004
+ const resolved = _lastResolved
2005
+ _lastResolved = null
2006
+ const cb = config.onHeal
2007
+ if (typeof cb !== 'function') return
2008
+ const framework = _isNative(ctx) ? 'appium' : _isPlaywright(ctx) ? 'playwright' : 'wdio'
2009
+ try {
2010
+ const event = { description, selector: selectorKey, method, framework }
2011
+ if (resolved) event.resolved = resolved
2012
+ const r = cb(event)
2013
+ if (r && typeof r.then === 'function') r.then(undefined, () => {})
2014
+ } catch {}
2015
+ }
2016
+
1991
2017
  async function _findByDescription(ctx, description, selectorKey = '') {
1992
2018
  const { record, somCandidates } = await _resolveElement(ctx, description, selectorKey)
1993
2019
  if (record) {
@@ -1995,6 +2021,7 @@ function createElementus(userConfig = {}) {
1995
2021
  const mark = {}
1996
2022
  const locator = record._locator || await markByElement(ctx, record, mark)
1997
2023
  await _cacheStore(ctx, description, selectorKey, record, record._uid || mark.uid || null)
2024
+ _lastResolved = _resolvedFacts(record)
1998
2025
  return locator
1999
2026
  } catch (err) {
2000
2027
  console.log(`[Resolve] Mark failed (${err.message}) — trying vision`)
@@ -2006,11 +2033,13 @@ function createElementus(userConfig = {}) {
2006
2033
  const mark = {}
2007
2034
  const locator = await markByElement(ctx, result.element, mark)
2008
2035
  await _cacheStore(ctx, description, selectorKey, result.element, mark.uid || null)
2036
+ _lastResolved = _resolvedFacts(result.element)
2009
2037
  return locator
2010
2038
  }
2011
2039
  const mark = {}
2012
2040
  const locator = await markAtCoordinates(ctx, result.coords.docX, result.coords.docY, mark)
2013
2041
  await _cacheStore(ctx, description, selectorKey, result.coords, mark.uid || null)
2042
+ _lastResolved = null
2014
2043
  return locator
2015
2044
  } catch (err) {
2016
2045
  throw new Error(`All fallback paths exhausted for "${description}": ${err.message}`)
@@ -2038,7 +2067,10 @@ function createElementus(userConfig = {}) {
2038
2067
  } catch {
2039
2068
  console.log(`\u2717 Locator failed \u2014 searching for: "${description}"`)
2040
2069
  }
2041
- return _findByDescription(ctx, description, _selectorKey(locator))
2070
+ const selectorKey = _selectorKey(locator)
2071
+ const resolved = await _findByDescription(ctx, description, selectorKey)
2072
+ _notifyHeal(ctx, description, selectorKey, 'locate')
2073
+ return resolved
2042
2074
  }
2043
2075
 
2044
2076
  /**
@@ -2088,6 +2120,8 @@ function createElementus(userConfig = {}) {
2088
2120
  // Store before clicking \u2014 the click may navigate away from the page
2089
2121
  await _cacheStore(ctx, description, selectorKey, record)
2090
2122
  await scrollAndClick(ctx, record)
2123
+ _lastResolved = _resolvedFacts(record)
2124
+ _notifyHeal(ctx, description, selectorKey, 'click')
2091
2125
  return
2092
2126
  }
2093
2127
  try {
@@ -2095,9 +2129,13 @@ function createElementus(userConfig = {}) {
2095
2129
  if (result.element) {
2096
2130
  await _cacheStore(ctx, description, selectorKey, result.element)
2097
2131
  await scrollAndClick(ctx, result.element)
2132
+ _lastResolved = _resolvedFacts(result.element)
2133
+ _notifyHeal(ctx, description, selectorKey, 'click')
2098
2134
  return
2099
2135
  }
2100
2136
  await clickAtCoords(ctx, result.coords)
2137
+ _lastResolved = null
2138
+ _notifyHeal(ctx, description, selectorKey, 'click')
2101
2139
  } catch (err) {
2102
2140
  throw new Error(`All fallback paths exhausted for "${description}": ${err.message}`)
2103
2141
  }
@@ -2160,6 +2198,7 @@ function createElementus(userConfig = {}) {
2160
2198
  if (!_resolved) {
2161
2199
  console.log(`[AI] ${prop}() \u2014 resolving via AI first for "${description}"`)
2162
2200
  _resolved = await _findByDescription(driverContext, description, wrapSelectorKey)
2201
+ _notifyHeal(driverContext, description, wrapSelectorKey, String(prop))
2163
2202
  }
2164
2203
  return _resolved[prop](...args)
2165
2204
  }
@@ -2168,7 +2207,10 @@ function createElementus(userConfig = {}) {
2168
2207
  return await original.apply(target, args)
2169
2208
  } catch (firstError) {
2170
2209
  console.log(`[AI] ${String(prop)}() failed \u2014 AI fallback for "${description}"`)
2171
- if (!_resolved) _resolved = await _findByDescription(driverContext, description, wrapSelectorKey)
2210
+ if (!_resolved) {
2211
+ _resolved = await _findByDescription(driverContext, description, wrapSelectorKey)
2212
+ _notifyHeal(driverContext, description, wrapSelectorKey, String(prop))
2213
+ }
2172
2214
 
2173
2215
  const resolvedMethod = _resolved[prop]
2174
2216
  if (typeof resolvedMethod !== 'function') {
package/index.d.ts ADDED
@@ -0,0 +1,104 @@
1
+ // Type definitions for elementus-ai
2
+ // Project: https://github.com/Morph93/elementus
3
+ //
4
+ // Self-healing element resolution for Playwright, WebdriverIO & Appium.
5
+ // These types describe the Playwright/core API. WebdriverIO's global `$`
6
+ // augmentation lives in the separate, opt-in `wdio.d.ts`.
7
+ //
8
+ // `@playwright/test` is an OPTIONAL peer dependency. The `@ts-ignore` below lets
9
+ // WDIO/Appium-only consumers (who have no Playwright installed) fall back to
10
+ // `any` for these types instead of failing module resolution.
11
+ // @ts-ignore -- optional peer dependency
12
+ import type { Page, Locator } from '@playwright/test'
13
+
14
+ export interface ElementusOptions {
15
+ /** LLM provider. @default 'lmstudio' */
16
+ provider?: 'lmstudio' | 'gemini'
17
+ /** LM Studio chat-completions endpoint. @default 'http://localhost:1234/v1/chat/completions' */
18
+ lmStudioUrl?: string
19
+ /** LM Studio model name. @default 'holo-3.1-9b' */
20
+ model?: string
21
+ /** Google Gemini API key (or set the GEMINI_API_KEY env var). @default null */
22
+ geminiApiKey?: string | null
23
+ /** Gemini model id. @default 'gemini-3.5-flash' */
24
+ geminiModel?: string
25
+ /** Max elements sent to the LLM for disambiguation. @default 20 */
26
+ maxCandidates?: number
27
+ /** Save debug screenshots to `debugDir`. @default false */
28
+ debug?: boolean
29
+ /** Directory for debug screenshots (required when `debug` is true). @default null */
30
+ debugDir?: string | null
31
+ /** Custom stop words to ignore in descriptions (replaces the defaults). @default null */
32
+ stopWords?: Set<string> | null
33
+ /** Max screenshot width (px) sent to the vision LLM. @default 1280 */
34
+ visionMaxWidth?: number
35
+ /** Opt-in fingerprint cache file, e.g. './elementus-cache.json'. @default null */
36
+ cacheFile?: string | null
37
+ /** Opt-in embedding model for semantic paraphrase matching. @default null */
38
+ embeddingModel?: string | null
39
+ /**
40
+ * Called after a heal resolves a replacement element (the original selector failed and the AI
41
+ * pipeline found a match). Best-effort and isolated — a throwing/rejecting callback never breaks
42
+ * a test. Fires at resolution time, so it does NOT guarantee the subsequent action succeeded.
43
+ */
44
+ onHeal?: (event: HealEvent) => void | Promise<void>
45
+ }
46
+
47
+ /** Event passed to `onHeal` when a broken selector is healed. */
48
+ export interface HealEvent {
49
+ /** The natural-language `{ ai }` description that resolved the element. */
50
+ description: string
51
+ /** The original selector that failed (selector key). */
52
+ selector: string
53
+ /** The action that triggered the heal: 'click', 'fill', 'isVisible', 'locate', etc. */
54
+ method: string
55
+ /** Which driver the heal happened on. */
56
+ framework: 'playwright' | 'wdio' | 'appium'
57
+ /**
58
+ * The element elementus resolved to (when known — absent on coordinate-only vision heals).
59
+ * Use it to compose a suggested replacement locator, e.g. getByRole(role, { name: text }).
60
+ */
61
+ resolved?: { tag: string; text: string; role: string | null }
62
+ }
63
+
64
+ /**
65
+ * Playwright's own `locator()` options, plus the Elementus `ai` hint.
66
+ * Derived from the installed Playwright types so it never drifts.
67
+ */
68
+ export type AiLocatorOptions = NonNullable<Parameters<Page['locator']>[1]> & {
69
+ /** Natural-language description; the self-healing fallback used when `selector` breaks. */
70
+ ai?: string
71
+ }
72
+
73
+ /**
74
+ * A Playwright Page whose `locator()` also accepts `{ ai }`. Locators created
75
+ * with an `ai` hint self-heal when the selector breaks; locators without it are
76
+ * returned unchanged (zero overhead).
77
+ */
78
+ export type ElementusPage = Page & {
79
+ locator(selector: string, options?: AiLocatorOptions): Locator
80
+ }
81
+
82
+ export interface Elementus {
83
+ /**
84
+ * Wrap a Playwright Page so `page.locator(selector, { ai })` self-heals.
85
+ * Call once per test, or in a fixture for the whole suite.
86
+ */
87
+ wrapPage(page: Page): ElementusPage
88
+ /**
89
+ * Wrap a WebdriverIO/Appium browser so `$(selector, { ai })` self-heals.
90
+ * Returns the same object it was given (now AI-aware).
91
+ */
92
+ wrapBrowser<T>(browser: T): T
93
+ /** Try `locator` first; fall back to AI resolution if it fails. */
94
+ locate(ctx: Page, locator: Locator, description: string): Promise<Locator>
95
+ /** Resolve an element from a natural-language description alone. */
96
+ find(ctx: Page, description: string): Promise<Locator>
97
+ /** Click with an optimized fallback (goto for links, JS click for buttons). */
98
+ click(ctx: Page, locator: Locator, description: string): Promise<void>
99
+ /** Low-level: wrap a single locator with AI fallback. Prefer wrapPage(). */
100
+ wrap(ctx: Page, locator: Locator, description: string): Locator
101
+ }
102
+
103
+ /** Create an Elementus instance with the given configuration. */
104
+ export function createElementus(options?: ElementusOptions): Elementus
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "elementus-ai",
3
- "version": "1.1.1",
3
+ "version": "1.4.0",
4
4
  "description": "Self-healing element resolution for Playwright, WDIO & Appium. AI-powered fallback when selectors break.",
5
5
  "main": "elementus.js",
6
+ "types": "index.d.ts",
6
7
  "scripts": {
7
8
  "test": "playwright test test/playwright.spec.js",
8
9
  "test:smoke": "playwright test test/playwright.spec.js -g \"T01 |T02 |T09 |T17 |T23 \""
@@ -51,8 +52,10 @@
51
52
  },
52
53
  "files": [
53
54
  "elementus.js",
55
+ "index.d.ts",
54
56
  "wdio.d.ts",
55
57
  "README.md",
58
+ "CHANGELOG.md",
56
59
  "LICENSE"
57
60
  ]
58
61
  }