We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] misleading error message "browser was not found" if browser fails to start for other reasons
# Environment
playwright==1.9.2
ubuntu 20.04 in docker
chromium v854489 installed via `python3 -m playwright install`

# Problem description:
Unintentionally, the test attempted to launch headful chromium, launch failed with misleading error message:
```
"chromium" browser was not found.
Please complete Playwright installation via running
    "python -m playwright install"
```
Launching browser as `chromium.launch(headless=True)` was succesful, so the problem clearly was not with browser installation, but browser launch inself (most probably, unavailable X11)

# Expected behavior:
browser stderr captured and displayed


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `playwright/_impl/_browser_type.py`
Content:
```
1 # Copyright (c) Microsoft Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 from pathlib import Path
16 from typing import Dict, List, Union
17 
18 from playwright._impl._api_structures import (
19     Geolocation,
20     HttpCredentials,
21     ProxySettings,
22     ViewportSize,
23 )
24 from playwright._impl._browser import Browser, normalize_context_params
25 from playwright._impl._browser_context import BrowserContext
26 from playwright._impl._connection import ChannelOwner, from_channel
27 from playwright._impl._helper import (
28     ColorScheme,
29     Env,
30     locals_to_params,
31     not_installed_error,
32 )
33 
34 
35 class BrowserType(ChannelOwner):
36     def __init__(
37         self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
38     ) -> None:
39         super().__init__(parent, type, guid, initializer)
40 
41     @property
42     def name(self) -> str:
43         return self._initializer["name"]
44 
45     @property
46     def executable_path(self) -> str:
47         return self._initializer["executablePath"]
48 
49     async def launch(
50         self,
51         executablePath: Union[str, Path] = None,
52         args: List[str] = None,
53         ignoreDefaultArgs: Union[bool, List[str]] = None,
54         handleSIGINT: bool = None,
55         handleSIGTERM: bool = None,
56         handleSIGHUP: bool = None,
57         timeout: float = None,
58         env: Env = None,
59         headless: bool = None,
60         devtools: bool = None,
61         proxy: ProxySettings = None,
62         downloadsPath: Union[str, Path] = None,
63         slowMo: float = None,
64         chromiumSandbox: bool = None,
65         firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,
66     ) -> Browser:
67         params = locals_to_params(locals())
68         normalize_launch_params(params)
69         try:
70             return from_channel(await self._channel.send("launch", params))
71         except Exception as e:
72             if f"{self.name}-" in str(e):
73                 raise not_installed_error(f'"{self.name}" browser was not found.')
74             raise e
75 
76     async def launch_persistent_context(
77         self,
78         userDataDir: Union[str, Path],
79         executablePath: Union[str, Path] = None,
80         args: List[str] = None,
81         ignoreDefaultArgs: Union[bool, List[str]] = None,
82         handleSIGINT: bool = None,
83         handleSIGTERM: bool = None,
84         handleSIGHUP: bool = None,
85         timeout: float = None,
86         env: Env = None,
87         headless: bool = None,
88         devtools: bool = None,
89         proxy: ProxySettings = None,
90         downloadsPath: Union[str, Path] = None,
91         slowMo: float = None,
92         viewport: ViewportSize = None,
93         noViewport: bool = None,
94         ignoreHTTPSErrors: bool = None,
95         javaScriptEnabled: bool = None,
96         bypassCSP: bool = None,
97         userAgent: str = None,
98         locale: str = None,
99         timezoneId: str = None,
100         geolocation: Geolocation = None,
101         permissions: List[str] = None,
102         extraHTTPHeaders: Dict[str, str] = None,
103         offline: bool = None,
104         httpCredentials: HttpCredentials = None,
105         deviceScaleFactor: float = None,
106         isMobile: bool = None,
107         hasTouch: bool = None,
108         colorScheme: ColorScheme = None,
109         acceptDownloads: bool = None,
110         chromiumSandbox: bool = None,
111         recordHarPath: Union[Path, str] = None,
112         recordHarOmitContent: bool = None,
113         recordVideoDir: Union[Path, str] = None,
114         recordVideoSize: ViewportSize = None,
115     ) -> BrowserContext:
116         userDataDir = str(Path(userDataDir))
117         params = locals_to_params(locals())
118         normalize_context_params(self._connection._is_sync, params)
119         normalize_launch_params(params)
120         try:
121             context = from_channel(
122                 await self._channel.send("launchPersistentContext", params)
123             )
124             context._options = params
125             return context
126         except Exception as e:
127             if f"{self.name}-" in str(e):
128                 raise not_installed_error(f'"{self.name}" browser was not found.')
129             raise e
130 
131 
132 def normalize_launch_params(params: Dict) -> None:
133     if "env" in params:
134         params["env"] = {name: str(value) for [name, value] in params["env"].items()}
135     if "ignoreDefaultArgs" in params:
136         if params["ignoreDefaultArgs"] is True:
137             params["ignoreAllDefaultArgs"] = True
138             del params["ignoreDefaultArgs"]
139     if "executablePath" in params:
140         params["executablePath"] = str(Path(params["executablePath"]))
141     if "downloadsPath" in params:
142         params["downloadsPath"] = str(Path(params["downloadsPath"]))
143
```

