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.

Files changed (92) hide show
  1. portacode/_version.py +16 -3
  2. portacode/cli.py +143 -17
  3. portacode/connection/client.py +149 -10
  4. portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +824 -21
  5. portacode/connection/handlers/__init__.py +28 -1
  6. portacode/connection/handlers/base.py +78 -16
  7. portacode/connection/handlers/chunked_content.py +244 -0
  8. portacode/connection/handlers/diff_handlers.py +603 -0
  9. portacode/connection/handlers/file_handlers.py +902 -17
  10. portacode/connection/handlers/project_aware_file_handlers.py +226 -0
  11. portacode/connection/handlers/project_state/README.md +312 -0
  12. portacode/connection/handlers/project_state/__init__.py +92 -0
  13. portacode/connection/handlers/project_state/file_system_watcher.py +179 -0
  14. portacode/connection/handlers/project_state/git_manager.py +1502 -0
  15. portacode/connection/handlers/project_state/handlers.py +875 -0
  16. portacode/connection/handlers/project_state/manager.py +1331 -0
  17. portacode/connection/handlers/project_state/models.py +108 -0
  18. portacode/connection/handlers/project_state/utils.py +50 -0
  19. portacode/connection/handlers/project_state_handlers.py +45 -2185
  20. portacode/connection/handlers/proxmox_infra.py +361 -0
  21. portacode/connection/handlers/registry.py +15 -4
  22. portacode/connection/handlers/session.py +483 -32
  23. portacode/connection/handlers/system_handlers.py +147 -8
  24. portacode/connection/handlers/tab_factory.py +53 -46
  25. portacode/connection/handlers/terminal_handlers.py +21 -8
  26. portacode/connection/handlers/update_handler.py +61 -0
  27. portacode/connection/multiplex.py +60 -2
  28. portacode/connection/terminal.py +214 -24
  29. portacode/keypair.py +63 -1
  30. portacode/link_capture/__init__.py +38 -0
  31. portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  32. portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  33. portacode/link_capture/bin/elinks +3 -0
  34. portacode/link_capture/bin/gio-open +3 -0
  35. portacode/link_capture/bin/gnome-open +3 -0
  36. portacode/link_capture/bin/gvfs-open +3 -0
  37. portacode/link_capture/bin/kde-open +3 -0
  38. portacode/link_capture/bin/kfmclient +3 -0
  39. portacode/link_capture/bin/link_capture_exec.sh +11 -0
  40. portacode/link_capture/bin/link_capture_wrapper.py +75 -0
  41. portacode/link_capture/bin/links +3 -0
  42. portacode/link_capture/bin/links2 +3 -0
  43. portacode/link_capture/bin/lynx +3 -0
  44. portacode/link_capture/bin/mate-open +3 -0
  45. portacode/link_capture/bin/netsurf +3 -0
  46. portacode/link_capture/bin/sensible-browser +3 -0
  47. portacode/link_capture/bin/w3m +3 -0
  48. portacode/link_capture/bin/x-www-browser +3 -0
  49. portacode/link_capture/bin/xdg-open +3 -0
  50. portacode/logging_categories.py +140 -0
  51. portacode/pairing.py +103 -0
  52. portacode/static/js/test-ntp-clock.html +63 -0
  53. portacode/static/js/utils/ntp-clock.js +232 -0
  54. portacode/utils/NTP_ARCHITECTURE.md +136 -0
  55. portacode/utils/__init__.py +1 -0
  56. portacode/utils/diff_apply.py +456 -0
  57. portacode/utils/diff_renderer.py +371 -0
  58. portacode/utils/ntp_clock.py +65 -0
  59. portacode-1.4.11.dev1.dist-info/METADATA +298 -0
  60. portacode-1.4.11.dev1.dist-info/RECORD +97 -0
  61. {portacode-0.3.19.dev4.dist-info → portacode-1.4.11.dev1.dist-info}/WHEEL +1 -1
  62. portacode-1.4.11.dev1.dist-info/top_level.txt +3 -0
  63. test_modules/README.md +296 -0
  64. test_modules/__init__.py +1 -0
  65. test_modules/test_device_online.py +44 -0
  66. test_modules/test_file_operations.py +743 -0
  67. test_modules/test_git_status_ui.py +370 -0
  68. test_modules/test_login_flow.py +50 -0
  69. test_modules/test_navigate_testing_folder.py +361 -0
  70. test_modules/test_play_store_screenshots.py +294 -0
  71. test_modules/test_terminal_buffer_performance.py +261 -0
  72. test_modules/test_terminal_interaction.py +80 -0
  73. test_modules/test_terminal_loading_race_condition.py +95 -0
  74. test_modules/test_terminal_start.py +56 -0
  75. testing_framework/.env.example +21 -0
  76. testing_framework/README.md +334 -0
  77. testing_framework/__init__.py +17 -0
  78. testing_framework/cli.py +326 -0
  79. testing_framework/core/__init__.py +1 -0
  80. testing_framework/core/base_test.py +336 -0
  81. testing_framework/core/cli_manager.py +177 -0
  82. testing_framework/core/hierarchical_runner.py +577 -0
  83. testing_framework/core/playwright_manager.py +520 -0
  84. testing_framework/core/runner.py +447 -0
  85. testing_framework/core/shared_cli_manager.py +234 -0
  86. testing_framework/core/test_discovery.py +112 -0
  87. testing_framework/requirements.txt +12 -0
  88. portacode-0.3.19.dev4.dist-info/METADATA +0 -241
  89. portacode-0.3.19.dev4.dist-info/RECORD +0 -30
  90. portacode-0.3.19.dev4.dist-info/top_level.txt +0 -1
  91. {portacode-0.3.19.dev4.dist-info → portacode-1.4.11.dev1.dist-info}/entry_points.txt +0 -0
  92. {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
@@ -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