Skip to content

Commit 86e6a03

Browse files
authored
[Fabric]: Performance Tests for React Native Windows (#15666)
* perf testing v1( view , text , textinput) * Fixed spike testing by using median and other issues * chore: gitignore perf-testing build output * remove scripts/ as separate folder * add component perf tests for Button, Image, ScrollView, Switch, Modal, ActivityIndicator, Pressable, TouchableOpacity, TouchableHighlight, FlatList, and SectionList * Cleanup * Change files * lint fix and format * cleanup * fix pipeline issues with cachedAct! * Save PR number to artifact for future report workflow * remove tsconfig from tracking * review comments * update baselines * lint:fix * add statistical stability gates (Mann-Whitney U, CV, gate/track mode) * add maxCV and mode to defaultConfig threshold * Add 100x stress-gate scenarios to all 13 perf suites * lint fix and format * update yarn lock * Bump version to 0.0.0-canary.1033
1 parent 4baaa8d commit 86e6a03

71 files changed

Lines changed: 9715 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/perf-tests.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Perf Tests
2+
3+
# Triggers on PRs touching source that could affect perf,
4+
# and on pushes to main/stable for baseline updates.
5+
on:
6+
pull_request:
7+
branches: [main, '*-stable']
8+
paths:
9+
- 'vnext/**'
10+
- 'packages/**'
11+
- 'vnext/Scripts/perf/**'
12+
- '.github/workflows/perf-tests.yml'
13+
push:
14+
branches: [main]
15+
paths:
16+
- 'packages/e2e-test-app-fabric/test/__perf__/**'
17+
18+
# Allow manual trigger for debugging
19+
workflow_dispatch:
20+
21+
# Cancel in-progress runs for the same PR
22+
concurrency:
23+
group: perf-${{ github.event.pull_request.number || github.sha }}
24+
cancel-in-progress: true
25+
26+
jobs:
27+
perf-tests:
28+
name: Component Performance Tests
29+
runs-on: windows-latest
30+
timeout-minutes: 30
31+
32+
permissions:
33+
contents: read
34+
actions: read
35+
36+
steps:
37+
# ── Setup ──────────────────────────────────────────────
38+
- name: Checkout head (PR)
39+
uses: actions/checkout@v4
40+
with:
41+
fetch-depth: 0 # Need history for baseline comparison
42+
43+
- name: Setup Node.js
44+
uses: actions/setup-node@v4
45+
with:
46+
node-version: 22
47+
cache: yarn
48+
49+
- name: Install dependencies
50+
run: yarn install --frozen-lockfile
51+
52+
- name: Build perf-testing package
53+
run: yarn workspace @react-native-windows/perf-testing build
54+
55+
# ── Run Tests ──────────────────────────────────────────
56+
- name: Run perf tests
57+
id: perf-run
58+
working-directory: packages/e2e-test-app-fabric
59+
env:
60+
CI: 'true'
61+
RN_TARGET_PLATFORM: windows
62+
run: yarn perf:ci
63+
continue-on-error: true # Don't fail here — let comparison decide
64+
65+
# ── Compare & Report ───────────────────────────────────
66+
- name: Compare against baselines
67+
id: compare
68+
working-directory: packages/e2e-test-app-fabric
69+
run: yarn perf:ci:compare
70+
continue-on-error: true
71+
72+
- name: Save PR number
73+
if: github.event_name == 'pull_request'
74+
run: echo "${{ github.event.pull_request.number }}" > packages/e2e-test-app-fabric/.perf-results/pr-number.txt
75+
76+
- name: Upload perf results
77+
if: always()
78+
uses: actions/upload-artifact@v4
79+
with:
80+
name: perf-results
81+
path: |
82+
packages/e2e-test-app-fabric/.perf-results/
83+
packages/e2e-test-app-fabric/test/__perf__/**/__perf_snapshots__/
84+
retention-days: 30
85+
86+
# ── Status Gate ────────────────────────────────────────
87+
- name: Check for regressions
88+
if: steps.compare.outcome == 'failure'
89+
run: |
90+
echo "::error::Performance regressions detected. See PR comment for details."
91+
exit 1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Performance tests for react native windows(Fabric)",
4+
"packageName": "@react-native-windows/perf-testing",
5+
"email": "74712637+iamAbhi-916@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Performance tests for react native windows(Fabric)",
4+
"packageName": "react-native-windows",
5+
"email": "74712637+iamAbhi-916@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @format
6+
*/
7+
8+
module.exports = {
9+
extends: ['@rnw-scripts/eslint-config'],
10+
parserOptions: {
11+
tsconfigRootDir: __dirname,
12+
},
13+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lib/
2+
lib-commonjs/
3+
*.tsbuildinfo
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# @react-native-windows/perf-testing
2+
3+
Performance testing utilities for React Native Windows components.
4+
5+
## Overview
6+
7+
This package provides infrastructure for measuring and tracking performance of React Native components. It enables:
8+
9+
- **Automated performance regression detection** on every PR
10+
- **Component-wise performance baselines** (mount, unmount, re-render times)
11+
- **Lightweight perf snapshots** similar to Jest snapshots
12+
- **Easy extensibility** for new components via base classes
13+
14+
## Installation
15+
16+
```bash
17+
npm install @react-native-windows/perf-testing --save-dev
18+
# or
19+
yarn add @react-native-windows/perf-testing --dev
20+
```
21+
22+
## Quick Start
23+
24+
### 1. Create a Performance Test
25+
26+
```typescript
27+
import { ComponentPerfTestBase, IScenario } from '@react-native-windows/perf-testing';
28+
import { View } from 'react-native';
29+
30+
class ViewPerfTest extends ComponentPerfTestBase {
31+
readonly componentName = 'View';
32+
readonly category = 'core' as const;
33+
readonly testId = 'perf-test-view';
34+
35+
createComponent(props?: Record<string, unknown>) {
36+
return <View testID={this.testId} {...props} />;
37+
}
38+
39+
getCustomScenarios(): IScenario[] {
40+
return [
41+
{
42+
name: 'nested-views',
43+
description: 'Test nested views performance',
44+
run: () => this.measureNestedViews(50),
45+
},
46+
];
47+
}
48+
}
49+
```
50+
51+
### 2. Write Tests
52+
53+
```typescript
54+
const viewPerfTest = new ViewPerfTest();
55+
56+
describe('View Performance', () => {
57+
test('mount time', async () => {
58+
const perf = await viewPerfTest.measureMount();
59+
expect(perf).toMatchPerfSnapshot();
60+
});
61+
62+
test('rerender time', async () => {
63+
const perf = await viewPerfTest.measureRerender();
64+
expect(perf).toMatchPerfSnapshot();
65+
});
66+
});
67+
```
68+
69+
### 3. Run Tests
70+
71+
```bash
72+
# Run perf tests
73+
yarn perf
74+
75+
# Update baselines
76+
yarn perf:update
77+
```
78+
79+
## API Reference
80+
81+
### Core Functions
82+
83+
| Function | Description |
84+
|----------|-------------|
85+
| `measurePerf(component, options)` | Core measurement function |
86+
| `toMatchPerfSnapshot(threshold?)` | Jest matcher for perf snapshots |
87+
88+
### Base Classes
89+
90+
| Class | Description |
91+
|-------|-------------|
92+
| `ComponentPerfTestBase` | Abstract base class for component perf tests |
93+
94+
### Interfaces
95+
96+
| Interface | Description |
97+
|-----------|-------------|
98+
| `PerfMetrics` | Measurement results |
99+
| `PerfThreshold` | Pass/fail thresholds |
100+
| `IComponentPerfTest` | Contract for component tests |
101+
| `IScenario` | Custom scenario interface |
102+
103+
### Threshold Presets
104+
105+
```typescript
106+
import { ThresholdPresets } from '@react-native-windows/perf-testing';
107+
108+
// Available presets
109+
ThresholdPresets.core // 10% regression threshold
110+
ThresholdPresets.list // 15% for list components
111+
ThresholdPresets.interactive // 20% for animated components
112+
ThresholdPresets.community // 25% relaxed threshold for external use
113+
```
114+
115+
## Extending for Custom Components
116+
117+
Community developers can create perf tests for their custom native components:
118+
119+
```typescript
120+
import { ComponentPerfTestBase } from '@react-native-windows/perf-testing';
121+
import { MyCustomSlider } from 'my-custom-component';
122+
123+
class MyCustomSliderPerfTest extends ComponentPerfTestBase {
124+
readonly componentName = 'MyCustomSlider';
125+
readonly category = 'custom' as const;
126+
readonly testId = 'perf-test-slider';
127+
128+
createComponent(props?: Record<string, unknown>) {
129+
return <MyCustomSlider testID={this.testId} {...props} />;
130+
}
131+
}
132+
```
133+
134+
## Configuration
135+
136+
### Jest Setup
137+
138+
Add to your Jest setup file:
139+
140+
```typescript
141+
import '@react-native-windows/perf-testing/matchers';
142+
```
143+
144+
### Custom Thresholds
145+
146+
```typescript
147+
expect(perf).toMatchPerfSnapshot({
148+
maxDurationIncrease: 15, // Allow 15% regression
149+
maxRenderCount: 5,
150+
});
151+
```
152+
153+
## License
154+
155+
MIT
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @format
6+
* @ts-check
7+
*/
8+
9+
require('@rnw-scripts/just-task');
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"name": "@react-native-windows/perf-testing",
3+
"version": "0.0.0-canary.1033",
4+
"description": "Performance testing utilities for React Native Windows components",
5+
"main": "lib-commonjs/index.js",
6+
"types": "lib-commonjs/index.d.ts",
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/microsoft/react-native-windows",
10+
"directory": "packages/@react-native-windows/perf-testing"
11+
},
12+
"license": "MIT",
13+
"private": false,
14+
"scripts": {
15+
"build": "rnw-scripts build",
16+
"clean": "rnw-scripts clean",
17+
"lint": "rnw-scripts lint",
18+
"lint:fix": "rnw-scripts lint:fix",
19+
"watch": "rnw-scripts watch"
20+
},
21+
"dependencies": {
22+
"@react-native-windows/fs": "0.82.0-preview.1"
23+
},
24+
"devDependencies": {
25+
"@rnw-scripts/eslint-config": "1.2.38",
26+
"@rnw-scripts/just-task": "2.3.58",
27+
"@rnw-scripts/ts-config": "2.0.6",
28+
"@types/jest": "^29.2.2",
29+
"@types/node": "^22.14.0",
30+
"@types/react": "^19.0.0",
31+
"@typescript-eslint/eslint-plugin": "^7.1.1",
32+
"@typescript-eslint/parser": "^7.1.1",
33+
"eslint": "^8.19.0",
34+
"prettier": "2.8.8",
35+
"typescript": "5.0.4"
36+
},
37+
"peerDependencies": {
38+
"jest": ">=29.0.3",
39+
"react": ">=18.0.0",
40+
"react-native": ">=0.72.0",
41+
"react-test-renderer": ">=18.0.0"
42+
},
43+
"peerDependenciesMeta": {
44+
"react-test-renderer": {
45+
"optional": true
46+
}
47+
},
48+
"files": [
49+
"lib-commonjs",
50+
"README.md"
51+
],
52+
"beachball": {
53+
"defaultNpmTag": "preview",
54+
"disallowedChangeTypes": [
55+
"major",
56+
"minor",
57+
"patch",
58+
"premajor",
59+
"preminor",
60+
"prepatch"
61+
]
62+
},
63+
"promoteRelease": true,
64+
"engines": {
65+
"node": ">= 22"
66+
}
67+
}

0 commit comments

Comments
 (0)