webtap-tool 0.7.0__py3-none-any.whl → 0.8.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.

Potentially problematic release.


This version of webtap-tool might be problematic. Click here for more details.

webtap/services/dom.py CHANGED
@@ -46,7 +46,7 @@ class DOMService:
46
46
  self.state = state
47
47
  self._inspection_active = False
48
48
  self._next_id = 1
49
- self._broadcast_queue: "Any | None" = None # asyncio.Queue for thread-safe broadcasts
49
+ self._broadcast_callback: "Any | None" = None # Callback to service._trigger_broadcast()
50
50
  self._state_lock = threading.Lock() # Protect state mutations
51
51
  self._pending_selections = 0 # Track in-flight selection processing
52
52
  self._executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="dom-worker")
@@ -59,13 +59,13 @@ class DOMService:
59
59
  """Set state after initialization."""
60
60
  self.state = state
61
61
 
62
- def set_broadcast_queue(self, queue: "Any") -> None:
63
- """Set queue for broadcasting state changes.
62
+ def set_broadcast_callback(self, callback: "Any") -> None:
63
+ """Set callback for broadcasting state changes.
64
64
 
65
65
  Args:
66
- queue: asyncio.Queue for thread-safe signaling
66
+ callback: Function to call when state changes (service._trigger_broadcast)
67
67
  """
68
- self._broadcast_queue = queue
68
+ self._broadcast_callback = callback
69
69
 
70
70
  def start_inspect(self) -> dict[str, Any]:
71
71
  """Enable CDP element inspection mode.
@@ -116,6 +116,7 @@ class DOMService:
116
116
  self._inspection_active = True
117
117
  logger.info("Element inspection mode enabled")
118
118
 
119
+ self._trigger_broadcast()
119
120
  return {"success": True, "inspect_active": True}
120
121
 
121
122
  except Exception as e:
@@ -143,6 +144,7 @@ class DOMService:
143
144
  self._inspection_active = False
144
145
  logger.info("Element inspection mode disabled")
145
146
 
147
+ self._trigger_broadcast()
146
148
  return {"success": True, "inspect_active": False}
147
149
 
148
150
  except Exception as e:
@@ -244,12 +246,12 @@ class DOMService:
244
246
  self._trigger_broadcast()
245
247
 
246
248
  def _trigger_broadcast(self) -> None:
247
- """Trigger SSE broadcast via queue (thread-safe helper)."""
248
- if self._broadcast_queue:
249
+ """Trigger SSE broadcast via service callback (ensures snapshot update)."""
250
+ if self._broadcast_callback:
249
251
  try:
250
- self._broadcast_queue.put_nowait({"type": "dom_update"})
252
+ self._broadcast_callback()
251
253
  except Exception as e:
252
- logger.debug(f"Failed to queue broadcast: {e}")
254
+ logger.debug(f"Failed to trigger broadcast: {e}")
253
255
 
254
256
  def _extract_node_data(self, backend_node_id: int) -> dict[str, Any]:
255
257
  """Extract complete element data via CDP.
@@ -486,14 +488,16 @@ class DOMService:
486
488
  self.state.browser_data["selections"] = {}
487
489
  self._next_id = 1
488
490
  logger.info("Selections cleared")
491
+ self._trigger_broadcast()
489
492
 
490
493
  def cleanup(self) -> None:
491
494
  """Cleanup resources (executor, callbacks).
492
495
 
493
496
  Call this before disconnect or app exit.
