Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
82791ab
fix: resolve artifact path mismatch in perf-comment workflow
iamAbhi-916 Mar 31, 2026
01eec9b
fix: include hidden files in perf artifact upload
iamAbhi-916 Apr 1, 2026
3357b6f
Merge branch 'main' into perf-artifact-hidden-files
iamAbhi-916 Apr 1, 2026
48752cf
fix: auto-discover native perf results in compare report
iamAbhi-916 Apr 1, 2026
e874644
Do not create m_childrenContainer when using a custom visual to mount…
acoates-ms Mar 31, 2026
3820fcc
RELEASE: Releasing 10 package(s) (main) (#15885)
azure-pipelines[bot] Mar 31, 2026
282a946
Implement onClick (#15889)
acoates-ms Mar 31, 2026
da48311
RELEASE: Releasing 10 package(s) (main) (#15891)
azure-pipelines[bot] Apr 1, 2026
fc904a3
Fork ubroken module (#15892)
vmoroz Apr 1, 2026
4c44734
fix: auto-discover native perf results in compare report
iamAbhi-916 Apr 1, 2026
737bc86
Change files
iamAbhi-916 Apr 1, 2026
c7ac99c
Merge branch 'perf-artifact-hidden-files' of https://github.com/iamAb…
iamAbhi-916 Apr 1, 2026
5695cab
fix: capture live run metrics in PerfJsonReporter instead of re-readi…
iamAbhi-916 Apr 1, 2026
a6b7ccc
Change files
iamAbhi-916 Apr 1, 2026
2a21f88
fix: use temp files for cross-process live metrics
iamAbhi-916 Apr 1, 2026
db149f3
lint fix
iamAbhi-916 Apr 2, 2026
f3f07a3
make touchableOpacity less noisy
iamAbhi-916 Apr 2, 2026
c378ad9
Merge branch 'main' into perf-artifact-hidden-files
iamAbhi-916 Apr 2, 2026
0021924
Change files
iamAbhi-916 Apr 2, 2026
42031cf
Merge branch 'main' into perf-artifact-hidden-files
iamAbhi-916 Apr 3, 2026
e720f37
codegen build fix
iamAbhi-916 Apr 3, 2026
0c1981d
Merge branch 'perf-artifact-hidden-files' of https://github.com/iamAb…
iamAbhi-916 Apr 3, 2026
9eec55a
revert: undo codegen changes (schema version mismatch)
iamAbhi-916 Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/perf-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: perf-results
include-hidden-files: true
path: |
packages/e2e-test-app-fabric/.perf-results/
packages/e2e-test-app-fabric/.native-perf-results/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: auto-discover native perf results in compare report",
"packageName": "@react-native-windows/automation",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: auto-discover native perf results in compare report",
"packageName": "@react-native-windows/automation-channel",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: auto-discover native perf results in compare report",
"packageName": "@react-native-windows/automation-commands",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: capture live run metrics in PerfJsonReporter instead of re-reading baselines",
"packageName": "@react-native-windows/codegen",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: capture live run metrics in PerfJsonReporter instead of re-reading baselines",
"packageName": "@react-native-windows/perf-testing",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: auto-discover native perf results in compare report",
"packageName": "react-native-windows",
"email": "74712637+iamAbhi-916@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ export class PerfJsonReporter {
const suites: SuiteResult[] = [];

for (const suite of results.testResults) {
// Load the snapshot file for this test suite (written by toMatchPerfSnapshot)
// Use live run metrics captured during the test run
const {file: snapshotFilePath} = SnapshotManager.getSnapshotPath(
suite.testFilePath,
);
const snapshots = SnapshotManager.load(snapshotFilePath);
const snapshots = SnapshotManager.getRunMetrics(snapshotFilePath) ?? {};

const passed = suite.testResults.filter(
t => t.status === 'passed',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import fs from '@react-native-windows/fs';
import * as path from 'path';
import * as os from 'os';
import type {PerfMetrics} from '../interfaces/PerfMetrics';
import type {PerfThreshold} from '../interfaces/PerfThreshold';

Expand All @@ -26,8 +27,63 @@ export type SnapshotFile = Record<string, SnapshotEntry>;

/**
* Manages reading and writing of perf snapshot files.
* Also writes live run metrics to a temp directory so the CI reporter
* (which runs in the Jest main process) can access fresh results
* from the worker process.
*/
export class SnapshotManager {
/** Directory for live run metrics (cross-process via temp files). */
private static readonly _runMetricsDir =
process.env.PERF_RUN_METRICS_DIR ||
path.join(os.tmpdir(), 'rnw-perf-run-metrics');

/** Record a live metric entry for the current run (written to temp file). */
static recordRunMetric(
snapshotFilePath: string,
key: string,
entry: SnapshotEntry,
): void {
const metricsFile = path.join(
SnapshotManager._runMetricsDir,
Buffer.from(snapshotFilePath).toString('base64url') + '.json',
);
let existing: SnapshotFile = {};
if (fs.existsSync(metricsFile)) {
existing = JSON.parse(
fs.readFileSync(metricsFile, 'utf-8'),
) as SnapshotFile;
} else if (!fs.existsSync(SnapshotManager._runMetricsDir)) {
fs.mkdirSync(SnapshotManager._runMetricsDir, {recursive: true});
}
existing[key] = entry;
fs.writeFileSync(
metricsFile,
JSON.stringify(existing, null, 2) + '\n',
'utf-8',
);
}

/** Get live run metrics for a snapshot file, or null if none were recorded. */
static getRunMetrics(snapshotFilePath: string): SnapshotFile | null {
const metricsFile = path.join(
SnapshotManager._runMetricsDir,
Buffer.from(snapshotFilePath).toString('base64url') + '.json',
);
if (fs.existsSync(metricsFile)) {
return JSON.parse(fs.readFileSync(metricsFile, 'utf-8')) as SnapshotFile;
}
return null;
}

/** Clean up temp run metrics directory. */
static clearRunMetrics(): void {
if (fs.existsSync(SnapshotManager._runMetricsDir)) {
for (const f of fs.readdirSync(SnapshotManager._runMetricsDir)) {
fs.unlinkSync(path.join(SnapshotManager._runMetricsDir, f));
}
}
}

static getSnapshotPath(testFilePath: string): {
dir: string;
file: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ expect.extend({

const threshold: PerfThreshold = {...DEFAULT_THRESHOLD, ...customThreshold};

// Always record the live metrics for the CI reporter
SnapshotManager.recordRunMetric(snapshotFile, snapshotKey, {
metrics: received,
threshold,
capturedAt: new Date().toISOString(),
});

// UPDATE MODE or FIRST RUN: write new baseline
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (isUpdateMode || !baseline) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
'use strict';

import React from 'react';
import { View, Text, Pressable } from 'react-native';
import {View, Text, Pressable} from 'react-native';

function HitTestWithOverflowVisibile() {
const [bgColor, setBgColor] = React.useState('red');

return (
<View>
<Text>
Clicking the pressable should work even if it is outside the bounds
of its parent.
Clicking the pressable should work even if it is outside the bounds of
its parent.
</Text>
<View
accessible={true}
accessibilityValue={{ text: bgColor }}
style={{ width: 150, height: 150 }}
accessibilityValue={{text: bgColor}}
style={{width: 150, height: 150}}
testID="visible-overflow-element">
<View
style={{
Expand Down Expand Up @@ -52,15 +52,14 @@ function HitTestWithOverflowHidden() {
return (
<View>
<Text>
Clicking within the visible view will trigger the pressable.
Clicking outside the bounds, where the pressable extends but is
clipped by its parent overflow:hidden, should not trigger the
pressable.
Clicking within the visible view will trigger the pressable. Clicking
outside the bounds, where the pressable extends but is clipped by its
parent overflow:hidden, should not trigger the pressable.
</Text>
<View
accessible={true}
accessibilityValue={{ text: bgColor }}
style={{ width: 150, height: 150 }}
accessibilityValue={{text: bgColor}}
style={{width: 150, height: 150}}
testID="hidden-overflow-element">
<View
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ function PointerDownButtonExample(): React.Node {
);
return (
<View>
<Text accessible={true} testID="pointer-button-state">{text}</Text>
<Text accessible={true} testID="pointer-button-state">
{text}
</Text>
<View
accessible={true}
testID="pointer-button-target"
Expand All @@ -58,7 +60,9 @@ function PointerUpButtonExample(): React.Node {
);
return (
<View>
<Text accessible={true} testID="pointer-up-button-state">{text}</Text>
<Text accessible={true} testID="pointer-up-button-state">
{text}
</Text>
<View
accessible={true}
testID="pointer-up-button-target"
Expand Down
22 changes: 8 additions & 14 deletions packages/@rnw-scripts/unbroken/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,11 @@ export class Checker {
this.options.exclusions ||
path.join(this.options.dir, '.unbroken_exclusions');
try {
const contents =
fs
.readFileSync(exclusionsFileName)
.toString()
.split(/\r?\n/)
.filter(x => x.trim() !== '');
const contents = fs
.readFileSync(exclusionsFileName)
.toString()
.split(/\r?\n/)
.filter(x => x.trim() !== '');
this.suppressions = contents
.filter(x => !x.startsWith('!'))
.map(x => Checker.normalizeSlashes(x));
Expand All @@ -84,7 +83,6 @@ export class Checker {
exclusions: string[];
urlCache: Record<string, number>;


private async recurseFindMarkdownFiles(
dirPath: string,
callback: {(path: string): Promise<void>},
Expand Down Expand Up @@ -136,9 +134,7 @@ export class Checker {
}

if (this.options['parse-ids']) {
await this.recurseFindMarkdownFiles(dirPath, x =>
this.getAndStoreId(x),
);
await this.recurseFindMarkdownFiles(dirPath, x => this.getAndStoreId(x));
}
await this.recurseFindMarkdownFiles(dirPath, x =>
this.verifyMarkDownFile(x),
Expand Down Expand Up @@ -249,9 +245,7 @@ export class Checker {

const anchors = this.getAnchors(contents.toLowerCase());
if (!anchors.includes(sectionAnchor.toLowerCase())) {
if (
!anchors.includes(sectionAnchor.replace(/\./g, '').toLowerCase())
) {
if (!anchors.includes(sectionAnchor.replace(/\./g, '').toLowerCase())) {
if (
!(
this.options['allow-local-line-sections'] &&
Expand Down Expand Up @@ -387,7 +381,7 @@ export class Checker {
}

private static isWebLink(url: string) {
return url.startsWith("https://") || url.startsWith('https://');
return url.startsWith('https://') || url.startsWith('https://');
}

async verifyMarkDownFile(filePath: string) {
Expand Down
6 changes: 5 additions & 1 deletion packages/@rnw-scripts/unbroken/src/unbroken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ async function run() {
exclusions: {type: 'string', short: 'e'},
'local-only': {type: 'boolean', short: 'l', default: false},
init: {type: 'boolean', short: 'i', default: false},
'allow-local-line-sections': {type: 'boolean', short: 'a', default: false},
'allow-local-line-sections': {
type: 'boolean',
short: 'a',
default: false,
},
quiet: {type: 'boolean', short: 'q', default: false},
superquiet: {type: 'boolean', short: 's', default: false},
'parse-ids': {type: 'boolean', default: false},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,14 @@ describe('Pointer Button Tests', () => {
});
test('onPointerUp reports correct button property on left click', async () => {
await searchBox('onPointerUp');
const component = await app.findElementByTestID(
'pointer-up-button-target',
);
const component = await app.findElementByTestID('pointer-up-button-target');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('pointer-up-button-target');
expect(dump).toMatchSnapshot();

// Left click release triggers onPointerUp with button=0
await component.click();
const stateText = await app.findElementByTestID(
'pointer-up-button-state',
);
const stateText = await app.findElementByTestID('pointer-up-button-state');

await app.waitUntil(
async () => {
Expand All @@ -155,16 +151,12 @@ describe('Pointer Button Tests', () => {
});
test('onPointerUp reports correct button property on middle click', async () => {
await searchBox('onPointerUp');
const component = await app.findElementByTestID(
'pointer-up-button-target',
);
const component = await app.findElementByTestID('pointer-up-button-target');
await component.waitForDisplayed({timeout: 5000});

// Middle click release triggers onPointerUp with button=1
await component.click({button: 'middle'});
const stateText = await app.findElementByTestID(
'pointer-up-button-state',
);
const stateText = await app.findElementByTestID('pointer-up-button-state');

await app.waitUntil(
async () => {
Expand All @@ -185,16 +177,12 @@ describe('Pointer Button Tests', () => {
});
test('onPointerUp reports correct button property on right click', async () => {
await searchBox('onPointerUp');
const component = await app.findElementByTestID(
'pointer-up-button-target',
);
const component = await app.findElementByTestID('pointer-up-button-target');
await component.waitForDisplayed({timeout: 5000});

// Right click release triggers onPointerUp with button=2
await component.click({button: 'right'});
const stateText = await app.findElementByTestID(
'pointer-up-button-state',
);
const stateText = await app.findElementByTestID('pointer-up-button-state');

await app.waitUntil(
async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ describe('TouchableOpacity Performance', () => {
expect(perf).toMatchPerfSnapshot({
maxDurationIncrease: 30,
minAbsoluteDelta: 15,
mode: 'gate',
mode: 'track',
});
});
});
Expand Down
Loading
Loading