2020MAX_PAYLOAD_BYTES = 10 * 1024 * 1024
2121
2222
23+ class SnapshotGatewayError (RuntimeError ):
24+ """
25+ Structured error for server-side (gateway) snapshot failures.
26+
27+ Keeps HTTP status/URL/response details available to callers for better logging/debugging.
28+ Subclasses RuntimeError for backward compatibility.
29+ """
30+
31+ def __init__ (
32+ self ,
33+ message : str ,
34+ * ,
35+ status_code : int | None = None ,
36+ url : str | None = None ,
37+ request_id : str | None = None ,
38+ response_text : str | None = None ,
39+ cause : Exception | None = None ,
40+ ) -> None :
41+ super ().__init__ (message )
42+ self .status_code = status_code
43+ self .url = url
44+ self .request_id = request_id
45+ self .response_text = response_text
46+ # Note: callers should use `raise ... from cause` to preserve chaining.
47+ _ = cause
48+
49+ @staticmethod
50+ def _snip (s : str | None , n : int = 400 ) -> str | None :
51+ if not s :
52+ return None
53+ t = str (s ).replace ("\n " , " " ).replace ("\r " , " " ).strip ()
54+ return t [:n ]
55+
56+ @classmethod
57+ def from_httpx (cls , e : Exception ) -> "SnapshotGatewayError" :
58+ status_code = None
59+ url = None
60+ request_id = None
61+ body = None
62+ try :
63+ resp = getattr (e , "response" , None )
64+ if resp is not None :
65+ status_code = getattr (resp , "status_code" , None )
66+ try :
67+ url = str (getattr (resp , "url" , None ) or "" )
68+ except Exception :
69+ url = None
70+ try :
71+ headers = getattr (resp , "headers" , None ) or {}
72+ request_id = headers .get ("x-request-id" ) or headers .get ("x-trace-id" )
73+ except Exception :
74+ request_id = None
75+ try :
76+ body = getattr (resp , "text" , None )
77+ except Exception :
78+ body = None
79+ req = getattr (e , "request" , None )
80+ if url is None and req is not None :
81+ try :
82+ url = str (getattr (req , "url" , None ) or "" )
83+ except Exception :
84+ url = None
85+ except Exception :
86+ pass
87+
88+ msg = "Server-side snapshot API failed"
89+ bits = []
90+ if status_code is not None :
91+ bits .append (f"status={ status_code } " )
92+ if url :
93+ bits .append (f"url={ url } " )
94+ if request_id :
95+ bits .append (f"request_id={ request_id } " )
96+ body_snip = cls ._snip (body )
97+ if body_snip :
98+ bits .append (f"body={ body_snip } " )
99+ if bits :
100+ msg = f"{ msg } : " + " " .join (bits )
101+ msg = msg + ". Try using use_api=False to use local extension instead."
102+ return cls (
103+ msg ,
104+ status_code = int (status_code ) if status_code is not None else None ,
105+ url = url ,
106+ request_id = str (request_id ) if request_id else None ,
107+ response_text = body_snip ,
108+ cause = e ,
109+ )
110+
111+ @classmethod
112+ def from_requests (cls , e : Exception ) -> "SnapshotGatewayError" :
113+ status_code = None
114+ url = None
115+ request_id = None
116+ body = None
117+ try :
118+ resp = getattr (e , "response" , None )
119+ if resp is not None :
120+ status_code = getattr (resp , "status_code" , None )
121+ try :
122+ url = str (getattr (resp , "url" , None ) or "" )
123+ except Exception :
124+ url = None
125+ try :
126+ headers = getattr (resp , "headers" , None ) or {}
127+ request_id = headers .get ("x-request-id" ) or headers .get ("x-trace-id" )
128+ except Exception :
129+ request_id = None
130+ try :
131+ body = getattr (resp , "text" , None )
132+ except Exception :
133+ body = None
134+ except Exception :
135+ pass
136+ msg = "Server-side snapshot API failed"
137+ bits = []
138+ if status_code is not None :
139+ bits .append (f"status={ status_code } " )
140+ if url :
141+ bits .append (f"url={ url } " )
142+ if request_id :
143+ bits .append (f"request_id={ request_id } " )
144+ body_snip = cls ._snip (body )
145+ if body_snip :
146+ bits .append (f"body={ body_snip } " )
147+ if bits :
148+ msg = f"{ msg } : " + " " .join (bits )
149+ msg = msg + ". Try using use_api=False to use local extension instead."
150+ return cls (
151+ msg ,
152+ status_code = int (status_code ) if status_code is not None else None ,
153+ url = url ,
154+ request_id = str (request_id ) if request_id else None ,
155+ response_text = body_snip ,
156+ cause = e ,
157+ )
158+
159+
23160def _is_execution_context_destroyed_error (e : Exception ) -> bool :
24161 """
25162 Playwright can throw while a navigation is in-flight, invalidating the JS execution context.
@@ -170,14 +307,20 @@ def _post_snapshot_to_gateway_sync(
170307 "Content-Type" : "application/json" ,
171308 }
172309
173- response = requests .post (
174- f"{ api_url } /v1/snapshot" ,
175- data = payload_json ,
176- headers = headers ,
177- timeout = 30 ,
178- )
179- response .raise_for_status ()
180- return response .json ()
310+ try :
311+ response = requests .post (
312+ f"{ api_url } /v1/snapshot" ,
313+ data = payload_json ,
314+ headers = headers ,
315+ timeout = 30 ,
316+ )
317+ response .raise_for_status ()
318+ return response .json ()
319+ except requests .exceptions .HTTPError as e :
320+ raise SnapshotGatewayError .from_requests (e ) from e
321+ except requests .exceptions .RequestException as e :
322+ # Network/timeouts/etc (no status code available)
323+ raise SnapshotGatewayError .from_requests (e ) from e
181324
182325
183326async def _post_snapshot_to_gateway_async (
@@ -202,13 +345,21 @@ async def _post_snapshot_to_gateway_async(
202345 }
203346
204347 async with httpx .AsyncClient (timeout = 30.0 ) as client :
205- response = await client .post (
206- f"{ api_url } /v1/snapshot" ,
207- content = payload_json ,
208- headers = headers ,
209- )
210- response .raise_for_status ()
211- return response .json ()
348+ try :
349+ response = await client .post (
350+ f"{ api_url } /v1/snapshot" ,
351+ content = payload_json ,
352+ headers = headers ,
353+ )
354+ response .raise_for_status ()
355+ return response .json ()
356+ except httpx .HTTPStatusError as e :
357+ raise SnapshotGatewayError .from_httpx (e ) from e
358+ except httpx .RequestError as e :
359+ raise SnapshotGatewayError .from_httpx (e ) from e
360+ except Exception as e :
361+ # JSON decode or other unexpected issues — keep details if possible.
362+ raise SnapshotGatewayError .from_httpx (e ) from e
212363
213364
214365def _merge_api_result_with_local (
0 commit comments