npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

lims-mcp

v0.1.3

Published

Locator Intelligence MCP Server — web-first AI locator generation, healing, and Playwright framework sync for Cursor.

Downloads

32

Readme

🔍 LIMS — Locator Intelligence MCP Server

Generate, validate, rank, and auto-heal UI locators for Playwright, Selenium, and Appium — directly inside Cursor AI.

Build Tests Node License MCP


📋 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 requires npm 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 pass

Then 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"| L

Key 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 paths

Step 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 together

To 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 to container.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.ts To change dynamic text rules: edit src/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 ran

DOM 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 length

To 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 var

All 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 unless playwright-mcp is installed globally via npm install -g @playwright/mcp. Use npx instead — 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 LIMS

You 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 test

Step 3 — Run your tests

npx playwright test tests/specs/login.spec.js

Step 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.x

LIMS depends on @modelcontextprotocol/sdk which uses the File Web 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