mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(test runner): replace declare/define with "options" (#10293)
1. Fixtures defined in test.extend() can now have `{ option: true }` configuration that makes them overridable in the config. Options support all other properties of fixtures - value/function, scope, auto. ``` const test = base.extend<MyOptions>({ foo: ['default', { option: true }], }); ``` 2. test.declare() and project.define are removed. 3. project.use applies overrides to default option values and nothing else. Any test.extend() and test.use() calls take priority over config options. Required user changes: if someone used to define fixture options with test.extend(), overriding them in config will stop working. The solution is to add `{ option: true }`. ``` // Old code export const test = base.extend<{ myOption: number, myFixture: number }>({ myOption: 123, myFixture: ({ myOption }, use) => use(2 * myOption), }); // New code export const test = base.extend<{ myOption: number, myFixture: number }>({ myOption: [123, { option: true }], myFixture: ({ myOption }, use) => use(2 * myOption), }); ```
This commit is contained in:
parent
17a2454226
commit
d9f849fb14
@ -367,7 +367,7 @@ test('test', async ({ page }) => {
|
|||||||
|
|
||||||
Playwright Test supports running multiple test projects at the same time. This is useful for running the same tests in multiple configurations. For example, consider running tests against multiple versions of some REST backend.
|
Playwright Test supports running multiple test projects at the same time. This is useful for running the same tests in multiple configurations. For example, consider running tests against multiple versions of some REST backend.
|
||||||
|
|
||||||
To make use of this feature, we will declare an "option fixture" for the backend version, and use it in the tests.
|
In the following example, we will declare an option for the backend version, and a fixture that uses the option, and we'll be configuring two projects that test against different versions.
|
||||||
|
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// my-test.js
|
// my-test.js
|
||||||
@ -375,8 +375,9 @@ const base = require('@playwright/test');
|
|||||||
const { startBackend } = require('./my-backend');
|
const { startBackend } = require('./my-backend');
|
||||||
|
|
||||||
exports.test = base.test.extend({
|
exports.test = base.test.extend({
|
||||||
// Default value for the version.
|
// Define an option and provide a default value.
|
||||||
version: '1.0',
|
// We can later override it in the config.
|
||||||
|
version: ['1.0', { option: true }],
|
||||||
|
|
||||||
// Use version when starting the backend.
|
// Use version when starting the backend.
|
||||||
backendURL: async ({ version }, use) => {
|
backendURL: async ({ version }, use) => {
|
||||||
@ -392,14 +393,13 @@ exports.test = base.test.extend({
|
|||||||
import { test as base } from '@playwright/test';
|
import { test as base } from '@playwright/test';
|
||||||
import { startBackend } from './my-backend';
|
import { startBackend } from './my-backend';
|
||||||
|
|
||||||
export type TestOptions = {
|
export type TestOptions = { version: string };
|
||||||
version: string;
|
type TestFixtures = { backendURL: string };
|
||||||
backendURL: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const test = base.extend<TestOptions>({
|
export const test = base.extend<TestOptions & TestFixtures>({
|
||||||
// Default value for the version.
|
// Define an option and provide a default value.
|
||||||
version: '1.0',
|
// We can later override it in the config.
|
||||||
|
version: ['1.0', { option: true }],
|
||||||
|
|
||||||
// Use version when starting the backend.
|
// Use version when starting the backend.
|
||||||
backendURL: async ({ version }, use) => {
|
backendURL: async ({ version }, use) => {
|
||||||
@ -410,7 +410,7 @@ export const test = base.extend<TestOptions>({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
We can use our fixtures in the test.
|
We can use our fixture and/or option in the test.
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// example.spec.js
|
// example.spec.js
|
||||||
const { test } = require('./my-test');
|
const { test } = require('./my-test');
|
||||||
@ -445,14 +445,13 @@ test('test 2', async ({ version, page, backendURL }) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we can run test in multiple configurations by using projects.
|
Now, we can run tests in multiple configurations by using projects.
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig<{ version: string }>} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig<{ version: string }>} */
|
||||||
const config = {
|
const config = {
|
||||||
timeout: 20000,
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'v1',
|
name: 'v1',
|
||||||
@ -474,7 +473,6 @@ import { PlaywrightTestConfig } from '@playwright/test';
|
|||||||
import { TestOptions } from './my-test';
|
import { TestOptions } from './my-test';
|
||||||
|
|
||||||
const config: PlaywrightTestConfig<TestOptions> = {
|
const config: PlaywrightTestConfig<TestOptions> = {
|
||||||
timeout: 20000,
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'v1',
|
name: 'v1',
|
||||||
@ -489,7 +487,7 @@ const config: PlaywrightTestConfig<TestOptions> = {
|
|||||||
export default config;
|
export default config;
|
||||||
```
|
```
|
||||||
|
|
||||||
Each project can be configured separately, and run different set of tests with different parameters. See [project options][TestProject] for the list of options available to each project.
|
Each project can be configured separately, and run different set of tests with different of [built-in][TestProject] and custom options.
|
||||||
|
|
||||||
You can run all projects or just a single one:
|
You can run all projects or just a single one:
|
||||||
```bash
|
```bash
|
||||||
|
@ -204,6 +204,7 @@ Hook function that takes one or two arguments: an object with fixtures and optio
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## method: Test.describe
|
## method: Test.describe
|
||||||
|
|
||||||
Declares a group of tests.
|
Declares a group of tests.
|
||||||
@ -418,6 +419,137 @@ A callback that is run immediately when calling [`method: Test.describe.serial.o
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## method: Test.extend
|
||||||
|
- returns: <[Test]>
|
||||||
|
|
||||||
|
Extends the `test` object by defining fixtures and/or options that can be used in the tests.
|
||||||
|
|
||||||
|
First define a fixture and/or an option.
|
||||||
|
|
||||||
|
```js js-flavor=js
|
||||||
|
// my-test.js
|
||||||
|
const base = require('@playwright/test');
|
||||||
|
const { TodoPage } = require('./todo-page');
|
||||||
|
|
||||||
|
// Extend basic test by providing a "defaultItem" option and a "todoPage" fixture.
|
||||||
|
exports.test = base.test.extend({
|
||||||
|
// Define an option and provide a default value.
|
||||||
|
// We can later override it in the config.
|
||||||
|
defaultItem: ['Do stuff', { option: true }],
|
||||||
|
|
||||||
|
// Define a fixture. Note that it can use built-in fixture "page"
|
||||||
|
// and a new option "defaultItem".
|
||||||
|
todoPage: async ({ page, defaultItem }, use) => {
|
||||||
|
const todoPage = new TodoPage(page);
|
||||||
|
await todoPage.goto();
|
||||||
|
await todoPage.addToDo(defaultItem);
|
||||||
|
await use(todoPage);
|
||||||
|
await todoPage.removeAll();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```js js-flavor=ts
|
||||||
|
import { test as base } from '@playwright/test';
|
||||||
|
import { TodoPage } from './todo-page';
|
||||||
|
|
||||||
|
export type Options = { defaultItem: string };
|
||||||
|
|
||||||
|
// Extend basic test by providing a "defaultItem" option and a "todoPage" fixture.
|
||||||
|
export const test = base.extend<Options & { todoPage: TodoPage }>({
|
||||||
|
// Define an option and provide a default value.
|
||||||
|
// We can later override it in the config.
|
||||||
|
defaultItem: ['Do stuff', { option: true }],
|
||||||
|
|
||||||
|
// Define a fixture. Note that it can use built-in fixture "page"
|
||||||
|
// and a new option "defaultItem".
|
||||||
|
todoPage: async ({ page, defaultItem }, use) => {
|
||||||
|
const todoPage = new TodoPage(page);
|
||||||
|
await todoPage.goto();
|
||||||
|
await todoPage.addToDo(defaultItem);
|
||||||
|
await use(todoPage);
|
||||||
|
await todoPage.removeAll();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the fixture in the test.
|
||||||
|
|
||||||
|
```js js-flavor=js
|
||||||
|
// example.spec.js
|
||||||
|
const { test } = require('./my-test');
|
||||||
|
|
||||||
|
test('test 1', async ({ todoPage }) => {
|
||||||
|
await todoPage.addToDo('my todo');
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```js js-flavor=ts
|
||||||
|
// example.spec.ts
|
||||||
|
import { test } from './my-test';
|
||||||
|
|
||||||
|
test('test 1', async ({ todoPage }) => {
|
||||||
|
await todoPage.addToDo('my todo');
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the option in config file.
|
||||||
|
|
||||||
|
```js js-flavor=js
|
||||||
|
// playwright.config.js
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/** @type {import('@playwright/test').PlaywrightTestConfig<{ defaultItem: string }>} */
|
||||||
|
const config = {
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'shopping',
|
||||||
|
use: { defaultItem: 'Buy milk' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wellbeing',
|
||||||
|
use: { defaultItem: 'Exercise!' },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
```
|
||||||
|
|
||||||
|
```js js-flavor=ts
|
||||||
|
// playwright.config.ts
|
||||||
|
import { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
import { Options } from './my-test';
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig<Options> = {
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'shopping',
|
||||||
|
use: { defaultItem: 'Buy milk' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wellbeing',
|
||||||
|
use: { defaultItem: 'Exercise!' },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more about [fixtures](./test-fixtures.md) and [parametrizing tests](./test-parameterize.md).
|
||||||
|
|
||||||
|
### param: Test.extend.fixtures
|
||||||
|
- `fixtures` <[Object]>
|
||||||
|
|
||||||
|
An object containing fixtures and/or options. Learn more about [fixtures format](./test-fixtures.md).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## method: Test.fail
|
## method: Test.fail
|
||||||
|
|
||||||
Marks a test or a group of tests as "should fail". Playwright Test runs these tests and ensures that they are actually failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed.
|
Marks a test or a group of tests as "should fail". Playwright Test runs these tests and ensures that they are actually failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed.
|
||||||
|
@ -82,7 +82,7 @@ test('should remove an item', async ({ todoPage }) => {
|
|||||||
import { test as base } from '@playwright/test';
|
import { test as base } from '@playwright/test';
|
||||||
import { TodoPage } from './todo-page';
|
import { TodoPage } from './todo-page';
|
||||||
|
|
||||||
// Extend basic test by providing a "table" fixture.
|
// Extend basic test by providing a "todoPage" fixture.
|
||||||
const test = base.extend<{ todoPage: TodoPage }>({
|
const test = base.extend<{ todoPage: TodoPage }>({
|
||||||
todoPage: async ({ page }, use) => {
|
todoPage: async ({ page }, use) => {
|
||||||
const todoPage = new TodoPage(page);
|
const todoPage = new TodoPage(page);
|
||||||
@ -139,21 +139,21 @@ test('hello world', ({ helloWorld }) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
It uses fixtures `hello` and `helloWorld` that are set up by the framework for each test run.
|
It uses an option `hello` and a fixture `helloWorld` that are set up by the framework for each test run.
|
||||||
|
|
||||||
Here is how test fixtures are declared and defined. Fixtures can use other fixtures - note how `helloWorld` uses `hello`.
|
Here is how test fixtures are defined. Fixtures can use other fixtures and/or options - note how `helloWorld` uses `hello`.
|
||||||
|
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// hello.js
|
// hello.js
|
||||||
const base = require('@playwright/test');
|
const base = require('@playwright/test');
|
||||||
|
|
||||||
// Extend base test with fixtures "hello" and "helloWorld".
|
// Extend base test with our options and fixtures.
|
||||||
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
|
const test = base.test.extend({
|
||||||
module.exports = base.test.extend({
|
// Define an option and provide a default value.
|
||||||
// This fixture is a constant, so we can just provide the value.
|
// We can later override it in the config.
|
||||||
hello: 'Hello',
|
hello: ['Hello', { option: true }],
|
||||||
|
|
||||||
// This fixture has some complex logic and is defined with a function.
|
// Define a fixture.
|
||||||
helloWorld: async ({ hello }, use) => {
|
helloWorld: async ({ hello }, use) => {
|
||||||
// Set up the fixture.
|
// Set up the fixture.
|
||||||
const value = hello + ', world!';
|
const value = hello + ', world!';
|
||||||
@ -164,24 +164,29 @@ module.exports = base.test.extend({
|
|||||||
// Clean up the fixture. Nothing to cleanup in this example.
|
// Clean up the fixture. Nothing to cleanup in this example.
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
|
||||||
|
module.exports = test;
|
||||||
```
|
```
|
||||||
|
|
||||||
```js js-flavor=ts
|
```js js-flavor=ts
|
||||||
// hello.ts
|
// hello.ts
|
||||||
import { test as base } from '@playwright/test';
|
import { test as base } from '@playwright/test';
|
||||||
|
|
||||||
// Define test fixtures "hello" and "helloWorld".
|
type TestOptions = {
|
||||||
type TestFixtures = {
|
|
||||||
hello: string;
|
hello: string;
|
||||||
|
};
|
||||||
|
type TestFixtures = {
|
||||||
helloWorld: string;
|
helloWorld: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extend base test with our fixtures.
|
// Extend base test with our options and fixtures.
|
||||||
const test = base.extend<TestFixtures>({
|
const test = base.extend<TestOptions & TestFixtures>({
|
||||||
// This fixture is a constant, so we can just provide the value.
|
// Define an option and provide a default value.
|
||||||
hello: 'Hello',
|
// We can later override it in the config.
|
||||||
|
hello: ['Hello', { option: true }],
|
||||||
|
|
||||||
// This fixture has some complex logic and is defined with a function.
|
// Define a fixture.
|
||||||
helloWorld: async ({ hello }, use) => {
|
helloWorld: async ({ hello }, use) => {
|
||||||
// Set up the fixture.
|
// Set up the fixture.
|
||||||
const value = hello + ', world!';
|
const value = hello + ', world!';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
id: test-parameterize
|
id: test-parameterize
|
||||||
title: "Parameterize tests"
|
title: "Parametrize tests"
|
||||||
---
|
---
|
||||||
|
|
||||||
You can either parameterize tests on a test level or on a project level.
|
You can either parametrize tests on a test level or on a project level.
|
||||||
|
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
|
|
||||||
@ -33,16 +33,18 @@ for (const name of people) {
|
|||||||
|
|
||||||
## Parametrized Projects
|
## Parametrized Projects
|
||||||
|
|
||||||
Playwright Test supports running multiple test projects at the same time. In the following example, we'll run two projects with different parameters.
|
Playwright Test supports running multiple test projects at the same time. In the following example, we'll run two projects with different options.
|
||||||
A parameter itself is represented as a [`fixture`](./api/class-fixtures), where the value gets set from the config. The first project runs with the value `Alice` and the second with the value `Bob`.
|
|
||||||
|
We declare the option `person` and set the value in the config. The first project runs with the value `Alice` and the second with the value `Bob`.
|
||||||
|
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// my-test.js
|
// my-test.js
|
||||||
const base = require('@playwright/test');
|
const base = require('@playwright/test');
|
||||||
|
|
||||||
exports.test = base.test.extend({
|
exports.test = base.test.extend({
|
||||||
// Default value for person.
|
// Define an option and provide a default value.
|
||||||
person: 'not-set',
|
// We can later override it in the config.
|
||||||
|
person: ['John', { option: true }],
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -55,12 +57,14 @@ export type TestOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const test = base.extend<TestOptions>({
|
export const test = base.extend<TestOptions>({
|
||||||
// Default value for the person.
|
// Define an option and provide a default value.
|
||||||
person: 'not-set',
|
// We can later override it in the config.
|
||||||
|
person: ['John', { option: true }],
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
We can use our fixtures in the test.
|
We can use this option in the test, similarly to [fixtures](./test-fixtures.md).
|
||||||
|
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// example.spec.js
|
// example.spec.js
|
||||||
const { test } = require('./my-test');
|
const { test } = require('./my-test');
|
||||||
@ -83,7 +87,8 @@ test('test 1', async ({ page, person }) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we can run test in multiple configurations by using projects.
|
Now, we can run tests in multiple configurations by using projects.
|
||||||
|
|
||||||
```js js-flavor=js
|
```js js-flavor=js
|
||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
@ -92,11 +97,11 @@ Now, we can run test in multiple configurations by using projects.
|
|||||||
const config = {
|
const config = {
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'Alice',
|
name: 'alice',
|
||||||
use: { person: 'Alice' },
|
use: { person: 'Alice' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Bob',
|
name: 'bob',
|
||||||
use: { person: 'Bob' },
|
use: { person: 'Bob' },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -111,17 +116,64 @@ import { PlaywrightTestConfig } from '@playwright/test';
|
|||||||
import { TestOptions } from './my-test';
|
import { TestOptions } from './my-test';
|
||||||
|
|
||||||
const config: PlaywrightTestConfig<TestOptions> = {
|
const config: PlaywrightTestConfig<TestOptions> = {
|
||||||
timeout: 20000,
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'alice',
|
name: 'alice',
|
||||||
use: { person: 'Alice' },
|
use: { person: 'Alice' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Bob',
|
name: 'bob',
|
||||||
use: { person: 'Bob' },
|
use: { person: 'Bob' },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We can also use the option in a fixture. Learn more about [fixtures](./test-fixtures.md).
|
||||||
|
|
||||||
|
```js js-flavor=js
|
||||||
|
// my-test.js
|
||||||
|
const base = require('@playwright/test');
|
||||||
|
|
||||||
|
exports.test = base.test.extend({
|
||||||
|
// Define an option and provide a default value.
|
||||||
|
// We can later override it in the config.
|
||||||
|
person: ['John', { option: true }],
|
||||||
|
|
||||||
|
// Override default "page" fixture.
|
||||||
|
page: async ({ page, person }, use) => {
|
||||||
|
await page.goto('/chat');
|
||||||
|
// We use "person" parameter as a "name" for the chat room.
|
||||||
|
await page.locator('#name').fill(person);
|
||||||
|
await page.click('text=Enter chat room');
|
||||||
|
// Each test will get a "page" that already has the person name.
|
||||||
|
await use(page);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```js js-flavor=ts
|
||||||
|
// my-test.ts
|
||||||
|
import { test as base } from '@playwright/test';
|
||||||
|
|
||||||
|
export type TestOptions = {
|
||||||
|
person: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const test = base.test.extend<TestOptions>({
|
||||||
|
// Define an option and provide a default value.
|
||||||
|
// We can later override it in the config.
|
||||||
|
person: ['John', { option: true }],
|
||||||
|
|
||||||
|
// Override default "page" fixture.
|
||||||
|
page: async ({ page, person }, use) => {
|
||||||
|
await page.goto('/chat');
|
||||||
|
// We use "person" parameter as a "name" for the chat room.
|
||||||
|
await page.locator('#name').fill(person);
|
||||||
|
await page.click('text=Enter chat room');
|
||||||
|
// Each test will get a "page" that already has the person name.
|
||||||
|
await use(page);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
@ -103,6 +103,14 @@ class Fixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFixtureTuple(value: any): value is [any, any] {
|
||||||
|
return Array.isArray(value) && typeof value[1] === 'object' && ('scope' in value[1] || 'auto' in value[1] || 'option' in value[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFixtureOption(value: any): value is [any, any] {
|
||||||
|
return isFixtureTuple(value) && !!value[1].option;
|
||||||
|
}
|
||||||
|
|
||||||
export class FixturePool {
|
export class FixturePool {
|
||||||
readonly digest: string;
|
readonly digest: string;
|
||||||
readonly registrations: Map<string, FixtureRegistration>;
|
readonly registrations: Map<string, FixtureRegistration>;
|
||||||
@ -115,7 +123,7 @@ export class FixturePool {
|
|||||||
const name = entry[0];
|
const name = entry[0];
|
||||||
let value = entry[1];
|
let value = entry[1];
|
||||||
let options: { auto: boolean, scope: FixtureScope } | undefined;
|
let options: { auto: boolean, scope: FixtureScope } | undefined;
|
||||||
if (Array.isArray(value) && typeof value[1] === 'object' && ('scope' in value[1] || 'auto' in value[1])) {
|
if (isFixtureTuple(value)) {
|
||||||
options = {
|
options = {
|
||||||
auto: !!value[1].auto,
|
auto: !!value[1].auto,
|
||||||
scope: value[1].scope || 'test'
|
scope: value[1].scope || 'test'
|
||||||
|
@ -31,16 +31,16 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
|||||||
_setupContextOptionsAndArtifacts: void;
|
_setupContextOptionsAndArtifacts: void;
|
||||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||||
};
|
};
|
||||||
type WorkerAndFileFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||||
_browserType: BrowserType;
|
_browserType: BrowserType;
|
||||||
_browserOptions: LaunchOptions;
|
_browserOptions: LaunchOptions;
|
||||||
_artifactsDir: () => string;
|
_artifactsDir: () => string;
|
||||||
_snapshotSuffix: string;
|
_snapshotSuffix: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
|
||||||
defaultBrowserType: [ 'chromium', { scope: 'worker' } ],
|
defaultBrowserType: [ 'chromium', { scope: 'worker', option: true } ],
|
||||||
browserName: [ ({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker' } ],
|
browserName: [ ({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker', option: true } ],
|
||||||
playwright: [async ({}, use, workerInfo) => {
|
playwright: [async ({}, use, workerInfo) => {
|
||||||
if (process.env.PW_GRID) {
|
if (process.env.PW_GRID) {
|
||||||
const gridClient = await GridClient.connect(process.env.PW_GRID);
|
const gridClient = await GridClient.connect(process.env.PW_GRID);
|
||||||
@ -50,12 +50,12 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
|||||||
await use(require('playwright-core'));
|
await use(require('playwright-core'));
|
||||||
}
|
}
|
||||||
}, { scope: 'worker' } ],
|
}, { scope: 'worker' } ],
|
||||||
headless: [ undefined, { scope: 'worker' } ],
|
headless: [ undefined, { scope: 'worker', option: true } ],
|
||||||
channel: [ undefined, { scope: 'worker' } ],
|
channel: [ undefined, { scope: 'worker', option: true } ],
|
||||||
launchOptions: [ {}, { scope: 'worker' } ],
|
launchOptions: [ {}, { scope: 'worker', option: true } ],
|
||||||
screenshot: [ 'off', { scope: 'worker' } ],
|
screenshot: [ 'off', { scope: 'worker', option: true } ],
|
||||||
video: [ 'off', { scope: 'worker' } ],
|
video: [ 'off', { scope: 'worker', option: true } ],
|
||||||
trace: [ 'off', { scope: 'worker' } ],
|
trace: [ 'off', { scope: 'worker', option: true } ],
|
||||||
|
|
||||||
_artifactsDir: [async ({}, use, workerInfo) => {
|
_artifactsDir: [async ({}, use, workerInfo) => {
|
||||||
let dir: string | undefined;
|
let dir: string | undefined;
|
||||||
@ -74,31 +74,31 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
|||||||
_browserType: [browserTypeWorkerFixture, { scope: 'worker' }],
|
_browserType: [browserTypeWorkerFixture, { scope: 'worker' }],
|
||||||
browser: [browserWorkerFixture, { scope: 'worker' } ],
|
browser: [browserWorkerFixture, { scope: 'worker' } ],
|
||||||
|
|
||||||
acceptDownloads: undefined,
|
acceptDownloads: [ undefined, { option: true } ],
|
||||||
bypassCSP: undefined,
|
bypassCSP: [ undefined, { option: true } ],
|
||||||
colorScheme: undefined,
|
colorScheme: [ undefined, { option: true } ],
|
||||||
deviceScaleFactor: undefined,
|
deviceScaleFactor: [ undefined, { option: true } ],
|
||||||
extraHTTPHeaders: undefined,
|
extraHTTPHeaders: [ undefined, { option: true } ],
|
||||||
geolocation: undefined,
|
geolocation: [ undefined, { option: true } ],
|
||||||
hasTouch: undefined,
|
hasTouch: [ undefined, { option: true } ],
|
||||||
httpCredentials: undefined,
|
httpCredentials: [ undefined, { option: true } ],
|
||||||
ignoreHTTPSErrors: undefined,
|
ignoreHTTPSErrors: [ undefined, { option: true } ],
|
||||||
isMobile: undefined,
|
isMobile: [ undefined, { option: true } ],
|
||||||
javaScriptEnabled: undefined,
|
javaScriptEnabled: [ undefined, { option: true } ],
|
||||||
locale: undefined,
|
locale: [ undefined, { option: true } ],
|
||||||
offline: undefined,
|
offline: [ undefined, { option: true } ],
|
||||||
permissions: undefined,
|
permissions: [ undefined, { option: true } ],
|
||||||
proxy: undefined,
|
proxy: [ undefined, { option: true } ],
|
||||||
storageState: undefined,
|
storageState: [ undefined, { option: true } ],
|
||||||
timezoneId: undefined,
|
timezoneId: [ undefined, { option: true } ],
|
||||||
userAgent: undefined,
|
userAgent: [ undefined, { option: true } ],
|
||||||
viewport: undefined,
|
viewport: [ undefined, { option: true } ],
|
||||||
actionTimeout: undefined,
|
actionTimeout: [ undefined, { option: true } ],
|
||||||
navigationTimeout: undefined,
|
navigationTimeout: [ undefined, { option: true } ],
|
||||||
baseURL: async ({ }, use) => {
|
baseURL: [ async ({ }, use) => {
|
||||||
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
||||||
},
|
}, { option: true } ],
|
||||||
contextOptions: {},
|
contextOptions: [ {}, { option: true } ],
|
||||||
|
|
||||||
_combinedContextOptions: async ({
|
_combinedContextOptions: async ({
|
||||||
acceptDownloads,
|
acceptDownloads,
|
||||||
|
@ -173,7 +173,6 @@ export class Loader {
|
|||||||
if (!path.isAbsolute(snapshotDir))
|
if (!path.isAbsolute(snapshotDir))
|
||||||
snapshotDir = path.resolve(configDir, snapshotDir);
|
snapshotDir = path.resolve(configDir, snapshotDir);
|
||||||
const fullProject: FullProject = {
|
const fullProject: FullProject = {
|
||||||
define: takeFirst(this._configOverrides.define, projectConfig.define, this._config.define, []),
|
|
||||||
expect: takeFirst(this._configOverrides.expect, projectConfig.expect, this._config.expect, undefined),
|
expect: takeFirst(this._configOverrides.expect, projectConfig.expect, this._config.expect, undefined),
|
||||||
outputDir,
|
outputDir,
|
||||||
repeatEach: takeFirst(this._configOverrides.repeatEach, projectConfig.repeatEach, this._config.repeatEach, 1),
|
repeatEach: takeFirst(this._configOverrides.repeatEach, projectConfig.repeatEach, this._config.repeatEach, 1),
|
||||||
@ -357,16 +356,6 @@ function validateProject(file: string, project: Project, title: string) {
|
|||||||
if (typeof project !== 'object' || !project)
|
if (typeof project !== 'object' || !project)
|
||||||
throw errorWithFile(file, `${title} must be an object`);
|
throw errorWithFile(file, `${title} must be an object`);
|
||||||
|
|
||||||
if ('define' in project && project.define !== undefined) {
|
|
||||||
if (Array.isArray(project.define)) {
|
|
||||||
project.define.forEach((item, index) => {
|
|
||||||
validateDefine(file, item, `${title}.define[${index}]`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
validateDefine(file, project.define, `${title}.define`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('name' in project && project.name !== undefined) {
|
if ('name' in project && project.name !== undefined) {
|
||||||
if (typeof project.name !== 'string')
|
if (typeof project.name !== 'string')
|
||||||
throw errorWithFile(file, `${title}.name must be a string`);
|
throw errorWithFile(file, `${title}.name must be a string`);
|
||||||
@ -417,11 +406,6 @@ function validateProject(file: string, project: Project, title: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateDefine(file: string, define: any, title: string) {
|
|
||||||
if (!define || typeof define !== 'object' || !define.test || !define.fixtures)
|
|
||||||
throw errorWithFile(file, `${title} must be an object with "test" and "fixtures" properties`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseFullConfig: FullConfig = {
|
const baseFullConfig: FullConfig = {
|
||||||
forbidOnly: false,
|
forbidOnly: false,
|
||||||
globalSetup: null,
|
globalSetup: null,
|
||||||
|
@ -14,39 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestType, FullProject, Fixtures, FixturesWithLocation } from './types';
|
import type { FullProject, Fixtures, FixturesWithLocation } from './types';
|
||||||
import { Suite, TestCase } from './test';
|
import { Suite, TestCase } from './test';
|
||||||
import { FixturePool } from './fixtures';
|
import { FixturePool, isFixtureOption } from './fixtures';
|
||||||
import { DeclaredFixtures, TestTypeImpl } from './testType';
|
import { TestTypeImpl } from './testType';
|
||||||
|
|
||||||
export class ProjectImpl {
|
export class ProjectImpl {
|
||||||
config: FullProject;
|
config: FullProject;
|
||||||
private index: number;
|
private index: number;
|
||||||
private defines = new Map<TestType<any, any>, Fixtures>();
|
|
||||||
private testTypePools = new Map<TestTypeImpl, FixturePool>();
|
private testTypePools = new Map<TestTypeImpl, FixturePool>();
|
||||||
private testPools = new Map<TestCase, FixturePool>();
|
private testPools = new Map<TestCase, FixturePool>();
|
||||||
|
|
||||||
constructor(project: FullProject, index: number) {
|
constructor(project: FullProject, index: number) {
|
||||||
this.config = project;
|
this.config = project;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.defines = new Map();
|
|
||||||
for (const { test, fixtures } of Array.isArray(project.define) ? project.define : [project.define])
|
|
||||||
this.defines.set(test, fixtures);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildTestTypePool(testType: TestTypeImpl): FixturePool {
|
private buildTestTypePool(testType: TestTypeImpl): FixturePool {
|
||||||
if (!this.testTypePools.has(testType)) {
|
if (!this.testTypePools.has(testType)) {
|
||||||
const fixtures = this.resolveFixtures(testType);
|
const fixtures = this.resolveFixtures(testType, this.config.use);
|
||||||
const overrides: Fixtures = this.config.use;
|
const pool = new FixturePool(fixtures);
|
||||||
const overridesWithLocation = {
|
|
||||||
fixtures: overrides,
|
|
||||||
location: {
|
|
||||||
file: `<configuration file>`,
|
|
||||||
line: 1,
|
|
||||||
column: 1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const pool = new FixturePool([...fixtures, overridesWithLocation]);
|
|
||||||
this.testTypePools.set(testType, pool);
|
this.testTypePools.set(testType, pool);
|
||||||
}
|
}
|
||||||
return this.testTypePools.get(testType)!;
|
return this.testTypePools.get(testType)!;
|
||||||
@ -121,13 +108,18 @@ export class ProjectImpl {
|
|||||||
return this._cloneEntries(suite, result, repeatEachIndex, filter) ? result : undefined;
|
return this._cloneEntries(suite, result, repeatEachIndex, filter) ? result : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveFixtures(testType: TestTypeImpl): FixturesWithLocation[] {
|
private resolveFixtures(testType: TestTypeImpl, configUse: Fixtures): FixturesWithLocation[] {
|
||||||
return testType.fixtures.map(f => {
|
return testType.fixtures.map(f => {
|
||||||
if (f instanceof DeclaredFixtures) {
|
const configKeys = new Set(Object.keys(configUse || {}));
|
||||||
const fixtures = this.defines.get(f.testType.test) || {};
|
const resolved = { ...f.fixtures };
|
||||||
return { fixtures, location: f.location };
|
for (const [key, value] of Object.entries(resolved)) {
|
||||||
|
if (!isFixtureOption(value) || !configKeys.has(key))
|
||||||
|
continue;
|
||||||
|
// Apply override from config file.
|
||||||
|
const override = (configUse as any)[key];
|
||||||
|
(resolved as any)[key] = [override, value[1]];
|
||||||
}
|
}
|
||||||
return f;
|
return { fixtures: resolved, location: f.location };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,17 @@ import { Fixtures, FixturesWithLocation, Location, TestType } from './types';
|
|||||||
import { errorWithLocation, serializeError } from './util';
|
import { errorWithLocation, serializeError } from './util';
|
||||||
|
|
||||||
const countByFile = new Map<string, number>();
|
const countByFile = new Map<string, number>();
|
||||||
|
const testTypeSymbol = Symbol('testType');
|
||||||
export class DeclaredFixtures {
|
|
||||||
testType!: TestTypeImpl;
|
|
||||||
location!: Location;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TestTypeImpl {
|
export class TestTypeImpl {
|
||||||
readonly fixtures: (FixturesWithLocation | DeclaredFixtures)[];
|
readonly fixtures: FixturesWithLocation[];
|
||||||
readonly test: TestType<any, any>;
|
readonly test: TestType<any, any>;
|
||||||
|
|
||||||
constructor(fixtures: (FixturesWithLocation | DeclaredFixtures)[]) {
|
constructor(fixtures: FixturesWithLocation[]) {
|
||||||
this.fixtures = fixtures;
|
this.fixtures = fixtures;
|
||||||
|
|
||||||
const test: any = wrapFunctionWithLocation(this._createTest.bind(this, 'default'));
|
const test: any = wrapFunctionWithLocation(this._createTest.bind(this, 'default'));
|
||||||
|
test[testTypeSymbol] = this;
|
||||||
test.expect = expect;
|
test.expect = expect;
|
||||||
test.only = wrapFunctionWithLocation(this._createTest.bind(this, 'only'));
|
test.only = wrapFunctionWithLocation(this._createTest.bind(this, 'only'));
|
||||||
test.describe = wrapFunctionWithLocation(this._describe.bind(this, 'default'));
|
test.describe = wrapFunctionWithLocation(this._describe.bind(this, 'default'));
|
||||||
@ -56,7 +53,7 @@ export class TestTypeImpl {
|
|||||||
test.step = wrapFunctionWithLocation(this._step.bind(this));
|
test.step = wrapFunctionWithLocation(this._step.bind(this));
|
||||||
test.use = wrapFunctionWithLocation(this._use.bind(this));
|
test.use = wrapFunctionWithLocation(this._use.bind(this));
|
||||||
test.extend = wrapFunctionWithLocation(this._extend.bind(this));
|
test.extend = wrapFunctionWithLocation(this._extend.bind(this));
|
||||||
test.declare = wrapFunctionWithLocation(this._declare.bind(this));
|
test.extendTest = wrapFunctionWithLocation(this._extendTest.bind(this));
|
||||||
this.test = test;
|
this.test = test;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,16 +200,19 @@ export class TestTypeImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _extend(location: Location, fixtures: Fixtures) {
|
private _extend(location: Location, fixtures: Fixtures) {
|
||||||
const fixturesWithLocation = { fixtures, location };
|
if ((fixtures as any)[testTypeSymbol])
|
||||||
|
throw new Error(`test.extend() accepts fixtures object, not a test object.\nDid you mean to call test.extendTest()?`);
|
||||||
|
const fixturesWithLocation: FixturesWithLocation = { fixtures, location };
|
||||||
return new TestTypeImpl([...this.fixtures, fixturesWithLocation]).test;
|
return new TestTypeImpl([...this.fixtures, fixturesWithLocation]).test;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _declare(location: Location) {
|
private _extendTest(location: Location, test: TestType<any, any>) {
|
||||||
const declared = new DeclaredFixtures();
|
const testTypeImpl = (test as any)[testTypeSymbol] as TestTypeImpl;
|
||||||
declared.location = location;
|
if (!testTypeImpl)
|
||||||
const child = new TestTypeImpl([...this.fixtures, declared]);
|
throw new Error(`test.extendTest() accepts a single "test" parameter.\nDid you mean to call test.extend() with fixtures instead?`);
|
||||||
declared.testType = child;
|
// Filter out common ancestor fixtures.
|
||||||
return child.test;
|
const newFixtures = testTypeImpl.fixtures.filter(theirs => !this.fixtures.find(ours => ours.fixtures === theirs.fixtures));
|
||||||
|
return new TestTypeImpl([...this.fixtures, ...newFixtures]).test;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
packages/playwright-test/types/test.d.ts
vendored
75
packages/playwright-test/types/test.d.ts
vendored
@ -36,7 +36,6 @@ export type ReportSlowTests = { max: number, threshold: number } | null;
|
|||||||
export type PreserveOutput = 'always' | 'never' | 'failures-only';
|
export type PreserveOutput = 'always' | 'never' | 'failures-only';
|
||||||
export type UpdateSnapshots = 'all' | 'none' | 'missing';
|
export type UpdateSnapshots = 'all' | 'none' | 'missing';
|
||||||
|
|
||||||
type FixtureDefine<TestArgs extends KeyValue = {}, WorkerArgs extends KeyValue = {}> = { test: TestType<TestArgs, WorkerArgs>, fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs> };
|
|
||||||
type UseOptions<TestArgs, WorkerArgs> = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] };
|
type UseOptions<TestArgs, WorkerArgs> = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] };
|
||||||
|
|
||||||
type ExpectSettings = {
|
type ExpectSettings = {
|
||||||
@ -319,7 +318,6 @@ interface TestProject {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export interface Project<TestArgs = {}, WorkerArgs = {}> extends TestProject {
|
export interface Project<TestArgs = {}, WorkerArgs = {}> extends TestProject {
|
||||||
define?: FixtureDefine | FixtureDefine[];
|
|
||||||
/**
|
/**
|
||||||
* Options for all tests in this project, for example
|
* Options for all tests in this project, for example
|
||||||
* [testOptions.browserName](https://playwright.dev/docs/api/class-testoptions#test-options-browser-name). Learn more about
|
* [testOptions.browserName](https://playwright.dev/docs/api/class-testoptions#test-options-browser-name). Learn more about
|
||||||
@ -782,7 +780,6 @@ export interface Config<TestArgs = {}, WorkerArgs = {}> extends TestConfig {
|
|||||||
* Playwright Test supports running multiple test projects at the same time. See [TestProject] for more information.
|
* Playwright Test supports running multiple test projects at the same time. See [TestProject] for more information.
|
||||||
*/
|
*/
|
||||||
projects?: Project<TestArgs, WorkerArgs>[];
|
projects?: Project<TestArgs, WorkerArgs>[];
|
||||||
define?: FixtureDefine | FixtureDefine[];
|
|
||||||
/**
|
/**
|
||||||
* Global options for all tests, for example
|
* Global options for all tests, for example
|
||||||
* [testOptions.browserName](https://playwright.dev/docs/api/class-testoptions#test-options-browser-name). Learn more about
|
* [testOptions.browserName](https://playwright.dev/docs/api/class-testoptions#test-options-browser-name). Learn more about
|
||||||
@ -2575,8 +2572,74 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
* [expect library documentation](https://jestjs.io/docs/expect) for more details.
|
* [expect library documentation](https://jestjs.io/docs/expect) for more details.
|
||||||
*/
|
*/
|
||||||
expect: Expect;
|
expect: Expect;
|
||||||
declare<T extends KeyValue = {}, W extends KeyValue = {}>(): TestType<TestArgs & T, WorkerArgs & W>;
|
/**
|
||||||
|
* Extends the `test` object by defining fixtures and/or options that can be used in the tests.
|
||||||
|
*
|
||||||
|
* First define a fixture and/or an option.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* import { test as base } from '@playwright/test';
|
||||||
|
* import { TodoPage } from './todo-page';
|
||||||
|
*
|
||||||
|
* export type Options = { defaultItem: string };
|
||||||
|
*
|
||||||
|
* // Extend basic test by providing a "defaultItem" option and a "todoPage" fixture.
|
||||||
|
* export const test = base.extend<Options & { todoPage: TodoPage }>({
|
||||||
|
* // Define an option and provide a default value.
|
||||||
|
* // We can later override it in the config.
|
||||||
|
* defaultItem: ['Do stuff', { option: true }],
|
||||||
|
*
|
||||||
|
* // Define a fixture. Note that it can use built-in fixture "page"
|
||||||
|
* // and a new option "defaultItem".
|
||||||
|
* todoPage: async ({ page, defaultItem }, use) => {
|
||||||
|
* const todoPage = new TodoPage(page);
|
||||||
|
* await todoPage.goto();
|
||||||
|
* await todoPage.addToDo(defaultItem);
|
||||||
|
* await use(todoPage);
|
||||||
|
* await todoPage.removeAll();
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Then use the fixture in the test.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // example.spec.ts
|
||||||
|
* import { test } from './my-test';
|
||||||
|
*
|
||||||
|
* test('test 1', async ({ todoPage }) => {
|
||||||
|
* await todoPage.addToDo('my todo');
|
||||||
|
* // ...
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Configure the option in config file.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // playwright.config.ts
|
||||||
|
* import { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
* import { Options } from './my-test';
|
||||||
|
*
|
||||||
|
* const config: PlaywrightTestConfig<Options> = {
|
||||||
|
* projects: [
|
||||||
|
* {
|
||||||
|
* name: 'shopping',
|
||||||
|
* use: { defaultItem: 'Buy milk' },
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: 'wellbeing',
|
||||||
|
* use: { defaultItem: 'Exercise!' },
|
||||||
|
* },
|
||||||
|
* ]
|
||||||
|
* };
|
||||||
|
* export default config;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Learn more about [fixtures](https://playwright.dev/docs/test-fixtures) and [parametrizing tests](https://playwright.dev/docs/test-parameterize).
|
||||||
|
* @param fixtures An object containing fixtures and/or options. Learn more about [fixtures format](https://playwright.dev/docs/test-fixtures).
|
||||||
|
*/
|
||||||
extend<T, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
extend<T, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
|
extendTest<T, W>(other: TestType<T, W>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyValue = { [key: string]: any };
|
type KeyValue = { [key: string]: any };
|
||||||
@ -2589,9 +2652,9 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
|
|||||||
} & {
|
} & {
|
||||||
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test' }];
|
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test' }];
|
||||||
} & {
|
} & {
|
||||||
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean }];
|
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean }];
|
||||||
} & {
|
} & {
|
||||||
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean }];
|
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean }];
|
||||||
};
|
};
|
||||||
|
|
||||||
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||||
|
@ -16,22 +16,17 @@
|
|||||||
|
|
||||||
import { test } from '@playwright/test';
|
import { test } from '@playwright/test';
|
||||||
import { commonFixtures, CommonFixtures } from './commonFixtures';
|
import { commonFixtures, CommonFixtures } from './commonFixtures';
|
||||||
import { serverFixtures, ServerFixtures, ServerWorkerOptions } from './serverFixtures';
|
import { serverTest } from './serverFixtures';
|
||||||
import { coverageFixtures, CoverageWorkerOptions } from './coverageFixtures';
|
import { coverageTest } from './coverageFixtures';
|
||||||
import { platformFixtures, PlatformWorkerFixtures } from './platformFixtures';
|
import { platformTest } from './platformFixtures';
|
||||||
import { testModeFixtures, TestModeWorkerFixtures } from './testModeFixtures';
|
import { testModeTest } from './testModeFixtures';
|
||||||
|
|
||||||
|
|
||||||
export type BaseTestWorkerFixtures = {
|
|
||||||
_snapshotSuffix: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const baseTest = test
|
export const baseTest = test
|
||||||
.extend<{}, CoverageWorkerOptions>(coverageFixtures as any)
|
.extendTest(coverageTest)
|
||||||
.extend<{}, PlatformWorkerFixtures>(platformFixtures)
|
.extendTest(platformTest)
|
||||||
.extend<{}, TestModeWorkerFixtures>(testModeFixtures as any)
|
.extendTest(testModeTest)
|
||||||
.extend<CommonFixtures>(commonFixtures)
|
.extend<CommonFixtures>(commonFixtures)
|
||||||
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures as any)
|
.extendTest(serverTest)
|
||||||
.extend<{}, BaseTestWorkerFixtures>({
|
.extend<{}, { _snapshotSuffix: string }>({
|
||||||
_snapshotSuffix: ['', { scope: 'worker' }],
|
_snapshotSuffix: ['', { scope: 'worker' }],
|
||||||
});
|
});
|
||||||
|
@ -14,18 +14,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures } from '@playwright/test';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { installCoverageHooks } from './coverage';
|
import { installCoverageHooks } from './coverage';
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
export type CoverageWorkerOptions = {
|
export type CoverageWorkerOptions = {
|
||||||
coverageName?: string;
|
coverageName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const coverageFixtures: Fixtures<{}, CoverageWorkerOptions & { __collectCoverage: void }> = {
|
export const coverageTest = test.extend<{}, { __collectCoverage: void } & CoverageWorkerOptions>({
|
||||||
coverageName: [ undefined, { scope: 'worker' } ],
|
coverageName: [ undefined, { scope: 'worker', option: true } ],
|
||||||
|
|
||||||
__collectCoverage: [ async ({ coverageName }, run, workerInfo) => {
|
__collectCoverage: [ async ({ coverageName }, run, workerInfo) => {
|
||||||
if (!coverageName) {
|
if (!coverageName) {
|
||||||
await run();
|
await run();
|
||||||
@ -40,4 +39,4 @@ export const coverageFixtures: Fixtures<{}, CoverageWorkerOptions & { __collectC
|
|||||||
await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true });
|
await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true });
|
||||||
await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8');
|
await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8');
|
||||||
}, { scope: 'worker', auto: true } ],
|
}, { scope: 'worker', auto: true } ],
|
||||||
};
|
});
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { TestModeWorkerFixtures } from './testModeFixtures';
|
import { TestModeWorkerOptions } from './testModeFixtures';
|
||||||
import { CoverageWorkerOptions } from './coverageFixtures';
|
import { CoverageWorkerOptions } from './coverageFixtures';
|
||||||
|
|
||||||
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||||
@ -38,7 +38,7 @@ const trace = !!process.env.PWTEST_TRACE;
|
|||||||
|
|
||||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||||
const testDir = path.join(__dirname, '..');
|
const testDir = path.join(__dirname, '..');
|
||||||
const config: Config<CoverageWorkerOptions & PlaywrightWorkerOptions & PlaywrightTestOptions & TestModeWorkerFixtures> = {
|
const config: Config<CoverageWorkerOptions & PlaywrightWorkerOptions & PlaywrightTestOptions & TestModeWorkerOptions> = {
|
||||||
testDir,
|
testDir,
|
||||||
outputDir,
|
outputDir,
|
||||||
expect: {
|
expect: {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Fixtures } from '@playwright/test';
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
export type PlatformWorkerFixtures = {
|
export type PlatformWorkerFixtures = {
|
||||||
platform: 'win32' | 'darwin' | 'linux';
|
platform: 'win32' | 'darwin' | 'linux';
|
||||||
@ -23,9 +23,9 @@ export type PlatformWorkerFixtures = {
|
|||||||
isLinux: boolean;
|
isLinux: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const platformFixtures: Fixtures<{}, PlatformWorkerFixtures> = {
|
export const platformTest = test.extend<{}, PlatformWorkerFixtures>({
|
||||||
platform: [ process.platform as 'win32' | 'darwin' | 'linux', { scope: 'worker' } ],
|
platform: [ process.platform as 'win32' | 'darwin' | 'linux', { scope: 'worker' } ],
|
||||||
isWindows: [ process.platform === 'win32', { scope: 'worker' } ],
|
isWindows: [ process.platform === 'win32', { scope: 'worker' } ],
|
||||||
isMac: [ process.platform === 'darwin', { scope: 'worker' } ],
|
isMac: [ process.platform === 'darwin', { scope: 'worker' } ],
|
||||||
isLinux: [ process.platform === 'linux', { scope: 'worker' } ],
|
isLinux: [ process.platform === 'linux', { scope: 'worker' } ],
|
||||||
};
|
});
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Fixtures } from '@playwright/test';
|
import { test, Fixtures } from '@playwright/test';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import socks from 'socksv5';
|
import socks from 'socksv5';
|
||||||
import { TestServer } from '../../utils/testserver';
|
import { TestServer } from '../../utils/testserver';
|
||||||
@ -33,8 +33,8 @@ export type ServerFixtures = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ServersInternal = ServerFixtures & { socksServer: socks.SocksServer };
|
export type ServersInternal = ServerFixtures & { socksServer: socks.SocksServer };
|
||||||
export const serverFixtures: Fixtures<ServerFixtures, ServerWorkerOptions & { __servers: ServersInternal }> = {
|
export const serverFixtures: Fixtures<ServerFixtures, { __servers: ServersInternal } & ServerWorkerOptions> = {
|
||||||
loopback: [ undefined, { scope: 'worker' } ],
|
loopback: [ undefined, { scope: 'worker', option: true } ],
|
||||||
__servers: [ async ({ loopback }, run, workerInfo) => {
|
__servers: [ async ({ loopback }, run, workerInfo) => {
|
||||||
const assetsPath = path.join(__dirname, '..', 'assets');
|
const assetsPath = path.join(__dirname, '..', 'assets');
|
||||||
const cachedPath = path.join(__dirname, '..', 'assets', 'cached');
|
const cachedPath = path.join(__dirname, '..', 'assets', 'cached');
|
||||||
@ -110,3 +110,5 @@ export const serverFixtures: Fixtures<ServerFixtures, ServerWorkerOptions & { __
|
|||||||
await run(__servers.asset);
|
await run(__servers.asset);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const serverTest = test.extend<ServerFixtures, ServerWorkerOptions & { __servers: ServersInternal }>(serverFixtures);
|
||||||
|
@ -14,18 +14,20 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Fixtures } from '@playwright/test';
|
import { test } from '@playwright/test';
|
||||||
import { DefaultTestMode, DriverTestMode, ServiceTestMode, TestModeName } from './testMode';
|
import { DefaultTestMode, DriverTestMode, ServiceTestMode, TestModeName } from './testMode';
|
||||||
|
|
||||||
export type TestModeWorkerFixtures = {
|
export type TestModeWorkerOptions = {
|
||||||
mode: TestModeName;
|
mode: TestModeName;
|
||||||
playwright: typeof import('playwright-core');
|
};
|
||||||
|
|
||||||
|
export type TestModeWorkerFixtures = {
|
||||||
|
playwright: typeof import('@playwright/test');
|
||||||
toImpl: (rpcObject: any) => any;
|
toImpl: (rpcObject: any) => any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const testModeFixtures: Fixtures<{}, TestModeWorkerFixtures> = {
|
export const testModeTest = test.extend<{}, TestModeWorkerOptions & TestModeWorkerFixtures>({
|
||||||
mode: [ 'default', { scope: 'worker' } ],
|
mode: [ 'default', { scope: 'worker', option: true } ],
|
||||||
|
|
||||||
playwright: [ async ({ mode }, run) => {
|
playwright: [ async ({ mode }, run) => {
|
||||||
const testMode = {
|
const testMode = {
|
||||||
default: new DefaultTestMode(),
|
default: new DefaultTestMode(),
|
||||||
@ -39,4 +41,4 @@ export const testModeFixtures: Fixtures<{}, TestModeWorkerFixtures> = {
|
|||||||
}, { scope: 'worker' } ],
|
}, { scope: 'worker' } ],
|
||||||
|
|
||||||
toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ],
|
toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ],
|
||||||
};
|
});
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { TestType } from '@playwright/test';
|
import { TestType } from '@playwright/test';
|
||||||
import { PlatformWorkerFixtures } from '../config/platformFixtures';
|
import { PlatformWorkerFixtures } from '../config/platformFixtures';
|
||||||
import { TestModeWorkerFixtures } from '../config/testModeFixtures';
|
import { TestModeWorkerFixtures, TestModeWorkerOptions } from '../config/testModeFixtures';
|
||||||
import { androidTest } from '../android/androidTest';
|
import { androidTest } from '../android/androidTest';
|
||||||
import { browserTest } from '../config/browserTest';
|
import { browserTest } from '../config/browserTest';
|
||||||
import { electronTest } from '../electron/electronTest';
|
import { electronTest } from '../electron/electronTest';
|
||||||
@ -24,7 +24,7 @@ import { PageTestFixtures, PageWorkerFixtures } from './pageTestApi';
|
|||||||
import { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
|
import { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
|
||||||
export { expect } from '@playwright/test';
|
export { expect } from '@playwright/test';
|
||||||
|
|
||||||
let impl: TestType<PageTestFixtures & ServerFixtures, PageWorkerFixtures & PlatformWorkerFixtures & TestModeWorkerFixtures & ServerWorkerOptions> = browserTest;
|
let impl: TestType<PageTestFixtures & ServerFixtures, PageWorkerFixtures & PlatformWorkerFixtures & TestModeWorkerFixtures & TestModeWorkerOptions & ServerWorkerOptions> = browserTest;
|
||||||
|
|
||||||
if (process.env.PWPAGE_IMPL === 'android')
|
if (process.env.PWPAGE_IMPL === 'android')
|
||||||
impl = androidTest;
|
impl = androidTest;
|
||||||
|
@ -361,7 +361,7 @@ test('should inerhit use options in projects', async ({ runInlineTest }) => {
|
|||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
const { test } = pwt;
|
const test = pwt.test.extend({ foo: ['', {option:true}], bar: ['', {option: true}] });
|
||||||
test('pass', async ({ foo, bar }, testInfo) => {
|
test('pass', async ({ foo, bar }, testInfo) => {
|
||||||
test.expect(foo).toBe('config');
|
test.expect(foo).toBe('config');
|
||||||
test.expect(bar).toBe('project');
|
test.expect(bar).toBe('project');
|
||||||
|
@ -492,7 +492,8 @@ test('should understand worker fixture params in overrides calling base', async
|
|||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
const test1 = pwt.test.extend({
|
const test1 = pwt.test.extend({
|
||||||
param: [ 'param', { scope: 'worker' }],
|
param: [ 'param', { scope: 'worker', option: true }],
|
||||||
|
}).extend({
|
||||||
foo: async ({}, test) => await test('foo'),
|
foo: async ({}, test) => await test('foo'),
|
||||||
bar: async ({foo}, test) => await test(foo + '-bar'),
|
bar: async ({foo}, test) => await test(foo + '-bar'),
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,7 @@ import * as path from 'path';
|
|||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { CommonFixtures, commonFixtures } from '../config/commonFixtures';
|
import { CommonFixtures, commonFixtures } from '../config/commonFixtures';
|
||||||
import { serverFixtures, ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
|
import { serverFixtures, serverOptions, ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
|
||||||
import { test as base, TestInfo } from './stable-test-runner';
|
import { test as base, TestInfo } from './stable-test-runner';
|
||||||
|
|
||||||
const removeFolderAsync = promisify(rimraf);
|
const removeFolderAsync = promisify(rimraf);
|
||||||
@ -195,32 +195,35 @@ type Fixtures = {
|
|||||||
runTSC: (files: Files) => Promise<TSCResult>;
|
runTSC: (files: Files) => Promise<TSCResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const common = base.extend<CommonFixtures>(commonFixtures as any);
|
export const test = base
|
||||||
export const test = common.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures as any).extend<Fixtures>({
|
.extend<CommonFixtures>(commonFixtures as any)
|
||||||
writeFiles: async ({}, use, testInfo) => {
|
// TODO: this is a hack until we roll the stable test runner.
|
||||||
await use(files => writeFiles(testInfo, files));
|
.extend<ServerFixtures, ServerWorkerOptions>({ ...serverOptions, ...serverFixtures } as any)
|
||||||
},
|
.extend<Fixtures>({
|
||||||
|
writeFiles: async ({}, use, testInfo) => {
|
||||||
|
await use(files => writeFiles(testInfo, files));
|
||||||
|
},
|
||||||
|
|
||||||
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => {
|
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => {
|
||||||
await use(async (files: Files, params: Params = {}, env: Env = {}, options: RunOptions = {}) => {
|
await use(async (files: Files, params: Params = {}, env: Env = {}, options: RunOptions = {}) => {
|
||||||
const baseDir = await writeFiles(testInfo, files);
|
const baseDir = await writeFiles(testInfo, files);
|
||||||
return await runPlaywrightTest(childProcess, baseDir, params, env, options);
|
return await runPlaywrightTest(childProcess, baseDir, params, env, options);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
runTSC: async ({ childProcess }, use, testInfo) => {
|
runTSC: async ({ childProcess }, use, testInfo) => {
|
||||||
await use(async files => {
|
await use(async files => {
|
||||||
const baseDir = await writeFiles(testInfo, { 'tsconfig.json': JSON.stringify(TSCONFIG), ...files });
|
const baseDir = await writeFiles(testInfo, { 'tsconfig.json': JSON.stringify(TSCONFIG), ...files });
|
||||||
const tsc = childProcess({
|
const tsc = childProcess({
|
||||||
command: ['npx', 'tsc', '-p', baseDir],
|
command: ['npx', 'tsc', '-p', baseDir],
|
||||||
cwd: baseDir,
|
cwd: baseDir,
|
||||||
shell: true,
|
shell: true,
|
||||||
});
|
});
|
||||||
const { exitCode } = await tsc.exited;
|
const { exitCode } = await tsc.exited;
|
||||||
return { exitCode, output: tsc.output };
|
return { exitCode, output: tsc.output };
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const TSCONFIG = {
|
const TSCONFIG = {
|
||||||
'compilerOptions': {
|
'compilerOptions': {
|
||||||
|
@ -39,34 +39,29 @@ test('test.extend should work', async ({ runInlineTest }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const base = pwt.test.declare();
|
export const base = pwt.test.extend({
|
||||||
|
suffix: ['', { scope: 'worker', option: true } ],
|
||||||
|
baseWorker: [async ({ suffix }, run) => {
|
||||||
|
global.logs.push('beforeAll-' + suffix);
|
||||||
|
await run();
|
||||||
|
global.logs.push('afterAll-' + suffix);
|
||||||
|
if (suffix.includes('base'))
|
||||||
|
console.log(global.logs.join('\\n'));
|
||||||
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
baseTest: async ({ suffix, derivedWorker }, run) => {
|
||||||
|
global.logs.push('beforeEach-' + suffix);
|
||||||
|
await run();
|
||||||
|
global.logs.push('afterEach-' + suffix);
|
||||||
|
},
|
||||||
|
});
|
||||||
export const test1 = base.extend(createDerivedFixtures('e1'));
|
export const test1 = base.extend(createDerivedFixtures('e1'));
|
||||||
export const test2 = base.extend(createDerivedFixtures('e2'));
|
export const test2 = base.extend(createDerivedFixtures('e2'));
|
||||||
`,
|
`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
import { base } from './helper';
|
|
||||||
|
|
||||||
function createBaseFixtures(suffix) {
|
|
||||||
return {
|
|
||||||
baseWorker: [async ({}, run) => {
|
|
||||||
global.logs.push('beforeAll-' + suffix);
|
|
||||||
await run();
|
|
||||||
global.logs.push('afterAll-' + suffix);
|
|
||||||
if (suffix.includes('base'))
|
|
||||||
console.log(global.logs.join('\\n'));
|
|
||||||
}, { scope: 'worker' }],
|
|
||||||
|
|
||||||
baseTest: async ({ derivedWorker }, run) => {
|
|
||||||
global.logs.push('beforeEach-' + suffix);
|
|
||||||
await run();
|
|
||||||
global.logs.push('afterEach-' + suffix);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { projects: [
|
module.exports = { projects: [
|
||||||
{ define: { test: base, fixtures: createBaseFixtures('base1') } },
|
{ use: { suffix: 'base1' } },
|
||||||
{ define: { test: base, fixtures: createBaseFixtures('base2') } },
|
{ use: { suffix: 'base2' } },
|
||||||
] };
|
] };
|
||||||
`,
|
`,
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
@ -126,53 +121,107 @@ test('test.extend should work', async ({ runInlineTest }) => {
|
|||||||
].join('\n'));
|
].join('\n'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test.declare should be inserted at the right place', async ({ runInlineTest }) => {
|
test('config should override options but not fixtures', async ({ runInlineTest }) => {
|
||||||
const { output, passed } = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'helper.ts': `
|
|
||||||
const test1 = pwt.test.extend({
|
|
||||||
foo: async ({}, run) => {
|
|
||||||
console.log('before-foo');
|
|
||||||
await run('foo');
|
|
||||||
console.log('after-foo');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const test2 = test1.declare<{ bar: string }>();
|
|
||||||
export const test3 = test2.extend({
|
|
||||||
baz: async ({ bar }, run) => {
|
|
||||||
console.log('before-baz');
|
|
||||||
await run(bar + 'baz');
|
|
||||||
console.log('after-baz');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
import { test2 } from './helper';
|
|
||||||
const fixtures = {
|
|
||||||
bar: async ({ foo }, run) => {
|
|
||||||
console.log('before-bar');
|
|
||||||
await run(foo + 'bar');
|
|
||||||
console.log('after-bar');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
define: { test: test2, fixtures },
|
use: { param: 'config' },
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
const { test3 } = require('./helper');
|
const test1 = pwt.test.extend({ param: [ 'default', { option: true } ] });
|
||||||
test3('should work', async ({baz}) => {
|
test1('default', async ({ param }) => {
|
||||||
console.log('test-' + baz);
|
console.log('default-' + param);
|
||||||
|
});
|
||||||
|
|
||||||
|
const test2 = test1.extend({
|
||||||
|
param: 'extend',
|
||||||
|
});
|
||||||
|
test2('extend', async ({ param }) => {
|
||||||
|
console.log('extend-' + param);
|
||||||
|
});
|
||||||
|
|
||||||
|
const test3 = test1.extend({
|
||||||
|
param: async ({ param }, use) => {
|
||||||
|
await use(param + '-fixture');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
test3('fixture', async ({ param }) => {
|
||||||
|
console.log('fixture-' + param);
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
expect(passed).toBe(1);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(output).toContain([
|
expect(result.passed).toBe(3);
|
||||||
'before-foo',
|
expect(result.output).toContain('default-config');
|
||||||
'before-bar',
|
expect(result.output).toContain('extend-extend');
|
||||||
'before-baz',
|
expect(result.output).toContain('fixture-config-fixture');
|
||||||
'test-foobarbaz',
|
});
|
||||||
'after-baz',
|
|
||||||
'after-bar',
|
test('test.extend should be able to merge', async ({ runInlineTest }) => {
|
||||||
'after-foo',
|
const result = await runInlineTest({
|
||||||
].join('\n'));
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
use: { param: 'from-config' },
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
const base = pwt.test.extend({
|
||||||
|
myFixture: 'abc',
|
||||||
|
});
|
||||||
|
|
||||||
|
const test1 = base
|
||||||
|
.extend({
|
||||||
|
param: [ 'default', { option: true } ],
|
||||||
|
fixture1: ({ param }, use) => use(param + '+fixture1'),
|
||||||
|
myFixture: 'override',
|
||||||
|
});
|
||||||
|
|
||||||
|
const test2 = base.extend({
|
||||||
|
fixture2: ({}, use) => use('fixture2'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const test3 = test1.extendTest(test2);
|
||||||
|
|
||||||
|
test3('merged', async ({ param, fixture1, myFixture, fixture2 }) => {
|
||||||
|
console.log('param-' + param);
|
||||||
|
console.log('fixture1-' + fixture1);
|
||||||
|
console.log('myFixture-' + myFixture);
|
||||||
|
console.log('fixture2-' + fixture2);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.output).toContain('param-from-config');
|
||||||
|
expect(result.output).toContain('fixture1-from-config+fixture1');
|
||||||
|
expect(result.output).toContain('myFixture-override');
|
||||||
|
expect(result.output).toContain('fixture2-fixture2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test.extend should print nice message when used as extendTest', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const test1 = pwt.test.extend({});
|
||||||
|
const test2 = pwt.test.extend({});
|
||||||
|
const test3 = test1.extend(test2);
|
||||||
|
|
||||||
|
test3('test', () => {});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.output).toContain('Did you mean to call test.extendTest()?');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test.extendTest should print nice message when used as extend', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const test3 = pwt.test.extendTest({});
|
||||||
|
test3('test', () => {});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.output).toContain('Did you mean to call test.extend() with fixtures instead?');
|
||||||
});
|
});
|
||||||
|
@ -139,7 +139,7 @@ test('should use options from the config', async ({ runInlineTest }) => {
|
|||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'helper.ts': `
|
'helper.ts': `
|
||||||
export const test = pwt.test.extend({
|
export const test = pwt.test.extend({
|
||||||
foo: 'foo',
|
foo: [ 'foo', { option: true } ],
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
|
@ -63,11 +63,17 @@ test('can return anything from hooks', async ({ runTSC }) => {
|
|||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test.declare should check types', async ({ runTSC }) => {
|
test('test.extend options should check types', async ({ runTSC }) => {
|
||||||
const result = await runTSC({
|
const result = await runTSC({
|
||||||
'helper.ts': `
|
'helper.ts': `
|
||||||
|
export type Params = { foo: string };
|
||||||
export const test = pwt.test;
|
export const test = pwt.test;
|
||||||
export const test1 = test.declare<{ foo: string }>();
|
export const test1 = test.extend<Params>({ foo: [ 'foo', { option: true } ] });
|
||||||
|
export const test1b = test.extend<{ bar: string }>({ bar: [ 'bar', { option: true } ] });
|
||||||
|
export const testerror = test.extend<{ foo: string }>({
|
||||||
|
// @ts-expect-error
|
||||||
|
foo: 123
|
||||||
|
});
|
||||||
export const test2 = test1.extend<{ bar: number }>({
|
export const test2 = test1.extend<{ bar: number }>({
|
||||||
bar: async ({ foo }, run) => { await run(parseInt(foo)); }
|
bar: async ({ foo }, run) => { await run(parseInt(foo)); }
|
||||||
});
|
});
|
||||||
@ -75,26 +81,31 @@ test('test.declare should check types', async ({ runTSC }) => {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
bar: async ({ baz }, run) => { await run(42); }
|
bar: async ({ baz }, run) => { await run(42); }
|
||||||
});
|
});
|
||||||
|
export const test4 = test1.extendTest(test1b);
|
||||||
`,
|
`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
import { test1 } from './helper';
|
import { Params } from './helper';
|
||||||
const configs: pwt.Config[] = [];
|
const configs: pwt.Config<Params>[] = [];
|
||||||
|
|
||||||
configs.push({});
|
configs.push({});
|
||||||
|
|
||||||
configs.push({
|
configs.push({
|
||||||
define: {
|
use: { foo: 'bar' },
|
||||||
test: test1,
|
|
||||||
fixtures: { foo: 'foo' }
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
configs.push({
|
configs.push({
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
define: { test: {}, fixtures: {} },
|
use: { foo: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
configs.push({
|
||||||
|
// @ts-expect-error
|
||||||
|
use: { unknown: true },
|
||||||
});
|
});
|
||||||
module.exports = configs;
|
module.exports = configs;
|
||||||
`,
|
`,
|
||||||
'a.spec.ts': `
|
'a.spec.ts': `
|
||||||
import { test, test1, test2, test3 } from './helper';
|
import { test, test1, test2, test3, test4 } from './helper';
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
test('my test', async ({ foo }) => {});
|
test('my test', async ({ foo }) => {});
|
||||||
test1('my test', async ({ foo }) => {});
|
test1('my test', async ({ foo }) => {});
|
||||||
@ -103,6 +114,7 @@ test('test.declare should check types', async ({ runTSC }) => {
|
|||||||
test2('my test', async ({ foo, bar }) => {});
|
test2('my test', async ({ foo, bar }) => {});
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
test2('my test', async ({ foo, baz }) => {});
|
test2('my test', async ({ foo, baz }) => {});
|
||||||
|
test4('my test', async ({ foo, bar }) => {});
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
|
9
utils/generate_types/overrides-test.d.ts
vendored
9
utils/generate_types/overrides-test.d.ts
vendored
@ -35,7 +35,6 @@ export type ReportSlowTests = { max: number, threshold: number } | null;
|
|||||||
export type PreserveOutput = 'always' | 'never' | 'failures-only';
|
export type PreserveOutput = 'always' | 'never' | 'failures-only';
|
||||||
export type UpdateSnapshots = 'all' | 'none' | 'missing';
|
export type UpdateSnapshots = 'all' | 'none' | 'missing';
|
||||||
|
|
||||||
type FixtureDefine<TestArgs extends KeyValue = {}, WorkerArgs extends KeyValue = {}> = { test: TestType<TestArgs, WorkerArgs>, fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs> };
|
|
||||||
type UseOptions<TestArgs, WorkerArgs> = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] };
|
type UseOptions<TestArgs, WorkerArgs> = { [K in keyof WorkerArgs]?: WorkerArgs[K] } & { [K in keyof TestArgs]?: TestArgs[K] };
|
||||||
|
|
||||||
type ExpectSettings = {
|
type ExpectSettings = {
|
||||||
@ -62,7 +61,6 @@ interface TestProject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Project<TestArgs = {}, WorkerArgs = {}> extends TestProject {
|
export interface Project<TestArgs = {}, WorkerArgs = {}> extends TestProject {
|
||||||
define?: FixtureDefine | FixtureDefine[];
|
|
||||||
use?: UseOptions<TestArgs, WorkerArgs>;
|
use?: UseOptions<TestArgs, WorkerArgs>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +131,6 @@ interface TestConfig {
|
|||||||
|
|
||||||
export interface Config<TestArgs = {}, WorkerArgs = {}> extends TestConfig {
|
export interface Config<TestArgs = {}, WorkerArgs = {}> extends TestConfig {
|
||||||
projects?: Project<TestArgs, WorkerArgs>[];
|
projects?: Project<TestArgs, WorkerArgs>[];
|
||||||
define?: FixtureDefine | FixtureDefine[];
|
|
||||||
use?: UseOptions<TestArgs, WorkerArgs>;
|
use?: UseOptions<TestArgs, WorkerArgs>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,8 +264,8 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
||||||
step(title: string, body: () => Promise<any>): Promise<any>;
|
step(title: string, body: () => Promise<any>): Promise<any>;
|
||||||
expect: Expect;
|
expect: Expect;
|
||||||
declare<T extends KeyValue = {}, W extends KeyValue = {}>(): TestType<TestArgs & T, WorkerArgs & W>;
|
|
||||||
extend<T, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
extend<T, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
|
extendTest<T, W>(other: TestType<T, W>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyValue = { [key: string]: any };
|
type KeyValue = { [key: string]: any };
|
||||||
@ -281,9 +278,9 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
|
|||||||
} & {
|
} & {
|
||||||
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test' }];
|
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test' }];
|
||||||
} & {
|
} & {
|
||||||
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean }];
|
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean }];
|
||||||
} & {
|
} & {
|
||||||
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean }];
|
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean }];
|
||||||
};
|
};
|
||||||
|
|
||||||
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user