'Evaluate before frame renders on playwright

I have a series of async page.evaluate() functions that I want to run before each page starts executing its own javascript. The page class provides something similar to inject a javascript file to each page / before before the client code is executed via page.add_init_script(script[, arg])). I'm trying to replicate that same logic via pure python.

A naive hook into frameattached and framenavigated works if the function quickly executes, but as soon as the async is actually awaiting it continues to evaluate the page script. Is there a way to block the frame from processing client-side javascript until handle_frame has returned?

Working code (sans race condition):

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()

        async def handle_frame(frame):
            await sleep(4)
            await frame.evaluate("Math.random = () => 42;")

        page.on("frameattached", handle_frame)
        page.on("framenavigated", handle_frame)

        await page.goto("about:blank")

        random_value = await page.evaluate("Math.random()")
        print(random_value)  # == 42

Non-Working code:

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()

        async def handle_frame(frame):
            await frame.evaluate("Math.random = () => 42;")

        page.on("frameattached", handle_frame)
        page.on("framenavigated", handle_frame)

        await page.goto("about:blank")

        random_value = await page.evaluate("Math.random()")
        print(random_value)  # == 0.63 ie. random


Solution 1:[1]

Is there a way to block the frame from processing client-side javascript until handle_frame has returned?

After some research, I think there isn't. Depending on where the scripts are on your page, the browser will execute them before HTML parsing is complete. The only way to be sure to execute javascript from python before client-side scripts is with the page.add_init_script function you mentioned (or maybe with some external library, but this is not the point). As the manual points out:

Playwright scripts run in your Playwright environment. Your page scripts run in the browser page environment. Those environments don't intersect, they are running in different virtual machines in different processes and even potentially on different computers.

However, from your code samples, it appears that what you might be trying to achieve is to ensure your async function is fully executed before you load a new page (await page.goto("about:blank")). If this is the case, the code below could work:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()

        async def handle_frame(x: int):
            await page.evaluate(f'Math.random = () => {x};')

        # First page
        await handle_frame(42)
        random_value = await page.evaluate("Math.random()")
        print(random_value) # 42

        await page.goto("about:blank")

        # Second page
        await handle_frame(21)
        random_value = await page.evaluate("Math.random()")
        print(random_value) # 21

        await page.goto("about:blank")

        # Third page; new page, we get a random value.
        random_value = await page.evaluate("Math.random()")
        print(random_value) # random

asyncio.run(main())

Result:

42
21
0.5651989214885142

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 evilmandarine