497
+ Safe to call multiple times.
494
498
  """
495
499
  # Shutdown executor - wait=False to avoid blocking on stuck tasks
496
- # cancel_futures=True prevents hanging on incomplete selections (Python 3.9+)
500
+ # cancel_futures=True prevents hanging on incomplete selections
497
501
  if hasattr(self, "_executor"):
498
502
  try:
499
503
  self._executor.shutdown(wait=False, cancel_futures=True)
@@ -501,12 +505,15 @@ class DOMService:
501
505
  except Exception as e:
502
506
  logger.debug(f"Executor shutdown error (non-fatal): {e}")
503
507
 
504
- # Clear inspection state
505
- if self._inspection_active:
508
+ # Clear inspection state (only if connected)
509
+ if self._inspection_active and self.cdp and self.cdp.is_connected:
506
510
  try:
507
511
  self.stop_inspect()
508
512
  except Exception as e:
509
513
  logger.debug(f"Failed to stop inspect on cleanup: {e}")
510
514
 
515
+ # Force clear inspection flag even if CDP call failed
516
+ self._inspection_active = False
517
+
511
518
 
512
519
  __all__ = ["DOMService"]
webtap/services/fetch.py CHANGED
@@ -21,6 +21,23 @@ class FetchService:
21
21
  self.enable_response_stage = False # Config option for future
22
22
  self.cdp: CDPSession | None = None
23
23
  self.body_service: BodyService | None = None
24
+ self._broadcast_callback: "Any | None" = None # Callback to service._trigger_broadcast()
25
+
26
+ def set_broadcast_callback(self, callback: "Any") -> None:
27
+ """Set callback for broadcasting state changes.
28
+
29
+ Args:
30
+ callback: Function to call when state changes (service._trigger_broadcast)
31
+ """
32
+ self._broadcast_callback = callback
33
+
34
+ def _trigger_broadcast(self) -> None:
35
+ """Trigger SSE broadcast via service callback (ensures snapshot update)."""
36
+ if self._broadcast_callback:
37
+ try:
38
+ self._broadcast_callback()
39
+ except Exception as e:
40
+ logger.debug(f"Failed to trigger broadcast: {e}")
24
41
 
25
42
  # ============= Core State Queries =============
26
43
 
@@ -147,6 +164,7 @@ class FetchService:
147
164
  stage_msg = "Request and Response stages" if response_stage else "Request stage only"
148
165
  logger.info(f"Fetch interception enabled ({stage_msg})")
149
166
 
167
+ self._trigger_broadcast() # Update snapshot
150
168
  return {"enabled": True, "stages": stage_msg, "paused": self.paused_count}
151
169
 
152
170
  except Exception as e:
@@ -174,6 +192,7 @@ class FetchService:
174
192
  self.body_service.clear_cache()
175
193
 
176
194
  logger.info("Fetch interception disabled")
195
+ self._trigger_broadcast() # Update snapshot
177
196
  return {"enabled": False}
178
197
 
179
198
  except Exception as e:
webtap/services/main.py CHANGED
@@ -12,6 +12,7 @@ from webtap.services.network import NetworkService
12
12
  from webtap.services.console import ConsoleService
13
13
  from webtap.services.body import BodyService
14
14
  from webtap.services.dom import DOMService
15
+ from webtap.services.state_snapshot import StateSnapshot
15
16
 
16
17
 
17
18
  REQUIRED_DOMAINS = [
@@ -47,8 +48,11 @@ class WebTapService:
47
48
  Args:
48
49
  state: WebTapState instance from app.py
49
50
  """
51
+ import threading
52
+
50
53
  self.state = state
51
54
  self.cdp = state.cdp
55
+ self._state_lock = threading.RLock() # Reentrant lock - safe to acquire multiple times by same thread
52
56
 
53
57
  self.enabled_domains: set[str] = set()
54
58
  self.filters = FilterManager()
@@ -65,8 +69,10 @@ class WebTapService:
65
69
  self.body.cdp = self.cdp
66
70
  self.dom.set_cdp(self.cdp)
67
71
  self.dom.set_state(self.state)
72
+ self.dom.set_broadcast_callback(self._trigger_broadcast) # DOM calls back for snapshot updates
68
73
 
69
74
  self.fetch.body_service = self.body
75
+ self.fetch.set_broadcast_callback(self._trigger_broadcast) # Fetch calls back for snapshot updates
70
76
 
71
77
  # Legacy wiring for CDP event handler
72
78
  self.cdp.fetch_service = self.fetch
@@ -75,14 +81,135 @@ class WebTapService:
75
81
  self.cdp.register_event_callback("Overlay.inspectNodeRequested", self.dom.handle_inspect_node_requested)
