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.
Files changed (64) hide show
  1. webtap/VISION.md +246 -0
  2. webtap/__init__.py +84 -0
  3. webtap/__main__.py +6 -0
  4. webtap/api/__init__.py +9 -0
  5. webtap/api/app.py +26 -0
  6. webtap/api/models.py +69 -0
  7. webtap/api/server.py +111 -0
  8. webtap/api/sse.py +182 -0
  9. webtap/api/state.py +89 -0
  10. webtap/app.py +79 -0
  11. webtap/cdp/README.md +275 -0
  12. webtap/cdp/__init__.py +12 -0
  13. webtap/cdp/har.py +302 -0
  14. webtap/cdp/schema/README.md +41 -0
  15. webtap/cdp/schema/cdp_protocol.json +32785 -0
  16. webtap/cdp/schema/cdp_version.json +8 -0
  17. webtap/cdp/session.py +667 -0
  18. webtap/client.py +81 -0
  19. webtap/commands/DEVELOPER_GUIDE.md +401 -0
  20. webtap/commands/TIPS.md +269 -0
  21. webtap/commands/__init__.py +29 -0
  22. webtap/commands/_builders.py +331 -0
  23. webtap/commands/_code_generation.py +110 -0
  24. webtap/commands/_tips.py +147 -0
  25. webtap/commands/_utils.py +273 -0
  26. webtap/commands/connection.py +220 -0
  27. webtap/commands/console.py +87 -0
  28. webtap/commands/fetch.py +310 -0
  29. webtap/commands/filters.py +116 -0
  30. webtap/commands/javascript.py +73 -0
  31. webtap/commands/js_export.py +73 -0
  32. webtap/commands/launch.py +72 -0
  33. webtap/commands/navigation.py +197 -0
  34. webtap/commands/network.py +136 -0
  35. webtap/commands/quicktype.py +306 -0
  36. webtap/commands/request.py +93 -0
  37. webtap/commands/selections.py +138 -0
  38. webtap/commands/setup.py +219 -0
  39. webtap/commands/to_model.py +163 -0
  40. webtap/daemon.py +185 -0
  41. webtap/daemon_state.py +53 -0
  42. webtap/filters.py +219 -0
  43. webtap/rpc/__init__.py +14 -0
  44. webtap/rpc/errors.py +49 -0
  45. webtap/rpc/framework.py +223 -0
  46. webtap/rpc/handlers.py +625 -0
  47. webtap/rpc/machine.py +84 -0
  48. webtap/services/README.md +83 -0
  49. webtap/services/__init__.py +15 -0
  50. webtap/services/console.py +124 -0
  51. webtap/services/dom.py +547 -0
  52. webtap/services/fetch.py +415 -0
  53. webtap/services/main.py +392 -0
  54. webtap/services/network.py +401 -0
  55. webtap/services/setup/__init__.py +185 -0
  56. webtap/services/setup/chrome.py +233 -0
  57. webtap/services/setup/desktop.py +255 -0
  58. webtap/services/setup/extension.py +147 -0
  59. webtap/services/setup/platform.py +162 -0
  60. webtap/services/state_snapshot.py +86 -0
  61. webtap_tool-0.11.0.dist-info/METADATA +535 -0
  62. webtap_tool-0.11.0.dist-info/RECORD +64 -0
  63. webtap_tool-0.11.0.dist-info/WHEEL +4 -0
  64. 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