portacode 0.3.19.dev4__py3-none-any.whl → 1.4.11.dev1__py3-none-any.whl
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.
Potentially problematic release.
This version of portacode might be problematic. Click here for more details.
- portacode/_version.py +16 -3
- portacode/cli.py +143 -17
- portacode/connection/client.py +149 -10
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +824 -21
- portacode/connection/handlers/__init__.py +28 -1
- portacode/connection/handlers/base.py +78 -16
- portacode/connection/handlers/chunked_content.py +244 -0
- portacode/connection/handlers/diff_handlers.py +603 -0
- portacode/connection/handlers/file_handlers.py +902 -17
- portacode/connection/handlers/project_aware_file_handlers.py +226 -0
- portacode/connection/handlers/project_state/README.md +312 -0
- portacode/connection/handlers/project_state/__init__.py +92 -0
- portacode/connection/handlers/project_state/file_system_watcher.py +179 -0
- portacode/connection/handlers/project_state/git_manager.py +1502 -0
- portacode/connection/handlers/project_state/handlers.py +875 -0
- portacode/connection/handlers/project_state/manager.py +1331 -0
- portacode/connection/handlers/project_state/models.py +108 -0
- portacode/connection/handlers/project_state/utils.py +50 -0
- portacode/connection/handlers/project_state_handlers.py +45 -2185
- portacode/connection/handlers/proxmox_infra.py +361 -0
- portacode/connection/handlers/registry.py +15 -4
- portacode/connection/handlers/session.py +483 -32
- portacode/connection/handlers/system_handlers.py +147 -8
- portacode/connection/handlers/tab_factory.py +53 -46
- portacode/connection/handlers/terminal_handlers.py +21 -8
- portacode/connection/handlers/update_handler.py +61 -0
- portacode/connection/multiplex.py +60 -2
- portacode/connection/terminal.py +214 -24
- portacode/keypair.py +63 -1
- portacode/link_capture/__init__.py +38 -0
- portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- portacode/link_capture/bin/elinks +3 -0
- portacode/link_capture/bin/gio-open +3 -0
- portacode/link_capture/bin/gnome-open +3 -0
- portacode/link_capture/bin/gvfs-open +3 -0
- portacode/link_capture/bin/kde-open +3 -0
- portacode/link_capture/bin/kfmclient +3 -0
- portacode/link_capture/bin/link_capture_exec.sh +11 -0
- portacode/link_capture/bin/link_capture_wrapper.py +75 -0
- portacode/link_capture/bin/links +3 -0
- portacode/link_capture/bin/links2 +3 -0
- portacode/link_capture/bin/lynx +3 -0
- portacode/link_capture/bin/mate-open +3 -0
- portacode/link_capture/bin/netsurf +3 -0
- portacode/link_capture/bin/sensible-browser +3 -0
- portacode/link_capture/bin/w3m +3 -0
- portacode/link_capture/bin/x-www-browser +3 -0
- portacode/link_capture/bin/xdg-open +3 -0
- portacode/logging_categories.py +140 -0
- portacode/pairing.py +103 -0
- portacode/static/js/test-ntp-clock.html +63 -0
- portacode/static/js/utils/ntp-clock.js +232 -0
- portacode/utils/NTP_ARCHITECTURE.md +136 -0
- portacode/utils/__init__.py +1 -0
- portacode/utils/diff_apply.py +456 -0
- portacode/utils/diff_renderer.py +371 -0
- portacode/utils/ntp_clock.py +65 -0
- portacode-1.4.11.dev1.dist-info/METADATA +298 -0
- portacode-1.4.11.dev1.dist-info/RECORD +97 -0
- {portacode-0.3.19.dev4.dist-info → portacode-1.4.11.dev1.dist-info}/WHEEL +1 -1
- portacode-1.4.11.dev1.dist-info/top_level.txt +3 -0
- test_modules/README.md +296 -0
- test_modules/__init__.py +1 -0
- test_modules/test_device_online.py +44 -0
- test_modules/test_file_operations.py +743 -0
- test_modules/test_git_status_ui.py +370 -0
- test_modules/test_login_flow.py +50 -0
- test_modules/test_navigate_testing_folder.py +361 -0
- test_modules/test_play_store_screenshots.py +294 -0
- test_modules/test_terminal_buffer_performance.py +261 -0
- test_modules/test_terminal_interaction.py +80 -0
- test_modules/test_terminal_loading_race_condition.py +95 -0
- test_modules/test_terminal_start.py +56 -0
- testing_framework/.env.example +21 -0
- testing_framework/README.md +334 -0
- testing_framework/__init__.py +17 -0
- testing_framework/cli.py +326 -0
- testing_framework/core/__init__.py +1 -0
- testing_framework/core/base_test.py +336 -0
- testing_framework/core/cli_manager.py +177 -0
- testing_framework/core/hierarchical_runner.py +577 -0
- testing_framework/core/playwright_manager.py +520 -0
- testing_framework/core/runner.py +447 -0
- testing_framework/core/shared_cli_manager.py +234 -0
- testing_framework/core/test_discovery.py +112 -0
- testing_framework/requirements.txt +12 -0
- portacode-0.3.19.dev4.dist-info/METADATA +0 -241
- portacode-0.3.19.dev4.dist-info/RECORD +0 -30
- portacode-0.3.19.dev4.dist-info/top_level.txt +0 -1
- {portacode-0.3.19.dev4.dist-info → portacode-1.4.11.dev1.dist-info}/entry_points.txt +0 -0
- {portacode-0.3.19.dev4.dist-info → portacode-1.4.11.dev1.dist-info/licenses}/LICENSE +0 -0
test_modules/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Test Modules Guide
|
|
2
|
+
|
|
3
|
+
This directory contains test modules for the **simplified** Portacode testing framework. The framework now supports **hierarchical test dependencies** and **easy assertions** for WebSocket messages, debug files, and more.
|
|
4
|
+
|
|
5
|
+
## 🆕 What's New - Simplified Framework
|
|
6
|
+
|
|
7
|
+
### ✨ Key Features
|
|
8
|
+
- **Hierarchical Dependencies**: Tests run in correct order automatically
|
|
9
|
+
- **Auto-Navigation**: `start_url` parameter ensures tests start from the right page
|
|
10
|
+
- **Simple Assertions**: Easy-to-use `assert_that()` helper
|
|
11
|
+
- **Debug File Inspection**: Built-in helpers for `client_sessions.json` and `project_state_debug.json`
|
|
12
|
+
- **WebSocket Debugging**: Detailed `websockets.json` logs for communication analysis
|
|
13
|
+
- **WebSocket Testing**: Assert on WebSocket messages easily
|
|
14
|
+
- **Auto Debug Mode**: CLI connects with `--debug` flag automatically
|
|
15
|
+
- **Full HD Recording**: High-quality video recording with proper viewport (1920x1080)
|
|
16
|
+
|
|
17
|
+
## 📝 Writing a Test Module
|
|
18
|
+
|
|
19
|
+
### Basic Structure with Dependencies
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from testing_framework.core.base_test import BaseTest, TestResult, TestCategory
|
|
23
|
+
|
|
24
|
+
class YourCustomTest(BaseTest):
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__(
|
|
27
|
+
name="your_test_name",
|
|
28
|
+
category=TestCategory.SMOKE,
|
|
29
|
+
description="What this test validates",
|
|
30
|
+
tags=["tag1", "tag2", "tag3"],
|
|
31
|
+
depends_on=["login_flow_test"], # Run after these tests
|
|
32
|
+
start_url="/dashboard/" # Auto-navigate to this URL before test
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def run(self) -> TestResult:
|
|
36
|
+
"""Main test logic with simple assertions."""
|
|
37
|
+
page = self.playwright_manager.page
|
|
38
|
+
assert_that = self.assert_that() # Get assertion helper
|
|
39
|
+
|
|
40
|
+
# Simple assertions
|
|
41
|
+
response = await page.goto("/dashboard")
|
|
42
|
+
assert_that.status_ok(response, "Dashboard request")
|
|
43
|
+
assert_that.url_contains(page, "/dashboard", "Dashboard URL")
|
|
44
|
+
|
|
45
|
+
# Check debug files
|
|
46
|
+
assert_that.debug_file_contains("client_sessions.json", "status", "active")
|
|
47
|
+
|
|
48
|
+
# Return result based on assertions
|
|
49
|
+
if assert_that.has_failures():
|
|
50
|
+
return TestResult(self.name, False, assert_that.get_failure_message())
|
|
51
|
+
|
|
52
|
+
return TestResult(self.name, True, "Test passed!")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 🎯 Easy Assertions
|
|
56
|
+
|
|
57
|
+
### Available Assertion Methods
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
assert_that = self.assert_that() # Get assertion helper
|
|
61
|
+
|
|
62
|
+
# Basic assertions
|
|
63
|
+
assert_that.eq(actual, expected, "Custom message")
|
|
64
|
+
assert_that.contains(container, item, "Should contain item")
|
|
65
|
+
assert_that.is_true(value, "Should be truthy")
|
|
66
|
+
assert_that.is_false(value, "Should be falsy")
|
|
67
|
+
|
|
68
|
+
# HTTP assertions
|
|
69
|
+
assert_that.status_ok(response, "Request should succeed")
|
|
70
|
+
|
|
71
|
+
# Page assertions
|
|
72
|
+
assert_that.url_contains(page, "/dashboard", "Should be on dashboard")
|
|
73
|
+
await assert_that.element_visible(page, ".success-message", "Success shown")
|
|
74
|
+
|
|
75
|
+
# WebSocket assertions
|
|
76
|
+
messages = [...] # Your WebSocket message list
|
|
77
|
+
assert_that.websocket_message(messages, "connection_established")
|
|
78
|
+
assert_that.websocket_message(messages, "file_update", {"file": "test.py"})
|
|
79
|
+
|
|
80
|
+
# Debug file assertions
|
|
81
|
+
assert_that.debug_file_contains("client_sessions.json", "status", "active")
|
|
82
|
+
assert_that.debug_file_contains("project_state_debug.json", "file_count")
|
|
83
|
+
|
|
84
|
+
# Check results
|
|
85
|
+
if assert_that.has_failures():
|
|
86
|
+
return TestResult(self.name, False, assert_that.get_failure_message())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 🔍 Debug File Inspection & WebSocket Debugging
|
|
90
|
+
|
|
91
|
+
### Built-in Inspector Helpers
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
inspector = self.inspect() # Get debug inspector
|
|
95
|
+
|
|
96
|
+
# Load debug files
|
|
97
|
+
sessions = inspector.load_client_sessions() # client_sessions.json
|
|
98
|
+
project_state = inspector.load_project_state() # project_state_debug.json
|
|
99
|
+
|
|
100
|
+
# Get specific data
|
|
101
|
+
active_sessions = inspector.get_active_sessions() # List of active session IDs
|
|
102
|
+
session_info = inspector.get_session_info("sess_123") # Info for specific session
|
|
103
|
+
project_files = inspector.get_project_files() # List of project files
|
|
104
|
+
|
|
105
|
+
# Use in assertions
|
|
106
|
+
assert_that.is_true(len(active_sessions) > 0, "Should have active sessions")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### WebSocket Debugging
|
|
110
|
+
|
|
111
|
+
Each test generates `websockets.json` with all WebSocket messages:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
[
|
|
115
|
+
{
|
|
116
|
+
"timestamp": "2025-08-07T07:37:46.712124",
|
|
117
|
+
"type": "message_sent",
|
|
118
|
+
"url": "ws://localhost:8001/ws/terminal/channel_123/",
|
|
119
|
+
"data": {"type": "command", "data": "ls -la"}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Located in: `test_results/run_TIMESTAMP/recordings/session_NAME/websockets.json`
|
|
125
|
+
|
|
126
|
+
## 🔗 Hierarchical Dependencies
|
|
127
|
+
|
|
128
|
+
### Dependency Types
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
class MyTest(BaseTest):
|
|
132
|
+
def __init__(self):
|
|
133
|
+
super().__init__(
|
|
134
|
+
# ... other params ...
|
|
135
|
+
depends_on=["login_flow_test", "ide_launch_test"] # Explicit dependencies
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def run(self) -> TestResult:
|
|
139
|
+
# Access dependency results
|
|
140
|
+
login_result = self.get_dependency_result("login_flow_test")
|
|
141
|
+
if login_result and login_result.success:
|
|
142
|
+
# Login was successful, proceed
|
|
143
|
+
pass
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Dependency Resolution
|
|
147
|
+
|
|
148
|
+
The framework automatically:
|
|
149
|
+
- **Sorts tests** in dependency order (topological sort)
|
|
150
|
+
- **Skips tests** whose dependencies failed
|
|
151
|
+
- **Prevents circular dependencies**
|
|
152
|
+
|
|
153
|
+
## 🧭 Auto-Navigation with start_url
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
class DashboardTest(BaseTest):
|
|
157
|
+
def __init__(self):
|
|
158
|
+
super().__init__(
|
|
159
|
+
name="dashboard_test",
|
|
160
|
+
start_url="/dashboard/" # Relative URL - auto-navigate before test runs
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- Use **relative URLs** like `/dashboard/`, `/project/123/`
|
|
165
|
+
- Framework navigates only if current page differs
|
|
166
|
+
- Prevents tests breaking from previous test page states
|
|
167
|
+
|
|
168
|
+
## 📂 Test Categories
|
|
169
|
+
|
|
170
|
+
- **`SMOKE`**: Basic functionality tests
|
|
171
|
+
- **`INTEGRATION`**: Cross-system tests
|
|
172
|
+
- **`UI`**: User interface tests
|
|
173
|
+
- **`API`**: API endpoint tests
|
|
174
|
+
- **`PERFORMANCE`**: Speed and load tests
|
|
175
|
+
- **`SECURITY`**: Security validation tests
|
|
176
|
+
|
|
177
|
+
## 🏷️ Test Tags
|
|
178
|
+
|
|
179
|
+
Use tags for flexible test filtering:
|
|
180
|
+
```python
|
|
181
|
+
tags=["login", "authentication", "smoke", "critical"]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Run tests by tags:
|
|
185
|
+
```bash
|
|
186
|
+
python -m testing_framework.cli run-tags login authentication
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## ✅ Test Result Best Practices
|
|
190
|
+
|
|
191
|
+
### Success Criteria
|
|
192
|
+
- Always verify the expected outcome occurred
|
|
193
|
+
- Check HTTP status codes (200 for success)
|
|
194
|
+
- Validate redirects go to expected URLs
|
|
195
|
+
- Confirm elements/content are present
|
|
196
|
+
|
|
197
|
+
### Failure Handling
|
|
198
|
+
```python
|
|
199
|
+
try:
|
|
200
|
+
# Test logic
|
|
201
|
+
if not expected_condition:
|
|
202
|
+
return TestResult(self.name, False, "Specific failure reason")
|
|
203
|
+
return TestResult(self.name, True, "Success message")
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return TestResult(self.name, False, f"Exception: {str(e)}")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Error Messages
|
|
209
|
+
- Be specific about what failed
|
|
210
|
+
- Include relevant URLs, status codes, or element selectors
|
|
211
|
+
- Help debugging with clear context
|
|
212
|
+
|
|
213
|
+
## 📋 Examples
|
|
214
|
+
|
|
215
|
+
### Login Test (Proper)
|
|
216
|
+
```python
|
|
217
|
+
async def run(self) -> TestResult:
|
|
218
|
+
page = self.playwright_manager.page
|
|
219
|
+
|
|
220
|
+
# Try accessing dashboard directly
|
|
221
|
+
response = await page.goto(f"{base_url}/dashboard/")
|
|
222
|
+
final_url = page.url
|
|
223
|
+
|
|
224
|
+
if "/dashboard" in final_url and response.status == 200:
|
|
225
|
+
return TestResult(self.name, True, f"Authenticated - Dashboard accessible")
|
|
226
|
+
elif "login" in final_url:
|
|
227
|
+
return TestResult(self.name, False, "Not authenticated - redirected to login")
|
|
228
|
+
else:
|
|
229
|
+
return TestResult(self.name, False, f"Unexpected response: {response.status}")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Form Submission Test
|
|
233
|
+
```python
|
|
234
|
+
async def run(self) -> TestResult:
|
|
235
|
+
page = self.playwright_manager.page
|
|
236
|
+
|
|
237
|
+
# Fill form
|
|
238
|
+
await page.fill("#email", "test@example.com")
|
|
239
|
+
await page.fill("#message", "Test message")
|
|
240
|
+
|
|
241
|
+
# Submit and wait for response
|
|
242
|
+
await page.click("button[type='submit']")
|
|
243
|
+
await page.wait_for_load_state("networkidle")
|
|
244
|
+
|
|
245
|
+
# Check for success indicator
|
|
246
|
+
if await page.is_visible(".success-alert"):
|
|
247
|
+
return TestResult(self.name, True, "Form submitted successfully")
|
|
248
|
+
elif await page.is_visible(".error-alert"):
|
|
249
|
+
error_text = await page.text_content(".error-alert")
|
|
250
|
+
return TestResult(self.name, False, f"Form error: {error_text}")
|
|
251
|
+
else:
|
|
252
|
+
return TestResult(self.name, False, "No response indicator found")
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## 🔧 File Naming
|
|
256
|
+
|
|
257
|
+
- Files: `test_feature_name.py`
|
|
258
|
+
- Classes: `FeatureNameTest`
|
|
259
|
+
- Test names: `feature_name_test`
|
|
260
|
+
|
|
261
|
+
## 🚀 Running Tests
|
|
262
|
+
|
|
263
|
+
### Hierarchical Mode (Recommended)
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# All tests with dependency resolution
|
|
267
|
+
python -m testing_framework.cli run-hierarchical
|
|
268
|
+
|
|
269
|
+
# Specific tests with dependencies
|
|
270
|
+
python -m testing_framework.cli run-hierarchical-tests login_flow_test websocket_test
|
|
271
|
+
|
|
272
|
+
# All tests with hierarchical option
|
|
273
|
+
python -m testing_framework.cli run-all --hierarchical
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Standard Mode
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Single test
|
|
280
|
+
python -m testing_framework.cli run-tests your_test_name
|
|
281
|
+
|
|
282
|
+
# By category
|
|
283
|
+
python -m testing_framework.cli run-category smoke
|
|
284
|
+
|
|
285
|
+
# By tags
|
|
286
|
+
python -m testing_framework.cli run-tags login authentication
|
|
287
|
+
|
|
288
|
+
# All tests (no dependencies)
|
|
289
|
+
python -m testing_framework.cli run-all
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### CLI Features
|
|
293
|
+
|
|
294
|
+
- **Auto Debug Mode**: CLI connects with `--debug` flag automatically
|
|
295
|
+
- **Dependency Analysis**: Shows which tests were skipped and why
|
|
296
|
+
- **Shared Connection**: All tests share one CLI connection for efficiency
|
test_modules/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Example test modules for the testing framework."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Test that device shows online in dashboard."""
|
|
2
|
+
|
|
3
|
+
from testing_framework.core.base_test import BaseTest, TestResult, TestCategory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DeviceOnlineTest(BaseTest):
|
|
7
|
+
"""Test that the device is showing as online in the dashboard."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(
|
|
11
|
+
name="device_online_test",
|
|
12
|
+
category=TestCategory.SMOKE,
|
|
13
|
+
description="Verify device shows as online in dashboard after login",
|
|
14
|
+
tags=["device", "online", "dashboard"],
|
|
15
|
+
depends_on=["login_flow_test"],
|
|
16
|
+
start_url="/dashboard/"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
async def run(self) -> TestResult:
|
|
20
|
+
"""Test device online status."""
|
|
21
|
+
page = self.playwright_manager.page
|
|
22
|
+
assert_that = self.assert_that()
|
|
23
|
+
|
|
24
|
+
# Find portacode streamer device card that's online
|
|
25
|
+
device_card = page.locator(".device-card.online").filter(has_text="portacode streamer")
|
|
26
|
+
await device_card.wait_for()
|
|
27
|
+
|
|
28
|
+
# Verify device name contains "portacode streamer"
|
|
29
|
+
device_name = device_card.locator(".device-name-text")
|
|
30
|
+
device_name_text = await device_name.text_content()
|
|
31
|
+
assert_that.contains(device_name_text.lower(), "portacode streamer", "Device name")
|
|
32
|
+
|
|
33
|
+
if assert_that.has_failures():
|
|
34
|
+
return TestResult(self.name, False, assert_that.get_failure_message())
|
|
35
|
+
|
|
36
|
+
return TestResult(self.name, True, "Device shows online in dashboard")
|
|
37
|
+
|
|
38
|
+
async def setup(self):
|
|
39
|
+
"""Setup for device online test."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
async def teardown(self):
|
|
43
|
+
"""Teardown for device online test."""
|
|
44
|
+
pass
|