screenforge 0.4.0__tar.gz → 0.4.1__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.
- screenforge-0.4.1/PKG-INFO +217 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/README.md +13 -13
- screenforge-0.4.1/cli/_version.py +1 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/pyproject.toml +2 -1
- screenforge-0.4.1/screenforge.egg-info/PKG-INFO +217 -0
- screenforge-0.4.0/PKG-INFO +0 -43
- screenforge-0.4.0/cli/_version.py +0 -1
- screenforge-0.4.0/screenforge.egg-info/PKG-INFO +0 -43
- {screenforge-0.4.0 → screenforge-0.4.1}/LICENSE +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/dispatch.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/doctor.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/action.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/default.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/demo.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/dry_run.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/init.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/plan.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/modes/workflow.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/parser.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/reporter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/session.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/shared.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/shorthand.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/cli/tool_protocol_handlers.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/adapters/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/adapters/android_adapter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/adapters/base_adapter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/adapters/ios_adapter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/adapters/web_adapter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/ai.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/ai_autonomous.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/ai_heal.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/cache_hash.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/cache_manager.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/cache_stats.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/cache_storage.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/cache/embedding_loader.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/capabilities.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/case_memory.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/error_codes.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/exceptions.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/executor.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/failure_diagnosis.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/history_manager.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/logs.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/mcp_server.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/preflight.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/progress.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/run_reporter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/run_resume.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/runtime_modes.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/tool_protocol.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/visual_fallback.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/common/workflow_schema.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/config/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/config/config.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/config/env_loader.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/screenforge.egg-info/SOURCES.txt +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/screenforge.egg-info/dependency_links.txt +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/screenforge.egg-info/entry_points.txt +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/screenforge.egg-info/requires.txt +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/screenforge.egg-info/top_level.txt +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/setup.cfg +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_ai_autonomous.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_ai_brain.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_ai_heal.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_android_smoke_live.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_cache_manager.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_capabilities.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_cli_action_json.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_codegen_quality.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_dispatch.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_doctor_orphan_browser.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_error_codes.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_executor.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_failure_diagnosis.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_interaction_actions.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_ios_smoke_live.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_mcp_ref_cache.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_ml_optional.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_parser.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_run_reporter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_run_resume.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_runtime_modes.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_screenshot_annotator.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_shorthand.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_tool_protocol_diagnosis.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_utils_ios.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_utils_web.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_utils_xml.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_visual_fallback.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_web_adapter.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_web_dom_complex_live.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/tests/test_web_smoke_live.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/utils/__init__.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/utils/screenshot_annotator.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/utils/utils_ios.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/utils/utils_web.py +0 -0
- {screenforge-0.4.0 → screenforge-0.4.1}/utils/utils_xml.py +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: screenforge
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: AI-driven cross-platform UI automation engine with test script generation
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/jhinzzz/ScreenForge
|
|
7
|
+
Project-URL: Repository, https://github.com/jhinzzz/ScreenForge
|
|
8
|
+
Project-URL: Issues, https://github.com/jhinzzz/ScreenForge/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: playwright>=1.50
|
|
13
|
+
Requires-Dist: openai>=2.0
|
|
14
|
+
Requires-Dist: allure-pytest>=2.15
|
|
15
|
+
Requires-Dist: loguru>=0.7
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Requires-Dist: python-dotenv>=1.0
|
|
18
|
+
Requires-Dist: PyYAML>=6.0
|
|
19
|
+
Requires-Dist: pillow>=12.0
|
|
20
|
+
Requires-Dist: lxml>=5.0
|
|
21
|
+
Requires-Dist: requests>=2.30
|
|
22
|
+
Requires-Dist: rich>=14.0
|
|
23
|
+
Requires-Dist: typer>=0.24
|
|
24
|
+
Requires-Dist: retry2>=0.9
|
|
25
|
+
Requires-Dist: opencv-python>=4.10
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: filelock>=3.12
|
|
28
|
+
Provides-Extra: android
|
|
29
|
+
Requires-Dist: uiautomator2>=3.5; extra == "android"
|
|
30
|
+
Requires-Dist: adbutils>=2.12; extra == "android"
|
|
31
|
+
Provides-Extra: ios
|
|
32
|
+
Requires-Dist: facebook-wda>=1.0; extra == "ios"
|
|
33
|
+
Provides-Extra: ml
|
|
34
|
+
Requires-Dist: torch>=2.0; extra == "ml"
|
|
35
|
+
Requires-Dist: transformers>=5.0; extra == "ml"
|
|
36
|
+
Requires-Dist: sentence-transformers>=5.0; extra == "ml"
|
|
37
|
+
Requires-Dist: scikit-learn>=1.8; extra == "ml"
|
|
38
|
+
Provides-Extra: playground
|
|
39
|
+
Requires-Dist: fastapi>=0.115; extra == "playground"
|
|
40
|
+
Requires-Dist: uvicorn>=0.34; extra == "playground"
|
|
41
|
+
Requires-Dist: websockets>=14.0; extra == "playground"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: screenforge[android,ios,ml,playground]; extra == "all"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# ScreenForge
|
|
47
|
+
|
|
48
|
+
[](https://github.com/jhinzzz/ScreenForge/actions/workflows/ci.yml)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
|
51
|
+
|
|
52
|
+
**[中文](https://github.com/jhinzzz/ScreenForge/blob/main/README_CN.md)** | English
|
|
53
|
+
|
|
54
|
+
> Describe what to test. Watch it happen. Get a pytest script.
|
|
55
|
+
|
|
56
|
+
ScreenForge is an AI-driven UI automation engine that turns natural language into executable test scripts. Unlike record-and-replay tools, you don't perform the actions yourself — the AI does it for you.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
## Why ScreenForge?
|
|
61
|
+
|
|
62
|
+
| | Playwright Codegen | Browser Use | Midscene.js | **ScreenForge** |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| Need to perform actions yourself? | Yes | No | No | **No** |
|
|
65
|
+
| Generates replayable test scripts? | Yes | No | No | **Yes (pytest)** |
|
|
66
|
+
| Self-healing when UI changes? | No | No | No | **Yes** |
|
|
67
|
+
| Works as AI Agent tool (MCP)? | No | Yes | No | **Yes** |
|
|
68
|
+
|
|
69
|
+
**Core architecture**: Your AI Agent is the brain (understands requirements, makes decisions). ScreenForge is the hands (executes UI actions, generates code).
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install screenforge
|
|
75
|
+
|
|
76
|
+
# See the magic without any API key:
|
|
77
|
+
screenforge --demo
|
|
78
|
+
|
|
79
|
+
# For real usage, set your LLM key:
|
|
80
|
+
export OPENAI_API_KEY=sk-...
|
|
81
|
+
|
|
82
|
+
# Inspect the current page (returns DOM tree for your Agent to analyze):
|
|
83
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
84
|
+
|
|
85
|
+
# Execute a single action:
|
|
86
|
+
screenforge --action click --platform web --locator-type text --locator-value "Login"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## How It Works
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
You (or your AI Agent) ScreenForge
|
|
93
|
+
│ │
|
|
94
|
+
├──── "Test the login" ─────►│
|
|
95
|
+
│ ├── inspect_ui (get DOM tree)
|
|
96
|
+
│◄── DOM tree ──────────────┤
|
|
97
|
+
│ │
|
|
98
|
+
├──── click #email ─────────►│
|
|
99
|
+
├──── input "user@..." ─────►│
|
|
100
|
+
├──── click "Sign In" ──────►│
|
|
101
|
+
│ │
|
|
102
|
+
│◄── pytest script ─────────┤
|
|
103
|
+
│◄── Allure report ─────────┤
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Each step: **inspect → decide → act → verify**. The AI decides, ScreenForge executes.
|
|
107
|
+
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
- **Cross-platform**: Android (uiautomator2), iOS (wda), Web (Playwright)
|
|
111
|
+
- **Self-healing engine**: When tests break due to UI changes, the engine auto-repairs locators with confidence scoring and AST validation
|
|
112
|
+
- **L1/L2 semantic cache**: Same page + same instruction = instant response, no LLM call needed
|
|
113
|
+
- **Visual fallback**: When DOM can't locate elements (Canvas, games), VLM parses screenshots
|
|
114
|
+
- **MCP server**: Any MCP-compatible Agent can drive ScreenForge natively
|
|
115
|
+
- **Structured output**: JSON Lines events + `report/runs/<id>/` artifacts for CI integration
|
|
116
|
+
|
|
117
|
+
## Agent Integration (Claude Code / Cursor / Codex)
|
|
118
|
+
|
|
119
|
+
ScreenForge exposes itself as a tool for AI Agents. The standard loop:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# 1. Get page structure (your Agent analyzes it)
|
|
123
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
124
|
+
|
|
125
|
+
# 2. Your Agent decides what to do, sends precise actions
|
|
126
|
+
screenforge --action click --platform web --locator-type text --locator-value "Login"
|
|
127
|
+
|
|
128
|
+
# 3. Verify the result, repeat
|
|
129
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For batch operations, use workflows:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
screenforge --workflow ./workflows/login.yaml --platform web --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or start the MCP server for native Agent integration:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
screenforge --mcp-server
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## GitHub Actions
|
|
145
|
+
|
|
146
|
+
Add ScreenForge to your CI pipeline:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
- uses: jhinzzz/ScreenForge@v1
|
|
150
|
+
with:
|
|
151
|
+
platform: web
|
|
152
|
+
workflow: ./workflows/login.yaml
|
|
153
|
+
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Results are auto-uploaded as Allure artifacts. See [action.yml](https://github.com/jhinzzz/ScreenForge/blob/main/action.yml) for all inputs.
|
|
157
|
+
|
|
158
|
+
See [Agent Integration Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) for the complete protocol.
|
|
159
|
+
|
|
160
|
+
## Installation (from source)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/jhinzzz/ScreenForge.git
|
|
164
|
+
cd ScreenForge
|
|
165
|
+
python -m venv .venv && source .venv/bin/activate
|
|
166
|
+
pip install -e ".[all]"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Platform-specific extras:
|
|
170
|
+
- Web only: `pip install -e .` (default, includes Playwright)
|
|
171
|
+
- Android: `pip install -e ".[android]"`
|
|
172
|
+
- iOS: `pip install -e ".[ios]"`
|
|
173
|
+
- ML/cache: `pip install -e ".[ml]"` (sentence-transformers for semantic cache)
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Required: LLM API key (OpenAI-compatible endpoint)
|
|
179
|
+
export OPENAI_API_KEY=sk-...
|
|
180
|
+
|
|
181
|
+
# Optional: custom endpoint (defaults to api.openai.com)
|
|
182
|
+
export OPENAI_BASE_URL=https://api.openai.com/v1
|
|
183
|
+
|
|
184
|
+
# Optional: model (defaults to gpt-4o)
|
|
185
|
+
export MODEL_NAME=gpt-4o
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or create a `.env` file (copy from `.env_template`).
|
|
189
|
+
|
|
190
|
+
## Badge
|
|
191
|
+
|
|
192
|
+
If ScreenForge generates tests for your project, add this badge to your README:
|
|
193
|
+
|
|
194
|
+
```markdown
|
|
195
|
+
[](https://github.com/jhinzzz/ScreenForge)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
[](https://github.com/jhinzzz/ScreenForge)
|
|
199
|
+
|
|
200
|
+
## Learn More
|
|
201
|
+
|
|
202
|
+
| Resource | Description |
|
|
203
|
+
|----------|-------------|
|
|
204
|
+
| [Mobile Setup](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mobile-setup.md) | Android & iOS device connection guide |
|
|
205
|
+
| [MCP Setup (3 min)](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mcp-setup.md) | Connect to Claude Desktop / Cursor / Cline / Claude Code |
|
|
206
|
+
| [Agent Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) | Integration protocol for AI Agents |
|
|
207
|
+
| [Capability Matrix](https://github.com/jhinzzz/ScreenForge/blob/main/docs/capability-matrix.md) | Supported platforms, actions, and locators |
|
|
208
|
+
| [Workflow Examples](https://github.com/jhinzzz/ScreenForge/tree/main/docs/workflows) | YAML workflow templates |
|
|
209
|
+
| [CHANGELOG](https://github.com/jhinzzz/ScreenForge/blob/main/CHANGELOG.md) | Version history |
|
|
210
|
+
|
|
211
|
+
## Contributing
|
|
212
|
+
|
|
213
|
+
See [CONTRIBUTING.md](https://github.com/jhinzzz/ScreenForge/blob/main/CONTRIBUTING.md) for guidelines. Issues and PRs welcome!
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
[MIT](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/jhinzzz/ScreenForge/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](LICENSE)
|
|
5
|
+
[](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
**[中文](
|
|
7
|
+
**[中文](https://github.com/jhinzzz/ScreenForge/blob/main/README_CN.md)** | English
|
|
8
8
|
|
|
9
9
|
> Describe what to test. Watch it happen. Get a pytest script.
|
|
10
10
|
|
|
11
11
|
ScreenForge is an AI-driven UI automation engine that turns natural language into executable test scripts. Unlike record-and-replay tools, you don't perform the actions yourself — the AI does it for you.
|
|
12
12
|
|
|
13
|
-

|
|
13
|
+

|
|
14
14
|
|
|
15
15
|
## Why ScreenForge?
|
|
16
16
|
|
|
@@ -108,9 +108,9 @@ Add ScreenForge to your CI pipeline:
|
|
|
108
108
|
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Results are auto-uploaded as Allure artifacts. See [action.yml](action.yml) for all inputs.
|
|
111
|
+
Results are auto-uploaded as Allure artifacts. See [action.yml](https://github.com/jhinzzz/ScreenForge/blob/main/action.yml) for all inputs.
|
|
112
112
|
|
|
113
|
-
See [Agent Integration Guide](docs/agent_guide.md) for the complete protocol.
|
|
113
|
+
See [Agent Integration Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) for the complete protocol.
|
|
114
114
|
|
|
115
115
|
## Installation (from source)
|
|
116
116
|
|
|
@@ -156,17 +156,17 @@ If ScreenForge generates tests for your project, add this badge to your README:
|
|
|
156
156
|
|
|
157
157
|
| Resource | Description |
|
|
158
158
|
|----------|-------------|
|
|
159
|
-
| [Mobile Setup](docs/mobile-setup.md) | Android & iOS device connection guide |
|
|
160
|
-
| [MCP Setup (3 min)](docs/mcp-setup.md) | Connect to Claude Desktop / Cursor / Cline / Claude Code |
|
|
161
|
-
| [Agent Guide](docs/agent_guide.md) | Integration protocol for AI Agents |
|
|
162
|
-
| [Capability Matrix](docs/capability-matrix.md) | Supported platforms, actions, and locators |
|
|
163
|
-
| [Workflow Examples](docs/workflows
|
|
164
|
-
| [CHANGELOG](CHANGELOG.md) | Version history |
|
|
159
|
+
| [Mobile Setup](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mobile-setup.md) | Android & iOS device connection guide |
|
|
160
|
+
| [MCP Setup (3 min)](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mcp-setup.md) | Connect to Claude Desktop / Cursor / Cline / Claude Code |
|
|
161
|
+
| [Agent Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) | Integration protocol for AI Agents |
|
|
162
|
+
| [Capability Matrix](https://github.com/jhinzzz/ScreenForge/blob/main/docs/capability-matrix.md) | Supported platforms, actions, and locators |
|
|
163
|
+
| [Workflow Examples](https://github.com/jhinzzz/ScreenForge/tree/main/docs/workflows) | YAML workflow templates |
|
|
164
|
+
| [CHANGELOG](https://github.com/jhinzzz/ScreenForge/blob/main/CHANGELOG.md) | Version history |
|
|
165
165
|
|
|
166
166
|
## Contributing
|
|
167
167
|
|
|
168
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Issues and PRs welcome!
|
|
168
|
+
See [CONTRIBUTING.md](https://github.com/jhinzzz/ScreenForge/blob/main/CONTRIBUTING.md) for guidelines. Issues and PRs welcome!
|
|
169
169
|
|
|
170
170
|
## License
|
|
171
171
|
|
|
172
|
-
[MIT](LICENSE)
|
|
172
|
+
[MIT](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.1"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: screenforge
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: AI-driven cross-platform UI automation engine with test script generation
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/jhinzzz/ScreenForge
|
|
7
|
+
Project-URL: Repository, https://github.com/jhinzzz/ScreenForge
|
|
8
|
+
Project-URL: Issues, https://github.com/jhinzzz/ScreenForge/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: playwright>=1.50
|
|
13
|
+
Requires-Dist: openai>=2.0
|
|
14
|
+
Requires-Dist: allure-pytest>=2.15
|
|
15
|
+
Requires-Dist: loguru>=0.7
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Requires-Dist: python-dotenv>=1.0
|
|
18
|
+
Requires-Dist: PyYAML>=6.0
|
|
19
|
+
Requires-Dist: pillow>=12.0
|
|
20
|
+
Requires-Dist: lxml>=5.0
|
|
21
|
+
Requires-Dist: requests>=2.30
|
|
22
|
+
Requires-Dist: rich>=14.0
|
|
23
|
+
Requires-Dist: typer>=0.24
|
|
24
|
+
Requires-Dist: retry2>=0.9
|
|
25
|
+
Requires-Dist: opencv-python>=4.10
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: filelock>=3.12
|
|
28
|
+
Provides-Extra: android
|
|
29
|
+
Requires-Dist: uiautomator2>=3.5; extra == "android"
|
|
30
|
+
Requires-Dist: adbutils>=2.12; extra == "android"
|
|
31
|
+
Provides-Extra: ios
|
|
32
|
+
Requires-Dist: facebook-wda>=1.0; extra == "ios"
|
|
33
|
+
Provides-Extra: ml
|
|
34
|
+
Requires-Dist: torch>=2.0; extra == "ml"
|
|
35
|
+
Requires-Dist: transformers>=5.0; extra == "ml"
|
|
36
|
+
Requires-Dist: sentence-transformers>=5.0; extra == "ml"
|
|
37
|
+
Requires-Dist: scikit-learn>=1.8; extra == "ml"
|
|
38
|
+
Provides-Extra: playground
|
|
39
|
+
Requires-Dist: fastapi>=0.115; extra == "playground"
|
|
40
|
+
Requires-Dist: uvicorn>=0.34; extra == "playground"
|
|
41
|
+
Requires-Dist: websockets>=14.0; extra == "playground"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: screenforge[android,ios,ml,playground]; extra == "all"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# ScreenForge
|
|
47
|
+
|
|
48
|
+
[](https://github.com/jhinzzz/ScreenForge/actions/workflows/ci.yml)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
|
51
|
+
|
|
52
|
+
**[中文](https://github.com/jhinzzz/ScreenForge/blob/main/README_CN.md)** | English
|
|
53
|
+
|
|
54
|
+
> Describe what to test. Watch it happen. Get a pytest script.
|
|
55
|
+
|
|
56
|
+
ScreenForge is an AI-driven UI automation engine that turns natural language into executable test scripts. Unlike record-and-replay tools, you don't perform the actions yourself — the AI does it for you.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
## Why ScreenForge?
|
|
61
|
+
|
|
62
|
+
| | Playwright Codegen | Browser Use | Midscene.js | **ScreenForge** |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| Need to perform actions yourself? | Yes | No | No | **No** |
|
|
65
|
+
| Generates replayable test scripts? | Yes | No | No | **Yes (pytest)** |
|
|
66
|
+
| Self-healing when UI changes? | No | No | No | **Yes** |
|
|
67
|
+
| Works as AI Agent tool (MCP)? | No | Yes | No | **Yes** |
|
|
68
|
+
|
|
69
|
+
**Core architecture**: Your AI Agent is the brain (understands requirements, makes decisions). ScreenForge is the hands (executes UI actions, generates code).
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install screenforge
|
|
75
|
+
|
|
76
|
+
# See the magic without any API key:
|
|
77
|
+
screenforge --demo
|
|
78
|
+
|
|
79
|
+
# For real usage, set your LLM key:
|
|
80
|
+
export OPENAI_API_KEY=sk-...
|
|
81
|
+
|
|
82
|
+
# Inspect the current page (returns DOM tree for your Agent to analyze):
|
|
83
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
84
|
+
|
|
85
|
+
# Execute a single action:
|
|
86
|
+
screenforge --action click --platform web --locator-type text --locator-value "Login"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## How It Works
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
You (or your AI Agent) ScreenForge
|
|
93
|
+
│ │
|
|
94
|
+
├──── "Test the login" ─────►│
|
|
95
|
+
│ ├── inspect_ui (get DOM tree)
|
|
96
|
+
│◄── DOM tree ──────────────┤
|
|
97
|
+
│ │
|
|
98
|
+
├──── click #email ─────────►│
|
|
99
|
+
├──── input "user@..." ─────►│
|
|
100
|
+
├──── click "Sign In" ──────►│
|
|
101
|
+
│ │
|
|
102
|
+
│◄── pytest script ─────────┤
|
|
103
|
+
│◄── Allure report ─────────┤
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Each step: **inspect → decide → act → verify**. The AI decides, ScreenForge executes.
|
|
107
|
+
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
- **Cross-platform**: Android (uiautomator2), iOS (wda), Web (Playwright)
|
|
111
|
+
- **Self-healing engine**: When tests break due to UI changes, the engine auto-repairs locators with confidence scoring and AST validation
|
|
112
|
+
- **L1/L2 semantic cache**: Same page + same instruction = instant response, no LLM call needed
|
|
113
|
+
- **Visual fallback**: When DOM can't locate elements (Canvas, games), VLM parses screenshots
|
|
114
|
+
- **MCP server**: Any MCP-compatible Agent can drive ScreenForge natively
|
|
115
|
+
- **Structured output**: JSON Lines events + `report/runs/<id>/` artifacts for CI integration
|
|
116
|
+
|
|
117
|
+
## Agent Integration (Claude Code / Cursor / Codex)
|
|
118
|
+
|
|
119
|
+
ScreenForge exposes itself as a tool for AI Agents. The standard loop:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# 1. Get page structure (your Agent analyzes it)
|
|
123
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
124
|
+
|
|
125
|
+
# 2. Your Agent decides what to do, sends precise actions
|
|
126
|
+
screenforge --action click --platform web --locator-type text --locator-value "Login"
|
|
127
|
+
|
|
128
|
+
# 3. Verify the result, repeat
|
|
129
|
+
echo '{"operation":"inspect_ui","platform":"web"}' | screenforge --tool-stdin
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For batch operations, use workflows:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
screenforge --workflow ./workflows/login.yaml --platform web --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or start the MCP server for native Agent integration:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
screenforge --mcp-server
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## GitHub Actions
|
|
145
|
+
|
|
146
|
+
Add ScreenForge to your CI pipeline:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
- uses: jhinzzz/ScreenForge@v1
|
|
150
|
+
with:
|
|
151
|
+
platform: web
|
|
152
|
+
workflow: ./workflows/login.yaml
|
|
153
|
+
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Results are auto-uploaded as Allure artifacts. See [action.yml](https://github.com/jhinzzz/ScreenForge/blob/main/action.yml) for all inputs.
|
|
157
|
+
|
|
158
|
+
See [Agent Integration Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) for the complete protocol.
|
|
159
|
+
|
|
160
|
+
## Installation (from source)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/jhinzzz/ScreenForge.git
|
|
164
|
+
cd ScreenForge
|
|
165
|
+
python -m venv .venv && source .venv/bin/activate
|
|
166
|
+
pip install -e ".[all]"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Platform-specific extras:
|
|
170
|
+
- Web only: `pip install -e .` (default, includes Playwright)
|
|
171
|
+
- Android: `pip install -e ".[android]"`
|
|
172
|
+
- iOS: `pip install -e ".[ios]"`
|
|
173
|
+
- ML/cache: `pip install -e ".[ml]"` (sentence-transformers for semantic cache)
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Required: LLM API key (OpenAI-compatible endpoint)
|
|
179
|
+
export OPENAI_API_KEY=sk-...
|
|
180
|
+
|
|
181
|
+
# Optional: custom endpoint (defaults to api.openai.com)
|
|
182
|
+
export OPENAI_BASE_URL=https://api.openai.com/v1
|
|
183
|
+
|
|
184
|
+
# Optional: model (defaults to gpt-4o)
|
|
185
|
+
export MODEL_NAME=gpt-4o
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or create a `.env` file (copy from `.env_template`).
|
|
189
|
+
|
|
190
|
+
## Badge
|
|
191
|
+
|
|
192
|
+
If ScreenForge generates tests for your project, add this badge to your README:
|
|
193
|
+
|
|
194
|
+
```markdown
|
|
195
|
+
[](https://github.com/jhinzzz/ScreenForge)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
[](https://github.com/jhinzzz/ScreenForge)
|
|
199
|
+
|
|
200
|
+
## Learn More
|
|
201
|
+
|
|
202
|
+
| Resource | Description |
|
|
203
|
+
|----------|-------------|
|
|
204
|
+
| [Mobile Setup](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mobile-setup.md) | Android & iOS device connection guide |
|
|
205
|
+
| [MCP Setup (3 min)](https://github.com/jhinzzz/ScreenForge/blob/main/docs/mcp-setup.md) | Connect to Claude Desktop / Cursor / Cline / Claude Code |
|
|
206
|
+
| [Agent Guide](https://github.com/jhinzzz/ScreenForge/blob/main/docs/agent_guide.md) | Integration protocol for AI Agents |
|
|
207
|
+
| [Capability Matrix](https://github.com/jhinzzz/ScreenForge/blob/main/docs/capability-matrix.md) | Supported platforms, actions, and locators |
|
|
208
|
+
| [Workflow Examples](https://github.com/jhinzzz/ScreenForge/tree/main/docs/workflows) | YAML workflow templates |
|
|
209
|
+
| [CHANGELOG](https://github.com/jhinzzz/ScreenForge/blob/main/CHANGELOG.md) | Version history |
|
|
210
|
+
|
|
211
|
+
## Contributing
|
|
212
|
+
|
|
213
|
+
See [CONTRIBUTING.md](https://github.com/jhinzzz/ScreenForge/blob/main/CONTRIBUTING.md) for guidelines. Issues and PRs welcome!
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
[MIT](https://github.com/jhinzzz/ScreenForge/blob/main/LICENSE)
|
screenforge-0.4.0/PKG-INFO
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: screenforge
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: AI-driven cross-platform UI automation engine with test script generation
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/jhinzzz/ScreenForge
|
|
7
|
-
Project-URL: Repository, https://github.com/jhinzzz/ScreenForge
|
|
8
|
-
Project-URL: Issues, https://github.com/jhinzzz/ScreenForge/issues
|
|
9
|
-
Requires-Python: >=3.11
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: playwright>=1.50
|
|
12
|
-
Requires-Dist: openai>=2.0
|
|
13
|
-
Requires-Dist: allure-pytest>=2.15
|
|
14
|
-
Requires-Dist: loguru>=0.7
|
|
15
|
-
Requires-Dist: pydantic>=2.0
|
|
16
|
-
Requires-Dist: python-dotenv>=1.0
|
|
17
|
-
Requires-Dist: PyYAML>=6.0
|
|
18
|
-
Requires-Dist: pillow>=12.0
|
|
19
|
-
Requires-Dist: lxml>=5.0
|
|
20
|
-
Requires-Dist: requests>=2.30
|
|
21
|
-
Requires-Dist: rich>=14.0
|
|
22
|
-
Requires-Dist: typer>=0.24
|
|
23
|
-
Requires-Dist: retry2>=0.9
|
|
24
|
-
Requires-Dist: opencv-python>=4.10
|
|
25
|
-
Requires-Dist: numpy>=1.24
|
|
26
|
-
Requires-Dist: filelock>=3.12
|
|
27
|
-
Provides-Extra: android
|
|
28
|
-
Requires-Dist: uiautomator2>=3.5; extra == "android"
|
|
29
|
-
Requires-Dist: adbutils>=2.12; extra == "android"
|
|
30
|
-
Provides-Extra: ios
|
|
31
|
-
Requires-Dist: facebook-wda>=1.0; extra == "ios"
|
|
32
|
-
Provides-Extra: ml
|
|
33
|
-
Requires-Dist: torch>=2.0; extra == "ml"
|
|
34
|
-
Requires-Dist: transformers>=5.0; extra == "ml"
|
|
35
|
-
Requires-Dist: sentence-transformers>=5.0; extra == "ml"
|
|
36
|
-
Requires-Dist: scikit-learn>=1.8; extra == "ml"
|
|
37
|
-
Provides-Extra: playground
|
|
38
|
-
Requires-Dist: fastapi>=0.115; extra == "playground"
|
|
39
|
-
Requires-Dist: uvicorn>=0.34; extra == "playground"
|
|
40
|
-
Requires-Dist: websockets>=14.0; extra == "playground"
|
|
41
|
-
Provides-Extra: all
|
|
42
|
-
Requires-Dist: screenforge[android,ios,ml,playground]; extra == "all"
|
|
43
|
-
Dynamic: license-file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: screenforge
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: AI-driven cross-platform UI automation engine with test script generation
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/jhinzzz/ScreenForge
|
|
7
|
-
Project-URL: Repository, https://github.com/jhinzzz/ScreenForge
|
|
8
|
-
Project-URL: Issues, https://github.com/jhinzzz/ScreenForge/issues
|
|
9
|
-
Requires-Python: >=3.11
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: playwright>=1.50
|
|
12
|
-
Requires-Dist: openai>=2.0
|
|
13
|
-
Requires-Dist: allure-pytest>=2.15
|
|
14
|
-
Requires-Dist: loguru>=0.7
|
|
15
|
-
Requires-Dist: pydantic>=2.0
|
|
16
|
-
Requires-Dist: python-dotenv>=1.0
|
|
17
|
-
Requires-Dist: PyYAML>=6.0
|
|
18
|
-
Requires-Dist: pillow>=12.0
|
|
19
|
-
Requires-Dist: lxml>=5.0
|
|
20
|
-
Requires-Dist: requests>=2.30
|
|
21
|
-
Requires-Dist: rich>=14.0
|
|
22
|
-
Requires-Dist: typer>=0.24
|
|
23
|
-
Requires-Dist: retry2>=0.9
|
|
24
|
-
Requires-Dist: opencv-python>=4.10
|
|
25
|
-
Requires-Dist: numpy>=1.24
|
|
26
|
-
Requires-Dist: filelock>=3.12
|
|
27
|
-
Provides-Extra: android
|
|
28
|
-
Requires-Dist: uiautomator2>=3.5; extra == "android"
|
|
29
|
-
Requires-Dist: adbutils>=2.12; extra == "android"
|
|
30
|
-
Provides-Extra: ios
|
|
31
|
-
Requires-Dist: facebook-wda>=1.0; extra == "ios"
|
|
32
|
-
Provides-Extra: ml
|
|
33
|
-
Requires-Dist: torch>=2.0; extra == "ml"
|
|
34
|
-
Requires-Dist: transformers>=5.0; extra == "ml"
|
|
35
|
-
Requires-Dist: sentence-transformers>=5.0; extra == "ml"
|
|
36
|
-
Requires-Dist: scikit-learn>=1.8; extra == "ml"
|
|
37
|
-
Provides-Extra: playground
|
|
38
|
-
Requires-Dist: fastapi>=0.115; extra == "playground"
|
|
39
|
-
Requires-Dist: uvicorn>=0.34; extra == "playground"
|
|
40
|
-
Requires-Dist: websockets>=14.0; extra == "playground"
|
|
41
|
-
Provides-Extra: all
|
|
42
|
-
Requires-Dist: screenforge[android,ios,ml,playground]; extra == "all"
|
|
43
|
-
Dynamic: license-file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|