From 5fd15d8a5e3324951a9c02f795ea94601daab0b6 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 31 May 2021 22:01:21 -0700 Subject: [PATCH] docs(test runner): put more example in various sections (#6812) --- docs/src/test-advanced.md | 4 +- docs/src/test-cli.md | 2 +- docs/src/test-configuration.md | 124 ++++++++++++++++++++++++++++ docs/src/test-examples.md | 145 --------------------------------- docs/src/test-fixtures.md | 2 +- docs/src/test-pom.md | 144 ++++++++++++++++++++++++++++++++ docs/src/test-reporters.md | 4 +- docs/src/test-snapshots.md | 56 ++++++++++++- 8 files changed, 327 insertions(+), 154 deletions(-) delete mode 100644 docs/src/test-examples.md create mode 100644 docs/src/test-pom.md diff --git a/docs/src/test-advanced.md b/docs/src/test-advanced.md index cf1f4156d3..4fc27419be 100644 --- a/docs/src/test-advanced.md +++ b/docs/src/test-advanced.md @@ -1,12 +1,10 @@ --- id: test-advanced -title: "Advanced: Configuration" +title: "Advanced: configuration" --- -
- ## Project configuration - `metadata: any` - Any JSON-serializable metadata that will be put directly to the test report. diff --git a/docs/src/test-cli.md b/docs/src/test-cli.md index 0f3d5a6124..08b1025696 100644 --- a/docs/src/test-cli.md +++ b/docs/src/test-cli.md @@ -1,6 +1,6 @@ --- id: test-cli -title: "Advanced: Command Line" +title: "Advanced: command line" --- ```sh diff --git a/docs/src/test-configuration.md b/docs/src/test-configuration.md index ad588b9d6e..3c86fd907b 100644 --- a/docs/src/test-configuration.md +++ b/docs/src/test-configuration.md @@ -305,3 +305,127 @@ There are many more things you can do with projects: :::note `--browser` command line option is not compatible with projects. Specify `browserName` in each project instead. ::: + +## Mobile emulation + +You can use configuration file to make default `context` emulate a mobile device. + +Here is an example configuration that runs tests in "Pixel 4" and "iPhone 11" emulation modes. Note that it uses the [projects](./test-advanced.md#projects) feature to run the same set of tests in multiple configurations. + +```js +// playwright.config.js +const { devices } = require('playwright'); + +module.exports = { + projects: [ + // "Pixel 4" tests use Chromium browser. + { + name: 'Pixel 4', + use: { + browserName: 'chromium', + ...devices['Pixel 4'], + }, + }, + + // "iPhone 11" tests use WebKit browser. + { + name: 'iPhone 11', + use: { + browserName: 'webkit', + ...devices['iPhone 11'], + }, + }, + ], +}; +``` + +```ts +// playwright.config.ts +import { PlaywrightTestConfig } from 'playwright/test'; +import { devices } from 'playwright'; + +const config: PlaywrightTestConfig = { + projects: [ + // "Pixel 4" tests use Chromium browser. + { + name: 'Pixel 4', + use: { + browserName: 'chromium', + ...devices['Pixel 4'], + }, + }, + + // "iPhone 11" tests use WebKit browser. + { + name: 'iPhone 11', + use: { + browserName: 'webkit', + ...devices['iPhone 11'], + }, + }, + ], +}; +export default config; +``` + +## Network mocking + +You don't have to configure anything to mock network requests. Just define a custom [Route] that mocks network for a browser context. + +```js +// example.spec.js +const { test, expect } = require('playwright/test'); + +test.beforeEach(async ({ context }) => { + // Block any css requests for each test in this file. + await context.route(/.css/, route => route.abort()); +}); + +test('loads page without css', async ({ page }) => { + await page.goto('https://playwright.dev'); + // ... test goes here +}); +``` + +```ts +// example.spec.ts +import { test, expect } from 'playwright/test'; + +test.beforeEach(async ({ context }) => { + // Block any css requests for each test in this file. + await context.route(/.css/, route => route.abort()); +}); + +test('loads page without css', async ({ page }) => { + await page.goto('https://playwright.dev'); + // ... test goes here +}); +``` + +Alternatively, you can use [`method: Page.route`] to mock network in a single test. + +```js +// example.spec.js +const { test, expect } = require('playwright/test'); + +test('loads page without images', async ({ page }) => { + // Block png and jpeg images. + await page.route(/(png|jpeg)$/, route => route.abort()); + + await page.goto('https://playwright.dev'); + // ... test goes here +}); +``` + +```ts +// example.spec.ts +import { test, expect } from 'playwright/test'; + +test('loads page without images', async ({ page }) => { + // Block png and jpeg images. + await page.route(/(png|jpeg)$/, route => route.abort()); + + await page.goto('https://playwright.dev'); + // ... test goes here +}); +``` diff --git a/docs/src/test-examples.md b/docs/src/test-examples.md deleted file mode 100644 index 6104c9df72..0000000000 --- a/docs/src/test-examples.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -id: test-examples -title: "Examples" ---- - - - -## Multiple pages - -The default `context` argument is a [BrowserContext]. Browser contexts are isolated execution environments that can host multiple pages. See [multi-page scenarios](./multi-pages.md) for more examples. - -```js -// example.spec.js -const { test } = require('playwright/test'); - -test('tests on multiple web pages', async ({ context }) => { - const pageFoo = await context.newPage(); - const pageBar = await context.newPage(); - // Test function -}); -``` - -```ts -// example.spec.ts -import { test } from 'playwright/test'; - -test('tests on multiple web pages', async ({ context }) => { - const pageFoo = await context.newPage(); - const pageBar = await context.newPage(); - // Test function -}); -``` - -## Mobile emulation - -`use` section in the configuration file can be used to configure mobile emulation in the default `context`. - -```js -// config.ts -import { PlaywrightTestConfig } from "playwright/test"; -import { devices } from "playwright"; - -const config: PlaywrightTestConfig = { - timeout: 30000, - projects: [ - { - name: 'chromium', - use: { - browserName: 'chromium', - headless: true, - ...devices["Pixel 2"], - }, - }, - ], -}; -export default config; -``` - -## Network mocking - -Define a custom route that mocks network calls for a browser context. - -```js -// In foo.spec.ts -import { test, expect } from "playwright/test"; - -test.beforeEach(async ({ context }) => { - // Block any css requests for each test in this file. - await context.route(/.css/, route => route.abort()); -}); - -test("loads page without css", async ({ page }) => { - // Alternatively, block any png requests just for this test. - await page.route(/.png/, route => route.abort()); - - // Test function code. - await page.goto("https://stackoverflow.com"); -}); -``` - -## Visual comparisons - -The `expect` API supports visual comparisons with `toMatchSnapshot`. This uses the [pixelmatch](https://github.com/mapbox/pixelmatch) library, and you can pass `threshold` as an option. - -```js -import { test, expect } from "playwright/test"; - -test("compares page screenshot", async ({ page }) => { - await page.goto("https://stackoverflow.com"); - const screenshot = await page.screenshot(); - expect(screenshot).toMatchSnapshot(`test.png`, { threshold: 0.2 }); -}); -``` - -On first execution, this will generate golden snapshots. Subsequent runs will compare against the golden snapshots. To update golden snapshots with new actual values, run with the `--update-snapshots` flag. - -```sh -# Update golden snapshots when they differ from actual -npx playwright test --update-snapshots -``` - -### Page object model - -To introduce a Page Object for a particular page, create a class that will use the `page` object. - -Create a `LoginPage` helper class to encapsulate common operations on the login page. -```js -// login-page.ts -import type { Page } from "playwright"; - -export class LoginPage { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - async goto() { - await this.page.goto("https://example.com/login"); - } - - async login() { - await this.page.fill("#username", TEST_USERNAME); - await this.page.fill("#password", TEST_PASSWORD); - await this.page.click("text=Login"); - } -} -``` - -Use the `LoginPage` class in the tests. -```js -// my.spec.ts -import { test, expect } from "playwright/test"; -import { LoginPage } from "./login-page"; - -test('login works', async ({ page }) => { - // Create the login page and perform operations. - const loginPage = new LoginPage(page); - await loginPage.goto(); - await loginPage.login(); - - // Verify it worked. - expect(await page.textContent("#user-info")).toBe("Welcome, Test User!"); -}); -``` diff --git a/docs/src/test-fixtures.md b/docs/src/test-fixtures.md index 559315ecfe..919ae0391e 100644 --- a/docs/src/test-fixtures.md +++ b/docs/src/test-fixtures.md @@ -1,6 +1,6 @@ --- id: test-fixtures -title: "Advanced: Fixtures" +title: "Advanced: fixtures" --- diff --git a/docs/src/test-pom.md b/docs/src/test-pom.md new file mode 100644 index 0000000000..6a39ce0af6 --- /dev/null +++ b/docs/src/test-pom.md @@ -0,0 +1,144 @@ +--- +id: test-pom +title: "Page Object Model" +--- + +Page Object Model is a common pattern that introduces abstractions over web app pages to simplify interactions with them in multiple tests. It is best explained by an example. + +We will create a `PlaywrightDevPage` helper class to encapsulate common operations on the `playwright.dev` page. Internally, it will use the `page` object. + +```js +// playwright-dev-page.js +exports.PlaywrightDevPage = class PlaywrightDevPage { + constructor(page: Page) { + this.page = page; + } + + async goto() { + await this.page.goto('https://playwright.dev'); + } + + async toc() { + const text = await this.page.innerText('article ul'); + return text.split('\n').filter(line => !!line); + } + + async getStarted() { + await this.page.click('text=Get started'); + await this.page.waitForSelector(`text=Core concepts`); + } + + async coreConcepts() { + await this.getStarted(); + await this.page.click('text=Core concepts'); + await this.page.waitForSelector(`h1:has-text("Core concepts")`); + } +} +``` + +```ts +// playwright-dev-page.ts +import type { Page } from 'playwright'; + +export class PlaywrightDevPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await this.page.goto('https://playwright.dev'); + } + + async toc() { + const text = await this.page.innerText('article ul'); + return text.split('\n').filter(line => !!line); + } + + async getStarted() { + await this.page.click('text=Get started'); + await this.page.waitForSelector(`text=Core concepts`); + } + + async coreConcepts() { + await this.getStarted(); + await this.page.click('text=Core concepts'); + await this.page.waitForSelector(`h1:has-text("Core concepts")`); + } +} +``` + +Now we can use the `PlaywrightDevPage` class in our tests. + +```js +// example.spec.js +const { test, expect } = require('playwright/test'); +const { PlaywrightDevPage } = require('./playwright-dev-page'); + +test('Get Started table of contents', async ({ page }) => { + const playwrightDev = new PlaywrightDevPage(page); + await playwrightDev.goto(); + await playwrightDev.getStarted(); + expect(await playwrightDev.toc()).toEqual([ + 'Installation', + 'Usage', + 'First script', + 'Record scripts', + 'TypeScript support', + 'System requirements', + 'Release notes' + ]); +}); + +test('Core Concepts table of contents', async ({ page }) => { + const playwrightDev = new PlaywrightDevPage(page); + await playwrightDev.goto(); + await playwrightDev.coreConcepts(); + expect(await playwrightDev.toc()).toEqual([ + 'Browser', + 'Browser contexts', + 'Pages and frames', + 'Selectors', + 'Auto-waiting', + 'Execution contexts: Playwright and Browser', + 'Evaluation Argument' + ]); +}); +``` + +```ts +// example.spec.ts +import { test, expect } from 'playwright/test'; +import { PlaywrightDevPage } from './playwright-dev-page'; + +test('Get Started table of contents', async ({ page }) => { + const playwrightDev = new PlaywrightDevPage(page); + await playwrightDev.goto(); + await playwrightDev.getStarted(); + expect(await playwrightDev.toc()).toEqual([ + 'Installation', + 'Usage', + 'First script', + 'Record scripts', + 'TypeScript support', + 'System requirements', + 'Release notes' + ]); +}); + +test('Core Concepts table of contents', async ({ page }) => { + const playwrightDev = new PlaywrightDevPage(page); + await playwrightDev.goto(); + await playwrightDev.coreConcepts(); + expect(await playwrightDev.toc()).toEqual([ + 'Browser', + 'Browser contexts', + 'Pages and frames', + 'Selectors', + 'Auto-waiting', + 'Execution contexts: Playwright and Browser', + 'Evaluation Argument' + ]); +}); +``` diff --git a/docs/src/test-reporters.md b/docs/src/test-reporters.md index 4bcd9a4358..dbdb4e83b1 100644 --- a/docs/src/test-reporters.md +++ b/docs/src/test-reporters.md @@ -194,7 +194,7 @@ module.exports = { }; ``` -```js +```ts // playwright.config.ts import { PlaywrightTestConfig } from 'playwright/test'; @@ -221,7 +221,7 @@ module.exports = { }; ``` -```js +```ts // playwright.config.ts import { PlaywrightTestConfig } from 'playwright/test'; diff --git a/docs/src/test-snapshots.md b/docs/src/test-snapshots.md index be6edfc5b5..10f3edf658 100644 --- a/docs/src/test-snapshots.md +++ b/docs/src/test-snapshots.md @@ -1,9 +1,9 @@ --- id: test-snapshots -title: "Snapshots" +title: "Visual comparisons" --- -Playwright Test includes the ability to produce and compare snapshots. For that, use `expect(value).toMatchSnapshot()`. Test runner auto-detects the content type, and includes built-in matchers for text, png and jpeg images, and arbitrary binary data. +Playwright Test includes the ability to produce and visually compare screenshots using `expect(value).toMatchSnapshot()`. On first execution, Playwright test will generate reference screenshots. Subsequent runs will compare against the reference. ```js // example.spec.js @@ -25,4 +25,56 @@ test('example test', async ({ page }) => { }); ``` +Sometimes you need to update the reference screenshot, for example when the page has changed. Do this with the `--update-snapshots` flag. + +```sh +npx playwright test --update-snapshots +``` + +Playwright Test uses the [pixelmatch](https://github.com/mapbox/pixelmatch) library. You can pass comparison `threshold` as an option. + +```js +// example.spec.js +const { test, expect } = require('playwright/test'); + +test('example test', async ({ page }) => { + await page.goto('https://playwright.dev'); + expect(await page.screenshot()).toMatchSnapshot({ threshold: 0.2 }); +}); +``` + +```ts +// example.spec.ts +import { test, expect } from 'playwright/test'; + +test('example test', async ({ page }) => { + await page.goto('https://playwright.dev'); + expect(await page.screenshot()).toMatchSnapshot({ threshold: 0.2 }); +}); +``` + +Apart from screenshots, `expect(value).toMatchSnapshot()` can also be used to compare text, png and jpeg images, or arbitrary binary data. Playwright Test auto-detects the content type and uses the appropriate comparison algorithm. + +Here we compare text content against the reference. + +```js +// example.spec.js +const { test, expect } = require('playwright/test'); + +test('example test', async ({ page }) => { + await page.goto('https://playwright.dev'); + expect(await page.textContent('.hero__title')).toMatchSnapshot(); +}); +``` + +```ts +// example.spec.ts +import { test, expect } from 'playwright/test'; + +test('example test', async ({ page }) => { + await page.goto('https://playwright.dev'); + expect(await page.textContent('.hero__title')).toMatchSnapshot(); +}); +``` + Snapshots are stored next to the test file, in a separate directory. For example, `my.spec.js` file will produce and store snapshots in the `my.spec.js-snapshots` directory. You should commit this directory to your version control (e.g. `git`), and review any changes to it.