Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Render the Solid component on the server, then pass the resulting HTML string to Pyppeteer with await page.setContent(html). If you need to test the app at its real URL, use page.goto(url) instead. For server-rendered output that depends on asynchronous Suspense boundaries, await Solid’s renderToStringAsync before loading the HTML. Loading markup alone does not hydrate it or enable Solid’s client-side behavior.

Choose how to render and load the page

The right method depends on whether your test needs a string of server-rendered markup, a real browser navigation, streamed output, or interactive client behavior. Solid’s render functions run in a server build; Pyppeteer then either receives the resulting HTML or navigates to an app served over HTTP. Solid’s project repository describes Solid as isomorphic, but that does not make its server-rendering APIs browser-bundle APIs.

Goal Solid step Pyppeteer step What the setup tests
Inspect a synchronous server-rendered snapshot renderToString(() => <App />) await page.setContent(html) The supplied HTML and its static document content; synchronous output does not wait for async Suspense boundaries.
Wait for server Suspense work before producing a string await renderToStringAsync(() => <App />) await page.setContent(html) HTML generated after server Suspense boundaries settle, subject to the configured timeout.
Test the running website and its network-loaded assets Run the app through its normal server setup. await page.goto(url, options) Navigation to the real URL and browser loading of its resources.
Test streamed server rendering Serve the response produced by renderToStream. Navigate to the endpoint and wait for the expected app state. The initial shell and later asynchronous fragments, with readiness determined by your app.
Test hydrated interactivity Produce matching server output and client JSX; include the hydration bootstrap and client code. Load the complete document and wait for the client app to attach. Server markup reused by Solid’s client runtime, provided the DOM and client JSX match.

Render Solid HTML, then call Pyppeteer

Solid’s renderToString returns the current server-rendered output synchronously. Use it when that output is enough; it does not wait for asynchronous Suspense boundaries. If those boundaries must resolve before you create the string, use renderToStringAsync, which returns a promise and supports timeoutMs as a maximum wait.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The following is an integration shape: generate HTML in your server-side Solid build, provide it to the Python test process, and load it into a page. Adapt imports, JSX compilation, app inputs, and how the HTML crosses the process boundary to your project. These snippets have not been executed against a particular project or package version.

// Server-side Solid module; run in a server build, not a browser bundle.
import { renderToStringAsync } from "solid-js/web";
import App from "./App";

const html = await renderToStringAsync(() => <App />);
// Return `html` to the test process, or embed it in a complete document.
# Python test process
import asyncio
from pyppeteer import launch

async def main():
    # Replace this with the call or fixture that obtains HTML from your
    # Solid server renderer. `html` must be a string of markup.
    html = await get_html_from_your_server_renderer()

    browser = await launch()
    try:
        page = await browser.newPage()
        await page.setContent(html)
        # Assert the content or state that this test actually needs.
        await page.waitForSelector("#app .expected-result")
    finally:
        await browser.close()

asyncio.get_event_loop().run_until_complete(main())

get_html_from_your_server_renderer() and #app .expected-result are project-specific: implement the former using your server or test fixture and replace the selector with one present in your markup. If you only need static content, inspect or assert that content instead of waiting for a selector that does not exist.

For a synchronous server snapshot

On the Solid side, use renderToString(() => <App />) when the rendered output should reflect the synchronous server render. Pass its returned string through the same Pyppeteer setContent flow. Do not rely on this API to wait for asynchronous Suspense work; choose the async renderer when that is required.

For async Suspense boundaries

Await renderToStringAsync before sending the string to Python. Its timeout option lets you bound the server render’s wait. A timeout is not the same as a Pyppeteer navigation timeout: one applies while Solid produces the string, the other while the browser navigates or waits.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a URL when you need a real navigation

page.setContent(html) assigns supplied markup to the page. page.goto(url) navigates to a URL. Use navigation when the purpose of the test includes the app server, normal browser resource loading, or the page’s actual URL context—not merely to inspect a string already produced by Solid.

page = await browser.newPage()
await page.goto("http://127.0.0.1:3000", {"waitUntil": "domcontentloaded"})
await page.waitForSelector("#app .expected-result")

Pyppeteer’s project source lists load, domcontentloaded, and networkidle0 as navigation conditions. In that source, networkidle0 means no more than zero network connections for at least 500 ms. This is a network milestone, not proof that your application has finished its own asynchronous work. Prefer a selector or explicit ready signal that represents the state the test needs. The documented behavior is in the Pyppeteer Page implementation source; check it against the version installed in your project.

Separate static markup from hydration

Loading Solid’s server-generated string with setContent gives the browser a document to inspect; it does not, by itself, run Solid’s client hydration. Solid’s hydrate API attaches client behavior to DOM already rendered by Solid. The server-rendered DOM must match the JSX returned by the client hydration function for hydration to succeed.

For a hydration test, serve or construct the complete document with the appropriate client bundle and hydration bootstrap, then wait for a meaningful interactive state and exercise it. Solid’s hydrationScript documentation describes initializing window._$HY and bootstrapping delegated event replay; include that script once in the server-rendered document when the page will hydrate. Treat server rendering and browser-side hydration as separate stages, and ensure the client app uses the same data and output assumptions as the server render.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Markup test: generate HTML on the server, load it with setContent, and inspect static output.
  • Browser app test: navigate to the hosted app or load a complete document with browser scripts and resources, then wait for a page-specific ready condition.
  • Hydration test: retain the server markup, load the bootstrap and client code, and verify client behavior rather than inferring it from the presence of HTML.

Handle streamed rendering with an app-specific readiness check

Solid’s renderToStream can flush an initial shell, including Suspense fallback content, and continue writing asynchronous content as resources resolve. It provides pipe for Node-style writables and pipeTo for a WritableStream. When Pyppeteer navigates to an endpoint backed by streamed SSR, do not assume a navigation milestone means the fragment your test needs has arrived. Wait for that content’s selector or another explicit application-ready condition.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your end goal is a screenshot of a page that is already available over HTTP, ScreenshotNeo offers a website screenshot API and MCP server from ScreenshotNeo. It does not replace Solid server rendering or test hydration; it can capture a rendered web page without you setting up Pyppeteer for that capture. One GET request returns an image or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Before capture, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot common setup failures

  • The HTML string is empty or incomplete: Check that the server renderer actually returns the string to the Pyppeteer process. If the missing part depends on Suspense work, use and await renderToStringAsync rather than assuming synchronous rendering waited for it.
  • Pyppeteer cannot find an expected selector: Confirm the selector exists in the supplied HTML, or that the URL navigation reaches the expected route. For streamed output or client-rendered content, wait for the relevant app state rather than treating navigation completion as readiness.
  • Static content appears but clicks or reactive updates do not work: setContent does not itself hydrate server markup. Load the complete document with the hydration bootstrap and matching client bundle, then verify that the server DOM matches the client JSX.
  • Hydration does not attach as expected: Check that the hydration function returns markup matching the server-rendered DOM and that the hydration bootstrap is present once in the document when needed.
  • Navigation returns before expected async content: Network-idle or DOM-content-loaded conditions do not necessarily represent app readiness. Wait for a content-specific selector or explicit ready signal.
  • Server rendering APIs fail in a browser bundle: Move Solid server rendering into a server build or process. The rendering functions documented here are not browser-bundle APIs.

Performance, reliability, and cost considerations

Use the smallest setup that matches the test’s goal. Passing an already-generated string avoids a browser navigation, while goto exercises a served page and its browser-loaded resources. Async server rendering can wait on Suspense boundaries, so set a suitable timeoutMs for the server render and separately configure or assert browser readiness as appropriate. For streamed pages, a meaningful app-specific wait avoids confusing an early shell with completed content.

Keep the Solid renderer and Pyppeteer test process as distinct components if they run in different environments: make the transfer of the HTML explicit, and report renderer errors separately from browser errors. The cited official documentation establishes these API distinctions, but does not provide a project-independent performance benchmark, cost estimate, or compatibility guarantee for a particular installed version. Confirm behavior against your app’s Solid and Pyppeteer versions.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.