{"repo": "alucard1123/needle", "files": {"README.md": {"ca3a44228a3d3827df185177a6356a72617a52a8": {"t": 1785674104.961423, "n": 7928, "h": "901d033883c5aa31", "c": "# needle\n\n[![PyPI](https://img.shields.io/pypi/v/needle.svg)](https://pypi.org/project/needle-fixer/)\n[![Python](https://img.shields.io/pypi/pyversions/needle.svg)](https://pypi.org/project/needle-fixer/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Tests](https://img.shields.io/badge/tests-pytest-blue.svg)](./tests)\n\n> 自动化测试异常自愈(Self-Healing)库。\n>\n> 当 Playwright / Selenium / 任意自动化脚本抛出异常时,`needle` 会按「责任链」模式自动寻找并执行可用的修复策略,把原本会失败的用例救回来。\n\n```python\nfrom needle import ExceptionHandler\nfrom needle.builtin import register_builtins\n\nregister_builtins() # 注册内置修复策略(可选)\n\ntry:\n page.get_by_role(\"button\", name=\"SEARCH\").click(timeout=5000)\nexcept Exception as e:\n ExceptionHandler(page=page).handle_exception(e)\n # 修复成功:正常返回;全部失败:抛出 RepairFailedException\n```\n\n---\n\n## 目录\n\n- [needle](#needle)\n - [目录](#目录)\n - [特性](#特性)\n - [安装](#安装)\n - [快速开始](#快速开始)\n - [1. 模块级便捷函数](#1-模块级便捷函数)\n - [2. 类级入口](#2-类级入口)\n - [3. 装饰器模式](#3-装饰器模式)\n - [核心概念](#核心概念)\n - [内置修复策略](#内置修复策略)\n - [自定义修复策略](#自定义修复策略)\n - [注入自定义后端](#注入自定义后端)\n - [默认后端的实例化](#默认后端的实例化)\n - [自定义 AI 客户端(ByPromptSolution 必须)](#自定义-ai-客户端bypromptsolution-必须)\n - [内置 Kimi / Moonshot 客户端](#内置-kimi--moonshot-客户端)\n - [装饰器模式](#装饰器模式)\n - [开发与测试](#开发与测试)\n - [许可证](#许可证)\n - [关于作者](#关于作者)\n\n---\n\n## 特性\n\n- **核心零依赖**:`handler` / `registry` / 基类 / 异常 / 装饰器 仅使用 Python 标准库。\n- **责任链调度**:按 `PRIORITY` 自动排序,逐个尝试 `can_fix → fix`,单个策略失败不会打断后续策略。\n- **Playwright 原生友好**:自动解析 `Locator` 超时异常,提取原始 locator、操作名与调用参数,修复后可保持参数一致重试。\n- **多种内置策略**:缓存、图像模板匹配、AI 提示词、环境型超时处理,按需安装依赖。\n- **可插拔后端**:重依赖(OpenCV、LLM 客户端)通过协议注入,可替换为项目自有实现。\n- **易于扩展**:继承 `NeedleSolution` 实现 `can_fix` / `fix`,注册即用。\n\n---\n\n## 安装\n\n```bash\n# 仅安装核心(零依赖)\npip install needle-fixer\n\n# 图像策略(OpenCV 模板匹配)\npip install \"needle-fixer[image]\"\n\n# AI 策略(内置 Kimi / Moonshot 客户端)\npip install \"needle-fixer[ai]\"\n\n# Playwright 运行时(如项目尚未安装)\npip install \"needle-fixer[playwright]\"\n\n# 全部可选依赖\npip install \"needle-fixer[all]\"\n\n# 开发依赖\npip install \"needle-fixer[dev]\"\n```\n\n> **注意**:缓存策略基于 JSON 文件,零依赖,已包含在核心包中。\n\n---\n\n## 快速开始\n\n### 1. 模块级便捷函数\n\n```python\nfrom needle import handle_exception\nfrom needle.builtin import register_builtins\n\nregister_builtins()\n\ntry:\n page.get_by_role(\"button\", name=\"SEARCH\").click(timeout=5000)\nexcept Exception as e:\n handle_exception(e, page=page)\n```\n\n### 2. 类级入口\n\n```python\nfrom needle import ExceptionHandler\nfrom needle.builtin import register_builtins\n\nregister_builtins()\n\nhandler = ExceptionHandler(page=page)\ntry:\n page.get_by_label(\"Name\").fill(\"needle\")\nexcept Exception as e:\n handler.handle_exception(e)\n```\n\n### 3. 装饰器模式\n\n```python\nfrom needle import with_recovery\n\n@with_recovery(\n context_extractor=lambda page, locator, **kw: {\n \"page\": page,\n \"locator\": locator,\n **kw,\n }\n)\ndef click_element(page, locator):\n page.locator(locator).click()\n\nclick_element(page, \"#submit\")\n```\n\n---\n\n## 核心概念\n\n```\nExceptionHandler.handle_exception(e)\n └─ ContextBuilder.build(e) # 异常 → 修复上下文 dict(可替换)\n └─ RepairFailedException.attempt_recovery()\n └─ SolutionRegistry.create_chain(ctx) # 按 PRIORITY 组装责任链\n └─ NeedleSolution.handle(ctx) # 逐个 can_fix → fix\n```\n\n| 组件 | 职责 |\n|------|------|\n| `ExceptionHandler` | 统一入口,负责构建上下文并触发修复。 |\n| `ContextBuilder` | 把原始异常翻译成修复策略可消费的 `dict`。内置 `PlaywrightContextBuilder` 与 `DefaultContextBuilder`。 |\n| `SolutionRegistry` | 策略注册中心,维护优先级排序,负责建链。 |\n| `NeedleSolution` | 修复策略基类,子类实现 `can_fix` / `fix`。 |\n| `RepairFailedException` | 修复失败时抛出,通过 `__cause__` 保留原始异常。 |\n\n---\n\n## 内置修复策略\n\n| 策略 | 优先级 | 说明 | 依赖 |\n|------|--------|------|------|\n| `ByCacheSolution` | 10 | 从 JSON 缓存读取备选 locator 逐个尝试。 | 无 |\n| `ByImageSolution` | 20 | OpenCV 模板匹配,按坐标定位元素。 | `opencv-python`, `numpy`, `Pillow` |\n| `ByPromptSolution` | 30 | 调用 AI 分析 DOM,给出新 locator。 | `httpx` |\n| `TimeoutSolution` | 50 | 处理环境型超时(如页面出现 waiting 提示)。 | 无 |\n\n使用全部内置策略:\n\n```python\nfrom needle.builtin import register_builtins\n\nregister_builtins()\n```\n\n或单独注册:\n\n```python\nfrom needle.core.registry import SolutionRegistry\nfrom needle.builtin import ByCacheSolution, ByImageSolution\n\nSolutionRegistry.register(ByCacheSolution)\nSolutionRegistry.register(ByImageSolution)\n```\n\n---\n\n## 自定义修复策略\n\n只需继承 `NeedleSolution` 并实现 `can_fix` / `fix`,然后注册:\n\n```python\nfrom needle import NeedleSolution, register_solution\n\n@register_solution\nclass MyRetrySolution(NeedleSolution):\n PRIORITY = 5 # 数字越小越先尝试\n\n def can_fix(self) -> bool:\n return \"locator\" in self.context\n\n def fix(self) -> bool:\n page, locator = self.context[\"page\"], self.context[\"locator\"]\n page.locator(locator).click()\n self.context[\"fixed_element\"] = locator # 可选:记录修复结果\n return True\n```\n\n---\n\n## 注入自定义后端\n\n内置策略的重依赖通过协议注入,可用宿主项目自己的实现替换默认实现:\n\n```python\nfrom needle.builtin import ByCacheSolution, ByImageSolution, ByPromptSolution\n\nByCacheSolution.configure(my_cache_backend) # CacheBackend\nByImageSolution.configure(my_image_matcher) # ImageMatcher\nByPromptSolution.configure(my_ai_client) # AIClient\n```\n\n### 默认后端的实例化\n\n```python\nfrom needle.builtin import (\n ByCacheSolution,\n ByImageSolution,\n ByPromptSolution,\n PickleDBCacheBackend,\n OpenCVImageMatcher,\n)\n\n# 1. 缓存后端:JSON 文件,key 为原 locator,value 为备选 locator 列表\nmy_cache_backend = PickleDBCacheBackend(db_path=\"needle_locator_cache.db\")\nByCacheSolution.configure(my_cache_backend)\n\n# 2. 图像匹配后端:OpenCV 模板匹配\nmy_image_matcher = OpenCVImageMatcher(\n image_dir=\"image_locator\", # 模板图片目录\n threshold=0.8, # 匹配阈值,范围 0~1\n)\nByImageSolution.configure(my_image_matcher)\n```\n\n### 自定义 AI 客户端(ByPromptSolution 必须)\n\n`needle` 没有内置通用 LLM 客户端,需要你自己实现 `AIClient` 协议:\n\n```python\nfrom typing import Optional\n\nclass MyAIClient:\n def analyze(self, html: str, description: str) -> Optional[str]:\n \"\"\"根据页面 HTML 和描述返回一个可用的 Playwright locator 表达式。\n\n 返回 None 表示无法给出建议;返回的字符串会被写入 context[\"fixed_element\"]。\n \"\"\"\n prompt = (\n f\"根据以下页面 HTML,给出一个能定位到「{description}」的 \"\n f\"Playwright locator 表达式。只返回表达式本身,不要任何解释。\\n\\n\"\n f\"{html[:8000]}\"\n )\n # 这里接入你实际使用的 LLM(OpenAI / Kimi / Claude / 自部署模型等)\n # response = call_your_llm(prompt)\n # return response.strip()\n raise NotImplementedError(\"请接入实际的 LLM API\")\n\n\nmy_ai_client = MyAIClient(api_key=\"sk-xxx\")\nByPromptSolution.configure(my_ai_client)\n```\n\n### 内置 Kimi / Moonshot 客户端\n\n`needle` 内置了一个基于 Moonshot AI(Kimi)的 `AIClient` 实现,安装 `ai` 依赖后即可使用:\n\n```bash\npip install \"needle[ai]\"\n```\n\n```python\nfrom needle.builtin import ByPromptSolution, KimiClient\n\nmy_ai_client = KimiClient(\n api_key=\"sk-xxxxxxxxxxxxxxxx\", # Moonshot API Key\n model=\"moonshot-v1-8k\", # 可选:moonshot-v1-32k / moonshot-v1-128k\n base_url=\"https://api.moonshot.cn/v1\",\n temperature=0.1,\n max_tokens=512,\n)\nByPromptSolution.configure(my_ai_client)\n```\n\n> **注意**:`ByPromptSolution` 在未配置 `AIClient` 时会直接跳过,因此如果只使用缓存/图像策略,可以不实现 AI 客户端。\n\n---\n\n## 装饰器模式\n\n`with_recovery` 可以把自动修复能力注入到任意函数:\n\n```python\nfrom needle import with_recovery\n\n@with_recovery(\n context_extractor=lambda page, locator, **kw: {\n \"page\": page,\n \"locator\": locator,\n **kw,\n }\n)\ndef click_element(page, locator):\n page.locator(locator).click()\n```\n\n- `context_extractor`:从被装饰函数参数中提取修复上下文,返回 `dict`。\n- `reraise_on_failure`:修复失败时是否重新抛出 `RepairFailedException`(默认 `True`)。\n\n---\n\n## 开发与测试\n\n```bash\n# 克隆仓库\ngit clone https://github.com/alucard1123/needle\ncd needle\n\n# 创建虚拟环境并安装开发依赖\npython -m venv .venv\nsource .venv/bin/activate\npip install -e \".[dev]\"\n\n# 运行测试\npytest\n\n# 运行测试并生成覆盖率报告\npytest --cov=needle --cov-report=term-missing\n```\n\n---\n\n## 许可证\n\n[MIT](./LICENSE)\n\n## 关于作者\n公众号: 中年老吴\n\n\"公众号二维码\"\n\n赏老吴杯咖啡:\n\n\"赏老吴杯咖啡\"\n\n找老吴私聊:\n\n\"找老吴私聊\"\n"}}}}