lims-mcp
v0.1.3
Published
Locator Intelligence MCP Server — web-first AI locator generation, healing, and Playwright framework sync for Cursor.
Downloads
32
Maintainers
Readme
🔍 LIMS — Locator Intelligence MCP Server
Generate, validate, rank, and auto-heal UI locators for Playwright, Selenium, and Appium — directly inside Cursor AI.
📋 Table of Contents
| # | Section | What you'll find | |---|---|---| | 1 | What LIMS Does | Problem solved, core capabilities | | 2 | Quick Start | Install, build, connect to Cursor | | 3 | Architecture | How all pieces fit together | | 4 | The 7 MCP Tools | Every tool, its input, its output | | 5 | File Map | Every file explained — what it does, when to touch it | | 6 | Locator Priority Strategy | Why certain selectors win over others | | 7 | Confidence Score Explained | What 0.98 vs 0.61 actually means | | 8 | Ranking Weights | The scoring formula, every knob | | 9 | Customisation Guide | Where to change configs, priorities, thresholds | | 10 | Runtime Modes | Playwright MCP subprocess vs HTTP vs standalone | | 11 | Platform Support | Web, Android, iOS | | 12 | Using LIMS with Your Framework | Step-by-step guide with real paths | | 13 | Documentation Index | All docs with descriptions |
🎯 What LIMS Does
Writing automation code in Playwright, Selenium, or Appium has one recurring problem: finding locators that do not break.
LIMS solves this as an MCP server inside Cursor. You describe an element in plain language. LIMS opens the page, inspects the DOM, generates ranked locators ordered by stability, validates them against a real browser, writes your Page Object files, and heals them automatically when the UI changes.
You say: "Generate locators for the Login button on https://myapp.com"
LIMS: captures DOM + screenshot
runs 7 priority tiers of candidate generation
enforces: must match exactly 1 element
ranks by stability + uniqueness + readability
validates in real Chromium browser
returns ──────────────────────────────────────────────────────
{
bestLocator: page.getByTestId('login-btn') confidence: 0.98
fallbacks: [
page.getByRole('button', { name: 'Login' }) confidence: 0.91
page.locator('#login-submit') confidence: 0.85
//button[text()='Login'] confidence: 0.40
]
}
writes: login.locator.ts login.page.ts login.spec.ts⚡ Quick Start
Option A — Use via npx (recommended — no clone needed)
The easiest way on any machine. Requires Node 20 or 22.
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"Playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
},
"LIMS": {
"command": "npx",
"args": ["-y", "lims-mcp"],
"env": {
"LIMS_PLAYWRIGHT_MCP_COMMAND": "npx",
"LIMS_PLAYWRIGHT_MCP_ARGS": "[\"-y\", \"@playwright/mcp@latest\"]",
"LIMS_PLAYWRIGHT_AUTO_BRIDGE": "false",
"LIMS_LEARNING_ENABLED": "true",
"LIMS_ARTIFACTS_ENABLED": "true",
"LIMS_ARTIFACTS_DIR": "/Users/<your-username>/.lims/artifacts",
"LIMS_PLAYWRIGHT_MCP_TIMEOUT_MS": "10000",
"PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1"
}
}
}
}Replace
<your-username>with your system username. Playwright MCP requiresnpm install -g @playwright/mcp. If you skip it, LIMS falls back to local DOM mode automatically.
Option B — Clone and build locally (for development / contribution)
git clone https://github.com/imran-imz7/lims-mcp
cd lims-mcp
npm install
npm run build # compiles TypeScript → dist/
npm test # runs 67 tests — all should passThen in ~/.cursor/mcp.json use the local build:
{
"mcpServers": {
"LIMS": {
"command": "node",
"args": ["/absolute/path/to/lims-mcp/dist/cli.js"],
"env": {
"LIMS_PLAYWRIGHT_MCP_COMMAND": "npx",
"LIMS_PLAYWRIGHT_MCP_ARGS": "[\"-y\", \"@playwright/mcp@latest\"]",
"LIMS_LEARNING_ENABLED": "true",
"LIMS_ARTIFACTS_ENABLED": "true",
"PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1"
}
}
}
}3 — Restart Cursor and use it
"Generate Playwright locators and a test file for the login form at https://myapp.com"🏗 Architecture
flowchart LR
U["You / Tester"]
C["Cursor AI Agent"]
PW["Playwright MCP\n(Cursor's — browser control)"]
L["LIMS MCP Server"]
LP["LIMS internal\nplaywright-mcp\n(validation only)"]
AF[".lims/artifacts/\n{ref}.json per element"]
LS[".lims/locator-learning.json\nmax 1000 records"]
FS["Framework Files\nfeature.locator.ts\nfeature.page.ts\nfeature.spec.ts"]
U -->|"plain language prompts"| C
C -->|"browser_navigate\nbrowser_snapshot"| PW
PW -->|"DOM + accessibility tree"| C
C -->|"MCP tool calls"| L
L -->|"browser_run_code\n(validate + capture)"| LP
LP -->|"runtime results\ncaptured page data"| L
L --> AF
L --> LS
AF -->|"fingerprint reuse\nfor healing"| L
LS -->|"ranking bias\npreferred/failed/healed"| L
L -->|"write / merge\ninside lims blocks"| FS
FS -->|"playwright test run"| PW
PW -->|"pass / fail"| C
C -->|"report_locator_result"| LKey rule: Playwright MCP (Cursor's) and LIMS are isolated MCP processes — the Cursor agent bridges them. LIMS has its own separate internal playwright-mcp subprocess for locator validation only.
🔄 How It Works — Full Workflow
Step 1 — Capture & Generate
sequenceDiagram
actor U as "You / Tester"
participant C as "Cursor AI Agent"
participant L as "LIMS MCP Server"
participant PW as "Playwright MCP (Cursor's)"
participant LP as "LIMS Internal playwright-mcp"
participant S as ".lims/ Store"
participant FS as "Framework Files on Disk"
U ->> C : Describe the element to test
C ->> PW : browser_navigate(pageUrl)
PW -->> C : page loaded
C ->> PW : browser_snapshot()
PW -->> C : DOM + accessibility tree
C ->> L : generate_locator(dom, target, platform)
Note over L: Schema validation → LocatorEngine.generate<br/>Tier 1: data-testid / data-test / data-qa<br/>Tier 2: aria-label / role / getByRole<br/>Tier 3: id / name (non-dynamic only)<br/>Tier 4: placeholder / label[for]<br/>Tier 5: visible static text<br/>Tier 6: relative (near/above/below)<br/>Tier 7: XPath fallback<br/>Tier 8: class (last resort)
L ->> L : UniquenessEngine — reject if ≠ 1 match
L ->> L : RankingEngine — score each candidate
L ->> LP : validate top 8 candidates live in browser
LP -->> L : executed / unique / visible / interactable
L ->> L : ConfidenceFusionEngine.fuse<br/>dom×0.50 + visual×0.20 + runtime×0.30
L ->> S : saveArtifact → .lims/artifacts/{ref}.json
L -->> C : bestLocator + confidence + fallbacks + locatorCatalog
C ->> L : sync_playwright_framework(feature, locatorBindings, testCases)
L ->> FS : write feature.locator.ts
L ->> FS : write feature.page.ts
L ->> FS : write feature.spec.ts
L -->> C : written file pathsStep 2 — Test, Feedback & Self-Healing
sequenceDiagram
actor U as "You / Tester"
participant C as "Cursor AI Agent"
participant PW as "Playwright Test Runner"
participant L as "LIMS MCP Server"
participant LP as "LIMS Internal playwright-mcp"
participant S as ".lims/ Store"
participant FS as "Framework Files on Disk"
U ->> PW : npx playwright test feature.spec.ts
PW -->> C : pass OR fail + error message
C ->> L : report_locator_result(locator, status, artifactRef)
L ->> S : appendOutcome + recordOutcome in learning store
alt status = "passed"
L -->> C : learned:true ✓
else status = "failed" → self-heal
L ->> S : load original fingerprint + DOM from artifact
L ->> LP : capture current page DOM
LP -->> L : fresh DOM
L ->> L : SimilarityEngine.bestMatch<br/>tag×0.12 + text×0.28 + attrs×0.35 + hierarchy×0.25
L ->> L : LocatorEngine.generate on matched node
L ->> LP : validate healed locator live
LP -->> L : runtime result
L ->> S : appendOutcome(healed) + recordOutcome(healed)
L -->> C : healedLocator + confidence + diff + explanation
C ->> L : sync_playwright_framework (healed locator)
L ->> FS : update feature.locator.ts
L -->> C : written ✓
end🛠 The 7 MCP Tools
| Tool | When to use | Key inputs | Key outputs |
|---|---|---|---|
| capture_generate_locator | Point at a live URL — capture + generate in one call | pageUrl or runtimeContext.useCurrentPage=true, target | bestLocator, fallbacks, artifactRef |
| generate_locator | You already have the HTML/XML snapshot | dom or xml, platform, target | bestLocator, confidence, fallbacks, automation |
| heal_locator | A locator broke after a UI change | dom, oldLocator, optional fingerprint | healedLocator, confidence, diff, explanation |
| report_locator_result | After running your test — tell LIMS pass or fail | locator, status, artifactRef | learned, optional improved |
| sync_playwright_framework | Write Page Object files to disk | feature, language, locatorBindings | Written file paths: *.spec.*, *.page.*, *.locator.* |
| analyze_dom | Inspect a page before generating locators | dom or xml | framework, recommendedAttributes, stabilityReport |
| health_check | Verify server and all integrations are working | none | healthy, prerequisites, runtime, issues |
🗂 File Map
Every file in the project — what it does, when you need to open it.
Entry Points
src/index.ts MCP server startup, StdioServerTransport wiring
src/cli.ts CLI entry point (node dist/cli.js)
src/di/container.ts Composition root — wires every dependency togetherTo add a new dependency or swap an adapter:
src/di/container.ts
src/mcp/ — MCP Transport Layer
Rule: No business logic here. Only input/output schema and tool registration.
| File | Purpose | When to touch it |
|---|---|---|
| schemas.ts | Zod schemas for every tool input | Add or change a tool's input fields |
| register-tools.ts | Registers all 7 tools with the MCP server | Add a new MCP tool |
src/application/ — Use Cases (Orchestration)
Each file = one use case. Composes domain engines and integration adapters.
| File | Purpose | When to touch it |
|---|---|---|
| locator-generation.service.ts | Full pipeline: parse → resolve → generate → validate → fuse → catalog | Change the generation flow or add a new signal |
| locator-healing.service.ts | Healing flow: try old → fingerprint → similarity → regenerate → validate | Change how healing decisions are made |
| locator-analysis.service.ts | DOM analysis: framework detection + stability report | Change what analyze_dom returns |
| locator-capture.service.ts | Capture live page then generate | Change the capture + generate pipeline |
| locator-feedback.service.ts | Record pass/fail, trigger healing on failure | Change learning or feedback behaviour |
| playwright-framework-sync.service.ts | Write .spec, .page, .locator files | Change file-writing logic |
| health-check.service.ts | Check Playwright, Tesseract, runtime mode | Add new health checks |
src/domain/ — Core Intelligence
Framework-agnostic business logic. No MCP types. No file I/O.
Locator Generation
| File | Purpose | When to touch it |
|---|---|---|
| locator/locator-engine.ts | Main engine — runs all 7 priority tiers, deduplicates, enforces uniqueness, calls ranking | Change candidate generation logic |
| locator/locator-engine-patterns.ts | Pattern helpers for candidate extraction | Add new attribute patterns |
| locator/locator-engine-trading-helpers.ts | Trading/financial UI special cases | Add trading platform support |
| locator/locator-engine-runtime-uniqueness.ts | Runtime uniqueness bridge | Change uniqueness checking |
| locator/target-resolver.ts | Resolves target string/descriptor to a DOM node | Change how targets are identified |
| locator/multi-platform-codegen.ts | Generates platform-specific locator strings | Add Appium, Selenium, or custom formats |
| locator/ui-pattern-intelligence.ts | Detects UI patterns: tables, modals, forms | Add new UI pattern detection |
| locator/locator-extension.ts | LocatorCandidateProvider interface | Implement a custom locator provider |
Locator Providers (plug-in pattern)
| File | Purpose | Register in |
|---|---|---|
| locator/providers/ag-grid-provider.ts | AG Grid row/cell locators | src/di/container.ts |
| locator/providers/react-virtualized-provider.ts | React Virtualized list locators | src/di/container.ts |
| locator/providers/flutter-web-provider.ts | Flutter web element locators | src/di/container.ts |
To add a new framework provider: implement
LocatorCandidateProvider, add tocontainer.ts.
Scoring and Ranking
| File | Purpose | When to touch it |
|---|---|---|
| ranking-engine/ranking-engine.ts | Weighted ranking of candidates | Change how candidates are ordered |
| confidence/confidence-fusion-engine.ts | Fuses DOM score + visual + runtime into final confidence | Change how confidence is calculated |
| uniqueness-engine/uniqueness-engine.ts | Enforces 1-match rule per candidate | Change uniqueness enforcement |
| attribute-stability/attribute-stability-analyzer.ts | Scores attribute stability | Add new stable/unstable attribute rules |
| selector-validator/selector-validator.ts | Validates selector syntax | Add selector syntax rules |
Healing
| File | Purpose | When to touch it |
|---|---|---|
| similarity-engine/similarity-engine.ts | Structural similarity for healing (tag, text, attributes, hierarchy) | Change how broken locators are matched to new DOM |
| element-fingerprint/element-fingerprint.ts | Element fingerprint generation | Change what gets stored in an artifact |
Dynamic / Temporal
| File | Purpose | When to touch it |
|---|---|---|
| dynamic/temporal-stability-engine.ts | Compares snapshots across time, rates mutation | Change temporal stability scoring |
| dynamic/snapshot-comparator.ts | DOM diff between two snapshots | Change snapshot comparison logic |
| dynamic/index.ts | STATIC / SEMI_DYNAMIC / HIGHLY_DYNAMIC text classifier | Change dynamic text classification rules |
Visual / Chart
| File | Purpose | When to touch it |
|---|---|---|
| visual/visual-locator-engine.ts | Screenshot + OCR correlation | Change visual locator logic |
| visual/canvas-element-detector.ts | Canvas / WebGL / SVG detection | Add chart surface detection |
| visual/dom-correlation-engine.ts | Correlates DOM elements with visual regions | Change visual-DOM mapping |
Framework Detection and Codegen
| File | Purpose | When to touch it |
|---|---|---|
| framework-detector/framework-detector.ts | Detects React, Angular, Vue, etc. | Add new framework detection |
| framework-sync/playwright-framework-codegen.ts | Renders .spec, .page, .locator TypeScript/JavaScript | Change generated file templates |
Other Domain
| File | Purpose |
|---|---|
| heuristics/heuristics-engine.ts | DOM heuristics (placeholder, label, input type detection) |
| css-builder/css-builder.ts | Builds CSS selectors from element attributes |
| xpath-builder/xpath-builder.ts | Builds XPath expressions |
| relative-locator-engine/relative-locator-engine.ts | near(), above(), below() relative locators |
| trading/trading-ui-support.ts | Trading/financial dashboard special handling |
| runtime/playwright-validator.ts | Domain facade for runtime validation |
| contracts/types.ts | All shared TypeScript types |
| contracts/ports.ts | All interface contracts (ports) |
| plugin/plugin-registry.ts | Plugin registry wiring |
src/infrastructure/ — Storage, Config, Parsing
| File | Purpose | When to touch it |
|---|---|---|
| config/app-config.ts | All environment variable definitions and defaults | Add or change any config option |
| cache/locator-artifact-store.ts | Reads/writes .lims/artifacts/*.json | Change artifact format |
| cache/locator-learning-store.ts | Reads/writes .lims/locator-learning.json | Change learning data format |
| cache/memory-cache.ts | In-memory TTL cache | Change cache eviction |
| parsers/dom-repository.ts | Parses HTML/XML into Cheerio + xmldom | Change DOM parsing |
| files/text-file-repository.ts | Writes framework files to disk | Change file output |
| logging/pino-logger.ts | Structured JSON logging via pino | Change log format |
src/integrations/ — External Adapters
| File | Purpose | When to touch it |
|---|---|---|
| playwright/playwright-mcp-validator.adapter.ts | Connects to playwright-mcp via stdio or HTTP | Change how LIMS talks to playwright-mcp |
| playwright/playwright-validator.adapter.ts | Tries MCP → HTTP bridge → local DOM fallback | Change validation fallback chain |
| playwright/playwright-web-capture.adapter.ts | Local Playwright page capture | Change local capture logic |
| playwright/composite-page-capture.adapter.ts | Tries adapters in order (MCP first, local second) | Change capture priority |
| playwright/runtime-validator-bridge.ts | Optional local HTTP validation server | Change HTTP bridge behaviour |
| ocr/tesseract-cli-ocr.adapter.ts | Tesseract CLI for screenshot OCR | Swap OCR engine |
| vision/basic-screenshot-analyzer.ts | Screenshot analysis using OCR | Change screenshot analysis |
src/utils/ — Shared Utilities
| File | Purpose | When to touch it |
|---|---|---|
| utils/constants.ts | RANKING_WEIGHTS, STABILITY_SCORES, LENGTH_NORM_TARGET | Tune scoring weights |
| utils/locator-priority.ts | LOCATOR_PRIORITY_TIERS (1–8) | Change locator priority order |
| utils/scoring-utils.ts | clamp01, lengthScore helpers | Change scoring math |
| utils/regex-patterns.ts | Regex for dynamic ID detection, hash detection | Add new unstable ID patterns |
| utils/playwright-locator-parse.ts | Parse page.getByTestId(...) strings | Add new Playwright locator patterns |
| utils/errors.ts | DomainError class | Change error codes |
🏆 Locator Priority Strategy
LIMS always evaluates candidates in this strict tier order. Lower tier number = tried first = preferred when stable.
┌─────┬──────────────────────┬────────────────────────────────────────────┬────────────┐
│Tier │ Strategy │ Examples │ Stability │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 1 │ Test attributes │ data-testid="submit" │ ██████████ │
│ │ │ data-test="login-btn" │ Highest │
│ │ │ data-qa="email-field" │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 2 │ Accessibility │ aria-label="Close dialog" │ █████████░ │
│ │ │ role="button" + name="Submit" │ Very high │
│ │ │ getByRole('textbox', {name:'Email'}) │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 3 │ Structural anchors │ id="login-form" (non-dynamic only) │ ████████░░ │
│ │ │ name="email" │ High │
│ │ │ Dynamic IDs auto-rejected (hash/uuid/ts) │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 4 │ Form hints │ placeholder="Enter your email" │ ███████░░░ │
│ │ │ type="submit" │ Medium-high│
│ │ │ label[for=...] associations │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 5 │ Visible text │ getByText('Login') — STATIC text only │ ██████░░░░ │
│ │ │ SEMI_DYNAMIC → lower confidence │ Medium │
│ │ │ HIGHLY_DYNAMIC → ❌ rejected automatically │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 6 │ Relative locators │ near('Email label') │ █████░░░░░ │
│ │ │ above('Password field') │ Medium-low │
│ │ │ right-of('Submit') │ │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 7 │ XPath fallback │ //input[@type='email'] │ ███░░░░░░░ │
│ │ │ (constrained, not absolute paths) │ Low │
├─────┼──────────────────────┼────────────────────────────────────────────┼────────────┤
│ 8 │ CSS class (last) │ .btn-primary .submit-button │ █░░░░░░░░░ │
│ │ │ Utility classes (.flex, .p-4) rejected │ Lowest │
└─────┴──────────────────────┴────────────────────────────────────────────┴────────────┘Dynamic text classification — automatically applied before any text-based locator is used:
STATIC → safe to use as locator signal
e.g. "Login", "Submit", "Email address"
SEMI_DYNAMIC → used with reduced confidence
e.g. "Welcome, John" (changes per user)
HIGHLY_DYNAMIC → never used as locator signal ❌
e.g. "$1,234.56", "00:03:42", "Loading 87%..."To change the priority order: edit
src/utils/locator-priority.tsTo change dynamic text rules: editsrc/domain/dynamic/index.ts
📊 Confidence Score Explained
Every locator LIMS returns has a confidence between 0.0 and 1.0. Here is what that number means and how it is calculated.
What the number means
1.00 ──── Perfect. Unique, visible, interactable, test-attribute based.
│ Passes all runtime checks. Will not break on normal UI changes.
│
0.90 ──── Excellent. Aria or structural anchor, runtime validated.
│
0.80 ──── Good. Stable attribute but no test-id. Passes DOM uniqueness.
│
0.70 ──── Acceptable. Visible text or relative locator. Stable enough.
│
0.60 ──── Use with caution. Placeholder or weak anchor.
│
0.50 ──── Fragile. May break on minor UI refactor.
│
0.30 ──── Last resort. XPath or class-based. High break risk.
│
0.00 ──── Rejected. Not unique (matches >1 element) or not visible.How confidence is calculated
LIMS fuses three signals into one final score:
Final Confidence = (DOM score × 0.50)
+ (Visual score × 0.20) ← only if screenshot provided
+ (Runtime score × 0.30) ← only if browser validation ranDOM Score — from the ranking engine (5 weighted factors)
┌─────────────────────┬────────┬───────────────────────────────────────────┐
│ Factor │ Weight │ What it measures │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ Uniqueness │ 0.35 │ Matches exactly 1 element = 1.0 │
│ │ │ Matches >1 element = 0.0 (hard gate) │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ Attribute stability │ 0.25 │ STABLE attribute (testid, aria) = 1.0 │
│ │ │ SEMI_STABLE (id, name) = 0.55 │
│ │ │ UNSTABLE (dynamic class, index) = 0.0 │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ Readability │ 0.15 │ Human-readable selector = higher score │
│ │ │ Long XPath chain = lower score │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ Maintainability │ 0.15 │ No complex regex = 0.55 base │
│ │ │ No long chain (< 6 hops) = +0.45 │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ Length │ 0.10 │ Shorter locator = higher score │
│ │ │ Normalised against 120-char target │
└─────────────────────┴────────┴───────────────────────────────────────────┘Runtime Score — from live browser validation
┌─────────────────────┬────────┬───────────────────────────────────────────┐
│ Check │ Points │ What it means │
├─────────────────────┼────────┼───────────────────────────────────────────┤
│ executed │ +0.20 │ Browser validation actually ran │
│ unique │ +0.25 │ locator.count() === 1 in real browser │
│ visible │ +0.25 │ element.isVisible() === true │
│ interactable │ +0.20 │ click({trial:true}) did not throw │
│ success │ +0.10 │ All above passed together │
└─────────────────────┴────────┴───────────────────────────────────────────┘
Max runtime score = 1.0 (all five checks pass)Visual Score — from screenshot + OCR (when screenshot provided)
0.0 – 1.0 based on how closely the OCR-extracted text near the element
matches the target description. Higher = better visual match.Example: how a score of 0.94 is built
Element: Login button (data-testid="login-btn", visible, interactable)
DOM score:
uniqueness 1.0 × 0.35 = 0.350
attribute stab. 1.0 × 0.25 = 0.250 ← data-testid = STABLE
readability 0.9 × 0.15 = 0.135
maintainability 1.0 × 0.15 = 0.150
length 0.9 × 0.10 = 0.090
─────────────────────────────────────
DOM total = 0.975
Runtime score:
executed+unique+visible+interactable+success = 1.0
Final confidence:
(0.975 × 0.50) + (1.0 × 0.30) = 0.487 + 0.300 = 0.787
→ normalised → 0.94⚖️ Ranking Weights
All weights live in one file: src/utils/constants.ts
export const RANKING_WEIGHTS = {
uniqueness: 0.35, // ← most important: must match exactly 1 element
attributeStability: 0.25, // ← second: is the attribute type stable?
readability: 0.15, // ← human-readable selectors preferred
maintainability: 0.15, // ← avoid regex chains and long XPath
length: 0.10, // ← shorter is better
}
export const STABILITY_SCORES = {
STABLE: 1.00, // data-testid, aria-label, role
SEMI_STABLE: 0.55, // id, name, placeholder
UNSTABLE: 0.00, // dynamic classes, index-based, hash IDs
}
export const LENGTH_NORM_TARGET = 120 // chars — locator longer than this scores 0 on lengthTo make uniqueness even more dominant: increase uniqueness weight and decrease others (all must sum to 1.0).
To favour shorter locators more: increase length weight.
To treat id as stable (not semi-stable): change SEMI_STABLE to 1.00 — but only if your app uses consistent IDs.
⚙️ Customisation Guide
Where to change what — quick reference
WHAT YOU WANT TO CHANGE WHERE TO CHANGE IT
──────────────────────────────────────────────────────────────────────
Add/change config env variable src/infrastructure/config/app-config.ts
Change locator priority order src/utils/locator-priority.ts
Tune confidence/ranking weights src/utils/constants.ts
Change dynamic text classification src/domain/dynamic/index.ts
Add a new locator provider (AG Grid etc.) src/domain/locator/providers/
+ register in src/di/container.ts
Change healing similarity logic src/domain/similarity-engine/similarity-engine.ts
Change generated file templates src/domain/framework-sync/playwright-framework-codegen.ts
Add a new MCP tool src/mcp/schemas.ts
+ src/mcp/register-tools.ts
+ new service in src/application/
Change Playwright connection mode src/infrastructure/config/app-config.ts
(LIMS_PLAYWRIGHT_MCP_URL or COMMAND)
Change artifact storage location LIMS_ARTIFACTS_DIR env var
Change learning store location LIMS_LEARNING_STORE_PATH env varAll environment variables
| Variable | Default | What it controls |
|---|---|---|
| LIMS_PLAYWRIGHT_MCP_URL | (none) | HTTP URL of a shared playwright-mcp server (e.g. http://localhost:8931/mcp) |
| LIMS_PLAYWRIGHT_MCP_COMMAND | (none) | Command to spawn playwright-mcp as subprocess (e.g. playwright-mcp) |
| LIMS_PLAYWRIGHT_MCP_ARGS | [] | JSON array of args for subprocess (e.g. ["--headless","--isolated"]) |
| LIMS_PLAYWRIGHT_MCP_TIMEOUT_MS | 7000 | Timeout per playwright-mcp call in ms |
| LIMS_PLAYWRIGHT_VALIDATOR_URL | (none) | URL of a custom HTTP validation endpoint |
| LIMS_PLAYWRIGHT_AUTO_BRIDGE | true | Auto-start local HTTP validation bridge when no MCP |
| LIMS_PLAYWRIGHT_BRIDGE_PORT | 4010 | Port for the auto bridge |
| LIMS_LEARNING_ENABLED | true | Record pass/fail outcomes for future ranking bias |
| LIMS_LEARNING_STORE_PATH | .lims/locator-learning.json | Path to learning history file |
| LIMS_ARTIFACTS_ENABLED | true | Save DOM + locator snapshots for healing |
| LIMS_ARTIFACTS_DIR | .lims/artifacts | Directory for artifact JSON files |
| LIMS_LOG_LEVEL | info | Log verbosity: debug, info, warn, error |
| LIMS_CACHE_TTL_MS | 60000 | In-memory DOM cache TTL in ms |
| LIMS_CACHE_MAX_ENTRIES | 500 | Max entries in in-memory DOM cache |
| LIMS_HEALTH_PROFILE | balanced | Health check mode: balanced, dom-first, screenshot-first |
🔌 Runtime Modes
LIMS picks a runtime validation mode automatically based on what is configured. Priority: URL > command > standalone.
┌─────┬──────────────────────────────┬───────────────────────────────────────────────┐
│ Pri │ Mode │ How to activate │
├─────┼──────────────────────────────┼───────────────────────────────────────────────┤
│ 1 │ HTTP — shared server │ Set LIMS_PLAYWRIGHT_MCP_URL │
│ │ One browser for Cursor+LIMS │ Run: npx @playwright/mcp --headless --port 8931│
│ │ Most efficient │ Cursor: { "url": "http://localhost:8931/mcp" }│
├─────┼──────────────────────────────┼───────────────────────────────────────────────┤
│ 2 │ stdio — LIMS subprocess │ Set LIMS_PLAYWRIGHT_MCP_COMMAND="npx" │
│ │ LIMS owns its browser │ LIMS_PLAYWRIGHT_MCP_ARGS='["-y", │
│ │ Fully self-contained │ "@playwright/mcp@latest"]' │
│ │ ✅ Recommended default │ Use same version as Cursor (e.g. @0.0.70) │
├─────┼──────────────────────────────┼───────────────────────────────────────────────┤
│ 3 │ Standalone — local DOM │ Set neither of the above │
│ │ No browser needed │ Works offline, works in CI, less accurate │
│ │ Always available │ Uses Cheerio + XPath on static HTML │
└─────┴──────────────────────────────┴───────────────────────────────────────────────┘Common mistake: Setting
LIMS_PLAYWRIGHT_MCP_COMMAND="playwright-mcp"(bare binary name) fails unlessplaywright-mcpis installed globally vianpm install -g @playwright/mcp. Usenpxinstead — it works everywhere without a global install.
If Playwright MCP is configured but unreachable (wrong command, timeout, crash), LIMS catches the error and falls back to Mode 3 automatically. It never crashes on startup.
📱 Platform Support
| Platform | Locator Generation | Self-Healing | Live Capture | Framework File Sync |
|---|---|---|---|---|
| Playwright (web) | ✅ Full | ✅ Full | ✅ Full — Chromium | ✅ .spec.ts + .page.ts + .locator.ts |
| Selenium (web) | ✅ CSS + XPath output | ✅ Full | Snapshot-based | Manual wiring |
| Appium Android | ✅ From UIAutomator XML | ✅ Snapshot-based | Snapshot-based | Not yet |
| Appium iOS | ✅ From XCUI XML | ✅ Snapshot-based | Snapshot-based | Not yet |
📚 Documentation Index
| Document | What it covers | |---|---| | docs/END_TO_END_GUIDE.md | Start here. Complete walkthrough with every step explained, backend internals, common questions | | docs/API_SPEC.md | Every MCP tool — exact inputs, exact outputs, scope notes | | docs/ARCHITECTURE.md | Layer boundaries, dependency rules, main flows | | docs/WORKFLOW_DIAGRAM.md | Mermaid sequence diagrams of capture → generate → sync → heal | | docs/MCP_WORKING_GUIDE.md | Quick daily reference — which tool to call when | | docs/PLAYWRIGHT_INTEGRATION.md | Playwright MCP configuration and integration details |
🚀 Using LIMS with Your Playwright Framework
This section shows how to use LIMS inside an existing Playwright JS/TS project to generate locators and write framework files directly into your folder structure.
Step 1 — Confirm LIMS is active
In Cursor chat, type:
Call health_check on LIMSYou must see "status": "LIMS_ACTIVE" before doing anything else. If you don't — fully quit Cursor (Cmd+Q / Alt+F4) and reopen it.
Step 2 — Generate a locator and write files to your framework
Paste this prompt in Cursor chat (fill in your values):
Use LIMS for my Playwright JS automation framework.
Step 1: browser_navigate to https://your-app.com/page
Step 2: browser_snapshot to capture the DOM
Step 3: call generate_locator with the captured DOM, platform "web",
target "<describe the element, e.g. Login button>"
Step 4: call sync_playwright_framework with:
feature: "login"
language: "js"
outputDir: "/absolute/path/to/your-framework/tests"
specDir: "specs"
pageDir: "pages"
locatorDir: "locators"
pageUrl: "https://your-app.com/page"
testCases: ["User can log in with valid credentials"]LIMS will create exactly these files:
tests/
├── locators/
│ └── login.locator.js ← generated selectors
├── pages/
│ └── login.page.js ← page object with methods
└── specs/
└── login.spec.js ← ready-to-run testStep 3 — Run your tests
npx playwright test tests/specs/login.spec.jsStep 4 — Report result back to LIMS (closes the learning loop)
Call report_locator_result on LIMS:
locator: "<bestLocator from Step 3 response>"
status: "passed"
platform: "web"
artifactRef: "<ref returned by LIMS in Step 3>"If the test failed — LIMS automatically heals the locator, regenerates it, and updates the file. You just re-run the test.
sync_playwright_framework — Path Parameters Reference
| Parameter | What it controls | Example |
|---|---|---|
| outputDir | Root of your framework | /Users/you/my-framework/tests |
| specDir | Sub-folder for spec files | specs |
| pageDir | Sub-folder for page objects | pages |
| locatorDir | Sub-folder for locator files | locators |
| language | "js" or "ts" | "js" |
| feature | Base name for all three files | "login" → login.spec.js etc. |
All three directories are created automatically if they don't exist.
Custom code you add outside the <lims:spec> / <lims:page> / <lims:locator> markers
is never overwritten on re-sync.
Node.js Version Requirement
node --version # must be v20.x.x or v22.x.xLIMS depends on
@modelcontextprotocol/sdkwhich uses theFileWeb API.
This API is only available in Node 20+. Node 18 is not supported.
If you are on Node 18:
nvm install 20 && nvm use 20 && nvm alias default 20🧪 Verification
npm run build # must exit 0
npm test # 67 tests, all must pass