Skip to content

Performance tests Fabric

Abhijeet Jha edited this page Mar 13, 2026 · 10 revisions

Performance Testing for React Native Windows

Two complementary test suites measure performance of RN Windows Fabric components:

Suite Measures Timer Environment
JS Perf Tests mount / unmount / rerender + scenarios React.Profiler (actualDuration) Jest (no app needed)
Native Perf Tests Full render pipeline (JS → Fabric → Yoga → Composition → frame) performance.mark/measure (QPC ~100 ns) Real RNTesterApp-Fabric via UIA

Part 1 — JS Perf Tests

How to Run

cd packages/e2e-test-app-fabric
yarn perf                                              # all tests
yarn perf -- --testPathPattern=FlatList                 # single component
yarn perf:update                                       # update all baselines
yarn perf:update --testPathPattern=TouchableHighlight   # update one baseline
yarn perf:create -- --name=ComponentName               # scaffold new test

Components (14 suites, 84 scenarios)

Core (9): View, Text, TextInput, Image, Switch, Button, ActivityIndicator, ScrollView, Modal Interactive (3): Pressable, TouchableHighlight, TouchableOpacity List (2): FlatList, SectionList

Each suite tests: mount, unmount, rerender, plus component-specific scenarios (bulk counts, style variants, stress tests).

Core Component Baselines (9 suites)
Component Scenario Baseline (ms) Max Regression Min Δ (ms) Notes
View mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
nested-views-50 4 15 % 5 heavier DOM
nested-views-100 7 15 % 5 heavier DOM
stress-views-500 10 10 % 10 stress gate
with-shadow 0 10 % 3
with-border-radius 0 10 % 3
Text mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
long-text-1000 0 10 % 3
nested-text 0 10 % 3
styled-text 0 10 % 3
multiple-text-100 7 10 % 10 stress gate
Image mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
with-resize-mode 0 10 % 3
with-border-radius 0 10 % 3
with-tint-color 0 10 % 3
with-blur-radius 0 10 % 3
with-accessibility 0 10 % 3
multiple-images-10 1 10 % 5 bulk noise
multiple-images-50 4 15 % 5 bulk noise
multiple-images-100 8 10 % 10 stress gate
TextInput mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
multiline 0 10 % 3
with-value 0 10 % 3
styled-input 0 10 % 3
multiple-text-inputs-100 7 10 % 10 stress gate
Switch mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
disabled 0.5 10 % 3
custom-colors 0 10 % 3
multiple-switches-10 1 10 % 5 bulk noise
multiple-switches-50 8 15 % 5 bulk noise
multiple-switches-100 16 10 % 10 stress gate
Button mount 1 10 % 3
unmount 0 10 % 3
rerender 1 10 % 3
disabled 1 10 % 3
with-color 1 10 % 3
with-accessibility 1 10 % 3
multiple-buttons-10 5 10 % 5 bulk noise
multiple-buttons-50 26 15 % 5 bulk noise
multiple-buttons-100 19 10 % 10 stress gate
ActivityIndicator mount 0 10 % 3
unmount 0 10 % 3
rerender 0 10 % 3
multiple-indicators-10 1 10 % 5 bulk noise
multiple-indicators-50 4 15 % 5 bulk noise
multiple-indicators-100 7 10 % 10 stress gate
ScrollView mount 0 10 % 3
unmount 0 10 % 3
rerender 0.5 10 % 3
with-children-20 3 10 % 3
with-children-100 15 15 % 5 heavy
horizontal 3 10 % 3
sticky-headers 3 10 % 3
nested-scroll-views 1 10 % 5
with-children-500 19 10 % 10 stress gate
Modal mount 0 10 % 3
unmount 0 10 % 3
rerender 0.5 10 % 3
with-rich-content 2 10 % 3
Interactive Component Baselines (3 suites)
Component Scenario Baseline (ms) Max Regression Min Δ (ms) Notes
Pressable mount 0.5 10 % 3
unmount 0 10 % 3
rerender 1 10 % 3
nested-pressables 1 10 % 3
multiple-pressables-10 3 10 % 5 bulk noise
multiple-pressables-50 15 15 % 5 bulk noise
multiple-pressables-100 12 10 % 10 stress gate
TouchableOpacity mount 1 10 % 3
rerender 1.5 10 % 3
nested-touchables 1.5 10 % 3
multiple-touchables-10 6 10 % 5 bulk noise
multiple-touchables-50 29 15 % 5 bulk noise
multiple-touchables-100 30 10 % 10 stress gate
TouchableHighlight mount 1 10 % 3
rerender 0.5 10 % 3
nested-touchables 1 10 % 3
multiple-touchables-10 2 10 % 5 bulk noise
multiple-touchables-50 12.5 15 % 5 bulk noise
multiple-touchables-100 22.5 10 % 10 stress gate
List Component Baselines (2 suites)
Component Scenario Baseline (ms) Max Regression Min Δ (ms) Notes
FlatList mount 4 10 % 3
unmount 0 10 % 3
rerender 9 10 % 3
with-10-items 4 10 % 3
with-100-items 5 10 % 5
with-500-items 5 15 % 10 large list
horizontal 4.5 10 % 5
with-separator 6 10 % 5
with-header-footer 2 10 % 5
with-empty-list 1 10 % 3
with-get-item-layout 2 10 % 5
inverted 2 10 % 5
with-1000-items 4 15 % 10 stress gate (virtualized)
with-num-columns 3 10 % 5
SectionList mount 5 10 % 3
unmount 0 10 % 3
rerender 11 10 % 3
3-sections × 5-items 5 10 % 5
5-sections × 10-items 6 10 % 5
10-sections × 20-items 5.5 15 % 10 200 items
20-sections × 10-items 5.5 15 % 10 200 items
with-section-separator 10 % 5
with-item-separator 10 % 5
with-header-footer 10 % 5
with-section-footer 10 % 5
sticky-section-headers 10 % 5
with-50-sections-20-items 2 15 % 10 stress gate (virtualized)
with-empty-list 0 10 % 3

