needle-fixer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. needle_fixer-0.1.0/LICENSE +21 -0
  2. needle_fixer-0.1.0/PKG-INFO +400 -0
  3. needle_fixer-0.1.0/README.md +358 -0
  4. needle_fixer-0.1.0/needle/__init__.py +39 -0
  5. needle_fixer-0.1.0/needle/builtin/__init__.py +41 -0
  6. needle_fixer-0.1.0/needle/builtin/backends.py +122 -0
  7. needle_fixer-0.1.0/needle/builtin/kimi_client.py +312 -0
  8. needle_fixer-0.1.0/needle/builtin/locator_solutions.py +206 -0
  9. needle_fixer-0.1.0/needle/builtin/timeout_solution.py +49 -0
  10. needle_fixer-0.1.0/needle/core/__init__.py +24 -0
  11. needle_fixer-0.1.0/needle/core/context.py +265 -0
  12. needle_fixer-0.1.0/needle/core/decorator.py +71 -0
  13. needle_fixer-0.1.0/needle/core/exceptions.py +50 -0
  14. needle_fixer-0.1.0/needle/core/handler.py +80 -0
  15. needle_fixer-0.1.0/needle/core/registry.py +126 -0
  16. needle_fixer-0.1.0/needle/core/solution.py +60 -0
  17. needle_fixer-0.1.0/needle_fixer.egg-info/PKG-INFO +400 -0
  18. needle_fixer-0.1.0/needle_fixer.egg-info/SOURCES.txt +27 -0
  19. needle_fixer-0.1.0/needle_fixer.egg-info/dependency_links.txt +1 -0
  20. needle_fixer-0.1.0/needle_fixer.egg-info/requires.txt +21 -0
  21. needle_fixer-0.1.0/needle_fixer.egg-info/top_level.txt +1 -0
  22. needle_fixer-0.1.0/pyproject.toml +47 -0
  23. needle_fixer-0.1.0/setup.cfg +4 -0
  24. needle_fixer-0.1.0/tests/test_custom_solution.py +53 -0
  25. needle_fixer-0.1.0/tests/test_handler.py +209 -0
  26. needle_fixer-0.1.0/tests/test_kimi_client.py +205 -0
  27. needle_fixer-0.1.0/tests/test_locator_solutions.py +270 -0
  28. needle_fixer-0.1.0/tests/test_recovery.py +89 -0
  29. needle_fixer-0.1.0/tests/test_registry.py +82 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 needle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: needle-fixer
