23 | """
24 | )
25 | assert (
26 | await page.eval_on_selector_all(
27 | 'div >> internal:and="span"', "els => els.map(e => e.textContent)"
28 | )
29 | ) == []
30 | assert (
31 | await page.eval_on_selector_all(
32 | 'div >> internal:and=".foo"', "els => els.map(e => e.textContent)"
33 | )
34 | ) == ["hello"]
35 | assert (
36 | await page.eval_on_selector_all(
37 | 'div >> internal:and=".bar"', "els => els.map(e => e.textContent)"
38 | )
39 | ) == ["world"]
40 | assert (
41 | await page.eval_on_selector_all(
42 | 'span >> internal:and="span"', "els => els.map(e => e.textContent)"
43 | )
44 | ) == ["hello2", "world2"]
45 | assert (
46 | await page.eval_on_selector_all(
47 | '.foo >> internal:and="div"', "els => els.map(e => e.textContent)"
48 | )
49 | ) == ["hello"]
50 | assert (
51 | await page.eval_on_selector_all(
52 | '.bar >> internal:and="span"', "els => els.map(e => e.textContent)"
53 | )
54 | ) == ["world2"]
55 |
--------------------------------------------------------------------------------
/tests/async/test_video.py:
--------------------------------------------------------------------------------
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 os
16 | from pathlib import Path
17 | from typing import Dict
18 |
19 | from undetected_playwright.async_api import Browser, BrowserType
20 | from tests.server import Server
21 |
22 |
23 | async def test_should_expose_video_path(
24 | browser: Browser, tmpdir: Path, server: Server
25 | ) -> None:
26 | page = await browser.new_page(record_video_dir=tmpdir)
27 | await page.goto(server.PREFIX + "/grid.html")
28 | assert page.video
29 | path = await page.video.path()
30 | assert str(tmpdir) in str(path)
31 | await page.context.close()
32 |
33 |
34 | async def test_short_video_should_throw(
35 | browser: Browser, tmpdir: Path, server: Server
36 | ) -> None:
37 | page = await browser.new_page(record_video_dir=tmpdir)
38 | await page.goto(server.PREFIX + "/grid.html")
39 | assert page.video
40 | path = await page.video.path()
41 | assert str(tmpdir) in str(path)
42 | await page.wait_for_timeout(1000)
43 | await page.context.close()
44 | assert os.path.exists(path)
45 |
46 |
47 | async def test_short_video_should_throw_persistent_context(
48 | browser_type: BrowserType, tmpdir: Path, launch_arguments: Dict, server: Server
49 | ) -> None:
50 | context = await browser_type.launch_persistent_context(
51 | str(tmpdir),
52 | **launch_arguments,
53 | viewport={"width": 320, "height": 240},
54 | record_video_dir=str(tmpdir) + "1",
55 | )
56 | page = context.pages[0]
57 | await page.goto(server.PREFIX + "/grid.html")
58 | await page.wait_for_timeout(1000)
59 | await context.close()
60 |
61 | assert page.video
62 | path = await page.video.path()
63 | assert str(tmpdir) in str(path)
64 |
65 |
66 | async def test_should_not_error_if_page_not_closed_before_save_as(
67 | browser: Browser, tmpdir: Path, server: Server
68 | ) -> None:
69 | page = await browser.new_page(record_video_dir=tmpdir)
70 | await page.goto(server.PREFIX + "/grid.html")
71 | await page.wait_for_timeout(1000) # make sure video has some data
72 | out_path = tmpdir / "some-video.webm"
73 | assert page.video
74 | saved = page.video.save_as(out_path)
75 | await page.close()
76 | await saved
77 | await page.context.close()
78 | assert os.path.exists(out_path)
79 |
--------------------------------------------------------------------------------
/tests/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/common/__init__.py
--------------------------------------------------------------------------------
/tests/common/test_collect_handles.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/common/test_collect_handles.py
--------------------------------------------------------------------------------
/tests/common/test_events.py:
--------------------------------------------------------------------------------
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 | from typing import Dict
15 |
16 | import pytest
17 |
18 | from undetected_playwright.sync_api import sync_playwright
19 | from tests.server import Server
20 |
21 |
22 | def test_events(browser_name: str, launch_arguments: Dict, server: Server) -> None:
23 | with pytest.raises(Exception, match="fail"):
24 |
25 | def fail() -> None:
26 | raise Exception("fail")
27 |
28 | with sync_playwright() as p:
29 | with p[browser_name].launch(**launch_arguments) as browser:
30 | with browser.new_page() as page:
31 | page.on("response", lambda _: fail())
32 | page.goto(server.PREFIX + "/grid.html")
33 |
--------------------------------------------------------------------------------
/tests/common/test_threads.py:
--------------------------------------------------------------------------------
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 threading
16 | from typing import Dict
17 |
18 | from undetected_playwright.sync_api import sync_playwright
19 |
20 |
21 | def test_running_in_thread(browser_name: str, launch_arguments: Dict) -> None:
22 | result = []
23 |
24 | class TestThread(threading.Thread):
25 | def run(self) -> None:
26 | with sync_playwright() as playwright:
27 | browser = playwright[browser_name].launch(**launch_arguments)
28 | # This should not throw ^^.
29 | browser.new_page()
30 | browser.close()
31 | result.append("Success")
32 |
33 | test_thread = TestThread()
34 | test_thread.start()
35 | test_thread.join()
36 | assert "Success" in result
37 |
--------------------------------------------------------------------------------
/tests/golden-chromium/grid-cell-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/grid-cell-0.png
--------------------------------------------------------------------------------
/tests/golden-chromium/mask-should-work-with-element-handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/mask-should-work-with-element-handle.png
--------------------------------------------------------------------------------
/tests/golden-chromium/mask-should-work-with-locator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/mask-should-work-with-locator.png
--------------------------------------------------------------------------------
/tests/golden-chromium/mask-should-work-with-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/mask-should-work-with-page.png
--------------------------------------------------------------------------------
/tests/golden-chromium/mock-binary-response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/mock-binary-response.png
--------------------------------------------------------------------------------
/tests/golden-chromium/mock-svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/mock-svg.png
--------------------------------------------------------------------------------
/tests/golden-chromium/screenshot-element-bounding-box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/screenshot-element-bounding-box.png
--------------------------------------------------------------------------------
/tests/golden-chromium/screenshot-sanity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-chromium/screenshot-sanity.png
--------------------------------------------------------------------------------
/tests/golden-firefox/grid-cell-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/grid-cell-0.png
--------------------------------------------------------------------------------
/tests/golden-firefox/mask-should-work-with-element-handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/mask-should-work-with-element-handle.png
--------------------------------------------------------------------------------
/tests/golden-firefox/mask-should-work-with-locator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/mask-should-work-with-locator.png
--------------------------------------------------------------------------------
/tests/golden-firefox/mask-should-work-with-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/mask-should-work-with-page.png
--------------------------------------------------------------------------------
/tests/golden-firefox/mock-binary-response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/mock-binary-response.png
--------------------------------------------------------------------------------
/tests/golden-firefox/mock-svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/mock-svg.png
--------------------------------------------------------------------------------
/tests/golden-firefox/screenshot-element-bounding-box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/screenshot-element-bounding-box.png
--------------------------------------------------------------------------------
/tests/golden-firefox/screenshot-sanity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-firefox/screenshot-sanity.png
--------------------------------------------------------------------------------
/tests/golden-webkit/grid-cell-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/grid-cell-0.png
--------------------------------------------------------------------------------
/tests/golden-webkit/mask-should-work-with-element-handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/mask-should-work-with-element-handle.png
--------------------------------------------------------------------------------
/tests/golden-webkit/mask-should-work-with-locator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/mask-should-work-with-locator.png
--------------------------------------------------------------------------------
/tests/golden-webkit/mask-should-work-with-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/mask-should-work-with-page.png
--------------------------------------------------------------------------------
/tests/golden-webkit/mock-binary-response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/mock-binary-response.png
--------------------------------------------------------------------------------
/tests/golden-webkit/mock-svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/mock-svg.png
--------------------------------------------------------------------------------
/tests/golden-webkit/screenshot-element-bounding-box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/screenshot-element-bounding-box.png
--------------------------------------------------------------------------------
/tests/golden-webkit/screenshot-sanity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/golden-webkit/screenshot-sanity.png
--------------------------------------------------------------------------------
/tests/sync/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/tests/sync/__init__.py
--------------------------------------------------------------------------------
/tests/sync/conftest.py:
--------------------------------------------------------------------------------
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 |
16 | from typing import Dict, Generator
17 |
18 | import pytest
19 |
20 | from undetected_playwright.sync_api import (
21 | Browser,
22 | BrowserContext,
23 | BrowserType,
24 | Page,
25 | Playwright,
26 | Selectors,
27 | sync_playwright,
28 | )
29 |
30 | from .utils import Utils
31 | from .utils import utils as utils_object
32 |
33 |
34 | @pytest.fixture
35 | def utils() -> Generator[Utils, None, None]:
36 | yield utils_object
37 |
38 |
39 | @pytest.fixture(scope="session")
40 | def playwright() -> Generator[Playwright, None, None]:
41 | with sync_playwright() as p:
42 | yield p
43 |
44 |
45 | @pytest.fixture(scope="session")
46 | def browser_type(
47 | playwright: Playwright, browser_name: str
48 | ) -> Generator[BrowserType, None, None]:
49 | browser_type = None
50 | if browser_name == "chromium":
51 | browser_type = playwright.chromium
52 | elif browser_name == "firefox":
53 | browser_type = playwright.firefox
54 | elif browser_name == "webkit":
55 | browser_type = playwright.webkit
56 | assert browser_type
57 | yield browser_type
58 |
59 |
60 | @pytest.fixture(scope="session")
61 | def browser(
62 | browser_type: BrowserType, launch_arguments: Dict
63 | ) -> Generator[Browser, None, None]:
64 | browser = browser_type.launch(**launch_arguments)
65 | yield browser
66 | browser.close()
67 |
68 |
69 | @pytest.fixture
70 | def context(browser: Browser) -> Generator[BrowserContext, None, None]:
71 | context = browser.new_context()
72 | yield context
73 | context.close()
74 |
75 |
76 | @pytest.fixture
77 | def page(context: BrowserContext) -> Generator[Page, None, None]:
78 | page = context.new_page()
79 | yield page
80 | page.close()
81 |
82 |
83 | @pytest.fixture(scope="session")
84 | def selectors(playwright: Playwright) -> Selectors:
85 | return playwright.selectors
86 |
--------------------------------------------------------------------------------
/tests/sync/test_browser.py:
--------------------------------------------------------------------------------
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 undetected_playwright.sync_api import Browser, BrowserType
16 |
17 |
18 | def test_should_return_browser_type(
19 | browser: Browser, browser_type: BrowserType
20 | ) -> None:
21 | assert browser.browser_type is browser_type
22 |
--------------------------------------------------------------------------------
/tests/sync/test_browsercontext_service_worker_policy.py:
--------------------------------------------------------------------------------
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 | from undetected_playwright.sync_api import Browser
15 | from tests.server import Server
16 |
17 |
18 | def test_should_allow_service_workers_by_default(
19 | browser: Browser, server: Server
20 | ) -> None:
21 | context = browser.new_context()
22 | page = context.new_page()
23 | page.goto(server.PREFIX + "/serviceworkers/fetchdummy/sw.html")
24 | page.evaluate("() => window.activationPromise")
25 | context.close()
26 |
27 |
28 | def test_block_blocks_service_worker_registration(
29 | browser: Browser, server: Server
30 | ) -> None:
31 | context = browser.new_context(service_workers="block")
32 | page = context.new_page()
33 | with page.expect_console_message(
34 | lambda m: "Service Worker registration blocked by Playwright" == m.text
35 | ):
36 | page.goto(server.PREFIX + "/serviceworkers/fetchdummy/sw.html")
37 | context.close()
38 |
--------------------------------------------------------------------------------
/tests/sync/test_browsertype_connect_cdp.py:
--------------------------------------------------------------------------------
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 typing import Dict
16 |
17 | import pytest
18 |
19 | from undetected_playwright.sync_api import BrowserType
20 | from tests.server import find_free_port
21 |
22 | pytestmark = pytest.mark.only_browser("chromium")
23 |
24 |
25 | def test_connect_to_an_existing_cdp_session(
26 | launch_arguments: Dict, browser_type: BrowserType
27 | ) -> None:
28 | port = find_free_port()
29 | browser_server = browser_type.launch(
30 | **launch_arguments, args=[f"--remote-debugging-port={port}"]
31 | )
32 | cdp_browser = browser_type.connect_over_cdp(f"http://127.0.0.1:{port}")
33 | assert len(cdp_browser.contexts) == 1
34 | cdp_browser.close()
35 | browser_server.close()
36 |
--------------------------------------------------------------------------------
/tests/sync/test_context_manager.py:
--------------------------------------------------------------------------------
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 typing import Dict
16 |
17 | import pytest
18 |
19 | from undetected_playwright.sync_api import BrowserContext, BrowserType
20 |
21 |
22 | def test_context_managers(browser_type: BrowserType, launch_arguments: Dict) -> None:
23 | with browser_type.launch(**launch_arguments) as browser:
24 | with browser.new_context() as context:
25 | with context.new_page():
26 | assert len(context.pages) == 1
27 | assert len(context.pages) == 0
28 | assert len(browser.contexts) == 1
29 | assert len(browser.contexts) == 0
30 | assert not browser.is_connected()
31 |
32 |
33 | def test_context_managers_not_hang(context: BrowserContext) -> None:
34 | with pytest.raises(Exception, match="Oops!"):
35 | with context.new_page():
36 | raise Exception("Oops!")
37 |
--------------------------------------------------------------------------------
/tests/sync/test_expect_misc.py:
--------------------------------------------------------------------------------
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 pytest
16 |
17 | from undetected_playwright.sync_api import Page, expect
18 | from tests.server import Server
19 |
20 |
21 | def test_to_be_in_viewport_should_work(page: Page) -> None:
22 | page.set_content(
23 | """
24 |
26 | """
27 | )
28 | expect(page.locator("#big")).to_be_in_viewport()
29 | expect(page.locator("#small")).not_to_be_in_viewport()
30 | page.locator("#small").scroll_into_view_if_needed()
31 | expect(page.locator("#small")).to_be_in_viewport()
32 | expect(page.locator("#small")).to_be_in_viewport(ratio=1)
33 |
34 |
35 | def test_to_be_in_viewport_should_respect_ratio_option(
36 | page: Page, server: Server
37 | ) -> None:
38 | page.set_content(
39 | """
40 |
41 |
42 | """
43 | )
44 | expect(page.locator("div")).to_be_in_viewport()
45 | expect(page.locator("div")).to_be_in_viewport(ratio=0.1)
46 | expect(page.locator("div")).to_be_in_viewport(ratio=0.2)
47 |
48 | expect(page.locator("div")).to_be_in_viewport(ratio=0.25)
49 | # In this test, element's ratio is 0.25.
50 | expect(page.locator("div")).not_to_be_in_viewport(ratio=0.26)
51 |
52 | expect(page.locator("div")).not_to_be_in_viewport(ratio=0.3)
53 | expect(page.locator("div")).not_to_be_in_viewport(ratio=0.7)
54 | expect(page.locator("div")).not_to_be_in_viewport(ratio=0.8)
55 |
56 |
57 | def test_to_be_in_viewport_should_have_good_stack(page: Page, server: Server) -> None:
58 | with pytest.raises(AssertionError) as exc_info:
59 | expect(page.locator("body")).not_to_be_in_viewport(timeout=100)
60 | assert 'unexpected value "viewport ratio' in str(exc_info.value)
61 |
62 |
63 | def test_to_be_in_viewport_should_report_intersection_even_if_fully_covered_by_other_element(
64 | page: Page, server: Server
65 | ) -> None:
66 | page.set_content(
67 | """
68 |
None:
20 | page.goto(f"{server.PREFIX}/input/textarea.html")
21 | page.fill("textarea", "some value")
22 | assert page.evaluate("result") == "some value"
23 |
24 |
25 | def test_fill_input(page: Page, server: Server) -> None:
26 | page.goto(f"{server.PREFIX}/input/textarea.html")
27 | page.fill("input", "some value")
28 | assert page.evaluate("result") == "some value"
29 |
--------------------------------------------------------------------------------
/tests/sync/test_input.py:
--------------------------------------------------------------------------------
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 os
16 | from pathlib import Path
17 | from typing import Any
18 |
19 | from undetected_playwright.sync_api import Page
20 |
21 |
22 | def test_expect_file_chooser(page: Page) -> None:
23 | page.set_content("
")
24 | with page.expect_file_chooser() as fc_info:
25 | page.click('input[type="file"]')
26 | fc = fc_info.value
27 | fc.set_files(
28 | {"name": "test.txt", "mimeType": "text/plain", "buffer": b"Hello World"}
29 | )
30 |
31 |
32 | def test_set_input_files_should_preserve_last_modified_timestamp(
33 | page: Page,
34 | assetdir: Path,
35 | ) -> None:
36 | page.set_content("
")
37 | input = page.locator("input")
38 | files: Any = ["file-to-upload.txt", "file-to-upload-2.txt"]
39 | input.set_input_files([assetdir / file for file in files])
40 | assert input.evaluate("input => [...input.files].map(f => f.name)") == files
41 | timestamps = input.evaluate("input => [...input.files].map(f => f.lastModified)")
42 | expected_timestamps = [os.path.getmtime(assetdir / file) * 1000 for file in files]
43 |
44 | # On Linux browser sometimes reduces the timestamp by 1ms: 1696272058110.0715 -> 1696272058109 or even
45 | # rounds it to seconds in WebKit: 1696272058110 -> 1696272058000.
46 | for i in range(len(timestamps)):
47 | assert abs(timestamps[i] - expected_timestamps[i]) < 1000
48 |
--------------------------------------------------------------------------------
/tests/sync/test_listeners.py:
--------------------------------------------------------------------------------
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 |
16 | from undetected_playwright.sync_api import Page, Response
17 | from tests.server import Server
18 |
19 |
20 | def test_listeners(page: Page, server: Server) -> None:
21 | log = []
22 |
23 | def print_response(response: Response) -> None:
24 | log.append(response)
25 |
26 | page.on("response", print_response)
27 | page.goto(f"{server.PREFIX}/input/textarea.html")
28 | assert len(log) > 0
29 | page.remove_listener("response", print_response)
30 |
31 | log = []
32 | page.goto(f"{server.PREFIX}/input/textarea.html")
33 | assert len(log) == 0
34 |
--------------------------------------------------------------------------------
/tests/sync/test_page_network_response.py:
--------------------------------------------------------------------------------
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 pytest
16 | from twisted.web import http
17 |
18 | from undetected_playwright.sync_api import Error, Page
19 | from tests.server import Server
20 |
21 |
22 | def test_should_reject_response_finished_if_page_closes(
23 | page: Page, server: Server
24 | ) -> None:
25 | page.goto(server.EMPTY_PAGE)
26 |
27 | def handle_get(request: http.Request) -> None:
28 | # In Firefox, |fetch| will be hanging until it receives |Content-Type| header
29 | # from server.
30 | request.setHeader("Content-Type", "text/plain; charset=utf-8")
31 | request.write(b"hello ")
32 |
33 | server.set_route("/get", handle_get)
34 | # send request and wait for server response
35 | with page.expect_response("**/*") as response_info:
36 | page.evaluate("() => fetch('./get', { method: 'GET' })")
37 | page_response = response_info.value
38 | page.close()
39 | with pytest.raises(Error) as exc_info:
40 | page_response.finished()
41 | error = exc_info.value
42 | assert "closed" in error.message
43 |
44 |
45 | def test_should_reject_response_finished_if_context_closes(
46 | page: Page, server: Server
47 | ) -> None:
48 | page.goto(server.EMPTY_PAGE)
49 |
50 | def handle_get(request: http.Request) -> None:
51 | # In Firefox, |fetch| will be hanging until it receives |Content-Type| header
52 | # from server.
53 | request.setHeader("Content-Type", "text/plain; charset=utf-8")
54 | request.write(b"hello ")
55 |
56 | server.set_route("/get", handle_get)
57 | # send request and wait for server response
58 | with page.expect_response("**/*") as response_info:
59 | page.evaluate("() => fetch('./get', { method: 'GET' })")
60 | page_response = response_info.value
61 |
62 | page.context.close()
63 | with pytest.raises(Error) as exc_info:
64 | page_response.finished()
65 | error = exc_info.value
66 | assert "closed" in error.message
67 |
--------------------------------------------------------------------------------
/tests/sync/test_page_request_intercept.py:
--------------------------------------------------------------------------------
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 pytest
16 |
17 | from undetected_playwright.sync_api import Error, Page, Route
18 | from tests.server import Server, TestServerRequest
19 |
20 |
21 | def test_should_support_timeout_option_in_route_fetch(
22 | server: Server, page: Page
23 | ) -> None:
24 | def _handle(request: TestServerRequest) -> None:
25 | request.responseHeaders.addRawHeader("Content-Length", "4096")
26 | request.responseHeaders.addRawHeader("Content-Type", "text/html")
27 | request.write(b"")
28 |
29 | server.set_route(
30 | "/slow",
31 | _handle,
32 | )
33 |
34 | def handle(route: Route) -> None:
35 | with pytest.raises(Error) as error:
36 | route.fetch(timeout=1000)
37 | assert "Request timed out after 1000ms" in error.value.message
38 |
39 | page.route("**/*", lambda route: handle(route))
40 | with pytest.raises(Error) as error:
41 | page.goto(server.PREFIX + "/slow", timeout=2000)
42 | assert "Timeout 2000ms exceeded" in error.value.message
43 |
44 |
45 | def test_should_intercept_with_url_override(server: Server, page: Page) -> None:
46 | def handle(route: Route) -> None:
47 | response = route.fetch(url=server.PREFIX + "/one-style.html")
48 | route.fulfill(response=response)
49 |
50 | page.route("**/*.html", lambda route: handle(route))
51 | response = page.goto(server.PREFIX + "/empty.html")
52 | assert response
53 | assert response.status == 200
54 | assert "one-style.css" in response.body().decode("utf-8")
55 |
--------------------------------------------------------------------------------
/tests/sync/test_pdf.py:
--------------------------------------------------------------------------------
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 os
16 | from pathlib import Path
17 |
18 | import pytest
19 |
20 | from undetected_playwright.sync_api import Page
21 |
22 |
23 | @pytest.mark.only_browser("chromium")
24 | def test_should_be_able_to_save_pdf_file(page: Page, tmpdir: Path) -> None:
25 | output_file = tmpdir / "foo.png"
26 | page.pdf(path=str(output_file))
27 | assert os.path.getsize(output_file) > 0
28 |
29 |
30 | @pytest.mark.only_browser("chromium")
31 | def test_should_be_able_capture_pdf_without_path(page: Page) -> None:
32 | buffer = page.pdf()
33 | assert buffer
34 |
--------------------------------------------------------------------------------
/tests/sync/test_request_fulfill.py:
--------------------------------------------------------------------------------
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 undetected_playwright.sync_api import Page, Route
16 | from tests.server import Server
17 |
18 |
19 | def test_should_fetch_original_request_and_fulfill(page: Page, server: Server) -> None:
20 | def handle(route: Route) -> None:
21 | response = page.request.fetch(route.request)
22 | route.fulfill(response=response)
23 |
24 | page.route("**/*", handle)
25 | response = page.goto(server.PREFIX + "/title.html")
26 | assert response
27 | assert response.status == 200
28 | assert page.title() == "Woof-Woof"
29 |
30 |
31 | def test_should_fulfill_json(page: Page, server: Server) -> None:
32 | def handle(route: Route) -> None:
33 | route.fulfill(status=201, headers={"foo": "bar"}, json={"bar": "baz"})
34 |
35 | page.route("**/*", handle)
36 |
37 | response = page.goto(server.EMPTY_PAGE)
38 | assert response
39 | assert response.status == 201
40 | assert response.headers["content-type"] == "application/json"
41 | assert response.json() == {"bar": "baz"}
42 |
43 |
44 | def test_should_fulfill_json_overriding_existing_response(
45 | page: Page, server: Server
46 | ) -> None:
47 | server.set_route(
48 | "/tags",
49 | lambda request: (
50 | request.setHeader("foo", "bar"),
51 | request.write('{"tags": ["a", "b"]}'.encode()),
52 | request.finish(),
53 | ),
54 | )
55 |
56 | original = {}
57 |
58 | def handle(route: Route) -> None:
59 | response = route.fetch()
60 | json = response.json()
61 | original["tags"] = json["tags"]
62 | json["tags"] = ["c"]
63 | route.fulfill(response=response, json=json)
64 |
65 | page.route("**/*", handle)
66 |
67 | response = page.goto(server.PREFIX + "/tags")
68 | assert response
69 | assert response.status == 200
70 | assert response.headers["content-type"] == "application/json"
71 | assert response.headers["foo"] == "bar"
72 | assert original["tags"] == ["a", "b"]
73 | assert response.json() == {"tags": ["c"]}
74 |
--------------------------------------------------------------------------------
/tests/sync/test_selectors_misc.py:
--------------------------------------------------------------------------------
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 undetected_playwright.sync_api import Page
16 |
17 |
18 | def test_should_work_with_internal_and(page: Page) -> None:
19 | page.set_content(
20 | """
21 |
hello
world
22 |
hello2world2
23 | """
24 | )
25 | assert (
26 | page.eval_on_selector_all(
27 | 'div >> internal:and="span"', "els => els.map(e => e.textContent)"
28 | )
29 | ) == []
30 | assert (
31 | page.eval_on_selector_all(
32 | 'div >> internal:and=".foo"', "els => els.map(e => e.textContent)"
33 | )
34 | ) == ["hello"]
35 | assert (
36 | page.eval_on_selector_all(
37 | 'div >> internal:and=".bar"', "els => els.map(e => e.textContent)"
38 | )
39 | ) == ["world"]
40 | assert (
41 | page.eval_on_selector_all(
42 | 'span >> internal:and="span"', "els => els.map(e => e.textContent)"
43 | )
44 | ) == ["hello2", "world2"]
45 | assert (
46 | page.eval_on_selector_all(
47 | '.foo >> internal:and="div"', "els => els.map(e => e.textContent)"
48 | )
49 | ) == ["hello"]
50 | assert (
51 | page.eval_on_selector_all(
52 | '.bar >> internal:and="span"', "els => els.map(e => e.textContent)"
53 | )
54 | ) == ["world2"]
55 |
--------------------------------------------------------------------------------
/tests/test_installation.py:
--------------------------------------------------------------------------------
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 os
16 | import shutil
17 | import subprocess
18 | import sys
19 | from pathlib import Path
20 | from venv import EnvBuilder
21 |
22 |
23 | def test_install(tmp_path: Path, browser_name: str) -> None:
24 | env_dir = tmp_path / "env"
25 | env = EnvBuilder(with_pip=True)
26 | env.create(env_dir=env_dir)
27 | context = env.ensure_directories(env_dir)
28 | root = Path(__file__).parent.parent.resolve()
29 | if sys.platform == "win32":
30 | wheelpath = list((root / "dist").glob("undetected_playwright*win_amd64*.whl"))[0]
31 | elif sys.platform == "linux":
32 | wheelpath = list((root / "dist").glob("undetected_playwright*manylinux1*.whl"))[0]
33 | elif sys.platform == "darwin":
34 | wheelpath = list((root / "dist").glob("undetected_playwright*macosx_*.whl"))[0]
35 | subprocess.check_output(
36 | [
37 | context.env_exe,
38 | "-m",
39 | "pip",
40 | "install",
41 | str(wheelpath),
42 | ]
43 | )
44 | environ = os.environ.copy()
45 | environ["PLAYWRIGHT_BROWSERS_PATH"] = str(tmp_path)
46 | subprocess.check_output(
47 | [context.env_exe, "-m", "undetected_playwright", "install", browser_name], env=environ
48 | )
49 | shutil.copyfile(root / "tests" / "assets" / "client.py", tmp_path / "main.py")
50 | subprocess.check_output(
51 | [context.env_exe, str(tmp_path / "main.py"), browser_name], env=environ
52 | )
53 | assert (tmp_path / f"{browser_name}.png").exists()
54 |
--------------------------------------------------------------------------------
/tests/test_reference_count_async.py:
--------------------------------------------------------------------------------
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 gc
16 | from collections import defaultdict
17 | from typing import Any
18 |
19 | import objgraph
20 | import pytest
21 |
22 | from undetected_playwright.async_api import async_playwright
23 | from tests.server import Server
24 |
25 |
26 | @pytest.mark.asyncio
27 | async def test_memory_objects(server: Server, browser_name: str) -> None:
28 | async with async_playwright() as p:
29 | browser = await p[browser_name].launch()
30 | page = await browser.new_page()
31 | await page.goto(server.EMPTY_PAGE)
32 |
33 | page.on("dialog", lambda dialog: dialog.dismiss())
34 | for _ in range(100):
35 | await page.evaluate("""async () => alert()""")
36 |
37 | await page.route("**/*", lambda route, _: route.fulfill(body="OK"))
38 |
39 | def handle_network_response_received(event: Any) -> None:
40 | event["__pw__is_last_network_response_received_event"] = True
41 |
42 | if browser_name == "chromium":
43 | # https://github.com/microsoft/playwright-python/issues/1602
44 | client = await page.context.new_cdp_session(page)
45 | await client.send("Network.enable")
46 |
47 | client.on(
48 | "Network.responseReceived",
49 | handle_network_response_received,
50 | )
51 |
52 | for _ in range(100):
53 | response = await page.evaluate("""async () => (await fetch("/")).text()""")
54 | assert response == "OK"
55 |
56 | await browser.close()
57 |
58 | gc.collect()
59 |
60 | pw_objects: defaultdict = defaultdict(int)
61 | for o in objgraph.by_type("dict"):
62 | name = o.get("_type")
63 | # https://github.com/microsoft/playwright-python/issues/1602
64 | if o.get("__pw__is_last_network_response_received_event", False):
65 | assert False
66 | if not name:
67 | continue
68 | pw_objects[name] += 1
69 |
70 | assert "Dialog" not in pw_objects
71 | assert "Request" not in pw_objects
72 | assert "Route" not in pw_objects
73 |
--------------------------------------------------------------------------------
/tests/testserver/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEsjCCApoCCQCIPLvQDgoZojANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9w
3 | dXBwZXRlZXItdGVzdHMwIBcNMTkwMjEzMTkwNzQzWhgPMzAxODA2MTYxOTA3NDNa
4 | MBoxGDAWBgNVBAMMD3B1cHBldGVlci10ZXN0czCCAiIwDQYJKoZIhvcNAQEBBQAD
5 | ggIPADCCAgoCggIBAJue1yqA4qn0SJR3rgTd6sCYVHMKqUouD0No09H7qf+5ZaIb
6 | 3yGpC5J9Bsf/ZbvD5xpgqbGEYkHj7Qh6Z/cPCSHA+ZpsUzDXVrLFXrdwwiK1FrIS
7 | rDI2RYsiP+e52XPC/acWC/7f+E54C62oMjYojaVaDn8gu06gyS1rXK2JITQ6CrKn
8 | b+PVSkjtPB4ku245u1qCKoblkNEZSkEmw8Csl+gw6ydGqOSQAoo8rsDte5zCMnPX
9 | 7XzL6EhRqpiVx7PCuQWnXhL7j9N214Pit7s7F8TeAA6yZR9oswW+h0dWO+XwocJ1
10 | rwkODXOngbCqO+GUxyuavIl2m0d2MP8n6Wa9RVqYetmPQzafKkR5hjiV4mgCFqNQ
11 | bHMTjI6udcR+h5pYoWKxN9/gJaWwyAAzck0AiMeGVrvKR3JKACqlTMzy/Y30obRF
12 | dddURoFf2wjKJvuTK9hHI7pwM5tlPEwu9bTCWNA6XXs2Bq1f6N2OAKhpKOcihNem
13 | aeGUPmygLPb66z9JO75yZXM+1yk1ScXaNHWZLmluVpEPk7maWULpSpxPAlaN3PmK
14 | 8lEihgfBBovampxZo8SvPEt+g5jGyPq9weNg8ic8476PuRVQdg7D8spVxl6whDlJ
15 | bcFojzgrX70t13jqZOtla4WK1vRnZAGplfoH0i5WvAVw+i5S/OVzsmNDtGFbAgMB
16 | AAEwDQYJKoZIhvcNAQELBQADggIBADUAjA/dH+b5UxDC5SL98w1hphw9PvD1cuGS
17 | sVnKPM236JoTiO3KVfm3NMBfSoBi1hPNkXzqr/R4xbyje4Kc4oYcdjGtpll3T5da
18 | wkx1+qumx6O2mEaOshxh76dfZfZne6SQphQKHw8PD10CfDb/NMnmdEbiOSENSqS4
19 | jGELuGviUl361oCBU45UEN7lfs7ANAhwSZyEO7deroyGdvsxfQUaqQrEQsG30jn3
20 | t0cCamYU6eK3bNR/yNXJrZFv3dzoquRY9H52YtVElRqdAIsNlnbxbqz0cm5xFKFt
21 | YTIrMSO1EvDTbB0PPwC5FJvONHhjwiWzgVXSnZrcs/05TsWWnSHH92S+wGCIBC+0
22 | 6fcSKnjdBn9ks5TrDX0TRY6N890KyDQWxPRhHYrMVpn833WY8y/SguxqiMgLFgMD
23 | WLy6yZzJloW7NgpLGAfMA0nMG1O92hfKmQw82Pyf3SVXGTDiXiEOXn0vN6bsPaV/
24 | 3Ws2LJQECnVfHj3TsuxdtwcO+VGcFCarMOqlhE6IlQzfK8ykYdP6wCkVgXEtiVCR
25 | T1OWUWCFowoFpwBFLf1lA065qsAymddnkrUEOMiScZ/3OZhmd+FvgQ+O0iYuqpeI
26 | xauiQ68+Jb4KjVWnu5QBVq8n1vUJ5+gAzowNMN9G+1+A282Ox23T48dce22BTS6B
27 | 3Taaccm+
28 | -----END CERTIFICATE-----
29 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
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 json
16 | import zipfile
17 | from pathlib import Path
18 | from typing import Any, Dict, List, Optional, Tuple, TypeVar
19 |
20 |
21 | def parse_trace(path: Path) -> Tuple[Dict[str, bytes], List[Any]]:
22 | resources: Dict[str, bytes] = {}
23 | with zipfile.ZipFile(path, "r") as zip:
24 | for name in zip.namelist():
25 | resources[name] = zip.read(name)
26 | action_map: Dict[str, Any] = {}
27 | events: List[Any] = []
28 | for name in ["trace.trace", "trace.network"]:
29 | for line in resources[name].decode().splitlines():
30 | if not line:
31 | continue
32 | event = json.loads(line)
33 | if event["type"] == "before":
34 | event["type"] = "action"
35 | action_map[event["callId"]] = event
36 | events.append(event)
37 | elif event["type"] == "input":
38 | pass
39 | elif event["type"] == "after":
40 | existing = action_map[event["callId"]]
41 | existing["error"] = event.get("error", None)
42 | else:
43 | events.append(event)
44 | return (resources, events)
45 |
46 |
47 | def get_trace_actions(events: List[Any]) -> List[str]:
48 | action_events = sorted(
49 | list(
50 | filter(
51 | lambda e: e["type"] == "action",
52 | events,
53 | )
54 | ),
55 | key=lambda e: e["startTime"],
56 | )
57 | return [e["apiName"] for e in action_events]
58 |
59 |
60 | TARGET_CLOSED_ERROR_MESSAGE = "Target page, context or browser has been closed"
61 |
62 | MustType = TypeVar("MustType")
63 |
64 |
65 | def must(value: Optional[MustType]) -> MustType:
66 | assert value
67 | return value
68 |
--------------------------------------------------------------------------------
/undetected_playwright/__init__.py:
--------------------------------------------------------------------------------
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 | """
16 | Python package `undetected_playwright` is a Python library to automate Chromium,
17 | Firefox and WebKit with a single API. Playwright is built to enable cross-browser
18 | web automation that is ever-green, capable, reliable and fast.
19 | """
20 | __version__ = "1.40.0-1700587210000"
21 |
--------------------------------------------------------------------------------
/undetected_playwright/__main__.py:
--------------------------------------------------------------------------------
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 subprocess
16 | import sys
17 |
18 | from undetected_playwright._impl._driver import compute_driver_executable, get_driver_env
19 |
20 |
21 | def main() -> None:
22 | driver_executable = compute_driver_executable()
23 | completed_process = subprocess.run(
24 | [str(driver_executable), *sys.argv[1:]], env=get_driver_env()
25 | )
26 | sys.exit(completed_process.returncode)
27 |
28 |
29 | if __name__ == "__main__":
30 | main()
31 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/undetected-playwright-python/4b15791d5263b886477c14a56d9bb54c1c375997/undetected_playwright/_impl/__init__.py
--------------------------------------------------------------------------------
/undetected_playwright/_impl/__pyinstaller/__init__.py:
--------------------------------------------------------------------------------
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 os
16 | from typing import List
17 |
18 |
19 | def get_hook_dirs() -> List[str]:
20 | return [os.path.dirname(__file__)]
21 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/__pyinstaller/hook-playwright.async_api.py:
--------------------------------------------------------------------------------
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 PyInstaller.utils.hooks import collect_data_files # type: ignore
16 |
17 | datas = collect_data_files("undetected_playwright")
18 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/__pyinstaller/hook-playwright.sync_api.py:
--------------------------------------------------------------------------------
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 PyInstaller.utils.hooks import collect_data_files # type: ignore
16 |
17 | datas = collect_data_files("undetected_playwright")
18 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/_accessibility.py:
--------------------------------------------------------------------------------
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 typing import Dict, Optional
16 |
17 | from undetected_playwright._impl._connection import Channel
18 | from undetected_playwright._impl._element_handle import ElementHandle
19 | from undetected_playwright._impl._helper import locals_to_params
20 |
21 |
22 | def _ax_node_from_protocol(axNode: Dict) -> Dict:
23 | result = {**axNode}
24 | if "valueNumber" in axNode:
25 | result["value"] = axNode["valueNumber"]
26 | elif "valueString" in axNode:
27 | result["value"] = axNode["valueString"]
28 |
29 | if "checked" in axNode:
30 | result["checked"] = (
31 | True
32 | if axNode.get("checked") == "checked"
33 | else (
34 | False if axNode.get("checked") == "unchecked" else axNode.get("checked")
35 | )
36 | )
37 |
38 | if "pressed" in axNode:
39 | result["pressed"] = (
40 | True
41 | if axNode.get("pressed") == "pressed"
42 | else (
43 | False if axNode.get("pressed") == "released" else axNode.get("pressed")
44 | )
45 | )
46 |
47 | if axNode.get("children"):
48 | result["children"] = list(map(_ax_node_from_protocol, axNode["children"]))
49 | if "valueNumber" in result:
50 | del result["valueNumber"]
51 | if "valueString" in result:
52 | del result["valueString"]
53 | return result
54 |
55 |
56 | class Accessibility:
57 | def __init__(self, channel: Channel) -> None:
58 | self._channel = channel
59 | self._loop = channel._connection._loop
60 | self._dispatcher_fiber = channel._connection._dispatcher_fiber
61 |
62 | async def snapshot(
63 | self, interestingOnly: bool = None, root: ElementHandle = None
64 | ) -> Optional[Dict]:
65 | params = locals_to_params(locals())
66 | if root:
67 | params["root"] = root._channel
68 | result = await self._channel.send("accessibilitySnapshot", params)
69 | return _ax_node_from_protocol(result) if result else None
70 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/_artifact.py:
--------------------------------------------------------------------------------
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 pathlib
16 | from pathlib import Path
17 | from typing import Dict, Optional, Union, cast
18 |
19 | from undetected_playwright._impl._connection import ChannelOwner, from_channel
20 | from undetected_playwright._impl._helper import Error, make_dirs_for_file, patch_error_message
21 | from undetected_playwright._impl._stream import Stream
22 |
23 |
24 | class Artifact(ChannelOwner):
25 | def __init__(
26 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
27 | ) -> None:
28 | super().__init__(parent, type, guid, initializer)
29 | self.absolute_path = initializer["absolutePath"]
30 |
31 | async def path_after_finished(self) -> pathlib.Path:
32 | if self._connection.is_remote:
33 | raise Error(
34 | "Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
35 | )
36 | path = await self._channel.send("pathAfterFinished")
37 | return pathlib.Path(path)
38 |
39 | async def save_as(self, path: Union[str, Path]) -> None:
40 | stream = cast(Stream, from_channel(await self._channel.send("saveAsStream")))
41 | make_dirs_for_file(path)
42 | await stream.save_as(path)
43 |
44 | async def failure(self) -> Optional[str]:
45 | return patch_error_message(await self._channel.send("failure"))
46 |
47 | async def delete(self) -> None:
48 | await self._channel.send("delete")
49 |
50 | async def read_info_buffer(self) -> bytes:
51 | stream = cast(Stream, from_channel(await self._channel.send("stream")))
52 | buffer = await stream.read_all()
53 | return buffer
54 |
55 | async def cancel(self) -> None:
56 | await self._channel.send("cancel")
57 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/_cdp_session.py:
--------------------------------------------------------------------------------
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 typing import Any, Dict
16 |
17 | from undetected_playwright._impl._connection import ChannelOwner
18 | from undetected_playwright._impl._helper import locals_to_params
19 |
20 |
21 | class CDPSession(ChannelOwner):
22 | def __init__(
23 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
24 | ) -> None:
25 | super().__init__(parent, type, guid, initializer)
26 | self._channel.on("event", lambda params: self._on_event(params))
27 |
28 | def _on_event(self, params: Any) -> None:
29 | self.emit(params["method"], params["params"])
30 |
31 | async def send(self, method: str, params: Dict = None) -> Dict:
32 | return await self._channel.send("send", locals_to_params(locals()))
33 |
34 | async def detach(self) -> None:
35 | await self._channel.send("detach")
36 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/_console_message.py:
--------------------------------------------------------------------------------
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 asyncio import AbstractEventLoop
16 | from typing import TYPE_CHECKING, Any, Dict, List, Optional
17 |
18 | from undetected_playwright._impl._api_structures import SourceLocation
19 | from undetected_playwright._impl._connection import from_channel, from_nullable_channel
20 | from undetected_playwright._impl._js_handle import JSHandle
21 |
22 | if TYPE_CHECKING: # pragma: no cover
23 | from undetected_playwright._impl._page import Page
24 |
25 |
26 | class ConsoleMessage:
27 | def __init__(
28 | self, event: Dict, loop: AbstractEventLoop, dispatcher_fiber: Any
29 | ) -> None:
30 | self._event = event
31 | self._loop = loop
32 | self._dispatcher_fiber = dispatcher_fiber
33 | self._page: Optional["Page"] = from_nullable_channel(event.get("page"))
34 |
35 | def __repr__(self) -> str:
36 | return f"
"
37 |
38 | def __str__(self) -> str:
39 | return self.text
40 |
41 | @property
42 | def type(self) -> str:
43 | return self._event["type"]
44 |
45 | @property
46 | def text(self) -> str:
47 | return self._event["text"]
48 |
49 | @property
50 | def args(self) -> List[JSHandle]:
51 | return list(map(from_channel, self._event["args"]))
52 |
53 | @property
54 | def location(self) -> SourceLocation:
55 | return self._event["location"]
56 |
57 | @property
58 | def page(self) -> Optional["Page"]:
59 | return self._page
60 |
--------------------------------------------------------------------------------
/undetected_playwright/_impl/_dialog.py:
--------------------------------------------------------------------------------
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 typing import TYPE_CHECKING, Dict, Optional
16 |
17 | from undetected_playwright._impl._connection import ChannelOwner, from_nullable_channel
18 | from undetected_playwright._impl._helper import locals_to_params
19 |
20 | if TYPE_CHECKING: # pragma: no cover
21 | from undetected_playwright._impl._page import Page
22 |
23 |
24 | class Dialog(ChannelOwner):
25 | def __init__(
26 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
27 | ) -> None:
28 | super().__init__(parent, type, guid, initializer)
29 | self._page: Optional["Page"] = from_nullable_channel(initializer.get("page"))
30 |
31 | def __repr__(self) -> str:
32 | return f"