76
82
  self.cdp.register_event_callback("Page.frameNavigated", self.dom.handle_frame_navigated)
77
83
 
84
+ # Register disconnect callback for unexpected disconnects
85
+ self.cdp.set_disconnect_callback(self._handle_unexpected_disconnect)
86
+
87
+ # Broadcast queue for SSE state updates (set by API server)
88
+ self._broadcast_queue: "Any | None" = None
89
+
90
+ # Immutable state snapshot for thread-safe SSE reads
91
+ # Updated atomically on every state change, read without locks
92
+ self._state_snapshot: StateSnapshot = StateSnapshot.create_empty()
93
+
94
+ def set_broadcast_queue(self, queue: "Any") -> None:
95
+ """Set queue for broadcasting state changes.
96
+
97
+ Args:
98
+ queue: asyncio.Queue for thread-safe signaling
99
+ """
100
+ self._broadcast_queue = queue
101
+
102
+ def _create_snapshot(self) -> StateSnapshot:
103
+ """Create immutable state snapshot from current state.
104
+
105
+ MUST be called with self._state_lock held to ensure atomic read.
106
+
107
+ Returns:
108
+ Frozen StateSnapshot with current state
109
+ """
110
+ # Connection state (read page_info first to avoid race with disconnect)
111
+ page_info = self.cdp.page_info
112
+ connected = self.cdp.is_connected and page_info is not None
113
+ page_id = page_info.get("id", "") if page_info else ""
114
+ page_title = page_info.get("title", "") if page_info else ""
115
+ page_url = page_info.get("url", "") if page_info else ""
116
+
117
+ # Event count
118
+ event_count = self.event_count
119
+
120
+ # Fetch state
121
+ fetch_enabled = self.fetch.enabled
122
+ paused_count = self.fetch.paused_count if fetch_enabled else 0
123
+
124
+ # Filter state (convert to immutable tuples)
125
+ fm = self.filters
126
+ filter_categories = list(fm.filters.keys())
127
+ enabled_filters = tuple(fm.enabled_categories)
128
+ disabled_filters = tuple(cat for cat in filter_categories if cat not in enabled_filters)
129
+
130
+ # Browser/DOM state (get_state() is already thread-safe internally)
131
+ browser_state = self.dom.get_state()
132
+
133
+ # Error state
134
+ error = self.state.error_state
135
+ error_message = error.get("message") if error else None
136
+ error_timestamp = error.get("timestamp") if error else None
137
+
138
+ # Deep copy selections to ensure true immutability
139
+ import copy
140
+
141
+ selections = copy.deepcopy(browser_state["selections"])
142
+
143
+ return StateSnapshot(
144
+ connected=connected,
145
+ page_id=page_id,
146
+ page_title=page_title,
147
+ page_url=page_url,
148
+ event_count=event_count,
149
+ fetch_enabled=fetch_enabled,
150
+ paused_count=paused_count,
151
+ enabled_filters=enabled_filters,
152
+ disabled_filters=disabled_filters,
153
+ inspect_active=browser_state["inspect_active"],
154
+ selections=selections, # Deep copy ensures nested dicts are immutable
155
+ prompt=browser_state["prompt"],
156
+ pending_count=browser_state["pending_count"],
157
+ error_message=error_message,
158
+ error_timestamp=error_timestamp,
159
+ )
160
+
161
+ def _trigger_broadcast(self) -> None:
162
+ """Trigger SSE broadcast with updated state snapshot (thread-safe).
163
+
164
+ Called after service mutations to:
165
+ 1. Create fresh immutable snapshot (atomic replacement)
166
+ 2. Signal SSE clients to broadcast
167
+
168
+ Uses RLock so same thread can call multiple times safely.
169
+ asyncio.Queue.put_nowait() is thread-safe for cross-thread communication.
170
+ """
171
+ import logging
172
+
173
+ logger = logging.getLogger(__name__)
174
+
175
+ # Update snapshot atomically
176
+ # RLock allows same thread to acquire multiple times, blocks other threads
177
+ try:
178
+ with self._state_lock:
179
+ self._state_snapshot = self._create_snapshot()
180
+ except (TypeError, AttributeError) as e:
181
+ # Programming errors should propagate for debugging
182
+ logger.error(f"Programming error in snapshot creation: {e}")
183
+ raise
184
+ except Exception as e:
185
+ # Unexpected errors logged but don't crash the app
186
+ logger.error(f"Failed to create state snapshot: {e}", exc_info=True)
187
+ return # Don't signal broadcast if snapshot creation failed
188
+
189
+ # Signal broadcast (store reference to avoid TOCTOU race)
190
+ queue = self._broadcast_queue
191
+ if queue:
192
+ try:
193
+ queue.put_nowait({"type": "state_change"})
194
+ except Exception as e:
195
+ logger.warning(f"Failed to queue broadcast: {e}")
196
+
197
+ def get_state_snapshot(self) -> StateSnapshot:
198
+ """Get current immutable state snapshot (thread-safe, no locks).
199
+
200
+ Returns:
201
+ Current StateSnapshot - immutable, safe to read from any thread
202
+ """
203
+ return self._state_snapshot
204
+
78
205
  @property
