<p dir="auto">Hi,</p>
<p dir="auto">One thing that I miss from Cypress is <a href="https://www.npmjs.com/package/@cypress/react" rel="nofollow"><code class="notranslate">@cypress/react</code></a>, which gives the ability to test react components in the browser easily.</p>
<p dir="auto">It is possible to write test that looks like this:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import { mount } from '@cypress/react'
import { createElement } from 'react';

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

it('works', () =&gt; {
  mount(createElement(MyComponent))
  cy.contains('Hello').should('be.visible')
});"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">mount</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@cypress/react'</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">createElement</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'react'</span><span class="pl-kos">;</span>

<span class="pl-c">// A simple react component</span>
<span class="pl-k">const</span> <span class="pl-smi">MyComponent</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-s">'h1'</span><span class="pl-kos">,</span> <span class="pl-c1">null</span><span class="pl-kos">,</span> <span class="pl-s">'Hello'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-en">it</span><span class="pl-kos">(</span><span class="pl-s">'works'</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-en">mount</span><span class="pl-kos">(</span><span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-smi">MyComponent</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
  <span class="pl-s1">cy</span><span class="pl-kos">.</span><span class="pl-en">contains</span><span class="pl-kos">(</span><span class="pl-s">'Hello'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">should</span><span class="pl-kos">(</span><span class="pl-s">'be.visible'</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">I can see that this is very use-case-specific and maybe not be something that you want to include in playwright(?).</p>
<p dir="auto">But, it would be nice if it would be possible to accomplish the same thing.</p>
<p dir="auto">After looking at the <a href="https://github.com/cypress-io/cypress/blob/2c6cd9e09923c853ad96df3efc3496e27a7c1b3e/npm/react/src/mount.ts#L98">source code of <code class="notranslate">@cypress/react</code></a> I thought that this might be possible to do in playwright as well. So I tried it out:</p>
<h3 dir="auto">Attempt 1 – <code class="notranslate">page.$eval</code></h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import { expect, test } from &quot;@playwright/test&quot;;
import { render } from &quot;react-dom&quot;;
import { createElement } from &quot;react&quot;;

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

test(&quot;basic test&quot;, async ({ page }) =&gt; {
  // This is a page containing a div with id &quot;root&quot;
  // The page can be created with fixtures: https://playwright.dev/docs/test-fixtures#worker-fixtures
  await page.goto(&quot;http://localhost:3000/&quot;);
  await page.$eval(&quot;#root&quot;, element =&gt; {
    // This will render the component inside &quot;element&quot;
    render(createElement(MyComponent), element);
  });
  const rootElement = page.locator(&quot;#root&quot;);
  await expect(rootElement).toHaveText(&quot;Hello&quot;);
});"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">expect</span><span class="pl-kos">,</span> <span class="pl-s1">test</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">"@playwright/test"</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">render</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">"react-dom"</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">createElement</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">"react"</span><span class="pl-kos">;</span>

<span class="pl-k">const</span> <span class="pl-smi">MyComponent</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-s">'h1'</span><span class="pl-kos">,</span> <span class="pl-c1">null</span><span class="pl-kos">,</span> <span class="pl-s">'Hello'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">"basic test"</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-c">// This is a page containing a div with id "root"</span>
  <span class="pl-c">// The page can be created with fixtures: https://playwright.dev/docs/test-fixtures#worker-fixtures</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">goto</span><span class="pl-kos">(</span><span class="pl-s">"http://localhost:3000/"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">$eval</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">,</span> <span class="pl-s1">element</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-c">// This will render the component inside "element"</span>
    <span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-smi">MyComponent</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-s1">element</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">const</span> <span class="pl-s1">rootElement</span> <span class="pl-c1">=</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">rootElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toHaveText</span><span class="pl-kos">(</span><span class="pl-s">"Hello"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">After running the test above with <code class="notranslate">npx playwright test</code> I got the error <code class="notranslate">page.$eval: ReferenceError: _reactDom is not defined</code> and I guess that is because the callback of <code class="notranslate">page.$eval</code> is serialized and passed to the browser context so closure does not work.</p>
<h3 dir="auto">Attempt 2 – <code class="notranslate">page.$eval</code> with arguments</h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="test(&quot;basic test&quot;, async ({ page, port }) =&gt; {
  await page.goto(`http://localhost:${port}/`);
  await page.$eval(&quot;#root&quot;, (element, arg) =&gt; {
    arg.render(arg.createElement(arg.MyComponent), element);
  }, { render, createElement, MyComponent });

  const rootElement = page.locator(&quot;#root&quot;);
  await expect(rootElement).toHaveText(&quot;Hello&quot;);
});"><pre class="notranslate"><span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">"basic test"</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page<span class="pl-kos">,</span> port <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">goto</span><span class="pl-kos">(</span><span class="pl-s">`http://localhost:<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">port</span><span class="pl-kos">}</span></span>/`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">$eval</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-s1">element</span><span class="pl-kos">,</span> <span class="pl-s1">arg</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-s1">arg</span><span class="pl-kos">.</span><span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-s1">arg</span><span class="pl-kos">.</span><span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-s1">arg</span><span class="pl-kos">.</span><span class="pl-c1">MyComponent</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-s1">element</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> render<span class="pl-kos">,</span> createElement<span class="pl-kos">,</span> MyComponent <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-k">const</span> <span class="pl-s1">rootElement</span> <span class="pl-c1">=</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">rootElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toHaveText</span><span class="pl-kos">(</span><span class="pl-s">"Hello"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">This code gives me the following error: <code class="notranslate">page.$eval: Unexpected value</code>, and once again that is because of serialization.</p>
<h3 dir="auto">Attempt 3 – <code class="notranslate">page.exposeFunction</code></h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="test(&quot;basic test&quot;, async ({ page, port }) =&gt; {
  await page.goto(`http://localhost:${port}/`);

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

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

  const rootElement = page.locator(&quot;#root&quot;);
  await expect(rootElement).toHaveText(&quot;Hello&quot;);
});"><pre class="notranslate"><span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">"basic test"</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page<span class="pl-kos">,</span> port <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">goto</span><span class="pl-kos">(</span><span class="pl-s">`http://localhost:<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">port</span><span class="pl-kos">}</span></span>/`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">exposeFunction</span><span class="pl-kos">(</span><span class="pl-s">'render'</span><span class="pl-kos">,</span> <span class="pl-s1">render</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">exposeFunction</span><span class="pl-kos">(</span><span class="pl-s">'createElement'</span><span class="pl-kos">,</span> <span class="pl-s1">createElement</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">exposeFunction</span><span class="pl-kos">(</span><span class="pl-s">'MyComponent'</span><span class="pl-kos">,</span> <span class="pl-smi">MyComponent</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">$eval</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">,</span> <span class="pl-s1">element</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-k">const</span> <span class="pl-s1">win</span>: <span class="pl-smi">any</span> <span class="pl-c1">=</span> <span class="pl-smi">window</span><span class="pl-kos">;</span>
    <span class="pl-s1">win</span><span class="pl-kos">.</span><span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-s1">win</span><span class="pl-kos">.</span><span class="pl-en">createElement</span><span class="pl-kos">(</span><span class="pl-s1">win</span><span class="pl-kos">.</span><span class="pl-c1">MyComponent</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-s1">element</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-k">const</span> <span class="pl-s1">rootElement</span> <span class="pl-c1">=</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">"#root"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">rootElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toHaveText</span><span class="pl-kos">(</span><span class="pl-s">"Hello"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">I now get the following warning in the terminal: <code class="notranslate">Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: null.</code> and the following error in the browser console after some debugging: <code class="notranslate">Error: Target container is not a DOM element</code>. I once again blame serialization.</p>
<h3 dir="auto">Question</h3>
<p dir="auto">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?</p>
<p dir="auto">Thanks!</p>
<p dir="auto">guys, any plans to implement component testing for vue and react js applications.</p>
<hr>
<h3 dir="auto">Edit: From the maintainers.</h3>
<p dir="auto">A beta just launched, which supports Vue, React, and Svelte, see here: <a href="https://playwright.dev/docs/test-components" rel="nofollow">https://playwright.dev/docs/test-components</a></p>