Skip to content

Commit

Permalink
cherry-pick(#27555): chore: composed->merge
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Oct 11, 2023
1 parent ae31f58 commit 3049d99
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 49 deletions.
46 changes: 34 additions & 12 deletions docs/src/release-notes-js.md
Expand Up @@ -8,46 +8,68 @@ import LiteYouTube from '@site/src/components/LiteYouTube';

## Version 1.39

### Extending expect with custom matchers
### Add custom matchers to your expect

You can extend Playwright assertions by providing custom matchers. These matchers will be available on the expect object.

```js title=fixtures.ts
```js title="test.spec.ts"
import { expect as baseExpect } from '@playwright/test';
export const expect = baseExpect.extend({
async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) {
// Note: this matcher never passes, see the documentation for a full example.
// Return a "pass" flag and a message getter.
return { pass: false, message: () => `Expected ${expected} amount` };
// ... see documentation for how to write matchers.
},
});

test('pass', async ({ page }) => {
await expect(page.getByTestId('cart')).toHaveAmount(5);
});
```

See the documentation [for a full example](./test-configuration.md#add-custom-matchers-using-expectextend).

### Merging fixtures and expect matchers
### Merge test fixtures

You can now merge test fixtures from multiple files or modules:

```js title="fixtures.ts"
import { mergeTests } from '@playwright/test';
import { test as dbTest } from 'database-test-utils';
import { test as a11yTest } from 'a11y-test-utils';

export const test = mergeTests(dbTest, a11yTest);
```

```js title="test.spec.ts"
import { test } from './fixtures';

test('passes', async ({ database, page, a11y }) => {
// use database and a11y fixtures.
});
```

### Merge custom expect matchers

You can combine fixtures and custom expect matchers from multiple files or modules.
You can now merge custom expect matchers from multiple files or modules:

```js title="fixtures.ts"
import { composedTest, composedExpect } from '@playwright/test';
import { mergeTests, mergeExpects } from '@playwright/test';
import { test as dbTest, expect as dbExpect } from 'database-test-utils';
import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils';

export const expect = composedExpect(dbExpect, a11yExpect);
export const test = composedTest(dbTest, a11yTest);
export const test = mergeTests(dbTest, a11yTest);
export const expect = mergeExpects(dbExpect, a11yExpect);
```

```js title="test.spec.ts"
import { test, expect } from './fixtures';

