QA How-To
Cypress vs Playwright for Component Testing (2026)
Compare Cypress vs Playwright for component testing with React setup, runnable examples, debugging, browser coverage, CI trade-offs, and a clear verdict.
18 min read | 2,946 words
TL;DR
Cypress is the safer default for teams prioritizing a mature component-testing workflow and an excellent interactive runner. Playwright is the better strategic fit when the team already relies on Playwright Test and accepts the experimental component-testing package in exchange for shared fixtures, locators, assertions, projects, and traces.
Key Takeaways
- Choose Cypress when component testing is the main goal and interactive debugging speed matters most.
- Choose Playwright when sharing locators, assertions, projects, traces, and CI conventions with an existing Playwright suite outweighs its experimental component-test status.
- Both runners mount real framework components in a browser and support user-facing locators, network control, and multi-browser execution.
- Cypress Component Testing has the more mature, guided developer experience, while Playwright offers stronger unification with cross-browser end-to-end testing.
- Test component behavior through roles, labels, visible text, and observable callbacks instead of implementation details.
- Run a small proof of concept against styling, routing, context, network, and CI constraints before migrating a large suite.
Cypress vs Playwright for component testing is not a simple question of which runner is faster. Cypress provides the more established component-testing experience, with guided setup and an interactive command log. Playwright component testing is still delivered through experimental framework packages, but it gives Playwright teams a consistent test API, browser matrix, fixtures, and trace workflow.
Use Cypress when component tests are a first-class frontend workflow and developers want the easiest visual debugging loop. Use Playwright when component tests must fit an existing Playwright Test architecture and shared tooling is more valuable than API stability. This guide compares both choices using the same React component, realistic network behavior, and CI concerns.
TL;DR
| Decision factor | Cypress Component Testing | Playwright Component Testing |
|---|---|---|
| Maturity | Stable, productized workflow | Experimental framework packages |
| React mount API | mount(<Component />) from cypress/react |
mount(<Component />) fixture from @playwright/experimental-ct-react |
| Interactive debugging | Excellent command timeline and in-browser snapshots | Strong inspector, reports, screenshots, video, and traces |
| Existing E2E reuse | Best for Cypress E2E teams | Best for Playwright Test teams |
| Browser projects | Chromium, Firefox, and supported browsers through Cypress configuration | Chromium, Firefox, and WebKit projects |
| Network control | cy.intercept() |
page.route() |
| Isolation model | Fresh component test context with Cypress lifecycle | Playwright Test contexts plus a component mount facade |
| Primary recommendation | Default for component-first adoption | Strategic choice for Playwright-standardized teams |
The verdict is conditional. Cypress wins on component-test maturity and the local feedback loop. Playwright wins on suite unification, WebKit coverage, parallel project configuration, and diagnostic traces. Do not select either tool from a feature checklist alone. Mount one production component with its real CSS, providers, router, and mocked API, then run it locally and in CI.
1. What Component Testing Actually Proves
A component test renders a component in a real browser without starting the complete application. It sits between a DOM-oriented unit test and a full end-to-end journey. The browser executes layout, CSS, events, focus behavior, and framework updates, while the test controls inputs and external boundaries.
The useful boundary is not necessarily one source file. A checkout form with validation hooks, design-system inputs, and a context provider can be one component-test subject. Its test should verify what a user can see and do: labels are associated with controls, invalid input produces an alert, submission disables the button, and a successful response displays confirmation. It should not assert private state variables or the number of hook calls.
Component tests are especially valuable for state-rich UI, error branches that are expensive to reach through E2E setup, accessibility behavior, responsive rendering, and visual regressions. They do not replace E2E tests for routing across deployed pages, authentication infrastructure, backend contracts, cookies across origins, or production asset delivery. Keep a smaller E2E layer for those risks.
Both tools in this comparison render into a real browser and provide retrying web assertions. That distinguishes them from tests that only emulate the DOM in a Node process. If you are starting with Cypress, the Cypress component testing guide explains the broader testing model. For React-specific Playwright architecture, see Playwright component testing with React.
2. Cypress vs Playwright for Component Testing: Architecture
Cypress runs the test command chain in coordination with the application iframe. Its runner shows the mounted component next to a chronological command log. Clicking an earlier command restores a visual snapshot, which makes failures in rendering and interaction unusually easy to inspect. Cypress knows about the dev-server bundler and framework adapter through the component configuration.
Playwright Component Testing uses Playwright Test plus a framework-specific experimental package. For React, the package starts a Vite-powered component application and supplies a mount fixture. A mounted component is represented by a Locator, so standard Playwright locators and assertions apply. Tests can use Playwright fixtures, projects, reporters, retries, and trace configuration.
There is an important serialization boundary in Playwright. Test code runs in Node, while component code runs in the browser. Plain data can cross that boundary, and Playwright supports callback wrapping for event handlers, but complex live objects are not interchangeable. If a component expects a router instance, application store, or provider hierarchy, create a wrapper component inside the component-test application rather than attempting to pass the instance from the test process.
Cypress also rewards explicit wrappers, but its developer experience makes provider setup feel closer to the browser application. In either runner, centralize mounting in one support helper. A custom mount that always supplies theme, locale, query client, and router prevents each spec from constructing a subtly different application shell.
3. Prerequisites and Equivalent React Setup
Use a current Node.js LTS release and an existing React TypeScript project. Keep the test runner packages on compatible versions selected by the package manager. Avoid copying a version number from an old article because component adapters and peer requirements change independently.
For Cypress, install the runner and launch its guided configuration:
npm install --save-dev cypress
npx cypress open
Choose Component Testing, select React, and confirm the detected Vite bundler. A minimal configuration is:
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.{ts,tsx}',
},
});
In cypress/support/component.ts, register the React mount command:
import { mount } from 'cypress/react';
import './component.css';
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount;
}
}
}
Cypress.Commands.add('mount', mount);
For Playwright React component testing, install the framework adapter and its browsers:
npm install --save-dev @playwright/experimental-ct-react
npx playwright install
Create this configuration:
// playwright-ct.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-react';
export default defineConfig({
testDir: './src',
testMatch: '**/*.pw-ct.tsx',
use: {
ctPort: 3100,
trace: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Verify setup before writing behavior tests. Run npx cypress run --component --browser chrome for Cypress. Run npx playwright test -c playwright-ct.config.ts --project=chromium for Playwright. An empty suite may report that no tests were found, which still confirms that configuration loaded. Dependency or Vite errors must be fixed before the comparison is meaningful.
4. Build One Component for Both Runners
Use the same component so the test semantics, not the sample complexity, drive the comparison. This newsletter form validates input, calls an injected asynchronous function, reports failures accessibly, and prevents duplicate submission.
// src/NewsletterForm.tsx
import { FormEvent, useState } from 'react';
type Props = {
subscribe: (email: string) => Promise<void>;
};
export function NewsletterForm({ subscribe }: Props) {
const [email, setEmail] = useState('');
const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
async function submit(event: FormEvent) {
event.preventDefault();
if (!email.includes('@')) {
setStatus('error');
return;
}
setStatus('saving');
try {
await subscribe(email);
setStatus('saved');
} catch {
setStatus('error');
}
}
return (
<form onSubmit={submit}>
<label htmlFor="newsletter-email">Work email</label>
<input
id="newsletter-email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
<button disabled={status === 'saving'}>
{status === 'saving' ? 'Subscribing...' : 'Subscribe'}
</button>
{status === 'saved' && <p role="status">Subscription confirmed</p>}
{status === 'error' && <p role="alert">Enter a valid email or try again</p>}
</form>
);
}
This component exposes observable behavior and accepts the external operation as a dependency. That makes both examples deterministic without inventing a backend. It also tests an accessibility contract: the input has a label, the error uses alert, and success uses status.
Verify the production component first with TypeScript and the normal application build. A component test cannot compensate for invalid JSX or incompatible React types. If it renders unstyled, import the production global stylesheet from the runner support file or Playwright component test entry rather than duplicating CSS in the spec.
5. Test the Component with Cypress
Create src/NewsletterForm.cy.tsx. Cypress stubs integrate with its command log and aliases, so the test can inspect the callback after interacting with the UI.
import { NewsletterForm } from './NewsletterForm';
describe('NewsletterForm', () => {
it('submits a valid email once', () => {
const subscribe = cy.stub().resolves().as('subscribe');
cy.mount(<NewsletterForm subscribe={subscribe} />);
cy.findByLabelText('Work email').type('qa@example.com');
cy.findByRole('button', { name: 'Subscribe' }).click();
cy.get('@subscribe').should('have.been.calledOnceWith', 'qa@example.com');
cy.findByRole('status').should('have.text', 'Subscription confirmed');
});
it('rejects an invalid email without submitting', () => {
const subscribe = cy.stub().resolves().as('subscribe');
cy.mount(<NewsletterForm subscribe={subscribe} />);
cy.findByLabelText('Work email').type('invalid');
cy.findByRole('button', { name: 'Subscribe' }).click();
cy.findByRole('alert').should('be.visible');
cy.get('@subscribe').should('not.have.been.called');
});
});
The findByRole and findByLabelText commands come from Testing Library integration. If the scaffold did not install them, add @testing-library/cypress and import @testing-library/cypress/add-commands in the component support file. You can also use built-in Cypress selectors, but semantic queries make the accessibility intent explicit.
Run the spec interactively with npx cypress open --component. Select the spec, click a command in the left panel, and inspect the component at that exact point. For CI, use npx cypress run --component --browser chrome. The expected result is two passing tests. A missing cy.mount means the support file was not loaded or the command type declaration is misplaced.
The chief Cypress advantage is visible here: the stub, typing, click, and retrying assertions form one readable timeline. For more patterns, the Cypress component testing example collection covers mounting and assertions in greater depth.
6. Test the Component with Playwright
Create src/NewsletterForm.pw-ct.tsx. Playwright's mount fixture returns a locator for the component root, while the page fixture remains available for page-level routes and diagnostics. Callbacks declared in JSX props are transformed so simple arguments and results can cross the Node-to-browser boundary.
import { expect, test } from '@playwright/experimental-ct-react';
import { NewsletterForm } from './NewsletterForm';
test('submits a valid email once', async ({ mount }) => {
const submitted: string[] = [];
const component = await mount(
<NewsletterForm
subscribe={async (email) => {
submitted.push(email);
}}
/>,
);
await component.getByLabel('Work email').fill('qa@example.com');
await component.getByRole('button', { name: 'Subscribe' }).click();
await expect(component.getByRole('status')).toHaveText('Subscription confirmed');
expect(submitted).toEqual(['qa@example.com']);
});
test('rejects an invalid email without submitting', async ({ mount }) => {
let calls = 0;
const component = await mount(
<NewsletterForm subscribe={async () => { calls += 1; }} />,
);
await component.getByLabel('Work email').fill('invalid');
await component.getByRole('button', { name: 'Subscribe' }).click();
await expect(component.getByRole('alert')).toBeVisible();
expect(calls).toBe(0);
});
In Playwright component tests, callbacks passed as JSX props are supported by the component-testing transform. Keep callback arguments serializable. If a callback needs a database client, test fixture, or other Node-only object, expose a plain result at the boundary or route the request instead.
Run npx playwright test -c playwright-ct.config.ts --project=chromium. The output should list two passed tests. Add --ui for UI Mode or --debug for the inspector. When a CI-only failure occurs, open the HTML report and retained trace. The trace correlates actions, DOM snapshots, network activity, console messages, and source locations, which is more complete than a final screenshot.
For a larger ready-to-run structure, use the complete Playwright React component testing guide.
7. Network Mocking, Providers, and Application Context
Many production components call fetch internally rather than accept a function prop. Cypress intercepts browser traffic with cy.intercept(). Register the route before mounting or before the action that triggers the request:
cy.intercept('POST', '/api/subscriptions', {
statusCode: 201,
body: { subscribed: true },
}).as('subscribeRequest');
cy.mount(<ApiNewsletterForm />);
cy.findByLabelText('Work email').type('qa@example.com');
cy.findByRole('button', { name: 'Subscribe' }).click();
cy.wait('@subscribeRequest').its('request.body').should('deep.equal', {
email: 'qa@example.com',
});
Playwright registers a page route and fulfills matching requests:
test('sends the subscription request', async ({ mount, page }) => {
let requestBody: unknown;
await page.route('**/api/subscriptions', async (route) => {
requestBody = route.request().postDataJSON();
await route.fulfill({ status: 201, json: { subscribed: true } });
});
const component = await mount(<ApiNewsletterForm />);
await component.getByLabel('Work email').fill('qa@example.com');
await component.getByRole('button', { name: 'Subscribe' }).click();
await expect(component.getByRole('status')).toHaveText('Subscription confirmed');
expect(requestBody).toEqual({ email: 'qa@example.com' });
});
Do not mock every internal module. Mock the narrow external boundary and let the component execute its validation, state changes, rendering, and request construction. The Cypress network stubbing guide provides additional interception patterns.
Providers deserve production-like wrappers. Define Cypress.Commands.add('mountApp', ...) or a React TestApp wrapper for Playwright. Include theme, memory router, internationalization, and query client there. Reset caches between tests. A global singleton query client can leak a successful response into the next case and create order-dependent failures.
8. Browser Coverage, Debugging, and Failure Evidence
Cypress can run component tests in its supported browser families, and its open mode is exceptionally good for local diagnosis. The command log shows automatic retries and lets you inspect snapshots before and after each action. Console props reveal yielded elements, requests, and stub calls. This shortens the path from a red assertion to the responsible state change.
Playwright projects make browser matrices concise. Chromium, Firefox, and WebKit can share one spec while using separate workers and reports. WebKit is valuable when Safari-like engine behavior is part of the risk model, although it is not a substitute for a final check on real Safari and Apple hardware. Playwright traces provide durable evidence from remote CI, where an interactive runner is unavailable.
Treat browser scope as a risk decision. A design-system button rarely needs every permutation on every commit. Run Chromium for fast pull-request feedback, then schedule Firefox and WebKit for the components where layout, focus, input, or browser APIs differ. Tagging or project dependencies can keep the broader matrix intentional.
Avoid hard waits in both tools. Cypress automatically retries commands and assertions until their timeout. Playwright web assertions such as toBeVisible() and toHaveText() retry against locators. cy.wait(1000) and page.waitForTimeout(1000) merely guess when the UI is ready, slowing success and failing under load. Wait for an accessible state, a request alias, a response, or a user-visible outcome.
9. CI Speed, Parallelism, and Maintenance Cost
Neither runner has a universal speed advantage. Results depend on dev-server startup, component graph size, browser count, worker allocation, CI CPU, video and trace policy, and how much state each test constructs. A benchmark that mounts a trivial button does not predict a suite of router-heavy pages with fonts and data clients.
Measure three numbers in your repository: cold startup to first result, median pull-request runtime, and rerun cost after changing one component. Also record flake rate over repeated CI runs. Compare the same browser, machine class, test cases, and artifact policy. Run enough repetitions to expose variance, then inspect outliers rather than advertising the fastest sample.
Cypress parallelization integrates naturally with Cypress Cloud when recording is enabled. Without a cloud service, teams commonly split specs across CI jobs. Playwright Test uses local workers and supports sharding across jobs, with blob reports that can be merged afterward. Both approaches require balanced specs. One 12-minute spec will limit the value of ten parallel executors.
Maintenance cost usually dominates runtime. Staying with the organization's existing runner avoids duplicate configuration, custom fixtures, reporting, selectors, CI caches, and team training. Conversely, forcing a component-first team into an experimental adapter can create migration work when APIs change. Include upgrade churn and debugging time in the decision, not just pipeline minutes.
10. Cypress vs Playwright for Component Testing: Decision Matrix
| Team situation | Better starting choice | Reason |
|---|---|---|
| New component-test program with no browser runner | Cypress | More mature component workflow and guided setup |
| Large, stable Playwright E2E platform | Playwright | Shared test runner, fixtures, locators, projects, reporters, and CI knowledge |
| Design-system developers rely on visual local iteration | Cypress | Command snapshots make rendering changes easy to inspect |
| WebKit behavior is a release risk | Playwright | First-class WebKit project support in the same configuration |
| Organization prohibits experimental test dependencies | Cypress | Playwright component packages are explicitly experimental |
| CI diagnosis depends on downloadable execution evidence | Playwright | Trace Viewer captures actions, DOM, network, console, and source |
| Existing Cypress custom commands and Cloud workflow | Cypress | Lower migration and operational cost |
| One unified API across component and cross-browser E2E tests | Playwright | Same locator and assertion language across layers |
A mixed-tool strategy is defensible only when boundaries are clear. For example, a design-system package may use Cypress component tests while product applications use Playwright E2E tests. The cost is two dependency stacks and two debugging models. Do not let individual squads choose casually, because fragmented fixtures and CI reporting become platform debt.
Run a two-week proof of concept with five representative components: a simple presentational control, a form, a provider-dependent view, a network-driven state machine, and a browser-sensitive widget. Score setup effort, authoring clarity, local debugging, CI evidence, browser coverage, runtime, and upgrade policy. The result will be more credible than preference-driven debate.
Which Should You Choose
Choose Cypress if you are establishing component testing as its own development discipline. Its interactive runner, time-travel command snapshots, mature configuration, and approachable stubs make it easy for frontend engineers to understand failures. It is also the prudent choice when production policy excludes experimental packages.
Choose Playwright if your organization already has strong Playwright Test conventions and component tests should join that platform. Shared role-based locators, fixtures, project matrices, reporters, sharding, and traces reduce operational duplication. Accept that the component-test adapter is experimental, isolate adapter-specific code in mount helpers, and budget for API changes.
If you still cannot decide, do not start with migration. Add the same five behavior tests in each runner and measure them under your real CI constraints. Include the hardest provider and styling case, not only a button. Select the runner that produces clearer failures and less custom infrastructure for the people who will maintain the suite.
11. Common Mistakes
Testing implementation details: Avoid CSS class names, React state, and hook call counts. Locate controls by role or label, then assert visible outcomes. A refactor should not break a behaviorally identical component.
Mounting without production styles: An unstyled component can hide overflow, focus, stacking, and responsive defects. Import the same global CSS and fonts used by the application, while keeping external font requests deterministic in CI.
Rebuilding providers inside every spec: Repeated wrapper code drifts. Create one typed mount helper that accepts optional route, theme, locale, and preloaded data. Reset its stores and caches for each test.
Using fixed delays: Timeouts conceal missing synchronization. Wait on cy.wait('@alias'), a Playwright response predicate, or a retrying assertion against the final UI state.
Confusing component tests with backend integration: A mounted component plus a stubbed route proves UI request behavior, not that the deployed API accepts the payload. Cover the contract separately and retain a thin E2E path.
Passing nonserializable objects into Playwright mounts: Browser and Node contexts are separate. Construct routers, stores, and clients in a browser-side wrapper; pass simple configuration values from the test.
Comparing unfair configurations: One browser with artifacts disabled cannot be compared to three browsers recording video and traces. Match scope before drawing conclusions about runtime.
Migrating the whole suite before a pilot: Conversion exposes hidden support code, selector assumptions, and CI dependencies. Prove representative components first, document gaps, and migrate only after the target architecture works.
12. Interview Questions and Answers
The structured interview section below covers architecture, mounting, synchronization, isolation, and tool selection. A strong answer should connect APIs to engineering consequences rather than declaring one framework universally superior. Practice explaining why Cypress's command log helps local diagnosis, why Playwright's Node and browser boundary matters, and how you would validate a tool choice with repository-specific evidence.
For additional preparation, work through the Cypress interview questions and answers and Playwright coding interview questions.
13. Where To Go Next
Start with one component that has real business behavior and one awkward dependency. Build a production-like mount wrapper, cover success and failure, and run the spec in the same container used by CI. Capture the commands, runtime, artifacts, and setup friction.
If Cypress wins, expand from the Cypress component testing examples into network and provider patterns. If Playwright wins, follow the Playwright component tests with Vite CI tutorial to harden the pipeline. Keep product-level confidence with a small E2E layer, and use /practice to rehearse tool-specific testing scenarios.
Conclusion
Cypress vs Playwright for component testing comes down to maturity versus platform consistency. Cypress is the default recommendation for a new component-first program because its workflow is stable, guided, and exceptionally inspectable. Playwright is compelling for teams already standardized on Playwright Test, especially when WebKit projects, shared fixtures, sharding, and traces matter.
Choose with evidence from your own component graph and CI environment. Mount representative production components, preserve realistic providers and styles, compare the same browser scope, and inspect failure quality. The best runner is the one your team can trust, debug, and maintain after the initial demo succeeds.
Interview Questions and Answers
How would you choose between Cypress and Playwright for component testing?
I would begin with the team's existing automation platform, required browser engines, experimental-dependency policy, debugging needs, and CI architecture. Then I would implement the same representative components in both tools and compare setup effort, failure evidence, runtime, flake rate, and maintenance. Cypress is my component-first default, while Playwright is attractive when platform unification provides a measurable benefit.
What is the main architectural difference in Playwright component testing?
The test runs in Node while the mounted component executes in the browser through a framework adapter. Values passed across that boundary must be serializable, with special support for callbacks. I construct complex routers, stores, and clients in a browser-side wrapper and pass only plain configuration from the test.
Why use role and label locators in component tests?
Roles and labels reflect how users and assistive technologies discover controls. They survive CSS and internal markup refactors better than class selectors, and they expose missing accessible names early. I use a test ID only when there is no stable user-facing semantic hook.
How do you prevent flaky synchronization in both runners?
I avoid fixed sleeps and wait for observable conditions. In Cypress that might be a retrying `should()` assertion or an aliased intercepted request; in Playwright it is usually a locator assertion or a specific response. The awaited condition must represent readiness, not merely elapsed time.
How would you organize shared providers for component tests?
I create one typed custom mount layer that supplies the production theme, memory router, localization, and a fresh data client. Tests override only the route, locale, preloaded state, or mock handlers they need. Each mount receives isolated stores and caches so execution order cannot affect results.
What should remain in end-to-end tests after adding component tests?
I keep thin critical journeys that validate deployed routing, authentication, cross-service integration, cookies or origins, and the actual backend contract. Component tests take over state combinations, validation branches, accessibility behavior, and UI errors that are slow to construct end to end. This produces faster feedback without losing system-level confidence.
How do Cypress and Playwright differ in debugging failures?
Cypress excels during local development because its command log and snapshots let me inspect the UI at each action. Playwright traces are especially effective for CI because they preserve actions, DOM, network, console, and source context in one artifact. I configure artifacts intentionally so routine passing runs do not overwhelm storage.
Frequently Asked Questions
Is Cypress or Playwright better for component testing in 2026?
Cypress is the safer general recommendation because its component-testing workflow is mature and its interactive command log is excellent for UI diagnosis. Playwright is often the better organizational choice when a team already uses Playwright Test and values shared fixtures, locators, browser projects, reporters, and traces.
Is Playwright component testing still experimental?
Yes. React, Vue, and Svelte component testing is distributed through packages whose names include `experimental-ct`. Teams that adopt it should isolate mounting and provider setup behind helpers so future adapter changes have a limited blast radius.
Can Cypress component tests run in multiple browsers?
Yes, Cypress component tests can run in supported browser families through the component runner. Define browser coverage from actual product risk, because executing every component in every browser can add substantial CI time without equal value.
Can Playwright component tests reuse Playwright E2E fixtures?
They use Playwright Test and can reuse suitable worker and test fixtures, reporters, projects, and assertion conventions. Fixtures that assume a deployed page or navigate with `page.goto()` may need refactoring because a component mount has a different lifecycle and boundary.
Do component tests replace end-to-end tests?
No. Component tests efficiently cover UI states, events, accessibility behavior, and controlled network branches, but they do not prove deployed routing, authentication, backend integration, or complete user journeys. Retain a focused E2E layer for those system risks.
How should I mock API calls in Cypress and Playwright component tests?
Use `cy.intercept()` in Cypress and `page.route()` in Playwright to control the browser's external request boundary. Register the mock before the action, return a contract-shaped response, and assert the user-visible result as well as any request detail that matters.
Which tool has better debugging for component tests?
Cypress has the strongest immediate local debugging loop through its command timeline and interactive snapshots. Playwright provides richer portable CI evidence through traces that combine actions, DOM snapshots, network, console output, and source locations.