Skip to content
97 changes: 79 additions & 18 deletions dotnet/src/webdriver/DriverOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@

namespace OpenQA.Selenium;

public abstract record UnhandledPromptBehaviorOption
{
public static implicit operator UnhandledPromptBehaviorOption(UnhandledPromptBehavior? value)
=> Single(value);

public static UnhandledPromptBehaviorOption Single(UnhandledPromptBehavior? value)
=> new UnhandledPromptBehaviorSingleOption(value);

public static UnhandledPromptBehaviorOption Multi(UnhandledPromptBehavior? alert = null,
UnhandledPromptBehavior? confirm = null,
UnhandledPromptBehavior? prompt = null,
UnhandledPromptBehavior? beforeUnload = null,
UnhandledPromptBehavior? @default = null)
=> new UnhandledPromptBehaviorMultiOption() with { Alert = alert, Confirm = confirm, Prompt = prompt, BeforeUnload = beforeUnload, Default = @default };
}

public sealed record UnhandledPromptBehaviorSingleOption(UnhandledPromptBehavior? Value) : UnhandledPromptBehaviorOption;

public sealed record UnhandledPromptBehaviorMultiOption : UnhandledPromptBehaviorOption
{
public UnhandledPromptBehavior? Alert { get; set; }

public UnhandledPromptBehavior? Confirm { get; set; }

public UnhandledPromptBehavior? Prompt { get; set; }

public UnhandledPromptBehavior? BeforeUnload { get; set; }

public UnhandledPromptBehavior? Default { get; set; }
}

/// <summary>
/// Specifies the behavior of handling unexpected alerts in the IE driver.
/// </summary>
Expand Down Expand Up @@ -164,7 +195,7 @@ protected DriverOptions()
/// Gets or sets the value for describing how unexpected alerts are to be handled in the browser.
/// Defaults to <see cref="UnhandledPromptBehavior.Default"/>.
/// </summary>
public UnhandledPromptBehavior UnhandledPromptBehavior { get; set; } = UnhandledPromptBehavior.Default;
public UnhandledPromptBehaviorOption? UnhandledPromptBehavior { get; set; }

/// <summary>
/// Gets or sets the value for describing how the browser is to wait for pages to load in the browser.
Expand Down Expand Up @@ -303,7 +334,7 @@ public virtual DriverOptionsMergeResult GetMergeResult(DriverOptions other)
return result;
}

if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default && other.UnhandledPromptBehavior != UnhandledPromptBehavior.Default)
if (this.UnhandledPromptBehavior is not null && other.UnhandledPromptBehavior is not null)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, != operator for records?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does feel like this method should tolerate it when both values are the same.

However, we should investigate the history of this method before making those decisions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand it as don't send anything over wire if default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we should investigate the history of this method before making those decisions.

Oh yes, we should understand the purpose of this method, not obvious to me at glance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimevans do you know?

{
result.IsMergeConflict = true;
result.MergeConflictOptionName = "UnhandledPromptBehavior";
Expand Down Expand Up @@ -508,29 +539,59 @@ protected IWritableCapabilities GenerateDesiredCapabilities(bool isSpecification
capabilities.SetCapability(CapabilityType.PageLoadStrategy, pageLoadStrategySetting);
}

if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default)
[return: NotNullIfNotNull(nameof(behavior))]
static string? UnhandledPromptBehaviorToString(UnhandledPromptBehavior? behavior) => behavior switch
{
string unhandledPropmtBehaviorSetting = "ignore";
switch (this.UnhandledPromptBehavior)
Selenium.UnhandledPromptBehavior.Ignore => "ignore",
Selenium.UnhandledPromptBehavior.Accept => "accept",
Selenium.UnhandledPromptBehavior.Dismiss => "dismiss",
Selenium.UnhandledPromptBehavior.AcceptAndNotify => "accept and notify",
Selenium.UnhandledPromptBehavior.DismissAndNotify => "dismiss and notify",
_ => null
};

if (this.UnhandledPromptBehavior is UnhandledPromptBehaviorSingleOption singleOption)
{
var stringValue = UnhandledPromptBehaviorToString(singleOption.Value);

if (stringValue is not null)
{
case UnhandledPromptBehavior.Accept:
unhandledPropmtBehaviorSetting = "accept";
break;
capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, stringValue);
}
}
else if (this.UnhandledPromptBehavior is UnhandledPromptBehaviorMultiOption multiOption)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should follow our usual pattern here to have a ToDictionary() method directly on the UnhandledPromptBehaviorMultiOption type:

public Dictionary<string, string> ToDictionary()
{
    Dictionary<string, string> multiOptionDictionary = [];

    if (Alert is not Selenium.UnhandledPromptBehavior.Default)
    {
        multiOptionDictionary["alert"] = UnhandledPromptBehaviorToString(Alert);
    }

    if (Confirm is not Selenium.UnhandledPromptBehavior.Default)
    {
        multiOptionDictionary["confirm"] = UnhandledPromptBehaviorToString(Confirm);
    }

    if (Prompt is not Selenium.UnhandledPromptBehavior.Default)
    {
        multiOptionDictionary["prompt"] = UnhandledPromptBehaviorToString(Prompt);
    }

    if (BeforeUnload is not Selenium.UnhandledPromptBehavior.Default)
    {
        multiOptionDictionary["beforeUnload"] = UnhandledPromptBehaviorToString(BeforeUnload);
    }

    if (Default is not Selenium.UnhandledPromptBehavior.Default)
    {
        multiOptionDictionary["default"] = UnhandledPromptBehaviorToString(Default);
    }

    return multiOptionDictionary;
}

and then to call that method here

else if (this.UnhandledPromptBehavior is UnhandledPromptBehaviorMultiOption multiOption)
{
    multiOptionDictionary = multiOption.ToDictionary();
    if (multiOptionDictionary.Count != 0)
    {
        capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, multiOptionDictionary);
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also allows us to move UnhandledPromptBehaviorToString to the UnhandledPromptBehaviorOption, cleaning up this 150-line method.

{
Dictionary<string, string> multiOptionDictionary = [];

case UnhandledPromptBehavior.Dismiss:
unhandledPropmtBehaviorSetting = "dismiss";
break;
if (multiOption.Alert is not null)
{
multiOptionDictionary["alert"] = UnhandledPromptBehaviorToString(multiOption.Alert);
}

case UnhandledPromptBehavior.AcceptAndNotify:
unhandledPropmtBehaviorSetting = "accept and notify";
break;
if (multiOption.Confirm is not null)
{
multiOptionDictionary["confirm"] = UnhandledPromptBehaviorToString(multiOption.Confirm);
}

case UnhandledPromptBehavior.DismissAndNotify:
unhandledPropmtBehaviorSetting = "dismiss and notify";
break;
if (multiOption.Prompt is not null)
{
multiOptionDictionary["prompt"] = UnhandledPromptBehaviorToString(multiOption.Prompt);
}

capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, unhandledPropmtBehaviorSetting);
if (multiOption.BeforeUnload is not null)
{
multiOptionDictionary["beforeUnload"] = UnhandledPromptBehaviorToString(multiOption.BeforeUnload);
}

if (multiOption.Default is not null)
{
multiOptionDictionary["default"] = UnhandledPromptBehaviorToString(multiOption.Default);
}

if (multiOptionDictionary.Count != 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to guard against {}? It feels like we should transparently sent this to the server.

What happens if we do this, anyway?

{
capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, multiOptionDictionary);
}
}

if (this.Proxy != null)
Expand Down