test('passes', async ({ database, page }) => {
test('passes', async ({ page, database }) => {
await expect(database).toHaveDatabaseUser('admin');
await expect(page).toPassA11yAudit();
});
```

### Boxed test steps
### Hide implementation details: box test steps

You can mark a [`method: Test.step`] as "boxed" so that errors inside it point to the step call site.

Expand Down
6 changes: 3 additions & 3 deletions docs/src/test-configuration-js.md
Expand Up @@ -217,12 +217,12 @@ Do not confuse Playwright's `expect` with the [`expect` library](https://jestjs.
You can combine custom matchers from multiple files or modules.
```js title="fixtures.ts"
import { composedTest, composedExpect } from '@playwright/test';
import { mergeTests, mergeExpects } from '@playwright/test';
import { test as dbTest, expect as dbExpect } from 'database-test-utils';
import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils';

export const expect = composedExpect(dbExpect, a11yExpect);
export const test = composedTest(dbTest, a11yTest);
export const expect = mergeExpects(dbExpect, a11yExpect);
export const test = mergeTests(dbTest, a11yTest);
```
```js title="test.spec.ts"
Expand Down
6 changes: 3 additions & 3 deletions packages/playwright/src/common/testType.ts
Expand Up @@ -230,7 +230,7 @@ export class TestTypeImpl {

private _extend(location: Location, fixtures: Fixtures) {
if ((fixtures as any)[testTypeSymbol])
throw new Error(`test.extend() accepts fixtures object, not a test object.\nDid you mean to call composedTest()?`);
throw new Error(`test.extend() accepts fixtures object, not a test object.\nDid you mean to call mergeTests()?`);
const fixturesWithLocation: FixturesWithLocation = { fixtures, location };
return new TestTypeImpl([...this.fixtures, fixturesWithLocation]).test;
}
Expand All @@ -249,12 +249,12 @@ function throwIfRunningInsideJest() {

export const rootTestType = new TestTypeImpl([]);

export function composedTest(...tests: TestType<any, any>[]) {
export function mergeTests(...tests: TestType<any, any>[]) {
let result = rootTestType;
for (const t of tests) {
const testTypeImpl = (t as any)[testTypeSymbol] as TestTypeImpl;
if (!testTypeImpl)
throw new Error(`composedTest() accepts "test" functions as parameters.\nDid you mean to call test.extend() with fixtures instead?`);
throw new Error(`mergeTests() accepts "test" functions as parameters.\nDid you mean to call test.extend() with fixtures instead?`);
// Filter out common ancestor fixtures.
const newFixtures = testTypeImpl.fixtures.filter(theirs => !result.fixtures.find(ours => ours.fixtures === theirs.fixtures));
result = new TestTypeImpl([...result.fixtures, ...newFixtures]);
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright/src/index.ts
Expand Up @@ -744,7 +744,7 @@ function renderApiCall(apiName: string, params: any) {
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);

export { defineConfig } from './common/configLoader';
export { composedTest } from './common/testType';
export { composedExpect } from './matchers/expect';
export { mergeTests } from './common/testType';
export { mergeExpects } from './matchers/expect';

export default test;
2 changes: 1 addition & 1 deletion packages/playwright/src/matchers/expect.ts
Expand Up @@ -338,6 +338,6 @@ function computeArgsSuffix(matcherName: string, args: any[]) {

expectLibrary.extend(customMatchers);

export function composedExpect(...expects: any[]) {
export function mergeExpects(...expects: any[]) {
return expect;
}
4 changes: 2 additions & 2 deletions packages/playwright/types/test.d.ts
Expand Up @@ -5328,15 +5328,15 @@ type MergedTestType<List> = TestType<MergedT<List>, MergedW<List>>;
/**
* Merges fixtures
*/
export function composedTest<List extends any[]>(...tests: List): MergedTestType<List>;
export function mergeTests<List extends any[]>(...tests: List): MergedTestType<List>;

type MergedExpectMatchers<List> = List extends [Expect<infer M>, ...(infer Rest)] ? M & MergedExpectMatchers<Rest> : {};
type MergedExpect<List> = Expect<MergedExpectMatchers<List>>;

/**
* Merges expects
*/
export function composedExpect<List extends any[]>(...expects: List): MergedExpect<List>;
export function mergeExpects<List extends any[]>(...expects: List): MergedExpect<List>;

// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};
Expand Down
4 changes: 2 additions & 2 deletions tests/config/baseTest.ts
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { composedTest } from '@playwright/test';
import { mergeTests } from '@playwright/test';
import { test } from '@playwright/test';
import type { CommonFixtures, CommonWorkerFixtures } from './commonFixtures';
import { commonFixtures } from './commonFixtures';
Expand All @@ -26,7 +26,7 @@ import { testModeTest } from './testModeFixtures';

export const base = test;

export const baseTest = composedTest(base, coverageTest, platformTest, testModeTest)
export const baseTest = mergeTests(base, coverageTest, platformTest, testModeTest)
.extend<CommonFixtures, CommonWorkerFixtures>(commonFixtures)
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures);

Expand Down
@@ -1,9 +1,9 @@
import { test as test1, expect as expect1, composedTest, composedExpect } from '@playwright/test';
import { test as test1, expect as expect1, mergeTests, mergeExpects } from '@playwright/test';
import type { Page } from '@playwright/test';
import { test as test2, expect as expect2 } from 'playwright-test-plugin';

const test = composedTest(test1, test2);
const expect = composedExpect(expect1, expect2);
const test = mergeTests(test1, test2);
const expect = mergeExpects(expect1, expect2);

test('sample test', async ({ page, plugin }) => {
type IsPage = (typeof page) extends Page ? true : never;
Expand Down
6 changes: 3 additions & 3 deletions tests/installation/fixture-scripts/plugin.spec.ts
@@ -1,8 +1,8 @@
import { test as test1, expect as expect1, composedTest, composedExpect } from '@playwright/test';
import { test as test1, expect as expect1, mergeTests, mergeExpects } from '@playwright/test';
import { test as test2, expect as expect2 } from 'playwright-test-plugin';

const test = composedTest(test1, test2);
const expect = composedExpect(expect1, expect2);
const test = mergeTests(test1, test2);
const expect = mergeExpects(expect1, expect2);

test('sample test', async ({ page, plugin }) => {
await page.setContent(`<div>hello</div><span>world</span>`);
Expand Down
12 changes: 6 additions & 6 deletions tests/playwright-test/expect.spec.ts
Expand Up @@ -879,10 +879,10 @@ test('should suppport toHaveAttribute without optional value', async ({ runTSC }
expect(result.exitCode).toBe(0);
});

test('should support composedExpect (TSC)', async ({ runTSC }) => {
test('should support mergeExpects (TSC)', async ({ runTSC }) => {
const result = await runTSC({
'a.spec.ts': `
import { test, composedExpect, expect as baseExpect } from '@playwright/test';
import { test, mergeExpects, expect as baseExpect } from '@playwright/test';
import type { Page } from '@playwright/test';
const expect1 = baseExpect.extend({
Expand All @@ -897,7 +897,7 @@ test('should support composedExpect (TSC)', async ({ runTSC }) => {
}
});
const expect = composedExpect(expect1, expect2);
const expect = mergeExpects(expect1, expect2);
test('custom matchers', async ({ page }) => {
await expect(page).toBeAGoodPage(123);
Expand All @@ -914,10 +914,10 @@ test('should support composedExpect (TSC)', async ({ runTSC }) => {
expect(result.exitCode).toBe(0);
});

test('should support composedExpect', async ({ runInlineTest }) => {
test('should support mergeExpects', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.spec.ts': `
import { test, composedExpect, expect as baseExpect } from '@playwright/test';
import { test, mergeExpects, expect as baseExpect } from '@playwright/test';
import type { Page } from '@playwright/test';
const expect1 = baseExpect.extend({
Expand All @@ -932,7 +932,7 @@ test('should support composedExpect', async ({ runInlineTest }) => {
}
});
const expect = composedExpect(expect1, expect2);
const expect = mergeExpects(expect1, expect2);
test('custom matchers', async ({ page }) => {
await expect(page).toBeAGoodPage(123);
Expand Down
16 changes: 8 additions & 8 deletions tests/playwright-test/test-extend.spec.ts
Expand Up @@ -160,15 +160,15 @@ test('config should override options but not fixtures', async ({ runInlineTest }
expect(result.output).toContain('fixture-config-fixture');
});

test('composedTest should be able to merge', async ({ runInlineTest }) => {
test('mergeTests should be able to merge', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: { param: 'from-config' },
};
`,
'a.test.ts': `
import { test, expect, composedTest } from '@playwright/test';
import { test, expect, mergeTests } from '@playwright/test';
const base = test.extend({
myFixture: 'abc',
});
Expand All @@ -184,7 +184,7 @@ test('composedTest should be able to merge', async ({ runInlineTest }) => {
fixture2: ({}, use) => use('fixture2'),
});
const test3 = composedTest(test1, test2);
const test3 = mergeTests(test1, test2);
test3('merged', async ({ param, fixture1, myFixture, fixture2 }) => {
console.log('param-' + param);
Expand All @@ -202,7 +202,7 @@ test('composedTest should be able to merge', async ({ runInlineTest }) => {
expect(result.output).toContain('fixture2-fixture2');
});

test('test.extend should print nice message when used as composedTest', async ({ runInlineTest }) => {
test('test.extend should print nice message when used as mergeTests', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test as base, expect } from '@playwright/test';
Expand All @@ -215,14 +215,14 @@ test('test.extend should print nice message when used as composedTest', async ({
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('Did you mean to call composedTest()?');
expect(result.output).toContain('Did you mean to call mergeTests()?');
});

test('composedTest should print nice message when used as extend', async ({ runInlineTest }) => {
test('mergeTests should print nice message when used as extend', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test as base, expect, composedTest } from '@playwright/test';
const test3 = composedTest(base, {});
import { test as base, expect, mergeTests } from '@playwright/test';
const test3 = mergeTests(base, {});
test3('test', () => {});
`,
});
Expand Down
4 changes: 2 additions & 2 deletions tests/playwright-test/types-2.spec.ts
Expand Up @@ -84,7 +84,7 @@ test('can return anything from hooks', async ({ runTSC }) => {
test('test.extend options should check types', async ({ runTSC }) => {
const result = await runTSC({
'helper.ts': `
import { test as base, expect, composedTest } from '@playwright/test';
import { test as base, expect, mergeTests } from '@playwright/test';
export type Params = { foo: string };
export const test = base;
export const test1 = test.extend<Params>({ foo: [ 'foo', { option: true } ] });
Expand All @@ -100,7 +100,7 @@ test('test.extend options should check types', async ({ runTSC }) => {
// @ts-expect-error
bar: async ({ baz }, run) => { await run(42); }
});
export const test4 = composedTest(test1, testW);
export const test4 = mergeTests(test1, testW);
const test5 = test4.extend<{}, { hey: string, hey2: string }>({
// @ts-expect-error
hey: [async ({ foo }, use) => {
Expand Down
4 changes: 2 additions & 2 deletions utils/generate_types/overrides-test.d.ts
Expand Up @@ -462,15 +462,15 @@ type MergedTestType<List> = TestType<MergedT<List>, MergedW<List>>;
/**
* Merges fixtures
*/
export function composedTest<List extends any[]>(...tests: List): MergedTestType<List>;
export function mergeTests<List extends any[]>(...tests: List): MergedTestType<List>;

type MergedExpectMatchers<List> = List extends [Expect<infer M>, ...(infer Rest)] ? M & MergedExpectMatchers<Rest> : {};
type MergedExpect<List> = Expect<MergedExpectMatchers<List>>;

/**
* Merges expects
*/
export function composedExpect<List extends any[]>(...expects: List): MergedExpect<List>;
export function mergeExpects<List extends any[]>(...expects: List): MergedExpect<List>;

// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};

0 comments on commit 3049d99

Please sign in to comment.