Version: Next

Core concepts

Playwright provides a set of APIs to automate Chromium, Firefox and WebKit browsers. By using the Playwright API, you can write scripts to create new browser pages, navigate to URLs and then interact with elements on a page.

Along with a test runner Playwright can be used to automate user interactions to validate and test web applications. The Playwright API enables this through the following primitives.


A Browser refers to an instance of Chromium, Firefox or WebKit. Playwright scripts generally start with launching a browser instance and end with closing the browser. Browser instances can be launched in headless (without a GUI) or headful mode.

from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)

Launching a browser instance can be expensive, and Playwright is designed to maximize what a single instance can do through multiple browser contexts.

API reference#

Browser contexts#

A BrowserContext is an isolated incognito-alike session within a browser instance. Browser contexts are fast and cheap to create. Browser contexts can be used to parallelize isolated test executions.

browser = playwright.chromium.launch()
context = browser.new_context()

Browser contexts can also be used to emulate multi-page scenarios involving mobile devices, permissions, locale and color scheme.

from playwright.sync_api import sync_playwright
with sync_playwright() as p:
iphone_11 = p.devices['iPhone 11 Pro']
browser = p.webkit.launch(headless=False)
context = browser.new_context(
geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 },

API reference#

Pages and frames#

A Browser context can have multiple pages. A Page refers to a single tab or a popup window within a browser context. It should be used to navigate to URLs and interact with the page content.

page = context.new_page()
# Navigate explicitly, similar to entering a URL in the browser.
# Fill an input.
page.fill('#search', 'query')
# Navigate implicitly by clicking a link.'#submit')
# Expect a new url.
# Page can navigate from the script - this will be picked up by Playwright.
# window.location.href = ''

Read more on page navigation and loading.

A page can have one or more Frame objects attached to it. Each page has a main frame and page-level interactions (like click) are assumed to operate in the main frame.

A page can have additional frames attached with the iframe HTML tag. These frames can be accessed for interactions inside the frame.

# Get frame using the frame's name attribute
frame = page.frame('frame-login')
# Get frame using frame's URL
frame = page.frame(url=r'.*domain.*')
# Get frame using any other selector
frame_element_handle = page.query_selector('.frame-class')
frame = frame_element_handle.content_frame()
# Interact with the frame
frame.fill('#username-input', 'John')

API reference#


Playwright can search for elements using CSS selectors, XPath selectors, HTML attributes like id, data-test-id and even text content.

You can explicitly specify the selector engine you are using or let Playwright detect it.

All selector engines except for XPath pierce shadow DOM by default. If you want to enforce regular DOM selection, you can use the *:light versions of the selectors. You don't typically need to though.

Learn more about selectors and selector engines here.

Some examples below:

# Using data-test-id= selector engine'data-test-id=foo')
# CSS and XPath selector engines are automatically detected'div')'//html/body/div')
# Find node by text substring'text=Hello w')
# Explicit CSS and XPath notation'css=div')'xpath=//html/body/div')
# Only search light DOM, outside WebComponent shadow DOM:'css:light=div')

Selectors using the same or different engines can be combined using the >> separator. For example,

# Click an element with text 'Sign Up' inside of a #free-month-promo.'#free-month-promo >> text=Sign Up')
# Capture textContent of a section that contains an element with text 'Selectors'.
section_text = page.eval_on_selector('*css=section >> text=Selectors', 'e => e.textContent')


Actions like, **kwargs) and page.fill(selector, value, **kwargs) auto-wait for the element to be visible and actionable. For example, click will:

  • wait for an element with the given selector to appear in the DOM
  • wait for it to become visible: have non-empty bounding box and no visibility:hidden
  • wait for it to stop moving: for example, wait until css transition finishes
  • scroll the element into view
  • wait for it to receive pointer events at the action point: for example, wait until element becomes non-obscured by other elements
  • retry if the element is detached during any of the above checks
# Playwright waits for #search element to be in the DOM
page.fill('#search', 'query')
# Playwright waits for element to stop animating
# and accept clicks.'#search')

You can explicitly wait for an element to appear in the DOM or to become visible:

# Wait for #search to appear in the DOM.
page.wait_for_selector('#search', state='attached')
# Wait for #promo to become visible, for example with `visibility:visible`.

... or to become hidden or detached

# Wait for #details to become hidden, for example with `display:none`.
page.wait_for_selector('#details', state='hidden')
# Wait for #promo to be removed from the DOM.
page.wait_for_selector('#promo', state='detached')

API reference#

Execution contexts: Playwright and Browser#

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.

The page.evaluate(expression, **kwargs) API can run a JavaScript function in the context of the web page and bring results back to the Playwright environment. Browser globals like window and document can be used in evaluate.

href = page.evaluate('() => document.location.href')

If the result is a Promise or if the function is asynchronous evaluate will automatically wait until it's resolved:

status = page.evaluate("""async () => {
response = fetch(location.href)
return response.status

Evaluation Argument#

Playwright evaluation methods like page.evaluate(expression, **kwargs) take a single optional argument. This argument can be a mix of Serializable values and JSHandle or ElementHandle instances. Handles are automatically converted to the value they represent.

# A primitive value.
page.evaluate('num => num', 42)
# An array.
page.evaluate('array => array.length', [1, 2, 3])
# An object.
page.evaluate('object =>', { 'foo': 'bar' })
# A single handle.
button = page.query_selector('button')
page.evaluate('button => button.textContent', button)
# Alternative notation using elementHandle.evaluate.
button.evaluate('(button, from) => button.textContent.substring(from)', 5)
# Object with multiple handles.
button1 = page.query_selector('.button1')
button2 = page.query_selector('.button2')
page.evaluate("""o => o.button1.textContent + o.button2.textContent""",
{ 'button1': button1, 'button2': button2 })
# Object destructuring works. Note that property names must match
# between the destructured object and the argument.
# Also note the required parenthesis.
({ button1, button2 }) => button1.textContent + button2.textContent""",
{ 'button1': button1, 'button2': button2 })
# Array works as well. Arbitrary names can be used for destructuring.
# Note the required parenthesis.
([b1, b2]) => b1.textContent + b2.textContent""",
[button1, button2])
# Any non-cyclic mix of serializables and handles works.
x => x.button1.textContent + x.list[0].textContent + String(""",
{ 'button1': button1, 'list': [button2], 'foo': None })


data = { 'text': 'some data', 'value': 1 }
# Pass |data| as a parameter.
result = page.evaluate("""data => {
}""", data)


data = { 'text': 'some data', 'value': 1 }
result = page.evaluate("""() => {
# There is no |data| in the web page.

API reference#