webtap-tool 0.11.0__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.
- webtap/VISION.md +246 -0
- webtap/__init__.py +84 -0
- webtap/__main__.py +6 -0
- webtap/api/__init__.py +9 -0
- webtap/api/app.py +26 -0
- webtap/api/models.py +69 -0
- webtap/api/server.py +111 -0
- webtap/api/sse.py +182 -0
- webtap/api/state.py +89 -0
- webtap/app.py +79 -0
- webtap/cdp/README.md +275 -0
- webtap/cdp/__init__.py +12 -0
- webtap/cdp/har.py +302 -0
- webtap/cdp/schema/README.md +41 -0
- webtap/cdp/schema/cdp_protocol.json +32785 -0
- webtap/cdp/schema/cdp_version.json +8 -0
- webtap/cdp/session.py +667 -0
- webtap/client.py +81 -0
- webtap/commands/DEVELOPER_GUIDE.md +401 -0
- webtap/commands/TIPS.md +269 -0
- webtap/commands/__init__.py +29 -0
- webtap/commands/_builders.py +331 -0
- webtap/commands/_code_generation.py +110 -0
- webtap/commands/_tips.py +147 -0
- webtap/commands/_utils.py +273 -0
- webtap/commands/connection.py +220 -0
- webtap/commands/console.py +87 -0
- webtap/commands/fetch.py +310 -0
- webtap/commands/filters.py +116 -0
- webtap/commands/javascript.py +73 -0
- webtap/commands/js_export.py +73 -0
- webtap/commands/launch.py +72 -0
- webtap/commands/navigation.py +197 -0
- webtap/commands/network.py +136 -0
- webtap/commands/quicktype.py +306 -0
- webtap/commands/request.py +93 -0
- webtap/commands/selections.py +138 -0
- webtap/commands/setup.py +219 -0
- webtap/commands/to_model.py +163 -0
- webtap/daemon.py +185 -0
- webtap/daemon_state.py +53 -0
- webtap/filters.py +219 -0
- webtap/rpc/__init__.py +14 -0
- webtap/rpc/errors.py +49 -0
- webtap/rpc/framework.py +223 -0
- webtap/rpc/handlers.py +625 -0
- webtap/rpc/machine.py +84 -0
- webtap/services/README.md +83 -0
- webtap/services/__init__.py +15 -0
- webtap/services/console.py +124 -0
- webtap/services/dom.py +547 -0
- webtap/services/fetch.py +415 -0
- webtap/services/main.py +392 -0
- webtap/services/network.py +401 -0
- webtap/services/setup/__init__.py +185 -0
- webtap/services/setup/chrome.py +233 -0
- webtap/services/setup/desktop.py +255 -0
- webtap/services/setup/extension.py +147 -0
- webtap/services/setup/platform.py +162 -0
- webtap/services/state_snapshot.py +86 -0
- webtap_tool-0.11.0.dist-info/METADATA +535 -0
- webtap_tool-0.11.0.dist-info/RECORD +64 -0
- webtap_tool-0.11.0.dist-info/WHEEL +4 -0
- webtap_tool-0.11.0.dist-info/entry_points.txt +2 -0
webtap/rpc/machine.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Connection state machine using transitions library.
|
|
2
|
+
|
|
3
|
+
PUBLIC API:
|
|
4
|
+
- ConnectionState: Enum of connection lifecycle states
|
|
5
|
+
- ConnectionMachine: Thread-safe state machine for connection management
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from transitions.extensions import LockedMachine
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnectionState(str, Enum):
|
|
15
|
+
"""Connection lifecycle states.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
DISCONNECTED: Not connected to any Chrome page
|
|
19
|
+
CONNECTING: Connection in progress
|
|
20
|
+
CONNECTED: Connected and ready for operations
|
|
21
|
+
INSPECTING: Element selection mode active
|
|
22
|
+
DISCONNECTING: Cleanup in progress
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
DISCONNECTED = "disconnected"
|
|
26
|
+
CONNECTING = "connecting"
|
|
27
|
+
CONNECTED = "connected"
|
|
28
|
+
INSPECTING = "inspecting"
|
|
29
|
+
DISCONNECTING = "disconnecting"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConnectionMachine(LockedMachine): # type: ignore
|
|
33
|
+
"""Thread-safe state machine for WebTap connection lifecycle.
|
|
34
|
+
|
|
35
|
+
States:
|
|
36
|
+
- disconnected: Not connected to any Chrome page
|
|
37
|
+
- connecting: Connection in progress
|
|
38
|
+
- connected: Connected and ready for operations
|
|
39
|
+
- inspecting: Element selection mode active
|
|
40
|
+
- disconnecting: Cleanup in progress
|
|
41
|
+
|
|
42
|
+
The machine uses LockedMachine for thread safety and queued transitions.
|
|
43
|
+
Epoch counter is incremented on successful connection.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
"""Initialize the connection state machine."""
|
|
48
|
+
_states = [s.value for s in ConnectionState]
|
|
49
|
+
_transitions: list[dict[str, Any]] = [
|
|
50
|
+
{"trigger": "start_connect", "source": ["disconnected", "connected", "inspecting"], "dest": "connecting"},
|
|
51
|
+
{
|
|
52
|
+
"trigger": "connect_success",
|
|
53
|
+
"source": "connecting",
|
|
54
|
+
"dest": "connected",
|
|
55
|
+
"after": "_increment_epoch",
|
|
56
|
+
},
|
|
57
|
+
{"trigger": "connect_failed", "source": "connecting", "dest": "disconnected"},
|
|
58
|
+
{"trigger": "start_inspect", "source": "connected", "dest": "inspecting"},
|
|
59
|
+
{"trigger": "stop_inspect", "source": "inspecting", "dest": "connected"},
|
|
60
|
+
{
|
|
61
|
+
"trigger": "start_disconnect",
|
|
62
|
+
"source": ["connected", "inspecting"],
|
|
63
|
+
"dest": "disconnecting",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"trigger": "disconnect_complete",
|
|
67
|
+
"source": ["disconnecting", "connecting"],
|
|
68
|
+
"dest": "disconnected",
|
|
69
|
+
},
|
|
70
|
+
{"trigger": "force_disconnect", "source": "*", "dest": "disconnected"},
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
super().__init__(
|
|
74
|
+
states=_states,
|
|
75
|
+
transitions=_transitions,
|
|
76
|
+
initial="disconnected",
|
|
77
|
+
auto_transitions=False,
|
|
78
|
+
queued=True,
|
|
79
|
+
)
|
|
80
|
+
self.epoch = 0
|
|
81
|
+
|
|
82
|
+
def _increment_epoch(self):
|
|
83
|
+
"""Increment epoch counter on successful connection."""
|
|
84
|
+
self.epoch += 1
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# WebTap Services Layer
|
|
2
|
+
|
|
3
|
+
The services layer provides clean, reusable interfaces for querying and managing CDP events stored in DuckDB.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
commands/ → RPCClient → /rpc endpoint → RPCFramework → services/ → cdp/session → DuckDB
|
|
9
|
+
↓
|
|
10
|
+
rpc/handlers.py (thin wrappers)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Services
|
|
14
|
+
|
|
15
|
+
### WebTapService (`main.py`)
|
|
16
|
+
Main orchestrator that manages all domain-specific services and CDP connection.
|
|
17
|
+
|
|
18
|
+
**Key Properties:**
|
|
19
|
+
- `event_count` - Total CDP events stored
|
|
20
|
+
|
|
21
|
+
**Key Methods:**
|
|
22
|
+
- `connect_to_page()` - Connect and enable CDP domains
|
|
23
|
+
- `disconnect()` - Clean disconnection
|
|
24
|
+
- `get_status()` - Comprehensive status with metrics from all services
|
|
25
|
+
|
|
26
|
+
### FetchService (`fetch.py`)
|
|
27
|
+
Manages HTTP request/response interception.
|
|
28
|
+
|
|
29
|
+
**Key Properties:**
|
|
30
|
+
- `paused_count` - Number of paused requests
|
|
31
|
+
|
|
32
|
+
**Key Methods:**
|
|
33
|
+
- `get_paused_rowids()` - List of paused request rowids
|
|
34
|
+
- `enable()` / `disable()` - Control interception
|
|
35
|
+
- `continue_request()` / `fail_request()` - Process paused requests
|
|
36
|
+
|
|
37
|
+
### NetworkService (`network.py`)
|
|
38
|
+
Queries network events (requests/responses).
|
|
39
|
+
|
|
40
|
+
**Key Properties:**
|
|
41
|
+
- `request_count` - Total network requests
|
|
42
|
+
|
|
43
|
+
**Key Methods:**
|
|
44
|
+
- `get_recent_requests()` - Network events with filter support
|
|
45
|
+
- `get_failed_requests()` - 4xx/5xx errors
|
|
46
|
+
- `get_request_by_id()` - All events for a request
|
|
47
|
+
|
|
48
|
+
### ConsoleService (`console.py`)
|
|
49
|
+
Queries console messages and browser logs.
|
|
50
|
+
|
|
51
|
+
**Key Properties:**
|
|
52
|
+
- `message_count` - Total console messages
|
|
53
|
+
- `error_count` - Console errors only
|
|
54
|
+
|
|
55
|
+
**Key Methods:**
|
|
56
|
+
- `get_recent_messages()` - Console events with level filter
|
|
57
|
+
- `get_errors()` / `get_warnings()` - Filtered queries
|
|
58
|
+
- `clear_browser_console()` - CDP command to clear console
|
|
59
|
+
|
|
60
|
+
## Design Principles
|
|
61
|
+
|
|
62
|
+
1. **Rowid-Native**: All queries return rowid as primary identifier
|
|
63
|
+
2. **Direct Queries**: No caching, query DuckDB on-demand
|
|
64
|
+
3. **Properties for Counts**: Common counts exposed as properties
|
|
65
|
+
4. **Methods for Queries**: Complex queries as methods with parameters
|
|
66
|
+
5. **Service Isolation**: Each service manages its domain independently
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
Services are accessed through the WebTapState:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# In commands (via RPC)
|
|
74
|
+
@app.command()
|
|
75
|
+
def network(state, limit: int = 50):
|
|
76
|
+
result = state.client.call("network", limit=limit)
|
|
77
|
+
return table_response(result["requests"], ...)
|
|
78
|
+
|
|
79
|
+
# In RPC handlers (thin wrappers)
|
|
80
|
+
def network(ctx: RPCContext, limit: int = 50) -> dict:
|
|
81
|
+
requests = ctx.service.network.get_requests(limit=limit)
|
|
82
|
+
return {"requests": requests}
|
|
83
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""WebTap service layer for managing CDP state and operations.
|
|
2
|
+
|
|
3
|
+
The service layer provides a clean interface between REPL commands/API endpoints
|
|
4
|
+
and the underlying CDP session. Services encapsulate domain-specific queries and
|
|
5
|
+
operations, making them reusable across different interfaces.
|
|
6
|
+
|
|
7
|
+
PUBLIC API:
|
|
8
|
+
- WebTapService: Main service orchestrating all domain services
|
|
9
|
+
- SetupService: Service for installing WebTap components
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from webtap.services.main import WebTapService
|
|
13
|
+
from webtap.services.setup import SetupService
|
|
14
|
+
|
|
15
|
+
__all__ = ["WebTapService", "SetupService"]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Console monitoring service for browser messages."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from webtap.cdp import CDPSession
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConsoleService:
|
|
13
|
+
"""Console event queries and monitoring.
|
|
14
|
+
|
|
15
|
+
Provides access to browser console messages captured via CDP.
|
|
16
|
+
Supports filtering by level (error, warning, log, info) and
|
|
17
|
+
querying message counts.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
cdp: CDP session for querying events
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Initialize console service."""
|
|
25
|
+
self.cdp: CDPSession | None = None
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def message_count(self) -> int:
|
|
29
|
+
"""Count of all console messages."""
|
|
30
|
+
if not self.cdp:
|
|
31
|
+
return 0
|
|
32
|
+
result = self.cdp.query(
|
|
33
|
+
"SELECT COUNT(*) FROM events WHERE method IN ('Runtime.consoleAPICalled', 'Log.entryAdded')"
|
|
34
|
+
)
|
|
35
|
+
return result[0][0] if result else 0
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def error_count(self) -> int:
|
|
39
|
+
"""Count of console errors."""
|
|
40
|
+
if not self.cdp:
|
|
41
|
+
return 0
|
|
42
|
+
result = self.cdp.query("""
|
|
43
|
+
SELECT COUNT(*) FROM events
|
|
44
|
+
WHERE method IN ('Runtime.consoleAPICalled', 'Log.entryAdded')
|
|
45
|
+
AND (
|
|
46
|
+
json_extract_string(event, '$.params.type') = 'error'
|
|
47
|
+
OR json_extract_string(event, '$.params.entry.level') = 'error'
|
|
48
|
+
)
|
|
49
|
+
""")
|
|
50
|
+
return result[0][0] if result else 0
|
|
51
|
+
|
|
52
|
+
def get_recent_messages(self, limit: int = 50, level: str | None = None) -> list[tuple]:
|
|
53
|
+
"""Get recent console messages with common fields extracted.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
limit: Maximum results
|
|
57
|
+
level: Optional filter by level (error, warning, log, info)
|
|
58
|
+
"""
|
|
59
|
+
if not self.cdp:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
sql = """
|
|
63
|
+
SELECT
|
|
64
|
+
rowid,
|
|
65
|
+
COALESCE(
|
|
66
|
+
json_extract_string(event, '$.params.type'),
|
|
67
|
+
json_extract_string(event, '$.params.entry.level')
|
|
68
|
+
) as Level,
|
|
69
|
+
COALESCE(
|
|
70
|
+
json_extract_string(event, '$.params.source'),
|
|
71
|
+
json_extract_string(event, '$.params.entry.source'),
|
|
72
|
+
'console'
|
|
73
|
+
) as Source,
|
|
74
|
+
COALESCE(
|
|
75
|
+
json_extract_string(event, '$.params.args[0].value'),
|
|
76
|
+
json_extract_string(event, '$.params.entry.text')
|
|
77
|
+
) as Message,
|
|
78
|
+
COALESCE(
|
|
79
|
+
json_extract_string(event, '$.params.timestamp'),
|
|
80
|
+
json_extract_string(event, '$.params.entry.timestamp')
|
|
81
|
+
) as Time
|
|
82
|
+
FROM events
|
|
83
|
+
WHERE method IN ('Runtime.consoleAPICalled', 'Log.entryAdded')
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
if level:
|
|
87
|
+
sql += f"""
|
|
88
|
+
AND (
|
|
89
|
+
json_extract_string(event, '$.params.type') = '{level.lower()}'
|
|
90
|
+
OR json_extract_string(event, '$.params.entry.level') = '{level.lower()}'
|
|
91
|
+
)
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
sql += f" ORDER BY rowid DESC LIMIT {limit}"
|
|
95
|
+
|
|
96
|
+
return self.cdp.query(sql)
|
|
97
|
+
|
|
98
|
+
def get_errors(self, limit: int = 20) -> list[tuple]:
|
|
99
|
+
"""Get console errors only.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
limit: Maximum results
|
|
103
|
+
"""
|
|
104
|
+
return self.get_recent_messages(limit=limit, level="error")
|
|
105
|
+
|
|
106
|
+
def get_warnings(self, limit: int = 20) -> list[tuple]:
|
|
107
|
+
"""Get console warnings only.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
limit: Maximum results
|
|
111
|
+
"""
|
|
112
|
+
return self.get_recent_messages(limit=limit, level="warning")
|
|
113
|
+
|
|
114
|
+
def clear_browser_console(self) -> bool:
|
|
115
|
+
"""Clear console in the browser (CDP command)."""
|
|
116
|
+
if not self.cdp:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
self.cdp.execute("Runtime.discardConsoleEntries")
|
|
121
|
+
return True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Failed to clear browser console: {e}")
|
|
124
|
+
return False
|