This library makes it possible to unit test react components in a real browser with Playwright.
Inspired by @cypress/react.
This is a proof of concept and is probably quite instable. I do not recommend anyone to use it at this stage.
npm install -D @playwright/test tjoskar/playwright-react
This is a proof of concept and in a exploratory stage.
There is currently four ways of using this lib.
Let's say you want to test this component:
// MyComponent.tsx
interface Props {
name: string;
}
export function MyComponent(props: Props) {
return <h1>Hello {props.name}</h1>;
}
// MyComponent.spec.ts
import { expect, test } from '@playwright/test';
import { createElement } from 'react';
import { setup } from '@tjoskar/playwright-react';
// Set up the components you want to test. You can add as many as you want.
const mount = setup({
MyComponent: () => import('./MyComponent').then(c => c.MyComponent),
});
test('Test MyComponent', async ({ page }) => {
await mount(page, ({ MyComponent }) => {
// You will get typescript intellisense here, but it can be better
return createElement(MyComponent, { name: "John" });
});
await expect(page.locator('text=Hello John')).toBeVisible();
});
- All setup in the same file
- Can use playwright's api
- Can not use jsx/tsx. See playwright#7121
- The code will be compiled by both Playwright (for runing in node) and esbuild (for runing in the browser)
- Closure will not work inside
mount
, ex. you can not access variables outsidemount
. Even if it looks like it. react.createElement
has bad ts types (pops is always optional).
// __tests__/ComponentUnderTest.tsx
import React from "react";
import { MyComponent } from "../MyComponent";
export const Stannis = () => <MyComponent name="Stannis" />;
// __tests__/MyComponent.spec.ts
import { expect } from "@playwright/test";
import { componentTest } from '@tjoskar/playwright-react';
componentTest("Test MyComponent", async ({ page, mount }) => {
await mount(() => import('./ComponentUnderTest').then(c => c.Stannis));
await expect(page.locator('text=Hello! My name is Stannis')).toBeVisible();
});
- Easy to understad
- Can use playwright's api
- Need seperate file for complex props (we do not need a seperate file in the example above)
// __tests__/MyComponent.comp-spec.tsx
import React from "react";
import { render, screen } from '@testing-library/react'
import { MyComponent } from "../MyComponent";
export const test = async () => {
render(<MyComponent name="Stannis" />);
screen.getByText(/Stannis/i);
}
// __tests__/MyComponent.spec.ts
import { expect } from "@playwright/test";
import { componentTest } from '@tjoskar/playwright-react';
componentTest("Test MyComponent", async ({ execute }) => {
await execute(() => import('./MyComponent.comp-spec').then(c => c.test));
});
- Can use (react) testing library
- Can not use playwright's api
- Need a assert lib (this can be solved)
- Need seperate file (this can be solved)
See tjoskar#1 for more information
// __tests__/ComponentUnderTest.tsx
import React from "react";
import { MyComponent } from "../MyComponent";
export const attachClickListener = ({ spy }: TestArgs) => {
const onClick = spy('click');
return () => <MyComponent name="Dexter" onClick={onClick} />;
};
// __tests__/MyComponent.spec.ts
import { expect } from "@playwright/test";
import { componentTest } from '@tjoskar/playwright-react';
componentTest("Test MyComponent with a spy function", async ({ page, mount }) => {
const { events } = await mount((utils) =>
import("./ComponentUnderTest").then((c) => c.attachClickListener(utils))
);
expect(events.callCount('click')).toBe(0);
await page.locator("text=Hello! My name is Dexter").click();
expect(events.callCount('click')).toBe(1);
expect(events.args('click')[0][0]).toBe('Dexter');
});
Create a component you want to take a snapshot of:
// src/components/__tests__/MyText.snap.tsx
import React from 'react';
import { MyText } from '../MyText';
export const tests = [{
name: 'My text: Stannis',
render(): JSX.Element {
return <MyText text="Stannis" />
}
}]
Create a test that will find all snapshots test and render them, one by one:
// src/snapshots.spec.tsx
import { takeSnapshot } from "@kivra/playwright-react";
takeSnapshot();
Update your playwright.config.ts
:
// playwright.config.ts
import { PlaywrightTestConfig } from '@kivra/playwright-react';
const config: PlaywrightTestConfig = {
testDir: 'src',
testMatch: '**/*.spec.ts',
use: {
viewport: { height: 1080, width: 1920 },
},
react: {
snapshotFileGlob: './src/**/*.snap.tsx',
}
};
export default config;
Create a Wrapper
component that will wrap every snapshot:
// .playwright/Wrapper.tsx
import React from "react";
import { mountAndTakeSnapshot } from '@kivra/playwright-react/client';
import { Wrapper } from './Wrapper';
function Wrapper({ children }: { children: JSX.Element }) {
return (
<div>
<Theme color="dark">{children}</Theme>
<Theme color="light">{children}</Theme>
</div>
);
}
mountAndTakeSnapshot(Test => (
<Wrapper>
<Test />
</Wrapper>
));
TODO: Document the server setup
Playwright will now take a snapshot of all your components in your ./src/**/*.snap.tsx
files
To test this in example, fisrt pack this lib with npm run build && npm pack
and then run npm install
inside example
. It does not work to install it by npm install ..
due to linking isuues.