Skip to content

Commit f1293d2

Browse files
committed
feat: add methods to retrieve network response body and logs
1 parent c930289 commit f1293d2

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

pydoll/browser/tab.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232
IFrameNotFound,
3333
InvalidFileExtension,
3434
InvalidIFrame,
35+
NetworkEventsNotEnabled,
3536
NoDialogPresent,
3637
NotAnIFrame,
3738
PageLoadTimeout,
3839
WaitElementTimeout,
3940
)
4041
from pydoll.protocol.base import Response
41-
from pydoll.protocol.network.types import Cookie, CookieParam
42+
from pydoll.protocol.network.responses import GetResponseBodyResponse
43+
from pydoll.protocol.network.types import Cookie, CookieParam, NetworkLog
4244
from pydoll.protocol.page.events import PageEvent
4345
from pydoll.protocol.page.responses import CaptureScreenshotResponse, PrintToPDFResponse
4446
from pydoll.protocol.runtime.responses import EvaluateResponse
@@ -313,6 +315,50 @@ async def get_cookies(self) -> list[Cookie]:
313315
)
314316
return response['result']['cookies']
315317

318+
async def get_network_response_body(self, request_id: str) -> str:
319+
"""
320+
Get the response body for a given request ID.
321+
322+
Args:
323+
request_id: Request ID to get the response body for.
324+
325+
Returns:
326+
The response body for the given request ID.
327+
328+
Raises:
329+
NetworkEventsNotEnabled: If network events are not enabled.
330+
"""
331+
if not self.network_events_enabled:
332+
raise NetworkEventsNotEnabled('Network events must be enabled to get response body')
333+
334+
response: GetResponseBodyResponse = await self._execute_command(
335+
NetworkCommands.get_response_body(request_id)
336+
)
337+
return response['result']['body']
338+
339+
async def get_network_logs(self, filter: Optional[str] = None) -> list[NetworkLog]:
340+
"""
341+
Get network logs.
342+
343+
Args:
344+
filter: Filter to apply to the network logs.
345+
346+
Returns:
347+
The network logs.
348+
349+
Raises:
350+
NetworkEventsNotEnabled: If network events are not enabled.
351+
"""
352+
if not self.network_events_enabled:
353+
raise NetworkEventsNotEnabled('Network events must be enabled to get network logs')
354+
355+
logs = self._connection_handler.network_logs
356+
if filter:
357+
logs = [
358+
log for log in logs if filter in log['params'].get('request', {}).get('url', '')
359+
]
360+
return logs
361+
316362
async def set_cookies(self, cookies: list[CookieParam]):
317363
"""
318364
Set multiple cookies for current page.

pydoll/constants.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,3 +913,63 @@ class DialogType(str, Enum):
913913
CONFIRM = 'confirm'
914914
PROMPT = 'prompt'
915915
BEFORE_UNLOAD = 'beforeunload'
916+
917+
918+
class InitiatorType(str, Enum):
919+
PARSER = 'parser'
920+
SCRIPT = 'script'
921+
PRELOAD = 'preload'
922+
SIGNED_EXCHANGE = 'SignedExchange'
923+
PREFLIGHT = 'preflight'
924+
OTHER = 'other'
925+
926+
927+
class NetworkServiceWorkerRouterSourceType(str, Enum):
928+
"""Network service worker router source types."""
929+
930+
NETWORK = 'network'
931+
CACHE = 'cache'
932+
FETCH_EVENT = 'fetch-event'
933+
RACE_NETWORK = 'race-network'
934+
RACE_NETWORK_AND_FETCH_HANDLER = 'race-network-and-fetch-handler'
935+
RACE_NETWORK_AND_CACHE = 'race-network-and-cache'
936+
937+
938+
class NetworkServiceWorkerResponseSource(str, Enum):
939+
"""Network service worker response source types."""
940+
941+
CACHE_STORAGE = 'cache-storage'
942+
HTTP_CACHE = 'http-cache'
943+
FALLBACK_CODE = 'fallback-code'
944+
NETWORK = 'network'
945+
946+
947+
class AlternateProtocolUsage(str, Enum):
948+
"""Alternate protocol usage types."""
949+
950+
ALTERNATIVE_JOB_WON_WITHOUT_RACE = 'alternativeJobWonWithoutRace'
951+
ALTERNATIVE_JOB_WON_RACE = 'alternativeJobWonRace'
952+
MAIN_JOB_WON_RACE = 'mainJobWonRace'
953+
MAPPING_MISSING = 'mappingMissing'
954+
BROKEN = 'broken'
955+
DNS_ALPN_H3_JOB_WON_WITHOUT_RACE = 'dnsAlpnH3JobWonWithoutRace'
956+
DNS_ALPN_H3_JOB_WON_RACE = 'dnsAlpnH3JobWonRace'
957+
UNSPECIFIED_REASON = 'unspecifiedReason'
958+
959+
960+
class SecurityState(str, Enum):
961+
"""Security state types."""
962+
963+
UNKNOWN = 'unknown'
964+
NEUTRAL = 'neutral'
965+
INSECURE = 'insecure'
966+
INFO = 'info'
967+
INSECURE_BROKEN = 'insecure-broken'
968+
969+
970+
class CertificateTransparencyCompliance(str, Enum):
971+
"""Certificate transparency compliance types."""
972+
973+
UNKNOWN = 'unknown'
974+
NOT_COMPLIANT = 'not-compliant'
975+
COMPLIANT = 'compliant'

pydoll/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,9 @@ class IFrameNotFound(PydollException):
233233
"""Raised when an iframe is not found."""
234234

235235
message = 'The iframe was not found'
236+
237+
238+
class NetworkEventsNotEnabled(PydollException):
239+
"""Raised when network events are not enabled."""
240+
241+
message = 'Network events not enabled'

pydoll/protocol/network/types.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
from typing import NotRequired, TypedDict
22

33
from pydoll.constants import (
4+
AlternateProtocolUsage,
5+
CertificateTransparencyCompliance,
46
ContentSecurityPolicySource,
57
CookiePriority,
68
CookieSameSite,
79
CookieSourceScheme,
10+
InitiatorType,
811
MixedContentType,
12+
NetworkServiceWorkerResponseSource,
13+
NetworkServiceWorkerRouterSourceType,
914
ReferrerPolicy,
1015
RefreshPolicy,
1116
ResourcePriority,
17+
ResourceType,
18+
SecurityState,
1219
TrustTokenOperationType,
1320
)
21+
from pydoll.protocol.runtime.types import StackTrace
1422

1523

1624
class SearchMatch(TypedDict):
@@ -150,3 +158,131 @@ class RequestPausedEventParams(TypedDict):
150158
class RequestPausedEvent(TypedDict):
151159
method: str
152160
params: RequestPausedEventParams
161+
162+
163+
class Initiator(TypedDict):
164+
type: InitiatorType
165+
stack: NotRequired[StackTrace]
166+
url: NotRequired[str]
167+
lineNumber: NotRequired[int]
168+
columnNumber: NotRequired[int]
169+
requestId: NotRequired[str]
170+
171+
172+
class ServiceWorkerRouterInfo(TypedDict):
173+
"""Service worker router info object."""
174+
175+
ruleIdMatched: NotRequired[int]
176+
matchedSourceType: NotRequired[NetworkServiceWorkerRouterSourceType]
177+
actualSourceType: NotRequired[NetworkServiceWorkerRouterSourceType]
178+
179+
180+
class ResourceTiming(TypedDict):
181+
"""Resource timing object."""
182+
183+
requestTime: float
184+
proxyStart: float
185+
proxyEnd: float
186+
dnsStart: float
187+
dnsEnd: float
188+
connectStart: float
189+
connectEnd: float
190+
sslStart: float
191+
sslEnd: float
192+
workerStart: float
193+
workerReady: float
194+
workerFetchStart: float
195+
workerRespondWithSettled: float
196+
workerRouterEvaluationStart: NotRequired[float]
197+
workerCacheLookupStart: NotRequired[float]
198+
sendStart: float
199+
sendEnd: float
200+
pushStart: float
201+
pushEnd: float
202+
receiveHeadersStart: float
203+
receiveHeadersEnd: float
204+
205+
206+
class SignedCertificateTimestamp(TypedDict):
207+
"""Signed certificate timestamp object."""
208+
209+
status: str
210+
origin: str
211+
logDescription: str
212+
logId: str
213+
timestamp: float
214+
hashAlgorithm: str
215+
signatureAlgorithm: str
216+
signatureData: str
217+
218+
219+
class SecurityDetails(TypedDict):
220+
"""Security details object."""
221+
222+
protocol: str
223+
keyExchange: str
224+
keyExchangeGroup: NotRequired[str]
225+
cipher: str
226+
mac: NotRequired[str]
227+
certificateId: int
228+
subjectName: str
229+
sanList: list[str]
230+
issuer: str
231+
validFrom: float
232+
validTo: float
233+
signedCertificateTimestampList: list[SignedCertificateTimestamp]
234+
certificateTransparencyCompliance: CertificateTransparencyCompliance
235+
serverSignatureAlgorithm: NotRequired[int]
236+
encryptedClientHello: bool
237+
238+
239+
class Response(TypedDict):
240+
url: str
241+
status: int
242+
statusText: str
243+
headers: list[dict]
244+
headersText: NotRequired[str]
245+
mimeType: str
246+
charset: str
247+
requestHeaders: NotRequired[list[dict]]
248+
requestHeadersText: NotRequired[str]
249+
connectionReused: bool
250+
connectionId: float
251+
remoteIPAddress: NotRequired[str]
252+
remotePort: NotRequired[int]
253+
fromDiskCache: NotRequired[bool]
254+
fromServiceWorker: NotRequired[bool]
255+
fromPrefetchCache: NotRequired[bool]
256+
fromEarlyHints: NotRequired[bool]
257+
serviceWorkerRouterInfo: NotRequired[ServiceWorkerRouterInfo]
258+
encodedDataLength: float
259+
timing: NotRequired[ResourceTiming]
260+
serviceWorkerResponseSource: NotRequired[NetworkServiceWorkerResponseSource]
261+
responseTime: NotRequired[float]
262+
cacheStorageCacheName: NotRequired[str]
263+
protocol: NotRequired[str]
264+
alternateProtocolUsage: NotRequired[AlternateProtocolUsage]
265+
securityState: SecurityState
266+
securityDetails: NotRequired[SecurityDetails]
267+
268+
269+
class NetworkLogParams(TypedDict):
270+
requestId: str
271+
loaderId: str
272+
documentURL: str
273+
request: Request
274+
timestamp: float
275+
wallTime: float
276+
initiator: Initiator
277+
redirectHasExtraInfo: bool
278+
redirectResponse: NotRequired[Response]
279+
type: NotRequired[ResourceType]
280+
frameId: NotRequired[str]
281+
hasUserGesture: NotRequired[bool]
282+
283+
284+
class NetworkLog(TypedDict):
285+
"""Network log object."""
286+
287+
method: str
288+
params: RequestPausedEventParams

0 commit comments

Comments
 (0)