From adab7a817e41eb457a1f632f3a6fc513c9312953 Mon Sep 17 00:00:00 2001 From: Sander Date: Fri, 3 Feb 2023 17:56:55 +0100 Subject: [PATCH] docs(ct): hooks and formatting (#20593) --- docs/src/test-components-js.md | 281 +++++++++++++++++++++++---------- 1 file changed, 195 insertions(+), 86 deletions(-) diff --git a/docs/src/test-components-js.md b/docs/src/test-components-js.md index 6576d2145d..e934038feb 100644 --- a/docs/src/test-components-js.md +++ b/docs/src/test-components-js.md @@ -18,9 +18,9 @@ test('event should work', async ({ mount }) => { let clicked = false; // Mount a component. Returns locator pointing to the component. - const component = await mount(); + const component = await mount( + + ); // As with any Playwright test, assert locator text. await expect(component).toContainText('Submit'); @@ -65,9 +65,9 @@ yarn create playwright --ct - ```bash - pnpm dlx create-playwright --ct - ``` +```bash +pnpm dlx create-playwright --ct +``` @@ -79,7 +79,7 @@ This step creates several files in your workspace: This file defines an html file that will be used to render components during testing. It must contain element with `id="root"`, that's where components are mounted. It must -also link the script called `playwright/index.[tj]s`. +also link the script called `playwright/index.{js,ts,jsx,tsx}`. ```html @@ -93,7 +93,7 @@ also link the script called `playwright/index.[tj]s`. #### `playwright/index.ts` You can include stylesheets, apply theme and inject code into the page where -component is mounted using this script. It can be either a `.js` or `.ts` file. +component is mounted using this script. It can be either a `.js`, `.ts`, `.jsx` or `.tsx` file. ```js // Apply theme here, add anything your component needs at runtime here. @@ -105,9 +105,9 @@ component is mounted using this script. It can be either a `.js` or `.ts` file. defaultValue="react" values={[ {label: 'React', value: 'react'}, - {label: 'Vue', value: 'vue'}, - {label: 'Svelte', value: 'svelte'}, {label: 'Solid', value: 'solid'}, + {label: 'Svelte', value: 'svelte'}, + {label: 'Vue', value: 'vue'}, ] }> @@ -135,7 +135,7 @@ import App from './App.vue'; test.use({ viewport: { width: 500, height: 500 } }); test('should work', async ({ mount }) => { - const component = await mount(); + const component = await mount(App); await expect(component).toContainText('Vite + Vue'); }); ``` @@ -182,7 +182,6 @@ test('should work', async ({ mount }) => { - ### Step 3. Run the tests You can run tests using the [VS Code extension](./getting-started-vscode.md) or the command line. @@ -199,52 +198,162 @@ Refer to [Playwright config](./test-configuration.md) for configuring your proje You can use `beforeMount` and `afterMount` hooks to configure your app. This lets you setup things like your app router, fake server etc. giving you the flexibility you need. You can also pass custom configuration from the `mount` call from a test, which is accessible from the `hooksConfig` fixture. -#### `playwright/index.ts` +#### `playwright/index.{js,ts,jsx,tsx}` -This includes any config that needs to be run before/after mounting the component. Here's an example of how to setup `miragejs` mocking library: +This includes any config that needs to be run before or after mounting the component. An example of configuring a router is provided below: -```js -import { beforeMount } from '@playwright/experimental-ct-react/hooks'; -import { createServer } from "miragejs" + + -beforeMount(async ({ hooksConfig }) => { - // Setting default values if custom config is not provided - const users = hooksConfig.users ?? [ - { id: "1", name: "Luke" }, - { id: "2", name: "Leia" }, - { id: "3", name: "Han" }, - ]; - createServer({ - routes() { - this.get("/api/users", () => users) - }, + ```js + // playwright/index.tsx + import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks'; + import { BrowserRouter } from 'react-router-dom'; + + export type HooksConfig = { + enableRouting?: boolean; + } + + beforeMount(async ({ App, hooksConfig }) => { + if (hooksConfig?.enableRouting) + return ; }); -}); -``` + ``` -#### In your test file: + #### In your test file: -```js -// src/Users.spec.tsx -import { test, expect } from "@playwright/experimental-ct-react"; -import React from "react"; -import { Users } from "./Users"; + ```js + // src/pages/ProductsPage.spec.tsx + import { test, expect } from '@playwright/experimental-ct-react'; + import type { HooksConfig } from 'playwright'; + import { ProductsPage } from './pages/ProductsPage'; -test("should work", async ({ mount }) => { - const component = await mount(, { - hooksConfig: { - users: [ - { id: "4", name: "Anakin" }, - { id: "5", name: "Padme" }, - ] + test('configure routing through hooks config', async ({ page, mount }) => { + const component = await mount(, { + hooksConfig: { enableRouting: true }, + }); + await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42'); + }); + ``` + + + + + + ```js + // playwright/index.tsx + import { beforeMount, afterMount } from '@playwright/experimental-ct-solid/hooks'; + import { Router } from '@solidjs/router'; + + export type HooksConfig = { + enableRouting?: boolean; + } + + beforeMount(async ({ App, hooksConfig }) => { + if (hooksConfig?.enableRouting) + return ; + }); + ``` + + #### In your test file: + + ```js + // src/pages/ProductsPage.spec.tsx + import { test, expect } from '@playwright/experimental-ct-solid'; + import type { HooksConfig } from 'playwright'; + import { ProductsPage } from './pages/ProductsPage'; + + test('configure routing through hooks config', async ({ page, mount }) => { + const component = await mount(, { + hooksConfig: { enableRouting: true }, + }); + await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42'); + }); + ``` + + + + + + ```js + // playwright/index.ts + import { beforeMount, afterMount } from '@playwright/experimental-ct-vue/hooks'; + import { router } from '../src/router'; + + export type HooksConfig = { + enableRouting?: boolean; + } + + beforeMount(async ({ app, hooksConfig }) => { + if (hooksConfig?.enableRouting) + app.use(router); + }); + ``` + + #### In your test file: + + ```js + // src/pages/ProductsPage.spec.ts + import { test, expect } from '@playwright/experimental-ct-vue'; + import type { HooksConfig } from 'playwright'; + import ProductsPage from './pages/ProductsPage.vue'; + + test('configure routing through hooks config', async ({ page, mount }) => { + const component = await mount(ProductsPage, { + hooksConfig: { enableRouting: true }, + }); + await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42'); + }); + ``` + + + + + + ```js + // playwright/index.ts + import { beforeMount, afterMount } from '@playwright/experimental-ct-vue2/hooks'; + import Router from 'vue-router'; + import { router } from '../src/router'; + + export type HooksConfig = { + enableRouting?: boolean; + } + + beforeMount(async ({ app, hooksConfig }) => { + if (hooksConfig?.enableRouting) { + Vue.use(Router); + return { router } } }); - await expect(component.locator("li")).toContainText([ - "Anakin", - "Padme", - ]); -}); -``` + ``` + #### In your test file: + + ```js + // src/pages/ProductsPage.spec.ts + import { test, expect } from '@playwright/experimental-ct-vue2'; + import type { HooksConfig } from 'playwright'; + import ProductsPage from './pages/ProductsPage.vue'; + + test('configure routing through hooks config', async ({ page, mount }) => { + const component = await mount(ProductsPage, { + hooksConfig: { enableRouting: true }, + }); + await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42'); + }); + ``` + + + + ## Under the hood @@ -293,23 +402,23 @@ test('…', async { mount, page, context } => { defaultValue="react" values={[ {label: 'React', value: 'react'}, - {label: 'Vue', value: 'vue'}, - {label: 'Svelte', value: 'svelte'}, {label: 'Solid', value: 'solid'}, + {label: 'Svelte', value: 'svelte'}, + {label: 'Vue', value: 'vue'}, ] }> ```js -import { test, expect } from '@playwright/experimental-ct-react' -import HelloWorld from './HelloWorld' +import { test, expect } from '@playwright/experimental-ct-react'; +import HelloWorld from './HelloWorld'; -test.use({ viewport: { width: 500, height: 500 } }) +test.use({ viewport: { width: 500, height: 500 } }); test('should work', async ({ mount }) => { const component = await mount(); - await expect(component).toContainText('Greetings') -}) + await expect(component).toContainText('Greetings'); +}); ``` @@ -317,19 +426,19 @@ test('should work', async ({ mount }) => { ```js -import { test, expect } from '@playwright/experimental-ct-vue' -import HelloWorld from './HelloWorld.vue' +import { test, expect } from '@playwright/experimental-ct-vue'; +import HelloWorld from './HelloWorld.vue'; -test.use({ viewport: { width: 500, height: 500 } }) +test.use({ viewport: { width: 500, height: 500 } }); test('should work', async ({ mount }) => { const component = await mount(HelloWorld, { props: { - msg: 'Greetings' - } + msg: 'Greetings', + }, }); - await expect(component).toContainText('Greetings') -}) + await expect(component).toContainText('Greetings'); +}); ``` @@ -337,19 +446,19 @@ test('should work', async ({ mount }) => { ```js -import { test, expect } from '@playwright/experimental-ct-svelte' -import HelloWorld from './HelloWorld.svelte' +import { test, expect } from '@playwright/experimental-ct-svelte'; +import HelloWorld from './HelloWorld.svelte'; -test.use({ viewport: { width: 500, height: 500 } }) +test.use({ viewport: { width: 500, height: 500 } }); test('should work', async ({ mount }) => { const component = await mount(HelloWorld, { props: { - msg: 'Greetings' - } + msg: 'Greetings', + }, }); - await expect(component).toContainText('Greetings') -}) + await expect(component).toContainText('Greetings'); +}); ``` @@ -357,15 +466,15 @@ test('should work', async ({ mount }) => { ```js -import { test, expect } from '@playwright/experimental-ct-solid' -import HelloWorld from './HelloWorld' +import { test, expect } from '@playwright/experimental-ct-solid'; +import HelloWorld from './HelloWorld'; -test.use({ viewport: { width: 500, height: 500 } }) +test.use({ viewport: { width: 500, height: 500 } }); test('should work', async ({ mount }) => { const component = await mount(); - await expect(component).toContainText('Greetings') -}) + await expect(component).toContainText('Greetings'); +}); ``` @@ -374,7 +483,7 @@ test('should work', async ({ mount }) => { Additionally, it adds some config options you can use in your `playwright-ct.config.{ts,js}`. -Finally, under the hood, each test re-uses the `context` and `page` fixture as a speed optimization for Component Testing. +Finally, under the hood, each test re-uses the `context` and `page` fixture as a speed optimization for Component Testing. It resets them in between each test so it should be functionally equivalent to `@playwright/test`'s guarantee that you get a new, isolated `context` and `page` fixture per-test. ### Q) Can I use `@playwright/test` and `@playwright/experimental-ct-{react,svelte,vue,solid}`? @@ -407,12 +516,12 @@ await mount(); You can specify plugins via Vite config for testing settings. Note that once you start specifying plugins, you are responsible for specifying the framework plugin as well, `vue()` in this case: ```js -import { defineConfig, devices } from '@playwright/experimental-ct-vue' +import { defineConfig, devices } from '@playwright/experimental-ct-vue'; -import { resolve } from 'path' -import vue from '@vitejs/plugin-vue' -import AutoImport from 'unplugin-auto-import/vite' -import Components from 'unplugin-vue-components/vite' +import { resolve } from 'path'; +import vue from '@vitejs/plugin-vue'; +import AutoImport from 'unplugin-auto-import/vite'; +import Components from 'unplugin-vue-components/vite'; export default defineConfig({ testDir: './tests/component', @@ -447,21 +556,21 @@ export default defineConfig({ }, }, }, - } + }, }); ``` -don't forget to initialize your plugins, for example if you are using Pinia, add init code into your `playwright/index.js`: +don't forget to initialize your plugins, for example if you are using Pinia, add init code into your `playwright/index.{js,ts,jsx,tsx}`: ```js import { createTestingPinia } from '@pinia/testing'; createTestingPinia({ createSpy: (args) => { - console.log('spy', args) + console.log('spy', args); return () => { - console.log('spyreturns') - } + console.log('spyreturns'); + }; }, }); ```