Browser sessions no longer close a tab after its request is completed. The tab returns to the pool as ready, and the next request reuses it, reapplying its own timeouts, extra headers, and resource/domain routes (after `unroute_all`) so nothing leaks between requests. Tabs that hit an error or got closed by the browser are closed and evicted, and the internal response listener is detached after every request so reused tabs don't stack handlers.
Adds `close_pages()` to all browser sessions to close every open tab; the next request opens a fresh one. `PagePool` gains `get_ready_page`/`remove_page`/`clear` and `PageInfo.mark_ready`, new pages start busy, and the unused `cleanup_error_pages` is removed.
Proxy-rotation contexts keep closing per request. Docs and the agent skill describe the new lifecycle.
A better fix than #418 is to eliminate this bug class and keep the AI agent knowing the default parameter values.
I have also shortened the docstrings where possible to save tokens.
The three MCP tools that accept a `session_id` forwarded every per-fetch
parameter to the session's `fetch()`, including their own defaults when
the caller supplied nothing. `validate_fetch` treats every key it
receives as an override, so the branch that reads the value from the
session config never ran, and the tool defaults replaced the settings the
session was opened with.
- Forward only the parameters the caller actually supplied
- Default those parameters to `None` on `fetch`, `bulk_fetch`,
`stealthy_fetch`, `bulk_stealthy_fetch` and `screenshot`
- Add regression tests for the forwarded kwargs, and for the session
settings surviving the validation the tools go through
The solver matched English challenge strings, so any non-default locale left the challenge unsolved.
The real cause was Playwright's context locale option patching the main thread only, while Web Workers kept the browser language, a mismatch Cloudflare rejects. Locale is now applied via browser launch flags, so the page, workers, and the Accept-Language header stay consistent, and the solver checks the language-free challenge markers with a retry cap.
Also catch patchright errors in the page-content retry so stealth pages stop crashing mid-solve.
Co-Authored-By: Parash Subedi <parash.subedi@grepsr.com>
ResponseCacheManager.put() only recognized the flat `dict` shape that
static-engine responses use for `Response.cookies`. Browser engines
(Playwright) populate cookies as a `tuple` of full cookie dicts, so the
isinstance(dict) guard fell through to the `else {}` branch and silently
discarded every cookie before it ever reached the cache file. Replaying a
cached browser-engine response then rebuilt it with cookies={}.
Preserve whichever shape the cookies are in instead of collapsing anything
non-dict to {}: serialize a tuple as a JSON array and a dict as a JSON
object, then restore the tuple shape on read since JSON arrays deserialize
back as `list`.
Add regression tests covering both the browser-engine (tuple) and
static-engine (dict) cookie round-trip through put()/get().
The per-request proxy resolution never fell back to the session default, so FetcherSession(proxy=...) was silently ignored, and requests went direct. Same fix in the sync and async paths, with regression tests asserting on the proxy that reaches curl_cffi.
Closes#295