From 3ce9bb38c5133bcfb88795bc67fe5e678bd5a0c5 Mon Sep 17 00:00:00 2001 From: mutnale_sushant Date: Mon, 16 Mar 2026 09:13:23 +0530 Subject: [PATCH 1/4] fix(playwright): filter unsupported context options in persistent browser This addresses issue #1784 by dynamically filtering options passed to launch_persistent_context and providing a warning log for ignored options like storage_state. --- pyproject.toml | 3 +++ src/crawlee/browsers/_playwright_browser.py | 16 +++++++++++++++- tests/unit/browsers/test_playwright_browser.py | 10 ++++++++++ uv.lock | 6 ++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2a3d21fb42..f4fb299805 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,11 +34,14 @@ keywords = [ "scraping", ] dependencies = [ + "apify-fingerprint-datapoints>=0.11.0", "async-timeout>=5.0.1", + "browserforge>=1.2.4", "cachetools>=5.5.0", "colorama>=0.4.0", "impit>=0.8.0", "more-itertools>=10.2.0", + "playwright>=1.58.0", "protego>=0.5.0", "psutil>=6.0.0", "pydantic-settings>=2.12.0", diff --git a/src/crawlee/browsers/_playwright_browser.py b/src/crawlee/browsers/_playwright_browser.py index 8ce19bfd26..c2a4ff9ddf 100644 --- a/src/crawlee/browsers/_playwright_browser.py +++ b/src/crawlee/browsers/_playwright_browser.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import inspect import shutil import tempfile from logging import getLogger @@ -67,8 +68,21 @@ async def new_context(self, **context_options: Any) -> BrowserContext: user_data_dir = tempfile.mkdtemp(prefix=self._TMP_DIR_PREFIX) self._temp_dir = Path(user_data_dir) + launch_persistent_context_sig = inspect.signature(self._browser_type.launch_persistent_context) + filtered_launch_options = { + key: value for key, value in launch_options.items() if key in launch_persistent_context_sig.parameters + } + + removed_options = set(launch_options.keys()) - set(filtered_launch_options.keys()) + if removed_options: + logger.warning( + f"The following options are not supported by Playwright's launch_persistent_context " + f'and will be ignored: {removed_options}. ' + 'To use these options, consider using incognito pages (use_incognito_pages=True).' + ) + self._context = await self._browser_type.launch_persistent_context( - user_data_dir=user_data_dir, **launch_options + user_data_dir=user_data_dir, **filtered_launch_options ) if self._temp_dir: diff --git a/tests/unit/browsers/test_playwright_browser.py b/tests/unit/browsers/test_playwright_browser.py index 120b886c59..56b8f15c1f 100644 --- a/tests/unit/browsers/test_playwright_browser.py +++ b/tests/unit/browsers/test_playwright_browser.py @@ -42,3 +42,13 @@ async def test_delete_temp_folder_with_close_browser(playwright: Playwright) -> assert current_temp_dir.exists() await persist_browser.close() assert not current_temp_dir.exists() + + +async def test_new_context_unsupported_options(playwright: Playwright) -> None: + persist_browser = PlaywrightPersistentBrowser( + playwright.chromium, user_data_dir=None, browser_launch_options={'headless': True} + ) + # This should not crash despite 'storage_state' being invalid for launch_persistent_context + context = await persist_browser.new_context(storage_state={'cookies': [], 'origins': []}) + assert context is not None + await persist_browser.close() diff --git a/uv.lock b/uv.lock index b57b9aabf0..1de293799e 100644 --- a/uv.lock +++ b/uv.lock @@ -787,11 +787,14 @@ name = "crawlee" version = "1.5.0" source = { editable = "." } dependencies = [ + { name = "apify-fingerprint-datapoints" }, { name = "async-timeout" }, + { name = "browserforge" }, { name = "cachetools" }, { name = "colorama" }, { name = "impit" }, { name = "more-itertools" }, + { name = "playwright" }, { name = "protego" }, { name = "psutil" }, { name = "pydantic" }, @@ -924,12 +927,14 @@ dev = [ requires-dist = [ { name = "aiomysql", marker = "extra == 'sql-mysql'", specifier = ">=0.3.2" }, { name = "aiosqlite", marker = "extra == 'sql-sqlite'", specifier = ">=0.21.0" }, + { name = "apify-fingerprint-datapoints", specifier = ">=0.11.0" }, { name = "apify-fingerprint-datapoints", marker = "extra == 'adaptive-crawler'", specifier = ">=0.0.3" }, { name = "apify-fingerprint-datapoints", marker = "extra == 'httpx'", specifier = ">=0.0.2" }, { name = "apify-fingerprint-datapoints", marker = "extra == 'playwright'", specifier = ">=0.0.2" }, { name = "async-timeout", specifier = ">=5.0.1" }, { name = "asyncpg", marker = "extra == 'sql-postgres'", specifier = ">=0.24.0" }, { name = "beautifulsoup4", extras = ["lxml"], marker = "extra == 'beautifulsoup'", specifier = ">=4.12.0" }, + { name = "browserforge", specifier = ">=1.2.4" }, { name = "browserforge", marker = "extra == 'adaptive-crawler'", specifier = ">=1.2.4" }, { name = "browserforge", marker = "extra == 'httpx'", specifier = ">=1.2.3" }, { name = "browserforge", marker = "extra == 'playwright'", specifier = ">=1.2.3" }, @@ -952,6 +957,7 @@ requires-dist = [ { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.34.1" }, { name = "opentelemetry-semantic-conventions", marker = "extra == 'otel'", specifier = ">=0.54" }, { name = "parsel", marker = "extra == 'parsel'", specifier = ">=1.10.0" }, + { name = "playwright", specifier = ">=1.58.0" }, { name = "playwright", marker = "extra == 'adaptive-crawler'", specifier = ">=1.27.0" }, { name = "playwright", marker = "extra == 'playwright'", specifier = ">=1.27.0" }, { name = "protego", specifier = ">=0.5.0" }, From 694e1ce834c2b36c2c98cf9f74fe93779c099c4b Mon Sep 17 00:00:00 2001 From: mutnale_sushant Date: Wed, 18 Mar 2026 11:28:04 +0530 Subject: [PATCH 2/4] refactor(playwright): move context options validation to PlaywrightBrowserController Moving the validation logic from the browser instance to its controller as suggested by the reviewer. This improves user experience by raising TypeError for typos and nonsensical arguments while still providing helpful warnings for valid but incompatible cross-mode options like storage_state in persistent contexts. Also fixed dependency management in pyproject.toml. --- pyproject.toml | 7 +-- src/crawlee/browsers/_playwright_browser.py | 16 +----- .../_playwright_browser_controller.py | 47 +++++++++++++--- .../unit/browsers/test_playwright_browser.py | 10 ---- .../test_playwright_controller_validation.py | 56 +++++++++++++++++++ uv.lock | 8 +-- 6 files changed, 103 insertions(+), 41 deletions(-) create mode 100644 tests/unit/browsers/test_playwright_controller_validation.py diff --git a/pyproject.toml b/pyproject.toml index f4fb299805..b974b7b1d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ keywords = [ "scraping", ] dependencies = [ - "apify-fingerprint-datapoints>=0.11.0", "async-timeout>=5.0.1", "browserforge>=1.2.4", "cachetools>=5.5.0", @@ -58,15 +57,15 @@ adaptive-crawler = [ "jaro-winkler>=2.0.3", "playwright>=1.27.0", "scikit-learn>=1.6.0", - "apify_fingerprint_datapoints>=0.0.3", + "apify_fingerprint_datapoints>=0.11.0", "browserforge>=1.2.4" ] beautifulsoup = ["beautifulsoup4[lxml]>=4.12.0", "html5lib>=1.0"] cli = ["cookiecutter>=2.6.0", "inquirer>=3.3.0", "rich>=13.9.0", "typer>=0.12.0"] curl-impersonate = ["curl-cffi>=0.9.0"] -httpx = ["httpx[brotli,http2,zstd]>=0.27.0", "apify_fingerprint_datapoints>=0.0.2", "browserforge>=1.2.3"] +httpx = ["httpx[brotli,http2,zstd]>=0.27.0", "apify_fingerprint_datapoints>=0.11.0", "browserforge>=1.2.3"] parsel = ["parsel>=1.10.0"] -playwright = ["playwright>=1.27.0", "apify_fingerprint_datapoints>=0.0.2", "browserforge>=1.2.3"] +playwright = ["playwright>=1.27.0", "apify_fingerprint_datapoints>=0.11.0", "browserforge>=1.2.3"] otel = [ "opentelemetry-api>=1.34.1", "opentelemetry-distro[otlp]>=0.54", diff --git a/src/crawlee/browsers/_playwright_browser.py b/src/crawlee/browsers/_playwright_browser.py index c2a4ff9ddf..8ce19bfd26 100644 --- a/src/crawlee/browsers/_playwright_browser.py +++ b/src/crawlee/browsers/_playwright_browser.py @@ -1,7 +1,6 @@ from __future__ import annotations import asyncio -import inspect import shutil import tempfile from logging import getLogger @@ -68,21 +67,8 @@ async def new_context(self, **context_options: Any) -> BrowserContext: user_data_dir = tempfile.mkdtemp(prefix=self._TMP_DIR_PREFIX) self._temp_dir = Path(user_data_dir) - launch_persistent_context_sig = inspect.signature(self._browser_type.launch_persistent_context) - filtered_launch_options = { - key: value for key, value in launch_options.items() if key in launch_persistent_context_sig.parameters - } - - removed_options = set(launch_options.keys()) - set(filtered_launch_options.keys()) - if removed_options: - logger.warning( - f"The following options are not supported by Playwright's launch_persistent_context " - f'and will be ignored: {removed_options}. ' - 'To use these options, consider using incognito pages (use_incognito_pages=True).' - ) - self._context = await self._browser_type.launch_persistent_context( - user_data_dir=user_data_dir, **filtered_launch_options + user_data_dir=user_data_dir, **launch_options ) if self._temp_dir: diff --git a/src/crawlee/browsers/_playwright_browser_controller.py b/src/crawlee/browsers/_playwright_browser_controller.py index f386a7a903..e1682a637f 100644 --- a/src/crawlee/browsers/_playwright_browser_controller.py +++ b/src/crawlee/browsers/_playwright_browser_controller.py @@ -2,12 +2,14 @@ from __future__ import annotations +import inspect from asyncio import Lock from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, cast from browserforge.injectors.playwright import AsyncNewContext from playwright.async_api import Browser, BrowserContext, Page, ProxySettings +from playwright.async_api import BrowserType as PlaywrightBrowserType from typing_extensions import override from crawlee._utils.docs import docs_group @@ -27,6 +29,14 @@ logger = getLogger(__name__) +# Cache Playwright signatures to avoid overhead in critical path +_launch_persistent_context_params = set(inspect.signature(PlaywrightBrowserType.launch_persistent_context).parameters) +_new_context_params = set(inspect.signature(Browser.new_context).parameters) + +_common_context_options = _launch_persistent_context_params & _new_context_params +_persistent_unique_context_options = _launch_persistent_context_params - _new_context_params +_incognito_unique_context_options = _new_context_params - _launch_persistent_context_params + @docs_group('Browser management') class PlaywrightBrowserController(BrowserController): @@ -222,11 +232,36 @@ async def _create_browser_context( `self._fingerprint_generator` is available. """ browser_new_context_options = dict(browser_new_context_options) if browser_new_context_options else {} + + filtered_options = {} + for key, value in browser_new_context_options.items(): + if self._use_incognito_pages: + # Incognito mode (new_context) + if key in _common_context_options or key in _incognito_unique_context_options: + filtered_options[key] = value + elif key in _persistent_unique_context_options: + logger.warning( + f'Option "{key}" is only supported in persistent context mode ' + '(use_incognito_pages=False) and will be ignored.' + ) + else: + raise TypeError(f'"{key}" is not a valid Playwright context option.') + elif key in _common_context_options or key in _persistent_unique_context_options: + # Persistent mode (launch_persistent_context) + filtered_options[key] = value + elif key in _incognito_unique_context_options: + logger.warning( + f'Option "{key}" is only supported in incognito context mode ' + '(use_incognito_pages=True) and will be ignored.' + ) + else: + raise TypeError(f'"{key}" is not a valid Playwright context option.') + if proxy_info: - if browser_new_context_options.get('proxy'): + if filtered_options.get('proxy'): logger.warning("browser_new_context_options['proxy'] overridden by explicit `proxy_info` argument.") - browser_new_context_options['proxy'] = ProxySettings( + filtered_options['proxy'] = ProxySettings( server=f'{proxy_info.scheme}://{proxy_info.hostname}:{proxy_info.port}', username=proxy_info.username, password=proxy_info.password, @@ -236,7 +271,7 @@ async def _create_browser_context( return await AsyncNewContext( browser=self._browser, fingerprint=self._fingerprint_generator.generate(), - **browser_new_context_options, + **filtered_options, ) if self._header_generator: @@ -256,7 +291,5 @@ async def _create_browser_context( else: extra_http_headers = None - browser_new_context_options['extra_http_headers'] = browser_new_context_options.get( - 'extra_http_headers', extra_http_headers - ) - return await self._browser.new_context(**browser_new_context_options) + filtered_options['extra_http_headers'] = filtered_options.get('extra_http_headers', extra_http_headers) + return await self._browser.new_context(**filtered_options) diff --git a/tests/unit/browsers/test_playwright_browser.py b/tests/unit/browsers/test_playwright_browser.py index 56b8f15c1f..120b886c59 100644 --- a/tests/unit/browsers/test_playwright_browser.py +++ b/tests/unit/browsers/test_playwright_browser.py @@ -42,13 +42,3 @@ async def test_delete_temp_folder_with_close_browser(playwright: Playwright) -> assert current_temp_dir.exists() await persist_browser.close() assert not current_temp_dir.exists() - - -async def test_new_context_unsupported_options(playwright: Playwright) -> None: - persist_browser = PlaywrightPersistentBrowser( - playwright.chromium, user_data_dir=None, browser_launch_options={'headless': True} - ) - # This should not crash despite 'storage_state' being invalid for launch_persistent_context - context = await persist_browser.new_context(storage_state={'cookies': [], 'origins': []}) - assert context is not None - await persist_browser.close() diff --git a/tests/unit/browsers/test_playwright_controller_validation.py b/tests/unit/browsers/test_playwright_controller_validation.py new file mode 100644 index 0000000000..e43d5c5db1 --- /dev/null +++ b/tests/unit/browsers/test_playwright_controller_validation.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import pytest +from playwright.async_api import Browser, Playwright, async_playwright + +from crawlee.browsers import PlaywrightBrowserController + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator + +@pytest.fixture +async def playwright() -> AsyncGenerator[Playwright, None]: + async with async_playwright() as playwright: + yield playwright + +@pytest.fixture +async def browser(playwright: Playwright) -> AsyncGenerator[Browser, None]: + browser = await playwright.chromium.launch() + yield browser + await browser.close() + +async def test_controller_validation_typo(browser: Browser) -> None: + controller = PlaywrightBrowserController(browser) + with pytest.raises(TypeError, match=r'"headles" is not a valid Playwright context option.'): + await controller.new_page(browser_new_context_options={'headles': True}) + await controller.close() + +async def test_controller_validation_cross_mode_persistent(browser: Browser, caplog: pytest.LogCaptureFixture) -> None: + # Default is persistent mode (use_incognito_pages=False) + controller = PlaywrightBrowserController(browser, use_incognito_pages=False) + # storage_state is incognito-only + with caplog.at_level(logging.WARNING): + page = await controller.new_page(browser_new_context_options={'storage_state': {'cookies': [], 'origins': []}}) + assert 'Option "storage_state" is only supported in incognito context mode' in caplog.text + await page.close() + await controller.close() + +async def test_controller_validation_cross_mode_incognito(browser: Browser, caplog: pytest.LogCaptureFixture) -> None: + controller = PlaywrightBrowserController(browser, use_incognito_pages=True) + # env is persistent-only + with caplog.at_level(logging.WARNING): + page = await controller.new_page(browser_new_context_options={'env': {}}) + assert 'Option "env" is only supported in persistent context mode' in caplog.text + await page.close() + await controller.close() + +async def test_controller_validation_valid_common(browser: Browser) -> None: + controller = PlaywrightBrowserController(browser) + # viewport is common + page = await controller.new_page(browser_new_context_options={'viewport': {'width': 800, 'height': 600}}) + assert page.viewport_size == {'width': 800, 'height': 600} + await page.close() + await controller.close() diff --git a/uv.lock b/uv.lock index 1de293799e..5a9fd5d4ab 100644 --- a/uv.lock +++ b/uv.lock @@ -787,7 +787,6 @@ name = "crawlee" version = "1.5.0" source = { editable = "." } dependencies = [ - { name = "apify-fingerprint-datapoints" }, { name = "async-timeout" }, { name = "browserforge" }, { name = "cachetools" }, @@ -927,10 +926,9 @@ dev = [ requires-dist = [ { name = "aiomysql", marker = "extra == 'sql-mysql'", specifier = ">=0.3.2" }, { name = "aiosqlite", marker = "extra == 'sql-sqlite'", specifier = ">=0.21.0" }, - { name = "apify-fingerprint-datapoints", specifier = ">=0.11.0" }, - { name = "apify-fingerprint-datapoints", marker = "extra == 'adaptive-crawler'", specifier = ">=0.0.3" }, - { name = "apify-fingerprint-datapoints", marker = "extra == 'httpx'", specifier = ">=0.0.2" }, - { name = "apify-fingerprint-datapoints", marker = "extra == 'playwright'", specifier = ">=0.0.2" }, + { name = "apify-fingerprint-datapoints", marker = "extra == 'adaptive-crawler'", specifier = ">=0.11.0" }, + { name = "apify-fingerprint-datapoints", marker = "extra == 'httpx'", specifier = ">=0.11.0" }, + { name = "apify-fingerprint-datapoints", marker = "extra == 'playwright'", specifier = ">=0.11.0" }, { name = "async-timeout", specifier = ">=5.0.1" }, { name = "asyncpg", marker = "extra == 'sql-postgres'", specifier = ">=0.24.0" }, { name = "beautifulsoup4", extras = ["lxml"], marker = "extra == 'beautifulsoup'", specifier = ">=4.12.0" }, From 57da910e4883d30ba26c2d6a1cd103b65df289ed Mon Sep 17 00:00:00 2001 From: mutnale_sushant Date: Wed, 18 Mar 2026 14:32:06 +0530 Subject: [PATCH 3/4] style: fix formatting in test_playwright_controller_validation.py Ran ruff formatter to fix CI lint error. --- .../unit/browsers/test_playwright_controller_validation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/browsers/test_playwright_controller_validation.py b/tests/unit/browsers/test_playwright_controller_validation.py index e43d5c5db1..034f0b9f77 100644 --- a/tests/unit/browsers/test_playwright_controller_validation.py +++ b/tests/unit/browsers/test_playwright_controller_validation.py @@ -11,23 +11,27 @@ if TYPE_CHECKING: from collections.abc import AsyncGenerator + @pytest.fixture async def playwright() -> AsyncGenerator[Playwright, None]: async with async_playwright() as playwright: yield playwright + @pytest.fixture async def browser(playwright: Playwright) -> AsyncGenerator[Browser, None]: browser = await playwright.chromium.launch() yield browser await browser.close() + async def test_controller_validation_typo(browser: Browser) -> None: controller = PlaywrightBrowserController(browser) with pytest.raises(TypeError, match=r'"headles" is not a valid Playwright context option.'): await controller.new_page(browser_new_context_options={'headles': True}) await controller.close() + async def test_controller_validation_cross_mode_persistent(browser: Browser, caplog: pytest.LogCaptureFixture) -> None: # Default is persistent mode (use_incognito_pages=False) controller = PlaywrightBrowserController(browser, use_incognito_pages=False) @@ -38,6 +42,7 @@ async def test_controller_validation_cross_mode_persistent(browser: Browser, cap await page.close() await controller.close() + async def test_controller_validation_cross_mode_incognito(browser: Browser, caplog: pytest.LogCaptureFixture) -> None: controller = PlaywrightBrowserController(browser, use_incognito_pages=True) # env is persistent-only @@ -47,6 +52,7 @@ async def test_controller_validation_cross_mode_incognito(browser: Browser, capl await page.close() await controller.close() + async def test_controller_validation_valid_common(browser: Browser) -> None: controller = PlaywrightBrowserController(browser) # viewport is common From ef3e68d4fe559785687af530727b9a59c19220af Mon Sep 17 00:00:00 2001 From: mutnale_sushant Date: Sun, 22 Mar 2026 15:12:14 +0530 Subject: [PATCH 4/4] fix(playwright): address review comments on dependencies and lazy cache Removed browserforge and playwright from core dependencies in pyproject.toml as they belong in optional dependencies. Refactored Playwright signature cache in _playwright_browser_controller.py to load lazily via lru_cache rather than at module import time, preventing overhead when Playwright is not used. --- pyproject.toml | 2 -- .../_playwright_browser_controller.py | 27 ++++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b974b7b1d3..c20a9ddbd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,12 +35,10 @@ keywords = [ ] dependencies = [ "async-timeout>=5.0.1", - "browserforge>=1.2.4", "cachetools>=5.5.0", "colorama>=0.4.0", "impit>=0.8.0", "more-itertools>=10.2.0", - "playwright>=1.58.0", "protego>=0.5.0", "psutil>=6.0.0", "pydantic-settings>=2.12.0", diff --git a/src/crawlee/browsers/_playwright_browser_controller.py b/src/crawlee/browsers/_playwright_browser_controller.py index e1682a637f..88b30ac333 100644 --- a/src/crawlee/browsers/_playwright_browser_controller.py +++ b/src/crawlee/browsers/_playwright_browser_controller.py @@ -5,6 +5,7 @@ import inspect from asyncio import Lock from datetime import datetime, timedelta, timezone +from functools import lru_cache from typing import TYPE_CHECKING, Any, cast from browserforge.injectors.playwright import AsyncNewContext @@ -29,13 +30,17 @@ logger = getLogger(__name__) -# Cache Playwright signatures to avoid overhead in critical path -_launch_persistent_context_params = set(inspect.signature(PlaywrightBrowserType.launch_persistent_context).parameters) -_new_context_params = set(inspect.signature(Browser.new_context).parameters) -_common_context_options = _launch_persistent_context_params & _new_context_params -_persistent_unique_context_options = _launch_persistent_context_params - _new_context_params -_incognito_unique_context_options = _new_context_params - _launch_persistent_context_params +# Cache Playwright signatures to avoid overhead in critical path +@lru_cache(maxsize=1) +def _get_context_params_cache() -> dict[str, set[str]]: + launch_persistent_params = set(inspect.signature(PlaywrightBrowserType.launch_persistent_context).parameters) + new_context_params = set(inspect.signature(Browser.new_context).parameters) + return { + 'common': launch_persistent_params & new_context_params, + 'persistent_unique': launch_persistent_params - new_context_params, + 'incognito_unique': new_context_params - launch_persistent_params, + } @docs_group('Browser management') @@ -233,23 +238,25 @@ async def _create_browser_context( """ browser_new_context_options = dict(browser_new_context_options) if browser_new_context_options else {} + params_cache = _get_context_params_cache() + filtered_options = {} for key, value in browser_new_context_options.items(): if self._use_incognito_pages: # Incognito mode (new_context) - if key in _common_context_options or key in _incognito_unique_context_options: + if key in params_cache['common'] or key in params_cache['incognito_unique']: filtered_options[key] = value - elif key in _persistent_unique_context_options: + elif key in params_cache['persistent_unique']: logger.warning( f'Option "{key}" is only supported in persistent context mode ' '(use_incognito_pages=False) and will be ignored.' ) else: raise TypeError(f'"{key}" is not a valid Playwright context option.') - elif key in _common_context_options or key in _persistent_unique_context_options: + elif key in params_cache['common'] or key in params_cache['persistent_unique']: # Persistent mode (launch_persistent_context) filtered_options[key] = value - elif key in _incognito_unique_context_options: + elif key in params_cache['incognito_unique']: logger.warning( f'Option "{key}" is only supported in incognito context mode ' '(use_incognito_pages=True) and will be ignored.'