Camoufox without a Context Manager

A common pattern when working with Camoufox in particular and Playwright in general is to use a context manager to handle the browser lifecycle. It’s clean, safe and ensures everything is torn down properly when your script completes. However, sometimes it’s not possible to wrestle all of your code into a context manager. There’s an alternative though.

Using a Context Manager

The normal approach to using Camoufox is via a context manager.

from camoufox.sync_api import Camoufox

with Camoufox(headless=False) as browser:
    page = browser.new_page()

    page.goto("https://www.example.com")
    print(page.content())

This works well when your entire script fits neatly inside a single block. The context manager guarantees that the browser starts when the block opens and shuts down automatically when it ends.

However, this pattern isn’t always convenient. If you’re building a larger application, want to open and close pages at different times, or need to keep the browser available across multiple functions, nesting everything inside a with block can be restrictive. The lifecycle becomes tied to your code structure, rather than the other way around.

Alternatives to a Context Manager

Fortunately, Camoufox (and Playwright) allow you to manage the browser manually. Instead of relying on the context manager, you can call .start() explicitly and keep the browser instance in a variable. This gives you complete flexibility to control the browser lifecycle.

from camoufox.sync_api import Camoufox

camoufox = Camoufox(headless=False)

# Start the browser manually (instead of using a context manager) and assign it to a variable.
browser = camoufox.start()

# Open a new page (tab) within the running browser instance.
page = browser.new_page()

page.goto("https://www.example.com")
print(page.content())

# Close the current page.
page.close()
# Close the browser instance and clean up associated resources.
browser.close()

This approach is more natural when you want full control over the browser object or when the code structure doesn’t fit neatly into a single with block. It also mirrors how Playwright works under the hood: context managers are a layer of convenience, not a requirement.

It does mean that you are responsible for browser cleanup. To ensure that these resources are freed you might use the atexit module or a finally clause in an exception handler.

There’s actually another way to do this, which might be convenient in some circumstances.

from playwright.sync_api import sync_playwright
from camoufox.pkgman import launch_path as camoufox_launch_path

playwright = sync_playwright().start()

browser = playwright.firefox.launch(
    headless=False, executable_path=camoufox_launch_path()
)

page = browser.new_page()

page.goto("https://www.example.com")
print(page.content())

page.close()
browser.close()

playwright.stop()

It starts by creating a lightweight Playwright object. This object has one purpose: launching browsers (either Firefox, Chome or WebKit). It’s used to launch a Firefox browser but with the path for the Camoufox executable. That path comes from the launch_path() function in the camoufox package. On my system that executable is at ~/.cache/camoufox/camoufox-bin. With this approach you should probably also explicitly stop the Playwright object, although this might not be strictly necessary because it should get mopped up by garbage collection.

Conclusion

Both styles are valid. The context manager version is safer and more concise, while the manual .start() pattern gives you flexibility and explicit control. Choose the one that matches the shape of your project rather than forcing your project to match the shape of the example.