Part 2 — Native Perf Tests

Prerequisites

  • RNTesterApp-Fabric built and deployed (Debug|x64 or Release|x64)
  • WinAppDriver installed (C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe)
  • Metro bundler running

How to Run

cd packages/e2e-test-app-fabric

# Terminal 1 — start Metro
yarn start

# Terminal 2 — run tests
yarn perf:native                   # gate mode (compare against baselines)
yarn perf:native:update            # save/update baselines
yarn perf:native:ci                # CI mode (--ci --forceExit)

Components (14 components, 1 scenario each)

Each measures native mount — full pipeline from JS reconciliation through Fabric, Yoga layout, to Composition frame commit.

What's Being Measured — Native Render Pipeline
performance.mark('start')
  │
  ├─ JS: setShowTarget(true)                          ← React state update
  ├─ React reconciliation (ShadowTree diff)
  │
  ├─ C++: SchedulerDelegate::schedulerDidFinishTransaction()
  ├─ C++: FabricUIManager::RCTPerformMountInstructions()
  │         ├─ ComponentView::updateProps()
  │         ├─ ComponentView::updateLayoutMetrics()    ← Yoga layout
  │         ├─ ComponentView::FinalizeUpdates()
  │         │     └─ BorderPrimitive::finalize()       ← Composition visuals
  │         └─ ComponentView::MountChildComponentView()
  │
  ├─ Visual tree committed to Composition
  ├─ requestAnimationFrame fires
  │
performance.mark('end')

Catches regressions in: prop handling, layout calculation, view creation, border/shadow rendering, visual tree insertion — any change to vnext/ Fabric ComponentView code.

Not measured: GPU composition commit, React reconciler internals (upstream Meta C++), DComposition API overhead.

Timer: performance.mark/measure → QPC (QueryPerformanceCounter), sub-microsecond resolution.

Native Perf Component Baselines (14 components)
Component Runs Warmup Threshold
View 15 2 native (15 % max, CV ≤ 0.5)
Text 15 2 native
TextInput 15 2 native
Image 15 2 native
Switch 15 2 native
ActivityIndicator 15 2 native
Button 15 2 native
Modal 15 2 native
Pressable 15 2 native
TouchableHighlight 15 2 native
TouchableOpacity 15 2 native
ScrollView 10 2 native + relaxed CV (≤ 0.6)
FlatList 10 2 native + relaxed (20 %, CV ≤ 0.6)
SectionList 10 2 native + relaxed (20 %, CV ≤ 0.6)

Regression Detection

Every test result passes through this pipeline (in toMatchPerfSnapshot and BaselineComparator):

measured durations[]
       │
       ▼
  CV > maxCV?  ──yes──▶  SKIP (too noisy to judge) → warn only
       │ no
       ▼
  Mann-Whitney U
  p ≥ 0.05?   ──yes──▶  PASS (not statistically significant)
       │ no
       ▼
  % change > maxDurationIncrease%
  AND absolute Δ > minAbsoluteDelta ms?
       │
    yes │ no
       ▼    ▼
  mode?    PASS
   │
  gate ──▶ FAIL CI
  track ─▶ WARN only

Both percentage and absolute delta gates must trip simultaneously — prevents a 1 ms → 2 ms jump (100 % but only 1 ms) from blocking CI.

Threshold Presets

Preset Max Regression Min Δ (ms) Max Renders Min Runs Max CV Mode
core 10 % 3 2 10 0.40 gate
list 15 % 5 5 5 0.50 gate
interactive 20 % 5 10 10 0.50 gate
native 15 % 5 1 10 0.50 gate
community 25 % 5 15 5 0.60 track

Folder Structure

Click to expand
packages/e2e-test-app-fabric/
├── jest.perf.config.js                          # JS perf Jest config
├── jest.native-perf.config.js                   # Native perf Jest config
├── jest.perf.setup.ts                           # Registers toMatchPerfSnapshot matcher
├── test/__perf__/                               # JS perf tests
│   ├── core/                                    # 9 core component tests
│   ├── interactive/                             # 3 interactive component tests
│   ├── list/                                    # 2 list component tests
│   └── */__perf_snapshots__/                    # Baseline JSONs
├── test/__native_perf__/                        # Native perf tests
│   └── core/
│       ├── View.native-perf-test.ts             # All 14 component benchmarks
│       └── __perf_snapshots__/                  # Baseline JSONs
└── test/NativePerfHelpers.ts                    # UIA automation helpers

packages/@react-native-windows/perf-testing/src/
├── base/ComponentPerfTestBase.ts                # Abstract base class
├── core/measurePerf.ts                          # Timing engine (React.Profiler)
├── core/statistics.ts                           # mean, median, stdDev
├── matchers/toMatchPerfSnapshot.ts              # Custom Jest matcher
├── matchers/snapshotManager.ts                  # Baseline file read/write
├── config/thresholdPresets.ts                   # Preset definitions
├── ci/BaselineComparator.ts                     # Regression detection
└── ci/PerfJsonReporter.ts                       # JSON results for CI

packages/@react-native-windows/tester/src/js/
└── examples-win/NativePerfBenchmark/            # Benchmark page in RNTesterApp
    └── NativePerfBenchmarkExample.js

Clone this wiki locally