Skip to content

Commit 77efb65

Browse files
committed
Update v1.52.5
- Introduce Locator.evaluate_all `isolatedContext` option - Introduce Automated Testing
1 parent a9af6d8 commit 77efb65

File tree

5 files changed

+287
-5
lines changed

5 files changed

+287
-5
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Patchright Chromium Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
run-playwright-tests:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout Repository
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: '3.11'
21+
22+
- name: Install Dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install pytest patchright
26+
27+
- name: Install Playwright Browsers
28+
run: |
29+
python -m patchright install --with-deps chromium
30+
31+
- name: Clone Playwright-Python Tests
32+
run: |
33+
git clone --depth 1 https://github.com/microsoft/playwright-python.git
34+
cp -r playwright-python/tests ./tests
35+
36+
- name: Modify Tests
37+
run: |
38+
python utils/modify_tests.py
39+
40+
- name: Run Chromium Tests
41+
run: |
42+
pytest --browser=chromium --disable-warnings --timeout 90 tests/sync/
43+
pytest --browser=chromium --disable-warnings --timeout 90 tests/async/

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,17 @@ object.evaluate_handle(
174174
)
175175
```
176176

177+
#### **`evaluate_all`** Method <sub>([`Frame.evaluate_all`](https://playwright.dev/python/docs/next/api/class-locator#locator-evaluate-all))</sub>
178+
- Added `isolated_context` to choose Execution Context (Main/Isolated). `Bool` (*optional*, Defaults to `True`)
179+
```diff
180+
Frame.evaluate_all(
181+
expression: str,
182+
arg: typing.Optional[typing.Any] = None,
183+
...,
184+
+ isolated_context: typing.Optional[bool] = True
185+
)
186+
```
187+
177188

178189
---
179190

patch_python_package.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import toml
66

77
patchright_version = os.environ.get('playwright_version')
8-
# patchright_version = "1.52.4"
8+
patchright_version = "1.52.5"
99

1010
def patch_file(file_path: str, patched_tree: ast.AST) -> None:
1111
with open(file_path, "w") as f:
@@ -194,7 +194,7 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
194194
frame_tree = ast.parse(frame_source)
195195

196196
for node in ast.walk(frame_tree):
197-
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
197+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle", "eval_on_selector_all"]:
198198
node.args.kwonlyargs.append(ast.arg(
199199
arg="isolatedContext",
200200
annotation=ast.Subscript(
@@ -225,7 +225,7 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
225225
frame_tree = ast.parse(frame_source)
226226

227227
for node in ast.walk(frame_tree):
228-
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
228+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle", "evaluate_all"]:
229229
node.args.kwonlyargs.append(ast.arg(
230230
arg="isolatedContext",
231231
annotation=ast.Subscript(
@@ -250,6 +250,12 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
250250
arg="isolatedContext",
251251
value=ast.Name(id="isolatedContext", ctx=ast.Load())
252252
))
253+
elif call_expr.func.attr == "eval_on_selector_all":
254+
call_expr.keywords.append(ast.keyword(
255+
arg="isolatedContext",
256+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
257+
))
258+
253259

254260
patch_file("playwright-python/playwright/_impl/_locator.py", frame_tree)
255261

@@ -414,7 +420,7 @@ async def route_handler(route: Route) -> None:
414420
for class_node in ast.walk(async_generated_tree):
415421
if isinstance(class_node, ast.ClassDef) and class_node.name in ["Page", "Frame", "Worker", "Locator"]:
416422
for node in class_node.body:
417-
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]: # , "evaluate_all"
423+
if isinstance(node, ast.AsyncFunctionDef) and (node.name in ["evaluate", "evaluate_handle"] or (class_node.name == "Locator" and node.name == "evaluate_all")): # , "evaluate_all"
418424
new_arg = ast.arg(arg="isolated_context", annotation=ast.Subscript(
419425
value=ast.Name(id="typing.Optional", ctx=ast.Load()),
420426
slice=ast.Name(id="bool", ctx=ast.Load()),
@@ -444,7 +450,7 @@ async def route_handler(route: Route) -> None:
444450
for class_node in ast.walk(async_generated_tree):
445451
if isinstance(class_node, ast.ClassDef) and class_node.name in ["Page", "Frame", "Worker", "Locator"]:
446452
for node in class_node.body:
447-
if isinstance(node, ast.FunctionDef) and node.name in ["evaluate", "evaluate_handle"]: # , "evaluate_all"
453+
if isinstance(node, ast.FunctionDef) and (node.name in ["evaluate", "evaluate_handle"] or (class_node.name == "Locator" and node.name == "evaluate_all")): # , "evaluate_all"
448454
new_arg = ast.arg(arg="isolated_context", annotation=ast.Subscript(
449455
value=ast.Name(id="typing.Optional", ctx=ast.Load()),
450456
slice=ast.Name(id="bool", ctx=ast.Load()),

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Only for test Runs
2+
[tool.pytest.ini_options]
3+
addopts = "-Wall -rsx" # -s
4+
markers = [
5+
"skip_browser",
6+
"only_browser",
7+
"skip_platform",
8+
"only_platform"
9+
]
10+
junit_family = "xunit2"
11+
asyncio_mode = "auto"
12+
asyncio_default_fixture_loop_scope = "session"
13+
asyncio_default_test_loop_scope = "session"

utils/modify_tests.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import os
2+
import ast
3+
import itertools
4+
5+
files_to_skip = [
6+
# Console Domain Disabled
7+
'test_console.py',
8+
9+
# query_selector is deprecated
10+
'test_element_handle.py',
11+
'test_element_handle_wait_for_element_state.py',
12+
13+
# https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/31
14+
'test_route_web_socket.py'
15+
]
16+
17+
tests_to_skip = [
18+
# Disabled Console Domain
19+
"test_block_blocks_service_worker_registration",
20+
"test_console_event_should_work",
21+
"test_console_event_should_work_in_popup",
22+
"test_console_event_should_work_in_popup_2",
23+
"test_console_event_should_work_in_immediately_closed_popup",
24+
"test_dialog_event_should_work_in_immdiately_closed_popup",
25+
"test_console_event_should_work_with_context_manager",
26+
"test_weberror_event_should_work",
27+
"test_console_repr",
28+
"test_console_should_work",
29+
"test_should_collect_trace_with_resources_but_no_js",
30+
"test_should_work_with_playwright_context_managers",
31+
"test_should_respect_traces_dir_and_name",
32+
"test_should_show_tracing_group_in_action_list",
33+
"test_page_error_event_should_work",
34+
"test_click_offscreen_buttons",
35+
"test_watch_position_should_be_notified",
36+
"test_page_error_should_fire",
37+
"test_page_error_should_handle_odd_values",
38+
"test_page_error_should_handle_object",
39+
"test_page_error_should_handle_window",
40+
"test_page_error_should_pass_error_name_property",
41+
"test_workers_should_report_console_logs",
42+
"test_workers_should_have_JSHandles_for_console_logs",
43+
"test_workers_should_report_errors",
44+
45+
# InitScript Timing
46+
"test_expose_function_should_be_callable_from_inside_add_init_script",
47+
"test_expose_bindinghandle_should_work",
48+
"test_browser_context_add_init_script_should_apply_to_an_in_process_popup",
49+
"test_should_expose_function_from_browser_context",
50+
51+
# Disable Popup Blocking
52+
"test_page_event_should_have_an_opener",
53+
54+
# query_selector is deprecated
55+
"test_should_work_with_layout_selectors",
56+
"test_should_dispatch_click_event_element_handle",
57+
"test_should_dispatch_drag_and_drop_events_element_handle",
58+
59+
# Minor Differences in Call Log. Deemed Unimportant
60+
"test_should_be_attached_fail_with_not",
61+
"test_add_script_tag_should_include_source_url_when_path_is_provided",
62+
63+
# Server/Client Header Mismatch
64+
"test_should_report_request_headers_array",
65+
"test_request_headers_should_get_the_same_headers_as_the_server_cors",
66+
"test_request_headers_should_get_the_same_headers_as_the_server",
67+
]
68+
69+
dont_isolate_evaluation_tests = [
70+
"test_timeout_waiting_for_stable_position",
71+
"test_jshandle_evaluate_accept_object_handle_as_argument",
72+
"test_jshandle_evaluate_accept_nested_handle",
73+
"test_jshandle_evaluate_accept_nested_window_handle",
74+
"test_jshandle_evaluate_accept_multiple_nested_handles",
75+
"test_should_dispatch_drag_drop_events",
76+
"test_should_dispatch_drag_and_drop_events_element_handle",
77+
"track_events",
78+
"captureLastKeydown",
79+
"test_expose_function_should_work_on_frames_before_navigation",
80+
]
81+
82+
# Directory containing the test files
83+
async_test_dir = 'tests/async'
84+
sync_test_dir = 'tests/sync'
85+
86+
# Reason for skipping tests_backup
87+
skip_reason = "Skipped as per documentation (https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/31)"
88+
89+
90+
class ParentAnnotator(ast.NodeVisitor):
91+
def __init__(self):
92+
self.parents = {}
93+
94+
def visit(self, node):
95+
for child in ast.iter_child_nodes(node):
96+
self.parents[child] = node
97+
self.visit(child)
98+
99+
def process_file(file_path):
100+
with open(file_path, 'r', encoding='utf-8') as f:
101+
source = f.read()
102+
103+
file_tree = ast.parse(source)
104+
annotator = ParentAnnotator()
105+
annotator.visit(file_tree)
106+
107+
for node in ast.walk(file_tree):
108+
# Rename Playwright Imports to Patchright
109+
if isinstance(node, ast.Import):
110+
for alias in node.names:
111+
if alias.name.startswith("playwright"):
112+
alias.name = alias.name.replace("playwright", "patchright", 1)
113+
if isinstance(node, ast.ImportFrom) and node.module.startswith("playwright"):
114+
node.module = node.module.replace("playwright", "patchright", 1)
115+
116+
# Skip Tests Documented: https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/31
117+
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
118+
# Add Server Arg to test_add_init_script Tests
119+
if "test_add_init_script" in file_path:
120+
node.args.args.append(ast.arg(arg='server', annotation=None))
121+
# Skip Tests to skip
122+
if node.name in tests_to_skip:
123+
skip_decorator = ast.parse(f"@pytest.mark.skip(reason='{skip_reason}')\ndef placeholder(): return").body[0].decorator_list[0]
124+
node.decorator_list.insert(0, skip_decorator)
125+
126+
# Add isolated_context=False to every necessary evaluate/evaluate_handle Call
127+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
128+
# Very Bad Hack to get the parent node
129+
test_name = ""
130+
current_node = node
131+
while annotator.parents.get(current_node):
132+
current_node = annotator.parents[current_node]
133+
if isinstance(current_node, ast.FunctionDef) or isinstance(current_node, ast.AsyncFunctionDef):
134+
test_name = current_node.name
135+
136+
if test_name in dont_isolate_evaluation_tests:
137+
# Don't add isolated_context=False to these tests
138+
continue
139+
140+
if node.func.attr in ("evaluate", "evaluate_handle", "evaluate_all") and isinstance(node.func.value, ast.Name) and node.func.value.id in ("page", "popup", "button", "new_page", "page1", "page2", "target", "page_1", "page_2", "frame"):
141+
node.keywords.append(ast.keyword(arg='isolated_context', value=ast.Constant(value=False)))
142+
143+
modified_source = ast.unparse(ast.fix_missing_locations(file_tree))
144+
145+
with open(file_path, 'w', encoding='utf-8') as f:
146+
f.write(modified_source)
147+
148+
def main():
149+
with open("./tests/assets/inject.html", "w") as f:
150+
f.write("<script>window.result = window.injected;</script>")
151+
152+
for root, _, files in itertools.chain(os.walk(sync_test_dir), os.walk(async_test_dir)):
153+
for file in files:
154+
file_path = os.path.join(root, file)
155+
156+
# Init Script Behaviour https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/30
157+
if file == "test_add_init_script.py":
158+
with open(file_path, 'r', encoding='utf-8') as f:
159+
content = f.read()
160+
161+
# Replace the full quoted strings with valid Python expressions (not strings)
162+
content = content.replace(
163+
'"data:text/html,<script>window.result = window.injected</script>"',
164+
'server.PREFIX + "/inject.html"'
165+
).replace(
166+
'"data:text/html,<html></html>"',
167+
'server.PREFIX + "/empty.html"'
168+
)
169+
170+
with open(file_path, 'w', encoding='utf-8') as f:
171+
f.write(content)
172+
173+
# Init Script Behaviour https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/30
174+
if file == "test_page_clock.py":
175+
with open(file_path, 'r', encoding='utf-8') as f:
176+
content = f.read()
177+
178+
# Replace the full quoted strings with valid Python expressions (not strings)
179+
content = content.replace(
180+
"about:blank",
181+
"https://www.google.com/blank.html"
182+
).replace(
183+
"data:text/html,",
184+
"https://www.google.com/blank.html"
185+
)
186+
187+
with open(file_path, 'w', encoding='utf-8') as f:
188+
f.write(content)
189+
190+
# Import Pytest
191+
if file in ("test_browsercontext_service_worker_policy.py", "test_tracing.py", "test_popup.py", "test_dispatch_event.py"):
192+
# Append "import pytest" to the top of the file
193+
with open(file_path, 'r+', encoding='utf-8') as f:
194+
content = f.read()
195+
if "import pytest" not in content:
196+
f.seek(0, 0)
197+
f.write("import pytest\n" + content)
198+
199+
# Skipping Files
200+
if file in files_to_skip:
201+
# Delete File
202+
os.remove(file_path)
203+
continue
204+
205+
if file.endswith('.py'):
206+
process_file(file_path)
207+
208+
if __name__ == '__main__':
209+
main()

0 commit comments

Comments
 (0)