79
206
  def event_count(self) -> int:
80
207
  """Total count of all CDP events stored."""
81
208
  if not self.cdp or not self.cdp.is_connected:
82
209
  return 0
83
210
  try:
84
- result = self.cdp.db.execute("SELECT COUNT(*) FROM events").fetchone()
85
- return result[0] if result else 0
211
+ result = self.cdp.query("SELECT COUNT(*) FROM events")
212
+ return result[0][0] if result else 0
86
213
  except Exception:
87
214
  return 0
88
215
 
@@ -105,6 +232,7 @@ class WebTapService:
105
232
  self.filters.load()
106
233
 
107
234
  page_info = self.cdp.page_info or {}
235
+ self._trigger_broadcast()
108
236
  return {"connected": True, "title": page_info.get("title", "Untitled"), "url": page_info.get("url", "")}
109
237
  except Exception as e:
110
238
  return {"error": str(e)}
@@ -118,6 +246,7 @@ class WebTapService:
118
246
 
119
247
  self.body.clear_cache()
120
248
  self.dom.clear_selections()
249
+ self.dom.cleanup() # Shutdown executor properly
121
250
 
122
251
  # Clear error state on disconnect
123
252
  if self.state.error_state:
@@ -126,6 +255,7 @@ class WebTapService:
126
255
  self.cdp.disconnect()
127
256
  self.enabled_domains.clear()
128
257
 
258
+ self._trigger_broadcast()
129
259
  return {"disconnected": True, "was_connected": was_connected}
130
260
 
131
261
  def enable_domains(self, domains: list[str]) -> dict[str, str]:
@@ -175,6 +305,7 @@ class WebTapService:
175
305
  def clear_events(self) -> dict[str, Any]:
176
306
  """Clear all stored CDP events."""
177
307
  self.cdp.clear_events()
308
+ self._trigger_broadcast()
178
309
  return {"cleared": True, "events": 0}
179
310
 
180
311
  def list_pages(self) -> dict[str, Any]:
@@ -187,3 +318,64 @@ class WebTapService:
187
318
  return {"pages": pages}
188
319
  except Exception as e:
189
320
  return {"error": str(e), "pages": []}