3
+ Version: 0.1.0
4
+ Summary: 自动化测试异常自愈库:handle_exception → 责任链查找 NeedleSolution 自动修复
5
+ Author: laowooooo
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/alucard1123/needle
8
+ Project-URL: Repository, https://github.com/alucard1123/needle
9
+ Project-URL: Issues, https://github.com/alucard1123/needle/issues
10
+ Keywords: testing,playwright,exception,self-healing,recovery,automation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: image
26
+ Requires-Dist: opencv-python; extra == "image"
27
+ Requires-Dist: numpy; extra == "image"
28
+ Requires-Dist: Pillow; extra == "image"
29
+ Provides-Extra: ai
30
+ Requires-Dist: httpx>=0.25; extra == "ai"
31
+ Provides-Extra: playwright
32
+ Requires-Dist: playwright>=1.40; extra == "playwright"
33
+ Provides-Extra: all
34
+ Requires-Dist: opencv-python; extra == "all"
35
+ Requires-Dist: numpy; extra == "all"
36
+ Requires-Dist: Pillow; extra == "all"
37
+ Requires-Dist: httpx>=0.25; extra == "all"
38
+ Requires-Dist: playwright>=1.40; extra == "all"
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest>=7.0; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # needle
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/needle.svg)](https://pypi.org/project/needle/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/needle.svg)](https://pypi.org/project/needle/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
+ [![Tests](https://img.shields.io/badge/tests-pytest-blue.svg)](./tests)
49
+
50
+ > 自动化测试异常自愈(Self-Healing)库。
51
+ >
52
+ > 当 Playwright / Selenium / 任意自动化脚本抛出异常时,`needle` 会按「责任链」模式自动寻找并执行可用的修复策略,把原本会失败的用例救回来。
53
+
54
+ ```python
55
+ from needle import ExceptionHandler
56
+ from needle.builtin import register_builtins
57
+
58
+ register_builtins() # 注册内置修复策略(可选)
59
+
60
+ try:
61
+ page.get_by_role("button", name="SEARCH").click(timeout=5000)
62
+ except Exception as e:
63
+ ExceptionHandler(page=page).handle_exception(e)
64
+ # 修复成功:正常返回;全部失败:抛出 RepairFailedException
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 目录
70
+
71
+ - [needle](#needle)
72
+ - [目录](#目录)
73
+ - [特性](#特性)
74
+ - [安装](#安装)
75
+ - [快速开始](#快速开始)
76
+ - [1. 模块级便捷函数](#1-模块级便捷函数)
77
+ - [2. 类级入口](#2-类级入口)
78
+ - [3. 装饰器模式](#3-装饰器模式)
79
+ - [核心概念](#核心概念)
80
+ - [内置修复策略](#内置修复策略)
81
+ - [自定义修复策略](#自定义修复策略)
82
+ - [注入自定义后端](#注入自定义后端)
83
+ - [默认后端的实例化](#默认后端的实例化)
84
+ - [自定义 AI 客户端(ByPromptSolution 必须)](#自定义-ai-客户端bypromptsolution-必须)
85
+ - [内置 Kimi / Moonshot 客户端](#内置-kimi--moonshot-客户端)
86
+ - [装饰器模式](#装饰器模式)
87
+ - [开发与测试](#开发与测试)
88
+ - [许可证](#许可证)
89
+ - [关于作者](#关于作者)
90
+
91
+ ---
92
+
93
+ ## 特性
94
+
95
+ - **核心零依赖**:`handler` / `registry` / 基类 / 异常 / 装饰器 仅使用 Python 标准库。
96
+ - **责任链调度**:按 `PRIORITY` 自动排序,逐个尝试 `can_fix → fix`,单个策略失败不会打断后续策略。
97
+ - **Playwright 原生友好**:自动解析 `Locator` 超时异常,提取原始 locator、操作名与调用参数,修复后可保持参数一致重试。
98
+ - **多种内置策略**:缓存、图像模板匹配、AI 提示词、环境型超时处理,按需安装依赖。
99
+ - **可插拔后端**:重依赖(OpenCV、LLM 客户端)通过协议注入,可替换为项目自有实现。
100
+ - **易于扩展**:继承 `NeedleSolution` 实现 `can_fix` / `fix`,注册即用。
101
+
102
+ ---
103
+
104
+ ## 安装
105
+
106
+ ```bash
107
+ # 仅安装核心(零依赖)
108
+ pip install needle-fixer
109
+
110
+ # 图像策略(OpenCV 模板匹配)
111
+ pip install "needle-fixer[image]"
112
+
113
+ # AI 策略(内置 Kimi / Moonshot 客户端)
114
+ pip install "needle-fixer[ai]"
115
+
116
+ # Playwright 运行时(如项目尚未安装)
117
+ pip install "needle-fixer[playwright]"
118
+
119
+ # 全部可选依赖
120
+ pip install "needle-fixer[all]"
121
+
122
+ # 开发依赖
123
+ pip install "needle-fixer[dev]"
124
+ ```
125
+
126
+ > **注意**:缓存策略基于 JSON 文件,零依赖,已包含在核心包中。
127
+
128
+ ---
129
+
130
+ ## 快速开始
131
+
132
+ ### 1. 模块级便捷函数
133
+
134
+ ```python
135
+ from needle import handle_exception
136
+ from needle.builtin import register_builtins
137
+
138
+ register_builtins()
139
+
140
+ try:
141
+ page.get_by_role("button", name="SEARCH").click(timeout=5000)
142
+ except Exception as e:
143
+ handle_exception(e, page=page)
144
+ ```
145
+
146
+ ### 2. 类级入口
147
+
148
+ ```python
149
+ from needle import ExceptionHandler
150
+ from needle.builtin import register_builtins
151
+
152
+ register_builtins()
153
+
154
+ handler = ExceptionHandler(page=page)
155
+ try:
156
+ page.get_by_label("Name").fill("needle")
157
+ except Exception as e:
158
+ handler.handle_exception(e)
159
+ ```
160
+
161
+ ### 3. 装饰器模式
162
+
163
+ ```python
164
+ from needle import with_recovery
165
+
166
+ @with_recovery(
167
+ context_extractor=lambda page, locator, **kw: {
168
+ "page": page,
169
+ "locator": locator,
170
+ **kw,
171
+ }
172
+ )
173
+ def click_element(page, locator):
174
+ page.locator(locator).click()
175
+
176
+ click_element(page, "#submit")
177
+ ```
178
+
179
+ ---
180
+
181
+ ## 核心概念
182
+
183
+ ```
184
+ ExceptionHandler.handle_exception(e)
185
+ └─ ContextBuilder.build(e) # 异常 → 修复上下文 dict(可替换)
186
+ └─ RepairFailedException.attempt_recovery()
187
+ └─ SolutionRegistry.create_chain(ctx) # 按 PRIORITY 组装责任链
188
+ └─ NeedleSolution.handle(ctx) # 逐个 can_fix → fix
189
+ ```
190
+
191
+ | 组件 | 职责 |
192
+ |------|------|
193
+ | `ExceptionHandler` | 统一入口,负责构建上下文并触发修复。 |
194
+ | `ContextBuilder` | 把原始异常翻译成修复策略可消费的 `dict`。内置 `PlaywrightContextBuilder` 与 `DefaultContextBuilder`。 |
195
+ | `SolutionRegistry` | 策略注册中心,维护优先级排序,负责建链。 |
196
+ | `NeedleSolution` | 修复策略基类,子类实现 `can_fix` / `fix`。 |
197
+ | `RepairFailedException` | 修复失败时抛出,通过 `__cause__` 保留原始异常。 |
198
+
199
+ ---
200
+
201
+ ## 内置修复策略
202
+
203
+ | 策略 | 优先级 | 说明 | 依赖 |
204
+ |------|--------|------|------|
205
+ | `ByCacheSolution` | 10 | 从 JSON 缓存读取备选 locator 逐个尝试。 | 无 |
206
+ | `ByImageSolution` | 20 | OpenCV 模板匹配,按坐标定位元素。 | `opencv-python`, `numpy`, `Pillow` |
207
+ | `ByPromptSolution` | 30 | 调用 AI 分析 DOM,给出新 locator。 | `httpx` |
208
+ | `TimeoutSolution` | 50 | 处理环境型超时(如页面出现 waiting 提示)。 | 无 |
209
+
210
+ 使用全部内置策略:
211
+
212
+ ```python
213
+ from needle.builtin import register_builtins
214
+
215
+ register_builtins()
216
+ ```
217
+
218
+ 或单独注册:
219
+
220
+ ```python
221
+ from needle.core.registry import SolutionRegistry
222
+ from needle.builtin import ByCacheSolution, ByImageSolution
223
+
224
+ SolutionRegistry.register(ByCacheSolution)
225
+ SolutionRegistry.register(ByImageSolution)
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 自定义修复策略
231
+
232
+ 只需继承 `NeedleSolution` 并实现 `can_fix` / `fix`,然后注册:
233
+
234
+ ```python
235
+ from needle import NeedleSolution, register_solution
236
+
237
+ @register_solution
238
+ class MyRetrySolution(NeedleSolution):
239
+ PRIORITY = 5 # 数字越小越先尝试
240
+
241
+ def can_fix(self) -> bool:
242
+ return "locator" in self.context
243
+
244
+ def fix(self) -> bool:
245
+ page, locator = self.context["page"], self.context["locator"]
246
+ page.locator(locator).click()
247
+ self.context["fixed_element"] = locator # 可选:记录修复结果
248
+ return True
249
+ ```
250
+
251
+ ---
252
+
253
+ ## 注入自定义后端
254
+
255
+ 内置策略的重依赖通过协议注入,可用宿主项目自己的实现替换默认实现:
256
+
257
+ ```python
258
+ from needle.builtin import ByCacheSolution, ByImageSolution, ByPromptSolution
259
+
260
+ ByCacheSolution.configure(my_cache_backend) # CacheBackend
261
+ ByImageSolution.configure(my_image_matcher) # ImageMatcher
262
+ ByPromptSolution.configure(my_ai_client) # AIClient
263
+ ```
264
+
265
+ ### 默认后端的实例化
266
+
267
+ ```python
268
+ from needle.builtin import (
269
+ ByCacheSolution,
270
+ ByImageSolution,
271
+ ByPromptSolution,
272
+ PickleDBCacheBackend,
273
+ OpenCVImageMatcher,
274
+ )
275
+
276
+ # 1. 缓存后端:JSON 文件,key 为原 locator,value 为备选 locator 列表
277
+ my_cache_backend = PickleDBCacheBackend(db_path="needle_locator_cache.db")
278
+ ByCacheSolution.configure(my_cache_backend)
279
+
280
+ # 2. 图像匹配后端:OpenCV 模板匹配
281
+ my_image_matcher = OpenCVImageMatcher(
282
+ image_dir="image_locator", # 模板图片目录
283
+ threshold=0.8, # 匹配阈值,范围 0~1
284
+ )
285
+ ByImageSolution.configure(my_image_matcher)
286
+ ```
287
+
288
+ ### 自定义 AI 客户端(ByPromptSolution 必须)
289
+
290
+ `needle` 没有内置通用 LLM 客户端,需要你自己实现 `AIClient` 协议:
291
+
292
+ ```python
293
+ from typing import Optional
294
+
295
+ class MyAIClient:
296
+ def analyze(self, html: str, description: str) -> Optional[str]:
297
+ """根据页面 HTML 和描述返回一个可用的 Playwright locator 表达式。
298
+
299
+ 返回 None 表示无法给出建议;返回的字符串会被写入 context["fixed_element"]。
300
+ """
301
+ prompt = (
302
+ f"根据以下页面 HTML,给出一个能定位到「{description}」的 "
303
+ f"Playwright locator 表达式。只返回表达式本身,不要任何解释。\n\n"
304
+ f"{html[:8000]}"
305
+ )
306
+ # 这里接入你实际使用的 LLM(OpenAI / Kimi / Claude / 自部署模型等)
307
+ # response = call_your_llm(prompt)
308
+ # return response.strip()
309
+ raise NotImplementedError("请接入实际的 LLM API")
310
+
311
+
312
+ my_ai_client = MyAIClient(api_key="sk-xxx")
313
+ ByPromptSolution.configure(my_ai_client)
314
+ ```
315
+
316
+ ### 内置 Kimi / Moonshot 客户端
317
+
318
+ `needle` 内置了一个基于 Moonshot AI(Kimi)的 `AIClient` 实现,安装 `ai` 依赖后即可使用:
319
+
320
+ ```bash
321
+ pip install "needle[ai]"
322
+ ```
323
+
324
+ ```python
325
+ from needle.builtin import ByPromptSolution, KimiClient
326
+
327
+ my_ai_client = KimiClient(
328
+ api_key="sk-xxxxxxxxxxxxxxxx", # Moonshot API Key
329
+ model="moonshot-v1-8k", # 可选:moonshot-v1-32k / moonshot-v1-128k
330
+ base_url="https://api.moonshot.cn/v1",
331
+ temperature=0.1,
332
+ max_tokens=512,
333
+ )
334
+ ByPromptSolution.configure(my_ai_client)
335
+ ```
336
+
337
+ > **注意**:`ByPromptSolution` 在未配置 `AIClient` 时会直接跳过,因此如果只使用缓存/图像策略,可以不实现 AI 客户端。
338
+
339
+ ---
340
+
341
+ ## 装饰器模式
342
+
343
+ `with_recovery` 可以把自动修复能力注入到任意函数:
344
+
345
+ ```python
346
+ from needle import with_recovery
347
+
348
+ @with_recovery(
349
+ context_extractor=lambda page, locator, **kw: {
350
+ "page": page,
351
+ "locator": locator,
352
+ **kw,
353
+ }
354
+ )
355
+ def click_element(page, locator):
356
+ page.locator(locator).click()
357
+ ```
358
+
359
+ - `context_extractor`:从被装饰函数参数中提取修复上下文,返回 `dict`。
360
+ - `reraise_on_failure`:修复失败时是否重新抛出 `RepairFailedException`(默认 `True`)。
361
+
362
+ ---
363
+
364
+ ## 开发与测试
365
+
366
+ ```bash
367
+ # 克隆仓库
368
+ git clone https://github.com/alucard1123/needle
369
+ cd needle
370
+
371
+ # 创建虚拟环境并安装开发依赖
372
+ python -m venv .venv
373
+ source .venv/bin/activate
374
+ pip install -e ".[dev]"
375
+
376
+ # 运行测试
377
+ pytest
378
+
379
+ # 运行测试并生成覆盖率报告
380
+ pytest --cov=needle --cov-report=term-missing
381
+ ```
382
+
383
+ ---
384
+
385
+ ## 许可证
386
+
387
+ [MIT](./LICENSE)
388
+
389
+ ## 关于作者
390
+ 公众号: 中年老吴
391
+
392
+ <img src="image.png" width="300" height="300" alt="公众号二维码">
393
+
394
+ 赏老吴杯咖啡:
395
+
396
+ <img src="IMG_0550.JPG" width="300" height="400" alt="赏老吴杯咖啡">
397
+
398
+ 找老吴私聊:
399
+
400
+ <img src="IMG_0549.JPG" width="300" height="400" alt="找老吴私聊">