diff --git a/change/react-native-windows-e9ae59ba-5d8f-44e3-baca-ee3cefee5b84.json b/change/react-native-windows-e9ae59ba-5d8f-44e3-baca-ee3cefee5b84.json
new file mode 100644
index 00000000000..45d72526c7e
--- /dev/null
+++ b/change/react-native-windows-e9ae59ba-5d8f-44e3-baca-ee3cefee5b84.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Add functional tests for Button component fast refresh scenarios",
+ "packageName": "react-native-windows",
+ "email": "copilot@github.com",
+ "dependentChangeType": "patch"
+}
\ No newline at end of file
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/Button/ButtonExample.windows.js b/packages/@react-native-windows/tester/src/js/examples-win/Button/ButtonExample.windows.js
index 034302faa95..94378fe611a 100644
--- a/packages/@react-native-windows/tester/src/js/examples-win/Button/ButtonExample.windows.js
+++ b/packages/@react-native-windows/tester/src/js/examples-win/Button/ButtonExample.windows.js
@@ -273,11 +273,146 @@ exports.examples = [
);
},
},
+ {
+ title: 'Button with dynamic text',
+ description: 'Button text updates when pressed',
+ render: function (): React.Node {
+ return ;
+ },
+ },
+ {
+ title: 'Button with dynamic color',
+ description: 'Button color updates when pressed',
+ render: function (): React.Node {
+ return ;
+ },
+ },
+ {
+ title: 'Button with dynamic disabled state',
+ description: 'Button disabled state toggles when pressed',
+ render: function (): React.Node {
+ return ;
+ },
+ },
+ {
+ title: 'Button with dynamic styling on press',
+ description: 'Button updates styling when pressed',
+ render: function (): React.Node {
+ return ;
+ },
+ },
];
+// Dynamic Button Components for fast refresh testing
+function DynamicTextButton(): React.Node {
+ const [buttonText, setButtonText] = React.useState('Initial Text');
+ const [pressCount, setPressCount] = React.useState(0);
+
+ const onPress = () => {
+ const newCount = pressCount + 1;
+ setPressCount(newCount);
+ setButtonText(`Pressed ${newCount} times`);
+ };
+
+ return (
+
+ );
+}
+
+function DynamicColorButton(): React.Node {
+ const [colorIndex, setColorIndex] = React.useState(0);
+ const colors = ['#007AFF', '#FF3B30', '#34C759', '#FF9500', '#5856D6'];
+
+ const onPress = () => {
+ setColorIndex((prev) => (prev + 1) % colors.length);
+ };
+
+ return (
+
+ {theme => (
+
+ )}
+
+ );
+}
+
+function DynamicDisabledButton(): React.Node {
+ const [isDisabled, setIsDisabled] = React.useState(false);
+ const [toggleText, setToggleText] = React.useState('Disable Me');
+
+ const onPress = () => {
+ if (!isDisabled) {
+ setIsDisabled(true);
+ setToggleText('Disabled');
+ // Re-enable after 2 seconds for testing
+ setTimeout(() => {
+ setIsDisabled(false);
+ setToggleText('Disable Me');
+ }, 2000);
+ }
+ };
+
+ return (
+
+ );
+}
+
+function DynamicStyleButton(): React.Node {
+ const [isPressed, setIsPressed] = React.useState(false);
+ const [pressCount, setPressCount] = React.useState(0);
+
+ const onPress = () => {
+ setIsPressed(true);
+ setPressCount(prev => prev + 1);
+ // Reset pressed state after visual feedback
+ setTimeout(() => setIsPressed(false), 300);
+ };
+
+ return (
+
+ {theme => (
+
+
+
+ )}
+
+ );
+}
+
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
},
+ dynamicContainer: {
+ padding: 10,
+ borderRadius: 5,
+ backgroundColor: 'transparent',
+ },
+ pressedContainer: {
+ backgroundColor: '#f0f0f0',
+ },
});
diff --git a/packages/e2e-test-app-fabric/test/ButtonComponentTest.test.ts b/packages/e2e-test-app-fabric/test/ButtonComponentTest.test.ts
index f7b85df5c53..afea47def24 100644
--- a/packages/e2e-test-app-fabric/test/ButtonComponentTest.test.ts
+++ b/packages/e2e-test-app-fabric/test/ButtonComponentTest.test.ts
@@ -138,4 +138,99 @@ describe('Button Tests', () => {
const dump3 = await dumpVisualTree('accessible_focusable_false_button');
expect(dump3).toMatchSnapshot();
});
+
+ // Functional tests for dynamic button behaviors
+ test('Button text should update on fast refresh', async () => {
+ await searchBox('dynamic text');
+ const component = await app.findElementByTestID('dynamic_text_button');
+ await component.waitForDisplayed({timeout: 5000});
+
+ // Get initial state
+ const initialDump = await dumpVisualTree('dynamic_text_button');
+ expect(initialDump).toMatchSnapshot('initial-text');
+
+ // Click to change text
+ await component.click();
+
+ // Verify text updated
+ const updatedDump = await dumpVisualTree('dynamic_text_button');
+ expect(updatedDump).toMatchSnapshot('updated-text');
+ expect(updatedDump.Text).toContain('Pressed 1 times');
+ });
+
+ test('Button color should update on fast refresh', async () => {
+ await searchBox('dynamic color');
+ const component = await app.findElementByTestID('dynamic_color_button');
+ await component.waitForDisplayed({timeout: 5000});
+
+ // Get initial state
+ const initialDump = await dumpVisualTree('dynamic_color_button');
+ expect(initialDump).toMatchSnapshot('initial-color');
+
+ // Click to change color
+ await component.click();
+
+ // Verify color updated (visual tree should show different styling)
+ const updatedDump = await dumpVisualTree('dynamic_color_button');
+ expect(updatedDump).toMatchSnapshot('updated-color');
+ });
+
+ test('Button disabled status should update on fast refresh', async () => {
+ await searchBox('dynamic disabled');
+ const component = await app.findElementByTestID('dynamic_disabled_button');
+ await component.waitForDisplayed({timeout: 5000});
+
+ // Get initial state (should be enabled)
+ const initialDump = await dumpVisualTree('dynamic_disabled_button');
+ expect(initialDump).toMatchSnapshot('initial-enabled');
+
+ // Click to disable
+ await component.click();
+
+ // Verify button is now disabled
+ const disabledDump = await dumpVisualTree('dynamic_disabled_button');
+ expect(disabledDump).toMatchSnapshot('disabled-state');
+ expect(disabledDump.Text).toContain('Disabled');
+
+ // Wait for auto re-enable (2 seconds)
+ await app.waitUntil(
+ async () => {
+ const dump = await dumpVisualTree('dynamic_disabled_button');
+ return dump.Text.includes('Disable Me');
+ },
+ {
+ timeout: 3000,
+ interval: 500,
+ timeoutMsg: 'Button should auto re-enable after 2 seconds',
+ }
+ );
+
+ // Verify button is enabled again
+ const reEnabledDump = await dumpVisualTree('dynamic_disabled_button');
+ expect(reEnabledDump).toMatchSnapshot('re-enabled-state');
+ });
+
+ test('Button should update relevant styling upon press', async () => {
+ await searchBox('dynamic styling');
+ const component = await app.findElementByTestID('dynamic_style_button');
+ await component.waitForDisplayed({timeout: 5000});
+
+ // Get initial state of both button and container
+ const initialDump = await dumpVisualTree('dynamic_style_button');
+ expect(initialDump).toMatchSnapshot('initial-styling');
+ const initialContainerDump = await dumpVisualTree('dynamic_style_container');
+ expect(initialContainerDump).toMatchSnapshot('initial-container-styling');
+
+ // Click to change styling
+ await component.click();
+
+ // Verify styling updated (should show press count and temporary color change)
+ const updatedDump = await dumpVisualTree('dynamic_style_button');
+ expect(updatedDump).toMatchSnapshot('updated-styling');
+ expect(updatedDump.Text).toContain('Style Button (1)');
+
+ // Also verify container styling changed
+ const updatedContainerDump = await dumpVisualTree('dynamic_style_container');
+ expect(updatedContainerDump).toMatchSnapshot('updated-container-styling');
+ });
});