Issue 1:
Hi,

One thing that I miss from Cypress is @cypress/react, which gives the ability to test react components in the browser easily.

It is possible to write test that looks like this:

import { mount } from '@cypress/react'
import { createElement } from 'react';

// A simple react component
const MyComponent = () => createElement('h1', null, 'Hello');

it('works', () => {
mount(createElement(MyComponent))
cy.contains('Hello').should('be.visible')
});

I can see that this is very use-case-specific and maybe not be something that you want to include in playwright(?).

But, it would be nice if it would be possible to accomplish the same thing.

After looking at the source code of @cypress/react I thought that this might be possible to do in playwright as well. So I tried it out:

Attempt 1 – page.$eval

import { expect, test } from "@playwright/test";
import { render } from "react-dom";
import { createElement } from "react";

const MyComponent = () => createElement('h1', null, 'Hello');

test("basic test", async ({ page }) => {
// This is a page containing a div with id "root"
// The page can be created with fixtures: https://playwright.dev/docs/test-fixtures#worker-fixtures
await page.goto("http://localhost:3000/");
await page.$eval("#root", element => {
// This will render the component inside "element"
render(createElement(MyComponent), element);
});
const rootElement = page.locator("#root");
await expect(rootElement).toHaveText("Hello");
});

After running the test above with npx playwright test I got the error page.$eval: ReferenceError: _reactDom is not defined and I guess that is because the callback of page.$eval is serialized and passed to the browser context so closure does not work.

Attempt 2 – page.$eval with arguments

test("basic test", async ({ page, port }) => {
await page.goto(`http://localhost:${port}/`);
await page.$eval("#root", (element, arg) => {
arg.render(arg.createElement(arg.MyComponent), element);
}, { render, createElement, MyComponent });

const rootElement = page.locator("#root");
await expect(rootElement).toHaveText("Hello");
});

This code gives me the following error: page.$eval: Unexpected value, and once again that is because of serialization.

Attempt 3 – page.exposeFunction

test("basic test", async ({ page, port }) => {
await page.goto(`http://localhost:${port}/`);

await page.exposeFunction('render', render);
await page.exposeFunction('createElement', createElement);
await page.exposeFunction('MyComponent', MyComponent);

await page.$eval("#root", element => {
const win: any = window;
win.render(win.createElement(win.MyComponent), element);
});

const rootElement = page.locator("#root");
await expect(rootElement).toHaveText("Hello");
});

I now get the following warning in the terminal: Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: null. and the following error in the browser console after some debugging: Error: Target container is not a DOM element. I once again blame serialization.

Question

I understand that this might be tricky (with two contexts), and I have no idea how Cypress solves this but does anyone with more playwright experience have an idea how this can be solved?

Thanks!

Issue 2:
guys, any plans to implement component testing for vue and react js applications.

Edit: From the maintainers.

A beta just launched, which supports Vue, React, and Svelte, see here: https://playwright.dev/docs/test-components