Skip to content

Commit 0ba931e

Browse files
committed
[Feature] Implement Option to choose Execution Context (Main/Isolated).
1 parent 4c195c1 commit 0ba931e

File tree

2 files changed

+204
-1
lines changed

2 files changed

+204
-1
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,30 @@ Patchright passes:
141141
## Documentation and API Reference
142142
See the original [Playwright Documentation](https://playwright.dev/python/docs/intro) and [API Reference](https://playwright.dev/python/docs/api/class-playwright)
143143

144+
## Extended Patchright API
145+
#### **`evaluate`** Method <sub>([`Frame.evaluate`](https://playwright.dev/python/docs/api/class-frame#frame-evaluate), [`Page.evaluate`](https://playwright.dev/python/docs/api/class-page#page-evaluate), [`Locator.evaluate`](https://playwright.dev/python/docs/api/class-locator#locator-evaluate), [`Worker.evaluate`](https://playwright.dev/python/docs/api/class-worker#worker-evaluate))</sub>
146+
- Added `isolated_context` to choose Execution Context (Main/Isolated). `Bool` (*optional*, Defaults to `True`)
147+
```diff
148+
object.evaluate(
149+
expression: str,
150+
arg: typing.Optional[typing.Any] = None,
151+
...,
152+
+ isolated_context: typing.Optional[bool] = True
153+
)
154+
```
155+
156+
#### **`evaluate_handle`** Method <sub>([`Frame.evaluate_handle`](https://playwright.dev/python/docs/api/class-frame#frame-evaluate-handle), [`Page.evaluate_handle`](https://playwright.dev/python/docs/api/class-page#page-evaluate-handle), [`Locator.evaluate_handle`](https://playwright.dev/python/docs/api/class-locator#locator-evaluate-handle), [`Worker.evaluate_handle`](https://playwright.dev/python/docs/api/class-worker#worker-evaluate-handle))</sub>
157+
- Added `isolated_context` to choose Execution Context (Main/Isolated). `Bool` (*optional*, Defaults to `True`)
158+
```diff
159+
object.evaluate_handle(
160+
expression: str,
161+
arg: typing.Optional[typing.Any] = None,
162+
...,
163+
+ isolated_context: typing.Optional[bool] = True
164+
)
165+
```
166+
167+
144168
---
145169

146170
## Bugs

patch_python_package.py

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
3737
with open("playwright-python/pyproject.toml", "w") as f:
3838
toml.dump(pyproject_source, f)
3939

40-
4140
# Patching setup.py
4241
with open("playwright-python/setup.py") as f:
4342
setup_source = f.read()
@@ -163,8 +162,96 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
163162
if isinstance(function_node, ast.Return):
164163
function_node.value = ast.Name(id="source", ctx=ast.Load())
165164

165+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
166+
node.args.kwonlyargs.append(ast.arg(
167+
arg="isolatedContext",
168+
annotation=ast.Subscript(
169+
value=ast.Name(id="Optional", ctx=ast.Load()),
170+
slice=ast.Name(id="bool", ctx=ast.Load()),
171+
ctx=ast.Load(),
172+
),
173+
))
174+
node.args.kw_defaults.append(ast.Constant(value=True))
175+
176+
for subnode in ast.walk(node):
177+
if isinstance(subnode, ast.Return) and isinstance(subnode.value, ast.Call):
178+
if subnode.value.args and isinstance(subnode.value.args[0], ast.Await):
179+
inner_call = subnode.value.args[0].value
180+
if isinstance(inner_call, ast.Call) and inner_call.func.attr == "send":
181+
for i, arg in enumerate(inner_call.args):
182+
if isinstance(arg, ast.Call) and arg.func.id == "dict":
183+
arg.keywords.append(ast.keyword(
184+
arg="isolatedContext",
185+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
186+
))
187+
166188
patch_file("playwright-python/playwright/_impl/_js_handle.py", js_handle_tree)
167189

190+
# Patching playwright/_impl/_frame.py
191+
with open("playwright-python/playwright/_impl/_frame.py") as f:
192+
frame_source = f.read()
193+
frame_tree = ast.parse(frame_source)
194+
195+
for node in ast.walk(frame_tree):
196+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
197+
node.args.kwonlyargs.append(ast.arg(
198+
arg="isolatedContext",
199+
annotation=ast.Subscript(
200+
value=ast.Name(id="Optional", ctx=ast.Load()),
201+
slice=ast.Name(id="bool", ctx=ast.Load()),
202+
ctx=ast.Load(),
203+
),
204+
))
205+
node.args.kw_defaults.append(ast.Constant(value=True))
206+
207+
for subnode in ast.walk(node):
208+
if isinstance(subnode, ast.Return) and isinstance(subnode.value, ast.Call):
209+
if subnode.value.args and isinstance(subnode.value.args[0], ast.Await):
210+
inner_call = subnode.value.args[0].value
211+
if isinstance(inner_call, ast.Call) and inner_call.func.attr == "send":
212+
for i, arg in enumerate(inner_call.args):
213+
if isinstance(arg, ast.Call) and arg.func.id == "dict":
214+
arg.keywords.append(ast.keyword(
215+
arg="isolatedContext",
216+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
217+
))
218+
219+
patch_file("playwright-python/playwright/_impl/_frame.py", frame_tree)
220+
221+
# Patching playwright/_impl/_locator.py
222+
with open("playwright-python/playwright/_impl/_locator.py") as f:
223+
frame_source = f.read()
224+
frame_tree = ast.parse(frame_source)
225+
226+
for node in ast.walk(frame_tree):
227+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
228+
node.args.kwonlyargs.append(ast.arg(
229+
arg="isolatedContext",
230+
annotation=ast.Subscript(
231+
value=ast.Name(id="Optional", ctx=ast.Load()),
232+
slice=ast.Name(id="bool", ctx=ast.Load()),
233+
ctx=ast.Load(),
234+
),
235+
))
236+
node.args.kw_defaults.append(ast.Constant(value=True))
237+
238+
for subnode in ast.walk(node):
239+
if isinstance(subnode, ast.Return) and isinstance(subnode.value, ast.Await):
240+
call_expr = subnode.value.value
241+
if isinstance(call_expr, ast.Call):
242+
if node.name in ["evaluate", "evaluate_handle"] and isinstance(call_expr.func, ast.Attribute):
243+
if call_expr.func.attr == "_with_element":
244+
if call_expr.args and isinstance(call_expr.args[0], ast.Lambda):
245+
lambda_func = call_expr.args[0].body
246+
if isinstance(lambda_func, ast.Call) and isinstance(lambda_func.func, ast.Attribute):
247+
if lambda_func.func.attr == node.name:
248+
lambda_func.keywords.append(ast.keyword(
249+
arg="isolatedContext",
250+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
251+
))
252+
253+
patch_file("playwright-python/playwright/_impl/_locator.py", frame_tree)
254+
168255
# Patching playwright/_impl/_browser_context.py
169256
with open("playwright-python/playwright/_impl/_browser_context.py") as f:
170257
browser_context_source = f.read()
@@ -271,6 +358,38 @@ async def route_handler(route: Route) -> None:
271358
await self.route("**/*", mapping.wrap_handler(route_handler))
272359
self.route_injecting = True""").body[0])
273360

361+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]:
362+
node.args.kwonlyargs.append(ast.arg(
363+
arg="isolatedContext",
364+
annotation=ast.Subscript(
365+
value=ast.Name(id="Optional", ctx=ast.Load()),
366+
slice=ast.Name(id="bool", ctx=ast.Load()),
367+
ctx=ast.Load(),
368+
),
369+
))
370+
node.args.kw_defaults.append(ast.Constant(value=True))
371+
372+
if "evaluateExpression" in ast.unparse(node.body[0]):
373+
for subnode in ast.walk(node):
374+
if isinstance(subnode, ast.Return) and isinstance(subnode.value, ast.Call):
375+
if subnode.value.args and isinstance(subnode.value.args[0], ast.Await):
376+
inner_call = subnode.value.args[0].value
377+
if isinstance(inner_call, ast.Call) and inner_call.func.attr == "send":
378+
for i, arg in enumerate(inner_call.args):
379+
if isinstance(arg, ast.Call) and arg.func.id == "dict":
380+
arg.keywords.append(ast.keyword(
381+
arg="isolatedContext",
382+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
383+
))
384+
elif "_main_frame" in ast.unparse(node.body[0]):
385+
for subnode in ast.walk(node):
386+
if isinstance(subnode, ast.Return) and isinstance(subnode.value, ast.Await) and isinstance(subnode.value.value, ast.Call):
387+
subnode.value.value.keywords.append(ast.keyword(
388+
arg="isolatedContext",
389+
value=ast.Name(id="isolatedContext", ctx=ast.Load())
390+
))
391+
392+
274393
patch_file("playwright-python/playwright/_impl/_page.py", page_tree)
275394

276395
# Patching playwright/_impl/_clock.py
@@ -286,6 +405,66 @@ async def route_handler(route: Route) -> None:
286405

287406
patch_file("playwright-python/playwright/_impl/_clock.py", clock_tree)
288407

408+
# Patching playwright/async_api/_generated.py
409+
with open("playwright-python/playwright/async_api/_generated.py") as f:
410+
async_generated_source = f.read()
411+
async_generated_tree = ast.parse(async_generated_source)
412+
413+
for class_node in ast.walk(async_generated_tree):
414+
if isinstance(class_node, ast.ClassDef) and class_node.name in ["Page", "Frame", "Worker", "Locator"]:
415+
for node in class_node.body:
416+
if isinstance(node, ast.AsyncFunctionDef) and node.name in ["evaluate", "evaluate_handle"]: # , "evaluate_all"
417+
new_arg = ast.arg(arg="isolated_context", annotation=ast.Subscript(
418+
value=ast.Name(id="typing.Optional", ctx=ast.Load()),
419+
slice=ast.Name(id="bool", ctx=ast.Load()),
420+
ctx=ast.Load(),
421+
))
422+
# Append the argument to kwonlyargs
423+
node.args.kwonlyargs.append(new_arg)
424+
node.args.kw_defaults.append(ast.Constant(value=True))
425+
426+
# Modify the inner function call inside return statement
427+
for subnode in ast.walk(node):
428+
if isinstance(subnode, ast.Call) and subnode.func.attr == node.name:
429+
subnode.keywords.append(
430+
ast.keyword(arg="isolatedContext",
431+
value=ast.Name(id="isolated_context",
432+
ctx=ast.Load())
433+
)
434+
)
435+
436+
patch_file("playwright-python/playwright/async_api/_generated.py", async_generated_tree)
437+
438+
# Patching playwright/sync_api/_generated.py
439+
with open("playwright-python/playwright/sync_api/_generated.py") as f:
440+
async_generated_source = f.read()
441+
async_generated_tree = ast.parse(async_generated_source)
442+
443+
for class_node in ast.walk(async_generated_tree):
444+
if isinstance(class_node, ast.ClassDef) and class_node.name in ["Page", "Frame", "Worker", "Locator"]:
445+
for node in class_node.body:
446+
if isinstance(node, ast.FunctionDef) and node.name in ["evaluate", "evaluate_handle"]: # , "evaluate_all"
447+
new_arg = ast.arg(arg="isolated_context", annotation=ast.Subscript(
448+
value=ast.Name(id="typing.Optional", ctx=ast.Load()),
449+
slice=ast.Name(id="bool", ctx=ast.Load()),
450+
ctx=ast.Load(),
451+
))
452+
# Append the argument to kwonlyargs
453+
node.args.kwonlyargs.append(new_arg)
454+
node.args.kw_defaults.append(ast.Constant(value=True))
455+
456+
# Modify the inner function call inside return statement
457+
for subnode in ast.walk(node):
458+
if isinstance(subnode, ast.Call) and subnode.func.attr == node.name:
459+
subnode.keywords.append(
460+
ast.keyword(arg="isolatedContext",
461+
value=ast.Name(id="isolated_context",
462+
ctx=ast.Load())
463+
)
464+
)
465+
466+
patch_file("playwright-python/playwright/sync_api/_generated.py", async_generated_tree)
467+
289468
# Patching Imports of every python file under the playwright-python/playwright directory
290469
for python_file in glob.glob("playwright-python/playwright/**.py") + glob.glob("playwright-python/playwright/**/**.py"):
291470
with open(python_file) as f:

0 commit comments

Comments
 (0)