From b864e80894d8fb62a3caf93c21b0edabf153e91d Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:42:17 +0300 Subject: [PATCH 1/4] DownloadWillBeginEventArgs --- .../BrowsingContext/BrowsingContextModule.cs | 4 +-- .../DownloadWillBeginEventArgs.cs | 25 +++++++++++++++++++ .../Json/BiDiJsonSerializerContext.cs | 1 + 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 dotnet/src/webdriver/BiDi/BrowsingContext/DownloadWillBeginEventArgs.cs diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs index 5571b4fa9ff6f..17f3dfbcd5f00 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs @@ -161,12 +161,12 @@ public async Task OnLoadAsync(Action handler, Brow return await Broker.SubscribeAsync("browsingContext.load", handler, options).ConfigureAwait(false); } - public async Task OnDownloadWillBeginAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + public async Task OnDownloadWillBeginAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) { return await Broker.SubscribeAsync("browsingContext.downloadWillBegin", handler, options).ConfigureAwait(false); } - public async Task OnDownloadWillBeginAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + public async Task OnDownloadWillBeginAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) { return await Broker.SubscribeAsync("browsingContext.downloadWillBegin", handler, options).ConfigureAwait(false); } diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadWillBeginEventArgs.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadWillBeginEventArgs.cs new file mode 100644 index 0000000000000..a1db10de67a1b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadWillBeginEventArgs.cs @@ -0,0 +1,25 @@ +// +// 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; + +namespace OpenQA.Selenium.BiDi.BrowsingContext; + +public sealed record DownloadWillBeginEventArgs(BiDi BiDi, string SuggestedFilename, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) + : BrowsingContextEventArgs(BiDi, Context), IBaseNavigationInfo; diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs index 4a74468315f4c..295c78e30af9c 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -112,6 +112,7 @@ namespace OpenQA.Selenium.BiDi.Communication.Json; [JsonSerializable(typeof(BrowsingContext.TraverseHistoryResult))] [JsonSerializable(typeof(BrowsingContext.BrowsingContextInfo))] +[JsonSerializable(typeof(BrowsingContext.DownloadWillBeginEventArgs))] [JsonSerializable(typeof(BrowsingContext.HistoryUpdatedEventArgs))] [JsonSerializable(typeof(BrowsingContext.NavigationInfo))] [JsonSerializable(typeof(BrowsingContext.UserPromptOpenedEventArgs))] From f72d056e0db3d1be156833d4448fed94cb9b0230 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:54:37 +0300 Subject: [PATCH 2/4] DownloadEnd event --- .../BiDi/BrowsingContext/BrowsingContext.cs | 14 ++++++-- .../BrowsingContext/BrowsingContextModule.cs | 10 ++++++ .../BrowsingContext/DownloadEndEventArgs.cs | 35 +++++++++++++++++++ .../Json/BiDiJsonSerializerContext.cs | 1 + 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs index 4b6e3af021f78..178ba5d9519a4 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs @@ -167,16 +167,26 @@ public Task OnLoadAsync(Func handler, Subscr return BiDi.BrowsingContext.OnLoadAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); } - public Task OnDownloadWillBeginAsync(Action handler, SubscriptionOptions? options = null) + public Task OnDownloadWillBeginAsync(Action handler, SubscriptionOptions? options = null) { return BiDi.BrowsingContext.OnDownloadWillBeginAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); } - public Task OnDownloadWillBeginAsync(Func handler, SubscriptionOptions? options = null) + public Task OnDownloadWillBeginAsync(Func handler, SubscriptionOptions? options = null) { return BiDi.BrowsingContext.OnDownloadWillBeginAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); } + public Task OnDownloadEndAsync(Action handler, SubscriptionOptions? options = null) + { + return BiDi.BrowsingContext.OnDownloadEndAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnDownloadEndAsync(Func handler, SubscriptionOptions? options = null) + { + return BiDi.BrowsingContext.OnDownloadEndAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + public Task OnNavigationAbortedAsync(Action handler, SubscriptionOptions? options = null) { return BiDi.BrowsingContext.OnNavigationAbortedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs index 17f3dfbcd5f00..9e42a9178dcf0 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs @@ -171,6 +171,16 @@ public async Task OnDownloadWillBeginAsync(Action OnDownloadEndAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.downloadEnd", handler, options).ConfigureAwait(false); + } + + public async Task OnDownloadEndAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.downloadEnd", handler, options).ConfigureAwait(false); + } + public async Task OnNavigationAbortedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) { return await Broker.SubscribeAsync("browsingContext.navigationAborted", handler, options).ConfigureAwait(false); diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs new file mode 100644 index 0000000000000..00d2dd83051ff --- /dev/null +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs @@ -0,0 +1,35 @@ +// +// 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.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.BrowsingContext; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "status")] +[JsonDerivedType(typeof(DownloadCanceledEventArgs), "canceled")] +[JsonDerivedType(typeof(DownloadCompleteEventArgs), "complete")] +public abstract record DownloadEndEventArgs(BiDi BiDi, BrowsingContext Context) + : BrowsingContextEventArgs(BiDi, Context); + +public abstract record DownloadCanceledEventArgs(BiDi BiDi, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) + : DownloadEndEventArgs(BiDi, Context), IBaseNavigationInfo; + +public abstract record DownloadCompleteEventArgs(BiDi BiDi, string? Filepath, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) + : DownloadEndEventArgs(BiDi, Context), IBaseNavigationInfo; diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs index 295c78e30af9c..2fe4ef7a2b346 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -113,6 +113,7 @@ namespace OpenQA.Selenium.BiDi.Communication.Json; [JsonSerializable(typeof(BrowsingContext.BrowsingContextInfo))] [JsonSerializable(typeof(BrowsingContext.DownloadWillBeginEventArgs))] +[JsonSerializable(typeof(BrowsingContext.DownloadEndEventArgs))] [JsonSerializable(typeof(BrowsingContext.HistoryUpdatedEventArgs))] [JsonSerializable(typeof(BrowsingContext.NavigationInfo))] [JsonSerializable(typeof(BrowsingContext.UserPromptOpenedEventArgs))] From 878ea2f9fd8753dc179ab807f2417d46e2822450 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:26:35 +0300 Subject: [PATCH 3/4] Tests --- .../BrowsingContext/DownloadEndEventArgs.cs | 12 ++-- .../webdriver/BiDi/Communication/Broker.cs | 1 + .../Json/BiDiJsonSerializerContext.cs | 2 + .../DownloadEndEventArgsConverter.cs | 45 +++++++++++++ .../BrowsingContextEventsTest.cs | 65 +++++++++++++++++++ 5 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/DownloadEndEventArgsConverter.cs create mode 100644 dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs index 00d2dd83051ff..59812df106df4 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/DownloadEndEventArgs.cs @@ -18,18 +18,18 @@ // using System; -using System.Text.Json.Serialization; namespace OpenQA.Selenium.BiDi.BrowsingContext; -[JsonPolymorphic(TypeDiscriminatorPropertyName = "status")] -[JsonDerivedType(typeof(DownloadCanceledEventArgs), "canceled")] -[JsonDerivedType(typeof(DownloadCompleteEventArgs), "complete")] +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "status")] +//[JsonDerivedType(typeof(DownloadCanceledEventArgs), "canceled")] +//[JsonDerivedType(typeof(DownloadCompleteEventArgs), "complete")] public abstract record DownloadEndEventArgs(BiDi BiDi, BrowsingContext Context) : BrowsingContextEventArgs(BiDi, Context); -public abstract record DownloadCanceledEventArgs(BiDi BiDi, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) +public sealed record DownloadCanceledEventArgs(BiDi BiDi, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) : DownloadEndEventArgs(BiDi, Context), IBaseNavigationInfo; -public abstract record DownloadCompleteEventArgs(BiDi BiDi, string? Filepath, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) +public sealed record DownloadCompleteEventArgs(BiDi BiDi, string? Filepath, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) : DownloadEndEventArgs(BiDi, Context), IBaseNavigationInfo; diff --git a/dotnet/src/webdriver/BiDi/Communication/Broker.cs b/dotnet/src/webdriver/BiDi/Communication/Broker.cs index 4292af05177d6..3c549fd6195bb 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Broker.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Broker.cs @@ -97,6 +97,7 @@ internal Broker(BiDi bidi, Uri url) new Json.Converters.Polymorphic.RemoteValueConverter(), new Json.Converters.Polymorphic.RealmInfoConverter(), new Json.Converters.Polymorphic.LogEntryConverter(), + new Json.Converters.Polymorphic.DownloadEndEventArgsConverter(), // // Enumerable diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs index 2fe4ef7a2b346..27cc45303456c 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -114,6 +114,8 @@ namespace OpenQA.Selenium.BiDi.Communication.Json; [JsonSerializable(typeof(BrowsingContext.BrowsingContextInfo))] [JsonSerializable(typeof(BrowsingContext.DownloadWillBeginEventArgs))] [JsonSerializable(typeof(BrowsingContext.DownloadEndEventArgs))] +[JsonSerializable(typeof(BrowsingContext.DownloadCanceledEventArgs))] +[JsonSerializable(typeof(BrowsingContext.DownloadCompleteEventArgs))] [JsonSerializable(typeof(BrowsingContext.HistoryUpdatedEventArgs))] [JsonSerializable(typeof(BrowsingContext.NavigationInfo))] [JsonSerializable(typeof(BrowsingContext.UserPromptOpenedEventArgs))] diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/DownloadEndEventArgsConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/DownloadEndEventArgsConverter.cs new file mode 100644 index 0000000000000..b065d172b41f4 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/DownloadEndEventArgsConverter.cs @@ -0,0 +1,45 @@ +// +// 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.BrowsingContext; +using OpenQA.Selenium.BiDi.Communication.Json.Internal; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class DownloadEndEventArgsConverter : JsonConverter +{ + public override DownloadEndEventArgs? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDiscriminator("status") switch + { + "canceled" => JsonSerializer.Deserialize(ref reader, options.GetTypeInfo()), + "complete" => JsonSerializer.Deserialize(ref reader, options.GetTypeInfo()), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, DownloadEndEventArgs value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs new file mode 100644 index 0000000000000..ab744ce349306 --- /dev/null +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs @@ -0,0 +1,65 @@ +// +// 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 NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.BrowsingContext; + +class BrowsingContextEventsTest : BiDiTestFixture +{ + [Test] + public async Task CanListenDownloadWillBeginEvent() + { + await context.NavigateAsync(UrlBuilder.WhereIs("downloads/download.html"), new() { Wait = ReadinessState.Complete }); + + TaskCompletionSource tcs = new(); + + await using var subscription = await context.OnDownloadWillBeginAsync(tcs.SetResult); + + driver.FindElement(By.Id("file-1")).Click(); + + var eventArgs = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); + + Assert.That(eventArgs, Is.Not.Null); + Assert.That(eventArgs.Context, Is.EqualTo(context)); + Assert.That(eventArgs.Url, Does.EndWith("downloads/file_1.txt")); + Assert.That(eventArgs.SuggestedFilename, Is.EqualTo("file_1.txt")); + } + + [Test] + public async Task CanListenDownloadEndEvent() + { + await context.NavigateAsync(UrlBuilder.WhereIs("downloads/download.html"), new() { Wait = ReadinessState.Complete }); + + TaskCompletionSource tcs = new(); + + await using var subscription = await context.OnDownloadEndAsync(tcs.SetResult); + + driver.FindElement(By.Id("file-1")).Click(); + + var eventArgs = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); + + Assert.That(eventArgs, Is.Not.Null); + Assert.That(eventArgs.Context, Is.EqualTo(context)); + Assert.That(eventArgs, Is.TypeOf()); + Assert.That(((DownloadCompleteEventArgs)eventArgs).Filepath, Is.Not.Empty); + } +} From 11beb60ab7d303d63ba5f4009bd362a092af39de Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:54:36 +0300 Subject: [PATCH 4/4] Ignore firefox --- .../common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs index ab744ce349306..503fcaee56431 100644 --- a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs @@ -26,6 +26,7 @@ namespace OpenQA.Selenium.BiDi.BrowsingContext; class BrowsingContextEventsTest : BiDiTestFixture { [Test] + [IgnoreBrowser(Selenium.Browser.Firefox, "Not supported yet?")] public async Task CanListenDownloadWillBeginEvent() { await context.NavigateAsync(UrlBuilder.WhereIs("downloads/download.html"), new() { Wait = ReadinessState.Complete }); @@ -45,6 +46,7 @@ public async Task CanListenDownloadWillBeginEvent() } [Test] + [IgnoreBrowser(Selenium.Browser.Firefox, "Not supported yet?")] public async Task CanListenDownloadEndEvent() { await context.NavigateAsync(UrlBuilder.WhereIs("downloads/download.html"), new() { Wait = ReadinessState.Complete });