vericue 0.3.5__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 (46) hide show
  1. vericue-0.3.5/PKG-INFO +12 -0
  2. vericue-0.3.5/README.md +87 -0
  3. vericue-0.3.5/examples/event_subscriptions.py +78 -0
  4. vericue-0.3.5/examples/keyboard_and_drag.py +88 -0
  5. vericue-0.3.5/examples/model_data_access.py +190 -0
  6. vericue-0.3.5/examples/ping_example.py +23 -0
  7. vericue-0.3.5/examples/qml_task_manager.py +244 -0
  8. vericue-0.3.5/examples/record_and_replay.py +89 -0
  9. vericue-0.3.5/examples/test_with_report.py +140 -0
  10. vericue-0.3.5/examples/touch_demo_screenshot.png +0 -0
  11. vericue-0.3.5/examples/touch_gestures.py +240 -0
  12. vericue-0.3.5/examples/visual_regression.py +127 -0
  13. vericue-0.3.5/examples/wait_for_signals.py +100 -0
  14. vericue-0.3.5/pyproject.toml +24 -0
  15. vericue-0.3.5/setup.cfg +4 -0
  16. vericue-0.3.5/tests/conftest.py +221 -0
  17. vericue-0.3.5/tests/robot/smoke.robot +26 -0
  18. vericue-0.3.5/tests/test_client.py +70 -0
  19. vericue-0.3.5/tests/test_fixtures.py +37 -0
  20. vericue-0.3.5/tests/test_integration.py +426 -0
  21. vericue-0.3.5/tests/test_interaction.py +167 -0
  22. vericue-0.3.5/tests/test_license_integration.py +236 -0
  23. vericue-0.3.5/tests/test_model_data.py +154 -0
  24. vericue-0.3.5/tests/test_multi.py +49 -0
  25. vericue-0.3.5/tests/test_new_features.py +346 -0
  26. vericue-0.3.5/tests/test_object_commands.py +339 -0
  27. vericue-0.3.5/tests/test_protocol.py +76 -0
  28. vericue-0.3.5/tests/test_robot.py +85 -0
  29. vericue-0.3.5/tests/test_touch.py +122 -0
  30. vericue-0.3.5/vericue/__init__.py +19 -0
  31. vericue-0.3.5/vericue/__main__.py +316 -0
  32. vericue-0.3.5/vericue/client.py +614 -0
  33. vericue-0.3.5/vericue/conftest_plugin.py +213 -0
  34. vericue-0.3.5/vericue/errors.py +20 -0
  35. vericue-0.3.5/vericue/inspector.py +297 -0
  36. vericue-0.3.5/vericue/multi.py +120 -0
  37. vericue-0.3.5/vericue/protocol.py +45 -0
  38. vericue-0.3.5/vericue/recorder.py +38 -0
  39. vericue-0.3.5/vericue/reporter.py +188 -0
  40. vericue-0.3.5/vericue/robot.py +213 -0
  41. vericue-0.3.5/vericue.egg-info/PKG-INFO +12 -0
  42. vericue-0.3.5/vericue.egg-info/SOURCES.txt +44 -0
  43. vericue-0.3.5/vericue.egg-info/dependency_links.txt +1 -0
  44. vericue-0.3.5/vericue.egg-info/entry_points.txt +5 -0
  45. vericue-0.3.5/vericue.egg-info/requires.txt +10 -0
  46. vericue-0.3.5/vericue.egg-info/top_level.txt +1 -0
