From 6188f604872518b5c400b4bba69718e26185a4ba Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:14:42 +0300 Subject: [PATCH 1/9] [dotnet] [bidi] Possibility to reset viewport --- .../src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs index 781f68d3765cf..19137043df0aa 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs @@ -18,13 +18,14 @@ // using OpenQA.Selenium.BiDi.Communication; +using System.Text.Json.Serialization; namespace OpenQA.Selenium.BiDi.BrowsingContext; internal sealed class SetViewportCommand(SetViewportParameters @params) : Command(@params, "browsingContext.setViewport"); -internal sealed record SetViewportParameters(BrowsingContext Context, Viewport? Viewport, double? DevicePixelRatio) : Parameters; +internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] Viewport? Viewport, double? DevicePixelRatio) : Parameters; public sealed class SetViewportOptions : CommandOptions { From 62db608c0d5f1e11018782c5da4f1cce90677598 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 16 Nov 2025 19:48:50 +0300 Subject: [PATCH 2/9] Always send nullable viewport --- dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs index 19137043df0aa..4edbda999829b 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs @@ -25,7 +25,7 @@ namespace OpenQA.Selenium.BiDi.BrowsingContext; internal sealed class SetViewportCommand(SetViewportParameters @params) : Command(@params, "browsingContext.setViewport"); -internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] Viewport? Viewport, double? DevicePixelRatio) : Parameters; +internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonIgnore(Condition = JsonIgnoreCondition.Never)] Viewport? Viewport, double? DevicePixelRatio) : Parameters; public sealed class SetViewportOptions : CommandOptions { From ff134d0aeeb02283ae5fe9d2bdbffa30c4e27ece Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:28:25 +0300 Subject: [PATCH 3/9] Optional --- .../BrowsingContext/BrowsingContextModule.cs | 2 +- .../BrowsingContext/SetViewportCommand.cs | 33 +++++++++++++++++-- .../BiDi/Json/Converters/OptionalConverter.cs | 31 +++++++++++++++++ dotnet/src/webdriver/BiDi/Optional.cs | 30 +++++++++++++++++ .../BrowsingContext/BrowsingContextTest.cs | 8 +++-- 5 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Optional.cs diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs index 72f2d5d995d0d..7ae9321338317 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs @@ -82,7 +82,7 @@ public async Task ReloadAsync(BrowsingContext context, ReloadOptio public async Task SetViewportAsync(BrowsingContext context, SetViewportOptions? options = null) { - var @params = new SetViewportParameters(context, options?.Viewport, options?.DevicePixelRatio); + var @params = new SetViewportParameters(context, options?.GetViewport(), options?.DevicePixelRatio); return await Broker.ExecuteCommandAsync(new SetViewportCommand(@params), options, JsonContext.SetViewportCommand, JsonContext.SetViewportResult).ConfigureAwait(false); } diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs index c045028078a5f..927da975f1ca8 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs @@ -17,16 +17,45 @@ // under the License. // +using OpenQA.Selenium.BiDi.Json.Converters; +using System.Text.Json.Serialization; + namespace OpenQA.Selenium.BiDi.BrowsingContext; internal sealed class SetViewportCommand(SetViewportParameters @params) : Command(@params, "browsingContext.setViewport"); -internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonIgnore(Condition = JsonIgnoreCondition.Never)] Viewport? Viewport, double? DevicePixelRatio) : Parameters; +internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonConverter(typeof(OptionalConverter))] Optional? Viewport, double? DevicePixelRatio) : Parameters; public sealed class SetViewportOptions : CommandOptions { - public Viewport? Viewport { get; set; } + private Optional? _viewport; + + public Viewport? Viewport + { + get + { + if (_viewport is null || !_viewport.Value.IsSet) + { + return null; + } + return _viewport.Value.Value; + } + set + { + if (value is null) + { + _viewport = new Optional(); + + } + else + { + _viewport = new Optional(value.Value); + } + } + } + + internal Optional? GetViewport() => _viewport; public double? DevicePixelRatio { get; set; } } diff --git a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs new file mode 100644 index 0000000000000..b323ca420de80 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Json.Converters; + +public sealed class OptionalConverter : JsonConverter> +{ + public override Optional Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + reader.Read(); // consume null + return new Optional(default!); + } + + T value = JsonSerializer.Deserialize(ref reader, options)!; + return new Optional(value); + } + + public override void Write(Utf8JsonWriter writer, Optional value, JsonSerializerOptions options) + { + if (!value.IsSet) + { + writer.WriteNullValue(); + return; + } + + JsonSerializer.Serialize(writer, value.Value, options); + } +} diff --git a/dotnet/src/webdriver/BiDi/Optional.cs b/dotnet/src/webdriver/BiDi/Optional.cs new file mode 100644 index 0000000000000..32776c374e40a --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Optional.cs @@ -0,0 +1,30 @@ +using System; + +namespace OpenQA.Selenium.BiDi; + +public readonly record struct Optional +{ + private readonly T _value; + public bool IsSet { get; } + + public T Value => IsSet + ? _value + : throw new InvalidOperationException("Optional has no value. Check IsSet first."); + + public Optional(T value) + { + _value = value; + IsSet = true; + } + + public bool TryGetValue(out T value) + { + value = _value; + return IsSet; + } + + public override string ToString() => IsSet ? $"Some({_value})" : "Unset"; + + // implicit conversion from T -> Optional + public static implicit operator Optional(T value) => new(value); +} diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs index a1a8cda5924b8..e274f75645863 100644 --- a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs @@ -298,13 +298,17 @@ public async Task CanCaptureScreenshotOfElement() [Test] public async Task CanSetViewport() { - await context.SetViewportAsync(new() { Viewport = new(250, 300) }); + await context.SetViewportAsync(); + + await context.SetViewportAsync(new() { Viewport = default }); + + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); } [Test] public async Task CanSetViewportWithDevicePixelRatio() { - await context.SetViewportAsync(new() { Viewport = new(250, 300), DevicePixelRatio = 5 }); + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300), DevicePixelRatio = 5 }); } [Test] From b5529fd852cff1a7691ea31235605b96e3b01e0d Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:43:21 +0300 Subject: [PATCH 4/9] Simplified --- .../BrowsingContext/BrowsingContextModule.cs | 2 +- .../BrowsingContext/SetViewportCommand.cs | 30 ++----------------- .../BrowsingContext/BrowsingContextTest.cs | 2 ++ 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs index 7ae9321338317..72f2d5d995d0d 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextModule.cs @@ -82,7 +82,7 @@ public async Task ReloadAsync(BrowsingContext context, ReloadOptio public async Task SetViewportAsync(BrowsingContext context, SetViewportOptions? options = null) { - var @params = new SetViewportParameters(context, options?.GetViewport(), options?.DevicePixelRatio); + var @params = new SetViewportParameters(context, options?.Viewport, options?.DevicePixelRatio); return await Broker.ExecuteCommandAsync(new SetViewportCommand(@params), options, JsonContext.SetViewportCommand, JsonContext.SetViewportResult).ConfigureAwait(false); } diff --git a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs index 927da975f1ca8..9021c64551106 100644 --- a/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs +++ b/dotnet/src/webdriver/BiDi/BrowsingContext/SetViewportCommand.cs @@ -25,37 +25,11 @@ namespace OpenQA.Selenium.BiDi.BrowsingContext; internal sealed class SetViewportCommand(SetViewportParameters @params) : Command(@params, "browsingContext.setViewport"); -internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonConverter(typeof(OptionalConverter))] Optional? Viewport, double? DevicePixelRatio) : Parameters; +internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonConverter(typeof(OptionalConverter))] Optional? Viewport, double? DevicePixelRatio) : Parameters; public sealed class SetViewportOptions : CommandOptions { - private Optional? _viewport; - - public Viewport? Viewport - { - get - { - if (_viewport is null || !_viewport.Value.IsSet) - { - return null; - } - return _viewport.Value.Value; - } - set - { - if (value is null) - { - _viewport = new Optional(); - - } - else - { - _viewport = new Optional(value.Value); - } - } - } - - internal Optional? GetViewport() => _viewport; + public Optional? Viewport { get; set; } public double? DevicePixelRatio { get; set; } } diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs index e274f75645863..6a8a8189e0b9a 100644 --- a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs @@ -302,6 +302,8 @@ public async Task CanSetViewport() await context.SetViewportAsync(new() { Viewport = default }); + await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly passing null + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); } From 8951622648c1a7a723f9a632689d0e7ae2fe5ec6 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:29:26 +0300 Subject: [PATCH 5/9] Format --- .../BiDi/Json/Converters/OptionalConverter.cs | 21 +++++++++++++- dotnet/src/webdriver/BiDi/Optional.cs | 29 +++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs index b323ca420de80..e9bed127f2802 100644 --- a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs +++ b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs @@ -1,3 +1,22 @@ +// +// 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; using System.Text.Json.Serialization; @@ -20,7 +39,7 @@ public override Optional Read(ref Utf8JsonReader reader, Type typeToConvert, public override void Write(Utf8JsonWriter writer, Optional value, JsonSerializerOptions options) { - if (!value.IsSet) + if (!value.HasValue) { writer.WriteNullValue(); return; diff --git a/dotnet/src/webdriver/BiDi/Optional.cs b/dotnet/src/webdriver/BiDi/Optional.cs index 32776c374e40a..e31fbbce2c9c6 100644 --- a/dotnet/src/webdriver/BiDi/Optional.cs +++ b/dotnet/src/webdriver/BiDi/Optional.cs @@ -1,3 +1,22 @@ +// +// 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; @@ -5,26 +24,24 @@ namespace OpenQA.Selenium.BiDi; public readonly record struct Optional { private readonly T _value; - public bool IsSet { get; } + public bool HasValue { get; } - public T Value => IsSet + public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value. Check IsSet first."); public Optional(T value) { _value = value; - IsSet = true; + HasValue = true; } public bool TryGetValue(out T value) { value = _value; - return IsSet; + return HasValue; } - public override string ToString() => IsSet ? $"Some({_value})" : "Unset"; - // implicit conversion from T -> Optional public static implicit operator Optional(T value) => new(value); } From 30574a5b0911f47eb9f8cd13604db14c3adeb6ee Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:32:07 +0300 Subject: [PATCH 6/9] Use TryGetValue --- .../webdriver/BiDi/Json/Converters/OptionalConverter.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs index e9bed127f2802..501490d7aa9b6 100644 --- a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs +++ b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs @@ -39,12 +39,13 @@ public override Optional Read(ref Utf8JsonReader reader, Type typeToConvert, public override void Write(Utf8JsonWriter writer, Optional value, JsonSerializerOptions options) { - if (!value.HasValue) + if (value.TryGetValue(out var v)) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + else { writer.WriteNullValue(); - return; } - - JsonSerializer.Serialize(writer, value.Value, options); } } From f7e50028771bc786fcc449fdd7d64123b40a3149 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:00:21 +0300 Subject: [PATCH 7/9] Update OptionalConverter.cs --- .../src/webdriver/BiDi/Json/Converters/OptionalConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs index 501490d7aa9b6..0fa368adce1f9 100644 --- a/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs +++ b/dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs @@ -39,9 +39,9 @@ public override Optional Read(ref Utf8JsonReader reader, Type typeToConvert, public override void Write(Utf8JsonWriter writer, Optional value, JsonSerializerOptions options) { - if (value.TryGetValue(out var v)) + if (value.TryGetValue(out var optionalValue)) { - JsonSerializer.Serialize(writer, value.Value, options); + JsonSerializer.Serialize(writer, optionalValue, options); } else { From 821941ce016d4705a7251534e3a4e33a04fbf411 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:28:44 +0300 Subject: [PATCH 8/9] Advanced tests --- .../BrowsingContext/BrowsingContextTest.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs index 6a8a8189e0b9a..a3b555ee23569 100644 --- a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs @@ -298,13 +298,34 @@ public async Task CanCaptureScreenshotOfElement() [Test] public async Task CanSetViewport() { - await context.SetViewportAsync(); + Task GetWidthAsync() => context.Script.EvaluateAsync("window.innerWidth", false); + Task GetHeightAsync() => context.Script.EvaluateAsync("window.innerHeight", false); - await context.SetViewportAsync(new() { Viewport = default }); + var defaultWidth = await GetWidthAsync(); + var defaultHeight = await GetHeightAsync(); - await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly passing null + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); + + Assert.That(await GetWidthAsync(), Is.EqualTo(250)); + Assert.That(await GetHeightAsync(), Is.EqualTo(300)); + + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); + await context.SetViewportAsync(); // Sends nothing + + Assert.That(await GetWidthAsync(), Is.EqualTo(250)); + Assert.That(await GetHeightAsync(), Is.EqualTo(300)); await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); + await context.SetViewportAsync(new() { Viewport = default }); // Sends nothing + + Assert.That(await GetWidthAsync(), Is.EqualTo(250)); + Assert.That(await GetHeightAsync(), Is.EqualTo(300)); + + await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); + await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly sends "null" + + Assert.That(await GetWidthAsync(), Is.EqualTo(defaultWidth)); + Assert.That(await GetHeightAsync(), Is.EqualTo(defaultHeight)); } [Test] From 92d78636091711df274ac0d72ae8e086542897cc Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 21 Nov 2025 22:29:57 +0300 Subject: [PATCH 9/9] Update BrowsingContextTest.cs --- dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs index a3b555ee23569..9a62004bede8b 100644 --- a/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs +++ b/dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs @@ -322,7 +322,7 @@ public async Task CanSetViewport() Assert.That(await GetHeightAsync(), Is.EqualTo(300)); await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) }); - await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly sends "null" + await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly sends "null", resetting to default Assert.That(await GetWidthAsync(), Is.EqualTo(defaultWidth)); Assert.That(await GetHeightAsync(), Is.EqualTo(defaultHeight));