diff --git a/docs-src/actionability.md b/docs-src/actionability.md new file mode 100644 index 0000000000..6e7e133019 --- /dev/null +++ b/docs-src/actionability.md @@ -0,0 +1,56 @@ +# Actionability + +Playwright does a range of actionability checks on the elements before performing certain actions. These checks ensure that action behaves as expected, for example Playwright does not click on a disabled button. + +Playwright waits until all the relevant actionability checks pass before performing an action. This means that action will fail with `TimeoutError` if checks do not pass within the specified `timeout`. + +Some actions like `page.click()` support `{force: true}` option that disable non-essential actionability checks, for example passing `force` to `click()` method will not check that the target element actually receives click events. + +| Actions | Performed checks | +| ------ | ------- | +| `check()`
`click()`
`dblclick()`
`tap()`
`uncheck()` | [Visible]
[Stable]
[Enabled]
[Receiving Events]
[Attached] | +| `hover()` | [Visible]
[Stable]
[Receiving Events]
[Attached] | +| `fill()` | [Visible]
[Enabled]
[Editable]
[Attached] | +| `dispatchEvent()`
`focus()`
`press()`
`setInputFiles()`
`selectOption()`
`type()` | [Attached] | +| `scrollIntoViewIfNeeded()`
`screenshot()` | [Visible]
[Stable]
[Attached] | +| `selectText()` | [Visible]
[Attached] | +| `getAttribute()`
`innerText()`
`innerHTML()`
`textContent()` | [Attached] | + +### Visible + +Element is considered visible when it has non-empty bounding box and does not have `visibility:hidden` computed style. Note that elements of zero size or with `display:none` are not considered visible. + +### Stable + +Element is considered stable when it has maintained the same bounding box for at least two consecutive animation frames. + +### Enabled + +Element is considered enabled when it is not a ` +``` + +#### playwright.$$(selector) + +Same as `playwright.$`, but returns all matching elements. + +```js +> playwright.$$('li >> text=John') + +> [
  • ,
  • ,
  • ,
  • ] +``` + +#### playwright.inspect(selector) + +Reveal element in the Elements panel (if DevTools of the respective browser supports it). + +```js +> playwright.inspect('text=Log in') +``` + +#### playwright.selector(element) + +Generates selector for the given element. + +```js +> playwright.selector($0) + +"div[id="glow-ingress-block"] >> text=/.*Hello.*/" +``` + +## Take screenshot + +```sh +# See command help +$ npx playwright screenshot --help +``` + +```sh +# Wait 3 seconds before capturing a screenshot after page loads ('load' event fires) +npx playwright \ + --device="iPhone 11" \ + --color-scheme=dark \ + screenshot \ + --wait-for-timeout=3000 \ + twitter.com twitter-iphone.png +``` + +```sh +# Capture a full page screenshot +npx playwright screenshot --full-page en.wikipedia.org wiki-full.png +``` + +## Generate PDF + +PDF generation only works in Headless Chromium. + +```sh +# See command help +$ npx playwright pdf https://en.wikipedia.org/wiki/PDF wiki.pdf +``` + +## Known limitations +Opening WebKit Web Inspector will disconnect Playwright from the browser. In such cases, code generation will stop. diff --git a/docs-src/core-concepts.md b/docs-src/core-concepts.md new file mode 100644 index 0000000000..fe95bf2d4e --- /dev/null +++ b/docs-src/core-concepts.md @@ -0,0 +1,350 @@ +# Core concepts + +Playwright provides a set of APIs to automate Chromium, Firefox and WebKit +browsers. By using the Playwright API, you can write JavaScript code 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. + + + + +
    + +## Browser + +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. + +```js +const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. + +const browser = await chromium.launch({ headless: false }); +await browser.close(); +``` + +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] + +
    + +## 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. + +```js +const browser = await chromium.launch(); +const context = await browser.newContext(); +``` + +Browser contexts can also be used to emulate multi-page scenarios involving +mobile devices, permissions, locale and color scheme. + +```js +const { devices } = require('playwright'); +const iPhone = devices['iPhone 11 Pro']; + +const context = await browser.newContext({ + ...iPhone, + permissions: ['geolocation'], + geolocation: { latitude: 52.52, longitude: 13.39}, + colorScheme: 'dark', + locale: 'de-DE' +}); +``` + +#### API reference + +- [BrowserContext] +- [`method: Browser.newContext`] + +
    + +## 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. + +```js +// Create a page. +const page = await context.newPage(); + +// Navigate explicitly, similar to entering a URL in the browser. +await page.goto('http://example.com'); +// Fill an input. +await page.fill('#search', 'query'); + +// Navigate implicitly by clicking a link. +await page.click('#submit'); +// Expect a new url. +console.log(page.url()); + +// Page can navigate from the script - this will be picked up by Playwright. +window.location.href = 'https://example.com'; +``` + +> Read more on [page navigation and loading](./navigations.md). + +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. + +```js +// Get frame using the frame's name attribute +const frame = page.frame('frame-login'); + +// Get frame using frame's URL +const frame = page.frame({ url: /.*domain.*/ }); + +// Get frame using any other selector +const frameElementHandle = await page.$('.frame-class'); +const frame = await frameElementHandle.contentFrame(); + +// Interact with the frame +await frame.fill('#username-input', 'John'); +``` + +#### API reference + +- [Page] +- [Frame] +- [`method: Page.frame`] + +
    + +## Selectors + +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](./selectors.md). + +Some examples below: + +```js +// Using data-test-id= selector engine +await page.click('data-test-id=foo'); +``` + +```js +// CSS and XPath selector engines are automatically detected +await page.click('div'); +await page.click('//html/body/div'); +``` + +```js +// Find node by text substring +await page.click('text=Hello w'); +``` + +```js +// Explicit CSS and XPath notation +await page.click('css=div'); +await page.click('xpath=//html/body/div'); +``` + +```js +// Only search light DOM, outside WebComponent shadow DOM: +await page.click('css:light=div'); +``` + +Selectors using the same or different engines can be combined using the `>>` separator. For example, + +```js +// Click an element with text 'Sign Up' inside of a #free-month-promo. +await page.click('#free-month-promo >> text=Sign Up'); +``` + +```js +// Capture textContent of a section that contains an element with text 'Selectors'. +const sectionText = await page.$eval('*css=section >> text=Selectors', e => e.textContent); +``` + +
    + +## Auto-waiting + +Actions like `click` and `fill` auto-wait for the element to be visible and [actionable](./actionability.md). 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 + + +```js +// Playwright waits for #search element to be in the DOM +await page.fill('#search', 'query'); +``` +```js +// Playwright waits for element to stop animating +// and accept clicks. +await page.click('#search'); +``` + +You can explicitly wait for an element to appear in the DOM or to become visible: + +```js +// Wait for #search to appear in the DOM. +await page.waitForSelector('#search', { state: 'attached' }); +// Wait for #promo to become visible, for example with `visibility:visible`. +await page.waitForSelector('#promo'); +``` + +... or to become hidden or detached + +```js +// Wait for #details to become hidden, for example with `display:none`. +await page.waitForSelector('#details', { state: 'hidden' }); +// Wait for #promo to be removed from the DOM. +await page.waitForSelector('#promo', { state: 'detached' }); +``` + +#### API reference + +- [`method: Page.click`] +- [`method: Page.fill`] +- [`method: Page.waitForSelector`] + +
    + +## Execution contexts: Node.js and Browser + +Playwright scripts run in your Node.js 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 [`method: Page.evaluate`] API can run a JavaScript function in the context +of the web page and bring results back to the Node.js environment. Browser globals like +`window` and `document` can be used in `evaluate`. + +```js +const href = await 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: +```js +const status = await page.evaluate(async () => { + const response = await fetch(location.href); + return response.status; +}); +``` + +### Evaluation + +Functions passed inside [`method: Page.evaluate`] can accept parameters. These parameters are +serialized and sent into your web page over the wire. You can pass primitive types, JSON-alike objects and remote object handles received from the page. + +Right: + +```js +const data = { text: 'some data', value: 1 }; +// Pass |data| as a parameter. +const result = await page.evaluate(data => { + window.myApp.use(data); +}, data); +``` + +Wrong: + +```js +const data = { text: 'some data', value: 1 }; +const result = await page.evaluate(() => { + // There is no |data| in the web page. + window.myApp.use(data); +}); +``` + +#### API reference + +- [`method: Page.evaluate`] +- [`method: Frame.evaluate`] +- [EvaluationArgument] + +
    + +## Object & Element handles + +Playwright can create Node-side handles to the page DOM elements or any other objects inside the page. These handles live in the Node.js process, whereas the actual objects reside in browser. + +There are two types of handles: +- [JSHandle] to reference any JavaScript objects in the page +- [ElementHandle] to reference DOM elements in the page + +Note that since any DOM element in the page is also a JavaScript object, +Playwright's [ElementHandle] extends [JSHandle]. + +### Handles Lifecycle +- Handles can be acquired using the page methods [`method: Page.evaluateHandle`], [`method: Page.$`] or [`method: Page.$$`] or + their frame counterparts [`method: Frame.evaluateHandle`], [`method: Frame.$`] or [`method: Frame.$$`]. +- Once created, handles will retain object from [garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management). +- Handles will be **automatically disposed** once the page or frame they belong to navigates or closes. +- Handles can be **manually disposed** using [`method: JSHandle.dispose`] method. + +### Example: ElementHandle + +```js +// The first parameter of the elementHandle.evaluate callback is the element handle points to. +const ulElementHandle = await page.$('ul'); +await ulElementHandle.evaluate(ulElement => getComputedStyle(ulElement).getPropertyValue('display')); +``` + +Handles can also be passed as arguments to [`method: Page.evaluate`] function: + +```js +// In the page API, you can pass handle as a parameter. +const ulElementHandle = await page.$('ul'); +await page.evaluate(uiElement => getComputedStyle(uiElement).getPropertyValue('display'), uiElement); +``` + +### Example: JSHandle + +```js +// Create a new array in the page, write a reference to it in +// window.myArray and get a handle to it. +const myArrayHandle = await page.evaluateHandle(() => { + window.myArray = [1]; + return myArray; +}); + +// Get current length of the array using the handle. +const length = await page.evaluate( + (arg) => arg.myArray.length, + { myArray: myArrayHandle } +); + +// Add one more element to the array using the handle +await page.evaluate((arg) => arg.myArray.push(arg.newElement), { + myArray: myArrayHandle, + newElement: 2 +}); + +// Get current length of the array using window.myArray reference. +const newLength = await page.evaluate(() => window.myArray.length); + +// Release the object when it's no longer needed. +await myArrayHandle.dispose(); +``` + +#### API reference +- [JSHandle] +- [ElementHandle] +- [`method: Page.evaluateHandle`] +- [`method: Page.$`] +- [`method: Page.$$`] +- [`method: JSHandle.evaluate`] diff --git a/docs-src/debug.md b/docs-src/debug.md new file mode 100644 index 0000000000..098947ed91 --- /dev/null +++ b/docs-src/debug.md @@ -0,0 +1,126 @@ +# Debugging tools + +Playwright scripts work with existing debugging tools, like Node.js debuggers +and browser developer tools. Playwright also introduces new debugging features +for browser automation. + + + + +## Run in headful mode + +Playwright runs browsers in headless mode by default. To change this behavior, +use `headless: false` as a launch option. You can also use the `slowMo` option +to slow down execution and follow along while debugging. + +```js +await chromium.launch({ headless: false, slowMo: 100 }); // or firefox, webkit +``` + +## Visual Studio Code debugger + +The VS Code debugger can be used to pause and resume execution of Playwright +scripts with breakpoints. The debugger can be configured in two ways. + +### Use launch config + +Setup [`launch.json` configuration](https://code.visualstudio.com/docs/nodejs/nodejs-debugging) +for your Node.js project. Once configured launch the scripts with F5 and use +breakpoints. + +### Use the new JavaScript debugger + +VS Code 1.46+ introduces the new JavaScript debugger behind a feature flag. The +new debugger does not require a `launch.json` configuration. To use this: + +1. Enable the preview debugger + * Open JSON settings and add `"debug.javascript.usePreview": true` + * Open settings UI and enable the `Debug › JavaScript: Use Preview` setting + +1. Set a breakpoint in VS Code + * Use the `debugger` keyword or set a breakpoint in the VS Code UI + +1. Run your Node.js script from the terminal + +## Browser Developer Tools + +You can use browser developer tools in Chromium, Firefox and WebKit while running +a Playwright script. Developer tools help to: + +* Inspect the DOM tree and **find element selectors** +* **See console logs** during execution (or learn how to [read logs via API](./verification.md#console-logs)) +* Check **network activity** and other developer tools features + +Chromium Developer Tools + +> **For WebKit**: Note that launching WebKit Inspector during the execution will + prevent the Playwright script from executing any further. + +### API for Chromium + +In Chromium, you can also open developer tools through a launch option. + +```js +await chromium.launch({ devtools: true }); +``` + +## Run in Debug Mode + +Set the `PWDEBUG` environment variable to run your scripts in debug mode. This +configures the browser for debugging. + +```sh +# Linux/macOS +$ PWDEBUG=1 npm run test + +# Windows +$ set PWDEBUG=1 +$ npm run test +``` + +### Defaults + +With PWDEBUG, the following defaults are configured for you: + +* **Run in headful**: With PWDEBUG, browsers always launch in headful mode +* **Disables timeout**: PWDEBUG sets timeout to 0 (= no timeout) +* **Preserve DevTools preferences**: When used with `devtools: true`, PWDEBUG + preserves the docked/undocked state of Chrome DevTools + +### Debugging Selectors + +PWDEBUG configures a `playwright` object in the browser to highlight +[Playwright selectors](./selectors.md). This can be used to verify text or +composite selectors. To use this: + +1. Setup a breakpoint to pause the execution +1. Open the console panel in browser developer tools +1. Use the `playwright` API + * `playwright.$(selector)`: Highlight the first occurrence of the selector. This reflects + how `page.$` would see the page. + * `playwright.$$(selector)`: Highlight all occurrences of the selector. This reflects + how `page.$$` would see the page. + * `playwright.inspect(selector)`: Inspect the selector in the Elements panel. + * `playwright.clear()`: Clear existing highlights. + +Highlight selectors + +### Evaluate Source Maps + +PWDEBUG also enables source maps for [`page.evaluate` executions](core-concepts.md#evaluation). +This improves the debugging experience for JavaScript executions in the page context. + +Highlight selectors + +## Verbose API logs + +Playwright supports verbose logging with the `DEBUG` environment variable. + +```sh +# Linux/macOS +$ DEBUG=pw:api npm run test + +# Windows +$ set DEBUG=pw:api +$ npm run test +``` diff --git a/docs-src/emulation.md b/docs-src/emulation.md new file mode 100644 index 0000000000..cf9403f796 --- /dev/null +++ b/docs-src/emulation.md @@ -0,0 +1,179 @@ +# Device and environment emulation + +Playwright allows overriding various parameters of the device where the browser is running: + - viewport size, device scale factor, touch support + - locale, timezone + - color scheme + - geolocation + +Most of these parameters are configured during the browser context construction, but some of them such as viewport size can be changed for individual pages. + + + + +
    + +## Devices + +Playwright comes with a registry of device parameters for selected mobile devices. It can be used to simulate browser behavior on a mobile device: + +```js +const { chromium, devices } = require('playwright'); +const browser = await chromium.launch(); + +const pixel2 = devices['Pixel 2']; +const context = await browser.newContext({ + ...pixel2, +}); +``` + +All pages created in the context above will share the same device parameters. + +#### API reference + +- [`property: Playwright.devices`] +- [`method: Browser.newContext`] + +
    + +## User agent + +All pages created in the context above will share the user agent specified: + +```js +const context = await browser.newContext({ + userAgent: 'My user agent' +}); +``` + +#### API reference + +- [`method: Browser.newContext`] + +
    + +## Viewport + +Create a context with custom viewport size: + +```js +// Create context with given viewport +const context = await browser.newContext({ + viewport: { width: 1280, height: 1024 } +}); + +// Resize viewport for individual page +await page.setViewportSize({ width: 1600, height: 1200 }); + +// Emulate high-DPI +const context = await browser.newContext({ + viewport: { width: 2560, height: 1440 }, + deviceScaleFactor: 2, +}); +``` + +#### API reference + +- [`method: Browser.newContext`] +- [`method: Page.setViewportSize`] + +
    + +## Locale & timezone + +```js +// Emulate locale and time +const context = await browser.newContext({ + locale: 'de-DE', + timezoneId: 'Europe/Berlin', +}); +``` + +#### API reference + +- [`method: Browser.newContext`] + +
    + +## Permissions + +Allow all pages in the context to show system notifications: +```js +const context = await browser.newContext({ + permissions: ['notifications'], +}); +``` + +Grant all pages in the existing context access to current location: +```js +await context.grantPermissions(['geolocation']); +``` + +Grant notifications access from a specific domain: +```js +await context.grantPermissions(['notifications'], {origin: 'https://skype.com'} ); +``` + +Revoke all permissions: +```js +await context.clearPermissions(); +``` + +#### API reference + +- [`method: Browser.newContext`] +- [`method: BrowserContext.grantPermissions`] +- [`method: BrowserContext.clearPermissions`] + +
    + +## Geolocation +Create a context with `"geolocation"` permissions granted: +```js +const context = await browser.newContext({ + geolocation: { longitude: 48.858455, latitude: 2.294474 }, + permissions: ['geolocation'] +}); +``` +Change the location later: + +```js +await context.setGeolocation({ longitude: 29.979097, latitude: 31.134256 }); +``` + +**Note** you can only change geolocation for all pages in the context. + +#### API reference + +- [`method: Browser.newContext`] +- [`method: BrowserContext.setGeolocation`] + +
    + +## Color scheme and media + +Create a context with dark or light mode. Pages created in this context will +follow this color scheme preference. + +```js +// Create context with dark mode +const context = await browser.newContext({ + colorScheme: 'dark' // or 'light' +}); + +// Create page with dark mode +const page = await browser.newPage({ + colorScheme: 'dark' // or 'light' +}); + +// Change color scheme for the page +await page.emulateMedia({ colorScheme: 'dark' }); + +// Change media for page +await page.emulateMedia({ media: 'print' }); +``` + +#### API reference + +- [`method: Browser.newContext`] +- [`method: Page.emulateMedia`] diff --git a/docs-src/extensibility.md b/docs-src/extensibility.md new file mode 100644 index 0000000000..b439fcd687 --- /dev/null +++ b/docs-src/extensibility.md @@ -0,0 +1,44 @@ +# Extensibility + + + + +## Custom selector engines + +Playwright supports custom selector engines, registered with [`method: Selectors.register`]. + +Selector engine should have the following properties: + +- `create` function to create a relative selector from `root` (root is either a `Document`, `ShadowRoot` or `Element`) to a `target` element. +- `query` function to query first element matching `selector` relative to the `root`. +- `queryAll` function to query all elements matching `selector` relative to the `root`. + +By default the engine is run directly in the frame's JavaScript context and, for example, can call an application-defined function. To isolate the engine from any JavaScript in the frame, but leave access to the DOM, register the engine with `{contentScript: true}` option. Content script engine is safer because it is protected from any tampering with the global objects, for example altering `Node.prototype` methods. All built-in selector engines run as content scripts. Note that running as a content script is not guaranteed when the engine is used together with other custom engines. + +An example of registering selector engine that queries elements based on a tag name: +```js +// Must be a function that evaluates to a selector engine instance. +const createTagNameEngine = () => ({ + // Returns the first element matching given selector in the root's subtree. + query(root, selector) { + return root.querySelector(selector); + }, + + // Returns all elements matching given selector in the root's subtree. + queryAll(root, selector) { + return Array.from(root.querySelectorAll(selector)); + } +}); + +// Register the engine. Selectors will be prefixed with "tag=". +await selectors.register('tag', createTagNameEngine); + +// Now we can use 'tag=' selectors. +const button = await page.$('tag=button'); + +// We can combine it with other selector engines using `>>` combinator. +await page.click('tag=div >> span >> "Click me"'); + +// We can use it in any methods supporting selectors. +const buttonCount = await page.$$eval('tag=button', buttons => buttons.length); +``` diff --git a/docs-src/input.md b/docs-src/input.md new file mode 100644 index 0000000000..e2b4f51ac8 --- /dev/null +++ b/docs-src/input.md @@ -0,0 +1,277 @@ +# Input + + + + +
    + +## Text input + +This is the easiest way to fill out the form fields. It focuses the element and triggers an `input` event with the entered text. It works for ``, `