Repository: microsoft/playwright-python

Issue:
[BUG] Execution hangs when trying to save video or delete video before calling page.close()
**Context:**
- Playwright Version: 1.23
- Operating System: Windows
- Python: 3.9
- Browser: All

**Code Snippet**

```from playwright.sync_api import Playwright, sync_playwright


def run(playwright: Playwright) -> None:
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            record_video_dir="temp_videos/",
            record_video_size={"width": 1920, "height": 1080})

    # Open new page
    page = context.new_page()

    # ---------------------
    # page.video.save_as("test.webm")
    # OR
    # page.video.delete()
    context.close()
    browser.close()


with sync_playwright() as playwright:
    run(playwright)
```

**Describe the bug**

Execution will hang, no stack trace will be produced when user tries to save video or delete video before closing the page (page.close)

Uncomment line 15 or 17 to reproduce

The docs for save_as suggest that it should be possible:
"Saves the video to a user-specified path. It is safe to call this method while the video is still in progress, or after the page has closed. "

Still in progress suggests that I do not need to page.close() first


Discussion:
I think this is specific to sync python. Calling `save_as()` is safe, but it will not resolve until the page is closed. We should update the documentation with a good example to make this clear.
You can close the page beforehand and then it should work.

---

we'll follow up with docs changes and throw if its sync python.



Resolving patch:
diff --git a/playwright/_impl/_video.py b/playwright/_impl/_video.py
--- a/playwright/_impl/_video.py
+++ b/playwright/_impl/_video.py
@@ -55,6 +55,10 @@ async def path(self) -> pathlib.Path:
         return artifact.absolute_path
 
     async def save_as(self, path: Union[str, pathlib.Path]) -> None:
+        if self._page._connection._is_sync and not self._page._is_closed:
+            raise Error(
+                "Page is not yet closed. Close the page prior to calling save_as"
+            )
         artifact = await self._artifact_future
         if not artifact:
             raise Error("Page did not produce any video frames")


Test patch:
diff --git a/tests/async/test_video.py b/tests/async/test_video.py
--- a/tests/async/test_video.py
+++ b/tests/async/test_video.py
@@ -49,3 +49,16 @@ async def test_short_video_should_throw_persistent_context(
 
     path = await page.video.path()
     assert str(tmpdir) in str(path)
+
+
+async def test_should_not_error_if_page_not_closed_before_save_as(
+    browser, tmpdir, server
+):
+    page = await browser.new_page(record_video_dir=tmpdir)
+    await page.goto(server.PREFIX + "/grid.html")
+    out_path = tmpdir / "some-video.webm"
+    saved = page.video.save_as(out_path)
+    await page.close()
+    await saved
+    await page.context.close()
+    assert os.path.exists(out_path)
diff --git a/tests/sync/test_video.py b/tests/sync/test_video.py
--- a/tests/sync/test_video.py
+++ b/tests/sync/test_video.py
@@ -16,7 +16,9 @@
 from pathlib import Path
 from typing import Dict
 
-from playwright.sync_api import Browser, BrowserType
+import pytest
+
+from playwright.sync_api import Browser, BrowserType, Error
 from tests.server import Server
 
 
@@ -91,3 +93,21 @@ def test_record_video_can_get_video_path_immediately(
     page.wait_for_timeout(1000)
     context.close()
     assert os.path.exists(path)
+
+
+def test_should_error_if_page_not_closed_before_save_as(
+    browser: Browser, tmpdir: Path, server: Server
+) -> None:
+    page = browser.new_page(record_video_dir=tmpdir)
+    page.goto(server.PREFIX + "/grid.html")
+    out_path = tmpdir / "some-video.webm"
+    with pytest.raises(Error) as err:
+        video = page.video
+        assert video
+        video.save_as(out_path)
+    assert "Page is not yet closed. Close the page prior to calling save_as" in str(err)
+    assert not os.path.exists(out_path)
+    page.context.close()
+
+    video.save_as(out_path)
+    assert os.path.exists(out_path)