321
+
322
+ def _handle_unexpected_disconnect(self, code: int, reason: str) -> None:
323
+ """Handle unexpected WebSocket disconnect (tab closed, crashed, etc).
324
+
325
+ Called from background thread by CDPSession._on_close.
326
+ Performs service-level cleanup and notifies SSE clients.
327
+ Events are preserved for debugging.
328
+
329
+ Args:
330
+ code: WebSocket close code (e.g., 1006 = abnormal closure)
331
+ reason: Human-readable close reason
332
+ """
333
+ import logging
334
+ import time
335
+
336
+ logger = logging.getLogger(__name__)
337
+
338
+ # Map WebSocket close codes to user-friendly messages
339
+ reason_map = {
340
+ 1000: "Page closed normally",
341
+ 1001: "Browser tab closed",
342
+ 1006: "Connection lost (tab crashed or browser closed)",
343
+ 1011: "Chrome internal error",
344
+ }
345
+
346
+ # Handle None code (abnormal closure with no code)
347
+ if code is None:
348
+ user_reason = "Connection lost (page closed or crashed)"
349
+ else:
350
+ user_reason = reason_map.get(code, f"Connection closed unexpectedly (code {code})")
351
+
352
+ logger.warning(f"Unexpected disconnect: {user_reason}")
353
+
354
+ try:
355
+ # Thread-safe state cleanup (called from background thread)
356
+ with self._state_lock:
357
+ # Clean up service state (no CDP calls - connection already gone)
358
+ if self.fetch.enabled:
359
+ self.fetch.enabled = False # Direct state update, no CDP disable
360
+
361
+ self.body.clear_cache()
362
+ self.dom.clear_selections()
363
+
364
+ # Events preserved for debugging - use Clear button to remove explicitly
365
+ # DB thread and field_paths persist for reconnection
366
+
367
+ # Set error state with disconnect info
368
+ self.state.error_state = {"message": user_reason, "timestamp": time.time()}
369
+
370
+ self.enabled_domains.clear()
371
+
372
+ # Cleanup outside lock (safe to call multiple times, has internal protection)
373
+ self.dom.cleanup() # Shutdown executor
374
+
375
+ # Notify SSE clients
376
+ self._trigger_broadcast()
377
+
378
+ logger.info("Unexpected disconnect cleanup completed")
379
+
380
+ except Exception as e:
381
+ logger.error(f"Error during unexpected disconnect cleanup: {e}")
@@ -0,0 +1,88 @@
1
+ """Immutable state snapshots for thread-safe SSE broadcasting.
2
+
3
+ PUBLIC API:
4
+ - StateSnapshot: Frozen dataclass for zero-lock state reads
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class StateSnapshot:
13
+ """Immutable snapshot of WebTap state.
14
+
15
+ Frozen dataclass provides inherent thread safety - multiple threads can
16
+ read simultaneously without locks. Updated atomically when state changes.
17
+
18
+ Used by SSE broadcast to avoid lock contention between asyncio event loop
19
+ and background threads (WebSocket, disconnect handlers).
20
+
21
+ Attributes:
22
+ connected: Whether connected to Chrome page
23
+ page_id: Stable page identifier (empty if not connected)
24
+ page_title: Page title (empty if not connected)
25
+ page_url: Page URL (empty if not connected)
26
+ event_count: Total CDP events stored
27
+ fetch_enabled: Whether fetch interception is active
28
+ paused_count: Number of paused requests (if fetch enabled)
29
+ enabled_filters: Tuple of enabled filter category names
30
+ disabled_filters: Tuple of disabled filter category names
31
+ inspect_active: Whether element inspection mode is active
32
+ selections: Dict of selected elements (id -> element data)
33
+ prompt: Browser prompt text (unused, reserved)
34
+ pending_count: Number of pending element selections being processed
35
+ error_message: Current error message or None
36
+ error_timestamp: Error timestamp or None
37
+ """
38
+
39
+ # Connection state
40
+ connected: bool
41
+ page_id: str
42
+ page_title: str
43
+ page_url: str
44
+
45
+ # Event state
46
+ event_count: int
47
+
48
+ # Fetch interception state
49
+ fetch_enabled: bool
50
+ paused_count: int
51
+
52
+ # Filter state (immutable tuples)
53
+ enabled_filters: tuple[str, ...]
54
+ disabled_filters: tuple[str, ...]
55
+
56
+ # Browser/DOM state
57
+ inspect_active: bool
58
+ selections: dict[str, Any] # Dict is mutable but replaced atomically
59
+ prompt: str
60
+ pending_count: int
61
+
62
+ # Error state
63
+ error_message: str | None
64
+ error_timestamp: float | None
65
+
66
+ @classmethod
67
+ def create_empty(cls) -> "StateSnapshot":
68
+ """Create empty snapshot for disconnected state."""
69
+ return cls(
70
+ connected=False,
71
+ page_id="",
72
+ page_title="",
73
+ page_url="",
74
+ event_count=0,
75
+ fetch_enabled=False,
76
+ paused_count=0,
77
+ enabled_filters=(),
78
+ disabled_filters=(),
79
+ inspect_active=False,
80
+ selections={},
81
+ prompt="",
82
+ pending_count=0,
83
+ error_message=None,
84
+ error_timestamp=None,
85
+ )
86
+
87
+
88
+ __all__ = ["StateSnapshot"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webtap-tool
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Terminal-based web page inspector for AI debugging sessions
5
5
  Author-email: Fredrik Angelsen <fredrikangelsen@gmail.com>
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -1,51 +1,54 @@
1
1
  webtap/VISION.md,sha256=kfoJfEPVc4chOrD9tNMDmYBY9rX9KB-286oZj70ALCE,7681
2
- webtap/__init__.py,sha256=DFWJGmqZfX8_h4csLA5pKPR4SkaHBMlUgU-WQIE96Gw,1092
3
- webtap/api.py,sha256=twDO_aA861yjjvrKquZ0phm2SIL07Wq37DmFjtVbTw4,17990
4
- webtap/app.py,sha256=75x_MQ2sjt6-HJi-YYRdFoKc3ss6SQfgFVArE1QKs3M,3851
2
+ webtap/__init__.py,sha256=gQPBOt8Q9Y9znQidBvIM5DbecNBTQe4XuoLK5WjFIfU,1250
3
+ webtap/api.py,sha256=w-7WGyR54fv980GF873w4T5TPErYcgx9PfRBNiddk64,18251
4
+ webtap/app.py,sha256=DG40lLVod4iSJP1TnrzxrEkkP4_sQ6G5euGubQqfEGQ,4073
5
5
  webtap/filters.py,sha256=kRCicGMSV3R_zSvwzqZqksnry6jxJNdXRcgWvpoBLfc,13323
6
6
  webtap/cdp/README.md,sha256=0TS0V_dRgRAzBqhddpXWD4S0YVi5wI4JgFJSll_KUBE,5660
7
7
  webtap/cdp/__init__.py,sha256=c6NFG0XJnAa5GTe9MLr9mDZcLZqoTQN7A1cvvOfLcgY,453
8
8
  webtap/cdp/query.py,sha256=x2Cy7KMolYkTelpROGezOfMFgYnbSlCvHkvvW1v_gLI,4229
9
- webtap/cdp/session.py,sha256=LkN676_gig06w9kcXhJXZQF0z0kDZG1TTvispiYw8fQ,16212
9
+ webtap/cdp/session.py,sha256=nm4fpHKiGskvHkhxvlmZULEVaY4PpLb7g7lmuwdEEG4,21573
10
10
  webtap/cdp/schema/README.md,sha256=hnWCzbXYcYtWaZb_SgjVaFBiG81S9b9Y3x-euQFwQDo,1222
11
11
  webtap/cdp/schema/cdp_protocol.json,sha256=dp9_OLYLuVsQb1oV5r6MZfMzURscBLyAXUckdaPWyv4,1488452
12
12
  webtap/cdp/schema/cdp_version.json,sha256=OhGy1qpfQjSe3Z7OqL6KynBFlDFBXxKGPZCY-ZN_lVU,399
13
13
  webtap/commands/DEVELOPER_GUIDE.md,sha256=LYOhycZ3k5EHx5nREfkjvLz7vOs8pXCRLlcDm-keWao,11973
14
- webtap/commands/TIPS.md,sha256=XwnPKY5AgLsxNw3q0aF4Amr0P891Pt4QIkw0_3AF58g,9293
14
+ webtap/commands/TIPS.md,sha256=ssTcJr-nzoAOtTQOvuziDVoWGFk7pLa7RomwX79_kaA,14228
15
15
  webtap/commands/__init__.py,sha256=rr3xM_bY0BgxkDOjsnsI8UBhjlz7nqiYlgJ8fjiJ1jQ,270
16
- webtap/commands/_builders.py,sha256=SYacZmZTdkolQ7OOf3rFtFPCjkukY8z020WFA-i_O_A,7902
16
+ webtap/commands/_builders.py,sha256=VUHiObHfOveduILYEX711wnA-tfx6GL_8t_zuQ-CPQw,8146
17
+ webtap/commands/_code_generation.py,sha256=uBpusbIGCWqm6HwaWSuhFnRu_J5jm4rJ7qmx-esogf4,3288
17
18
  webtap/commands/_tips.py,sha256=SleMpwdghrHNqdzR60Cu8T0NZqJfWfcfrgIcyWI6GIQ,4793
18
19
  webtap/commands/_utils.py,sha256=VLXDhhhJITrQjwEyeLRTU2ey0QcLzY-_OxTjtPJlhYM,6816
19
- webtap/commands/body.py,sha256=hJAoVU3iWBYCctPwBxVJM_xVe02TQnePB_OMTpsh1q4,7076
20
- webtap/commands/connection.py,sha256=nvJ5k1KlyphTeVW9yJaATHo4N-hX7w62pf1-fAypSRk,5430
20
+ webtap/commands/body.py,sha256=wEBD1hCEopmPSnq7M_f5mU0Rb4EPGak9OVED4jo0ZW4,7278
21
+ webtap/commands/connection.py,sha256=o_Ny6dbQewNsPZ8QfdGZKY4mgqeAhqbRIqq56ksniFU,6264
21
22
  webtap/commands/console.py,sha256=BBaxSiLsVBChBY3Xi_nXwWjFlfc5KW9FQTPp5PzMUoE,2145
22
23
  webtap/commands/events.py,sha256=dsS6xd8GfkZ4VOnAQSCMxyEvwdha9J0Kh9oeK0CaU5Y,4058
23
24
  webtap/commands/fetch.py,sha256=8J6TPBWhStbkN5c5Q4KmK6nB5WiIgnAk0BkPFbh9ggg,7737
24
25
  webtap/commands/filters.py,sha256=jDZ8JcYIZv_K6aupwuAo9uqAi85e3EIKbf38BXz5nnI,10316
25
26
  webtap/commands/inspect.py,sha256=QonZigFYnfEVWYQY__r0n1aVvTqFBukFV-AWzc5KmfA,5711
26
- webtap/commands/javascript.py,sha256=j0gKl6Y6-TWBYuN8SjoRHFPOhns9C1h8NkpIQkhRXG0,4879
27
+ webtap/commands/javascript.py,sha256=d1aCs6VthNUkxGXRIWHxCzFTwrr0j81NVtGknsz75GU,4274
27
28
  webtap/commands/launch.py,sha256=iZDLundKlxKRLKf3Vz5at42-tp2f-Uj5wZf7fbhBfA0,2202
28
- webtap/commands/navigation.py,sha256=aRVW-t2YJ0Haf6Wkg7uq8GstiRftklop9W1TicUk3oo,6036
29
+ webtap/commands/navigation.py,sha256=OBhCUnRvzF4aZ7c3YZUznr-yU4uuLBGTndDtkb1EJHg,6167
29
30
  webtap/commands/network.py,sha256=gEOg_u7VF9A5aKv5myzLCuvfAUkF1OPxsuj4UAgbS44,3111
31
+ webtap/commands/quicktype.py,sha256=t4hAVxkAbwKcf_gZlsxWIUPIABoDrboeZMS937usXHg,9002
30
32
  webtap/commands/selections.py,sha256=M001d_Gc51aSTuVeXGa19LDh2ZGR_qBJEjVGKpcGGFM,4895
31
33
  webtap/commands/server.py,sha256=DOcIgYuKp0ydwrK9EA3hGwqOwfwM9DABhdPu3hk_jjo,6948
32
34
  webtap/commands/setup.py,sha256=dov1LaN50nAEMNIuBLSK7mcnwhfn9rtqdTopBm1-PhA,9648
33
- webtap/commands/to_model.py,sha256=jOb93t616m5weT75VyF506J6nydDXUWENF7cpscbe9Q,4962
35
+ webtap/commands/to_model.py,sha256=M_X_Y6Eb6tU-Ar4UP43q5AHBfBlCXV0chEuwRJ-poOE,3214
34
36
  webtap/services/README.md,sha256=rala_jtnNgSiQ1lFLM7x_UQ4SJZDceAm7dpkQMRTYaI,2346
35
37
  webtap/services/__init__.py,sha256=IjFqu0Ak6D-r18aokcQMtenDV3fbelvfjTCejGv6CZ0,570
36
- webtap/services/body.py,sha256=XQPa19y5eUc3XJ2TuwVK6kffO1VQoKqNs33MBBz7hzU,3913
38
+ webtap/services/body.py,sha256=5c1YO3xKHU4qHFLeczag0m91PoSzp9-tBE30SENJEzg,11586
37
39
  webtap/services/console.py,sha256=XVfSKTvEHyyOdujsg85S3wtj1CdZhzKtWwlx25MvSv8,3768
38
- webtap/services/dom.py,sha256=PC-mV56NMLvw37JNzI_jOnZrM7BiDPPn3kOQI9U81vI,19067
39
- webtap/services/fetch.py,sha256=nl6bpU2Vnf40kau4-mqAnIkhC-7Lx2vbTJKUglz9KnE,13602
40
- webtap/services/main.py,sha256=KUJFXzXL0ORlYfDV6DNULv_nzNfmlBFz_mfOvscp2wY,6287
40
+ webtap/services/dom.py,sha256=JNYHFrgPpJnMZJ3s267R5m6xFWJwKA8Fwn3XGln6wxs,19399
41
+ webtap/services/fetch.py,sha256=-ZsfEfF3iFge3fiyLniflU0v0In-SxJMb7VtIe7_sjU,14417
42
+ webtap/services/main.py,sha256=kHKHtLnwR3rnpbtJXZZtAdNmD_dg0ejvd-TvrkXTqLg,13948
41
43
  webtap/services/network.py,sha256=0o_--F6YvmXqqFqrcjL1gc6Vr9V1Ytb_U7r_DSUWupA,3444
44
+ webtap/services/state_snapshot.py,sha256=xy7q-QaDhrHrEKVctJNH0t14zzI4DNmlt_-4C5ipr2M,2701
42
45
  webtap/services/setup/__init__.py,sha256=lfoKCAroc-JoE_r7L-KZkF85ZWiB41MBIgrR7ZISSoE,7157
43
46
  webtap/services/setup/chrome.py,sha256=zfPWeb6zm_xjIfiS2S_O9lR2BjGKaPXXo06pN_B9lAU,7187
44
47
  webtap/services/setup/desktop.py,sha256=fXwQa201W-s2mengm_dJZ9BigJopVrO9YFUQcW_TSFQ,8022
45
48
  webtap/services/setup/extension.py,sha256=iJY43JlQO6Vicgd9Mz6Mw0LQfbBNUGhnwI8n-LnvHBY,3602
46
49
  webtap/services/setup/filters.py,sha256=lAPSLMH_KZQO-7bRkmURwzforx7C3SDrKEw2ZogN-Lo,3220
47
50
  webtap/services/setup/platform.py,sha256=7yn-7LQFffgerWzWRtOG-yNEsR36ICThYUAu_N2FAso,4532
48
- webtap_tool-0.7.0.dist-info/METADATA,sha256=JAfV9YTu10f5ss15yatHtYFx8W8HJewrPthMBS0TwN4,17636
49
- webtap_tool-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- webtap_tool-0.7.0.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
51
- webtap_tool-0.7.0.dist-info/RECORD,,
51
+ webtap_tool-0.8.0.dist-info/METADATA,sha256=6EJihElxEAWvWgBttK2EXsdNPJc95JKY96FzZ50Z4Hs,17636
52
+ webtap_tool-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ webtap_tool-0.8.0.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
54
+ webtap_tool-0.8.0.dist-info/RECORD,,