Path: `playwright/_impl/_helper.py`
Content:
```
1 # Copyright (c) Microsoft Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import fnmatch
16 import math
17 import re
18 import sys
19 import time
20 import traceback
21 from types import TracebackType
22 from typing import (
23     TYPE_CHECKING,
24     Any,
25     Callable,
26     Dict,
27     List,
28     Optional,
29     Pattern,
30     Union,
31     cast,
32 )
33 
34 from playwright._impl._api_types import Error, TimeoutError
35 
36 if sys.version_info >= (3, 8):  # pragma: no cover
37     from typing import Literal, TypedDict
38 else:  # pragma: no cover
39     from typing_extensions import Literal, TypedDict
40 
41 
42 if TYPE_CHECKING:  # pragma: no cover
43     from playwright._impl._network import Request, Response, Route
44 
45 URLMatch = Union[str, Pattern, Callable[[str], bool]]
46 URLMatchRequest = Union[str, Pattern, Callable[["Request"], bool]]
47 URLMatchResponse = Union[str, Pattern, Callable[["Response"], bool]]
48 RouteHandler = Union[Callable[["Route"], Any], Callable[["Route", "Request"], Any]]
49 
50 ColorScheme = Literal["dark", "light", "no-preference"]
51 DocumentLoadState = Literal["domcontentloaded", "load", "networkidle"]
52 KeyboardModifier = Literal["Alt", "Control", "Meta", "Shift"]
53 MouseButton = Literal["left", "middle", "right"]
54 
55 
56 class ErrorPayload(TypedDict, total=False):
57     message: str
58     name: str
59     stack: str
60     value: Any
61 
62 
63 class Header(TypedDict):
64     name: str
65     value: str
66 
67 
68 class ContinueParameters(TypedDict, total=False):
69     url: Optional[str]
70     method: Optional[str]
71     headers: Optional[List[Header]]
72     postData: Optional[str]
73 
74 
75 class ParsedMessageParams(TypedDict):
76     type: str
77     guid: str
78     initializer: Dict
79 
80 
81 class ParsedMessagePayload(TypedDict, total=False):
82     id: int
83     guid: str
84     method: str
85     params: ParsedMessageParams
86     result: Any
87     error: ErrorPayload
88 
89 
90 class Document(TypedDict):
91     request: Optional[Any]
92 
93 
94 class FrameNavigatedEvent(TypedDict):
95     url: str
96     name: str
97     newDocument: Optional[Document]
98     error: Optional[str]
99 
100 
101 Env = Dict[str, Union[str, float, bool]]
102 
103 
104 class URLMatcher:
105     def __init__(self, match: URLMatch) -> None:
106         self._callback: Optional[Callable[[str], bool]] = None
107         self._regex_obj: Optional[Pattern] = None
108         if isinstance(match, str):
109             regex = fnmatch.translate(match)
110             self._regex_obj = re.compile(regex)
111         elif isinstance(match, Pattern):
112             self._regex_obj = match
113         else:
114             self._callback = match
115         self.match = match
116 
117     def matches(self, url: str) -> bool:
118         if self._callback:
119             return self._callback(url)
120         if self._regex_obj:
121             return cast(bool, self._regex_obj.search(url))
122         return False
123 
124 
125 class TimeoutSettings:
126     def __init__(self, parent: Optional["TimeoutSettings"]) -> None:
127         self._parent = parent
128         self._timeout = 30000.0
129         self._navigation_timeout = 30000.0
130 
131     def set_timeout(self, timeout: float) -> None:
132         self._timeout = timeout
133 
134     def timeout(self) -> float:
135         if self._timeout is not None:
136             return self._timeout
137         if self._parent:
138             return self._parent.timeout()
139         return 30000
140 
141     def set_navigation_timeout(self, navigation_timeout: float) -> None:
142         self._navigation_timeout = navigation_timeout
143 
144     def navigation_timeout(self) -> float:
145         if self._navigation_timeout is not None:
146             return self._navigation_timeout
147         if self._parent:
148             return self._parent.navigation_timeout()
149         return 30000
150 
151 
152 def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload:
153     return dict(message=str(ex), name="Error", stack="".join(traceback.format_tb(tb)))
154 
155 
156 def parse_error(error: ErrorPayload) -> Error:
157     base_error_class = Error
158     if error.get("name") == "TimeoutError":
159         base_error_class = TimeoutError
160     return base_error_class(
161         cast(str, patch_error_message(error.get("message"))), error["stack"]
162     )
163 
164 
165 def patch_error_message(message: Optional[str]) -> Optional[str]:
166     if not message:
167         return None
168 
169     match = re.match(r"(\w+)(: expected .*)", message)
170     if match:
171         message = to_snake_case(match.group(1)) + match.group(2)
172     message = message.replace(
173         "Pass { acceptDownloads: true }", "Pass { accept_downloads: True }"
174     )
175     return message
176 
177 
178 def locals_to_params(args: Dict) -> Dict:
179     copy = {}
180     for key in args:
181         if key == "self":
182             continue
183         if args[key] is not None:
184             copy[key] = args[key]
185     return copy
186 
187 
188 def monotonic_time() -> int:
189     return math.floor(time.monotonic() * 1000)
190 
191 
192 class RouteHandlerEntry:
193     def __init__(self, matcher: URLMatcher, handler: RouteHandler):
194         self.matcher = matcher
195         self.handler = handler
196 
197 
198 def is_safe_close_error(error: Exception) -> bool:
199     message = str(error)
200     return message.endswith("Browser has been closed") or message.endswith(
201         "Target page, context or browser has been closed"
202     )
203 
204 
205 def not_installed_error(message: str) -> Exception:
206     return Exception(
207         f"""
208 ================================================================================
209 {message}
210 Please complete Playwright installation via running
211 
212     "python -m playwright install"
213 
214 ================================================================================
215 """
216     )
217 
218 
219 to_snake_case_regex = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")
220 
221 
222 def to_snake_case(name: str) -> str:
223     return to_snake_case_regex.sub(r"_\1", name).lower()
224
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py
--- a/playwright/_impl/_browser_type.py
+++ b/playwright/_impl/_browser_type.py
@@ -69,7 +69,7 @@
         try:
             return from_channel(await self._channel.send("launch", params))
         except Exception as e:
-            if f"{self.name}-" in str(e):
+            if "because executable doesn't exist" in str(e):
                 raise not_installed_error(f'"{self.name}" browser was not found.')
             raise e
 
@@ -124,7 +124,7 @@
             context._options = params
             return context
         except Exception as e:
-            if f"{self.name}-" in str(e):
+            if "because executable doesn't exist" in str(e):
                 raise not_installed_error(f'"{self.name}" browser was not found.')
             raise e
 
diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py
--- a/playwright/_impl/_helper.py
+++ b/playwright/_impl/_helper.py
@@ -203,7 +203,7 @@
 
 
 def not_installed_error(message: str) -> Exception:
-    return Exception(
+    return Error(
         f"""
 ================================================================================
 {message}

{"golden_diff": "diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py\n--- a/playwright/_impl/_browser_type.py\n+++ b/playwright/_impl/_browser_type.py\n@@ -69,7 +69,7 @@\n         try:\n             return from_channel(await self._channel.send(\"launch\", params))\n         except Exception as e:\n-            if f\"{self.name}-\" in str(e):\n+            if \"because executable doesn't exist\" in str(e):\n                 raise not_installed_error(f'\"{self.name}\" browser was not found.')\n             raise e\n \n@@ -124,7 +124,7 @@\n             context._options = params\n             return context\n         except Exception as e:\n-            if f\"{self.name}-\" in str(e):\n+            if \"because executable doesn't exist\" in str(e):\n                 raise not_installed_error(f'\"{self.name}\" browser was not found.')\n             raise e\n \ndiff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py\n--- a/playwright/_impl/_helper.py\n+++ b/playwright/_impl/_helper.py\n@@ -203,7 +203,7 @@\n \n \n def not_installed_error(message: str) -> Exception:\n-    return Exception(\n+    return Error(\n         f\"\"\"\n ================================================================================\n {message}\n", "issue": "[BUG] misleading error message \"browser was not found\" if browser fails to start for other reasons\n# Environment\r\nplaywright==1.9.2\r\nubuntu 20.04 in docker\r\nchromium v854489 installed via `python3 -m playwright install`\r\n\r\n# Problem description:\r\nUnintentionally, the test attempted to launch headful chromium, launch failed with misleading error message:\r\n```\r\n\"chromium\" browser was not found.\r\nPlease complete Playwright installation via running\r\n    \"python -m playwright install\"\r\n```\r\nLaunching browser as `chromium.launch(headless=True)` was succesful, so the problem clearly was not with browser installation, but browser launch inself (most probably, unavailable X11)\r\n\r\n# Expected behavior:\r\nbrowser stderr captured and displayed\r\n\n", "before_files": [{"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pathlib import Path\nfrom typing import Dict, List, Union\n\nfrom playwright._impl._api_structures import (\n    Geolocation,\n    HttpCredentials,\n    ProxySettings,\n    ViewportSize,\n)\nfrom playwright._impl._browser import Browser, normalize_context_params\nfrom playwright._impl._browser_context import BrowserContext\nfrom playwright._impl._connection import ChannelOwner, from_channel\nfrom playwright._impl._helper import (\n    ColorScheme,\n    Env,\n    locals_to_params,\n    not_installed_error,\n)\n\n\nclass BrowserType(ChannelOwner):\n    def __init__(\n        self, parent: ChannelOwner, type: str, guid: str, initializer: Dict\n    ) -> None:\n        super().__init__(parent, type, guid, initializer)\n\n    @property\n    def name(self) -> str:\n        return self._initializer[\"name\"]\n\n    @property\n    def executable_path(self) -> str:\n        return self._initializer[\"executablePath\"]\n\n    async def launch(\n        self,\n        executablePath: Union[str, Path] = None,\n        args: List[str] = None,\n        ignoreDefaultArgs: Union[bool, List[str]] = None,\n        handleSIGINT: bool = None,\n        handleSIGTERM: bool = None,\n        handleSIGHUP: bool = None,\n        timeout: float = None,\n        env: Env = None,\n        headless: bool = None,\n        devtools: bool = None,\n        proxy: ProxySettings = None,\n        downloadsPath: Union[str, Path] = None,\n        slowMo: float = None,\n        chromiumSandbox: bool = None,\n        firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,\n    ) -> Browser:\n        params = locals_to_params(locals())\n        normalize_launch_params(params)\n        try:\n            return from_channel(await self._channel.send(\"launch\", params))\n        except Exception as e:\n            if f\"{self.name}-\" in str(e):\n                raise not_installed_error(f'\"{self.name}\" browser was not found.')\n            raise e\n\n    async def launch_persistent_context(\n        self,\n        userDataDir: Union[str, Path],\n        executablePath: Union[str, Path] = None,\n        args: List[str] = None,\n        ignoreDefaultArgs: Union[bool, List[str]] = None,\n        handleSIGINT: bool = None,\n        handleSIGTERM: bool = None,\n        handleSIGHUP: bool = None,\n        timeout: float = None,\n        env: Env = None,\n        headless: bool = None,\n        devtools: bool = None,\n        proxy: ProxySettings = None,\n        downloadsPath: Union[str, Path] = None,\n        slowMo: float = None,\n        viewport: ViewportSize = None,\n        noViewport: bool = None,\n        ignoreHTTPSErrors: bool = None,\n        javaScriptEnabled: bool = None,\n        bypassCSP: bool = None,\n        userAgent: str = None,\n        locale: str = None,\n        timezoneId: str = None,\n        geolocation: Geolocation = None,\n        permissions: List[str] = None,\n        extraHTTPHeaders: Dict[str, str] = None,\n        offline: bool = None,\n        httpCredentials: HttpCredentials = None,\n        deviceScaleFactor: float = None,\n        isMobile: bool = None,\n        hasTouch: bool = None,\n        colorScheme: ColorScheme = None,\n        acceptDownloads: bool = None,\n        chromiumSandbox: bool = None,\n        recordHarPath: Union[Path, str] = None,\n        recordHarOmitContent: bool = None,\n        recordVideoDir: Union[Path, str] = None,\n        recordVideoSize: ViewportSize = None,\n    ) -> BrowserContext:\n        userDataDir = str(Path(userDataDir))\n        params = locals_to_params(locals())\n        normalize_context_params(self._connection._is_sync, params)\n        normalize_launch_params(params)\n        try:\n            context = from_channel(\n                await self._channel.send(\"launchPersistentContext\", params)\n            )\n            context._options = params\n            return context\n        except Exception as e:\n            if f\"{self.name}-\" in str(e):\n                raise not_installed_error(f'\"{self.name}\" browser was not found.')\n            raise e\n\n\ndef normalize_launch_params(params: Dict) -> None:\n    if \"env\" in params:\n        params[\"env\"] = {name: str(value) for [name, value] in params[\"env\"].items()}\n    if \"ignoreDefaultArgs\" in params:\n        if params[\"ignoreDefaultArgs\"] is True:\n            params[\"ignoreAllDefaultArgs\"] = True\n            del params[\"ignoreDefaultArgs\"]\n    if \"executablePath\" in params:\n        params[\"executablePath\"] = str(Path(params[\"executablePath\"]))\n    if \"downloadsPath\" in params:\n        params[\"downloadsPath\"] = str(Path(params[\"downloadsPath\"]))\n", "path": "playwright/_impl/_browser_type.py"}, {"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport fnmatch\nimport math\nimport re\nimport sys\nimport time\nimport traceback\nfrom types import TracebackType\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n    Dict,\n    List,\n    Optional,\n    Pattern,\n    Union,\n    cast,\n)\n\nfrom playwright._impl._api_types import Error, TimeoutError\n\nif sys.version_info >= (3, 8):  # pragma: no cover\n    from typing import Literal, TypedDict\nelse:  # pragma: no cover\n    from typing_extensions import Literal, TypedDict\n\n\nif TYPE_CHECKING:  # pragma: no cover\n    from playwright._impl._network import Request, Response, Route\n\nURLMatch = Union[str, Pattern, Callable[[str], bool]]\nURLMatchRequest = Union[str, Pattern, Callable[[\"Request\"], bool]]\nURLMatchResponse = Union[str, Pattern, Callable[[\"Response\"], bool]]\nRouteHandler = Union[Callable[[\"Route\"], Any], Callable[[\"Route\", \"Request\"], Any]]\n\nColorScheme = Literal[\"dark\", \"light\", \"no-preference\"]\nDocumentLoadState = Literal[\"domcontentloaded\", \"load\", \"networkidle\"]\nKeyboardModifier = Literal[\"Alt\", \"Control\", \"Meta\", \"Shift\"]\nMouseButton = Literal[\"left\", \"middle\", \"right\"]\n\n\nclass ErrorPayload(TypedDict, total=False):\n    message: str\n    name: str\n    stack: str\n    value: Any\n\n\nclass Header(TypedDict):\n    name: str\n    value: str\n\n\nclass ContinueParameters(TypedDict, total=False):\n    url: Optional[str]\n    method: Optional[str]\n    headers: Optional[List[Header]]\n    postData: Optional[str]\n\n\nclass ParsedMessageParams(TypedDict):\n    type: str\n    guid: str\n    initializer: Dict\n\n\nclass ParsedMessagePayload(TypedDict, total=False):\n    id: int\n    guid: str\n    method: str\n    params: ParsedMessageParams\n    result: Any\n    error: ErrorPayload\n\n\nclass Document(TypedDict):\n    request: Optional[Any]\n\n\nclass FrameNavigatedEvent(TypedDict):\n    url: str\n    name: str\n    newDocument: Optional[Document]\n    error: Optional[str]\n\n\nEnv = Dict[str, Union[str, float, bool]]\n\n\nclass URLMatcher:\n    def __init__(self, match: URLMatch) -> None:\n        self._callback: Optional[Callable[[str], bool]] = None\n        self._regex_obj: Optional[Pattern] = None\n        if isinstance(match, str):\n            regex = fnmatch.translate(match)\n            self._regex_obj = re.compile(regex)\n        elif isinstance(match, Pattern):\n            self._regex_obj = match\n        else:\n            self._callback = match\n        self.match = match\n\n    def matches(self, url: str) -> bool:\n        if self._callback:\n            return self._callback(url)\n        if self._regex_obj:\n            return cast(bool, self._regex_obj.search(url))\n        return False\n\n\nclass TimeoutSettings:\n    def __init__(self, parent: Optional[\"TimeoutSettings\"]) -> None:\n        self._parent = parent\n        self._timeout = 30000.0\n        self._navigation_timeout = 30000.0\n\n    def set_timeout(self, timeout: float) -> None:\n        self._timeout = timeout\n\n    def timeout(self) -> float:\n        if self._timeout is not None:\n            return self._timeout\n        if self._parent:\n            return self._parent.timeout()\n        return 30000\n\n    def set_navigation_timeout(self, navigation_timeout: float) -> None:\n        self._navigation_timeout = navigation_timeout\n\n    def navigation_timeout(self) -> float:\n        if self._navigation_timeout is not None:\n            return self._navigation_timeout\n        if self._parent:\n            return self._parent.navigation_timeout()\n        return 30000\n\n\ndef serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload:\n    return dict(message=str(ex), name=\"Error\", stack=\"\".join(traceback.format_tb(tb)))\n\n\ndef parse_error(error: ErrorPayload) -> Error:\n    base_error_class = Error\n    if error.get(\"name\") == \"TimeoutError\":\n        base_error_class = TimeoutError\n    return base_error_class(\n        cast(str, patch_error_message(error.get(\"message\"))), error[\"stack\"]\n    )\n\n\ndef patch_error_message(message: Optional[str]) -> Optional[str]:\n    if not message:\n        return None\n\n    match = re.match(r\"(\\w+)(: expected .*)\", message)\n    if match:\n        message = to_snake_case(match.group(1)) + match.group(2)\n    message = message.replace(\n        \"Pass { acceptDownloads: true }\", \"Pass { accept_downloads: True }\"\n    )\n    return message\n\n\ndef locals_to_params(args: Dict) -> Dict:\n    copy = {}\n    for key in args:\n        if key == \"self\":\n            continue\n        if args[key] is not None:\n            copy[key] = args[key]\n    return copy\n\n\ndef monotonic_time() -> int:\n    return math.floor(time.monotonic() * 1000)\n\n\nclass RouteHandlerEntry:\n    def __init__(self, matcher: URLMatcher, handler: RouteHandler):\n        self.matcher = matcher\n        self.handler = handler\n\n\ndef is_safe_close_error(error: Exception) -> bool:\n    message = str(error)\n    return message.endswith(\"Browser has been closed\") or message.endswith(\n        \"Target page, context or browser has been closed\"\n    )\n\n\ndef not_installed_error(message: str) -> Exception:\n    return Exception(\n        f\"\"\"\n================================================================================\n{message}\nPlease complete Playwright installation via running\n\n    \"python -m playwright install\"\n\n================================================================================\n\"\"\"\n    )\n\n\nto_snake_case_regex = re.compile(\"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))\")\n\n\ndef to_snake_case(name: str) -> str:\n    return to_snake_case_regex.sub(r\"_\\1\", name).lower()\n", "path": "playwright/_impl/_helper.py"}], "after_files": [{"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pathlib import Path\nfrom typing import Dict, List, Union\n\nfrom playwright._impl._api_structures import (\n    Geolocation,\n    HttpCredentials,\n    ProxySettings,\n    ViewportSize,\n)\nfrom playwright._impl._browser import Browser, normalize_context_params\nfrom playwright._impl._browser_context import BrowserContext\nfrom playwright._impl._connection import ChannelOwner, from_channel\nfrom playwright._impl._helper import (\n    ColorScheme,\n    Env,\n    locals_to_params,\n    not_installed_error,\n)\n\n\nclass BrowserType(ChannelOwner):\n    def __init__(\n        self, parent: ChannelOwner, type: str, guid: str, initializer: Dict\n    ) -> None:\n        super().__init__(parent, type, guid, initializer)\n\n    @property\n    def name(self) -> str:\n        return self._initializer[\"name\"]\n\n    @property\n    def executable_path(self) -> str:\n        return self._initializer[\"executablePath\"]\n\n    async def launch(\n        self,\n        executablePath: Union[str, Path] = None,\n        args: List[str] = None,\n        ignoreDefaultArgs: Union[bool, List[str]] = None,\n        handleSIGINT: bool = None,\n        handleSIGTERM: bool = None,\n        handleSIGHUP: bool = None,\n        timeout: float = None,\n        env: Env = None,\n        headless: bool = None,\n        devtools: bool = None,\n        proxy: ProxySettings = None,\n        downloadsPath: Union[str, Path] = None,\n        slowMo: float = None,\n        chromiumSandbox: bool = None,\n        firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,\n    ) -> Browser:\n        params = locals_to_params(locals())\n        normalize_launch_params(params)\n        try:\n            return from_channel(await self._channel.send(\"launch\", params))\n        except Exception as e:\n            if \"because executable doesn't exist\" in str(e):\n                raise not_installed_error(f'\"{self.name}\" browser was not found.')\n            raise e\n\n    async def launch_persistent_context(\n        self,\n        userDataDir: Union[str, Path],\n        executablePath: Union[str, Path] = None,\n        args: List[str] = None,\n        ignoreDefaultArgs: Union[bool, List[str]] = None,\n        handleSIGINT: bool = None,\n        handleSIGTERM: bool = None,\n        handleSIGHUP: bool = None,\n        timeout: float = None,\n        env: Env = None,\n        headless: bool = None,\n        devtools: bool = None,\n        proxy: ProxySettings = None,\n        downloadsPath: Union[str, Path] = None,\n        slowMo: float = None,\n        viewport: ViewportSize = None,\n        noViewport: bool = None,\n        ignoreHTTPSErrors: bool = None,\n        javaScriptEnabled: bool = None,\n        bypassCSP: bool = None,\n        userAgent: str = None,\n        locale: str = None,\n        timezoneId: str = None,\n        geolocation: Geolocation = None,\n        permissions: List[str] = None,\n        extraHTTPHeaders: Dict[str, str] = None,\n        offline: bool = None,\n        httpCredentials: HttpCredentials = None,\n        deviceScaleFactor: float = None,\n        isMobile: bool = None,\n        hasTouch: bool = None,\n        colorScheme: ColorScheme = None,\n        acceptDownloads: bool = None,\n        chromiumSandbox: bool = None,\n        recordHarPath: Union[Path, str] = None,\n        recordHarOmitContent: bool = None,\n        recordVideoDir: Union[Path, str] = None,\n        recordVideoSize: ViewportSize = None,\n    ) -> BrowserContext:\n        userDataDir = str(Path(userDataDir))\n        params = locals_to_params(locals())\n        normalize_context_params(self._connection._is_sync, params)\n        normalize_launch_params(params)\n        try:\n            context = from_channel(\n                await self._channel.send(\"launchPersistentContext\", params)\n            )\n            context._options = params\n            return context\n        except Exception as e:\n            if \"because executable doesn't exist\" in str(e):\n                raise not_installed_error(f'\"{self.name}\" browser was not found.')\n            raise e\n\n\ndef normalize_launch_params(params: Dict) -> None:\n    if \"env\" in params:\n        params[\"env\"] = {name: str(value) for [name, value] in params[\"env\"].items()}\n    if \"ignoreDefaultArgs\" in params:\n        if params[\"ignoreDefaultArgs\"] is True:\n            params[\"ignoreAllDefaultArgs\"] = True\n            del params[\"ignoreDefaultArgs\"]\n    if \"executablePath\" in params:\n        params[\"executablePath\"] = str(Path(params[\"executablePath\"]))\n    if \"downloadsPath\" in params:\n        params[\"downloadsPath\"] = str(Path(params[\"downloadsPath\"]))\n", "path": "playwright/_impl/_browser_type.py"}, {"content": "# Copyright (c) Microsoft Corporation.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport fnmatch\nimport math\nimport re\nimport sys\nimport time\nimport traceback\nfrom types import TracebackType\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n    Dict,\n    List,\n    Optional,\n    Pattern,\n    Union,\n    cast,\n)\n\nfrom playwright._impl._api_types import Error, TimeoutError\n\nif sys.version_info >= (3, 8):  # pragma: no cover\n    from typing import Literal, TypedDict\nelse:  # pragma: no cover\n    from typing_extensions import Literal, TypedDict\n\n\nif TYPE_CHECKING:  # pragma: no cover\n    from playwright._impl._network import Request, Response, Route\n\nURLMatch = Union[str, Pattern, Callable[[str], bool]]\nURLMatchRequest = Union[str, Pattern, Callable[[\"Request\"], bool]]\nURLMatchResponse = Union[str, Pattern, Callable[[\"Response\"], bool]]\nRouteHandler = Union[Callable[[\"Route\"], Any], Callable[[\"Route\", \"Request\"], Any]]\n\nColorScheme = Literal[\"dark\", \"light\", \"no-preference\"]\nDocumentLoadState = Literal[\"domcontentloaded\", \"load\", \"networkidle\"]\nKeyboardModifier = Literal[\"Alt\", \"Control\", \"Meta\", \"Shift\"]\nMouseButton = Literal[\"left\", \"middle\", \"right\"]\n\n\nclass ErrorPayload(TypedDict, total=False):\n    message: str\n    name: str\n    stack: str\n    value: Any\n\n\nclass Header(TypedDict):\n    name: str\n    value: str\n\n\nclass ContinueParameters(TypedDict, total=False):\n    url: Optional[str]\n    method: Optional[str]\n    headers: Optional[List[Header]]\n    postData: Optional[str]\n\n\nclass ParsedMessageParams(TypedDict):\n    type: str\n    guid: str\n    initializer: Dict\n\n\nclass ParsedMessagePayload(TypedDict, total=False):\n    id: int\n    guid: str\n    method: str\n    params: ParsedMessageParams\n    result: Any\n    error: ErrorPayload\n\n\nclass Document(TypedDict):\n    request: Optional[Any]\n\n\nclass FrameNavigatedEvent(TypedDict):\n    url: str\n    name: str\n    newDocument: Optional[Document]\n    error: Optional[str]\n\n\nEnv = Dict[str, Union[str, float, bool]]\n\n\nclass URLMatcher:\n    def __init__(self, match: URLMatch) -> None:\n        self._callback: Optional[Callable[[str], bool]] = None\n        self._regex_obj: Optional[Pattern] = None\n        if isinstance(match, str):\n            regex = fnmatch.translate(match)\n            self._regex_obj = re.compile(regex)\n        elif isinstance(match, Pattern):\n            self._regex_obj = match\n        else:\n            self._callback = match\n        self.match = match\n\n    def matches(self, url: str) -> bool:\n        if self._callback:\n            return self._callback(url)\n        if self._regex_obj:\n            return cast(bool, self._regex_obj.search(url))\n        return False\n\n\nclass TimeoutSettings:\n    def __init__(self, parent: Optional[\"TimeoutSettings\"]) -> None:\n        self._parent = parent\n        self._timeout = 30000.0\n        self._navigation_timeout = 30000.0\n\n    def set_timeout(self, timeout: float) -> None:\n        self._timeout = timeout\n\n    def timeout(self) -> float:\n        if self._timeout is not None:\n            return self._timeout\n        if self._parent:\n            return self._parent.timeout()\n        return 30000\n\n    def set_navigation_timeout(self, navigation_timeout: float) -> None:\n        self._navigation_timeout = navigation_timeout\n\n    def navigation_timeout(self) -> float:\n        if self._navigation_timeout is not None:\n            return self._navigation_timeout\n        if self._parent:\n            return self._parent.navigation_timeout()\n        return 30000\n\n\ndef serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload:\n    return dict(message=str(ex), name=\"Error\", stack=\"\".join(traceback.format_tb(tb)))\n\n\ndef parse_error(error: ErrorPayload) -> Error:\n    base_error_class = Error\n    if error.get(\"name\") == \"TimeoutError\":\n        base_error_class = TimeoutError\n    return base_error_class(\n        cast(str, patch_error_message(error.get(\"message\"))), error[\"stack\"]\n    )\n\n\ndef patch_error_message(message: Optional[str]) -> Optional[str]:\n    if not message:\n        return None\n\n    match = re.match(r\"(\\w+)(: expected .*)\", message)\n    if match:\n        message = to_snake_case(match.group(1)) + match.group(2)\n    message = message.replace(\n        \"Pass { acceptDownloads: true }\", \"Pass { accept_downloads: True }\"\n    )\n    return message\n\n\ndef locals_to_params(args: Dict) -> Dict:\n    copy = {}\n    for key in args:\n        if key == \"self\":\n            continue\n        if args[key] is not None:\n            copy[key] = args[key]\n    return copy\n\n\ndef monotonic_time() -> int:\n    return math.floor(time.monotonic() * 1000)\n\n\nclass RouteHandlerEntry:\n    def __init__(self, matcher: URLMatcher, handler: RouteHandler):\n        self.matcher = matcher\n        self.handler = handler\n\n\ndef is_safe_close_error(error: Exception) -> bool:\n    message = str(error)\n    return message.endswith(\"Browser has been closed\") or message.endswith(\n        \"Target page, context or browser has been closed\"\n    )\n\n\ndef not_installed_error(message: str) -> Exception:\n    return Error(\n        f\"\"\"\n================================================================================\n{message}\nPlease complete Playwright installation via running\n\n    \"python -m playwright install\"\n\n================================================================================\n\"\"\"\n    )\n\n\nto_snake_case_regex = re.compile(\"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))\")\n\n\ndef to_snake_case(name: str) -> str:\n    return to_snake_case_regex.sub(r\"_\\1\", name).lower()\n", "path": "playwright/_impl/_helper.py"}]}