diff --git a/dotnet/src/webdriver/DriverOptions.cs b/dotnet/src/webdriver/DriverOptions.cs index bfcb2107c1ea6..2de9dd2cbb4c9 100644 --- a/dotnet/src/webdriver/DriverOptions.cs +++ b/dotnet/src/webdriver/DriverOptions.cs @@ -24,44 +24,6 @@ namespace OpenQA.Selenium; -/// -/// Specifies the behavior of handling unexpected alerts in the IE driver. -/// -public enum UnhandledPromptBehavior -{ - /// - /// Indicates the behavior is not set. - /// - Default, - - /// - /// Ignore unexpected alerts, such that the user must handle them. - /// - Ignore, - - /// - /// Accept unexpected alerts. - /// - Accept, - - /// - /// Dismiss unexpected alerts. - /// - Dismiss, - - /// - /// Accepts unexpected alerts and notifies the user that the alert has - /// been accepted by throwing an - /// - AcceptAndNotify, - - /// - /// Dismisses unexpected alerts and notifies the user that the alert has - /// been dismissed by throwing an - /// - DismissAndNotify -} - /// /// Specifies the behavior of waiting for page loads in the driver. /// @@ -160,9 +122,9 @@ protected DriverOptions() /// /// Gets or sets the value for describing how unexpected alerts are to be handled in the browser. - /// Defaults to . + /// Defaults to , leaving the behavior unset. /// - public UnhandledPromptBehavior UnhandledPromptBehavior { get; set; } = UnhandledPromptBehavior.Default; + public UserPromptHandler? UnhandledPromptBehavior { get; set; } /// /// Gets or sets the value for describing how the browser is to wait for pages to load in the browser. @@ -298,7 +260,7 @@ public virtual DriverOptionsMergeResult GetMergeResult(DriverOptions other) return result; } - if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default && other.UnhandledPromptBehavior != UnhandledPromptBehavior.Default) + if (this.UnhandledPromptBehavior != other.UnhandledPromptBehavior) { result.IsMergeConflict = true; result.MergeConflictOptionName = "UnhandledPromptBehavior"; @@ -503,29 +465,11 @@ protected IWritableCapabilities GenerateDesiredCapabilities(bool isSpecification capabilities.SetCapability(CapabilityType.PageLoadStrategy, pageLoadStrategySetting); } - if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default) - { - string unhandledPropmtBehaviorSetting = "ignore"; - switch (this.UnhandledPromptBehavior) - { - case UnhandledPromptBehavior.Accept: - unhandledPropmtBehaviorSetting = "accept"; - break; + var unhandledPromptBehaviorCapability = this.UnhandledPromptBehavior?.ToCapabilities(); - case UnhandledPromptBehavior.Dismiss: - unhandledPropmtBehaviorSetting = "dismiss"; - break; - - case UnhandledPromptBehavior.AcceptAndNotify: - unhandledPropmtBehaviorSetting = "accept and notify"; - break; - - case UnhandledPromptBehavior.DismissAndNotify: - unhandledPropmtBehaviorSetting = "dismiss and notify"; - break; - } - - capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, unhandledPropmtBehaviorSetting); + if (unhandledPromptBehaviorCapability != null) + { + capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, unhandledPromptBehaviorCapability); } if (this.Proxy != null) diff --git a/dotnet/src/webdriver/UserPromptHandler.cs b/dotnet/src/webdriver/UserPromptHandler.cs new file mode 100644 index 0000000000000..e226c79aab1fa --- /dev/null +++ b/dotnet/src/webdriver/UserPromptHandler.cs @@ -0,0 +1,195 @@ +// +// 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. +// + +namespace OpenQA.Selenium; + +/// +/// Represents a WebDriver session's user prompt handler, which defines how unhandled browser prompts +/// (alerts, confirms, prompts, beforeunload dialogs, file selection dialogs) are managed during automation. +/// +/// +/// This corresponds to the W3C WebDriver unhandledPromptBehavior capability, which may be expressed +/// either as a single string applied to all prompt types, or as a per-prompt-type map. +/// +/// Available variants: +/// +/// - Wraps a single value applied to all prompt types. Create via the implicit conversion from or by constructing directly. +/// - Allows configuring per-prompt behaviors (Alert, Confirm, Prompt, BeforeUnload, File, Default). +/// +/// +/// +public abstract record UserPromptHandler +{ + private UserPromptHandler() { } + + /// + /// Converts a nullable to a instance, + /// or when is . + /// + /// The value to convert. + public static implicit operator UserPromptHandler?(UnhandledPromptBehavior? value) + => value is { } v ? new Uniform(v) : null; + + internal abstract object? ToCapabilities(); + + private static string ConvertBehaviorToString(UnhandledPromptBehavior behavior) => + behavior switch + { + UnhandledPromptBehavior.Ignore => "ignore", + UnhandledPromptBehavior.Accept => "accept", + UnhandledPromptBehavior.Dismiss => "dismiss", + UnhandledPromptBehavior.AcceptAndNotify => "accept and notify", + UnhandledPromptBehavior.DismissAndNotify => "dismiss and notify", + _ => throw new InvalidOperationException($"UnhandledPromptBehavior value '{behavior}' is not recognized."), + }; + + /// + /// Represents a user prompt handler that applies a single value + /// as the fallback default for all prompt types. + /// + /// The unhandled prompt behavior to apply. Specifies how unexpected browser prompts are handled during automation. + public sealed record Uniform(UnhandledPromptBehavior Value) : UserPromptHandler + { + internal override object? ToCapabilities() + { +#pragma warning disable CS0618 // UnhandledPromptBehavior.Default is obsolete + if (Value == UnhandledPromptBehavior.Default) + { + return null; + } +#pragma warning restore CS0618 + + return ConvertBehaviorToString(Value); + } + } + + /// + /// Represents a user prompt handler that specifies distinct values + /// for individual prompt types (alert, confirm, prompt, beforeunload, file), with a fallback default. + /// + /// Use this variant to configure distinct behaviors for alert, confirm, prompt, beforeunload, and file + /// selection dialogs encountered during browser automation. Each property allows you to control the response to a + /// specific type of unhandled prompt, enabling fine-grained handling beyond a single global setting. + public sealed record PerPromptType : UserPromptHandler + { + /// + /// Gets the behavior to use when an unexpected alert is encountered during automation, + /// or to leave it unset. + /// + public UnhandledPromptBehavior? Alert { get; init; } + + /// + /// Gets the behavior to use when a confirmation prompt is encountered, + /// or to leave it unset. + /// + public UnhandledPromptBehavior? Confirm { get; init; } + + /// + /// Gets the behavior to use when an unexpected prompt is encountered during automation, + /// or to leave it unset. + /// + public UnhandledPromptBehavior? Prompt { get; init; } + + /// + /// Gets the behavior to use when an unexpected beforeunload dialog is encountered, + /// or to leave it unset. + /// + public UnhandledPromptBehavior? BeforeUnload { get; init; } + + /// + /// Gets the behavior to use when an unexpected file selection dialog is encountered, + /// or to leave it unset. + /// + /// The "file" prompt type is respected only in WebDriver BiDi sessions. + public UnhandledPromptBehavior? File { get; init; } + + /// + /// Gets the fallback behavior to use when no specific handler is defined for a given prompt type, + /// or to leave it unset. + /// + public UnhandledPromptBehavior? Default { get; init; } + + internal override object? ToCapabilities() + { + Dictionary capabilities = []; + AddIfSet(capabilities, "alert", Alert); + AddIfSet(capabilities, "confirm", Confirm); + AddIfSet(capabilities, "prompt", Prompt); + AddIfSet(capabilities, "beforeUnload", BeforeUnload); + AddIfSet(capabilities, "file", File); + AddIfSet(capabilities, "default", Default); + return capabilities; + } + + private static void AddIfSet(Dictionary capabilities, string key, UnhandledPromptBehavior? value) + { +#pragma warning disable CS0618 // UnhandledPromptBehavior.Default is obsolete + if (value is { } v && v != UnhandledPromptBehavior.Default) + { + capabilities[key] = ConvertBehaviorToString(v); + } +#pragma warning restore CS0618 + } + } +} + +/// +/// Specifies how a WebDriver session handles an unhandled user prompt. +/// +/// +/// Corresponds to the handler values defined for the W3C WebDriver +/// unhandledPromptBehavior capability: +/// dismiss, accept, dismiss and notify, accept and notify, and ignore. +/// When no handler is configured, the spec's implicit default is . +/// +public enum UnhandledPromptBehavior +{ + /// + /// Sentinel value meaning "behavior not set". Not part of the W3C WebDriver spec. + /// + [Obsolete("Use a nullable UnhandledPromptBehavior? and pass null to leave the behavior unset. This member will be removed in v4.46.")] + Default, + + /// + /// Ignore unexpected alerts, such that the user must handle them. + /// + Ignore, + + /// + /// Accept unexpected alerts. + /// + Accept, + + /// + /// Dismiss unexpected alerts. + /// + Dismiss, + + /// + /// Accepts unexpected alerts and notifies the user that the alert has + /// been accepted by throwing an + /// + AcceptAndNotify, + + /// + /// Dismisses unexpected alerts and notifies the user that the alert has + /// been dismissed by throwing an + /// + DismissAndNotify +} diff --git a/dotnet/test/webdriver/AlertsTests.cs b/dotnet/test/webdriver/AlertsTests.cs index bd13810b1c948..49486304d0c5d 100644 --- a/dotnet/test/webdriver/AlertsTests.cs +++ b/dotnet/test/webdriver/AlertsTests.cs @@ -555,5 +555,4 @@ private Func WindowHandleCountToBe(int count) return driver.WindowHandles.Count == count; }; } - } diff --git a/dotnet/test/webdriver/UnexpectedAlertBehaviorTests.cs b/dotnet/test/webdriver/UnexpectedAlertBehaviorTests.cs index f93e1f54a113a..45e875c1a4129 100644 --- a/dotnet/test/webdriver/UnexpectedAlertBehaviorTests.cs +++ b/dotnet/test/webdriver/UnexpectedAlertBehaviorTests.cs @@ -71,56 +71,49 @@ public void CanSilentlyDismissUnhandledAlert() [Test] public void CanDismissUnhandledAlertsByDefault() { - ExecuteTestWithUnhandledPrompt(UnhandledPromptBehavior.Default, "null"); + ExecuteTestWithUnhandledPrompt(null, "null"); + } + + [Test] + public void CanDismissUnhandledAlertsViaPerType() + { + ExecuteTestWithUnhandledPrompt(new UserPromptHandler.PerPromptType + { + Alert = UnhandledPromptBehavior.Dismiss + }, "null"); + } + + [Test] + public void CanDismissUnhandledAlertsViaDefaultPerType() + { + ExecuteTestWithUnhandledPrompt(new UserPromptHandler.PerPromptType(), "null"); } [Test] [IgnoreBrowser(Browser.Safari, "Test hangs waiting for alert acknowledgement in Safari, but works in Tech Preview")] public void CanIgnoreUnhandledAlert() { - Assert.That(() => ExecuteTestWithUnhandledPrompt(UnhandledPromptBehavior.Ignore, "Text ignored"), Throws.InstanceOf().With.InnerException.InstanceOf()); + Assert.That( + () => ExecuteTestWithUnhandledPrompt(UnhandledPromptBehavior.Ignore, "Text ignored"), + Throws.InstanceOf().With.InnerException.InstanceOf()); localDriver.SwitchTo().Alert().Dismiss(); } - private void ExecuteTestWithUnhandledPrompt(UnhandledPromptBehavior behavior, string expectedAlertText) + private void ExecuteTestWithUnhandledPrompt(UserPromptHandler behavior, string expectedAlertText) { - bool silentlyHandlePrompt = behavior == UnhandledPromptBehavior.Accept || behavior == UnhandledPromptBehavior.Dismiss; - UnhandledPromptBehaviorOptions options = new UnhandledPromptBehaviorOptions(); - if (behavior != UnhandledPromptBehavior.Default) + UnhandledPromptBehaviorOptions options = new() { - options.UnhandledPromptBehavior = behavior; - } + UnhandledPromptBehavior = behavior, + }; localDriver = EnvironmentManager.Instance.CreateDriverInstance(options); localDriver.Url = alertsPage; IWebElement resultElement = localDriver.FindElement(By.Id("text")); localDriver.FindElement(By.Id("prompt-with-default")).Click(); - WaitFor(ElementTextToBeEqual(resultElement, expectedAlertText, silentlyHandlePrompt), "Did not find text"); + WaitFor(() => resultElement.Text == expectedAlertText, "Did not find text"); } - private Func ElementTextToBeEqual(IWebElement resultElement, string expectedAlertText, bool silentlyHandlePrompt) - { - return () => - { - try - { - return resultElement.Text == expectedAlertText; - } - catch (UnhandledAlertException) - { - if (!silentlyHandlePrompt) - { - throw; - } - } - catch (NoSuchElementException) - { - } - - return false; - }; - } public class UnhandledPromptBehaviorOptions : DriverOptions {