Skip to content

Commit 82885a1

Browse files
authored
fix(transport): handle connection error correctly (microsoft#650) (microsoft#651)
1 parent 5d976dc commit 82885a1

File tree

5 files changed

+86
-16
lines changed

5 files changed

+86
-16
lines changed

playwright/_impl/_browser_type.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import asyncio
1516
from pathlib import Path
1617
from typing import Dict, List, Optional, Union, cast
1718

@@ -179,11 +180,24 @@ async def connect(
179180
self._connection._object_factory,
180181
transport,
181182
)
183+
await connection._transport.start()
182184
connection._is_sync = self._connection._is_sync
183185
connection._loop = self._connection._loop
184186
connection._loop.create_task(connection.run())
187+
obj = asyncio.create_task(
188+
connection.wait_for_object_with_known_name("Playwright")
189+
)
190+
done, pending = await asyncio.wait(
191+
{
192+
obj,
193+
connection._transport.on_error_future, # type: ignore
194+
},
195+
return_when=asyncio.FIRST_COMPLETED,
196+
)
197+
if not obj.done():
198+
obj.cancel()
199+
playwright = next(iter(done)).result()
185200
self._connection._child_ws_connections.append(connection)
186-
playwright = await connection.wait_for_object_with_known_name("Playwright")
187201
pre_launched_browser = playwright._initializer.get("preLaunchedBrowser")
188202
assert pre_launched_browser
189203
browser = cast(Browser, from_channel(pre_launched_browser))

playwright/_impl/_transport.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def _get_stderr_fileno() -> Optional[int]:
4242

4343
class Transport(ABC):
4444
def __init__(self) -> None:
45+
self.on_error_future: asyncio.Future
4546
self.on_message = lambda _: None
4647

4748
@abstractmethod
@@ -55,9 +56,14 @@ def dispose(self) -> None:
5556
async def wait_until_stopped(self) -> None:
5657
pass
5758

58-
async def run(self) -> None:
59+
async def start(self) -> None:
60+
if not hasattr(self, "on_error_future"):
61+
self.on_error_future = asyncio.Future()
5962
self._loop = asyncio.get_running_loop()
60-
self.on_error_future: asyncio.Future = asyncio.Future()
63+
64+
@abstractmethod
65+
async def run(self) -> None:
66+
pass
6167

6268
@abstractmethod
6369
def send(self, message: Dict) -> None:
@@ -93,17 +99,28 @@ async def wait_until_stopped(self) -> None:
9399
await self._proc.wait()
94100

95101
async def run(self) -> None:
96-
await super().run()
102+
await self.start()
97103
self._stopped_future: asyncio.Future = asyncio.Future()
98104

99-
self._proc = proc = await asyncio.create_subprocess_exec(
100-
str(self._driver_executable),
101-
"run-driver",
102-
stdin=asyncio.subprocess.PIPE,
103-
stdout=asyncio.subprocess.PIPE,
104-
stderr=_get_stderr_fileno(),
105-
limit=32768,
106-
)
105+
try:
106+
self._proc = proc = await asyncio.create_subprocess_exec(
107+
str(self._driver_executable),
108+
"run-driver",
109+
stdin=asyncio.subprocess.PIPE,
110+
stdout=asyncio.subprocess.PIPE,
111+
stderr=_get_stderr_fileno(),
112+
limit=32768,
113+
)
114+
except FileNotFoundError:
115+
self.on_error_future.set_exception(
116+
Error(
117+
"playwright's driver is not found, You can read the contributing guide "
118+
"for some guidance on how to get everything setup for working on the code "
119+
"https://github.com/microsoft/playwright-python/blob/master/CONTRIBUTING.md"
120+
)
121+
)
122+
return
123+
107124
assert proc.stdout
108125
assert proc.stdin
109126
self._output = proc.stdin
@@ -160,15 +177,22 @@ async def wait_until_stopped(self) -> None:
160177
await self._connection.wait_closed()
161178

162179
async def run(self) -> None:
163-
await super().run()
180+
await self.start()
164181

165182
options: Dict[str, Any] = {}
166183
if self.timeout is not None:
167184
options["close_timeout"] = self.timeout / 1000
168185
options["ping_timeout"] = self.timeout / 1000
186+
169187
if self.headers is not None:
170188
options["extra_headers"] = self.headers
171-
self._connection = await websockets.connect(self.ws_endpoint, **options)
189+
try:
190+
self._connection = await websockets.connect(self.ws_endpoint, **options)
191+
except Exception as err:
192+
self.on_error_future.set_exception(
193+
Error(f"playwright's websocket endpoint connection error: {err}")
194+
)
195+
return
172196

173197
while not self._stopped:
174198
try:

playwright/async_api/_context_manager.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,22 @@ async def __aenter__(self) -> AsyncPlaywright:
3232
)
3333
loop = asyncio.get_running_loop()
3434
self._connection._loop = loop
35+
obj = asyncio.create_task(
36+
self._connection.wait_for_object_with_known_name("Playwright")
37+
)
38+
await self._connection._transport.start()
3539
loop.create_task(self._connection.run())
36-
playwright = AsyncPlaywright(
37-
await self._connection.wait_for_object_with_known_name("Playwright")
40+
done, pending = await asyncio.wait(
41+
{
42+
obj,
43+
self._connection._transport.on_error_future, # type: ignore
44+
},
45+
return_when=asyncio.FIRST_COMPLETED,
3846
)
47+
if not obj.done():
48+
obj.cancel()
49+
obj = next(iter(done)).result()
50+
playwright = AsyncPlaywright(obj) # type: ignore
3951
playwright.stop = self.__aexit__ # type: ignore
4052
return playwright
4153

tests/async/test_browsertype_connect.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,13 @@ async def test_prevent_getting_video_path(
182182
== "Path is not available when using browserType.connect(). Use save_as() to save a local copy."
183183
)
184184
remote_server.kill()
185+
186+
187+
async def test_connect_to_closed_server_without_hangs(
188+
browser_type: BrowserType, launch_server
189+
):
190+
remote_server = launch_server()
191+
remote_server.kill()
192+
with pytest.raises(Error) as exc:
193+
await browser_type.connect(remote_server.ws_endpoint)
194+
assert "playwright's websocket endpoint connection error" in exc.value.message

tests/sync/test_browsertype_connect.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,13 @@ def test_browser_type_connect_should_forward_close_events_to_pages(
145145
assert events == ["page::close", "context::close", "browser::disconnected"]
146146
remote.kill()
147147
assert events == ["page::close", "context::close", "browser::disconnected"]
148+
149+
150+
def test_connect_to_closed_server_without_hangs(
151+
browser_type: BrowserType, launch_server
152+
):
153+
remote_server = launch_server()
154+
remote_server.kill()
155+
with pytest.raises(Error) as exc:
156+
browser_type.connect(remote_server.ws_endpoint)
157+
assert "playwright's websocket endpoint connection error" in exc.value.message

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy