FAQ
Isn't this like Playwright's page.evaluate?
Yes (but in reverse)! We were inspired by exactly this approach, but in Playwright you drive tests
from outside the app, and have to sort of sneak in through the chimney if you want access to data
from the app context.
If you want to compare browser state with server state, you write code like this on your test server:
page.evaluate((data) => somePageExpression(data), data). And it ships that function over
to the browser and executes it there (in the browser's global context) and exfiltrates the return
values to your test server where you then run your pass/fail/expect/assert/evaluate.
This works, but it has a problem: the data you need often isn't available in the global context.
If you want to check the value returned by useProfile() at the exact moment
your component uses it, you can't. The closest you can get is to attach things to
window and hope for the best:
// In your app code (gross)
window.__profileData = profile
// In your test file
const profile = await page.evaluate(() => window.__profileData)
And even this doesn't give you the right timing. You're not getting the value at the moment the component rendered—you're getting whatever value happens to be there when your test remembers to check. The more work you put into transitions, caching logic, and optimistic updates, the more this matters.
Scenetest assertions run inside your components, at the exact point in the lifecycle where you expect certain things to be true. No window hacks. No timing guesswork.
Isn't this like Vitest's in-source testing?
Vitest has an in-source testing feature that lets you colocate tests with your code:
export function add(...args: number[]) {
return args.reduce((a, b) => a + b, 0)
}
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
it('add', () => {
expect(add(1, 2, 3)).toBe(6)
})
}
This is great for unit tests, and the colocation is nice. But these tests don't run in the application context. They run in Vitest's test environment, not in your actual React app with your actual state and your actual data. Vitest In-Source's benefit is it runs in the same closure as your app code... but when you are using a framework like React, Svelte, Solid or Vue, with a client router and a cache and so on, you need better than in-closure tests; you need to be in the component/render lifecycle of the component you're testing, in the callback for the callback you're testing, and so on.
There's just no way around the need for this. And what it means is the test-writer has the tools to gather up any context or data that is serializable and ship it to the protected / privileged test environment to then check any other resources in your whole stack; in theory there is really no limit to the scope or precision of your test.
What about Cypress component testing?
Cypress is a great tool for driving browsers through automated scripts and journeys in your app, and component testing is a popular way of mounting individual components in isolation and then doing things to them to be sure they respond as expected. That's useful for testing components as units – and it becomes even more powerful if you have the same inline, in-scope assertions running inside those components while the engineer is building the feature, when your e2e tests run, when your AI agent spins up its own little browser and clicks around, and when your human coworker is doing the final check for you before deployment.