playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Bug]: Playwright CT & React's renderToStaticMarkup "Error: Objects are not valid as a React child (found: object with keys {__pw_type"...

Open NedWilbur opened this issue 8 months ago • 9 comments

Version

1.51.1

Steps to reproduce

  1. Clone repo here
  2. Run outside of PW w/ npm run start => No issues
  3. Run within PW test w/ npm run test => Error

Expected behavior

No issues

Actual behavior

Error: Objects are not valid as a React child (found: object with keys {__pw_type, type, props, key}). If you meant to render a collection of children, use an array instead.

   at src/build.tsx:11

   9 |     throw new Error("Invalid React element");
  10 |
> 11 |   const html = renderToStaticMarkup(element);
     |                                    ^
  12 |   console.log(html);
  13 |
  14 |   return html;
    at retryNode (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5981:17)
    at renderNodeDestructive (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5849:11)
    at finishFunctionComponent (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:4743:13)
    at renderElement (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5185:11)
    at retryNode (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5899:31)
    at performWork (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6743:17)
    at startWork (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7467:7)
    at renderToStringImpl (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7555:7)
    at process.env.NODE_ENV.exports.renderToStaticMarkup (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9019:14)
    at render (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/src/build.tsx:11:36)
    at Object.<anonymous> (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/src/build.tsx:17:1)
    at Object.<anonymous> (/Users/nedwilbur/Projects/pw-rendertostaticmarkup/src/renderToStaticMarkup.test.tsx:2:1)

Additional context

I understand this error is coming from React, but I suspect Playwright is adding this __pw_type property, which renderToStaticMarkup cannot handle. This is needed to validate the output of renderToStaticMarkup as the output of this is used in parts of our application.

Environment

Binaries:
    Node: 22.14.0 - ~/.nvm/versions/node/v22.14.0/bin/node
    Yarn: 1.22.22 - ~/.yarn/bin/yarn
    npm: 10.9.2 - ~/.nvm/versions/node/v22.14.0/bin/npm
  IDEs:
    VSCode: 1.99.2 - /opt/homebrew/bin/code
  Languages:
    Bash: 5.2.37 - /opt/homebrew/bin/bash
  npmPackages:
    @playwright/experimental-ct-react: ^1.51.1 => 1.51.1 
    @playwright/test: ^1.51.1 => 1.51.1

NedWilbur avatar Apr 16 '25 18:04 NedWilbur

Update - this occurs with standard PW package too, not just with the CT wrapper package.

NedWilbur avatar Apr 17 '25 18:04 NedWilbur

Can you provide more context on what you're trying to accomplish? With the repro you shared, you're not actually using anything from Component Testing other than the settings (you're referencing defineConfig). Playwright has to do something with JSX it's provided with; Playwright transforms it into a shared format that is then consumed in the individual CT framework implementations. In your case, you're not using those implementations, and are instead passing JSX output directly to React.

What do you want to do with renderToStaticMarkup()? How does it relate to testing with Playwright?

agg23 avatar Apr 17 '25 18:04 agg23

@agg23 I updated the project to use vanilla playwright and the issue still occurs.

We use React for our email templates, which are "built" via renderToStaticMarkup, and eventually used when generating emails.

We want to use Playwright to visually validate emails inside various email clients via toHaveScreenshot. The test ideally would "build" the email templates via renderToStaticMarkup, send the email with the generated HTML, navigate to each email client, and visually validate it against snapshots.

As a workaround, we are "pre-building" the templates using renderToStaticMarkup to a file, then kicking off the tests which read the pre-built file. This adds a bit of complexity to our processes and would be streamlined if PW did not append this property and we could call renderToStaticMarkup within the test itself.

NedWilbur avatar Apr 17 '25 19:04 NedWilbur

Playwright transpiles JSX into a shared format so that it doesn't crash whenever people import a file that contains JSX. This was later expanded for component testing, so you could actually use the JSX we transpile. You can see our JSX runtime here.

Component testing consumes this JSX in its own way. Since you're using React, see how we convert the objects into React elements.


What I believe you want to have happen is you want Playwright to automatically (or through some configured property) know that you're using React, so your in-test renderToStaticMarkup() calls actually receive the React data you expect. This is not currently a supported use case.

Your options currently are to either use the playwright-ct-react render() method to run your JSX code (this will run it in-browser, which may not even be supported by React and is a lot slower than running in Node directly) or to duplicate the React JSX handling code) to build the objects yourself to pass to renderToStaticMarkup(). You could also spawn a separate Node process or pre-generate your output templates.

agg23 avatar Apr 18 '25 13:04 agg23

A potential workaround is to use Preact and https://www.npmjs.com/package/preact-render-to-string . I am using it to do server-side rendering of JSX.

At the moment, I'm switching a project to use React and renderToString from react-dom/server. I was surprised to see all of the Playwright tests fail because apparently Playwright is modifying JSX.

GET /signup-email-page 500 6.741 ms - 5834
Error: Objects are not valid as a React child (found: object with keys {__pw_type, type, props, key}). If you meant to render a collection of children, use an array instead.
    at retryNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5981:17)
    at performWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6743:17)
    at startWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7467:7)
    at renderToStringImpl (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7555:7)
    at process.env.NODE_ENV.exports.renderToString (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9027:14)
    at renderHtml (/app/api/tsx.tsx:10:39)
    at /app/api/server.tsx:261:27

Now I'm trying to find a way to turn off the component testing functionality. I'm getting the error with v1.37.1 through v1.54.2 (the latest).

mleonhard avatar Aug 01 '25 23:08 mleonhard

Related:

  • https://github.com/microsoft/playwright/issues/26936

mleonhard avatar Aug 01 '25 23:08 mleonhard

Haven't tested it, but the beforeMount or afterMount might be useful:

// playwright/index.tsx
import { beforeMount } from '@playwright/experimental-ct-react/hooks';

beforeMount(async ({ App, hooksConfig }) => {
  if (hooksConfig.emailTemplate) {
    document.getElementById('root').innerHTML = renderToString(App);
  }
});
// email-template.test.tsx
import { test } from '@playwright/experimental-ct-react';

test('email template', async ({ mount }) => {
  const component = await mount(EmailTemplate, { 
    hooksConfig: { emailTemplate = true }
  });
});

sand4rt avatar Aug 02 '25 13:08 sand4rt

I'm trying to use renderToStaticMarkup to generate an HTML report in a custom reporter, and I'm also getting the same error:

Error in reporter Error: Objects are not valid as a React child (found: object with keys {__pw_type, type, props, key}). If you meant to render a collection of children, use an array instead.

I am NOT using component testing at all

ashleyryan avatar Nov 16 '25 01:11 ashleyryan

I'm hitting this in a non-component context test as well.

I have a function:

// file: util.tsx
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { EmailTemplate } from './templates';

export const renderEmail = (names: string[]): string => renderToStaticMarkup(<EmailTemplate names={names} />)

It works outside of the browser and outside of playwright test, but when used in a @playwright/test, it errors out with:

Error in reporter Error: Objects are not valid as a React child (found: object with keys {__pw_type, type, props, key}). If you meant to render a collection of children, use an array instead.

@agg23 - I suspect the issue is even in projects that are not using component tests, the PW custom jsx runtime is always being used: https://github.com/microsoft/playwright/blob/02708fac2ebc02eb68b4fa4a402667953655427e/packages/playwright/bundles/babel/src/babelBundleImpl.ts#L74

rwoll avatar Nov 21 '25 00:11 rwoll