diff --git a/dotnet/src/webdriver/BiDi/Communication/Broker.cs b/dotnet/src/webdriver/BiDi/Communication/Broker.cs
index db2058224749b..dd2c9a6c1244c 100644
--- a/dotnet/src/webdriver/BiDi/Communication/Broker.cs
+++ b/dotnet/src/webdriver/BiDi/Communication/Broker.cs
@@ -75,6 +75,7 @@ internal Broker(BiDi bidi, Uri url)
new BrowserUserContextConverter(bidi),
new BrowserClientWindowConverter(),
new NavigationConverter(),
+ new CollectorConverter(_bidi),
new InterceptConverter(_bidi),
new RequestConverter(),
new ChannelConverter(),
diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs
index e467b53c4922e..5d4ac7e7b0c72 100644
--- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs
+++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs
@@ -118,13 +118,18 @@ namespace OpenQA.Selenium.BiDi.Communication.Json;
[JsonSerializable(typeof(BrowsingContext.UserPromptOpenedEventArgs))]
[JsonSerializable(typeof(BrowsingContext.UserPromptClosedEventArgs))]
+[JsonSerializable(typeof(Network.AddDataCollectorCommand))]
+[JsonSerializable(typeof(Network.AddDataCollectorResult))]
[JsonSerializable(typeof(Network.AddInterceptCommand))]
[JsonSerializable(typeof(Network.AddInterceptResult))]
[JsonSerializable(typeof(Network.ContinueRequestCommand))]
[JsonSerializable(typeof(Network.ContinueResponseCommand))]
[JsonSerializable(typeof(Network.ContinueWithAuthCommand))]
[JsonSerializable(typeof(Network.FailRequestCommand))]
+[JsonSerializable(typeof(Network.GetDataCommand))]
+[JsonSerializable(typeof(Network.GetDataResult))]
[JsonSerializable(typeof(Network.ProvideResponseCommand))]
+[JsonSerializable(typeof(Network.RemoveDataCollectorCommand))]
[JsonSerializable(typeof(Network.RemoveInterceptCommand))]
[JsonSerializable(typeof(Network.SetCacheBehaviorCommand))]
diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/CollectorConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/CollectorConverter.cs
new file mode 100644
index 0000000000000..836f252435a4d
--- /dev/null
+++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/CollectorConverter.cs
@@ -0,0 +1,47 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using OpenQA.Selenium.BiDi.Network;
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace OpenQA.Selenium.BiDi.Communication.Json.Converters;
+
+internal class CollectorConverter : JsonConverter
+{
+ private readonly BiDi _bidi;
+
+ public CollectorConverter(BiDi bidi)
+ {
+ _bidi = bidi;
+ }
+
+ public override Collector? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var id = reader.GetString();
+
+ return new Collector(_bidi, id!);
+ }
+
+ public override void Write(Utf8JsonWriter writer, Collector value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.Id);
+ }
+}
diff --git a/dotnet/src/webdriver/BiDi/Network/AddDataCollectorCommand.cs b/dotnet/src/webdriver/BiDi/Network/AddDataCollectorCommand.cs
new file mode 100644
index 0000000000000..fa9784f0a273b
--- /dev/null
+++ b/dotnet/src/webdriver/BiDi/Network/AddDataCollectorCommand.cs
@@ -0,0 +1,49 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using System.Collections.Generic;
+using OpenQA.Selenium.BiDi.Communication;
+
+namespace OpenQA.Selenium.BiDi.Network;
+
+internal sealed class AddDataCollectorCommand(AddDataCollectorParameters @params)
+ : Command(@params, "network.addDataCollector");
+
+internal sealed record AddDataCollectorParameters(IEnumerable DataTypes, int MaxEncodedDataSize, CollectorType? CollectorType, IEnumerable? Contexts, IEnumerable? UserContexts) : Parameters;
+
+public class AddDataCollectorOptions : CommandOptions
+{
+ public CollectorType? CollectorType { get; set; }
+
+ public IEnumerable? Contexts { get; set; }
+
+ public IEnumerable? UserContexts { get; set; }
+}
+
+public sealed record AddDataCollectorResult(Collector Collector) : EmptyResult;
+
+public enum DataType
+{
+ Response
+}
+
+public enum CollectorType
+{
+ Blob
+}
diff --git a/dotnet/src/webdriver/BiDi/Network/BytesValue.cs b/dotnet/src/webdriver/BiDi/Network/BytesValue.cs
index 8d57fb7b52f0f..b69e2810f2c8a 100644
--- a/dotnet/src/webdriver/BiDi/Network/BytesValue.cs
+++ b/dotnet/src/webdriver/BiDi/Network/BytesValue.cs
@@ -29,6 +29,16 @@ public abstract record BytesValue
{
public static implicit operator BytesValue(string value) => new StringBytesValue(value);
public static implicit operator BytesValue(byte[] value) => new Base64BytesValue(value);
+
+ public static explicit operator string(BytesValue value)
+ {
+ if (value is StringBytesValue stringBytesValue)
+ {
+ return stringBytesValue.Value;
+ }
+
+ throw new InvalidCastException($"Cannot cast '{value.GetType()}' to '{typeof(string)}'.");
+ }
}
public sealed record StringBytesValue(string Value) : BytesValue;
diff --git a/dotnet/src/webdriver/BiDi/Network/Collector.cs b/dotnet/src/webdriver/BiDi/Network/Collector.cs
new file mode 100644
index 0000000000000..0a1f56a5064a5
--- /dev/null
+++ b/dotnet/src/webdriver/BiDi/Network/Collector.cs
@@ -0,0 +1,58 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using System;
+using System.Threading.Tasks;
+
+namespace OpenQA.Selenium.BiDi.Network;
+
+public sealed class Collector : IAsyncDisposable
+{
+ private readonly BiDi _bidi;
+
+ internal Collector(BiDi bidi, string id)
+ {
+ _bidi = bidi;
+ Id = id;
+ }
+
+ internal string Id { get; }
+
+ public async Task RemoveAsync()
+ {
+ await _bidi.Network.RemoveDataCollectorAsync(this).ConfigureAwait(false);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await RemoveAsync();
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is Collector collectortObj) return collectortObj.Id == Id;
+
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+}
diff --git a/dotnet/src/webdriver/BiDi/Network/GetDataCommand.cs b/dotnet/src/webdriver/BiDi/Network/GetDataCommand.cs
new file mode 100644
index 0000000000000..fe1e3f6cfe074
--- /dev/null
+++ b/dotnet/src/webdriver/BiDi/Network/GetDataCommand.cs
@@ -0,0 +1,36 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using OpenQA.Selenium.BiDi.Communication;
+
+namespace OpenQA.Selenium.BiDi.Network;
+
+internal sealed class GetDataCommand(GetDataParameters @params)
+ : Command(@params, "network.getData");
+
+internal sealed record GetDataParameters(DataType DataType, Request Request, Collector? Collector, bool? Disown) : Parameters;
+
+public sealed class GetDataOptions : CommandOptions
+{
+ public Collector? Collector { get; set; }
+
+ public bool? Disown { get; set; }
+}
+
+public sealed record GetDataResult(BytesValue Bytes) : EmptyResult;
diff --git a/dotnet/src/webdriver/BiDi/Network/NetworkModule.cs b/dotnet/src/webdriver/BiDi/Network/NetworkModule.cs
index 38b74db9ee551..95e38245af4b5 100644
--- a/dotnet/src/webdriver/BiDi/Network/NetworkModule.cs
+++ b/dotnet/src/webdriver/BiDi/Network/NetworkModule.cs
@@ -26,6 +26,15 @@ namespace OpenQA.Selenium.BiDi.Network;
public sealed partial class NetworkModule(Broker broker) : Module(broker)
{
+ public async Task AddDataCollectorAsync(IEnumerable DataTypes, int MaxEncodedDataSize, AddDataCollectorOptions? options = null)
+ {
+ var @params = new AddDataCollectorParameters(DataTypes, MaxEncodedDataSize, options?.CollectorType, options?.Contexts, options?.UserContexts);
+
+ var result = await Broker.ExecuteCommandAsync(new AddDataCollectorCommand(@params), options).ConfigureAwait(false);
+
+ return result.Collector;
+ }
+
public async Task AddInterceptAsync(IEnumerable phases, AddInterceptOptions? options = null)
{
var @params = new AddInterceptParameters(phases, options?.Contexts, options?.UrlPatterns);
@@ -35,6 +44,13 @@ public async Task AddInterceptAsync(IEnumerable phase
return result.Intercept;
}
+ public async Task RemoveDataCollectorAsync(Collector collector, RemoveDataCollectorOptions? options = null)
+ {
+ var @params = new RemoveDataCollectorParameters(collector);
+
+ return await Broker.ExecuteCommandAsync(new RemoveDataCollectorCommand(@params), options).ConfigureAwait(false);
+ }
+
public async Task RemoveInterceptAsync(Intercept intercept, RemoveInterceptOptions? options = null)
{
var @params = new RemoveInterceptParameters(intercept);
@@ -70,6 +86,15 @@ public async Task FailRequestAsync(Request request, FailRequestOpti
return await Broker.ExecuteCommandAsync(new FailRequestCommand(@params), options).ConfigureAwait(false);
}
+ public async Task GetDataAsync(DataType dataType, Request request, GetDataOptions? options = null)
+ {
+ var @params = new GetDataParameters(dataType, request, options?.Collector, options?.Disown);
+
+ var result = await Broker.ExecuteCommandAsync(new GetDataCommand(@params), options).ConfigureAwait(false);
+
+ return result.Bytes;
+ }
+
public async Task ProvideResponseAsync(Request request, ProvideResponseOptions? options = null)
{
var @params = new ProvideResponseParameters(request, options?.Body, options?.Cookies, options?.Headers, options?.ReasonPhrase, options?.StatusCode);
diff --git a/dotnet/src/webdriver/BiDi/Network/RemoveDataCollectorCommand.cs b/dotnet/src/webdriver/BiDi/Network/RemoveDataCollectorCommand.cs
new file mode 100644
index 0000000000000..5bb81b557fc8a
--- /dev/null
+++ b/dotnet/src/webdriver/BiDi/Network/RemoveDataCollectorCommand.cs
@@ -0,0 +1,29 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using OpenQA.Selenium.BiDi.Communication;
+
+namespace OpenQA.Selenium.BiDi.Network;
+
+internal sealed class RemoveDataCollectorCommand(RemoveDataCollectorParameters @params)
+ : Command(@params, "network.removeDataCollector");
+
+internal sealed record RemoveDataCollectorParameters(Collector Collector) : Parameters;
+
+public class RemoveDataCollectorOptions : CommandOptions;
diff --git a/dotnet/test/common/BiDi/Network/NetworkTest.cs b/dotnet/test/common/BiDi/Network/NetworkTest.cs
index 91f37e8c9520a..8aa1c07601d1f 100644
--- a/dotnet/test/common/BiDi/Network/NetworkTest.cs
+++ b/dotnet/test/common/BiDi/Network/NetworkTest.cs
@@ -19,12 +19,23 @@
using NUnit.Framework;
using OpenQA.Selenium.BiDi.BrowsingContext;
+using System;
using System.Threading.Tasks;
namespace OpenQA.Selenium.BiDi.Network;
class NetworkTest : BiDiTestFixture
{
+ [Test]
+ public async Task CanAddDataCollector()
+ {
+ // Firefox doesn't like int.MaxValue as max encoded data size
+ // invalid argument: Expected "maxEncodedDataSize" to be less than the max total data size available (200000000), got 2147483647
+ await using var collector = await bidi.Network.AddDataCollectorAsync([DataType.Response], 200000000);
+
+ Assert.That(collector, Is.Not.Null);
+ }
+
[Test]
public async Task CanAddIntercept()
{
@@ -210,6 +221,30 @@ public async Task CanFailRequest()
Assert.That(action, Throws.TypeOf().With.Message.Contain("net::ERR_FAILED").Or.Message.Contain("NS_ERROR_ABORT"));
}
+ [Test]
+ public async Task CanGetData()
+ {
+ // Firefox doesn't like int.MaxValue as max encoded data size
+ // invalid argument: Expected "maxEncodedDataSize" to be less than the max total data size available (200000000), got 2147483647
+ await using var collector = await bidi.Network.AddDataCollectorAsync([DataType.Response], 200000000);
+
+ TaskCompletionSource responseBodyCompletionSource = new();
+
+ await using var _ = await bidi.Network.OnResponseCompletedAsync(async e =>
+ {
+ if (e.Response.Url.Contains("simpleTest.html"))
+ {
+ responseBodyCompletionSource.SetResult((string)await bidi.Network.GetDataAsync(DataType.Response, e.Request.Request));
+ }
+ });
+
+ await context.NavigateAsync(UrlBuilder.WhereIs("simpleTest.html"), new() { Wait = ReadinessState.Complete });
+
+ var responseBody = await responseBodyCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ Assert.That(responseBody, Contains.Substring("Hello WebDriver"));
+ }
+
[Test]
public void CanSetCacheBehavior()
{
diff --git a/dotnet/test/common/CustomDriverConfigs/StableChannelChromeDriver.cs b/dotnet/test/common/CustomDriverConfigs/StableChannelChromeDriver.cs
index 2cc7548bf69cc..77981244fc789 100644
--- a/dotnet/test/common/CustomDriverConfigs/StableChannelChromeDriver.cs
+++ b/dotnet/test/common/CustomDriverConfigs/StableChannelChromeDriver.cs
@@ -39,6 +39,6 @@ public StableChannelChromeDriver(ChromeDriverService service, ChromeOptions opti
public static ChromeOptions DefaultOptions
{
- get { return new ChromeOptions() { BrowserVersion = "139" }; }
+ get { return new ChromeOptions(); }
}
}