diff --git a/docs/src/auth.md b/docs/src/auth.md index 4ca67b34a6..31f821f88e 100644 --- a/docs/src/auth.md +++ b/docs/src/auth.md @@ -166,65 +166,91 @@ When using [Playwright Test](./intro.md), you can log in once in the global setu and then reuse authentication state in tests. That way all your tests are completely isolated, yet you only waste time logging in once for the entire test suite run. -First, introduce the global setup that would log in once. +First, introduce the global setup that would sign in once. In this example we use the `baseURL` and `storageState` options from the configuration file. ```js js-flavor=js // global-setup.js const { chromium } = require('@playwright/test'); -module.exports = async () => { +module.exports = async config => { + const { baseURL, storageState } = config.projects[0].use; const browser = await chromium.launch(); const page = await browser.newPage(); - await page.goto('http://localhost:5000/'); - await page.click('text=login'); + await page.goto(baseURL); await page.fill('input[name="user"]', 'user'); await page.fill('input[name="password"]', 'password'); - await page.click('input:has-text("login")'); - await page.context().storageState({ path: 'state.json' }); + await page.click('text=Sign in'); + await page.context().storageState({ path: storageState }); await browser.close(); }; ``` ```js js-flavor=ts // global-setup.ts -import { chromium } from '@playwright/test'; +import { chromium, FullConfig } from '@playwright/test'; -async function globalSetup() { +async function globalSetup(config: FullConfig) { + const { baseURL, storageState } = config.projects[0].use; const browser = await chromium.launch(); const page = await browser.newPage(); - await page.goto('http://localhost:5000/'); - await page.click('text=login'); + await page.goto(baseURL); await page.fill('input[name="user"]', 'user'); await page.fill('input[name="password"]', 'password'); - await page.click('input:has-text("login")'); - await page.context().storageState({ path: 'state.json' }); + await page.click('text=Sign in'); + await page.context().storageState({ path: storageState }); await browser.close(); } export default globalSetup; ``` -Then reuse saved authentication state in your tests. +Next specify `globalSetup`, `baseURL` and `storageState` in the configuration file. + +```js js-flavor=js +// playwright.config.js +// @ts-check +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +const config = { + globalSetup: require.resolve('./global-setup'), + use: { + baseURL: 'http://localhost:3000/', + storageState: 'state.json', + }, +}; +module.exports = config; +``` + +```js js-flavor=ts +// playwright.config.ts +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + globalSetup: require.resolve('./global-setup'), + use: { + baseURL: 'http://localhost:3000/', + storageState: 'state.json', + }, +}; +export default config; +``` + +Tests start already authenticated because we specify `storageState` that was populated by global setup. ```js js-flavor=ts import { test } from '@playwright/test'; -test.use({ storageState: 'state.json' }); - test('test', async ({ page }) => { - await page.goto('http://localhost:5000/'); - // You are logged in! + await page.goto('/'); + // You are signed in! }); ``` ```js js-flavor=js const { test } = require('@playwright/test'); -test.use({ storageState: 'state.json' }); - test('test', async ({ page }) => { - await page.goto('http://localhost:5000/'); - // You are logged in! + await page.goto('/'); + // You are signed in! }); ``` diff --git a/docs/src/test-advanced-js.md b/docs/src/test-advanced-js.md index 96bf1a3d7e..d0d25b89c8 100644 --- a/docs/src/test-advanced-js.md +++ b/docs/src/test-advanced-js.md @@ -270,50 +270,49 @@ test('test', async ({ page, baseURL }) => { ## Global setup and teardown -To set something up once before running all tests, use `globalSetup` option in the [configuration file](#configuration-object). +To set something up once before running all tests, use `globalSetup` option in the [configuration file](#configuration-object). Global setup file must export a single function that takes a config object. This function will be run once before all the tests. Similarly, use `globalTeardown` to run something once after all the tests. Alternatively, let `globalSetup` return a function that will be used as a global teardown. You can pass data such as port number, authentication tokens, etc. from your global setup to your tests using environment. -Here is a global setup example that runs an app. +Here is a global setup example that authenticates once and reuses authentication state in tests. It uses `baseURL` and `storageState` options from the configuration file. + ```js js-flavor=js // global-setup.js -const app = require('./my-app'); +const { chromium } = require('@playwright/test'); -module.exports = async () => { - const server = require('http').createServer(app); - await new Promise(done => server.listen(done)); - - // Expose port to the tests. - process.env.SERVER_PORT = String(server.address().port); - - // Return the teardown function. - return async () => { - await new Promise(done => server.close(done)); - }; +module.exports = async config => { + const { baseURL, storageState } = config.projects[0].use; + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(baseURL); + await page.fill('input[name="user"]', 'user'); + await page.fill('input[name="password"]', 'password'); + await page.click('text=Sign in'); + await page.context().storageState({ path: storageState }); + await browser.close(); }; ``` ```js js-flavor=ts // global-setup.ts -import app from './my-app'; -import * as http from 'http'; +import { chromium, FullConfig } from '@playwright/test'; -async function globalSetup() { - const server = http.createServer(app); - await new Promise(done => server.listen(done)); - - // Expose port to the tests. - process.env.SERVER_PORT = String(server.address().port); - - // Return the teardown function. - return async () => { - await new Promise(done => server.close(done)); - }; +async function globalSetup(config: FullConfig) { + const { baseURL, storageState } = config.projects[0].use; + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(baseURL); + await page.fill('input[name="user"]', 'user'); + await page.fill('input[name="password"]', 'password'); + await page.click('text=Sign in'); + await page.context().storageState({ path: storageState }); + await browser.close(); } + export default globalSetup; ``` -Now add `globalSetup` option to the configuration file. +Specify `globalSetup`, `baseURL` and `storageState` in the configuration file. ```js js-flavor=js // playwright.config.js @@ -321,6 +320,10 @@ Now add `globalSetup` option to the configuration file. /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { globalSetup: require.resolve('./global-setup'), + use: { + baseURL: 'http://localhost:3000/', + storageState: 'state.json', + }, }; module.exports = config; ``` @@ -331,27 +334,31 @@ import { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { globalSetup: require.resolve('./global-setup'), + use: { + baseURL: 'http://localhost:3000/', + storageState: 'state.json', + }, }; export default config; ``` -Tests will now run after the global setup is done and will have access to the data created in the global setup: +Tests start already authenticated because we specify `storageState` that was populated by global setup. -```js js-flavor=js -// test.spec.js -const { test } = require('@playwright/test'); +```js js-flavor=ts +import { test } from '@playwright/test'; -test('test', async ({ }) => { - console.log(process.env.SERVER_PORT); +test('test', async ({ page }) => { + await page.goto('/'); + // You are signed in! }); ``` -```js js-flavor=ts -// test.spec.ts -import { test } = from '@playwright/test'; +```js js-flavor=js +const { test } = require('@playwright/test'); -test('test', async ({ }) => { - console.log(process.env.SERVER_PORT); +test('test', async ({ page }) => { + await page.goto('/'); + // You are signed in! }); ``` diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 09e738aa6b..6f50da901d 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -48,7 +48,7 @@ Whether to exit with an error if any tests or groups are marked as [`method: Tes ## property: TestConfig.globalSetup - type: <[string]> -Path to the global setup file. This file will be required and run before all the tests. It must export a single function. +Path to the global setup file. This file will be required and run before all the tests. It must export a single function that takes a [`TestConfig`] argument. Learn more about [global setup and teardown](./test-advanced.md#global-setup-and-teardown). diff --git a/tests/playwright-test/entry/index.js b/tests/playwright-test/entry/index.js index 53fb7dce54..fede33784a 100644 --- a/tests/playwright-test/entry/index.js +++ b/tests/playwright-test/entry/index.js @@ -14,4 +14,11 @@ * limitations under the License. */ -module.exports = require('../../../lib/test/index'); +const pwt = require('../../../lib/test/index'); +const playwright = require('../../../lib/inprocess'); +const combinedExports = { + ...playwright, + ...pwt, +}; +Object.defineProperty(combinedExports, '__esModule', { value: true }); +module.exports = combinedExports; diff --git a/tests/playwright-test/global-setup.spec.ts b/tests/playwright-test/global-setup.spec.ts index a661bed8da..6c35c8a186 100644 --- a/tests/playwright-test/global-setup.spec.ts +++ b/tests/playwright-test/global-setup.spec.ts @@ -236,4 +236,47 @@ test('globalSetup should allow requiring a package from node_modules', async ({ `, }); expect(results[0].status).toBe('passed'); -}); \ No newline at end of file +}); + +test('globalSetup should work for auth', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { + globalSetup: require.resolve('./auth.js'), + use: { + baseURL: 'https://www.example.com', + storageState: 'state.json', + }, + }; + `, + 'auth.js': ` + module.exports = async config => { + const { baseURL, storageState } = config.projects[0].use; + const browser = await pwt.chromium.launch(); + const page = await browser.newPage(); + await page.route('**/*', route => { + route.fulfill({ body: '' }).catch(() => {}); + }); + await page.goto(baseURL); + await page.evaluate(() => { + localStorage['name'] = 'value'; + }); + await page.context().storageState({ path: storageState }); + await browser.close(); + }; + `, + 'a.test.js': ` + const { test } = pwt; + test('should have storage state', async ({ page }) => { + await page.route('**/*', route => { + route.fulfill({ body: '' }).catch(() => {}); + }); + await page.goto('/'); + const value = await page.evaluate(() => localStorage['name']); + expect(value).toBe('value'); + }); + `, + }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); diff --git a/types/test.d.ts b/types/test.d.ts index 355dbe3d19..25519172a0 100644 --- a/types/test.d.ts +++ b/types/test.d.ts @@ -558,8 +558,8 @@ interface TestConfig { */ forbidOnly?: boolean; /** - * Path to the global setup file. This file will be required and run before all the tests. It must export a single - * function. + * Path to the global setup file. This file will be required and run before all the tests. It must export a single function + * that takes a [`TestConfig`] argument. * * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). * @@ -908,8 +908,8 @@ export interface FullConfig { */ forbidOnly: boolean; /** - * Path to the global setup file. This file will be required and run before all the tests. It must export a single - * function. + * Path to the global setup file. This file will be required and run before all the tests. It must export a single function + * that takes a [`TestConfig`] argument. * * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). *