From f8dd0922336eaf967b65dd87e3978b453ca318ef Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 5 Jun 2026 18:01:31 -0700 Subject: [PATCH 01/15] Remove redundant condition Desktop.IntegrationTests.SkipRNTester Modify Desktop.IntegrationTests.Filter instead. --- .ado/jobs/desktop-single.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.ado/jobs/desktop-single.yml b/.ado/jobs/desktop-single.yml index 98da16e4abe..0dff5f80250 100644 --- a/.ado/jobs/desktop-single.yml +++ b/.ado/jobs/desktop-single.yml @@ -62,12 +62,6 @@ steps: - template: ../templates/apply-published-version-vars.yml - - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: - - pwsh: | - $newValue = '(FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests::)&' + "$(Desktop.IntegrationTests.Filter)" - Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" - displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests - - template: ../templates/msbuild-sln.yml parameters: solutionDir: vnext From 9163c69dce01951bd57d53eaf8d9f40206cc1ea0 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 6 Jun 2026 16:36:37 -0700 Subject: [PATCH 02/15] Re-introduce WebSocket and WebSocketBlob tests --- .../RNTesterHeadlessTests.cpp | 61 ++++-- vnext/overrides.json | 6 +- .../IntegrationTests/WebSocketBlobTest.js | 206 ++++++------------ .../src-win/IntegrationTests/WebSocketTest.js | 65 ++++++ 4 files changed, 168 insertions(+), 170 deletions(-) create mode 100644 vnext/src-win/IntegrationTests/WebSocketTest.js diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index f25eb3cdd4d..72f60f71b10 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -17,6 +17,35 @@ namespace msrn = winrt::Microsoft::ReactNative; namespace Microsoft::React::Test { +namespace { +void RunHeadlessJsTest(std::wstring_view jsBundle) { + TestModule::Reset(); + + winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; + bool instanceFailed{false}; + + auto holder = TestReactNativeHostHolder( + jsBundle, [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { + host.InstanceSettings().InstanceLoaded( + [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { + instanceFailed = args.Failed(); + SetEvent(instanceLoadedEvent.get()); + }); + }); + + // First, wait for instance to load + WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); + if (instanceFailed) { + auto err = holder.GetLastError(); + auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); + Assert::Fail(msg.c_str()); + } + + auto status = TestModule::AwaitCompletion(); + Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); +} +} // namespace + TEST_CLASS (RNTesterHeadlessTests) { TEST_CLASS_INITIALIZE(Initialize) { // https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/win32/mddbootstrap/nf-mddbootstrap-mddbootstrapinitialize2 @@ -37,31 +66,15 @@ TEST_CLASS (RNTesterHeadlessTests) { } TEST_METHOD(Dummy) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/DummyTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - // First, wait for instance to load - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } + RunHeadlessJsTest(L"IntegrationTests/DummyTest"); + } + + TEST_METHOD(WebSocket) { + RunHeadlessJsTest(L"IntegrationTests/WebSocketTest"); + } - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + TEST_METHOD(WebSocketBlob) { + RunHeadlessJsTest(L"IntegrationTests/WebSocketBlobTest"); } }; diff --git a/vnext/overrides.json b/vnext/overrides.json index 4d3e420d543..cdc3a7f7caa 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -264,6 +264,10 @@ "type": "platform", "file": "src-win/IntegrationTests/WebSocketBlobTest.js" }, + { + "type": "platform", + "file": "src-win/IntegrationTests/WebSocketTest.js" + }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketMultipleSendTest.js" @@ -723,4 +727,4 @@ "baseHash": "365c5df75b38b129d364af3f6700cb206ce5bd2a" } ] -} \ No newline at end of file +} diff --git a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js index 7b18bfe2c07..16033c93ff6 100644 --- a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js @@ -1,159 +1,75 @@ /** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} // eslint-disable-next-line @microsoft/sdl/no-insecure-url -const DEFAULT_WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; - -const WS_EVENTS = ['close', 'error', 'message', 'open']; - -type State = { - url: string, - fetchStatus: ?string, - socket: ?WebSocket, - socketState: ?number, - lastSocketEvent: ?string, - lastMessage: ?Blob, - testMessage: Uint8Array, - testExpectedResponse: Uint8Array, - ... -}; - -class WebSocketBlobTest extends React.Component<{}, State> { - state: State = { - url: DEFAULT_WS_URL, - fetchStatus: null, - socket: null, - socketState: null, - lastSocketEvent: null, - lastMessage: null, - testMessage: new Uint8Array([1, 2, 3]), - testExpectedResponse: new Uint8Array([4, 5, 6, 7]), - }; - - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; - - _connect = () => { - const socket = new WebSocket(this.state.url); - socket.binaryType = 'blob'; - WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent)); - this.setState({ - socket, - socketState: socket.readyState, - }); - }; - - _socketIsConnected = (): boolean => { - return this.state.socketState === 1; //'OPEN' - }; - - _socketIsDisconnected = (): boolean => { - return this.state.socketState === 3; //'CLOSED' - }; - - _disconnect = () => { - if (!this.state.socket) { - return; - } - this.state.socket.close(); - }; - - _onSocketEvent = (event: any) => { - const state: any = { - socketState: event.target.readyState, - lastSocketEvent: event.type, - }; - if (event.type === 'message') { - state.lastMessage = event.data; - } - this.setState(state); - }; - - _sendBinary = (message: Uint8Array) => { - if (!this.state.socket) { - return; - } - this.state.socket.send(message); - }; - - _sendTestMessage = () => { - this._sendBinary(this.state.testMessage); - }; - - _receivedTestExpectedResponse = (): boolean => { - // Can't iterate through Blob response. Blob.arrayBuffer() not supported. - return ( - this.state.lastMessage?.size === this.state.testExpectedResponse.length - ); - }; - - componentDidMount() { - this.testConnect(); +const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; +const TEST_MESSAGE = new Uint8Array([1, 2, 3]); +const EXPECTED_SIZE = 4; + +let completed = false; +const socket = new WebSocket(WS_URL); +socket.binaryType = 'blob'; + +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function complete(passed, reason) { + if (completed) { + return; } - testConnect: () => void = () => { - this._connect(); - this._waitFor(this._socketIsConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendAndReceive(); - }); - }; - - testSendAndReceive: () => void = () => { - this._sendTestMessage(); - this._waitFor(this._receivedTestExpectedResponse, 5, messageReceived => { - if (!messageReceived) { - TestModule.markTestPassed(false); - return; - } - this.testDisconnect(); - }); - }; - - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketIsDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; - render(): React.Node { - return ; + completed = true; + clearTimeout(timeoutId); + + if (!passed && reason) { + console.log('WebSocketBlobTest FAIL: ' + reason); } -} // class WebSocketBlobTest -WebSocketBlobTest.displayName = 'WebSocketBlobTest'; + TestModule.markTestPassed(passed); +} + +socket.addEventListener('open', () => { + socket.send(TEST_MESSAGE); +}); -AppRegistry.registerComponent('WebSocketBlobTest', () => WebSocketBlobTest); +socket.addEventListener('message', event => { + const data = event.data; -module.exports = WebSocketBlobTest; + if (!(data instanceof Blob)) { + complete(false, 'expected Blob response'); + socket.close(); + return; + } + + if (data.size !== EXPECTED_SIZE) { + complete(false, 'unexpected response size'); + socket.close(); + return; + } + + complete(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); + } +}); diff --git a/vnext/src-win/IntegrationTests/WebSocketTest.js b/vnext/src-win/IntegrationTests/WebSocketTest.js new file mode 100644 index 00000000000..1dc16199a89 --- /dev/null +++ b/vnext/src-win/IntegrationTests/WebSocketTest.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + */ + +'use strict'; + +const {TurboModuleRegistry} = require('react-native'); +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} + +// eslint-disable-next-line @microsoft/sdl/no-insecure-url +const WS_URL = 'ws://localhost:5555/'; +const TEST_MESSAGE = 'testMessage'; +const EXPECTED_RESPONSE = 'testMessage_response'; + +let completed = false; +const socket = new WebSocket(WS_URL); +const timeoutId = setTimeout(() => { + complete(false, 'timeout waiting for websocket response'); +}, 10000); + +function complete(passed, reason) { + if (completed) { + return; + } + + completed = true; + clearTimeout(timeoutId); + + if (!passed && reason) { + console.log('WebSocketTest FAIL: ' + reason); + } + + TestModule.markTestPassed(passed); +} + +socket.addEventListener('open', () => { + socket.send(TEST_MESSAGE); +}); + +socket.addEventListener('message', event => { + if (event.data !== EXPECTED_RESPONSE) { + complete(false, 'unexpected response payload'); + socket.close(); + return; + } + + complete(true); + socket.close(); +}); + +socket.addEventListener('error', () => { + complete(false, 'websocket error'); +}); + +socket.addEventListener('close', () => { + if (!completed) { + complete(false, 'socket closed before completing test'); + } +}); From a76cd6bb9224299505f8f83fb898b379df9e0a24 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Sun, 7 Jun 2026 00:21:38 -0700 Subject: [PATCH 03/15] Make WSBlob test compare contents --- .../RNTesterHeadlessTests.cpp | 31 ++--------- .../IntegrationTests/WebSocketBlobTest.js | 52 +++++++++++++++++-- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index aa68b301c8c..6fc8b149ac0 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Modules/TestModule.h" #include "TestReactNativeHostHolder.h" @@ -59,6 +60,8 @@ TEST_CLASS (RNTesterHeadlessTests) { MddBootstrapInitializeOptions::MddBootstrapInitializeOptions_None))) { throw std::exception("Could not initialize Windows App runtime"); } + + SetRuntimeOptionBool("WebSocket.ResourceV2", true); } TEST_CLASS_CLEANUP(Cleanup) { @@ -77,34 +80,8 @@ TEST_CLASS (RNTesterHeadlessTests) { RunHeadlessJsTest(L"IntegrationTests/WebSocketBlobTest"); } - BEGIN_TEST_METHOD_ATTRIBUTE(WebSocketArrayBuffer) - TEST_IGNORE() - END_TEST_METHOD_ATTRIBUTE() TEST_METHOD(WebSocketArrayBuffer) { - TestModule::Reset(); - - winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; - bool instanceFailed{false}; - - auto holder = TestReactNativeHostHolder( - L"IntegrationTests/WebSocketArrayBufferTest", - [&instanceLoadedEvent, &instanceFailed](msrn::ReactNativeHost const &host) noexcept { - host.InstanceSettings().InstanceLoaded( - [&instanceLoadedEvent, &instanceFailed](auto const &, msrn::InstanceLoadedEventArgs args) noexcept { - instanceFailed = args.Failed(); - SetEvent(instanceLoadedEvent.get()); - }); - }); - - WaitForSingleObject(instanceLoadedEvent.get(), INFINITE); - if (instanceFailed) { - auto err = holder.GetLastError(); - auto msg = L"InstanceLoaded reported failure: " + (err.empty() ? L"(no error captured)" : err); - Assert::Fail(msg.c_str()); - } - - auto status = TestModule::AwaitCompletion(); - Assert::IsTrue(status == TestStatus::Passed, L"Test did not pass (JS did not call markTestPassed within timeout)"); + RunHeadlessJsTest(L"IntegrationTests/WebSocketArrayBufferTest"); } }; diff --git a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js index 16033c93ff6..1b7b675276b 100644 --- a/vnext/src-win/IntegrationTests/WebSocketBlobTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketBlobTest.js @@ -16,7 +16,9 @@ if (!TestModule) { // eslint-disable-next-line @microsoft/sdl/no-insecure-url const WS_URL = 'ws://localhost:5555/rnw/rntester/websocketbinarytest'; const TEST_MESSAGE = new Uint8Array([1, 2, 3]); -const EXPECTED_SIZE = 4; +// Expected bytes come from RNTesterIntegrationTests.WebSocketBinaryTest outgoingBytes. +const EXPECTED_BYTES = new Uint8Array([4, 5, 6, 7]); +const EXPECTED_SIZE = EXPECTED_BYTES.length; let completed = false; const socket = new WebSocket(WS_URL); @@ -41,6 +43,25 @@ function complete(passed, reason) { TestModule.markTestPassed(passed); } +function decodeBlobToBytes(blob, onSuccess, onError) { + if (typeof blob.arrayBuffer === 'function') { + blob + .arrayBuffer() + .then(buffer => onSuccess(new Uint8Array(buffer))) + .catch(onError); + return; + } + + const reader = new FileReader(); + reader.onload = () => { + onSuccess(new Uint8Array(reader.result)); + }; + reader.onerror = () => { + onError(reader.error || new Error('failed to read blob')); + }; + reader.readAsArrayBuffer(blob); +} + socket.addEventListener('open', () => { socket.send(TEST_MESSAGE); }); @@ -60,8 +81,33 @@ socket.addEventListener('message', event => { return; } - complete(true); - socket.close(); + decodeBlobToBytes( + data, + bytes => { + for (let i = 0; i < EXPECTED_BYTES.length; i++) { + if (bytes[i] !== EXPECTED_BYTES[i]) { + complete( + false, + 'unexpected response byte at index ' + + i + + ': expected ' + + EXPECTED_BYTES[i] + + ', got ' + + bytes[i], + ); + socket.close(); + return; + } + } + + complete(true); + socket.close(); + }, + error => { + complete(false, 'failed to decode blob: ' + String(error)); + socket.close(); + }, + ); }); socket.addEventListener('error', () => { From 0b357c675f15ec45a6b5c7060e08bcda976d25ea Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Sun, 7 Jun 2026 00:30:51 -0700 Subject: [PATCH 04/15] Rename method to RunTest --- .../Desktop.IntegrationTests/RNTesterHeadlessTests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 6fc8b149ac0..a85bc724fa8 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -19,7 +19,7 @@ namespace msrn = winrt::Microsoft::ReactNative; namespace Microsoft::React::Test { namespace { -void RunHeadlessJsTest(std::wstring_view jsBundle) { +void RunTest(std::wstring_view jsBundle) { TestModule::Reset(); winrt::handle instanceLoadedEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; @@ -69,19 +69,19 @@ TEST_CLASS (RNTesterHeadlessTests) { } TEST_METHOD(Dummy) { - RunHeadlessJsTest(L"IntegrationTests/DummyTest"); + RunTest(L"IntegrationTests/DummyTest"); } TEST_METHOD(WebSocket) { - RunHeadlessJsTest(L"IntegrationTests/WebSocketTest"); + RunTest(L"IntegrationTests/WebSocketTest"); } TEST_METHOD(WebSocketBlob) { - RunHeadlessJsTest(L"IntegrationTests/WebSocketBlobTest"); + RunTest(L"IntegrationTests/WebSocketBlobTest"); } TEST_METHOD(WebSocketArrayBuffer) { - RunHeadlessJsTest(L"IntegrationTests/WebSocketArrayBufferTest"); + RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); } }; From 8cb3ab1fefb9376f280bdda30836f60b230a50ac Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Sun, 7 Jun 2026 00:43:09 -0700 Subject: [PATCH 05/15] Handle initial message from default endpoint --- .../src-win/IntegrationTests/WebSocketTest.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/vnext/src-win/IntegrationTests/WebSocketTest.js b/vnext/src-win/IntegrationTests/WebSocketTest.js index 1dc16199a89..b1975486599 100644 --- a/vnext/src-win/IntegrationTests/WebSocketTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketTest.js @@ -17,13 +17,24 @@ if (!TestModule) { const WS_URL = 'ws://localhost:5555/'; const TEST_MESSAGE = 'testMessage'; const EXPECTED_RESPONSE = 'testMessage_response'; +const INITIAL_SERVER_GREETING = 'hello'; let completed = false; +let messageSent = false; const socket = new WebSocket(WS_URL); const timeoutId = setTimeout(() => { complete(false, 'timeout waiting for websocket response'); }, 10000); +function sendTestMessageIfNeeded() { + if (messageSent || socket.readyState !== WebSocket.OPEN) { + return; + } + + messageSent = true; + socket.send(TEST_MESSAGE); +} + function complete(passed, reason) { if (completed) { return; @@ -40,10 +51,14 @@ function complete(passed, reason) { } socket.addEventListener('open', () => { - socket.send(TEST_MESSAGE); + sendTestMessageIfNeeded(); }); socket.addEventListener('message', event => { + if (event.data === INITIAL_SERVER_GREETING) { + return; + } + if (event.data !== EXPECTED_RESPONSE) { complete(false, 'unexpected response payload'); socket.close(); @@ -63,3 +78,5 @@ socket.addEventListener('close', () => { complete(false, 'socket closed before completing test'); } }); + +sendTestMessageIfNeeded(); From ce92f29b93fbaf5ad16de50f5c878ce7aa75961d Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Sun, 7 Jun 2026 00:52:18 -0700 Subject: [PATCH 06/15] Change files --- ...ative-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json diff --git a/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json new file mode 100644 index 00000000000..dc96d8037a6 --- /dev/null +++ b/change/react-native-windows-5ebbf393-f061-48ee-aa68-dfdd086cfbbd.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Re-introduce WebSocket JS integration tests", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} From 2d11bf36ea7b16edefd8ffa2bbc0d04ce186d079 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:01:21 -0700 Subject: [PATCH 07/15] Migrate XHR test --- .../RNTesterHeadlessTests.cpp | 4 + vnext/TestWebSite/wwwroot/static/sample.txt | 2 +- vnext/src-win/IntegrationTests/XHRTest.js | 89 ++++--------------- 3 files changed, 24 insertions(+), 71 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index a85bc724fa8..f81d2c48616 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -83,6 +83,10 @@ TEST_CLASS (RNTesterHeadlessTests) { TEST_METHOD(WebSocketArrayBuffer) { RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); } + + TEST_METHOD(XHR) { + RunTest(L"IntegrationTests/XHRTest"); + } }; } // namespace Microsoft::React::Test diff --git a/vnext/TestWebSite/wwwroot/static/sample.txt b/vnext/TestWebSite/wwwroot/static/sample.txt index 5bd282c8887..47e646f426f 100644 --- a/vnext/TestWebSite/wwwroot/static/sample.txt +++ b/vnext/TestWebSite/wwwroot/static/sample.txt @@ -1 +1 @@ -Sample Static Text File +Sample Static Text File \ No newline at end of file diff --git a/vnext/src-win/IntegrationTests/XHRTest.js b/vnext/src-win/IntegrationTests/XHRTest.js index a03ba97c658..2b85a9a0f3d 100644 --- a/vnext/src-win/IntegrationTests/XHRTest.js +++ b/vnext/src-win/IntegrationTests/XHRTest.js @@ -2,81 +2,30 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, -}; - -class XHRTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - }; - - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open( - 'GET', - 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config', - ); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.send(); - }; - - _getSucceeded = (): boolean => { - console.log( - `_getSucceeded [${this.state.statusCode}],[${this.state.xhr.responseText.length}]`, - ); - return ( - this.state.statusCode === 200 && - this.state.xhr.responseText.length === 387 - ); - }; - - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; - - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 5, doneSucceeded => { - TestModule.markTestPassed(doneSucceeded); - }); - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('XHRTest', () => XHRTest); +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +const xhr = new XMLHttpRequest(); +xhr.onloadend = () => { + const responseText = xhr.responseText || ''; + const passed = + xhr.status === 200 && responseText === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); +}; +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = XHRTest; +xhr.open('GET', URL); +xhr.send(); From f3d3515826c014f8af0cd551297ab1776dd41ad0 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:15:39 -0700 Subject: [PATCH 08/15] Port Fetch test --- .../RNTesterHeadlessTests.cpp | 12 ++-- vnext/src-win/IntegrationTests/FetchTest.js | 57 ++++++------------- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index f81d2c48616..0a5a548a22b 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -72,6 +72,14 @@ TEST_CLASS (RNTesterHeadlessTests) { RunTest(L"IntegrationTests/DummyTest"); } + TEST_METHOD(Fetch) { + RunTest(L"IntegrationTests/FetchTest"); + } + + TEST_METHOD(XHR) { + RunTest(L"IntegrationTests/XHRTest"); + } + TEST_METHOD(WebSocket) { RunTest(L"IntegrationTests/WebSocketTest"); } @@ -83,10 +91,6 @@ TEST_CLASS (RNTesterHeadlessTests) { TEST_METHOD(WebSocketArrayBuffer) { RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); } - - TEST_METHOD(XHR) { - RunTest(L"IntegrationTests/XHRTest"); - } }; } // namespace Microsoft::React::Test diff --git a/vnext/src-win/IntegrationTests/FetchTest.js b/vnext/src-win/IntegrationTests/FetchTest.js index 79c666fa581..ff645f5516a 100644 --- a/vnext/src-win/IntegrationTests/FetchTest.js +++ b/vnext/src-win/IntegrationTests/FetchTest.js @@ -2,52 +2,27 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -const uri = - 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml'; -const expectedContent = 'enableScripts: false'; - -type State = { - uri: string, - expected: string, - content: string, -}; - -class FetchTest extends React.Component<{...}, State> { - state: State = { - uri: 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/.yarnrc.yml', - expected: 'enableScripts: false', - content: '', - }; - - async componentDidMount() { - const response = await fetch(uri); - const text = await response.text(); - this.setState({content: text}); - - if (this.state.content === expectedContent) { - TestModule.markTestPassed(true); - } else { - TestModule.markTestPassed(false); - } - } - - render(): React.Node { - return ; - } +if (!TestModule) { + throw new Error('TestModule is not available'); } -AppRegistry.registerComponent('FetchTest', () => FetchTest); - -module.exports = FetchTest; +const URL = 'http://localhost:5555/static/sample.txt'; +const EXPECTED_CONTENT = 'Sample Static Text File'; + +fetch(URL) + .then(response => response.text()) + .then(text => { + const passed = text === EXPECTED_CONTENT; + TestModule.markTestPassed(passed); + }) + .catch(() => { + TestModule.markTestPassed(false); + }); From 69deaa139cdc0b88473276ac0bc7266f333fad67 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:31:25 -0700 Subject: [PATCH 09/15] Port Blob test --- .../RNTesterHeadlessTests.cpp | 4 + vnext/src-win/IntegrationTests/BlobTest.js | 161 +++++++----------- 2 files changed, 66 insertions(+), 99 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 0a5a548a22b..7a293454f94 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -91,6 +91,10 @@ TEST_CLASS (RNTesterHeadlessTests) { TEST_METHOD(WebSocketArrayBuffer) { RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); } + + TEST_METHOD(Blob) { + RunTest(L"IntegrationTests/BlobTest"); + } }; } // namespace Microsoft::React::Test diff --git a/vnext/src-win/IntegrationTests/BlobTest.js b/vnext/src-win/IntegrationTests/BlobTest.js index 47cf5ecb81e..363ee3c99f7 100644 --- a/vnext/src-win/IntegrationTests/BlobTest.js +++ b/vnext/src-win/IntegrationTests/BlobTest.js @@ -2,115 +2,78 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format - * @flow */ 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); +const {TurboModuleRegistry} = require('react-native'); -const {AppRegistry, View} = ReactNative; +const TestModule = TurboModuleRegistry.get('TestModule'); -const {TestModule} = ReactNative.NativeModules; - -type State = { - statusCode: number, - xhr: XMLHttpRequest, - expected: string, -}; - -class BlobTest extends React.Component<{...}, State> { - state: State = { - statusCode: 0, - xhr: new XMLHttpRequest(), - // http://localhost:5555/static/react.png - // cspell:disable - expected: - 'data:application/octet-stream;base64,' + - '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' + - 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII=', - // cspell:enable - }; +if (!TestModule) { + throw new Error('TestModule is not available'); +} - _get = () => { - this.state.xhr.onloadend = () => { - this.setState({ - statusCode: this.state.xhr.status, - }); - }; - this.state.xhr.open('GET', 'http://localhost:5555/static/react.png'); - this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.state.xhr.responseType = 'blob'; - this.state.xhr.send(); - }; +const URL = 'http://localhost:5555/static/react.png'; - _getSucceeded = (): boolean => { - return this.state.statusCode === 200 && this.state.xhr.response !== null; - }; +// cspell:disable +const EXPECTED_DATA_URL = + 'data:application/octet-stream;base64,' + + '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' + + 'uP2D2H8BsGL5W7blULYAAAAASUVORK5CYII='; +// cspell:enable - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +const xhr = new XMLHttpRequest(); +xhr.responseType = 'blob'; - componentDidMount() { - this._get(); - this._waitFor(this._getSucceeded, 6, doneSucceeded => { - let reader = new FileReader(); - reader.readAsDataURL(this.state.xhr.response); - reader.onload = () => { - TestModule.markTestPassed( - doneSucceeded && this.state.expected === reader.result, - ); - }; - }); +xhr.onloadend = () => { + const succeeded = xhr.status === 200 && xhr.response !== null; + if (!succeeded) { + TestModule.markTestPassed(false); + return; } - render(): React.Node { - return ; - } -} + const reader = new FileReader(); + reader.onload = () => { + TestModule.markTestPassed(reader.result === EXPECTED_DATA_URL); + }; + reader.onerror = () => { + TestModule.markTestPassed(false); + }; + reader.readAsDataURL(xhr.response); +}; -AppRegistry.registerComponent('BlobTest', () => BlobTest); +xhr.onerror = () => { + TestModule.markTestPassed(false); +}; -module.exports = BlobTest; +xhr.open('GET', URL); +xhr.setRequestHeader('Accept-Encoding', 'utf-8'); +xhr.send(); From 3e92545ca610c4fef48040cc1923ef843836461f Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:37:54 -0700 Subject: [PATCH 10/15] WebSocketMultipleSendTest --- .../RNTesterHeadlessTests.cpp | 4 + .../WebSocketMultipleSendTest.js | 199 +++++++----------- 2 files changed, 84 insertions(+), 119 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 7a293454f94..67cb12d5ca6 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -92,6 +92,10 @@ TEST_CLASS (RNTesterHeadlessTests) { RunTest(L"IntegrationTests/WebSocketArrayBufferTest"); } + TEST_METHOD(WebSocketMultipleSend) { + RunTest(L"IntegrationTests/WebSocketMultipleSendTest"); + } + TEST_METHOD(Blob) { RunTest(L"IntegrationTests/BlobTest"); } diff --git a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js index e3658cdab96..afaa789368f 100644 --- a/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js +++ b/vnext/src-win/IntegrationTests/WebSocketMultipleSendTest.js @@ -10,7 +10,6 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow */ /* @@ -25,10 +24,13 @@ Array.from('abcdef').forEach( e => { ws.send(e.repeat(1025)) }); 'use strict'; -const React = require('react'); -const ReactNative = require('react-native'); -const {AppRegistry, View} = ReactNative; -const {TestModule} = ReactNative.NativeModules; +const {TurboModuleRegistry} = require('react-native'); + +const TestModule = TurboModuleRegistry.get('TestModule'); + +if (!TestModule) { + throw new Error('TestModule is not available'); +} // eslint-disable-next-line @microsoft/sdl/no-insecure-url const URL_BASE = 'ws://localhost:5555/rnw/rntester/websocketmultiplesendtest'; @@ -41,131 +43,90 @@ const EXPECTED = 'abcdef'; const ID = Math.floor(Math.random() * 1000000); -type State = { - sendUrl: string, - receiveUrl: string, - sendSocket: ?WebSocket, - receiveSocket: ?WebSocket, - result: ?string, -}; - -class WebSocketMultipleSendTest extends React.Component<{}, State> { - state: State = { - sendUrl: `${URL_BASE}/send/${ID}`, - receiveUrl: `${URL_BASE}/receive/${ID}`, - sendSocket: null, - receiveSocket: null, - result: '', - }; +const sendUrl = `${URL_BASE}/send/${ID}`; +const receiveUrl = `${URL_BASE}/receive/${ID}`; - _waitFor = (condition: any, timeout: any, callback: any) => { - let remaining = timeout; - const timeoutFunction = function () { - if (condition()) { - callback(true); - return; - } - remaining--; - if (remaining === 0) { - callback(false); - } else { - setTimeout(timeoutFunction, 1000); - } - }; - setTimeout(timeoutFunction, 1000); - }; +let sendSocket = null; +let receiveSocket = null; +let result = ''; - _socketsAreConnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 1 && - this.state.sendSocket?.readyState === 1 - ); // OPEN +const waitFor = (condition, timeout, callback) => { + let remaining = timeout; + const timeoutFunction = function () { + if (condition()) { + callback(true); + return; + } + remaining--; + if (remaining === 0) { + callback(false); + } else { + setTimeout(timeoutFunction, 1000); + } }; + setTimeout(timeoutFunction, 1000); +}; - _socketsAreDisconnected = (): boolean => { - return ( - this.state.sendSocket?.readyState === 3 && - this.state.sendSocket?.readyState === 3 - ); // CLOSED - }; +const socketsAreConnected = () => { + return sendSocket?.readyState === 1 && sendSocket?.readyState === 1; // OPEN +}; - _resultIsComplete = (): boolean => { - return this.state.result === EXPECTED; - }; +const socketsAreDisconnected = () => { + return sendSocket?.readyState === 3 && sendSocket?.readyState === 3; // CLOSED +}; - _disconnect = () => { - if (this.state.receiveSocket) { - this.state.receiveSocket.close(); - } - }; +const resultIsComplete = () => { + return result === EXPECTED; +}; - testDisconnect: () => void = () => { - this._disconnect(); - this._waitFor(this._socketsAreDisconnected, 5, disconnectSucceeded => { - TestModule.markTestPassed(disconnectSucceeded); - }); - }; +const disconnect = () => { + if (receiveSocket) { + receiveSocket.close(); + } +}; - testSendMultipleAndClose: () => void = () => { - this.state.sendSocket?.send('a'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('b'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('c'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('d'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('e'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.send('f'.repeat(MESSAGE_SIZE)); - this.state.sendSocket?.close(); - - this._waitFor(this._resultIsComplete, 5, resultComplete => { - if (!resultComplete) { - TestModule.markTestPassed(false); - return; - } - this.testDisconnect(); - }); - }; +const testDisconnect = () => { + disconnect(); + waitFor(socketsAreDisconnected, 5, disconnectSucceeded => { + TestModule.markTestPassed(disconnectSucceeded); + }); +}; - _onSocketEvent = (event: any) => { - if (event.type === 'message' && event.data.length) { - var message = this.state.result + event.data[0]; - this.setState({ - result: message, - }); +const testSendMultipleAndClose = () => { + sendSocket?.send('a'.repeat(MESSAGE_SIZE)); + sendSocket?.send('b'.repeat(MESSAGE_SIZE)); + sendSocket?.send('c'.repeat(MESSAGE_SIZE)); + sendSocket?.send('d'.repeat(MESSAGE_SIZE)); + sendSocket?.send('e'.repeat(MESSAGE_SIZE)); + sendSocket?.send('f'.repeat(MESSAGE_SIZE)); + sendSocket?.close(); + + waitFor(resultIsComplete, 5, resultComplete => { + if (!resultComplete) { + TestModule.markTestPassed(false); + return; } - }; - - _connect = () => { - const sendSocket = new WebSocket(this.state.sendUrl); - const receiveSocket = new WebSocket(this.state.receiveUrl); - WS_EVENTS.forEach(ev => - receiveSocket.addEventListener(ev, this._onSocketEvent), - ); - this.setState({ - sendSocket, - receiveSocket, - }); - }; - - componentDidMount() { - this._connect(); - this._waitFor(this._socketsAreConnected, 5, connectSucceeded => { - if (!connectSucceeded) { - TestModule.markTestPassed(false); - return; - } - this.testSendMultipleAndClose(); - }); - } + testDisconnect(); + }); +}; - render(): React.Node { - return ; +const onSocketEvent = event => { + if (event.type === 'message' && event.data.length) { + result += event.data[0]; } -} // class WebSocketMultipleSendTest - -WebSocketMultipleSendTest.displayName = 'WebSocketMultipleSendTest'; +}; -AppRegistry.registerComponent( - 'WebSocketMultipleSendTest', - () => WebSocketMultipleSendTest, -); +const connect = () => { + sendSocket = new WebSocket(sendUrl); + receiveSocket = new WebSocket(receiveUrl); + WS_EVENTS.forEach(ev => receiveSocket.addEventListener(ev, onSocketEvent)); +}; -module.exports = WebSocketMultipleSendTest; +connect(); +waitFor(socketsAreConnected, 5, connectSucceeded => { + if (!connectSucceeded) { + TestModule.markTestPassed(false); + return; + } + testSendMultipleAndClose(); +}); From 37ca0eea94887d59951c96f14f7ffe773fd5b74b Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:48:08 -0700 Subject: [PATCH 11/15] Remove JS WebSocket servers --- vnext/overrides.json | 8 ----- ...ebsocket_integration_test_server_binary.js | 33 ------------------- .../websocket_integration_test_server_blob.js | 33 ------------------- 3 files changed, 74 deletions(-) delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js delete mode 100644 vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js diff --git a/vnext/overrides.json b/vnext/overrides.json index 5425d85d586..9e17cd794ed 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -248,14 +248,6 @@ "type": "platform", "file": "src-win/IntegrationTests/LoggingTest.js" }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_binary.js" - }, - { - "type": "platform", - "file": "src-win/IntegrationTests/websocket_integration_test_server_blob.js" - }, { "type": "platform", "file": "src-win/IntegrationTests/WebSocketArrayBufferTest.js" diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js deleted file mode 100644 index 54583577d48..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_binary.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'arraybuffer'; - ws.on('message', message => { - console.log(message); - - ws.send(new Uint8Array([4, 5, 6, 7]).buffer); - }); -}); diff --git a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js b/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js deleted file mode 100644 index d08ae16e0c5..00000000000 --- a/vnext/src-win/IntegrationTests/websocket_integration_test_server_blob.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ - -'use strict'; - -/* eslint-env node */ - -const WebSocket = require('ws'); - -console.log(`\ -WebSocket binary integration test server - -This will send each incoming message back, in binary form. - -`); - -const server = new WebSocket.Server({port: 5557}); -server.on('connection', ws => { - ws.binaryType = 'blob'; - ws.on('message', message => { - console.log(message); - - ws.send([4, 5, 6, 7]); - }); -}); From 0445f34a07d0d036d47ceca3e523488c7a10fb37 Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:50:42 -0700 Subject: [PATCH 12/15] Skip in CI for now --- .ado/build-template.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ado/build-template.yml b/.ado/build-template.yml index a49d68b64aa..52a2ada03a9 100644 --- a/.ado/build-template.yml +++ b/.ado/build-template.yml @@ -281,6 +281,7 @@ extends: #12714 - Disable for first deployment of test website. - name: Desktop.IntegrationTests.Filter value: > + (FullyQualifiedName!=Microsoft::React::Test::RNTesterHeadlessTests)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& From 551993fcbd11a20c49ca060f3a1e983ee040f3da Mon Sep 17 00:00:00 2001 From: Julio Cesar Rocha Date: Mon, 8 Jun 2026 00:59:36 -0700 Subject: [PATCH 13/15] Drop LF --- vnext/overrides.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/overrides.json b/vnext/overrides.json index 9e17cd794ed..e471e0c740f 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -723,4 +723,4 @@ "baseHash": "365c5df75b38b129d364af3f6700cb206ce5bd2a" } ] -} +} \ No newline at end of file From b0ffad9cababfe31811e9001619e776261f7ad8a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 14:37:11 -0700 Subject: [PATCH 14/15] Drop option `WebSocket.ResourceV2` --- vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp index 67cb12d5ca6..c819059c280 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterHeadlessTests.cpp @@ -60,8 +60,6 @@ TEST_CLASS (RNTesterHeadlessTests) { MddBootstrapInitializeOptions::MddBootstrapInitializeOptions_None))) { throw std::exception("Could not initialize Windows App runtime"); } - - SetRuntimeOptionBool("WebSocket.ResourceV2", true); } TEST_CLASS_CLEANUP(Cleanup) { From 2cbb38831c86d4d21d50f734d9bcbda6b6739636 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 8 Jun 2026 16:09:35 -0700 Subject: [PATCH 15/15] Make filter match instead of equality --- .ado/build-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ado/build-template.yml b/.ado/build-template.yml index 52a2ada03a9..f328ee6760c 100644 --- a/.ado/build-template.yml +++ b/.ado/build-template.yml @@ -281,7 +281,7 @@ extends: #12714 - Disable for first deployment of test website. - name: Desktop.IntegrationTests.Filter value: > - (FullyQualifiedName!=Microsoft::React::Test::RNTesterHeadlessTests)& + (FullyQualifiedName!~Microsoft::React::Test::RNTesterHeadlessTests)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)&