vericue-0.3.5/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: vericue
3
+ Version: 0.3.5
4
+ Summary: Python client library for veriCue test automation framework
5
+ Requires-Python: >=3.10
6
+ Provides-Extra: dev
7
+ Requires-Dist: pytest>=7.0; extra == "dev"
8
+ Requires-Dist: pytest-asyncio; extra == "dev"
9
+ Provides-Extra: inspector
10
+ Requires-Dist: rich>=13.0; extra == "inspector"
11
+ Provides-Extra: robot
12
+ Requires-Dist: robotframework>=6.0; extra == "robot"
@@ -0,0 +1,87 @@
1
+ # veriCue Python Client
2
+
3
+ An asynchronous Python client library for communicating with the veriCue server. Built on asyncio.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Install from the client directory
9
+ pip install -e client/python
10
+
11
+ # With development dependencies (pytest)
12
+ pip install -e "client/python[dev]"
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ import asyncio
19
+ from vericue import VeriCueClient
20
+
21
+ async def main():
22
+ async with VeriCueClient() as client:
23
+ await client.connect("127.0.0.1", 4242)
24
+
25
+ pong = await client.ping()
26
+ print(f"Ping: {pong}") # True
27
+
28
+ version = await client.version()
29
+ print(f"Server: {version}") # {"server": "vericue", "version": "0.1.0", ...}
30
+
31
+ echo = await client.echo("hello")
32
+ print(f"Echo: {echo}") # "hello"
33
+
34
+ asyncio.run(main())
35
+ ```
36
+
37
+ ## API
38
+
39
+ ```python
40
+ class VeriCueClient:
41
+ async def connect(host="127.0.0.1", port=4242) -> None
42
+ async def disconnect() -> None
43
+ is_connected: bool
44
+
45
+ # High-level commands
46
+ async def ping() -> bool
47
+ async def version() -> dict
48
+ async def echo(text: str) -> str
49
+
50
+ # Low-level command
51
+ async def send_request(method: str, params: dict = None) -> dict
52
+ ```
53
+
54
+ Supports async context manager (`async with`). Default timeout: 10 seconds (configurable via `VeriCueClient(timeout=30.0)`).
55
+
56
+ ## Error Handling
57
+
58
+ ```python
59
+ from vericue import VeriCueClient, ServerError, ConnectionError, ProtocolError
60
+
61
+ try:
62
+ result = await client.send_request("unknown_method")
63
+ except ServerError as e:
64
+ print(e.code, e.message) # 1003, "Unknown method: unknown_method"
65
+ except ConnectionError:
66
+ print("Not connected to server")
67
+ ```
68
+
69
+ ## Testing
70
+
71
+ ```bash
72
+ pytest client/python/tests/
73
+ ```
74
+
75
+ Tests use a mock TCP server (asyncio) — no running veriCue server required.
76
+
77
+ ## Structure
78
+
79
+ ```
80
+ vericue/
81
+ ├── __init__.py — public exports
82
+ ├── client.py — VeriCueClient (asyncio)
83
+ ├── protocol.py — framing and message building
84
+ └── errors.py — exceptions (VeriCueError, ServerError, ConnectionError, ProtocolError)
85
+ examples/ — usage examples
86
+ tests/ — unit and integration tests
87
+ ```
@@ -0,0 +1,78 @@
1
+ """Live event subscription example.
2
+
3
+ Subscribes to a button's ``clicked`` signal and a line-edit's ``text``
4
+ property, simulates input, then prints every push event the server
5
+ sends. Demonstrates both per-subscription callbacks and the shared
6
+ ``events()`` async iterator.
7
+
8
+ Usage:
9
+ # Start the test app first (in another shell):
10
+ QT_QPA_PLATFORM=offscreen \
11
+ LD_LIBRARY_PATH=build/server:$QT_LIB \
12
+ build/examples/test_app/vericue-test-app --port 4242
13
+
14
+ python client/python/examples/event_subscriptions.py
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+
21
+ from vericue import VeriCueClient
22
+
23
+
24
+ async def main() -> None:
25
+ async with VeriCueClient() as client:
26
+ await client.connect("127.0.0.1", 4242)
27
+
28
+ # 1. Subscribe with a per-event callback.
29
+ async def on_click(event: dict) -> None:
30
+ print(f" → callback got click event at {event['timestamp']}")
31
+
32
+ click_id = await client.subscribe_signal(
33
+ "TestWindow/centralWidget/testButton", "clicked", callback=on_click,
34
+ )
35
+ print(f"Subscribed to clicked: {click_id}")
36
+
37
+ # 2. Subscribe to a property and just consume from the queue.
38
+ text_id = await client.subscribe_property(
39
+ "TestWindow/centralWidget/testInput", "text",
40
+ )
41
+ print(f"Subscribed to text property: {text_id}")
42
+
43
+ # 3. Subscribe to destroyed.
44
+ destroyed_id = await client.subscribe_destroyed(
45
+ "TestWindow/centralWidget/testLabel",
46
+ )
47
+ print(f"Subscribed to destroyed: {destroyed_id}")
48
+
49
+ # Inspect what's active server-side.
50
+ active = await client.list_subscriptions()
51
+ print(f"Active subscriptions: {[s['subscription_id'] for s in active]}")
52
+
53
+ # Simulate some activity that should produce events.
54
+ await client.mouse_click("TestWindow/centralWidget/testButton")
55
+ await client.set_property(
56
+ "TestWindow/centralWidget/testInput", "text", "hello world",
57
+ )
58
+
59
+ # Drain a few events.
60
+ for _ in range(2):
61
+ try:
62
+ event = await client.next_event(timeout=2.0)
63
+ print(
64
+ f" ← {event['event']:18s} sub={event['subscription_id']} "
65
+ f"data={event['data']}"
66
+ )
67
+ except asyncio.TimeoutError:
68
+ print(" (no more events within 2s)")
69
+ break
70
+
71
+ await client.unsubscribe(click_id)
72
+ await client.unsubscribe(text_id)
73
+ await client.unsubscribe(destroyed_id)
74
+ print("All subscriptions removed.")
75
+
76
+
77
+ if __name__ == "__main__":
78
+ asyncio.run(main())
@@ -0,0 +1,88 @@
1
+ """Keyboard shortcuts and drag & drop example.
2
+
3
+ Demonstrates key_press (special keys, modifier combos) and drag commands.
4
+
5
+ Usage:
6
+ 1. Start a demo app: ./build/examples/demo_app/vericue-demo-app
7
+ 2. Run this script: python examples/keyboard_and_drag.py
8
+ """
9
+
10
+ import asyncio
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).parent.parent))
15
+
16
+ from vericue import VeriCueClient
17
+
18
+
19
+ async def main():
20
+ async with VeriCueClient() as client:
21
+ await client.connect("127.0.0.1", 4242)
22
+ print("Connected.\n")
23
+
24
+ # --- Keyboard shortcuts ---
25
+ print("=== Keyboard Shortcuts ===")
26
+
27
+ # Click on input field and type, then use keyboard to select all + delete
28
+ input_path = "DemoMainWindow/centralWidget/inputField"
29
+
30
+ await client.mouse_click(input_path)
31
+ await client.type_text(input_path, "Hello World")
32
+ print("Typed 'Hello World'")
33
+
34
+ # Select all with Ctrl+A
35
+ await client.key_press(input_path, "ctrl+a")
36
+ print("Pressed Ctrl+A (select all)")
37
+
38
+ # Delete selection
39
+ await client.key_press(input_path, "delete")
40
+ print("Pressed Delete")
41
+
42
+ # Type new text and press Enter
43
+ await client.type_text(input_path, "New content")
44
+ await client.key_press(input_path, "enter")
45
+ print("Typed 'New content' + Enter")
46
+
47
+ # Tab to next widget
48
+ await client.key_press(input_path, "tab")
49
+ print("Pressed Tab (focus next widget)")
50
+
51
+ # Arrow keys
52
+ await client.key_press(input_path, "up", repeat=3)
53
+ print("Pressed Up x3")
54
+
55
+ # Escape
56
+ await client.key_press(input_path, "escape")
57
+ print("Pressed Escape")
58
+
59
+ # Function key
60
+ await client.key_press(input_path, "f1")
61
+ print("Pressed F1")
62
+
63
+ # --- Drag ---
64
+ print("\n=== Drag & Drop ===")
65
+
66
+ # Drag on the progress bar (simulates slider drag)
67
+ progress_path = "DemoMainWindow/centralWidget/progressBar"
68
+
69
+ result = await client.drag(progress_path, 10, 10, 150, 10)
70
+ print(f"Dragged from ({result['from']['x']},{result['from']['y']}) "
71
+ f"to ({result['to']['x']},{result['to']['y']})")
72
+
73
+ # Drag with right button
74
+ result = await client.drag(progress_path, 0, 0, 50, 50, button="right")
75
+ print(f"Right-button drag completed")
76
+
77
+ # Drag with modifier (Ctrl+drag for copy in many apps)
78
+ result = await client.drag(
79
+ progress_path, 20, 15, 180, 15,
80
+ modifiers=["ctrl"]
81
+ )
82
+ print(f"Ctrl+drag completed")
83
+
84
+ print("\nDone!")
85
+
86
+
87
+ if __name__ == "__main__":
88
+ asyncio.run(main())
@@ -0,0 +1,190 @@
1
+ """Model/View data access example.
2
+
3
+ Demonstrates reading data from QTableView, QTreeView, and QListView models:
4
+ model info (row/column counts, headers), single cell reads, range queries,
5
+ and filtering.
6
+
7
+ Usage:
8
+ 1. Build and start the table demo:
9
+ cmake --build build --parallel
10
+ ./build/examples/table_app/vericue-table-app --port 4245
11
+ 2. Run this script:
12
+ python examples/model_data_access.py
13
+ """
14
+
15
+ import asyncio
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ sys.path.insert(0, str(Path(__file__).parent.parent))
20
+
21
+ from vericue import VeriCueClient, ServerError
22
+
23
+
24
+ HOST = "127.0.0.1"
25
+ PORT = 4245
26
+
27
+
28
+ def log(msg: str) -> None:
29
+ print(f" >> {msg}")
30
+
31
+
32
+ async def main():
33
+ async with VeriCueClient(timeout=15.0) as client:
34
+ await client.connect(HOST, PORT)
35
+ log("Connected to Table Demo app\n")
36
+
37
+ # ──────────────────────────────────────────
38
+ # 1. EMPLOYEE TABLE — model info
39
+ # ──────────────────────────────────────────
40
+ print("=" * 60)
41
+ print(" 1. EMPLOYEE TABLE — Model Info")
42
+ print("=" * 60)
43
+
44
+ table_path = (await client.find_object(object_name="employeeTable"))["path"]
45
+ info = await client.get_model_info(table_path)
46
+
47
+ log(f"Model class: {info['model_class']}")
48
+ log(f"Rows: {info['row_count']}")
49
+ log(f"Columns: {info['column_count']}")
50
+ log(f"Headers: {info['horizontal_headers']}")
51
+
52
+ # ──────────────────────────────────────────
53
+ # 2. EMPLOYEE TABLE — single cell reads
54
+ # ──────────────────────────────────────────
55
+ print(f"\n{'=' * 60}")
56
+ print(" 2. EMPLOYEE TABLE — Single Cell Reads")
57
+ print("=" * 60)
58
+
59
+ for row in range(min(5, info["row_count"])):
60
+ name = await client.get_model_data(table_path, row=row, column=0)
61
+ dept = await client.get_model_data(table_path, row=row, column=1)
62
+ salary = await client.get_model_data(table_path, row=row, column=2)
63
+ log(f"Row {row}: {name['value']:20s} | {dept['value']:15s} | ${salary['value']}")
64
+
65
+ # ──────────────────────────────────────────
66
+ # 3. EMPLOYEE TABLE — range query (all rows)
67
+ # ──────────────────────────────────────────
68
+ print(f"\n{'=' * 60}")
69
+ print(" 3. EMPLOYEE TABLE — Full Range Query")
70
+ print("=" * 60)
71
+
72
+ data = await client.get_model_data(table_path)
73
+ log(f"Fetched {data['row_count']} rows")
74
+
75
+ # Print as formatted table
76
+ headers = info["horizontal_headers"]
77
+ col_widths = [max(len(str(h)), 15) for h in headers]
78
+
79
+ header_line = " | ".join(f"{h:<{w}}" for h, w in zip(headers, col_widths))
80
+ log(header_line)
81
+ log("-" * len(header_line))
82
+
83
+ for row_data in data["rows"]:
84
+ cells = row_data["cells"]
85
+ values = [str(cells.get(str(i), "")) for i in range(len(headers))]
86
+ line = " | ".join(f"{v:<{w}}" for v, w in zip(values, col_widths))
87
+ log(line)
88
+
89
+ # ──────────────────────────────────────────
90
+ # 4. EMPLOYEE TABLE — filtered columns
91
+ # ──────────────────────────────────────────
92
+ print(f"\n{'=' * 60}")
93
+ print(" 4. EMPLOYEE TABLE — Name and City Only (columns 0, 3)")
94
+ print("=" * 60)
95
+
96
+ data = await client.get_model_data(table_path, columns=[0, 3])
97
+ for row_data in data["rows"]:
98
+ cells = row_data["cells"]
99
+ name = cells.get("0", "")
100
+ city = cells.get("3", "")
101
+ log(f"{name:20s} → {city}")
102
+
103
+ # ──────────────────────────────────────────
104
+ # 5. EMPLOYEE TABLE — partial range
105
+ # ──────────────────────────────────────────
106
+ print(f"\n{'=' * 60}")
107
+ print(" 5. EMPLOYEE TABLE — Rows 3-7 only")
108
+ print("=" * 60)
109
+
110
+ data = await client.get_model_data(table_path, start_row=3, end_row=7)
111
+ log(f"Fetched rows {data['start_row']}-{data['end_row']} ({data['row_count']} rows)")
112
+ for row_data in data["rows"]:
113
+ cells = row_data["cells"]
114
+ log(f" Row {row_data['row']}: {cells.get('0', '')} — {cells.get('1', '')}")
115
+
116
+ # ──────────────────────────────────────────
117
+ # 6. CITY LIST — model info + data
118
+ # ──────────────────────────────────────────
119
+ print(f"\n{'=' * 60}")
120
+ print(" 6. CITY LIST")
121
+ print("=" * 60)
122
+
123
+ list_path = (await client.find_object(object_name="cityList"))["path"]
124
+ list_info = await client.get_model_info(list_path)
125
+ log(f"Model: {list_info['model_class']}, {list_info['row_count']} items")
126
+
127
+ list_data = await client.get_model_data(list_path)
128
+ cities = [row["cells"]["0"] for row in list_data["rows"]]
129
+ log(f"Cities: {', '.join(cities)}")
130
+
131
+ # ──────────────────────────────────────────
132
+ # 7. FILE TREE — model info + data
133
+ # ──────────────────────────────────────────
134
+ print(f"\n{'=' * 60}")
135
+ print(" 7. FILE TREE")
136
+ print("=" * 60)
137
+
138
+ tree_path = (await client.find_object(object_name="fileTree"))["path"]
139
+ tree_info = await client.get_model_info(tree_path)
140
+ log(f"Model: {tree_info['model_class']}")
141
+ log(f"Top-level rows: {tree_info['row_count']}, columns: {tree_info['column_count']}")
142
+ log(f"Headers: {tree_info['horizontal_headers']}")
143
+
144
+ tree_data = await client.get_model_data(tree_path)
145
+ for row_data in tree_data["rows"]:
146
+ cells = row_data["cells"]
147
+ log(f" {cells.get('0', '')} {cells.get('1', '')}")
148
+
149
+ # ──────────────────────────────────────────
150
+ # 8. SEARCH — find employees by department
151
+ # ──────────────────────────────────────────
152
+ print(f"\n{'=' * 60}")
153
+ print(" 8. SEARCH — Engineering Department")
154
+ print("=" * 60)
155
+
156
+ all_data = await client.get_model_data(table_path)
157
+ engineering = [
158
+ row for row in all_data["rows"]
159
+ if row["cells"].get("1") == "Engineering"
160
+ ]
161
+ log(f"Found {len(engineering)} engineers:")
162
+ for row_data in engineering:
163
+ cells = row_data["cells"]
164
+ log(f" {cells['0']:20s} ${cells['2']} in {cells['3']}")
165
+
166
+ # ──────────────────────────────────────────
167
+ # 9. AGGREGATION — average salary by department
168
+ # ──────────────────────────────────────────
169
+ print(f"\n{'=' * 60}")
170
+ print(" 9. SALARY SUMMARY")
171
+ print("=" * 60)
172
+
173
+ departments: dict[str, list[int]] = {}
174
+ for row_data in all_data["rows"]:
175
+ cells = row_data["cells"]
176
+ dept = cells.get("1", "")
177
+ salary = int(cells.get("2", "0"))
178
+ departments.setdefault(dept, []).append(salary)
179
+
180
+ for dept, salaries in sorted(departments.items()):
181
+ avg = sum(salaries) / len(salaries)
182
+ log(f" {dept:15s} avg: ${avg:>10,.0f} headcount: {len(salaries)}")
183
+
184
+ print(f"\n{'=' * 60}")
185
+ print(" DONE — all model data access completed")
186
+ print("=" * 60)
187
+
188
+
189
+ if __name__ == "__main__":
190
+ asyncio.run(main())
@@ -0,0 +1,23 @@
1
+ """Minimal example: connect to a veriCue server and run basic commands."""
2
+
3
+ import asyncio
4
+
5
+ from vericue import VeriCueClient
6
+
7
+
8
+ async def main():
9
+ async with VeriCueClient() as client:
10
+ await client.connect("127.0.0.1", 4242)
11
+
12
+ pong = await client.ping()
13
+ print(f"Ping: {pong}")
14
+
15
+ version = await client.version()
16
+ print(f"Version: {version}")
17
+
18
+ echo = await client.echo("hello vericue")
19
+ print(f"Echo: {echo}")
20
+
21
+
22
+ if __name__ == "__main__":
23
+ asyncio.run(main())