diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 93c0db3..81b7312 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -25,6 +25,10 @@ jobs: npx prettier --check . - run: npm run build - run: npm test + env: + # Used by chalk. Ensures output from Jest includes ANSI escape + # characters that are needed to match test snapshots. + FORCE_COLOR: true - run: npx codecov env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index c1f0b8d..db4b0e6 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,16 @@ To activate it in your Jest environment you have to include it in your configura } ``` -### Without Jest +### With Playwright test runner -```javascript -import expect from "expect-playwright" +To activate with the Playwright test runner, use `expect.extend` to add the `expect-playwright` matchers. -await expect(page).toHaveText("#foo", "my text") +```js +// folio.config.ts +import { expect } from "@playwright/test" +import { matchers } from "expect-playwright" + +expect.extend(matchers) ``` ## Why do I need it diff --git a/src/__snapshots__/index.test.ts.snap b/src/__snapshots__/index.test.ts.snap deleted file mode 100644 index 34dbd55..0000000 --- a/src/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`expectPlaywright should be able to handle negative cases return right result for page and 2 arguments 1`] = `"'zzzBarzzz' is not included in 'zzzzz'."`; - -exports[`expectPlaywright should be able to handle negative cases return right result for page and 4 arguments 1`] = `"Error: Timeout exceed for element '#bar'"`; diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index 94fe597..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import matchers from "./matchers" - -import expectPlaywright from "." - -describe("expect-playwright", () => { - afterEach(async () => { - await page.setContent("") - }) - it("should apply the functions", () => { - for (let matcher in matchers) { - // @ts-ignore - expect(expect(null)[matcher]).not.toBeUndefined() - } - }) - it("should be possible to use a not selector", async () => { - await page.setContent(`
zzzBarzzz
`) - await expect(page).not.toHaveText("This is definitely not there") - }) - it("should be possible to use a normal selector", async () => { - await page.setContent(`
zzzBarzzz
`) - await expect(page).toHaveText("zzzBarzzz") - }) -}) - -describe("expectPlaywright", () => { - afterEach(async () => { - await page.setContent("") - }) - describe("should be able to handle positive cases", () => { - it("return right result for page and 2 arguments", async () => { - await page.setContent(`
zzzBarzzz
`) - expect(await expectPlaywright(page).toHaveText("zzzBarzzz")).toBe(true) - }) - it("return right result for page and 3 arguments", async () => { - await page.setContent(`
zzzBarzzz
`) - expect(await expectPlaywright(page).toHaveText("#bar", "zzzBarzzz")).toBe( - true - ) - }) - it("return right result for element and 2 arguments", async () => { - await page.setContent(`
zzzFoozzz
`) - const elem = await page.$("#foo") - expect(await expectPlaywright(elem!).toHaveText("zzzFoozzz")).toBe(true) - }) - }) - describe("should be able to handle negative cases", () => { - it("return right result for page and 2 arguments", async () => { - await page.setContent(`
zzzzz
`) - await expect( - expectPlaywright(page).toHaveText("zzzBarzzz") - ).rejects.toThrowErrorMatchingSnapshot() - }) - it("return right result for page and 4 arguments", async () => { - await page.setContent(`
zzzBarzzz
`) - await expect( - expectPlaywright(page).toHaveText("#bar", "zzzBarzzz", { - timeout: 1 * 1000, - }) - ).rejects.toThrowErrorMatchingSnapshot() - }) - }) -}) diff --git a/src/index.ts b/src/index.ts index f94109d..1cad6e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ import matchers from "./matchers" -import type { Page, ElementHandle } from "playwright-core" -import type { PlaywrightMatchers } from "../global" // @ts-ignore if (typeof global.expect !== "undefined") { @@ -8,22 +6,4 @@ if (typeof global.expect !== "undefined") { global.expect.extend(matchers) } -const expectWrapper = ( - pageOrElement: Page | ElementHandle -): PlaywrightMatchers => - Object.entries(matchers).reduce( - (acc, [name, matcher]) => ({ - ...acc, - [name]: async (...args: any[]) => { - // @ts-ignore - const result = await matcher(pageOrElement, ...args) - if (!result.pass) { - throw new Error(result.message()) - } - return true - }, - }), - {} as PlaywrightMatchers - ) - -export = expectWrapper +export { matchers } diff --git a/src/matchers/tests/utils.ts b/src/matchers/tests/utils.ts index 57d3762..13299e0 100644 --- a/src/matchers/tests/utils.ts +++ b/src/matchers/tests/utils.ts @@ -8,3 +8,7 @@ export const testWrapper = (result: SyncExpectationResult) => { throw new Error(result.message()) } } + +export const assertSnapshot = async (fn: () => Promise) => { + await expect(fn).rejects.toThrowErrorMatchingSnapshot() +} diff --git a/src/matchers/toEqualText/__snapshots__/index.test.ts.snap b/src/matchers/toEqualText/__snapshots__/index.test.ts.snap index d03b6c7..d544ad6 100644 --- a/src/matchers/toEqualText/__snapshots__/index.test.ts.snap +++ b/src/matchers/toEqualText/__snapshots__/index.test.ts.snap @@ -1,9 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`toEqualText selector negative 1`] = `"'not-existing' does not equal 'zzzBarzzz' of '#foobar'."`; +exports[`toEqualText element negative 1`] = ` +"expect(received).toEqualText(expected) -exports[`toEqualText selector positive 1`] = `"'Bar' does equal 'Bar'."`; +Expected: \\"not-existing\\" +Received: \\"zzzBarzzz\\"" +`; -exports[`toEqualText selector positive frame 1`] = `"'Example Domain' does equal 'Example Domain'."`; +exports[`toEqualText page negative 1`] = ` +"expect(received).toEqualText(expected) + +Expected: \\"not-existing\\" +Received: \\"zzzBarzzz\\"" +`; + +exports[`toEqualText selector negative 1`] = ` +"expect(received).toEqualText(expected) + +Expected: \\"Bar\\" +Received: \\"zzzBarzzz\\"" +`; exports[`toEqualText selector timeout should throw an error after the timeout exceeds 1`] = `"Error: Timeout exceed for element '#foobar'"`; + +exports[`toEqualText selector with 'not' usage negative 1`] = ` +"expect(received).not.toEqualText(expected) + +Expected: not \\"Bar\\"" +`; diff --git a/src/matchers/toEqualText/index.test.ts b/src/matchers/toEqualText/index.test.ts index edca387..835f0c0 100644 --- a/src/matchers/toEqualText/index.test.ts +++ b/src/matchers/toEqualText/index.test.ts @@ -1,6 +1,7 @@ -import { testWrapper } from "../tests/utils" - import toEqualText from "." +import { assertSnapshot } from "../tests/utils" + +expect.extend({ toEqualText }) describe("toEqualText", () => { afterEach(async () => { @@ -10,40 +11,45 @@ describe("toEqualText", () => { it("positive frame", async () => { await page.setContent(``) const iframe = await page.$("iframe") - const result = await toEqualText(iframe!, "h1", "Example Domain") - expect(result.pass).toBe(true) - expect(result.message()).toMatchSnapshot() + await expect(iframe!).toEqualText("h1", "Example Domain") }) it("positive", async () => { await page.setContent(`
Bar
`) - const result = await toEqualText(page, "#foobar", "Bar") - expect(result.pass).toBe(true) - expect(result.message()).toMatchSnapshot() + await expect(page).toEqualText("#foobar", "Bar") }) it("negative", async () => { await page.setContent(`
zzzBarzzz
`) - expect( - testWrapper(await toEqualText(page, "#foobar", "not-existing")) - ).toThrowErrorMatchingSnapshot() + await assertSnapshot(() => expect(page).toEqualText("#foobar", "Bar")) + }) + describe("with 'not' usage", () => { + it("positive in frame", async () => { + await page.setContent(``) + const iframe = await page.$("iframe") + await expect(iframe!).not.toEqualText("h1", "Foo") + }) + it("positive", async () => { + await page.setContent(`
Bar
`) + await expect(page).not.toEqualText("#foobar", "Foo") + }) + it("negative", async () => { + await page.setContent(`
Bar
`) + await assertSnapshot(() => + expect(page).not.toEqualText("#foobar", "Bar") + ) + }) }) describe("timeout", () => { it("positive: should be able to use a custom timeout", async () => { setTimeout(async () => { await page.setContent(`
Bar
`) }, 500) - expect(testWrapper(await toEqualText(page, "#foobar", "Bar"))).toBe( - true - ) + await expect(page).toEqualText("#foobar", "Bar") }) it("should throw an error after the timeout exceeds", async () => { const start = new Date().getTime() - expect( - testWrapper( - await toEqualText(page, "#foobar", "Bar", { - timeout: 1 * 1000, - }) - ) - ).toThrowErrorMatchingSnapshot() + await assertSnapshot(() => + expect(page).toEqualText("#foobar", "Bar", { timeout: 1 * 1000 }) + ) const duration = new Date().getTime() - start expect(duration).toBeLessThan(1500) }) @@ -53,28 +59,24 @@ describe("toEqualText", () => { it("positive", async () => { await page.setContent(`
Bar
`) const element = await page.$("#foobar") - expect(element).not.toBe(null) - expect(testWrapper(await toEqualText(element!, "Bar"))).toBe(true) + expect(element).not.toBeNull() + await expect(element!).toEqualText("Bar") }) it("negative", async () => { await page.setContent(`
zzzBarzzz
`) const element = await page.$("#foobar") - expect(element).not.toBe(null) - expect( - testWrapper(await toEqualText(element!, "not-existing")) - ).toThrowError() + expect(element).not.toBeNull() + await assertSnapshot(() => expect(element!).toEqualText("not-existing")) }) }) describe("page", () => { it("positive", async () => { await page.setContent(`
Bar
`) - expect(testWrapper(await toEqualText(page, "Bar"))).toBe(true) + await expect(page).toEqualText("Bar") }) it("negative", async () => { await page.setContent(`
zzzBarzzz
`) - expect( - testWrapper(await toEqualText(page, "not-existing")) - ).toThrowError() + await assertSnapshot(() => expect(page).toEqualText("not-existing")) }) }) }) diff --git a/src/matchers/toEqualText/index.ts b/src/matchers/toEqualText/index.ts index 318ff79..cf355e2 100644 --- a/src/matchers/toEqualText/index.ts +++ b/src/matchers/toEqualText/index.ts @@ -1,30 +1,20 @@ import { SyncExpectationResult } from "expect/build/types" -import { getElementText, quote, InputArguments } from "../utils" +import { getElementText, getMessage, InputArguments } from "../utils" -const toEqualText = async ( +const toEqualText: jest.CustomMatcher = async function ( ...args: InputArguments -): Promise => { +): Promise { try { - const { elementHandle, selector, expectedValue } = await getElementText( - ...args - ) + const { elementHandle, expectedValue } = await getElementText(...args) /* istanbul ignore next */ - const actualTextContent = await elementHandle.evaluate( + const actualTextContent = await elementHandle.evaluate( (el) => el.textContent ) - if (actualTextContent === expectedValue) { - return { - pass: true, - message: () => - `${quote(expectedValue)} does equal ${quote(actualTextContent)}.`, - } - } + return { - pass: false, + pass: actualTextContent === expectedValue, message: () => - `${quote(expectedValue)} does not equal ${quote(actualTextContent)}${ - selector ? " of " + quote(selector) + "." : "." - }`, + getMessage(this, "toEqualText", expectedValue, actualTextContent), } } catch (err) { return { diff --git a/src/matchers/utils.ts b/src/matchers/utils.ts index 9a774a3..4ea35cb 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -113,4 +113,22 @@ export const getElementText = async ( throw new Error(`Invalid input length: ${args.length}`) } -export const quote = (val: string | null) => `'${val}'` +export const quote = (val: string | null) => (val === null ? "" : `'${val}'`) + +export const getMessage = ( + { isNot, promise, utils }: jest.MatcherContext, + matcher: string, + expected: string | null, + received: string | null +) => { + const message = isNot + ? `Expected: not ${utils.printExpected(expected)}` + : `Expected: ${utils.printExpected(expected)}\n` + + `Received: ${utils.printReceived(received)}` + + return ( + utils.matcherHint(matcher, undefined, undefined, { isNot, promise }) + + "\n\n" + + message + ) +}