diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 754dc6045e1e6..6a30ca5761876 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -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. diff --git a/docs/src/test-configuration-js.md b/docs/src/test-configuration-js.md index cfb534fff0ecd..99117a16bb6e3 100644 --- a/docs/src/test-configuration-js.md +++ b/docs/src/test-configuration-js.md @@ -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" diff --git a/packages/playwright/src/common/testType.ts b/packages/playwright/src/common/testType.ts index 5af6df000e84b..0b3dc6e1c9bf7 100644 --- a/packages/playwright/src/common/testType.ts +++ b/packages/playwright/src/common/testType.ts @@ -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; } @@ -249,12 +249,12 @@ function throwIfRunningInsideJest() { export const rootTestType = new TestTypeImpl([]); -export function composedTest(...tests: TestType[]) { +export function mergeTests(...tests: TestType[]) { 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]); diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 66f78699cc027..d2723aacce4cc 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -744,7 +744,7 @@ function renderApiCall(apiName: string, params: any) { export const test = _baseTest.extend(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; diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index dc05f09735e4f..102ec98d9a4f4 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -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; } diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 9fa455eb21cc5..e8abf30211c1e 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5328,7 +5328,7 @@ type MergedTestType = TestType, MergedW>; /** * Merges fixtures */ -export function composedTest(...tests: List): MergedTestType; +export function mergeTests(...tests: List): MergedTestType; type MergedExpectMatchers = List extends [Expect, ...(infer Rest)] ? M & MergedExpectMatchers : {}; type MergedExpect = Expect>; @@ -5336,7 +5336,7 @@ type MergedExpect = Expect>; /** * Merges expects */ -export function composedExpect(...expects: List): MergedExpect; +export function mergeExpects(...expects: List): MergedExpect; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/tests/config/baseTest.ts b/tests/config/baseTest.ts index a89c5ff7cc1a2..c49eec6699e23 100644 --- a/tests/config/baseTest.ts +++ b/tests/config/baseTest.ts @@ -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'; @@ -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) .extend(serverFixtures); diff --git a/tests/installation/fixture-scripts/playwright-test-plugin-types.ts b/tests/installation/fixture-scripts/playwright-test-plugin-types.ts index e16d612b872fc..880520ebce98d 100644 --- a/tests/installation/fixture-scripts/playwright-test-plugin-types.ts +++ b/tests/installation/fixture-scripts/playwright-test-plugin-types.ts @@ -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; diff --git a/tests/installation/fixture-scripts/plugin.spec.ts b/tests/installation/fixture-scripts/plugin.spec.ts index 93a40a7584897..125655c5d5e63 100644 --- a/tests/installation/fixture-scripts/plugin.spec.ts +++ b/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(`
hello
world`); diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 0833db485f17d..0167dd0e5041d 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -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({ @@ -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); @@ -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({ @@ -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); diff --git a/tests/playwright-test/test-extend.spec.ts b/tests/playwright-test/test-extend.spec.ts index b4584b1d48c20..94ee2bf19a250 100644 --- a/tests/playwright-test/test-extend.spec.ts +++ b/tests/playwright-test/test-extend.spec.ts @@ -160,7 +160,7 @@ 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 = { @@ -168,7 +168,7 @@ test('composedTest should be able to merge', async ({ runInlineTest }) => { }; `, 'a.test.ts': ` - import { test, expect, composedTest } from '@playwright/test'; + import { test, expect, mergeTests } from '@playwright/test'; const base = test.extend({ myFixture: 'abc', }); @@ -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); @@ -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'; @@ -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', () => {}); `, }); diff --git a/tests/playwright-test/types-2.spec.ts b/tests/playwright-test/types-2.spec.ts index 699e241ec5271..f463f21d60997 100644 --- a/tests/playwright-test/types-2.spec.ts +++ b/tests/playwright-test/types-2.spec.ts @@ -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({ foo: [ 'foo', { option: true } ] }); @@ -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) => { diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 988df1b9bf896..ac89c360a48db 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -462,7 +462,7 @@ type MergedTestType = TestType, MergedW>; /** * Merges fixtures */ -export function composedTest(...tests: List): MergedTestType; +export function mergeTests(...tests: List): MergedTestType; type MergedExpectMatchers = List extends [Expect, ...(infer Rest)] ? M & MergedExpectMatchers : {}; type MergedExpect = Expect>; @@ -470,7 +470,7 @@ type MergedExpect = Expect>; /** * Merges expects */ -export function composedExpect(...expects: List): MergedExpect; +export function mergeExpects(...expects: List): MergedExpect; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {};