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 5571b4fa9ff6f..9e42a9178dcf0 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs @@ -161,16 +161,26 @@ 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); } + public async Task 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..59812df106df4 --- /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; + +namespace OpenQA.Selenium.BiDi.BrowsingContext; + +// 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 sealed record DownloadCanceledEventArgs(BiDi BiDi, BrowsingContext Context, Navigation? Navigation, DateTimeOffset Timestamp, string Url) + : DownloadEndEventArgs(BiDi, Context), IBaseNavigationInfo; + +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/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/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 4a74468315f4c..27cc45303456c 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -112,6 +112,10 @@ namespace OpenQA.Selenium.BiDi.Communication.Json; [JsonSerializable(typeof(BrowsingContext.TraverseHistoryResult))] [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..503fcaee56431 --- /dev/null +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextEventsTest.cs @@ -0,0 +1,67 @@ +// +// 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] + [IgnoreBrowser(Selenium.Browser.Firefox, "Not supported yet?")] + 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] + [IgnoreBrowser(Selenium.Browser.Firefox, "Not supported yet?")] + 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); + } +}