diff --git a/Build/Build.cs b/Build/Build.cs
index 87eba6bdae..060eea7d85 100644
--- a/Build/Build.cs
+++ b/Build/Build.cs
@@ -325,7 +325,7 @@ public override void ConfigureTestMatrix (TestMatricesBuilder builder)
{ AnyOs, Firefox, NET8_0, Release, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
{ AnyOs, Edge, NET8_0, Release, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
{ AnyOs, Chrome, NET10_0, Debug, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
- // { AnyOs, Firefox, NET10_0, Release, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
+ { AnyOs, Firefox, NET10_0, Release, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
{ AnyOs, Edge, NET10_0, Release, x64, NoDB, EnforcedLocalMachine(Docker_Win_NET10_0) },
},
allowEmpty: true);
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4902089ff3..01547335e3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -41,8 +41,8 @@
-
-
+
+
diff --git a/Remotion/Web/Development.WebTesting.IntegrationTests/WebTestHelperTest.cs b/Remotion/Web/Development.WebTesting.IntegrationTests/WebTestHelperTest.cs
index 5ef630ddb9..c19e607a6d 100644
--- a/Remotion/Web/Development.WebTesting.IntegrationTests/WebTestHelperTest.cs
+++ b/Remotion/Web/Development.WebTesting.IntegrationTests/WebTestHelperTest.cs
@@ -60,6 +60,18 @@ private static bool RelevantProcessFilter (Process process)
private Mock _testContext;
private Dictionary _testContextProperties;
+ [OneTimeSetUp]
+ public void OneTimeSetUp ()
+ {
+ var webTestHelper = WebTestHelper.CreateFromConfiguration();
+ //TODO: RM-9595 fix and reenable WebTestHelperTests for firefox
+ //WebTestHelperTests currently cause firefox with Bidi active to hang
+ if (webTestHelper.BrowserConfiguration.IsFirefox())
+ {
+ Assert.Ignore();
+ }
+ }
+
[SetUp]
public void SetUp ()
{
diff --git a/Remotion/Web/Development.WebTesting/Accessibility/AccessibilityAnalyzer.cs b/Remotion/Web/Development.WebTesting/Accessibility/AccessibilityAnalyzer.cs
index 77df75eae3..3e19f477b7 100644
--- a/Remotion/Web/Development.WebTesting/Accessibility/AccessibilityAnalyzer.cs
+++ b/Remotion/Web/Development.WebTesting/Accessibility/AccessibilityAnalyzer.cs
@@ -22,6 +22,7 @@
using Microsoft.Extensions.Logging;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
+using Remotion.Utilities;
using Remotion.Web.Development.WebTesting.Accessibility.Implementation;
using Remotion.Web.Development.WebTesting.Utilities;
@@ -208,7 +209,9 @@ public AccessibilityResult Analyze ([NotNull] string cssSelector, [CanBeNull] Ti
[NotNull]
public AccessibilityResult Analyze ([CanBeNull] TimeSpan? timeout = null)
{
- var outerFrame = (string)JsExecutor.ExecuteScript("return self.name;");
+ var outerFrame = (string?)JsExecutor.ExecuteScript("return self.name;");
+ Assertion.IsNotNull(outerFrame, "Failed to retrieve the current frame's name.");
+
if (outerFrame != "")
WebDriver.SwitchTo().DefaultContent();
@@ -233,11 +236,10 @@ private AccessibilityResult GetAccessibilityResult (string? cssSelector, TimeSpa
var axeRunFunctionCall = BuildAxeRunFunctionCall(cssSelector);
- string result;
+ string? result;
using (new PerformanceTimer(Logger, "Accessibility analysis has been performed."))
{
- result = (string)JsExecutor.ExecuteAsyncScript(axeRunFunctionCall);
-
+ result = (string?)JsExecutor.ExecuteAsyncScript(axeRunFunctionCall);
if (result == null)
throw new InvalidOperationException("Could not obtain accessibility analysis result.");
}
@@ -249,7 +251,10 @@ private AccessibilityResult GetAccessibilityResult (string? cssSelector, TimeSpa
private bool AxeIsInjected ()
{
- return (bool)JsExecutor.ExecuteScript("return (typeof axe !== 'undefined')");
+ var result = (bool?)JsExecutor.ExecuteScript("return (typeof axe !== 'undefined')");
+ Assertion.IsNotNull(result, "Failed to determine if aXe library is injected.");
+
+ return result.Value;
}
///
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/BiDiBrowserLogProvider.cs b/Remotion/Web/Development.WebTesting/BrowserSession/BiDiBrowserLogProvider.cs
index 069ac7f3a1..ab368f782b 100644
--- a/Remotion/Web/Development.WebTesting/BrowserSession/BiDiBrowserLogProvider.cs
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/BiDiBrowserLogProvider.cs
@@ -3,10 +3,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using Coypu;
-using OpenQA.Selenium;
using OpenQA.Selenium.BiDi;
-using OpenQA.Selenium.BiDi.Modules.BrowsingContext;
namespace Remotion.Web.Development.WebTesting.BrowserSession;
@@ -19,24 +16,19 @@ namespace Remotion.Web.Development.WebTesting.BrowserSession;
///
public class BiDiBrowserLogProvider : IBrowserLogProvider, IDisposable
{
- private readonly IDriver _driver;
-
private readonly ConcurrentQueue _logEntries = new();
private readonly Subscription _eventSubscription;
- public BiDiBrowserLogProvider (IDriver driver)
+ public BiDiBrowserLogProvider (IBidiConnectionProvider bidiProvider)
{
- ArgumentNullException.ThrowIfNull(driver);
-
- _driver = driver;
-
- var bidi = ((IWebDriver)driver.Native).AsBiDiAsync().GetAwaiter().GetResult();
- _eventSubscription = bidi.Log.OnEntryAddedAsync(entry => _logEntries.Enqueue(new BrowserLogEntry(entry))).GetAwaiter().GetResult();
+ ArgumentNullException.ThrowIfNull(bidiProvider);
- // Accept all user prompts as they come up - IWebTestHelper.AcceptPossibleModalDialog() does not work with BiDi
- // because the WebTest-Thread is not continued when a user prompt is shown.
- bidi.BrowsingContext.OnUserPromptOpenedAsync(args => args.BiDi.BrowsingContext.HandleUserPromptAsync(args.Context, new HandleUserPromptOptions { Accept = true })).Wait();
- }
+ bidiProvider.OpenBidiConnection();
+ _eventSubscription = bidiProvider.BiDiConnection.Log.OnEntryAddedAsync(entry => _logEntries.Enqueue(new BrowserLogEntry(entry)), new SubscriptionOptions
+ {
+ Timeout = bidiProvider.DefaultBidiTimeout
+ }).GetAwaiter().GetResult();
+}
///
public IReadOnlyCollection GetBrowserLogs ()
@@ -52,10 +44,13 @@ public void ResetBrowserLogs ()
public void Dispose ()
{
- _eventSubscription.DisposeAsync().AsTask().GetAwaiter().GetResult();
-
- var driver = (IWebDriver)_driver.Native;
- var biDi = driver.AsBiDiAsync().GetAwaiter().GetResult();
- biDi.DisposeAsync().GetAwaiter().GetResult();
+ try
+ {
+ _eventSubscription.DisposeAsync().AsTask().GetAwaiter().GetResult();
+ }
+ catch (Exception)
+ {
+ //ignored
+ }
}
}
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/BrowserLogEntry.cs b/Remotion/Web/Development.WebTesting/BrowserSession/BrowserLogEntry.cs
index bb7dc5ccbe..3d4d7259b6 100644
--- a/Remotion/Web/Development.WebTesting/BrowserSession/BrowserLogEntry.cs
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/BrowserLogEntry.cs
@@ -17,7 +17,7 @@
using System;
using System.Globalization;
using JetBrains.Annotations;
-using OpenQA.Selenium.BiDi.Modules.Log;
+using OpenQA.Selenium.BiDi.Log;
using OpenQA.Selenium;
namespace Remotion.Web.Development.WebTesting.BrowserSession
@@ -47,21 +47,21 @@ private static LogLevel GetLogLevel ([NotNull] Level bidiLogLevel)
{
return bidiLogLevel switch
{
- OpenQA.Selenium.BiDi.Modules.Log.Level.Debug => LogLevel.Debug,
- OpenQA.Selenium.BiDi.Modules.Log.Level.Info => LogLevel.Info,
- OpenQA.Selenium.BiDi.Modules.Log.Level.Warn => LogLevel.Warning,
- OpenQA.Selenium.BiDi.Modules.Log.Level.Error => LogLevel.Severe,
+ OpenQA.Selenium.BiDi.Log.Level.Debug => LogLevel.Debug,
+ OpenQA.Selenium.BiDi.Log.Level.Info => LogLevel.Info,
+ OpenQA.Selenium.BiDi.Log.Level.Warn => LogLevel.Warning,
+ OpenQA.Selenium.BiDi.Log.Level.Error => LogLevel.Severe,
_ => LogLevel.Off
};
}
- public BrowserLogEntry ([NotNull] Entry logEntry)
- : this(GetLogLevel(logEntry.Level), logEntry.Text, logEntry.Timestamp.DateTime)
+ public BrowserLogEntry ([NotNull] OpenQA.Selenium.BiDi.Log.LogEntry logEntry)
+ : this(GetLogLevel(logEntry.Level), logEntry.Text ?? "", logEntry.Timestamp.DateTime)
{
ArgumentNullException.ThrowIfNull(logEntry);
}
- public BrowserLogEntry ([NotNull] LogEntry logEntry)
+ public BrowserLogEntry ([NotNull] OpenQA.Selenium.LogEntry logEntry)
: this(logEntry.Level, logEntry.Message, logEntry.Timestamp)
{
ArgumentNullException.ThrowIfNull(logEntry);
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/BrowserSessionBase.cs b/Remotion/Web/Development.WebTesting/BrowserSession/BrowserSessionBase.cs
index 2ad00b1a0f..b923f5e303 100644
--- a/Remotion/Web/Development.WebTesting/BrowserSession/BrowserSessionBase.cs
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/BrowserSessionBase.cs
@@ -21,6 +21,8 @@
using Coypu;
using JetBrains.Annotations;
using OpenQA.Selenium;
+using OpenQA.Selenium.BiDi;
+using OpenQA.Selenium.BiDi.BrowsingContext;
using Remotion.Web.Development.WebTesting.Utilities;
using Remotion.Web.Development.WebTesting.WebDriver.Configuration;
@@ -43,15 +45,18 @@ public static void ApplyCommonWebTestFeatureDefaults (
}
private readonly TimeSpan _browserProcessesShutdownTime = TimeSpan.FromSeconds(60);
+ private readonly TimeSpan _bidiTimeout = TimeSpan.FromSeconds(1);
private readonly T _browserConfiguration;
private readonly Coypu.BrowserSession _value;
private readonly int _driverProcessID;
private readonly bool _headless;
- private bool _isDisposed;
-
private readonly WebTestFeatureCollection _features;
+ private BiDi? _bidiConnection;
+ private Subscription? _promptSubscription;
+ private bool _isDisposed;
+
protected BrowserSessionBase (
[NotNull] Coypu.BrowserSession value,
[NotNull] T browserConfiguration,
@@ -87,9 +92,30 @@ protected T BrowserConfiguration
get { return _browserConfiguration; }
}
- public void AcceptModalDialog (Options? options = null)
+ /// />
+ public BiDi BiDiConnection => _bidiConnection
+ ?? throw new InvalidOperationException("Call 'OpenBidiConnection' before accessing 'BiDiConnection'.");
+
+ ///
+ [System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_bidiConnection))]
+ public void OpenBidiConnection ()
{
- _value.AcceptModalDialog(options);
+ if (_bidiConnection != null)
+ return;
+
+ _bidiConnection = ((OpenQA.Selenium.WebDriver)Driver.Native).AsBiDiAsync().GetAwaiter().GetResult();
+
+ // TODO: RM-9596
+ // This should be moved into a scope based PromptHandler that can be used during a test to enable and disable handling of prompts similar to how it works for non bidi prompt handling.
+ // Accept all user prompts as they come up - IWebTestHelper.AcceptPossibleModalDialog() does not work with BiDi
+ // because the WebTest-Thread is not continued when a user prompt is shown.
+ _promptSubscription = _bidiConnection.BrowsingContext.OnUserPromptOpenedAsync(args =>
+ args.BiDi.BrowsingContext.HandleUserPromptAsync(args.Context, new HandleUserPromptOptions { Accept = true, Timeout = _bidiTimeout}).GetAwaiter().GetResult(),
+ new BrowsingContextsSubscriptionOptions(new SubscriptionOptions
+ {
+ Timeout = _bidiTimeout
+ }))
+ .GetAwaiter().GetResult();
}
public IDriver Driver
@@ -137,9 +163,27 @@ public virtual void Dispose ()
return;
_isDisposed = true;
-
_features.Dispose();
+ try
+ {
+ _promptSubscription?.DisposeAsync().AsTask().GetAwaiter().GetResult();
+ }
+ catch (Exception)
+ {
+ //ignored
+ }
+
+ try
+ {
+ _bidiConnection?.DisposeAsync().AsTask().GetAwaiter().GetResult();
+ }
+ catch (Exception)
+ {
+ //ignored
+ }
+
+
// Get processes for driver and main browser, as well as the sub processes of the browser
var driverProcess = FindDriverProcess();
var browserProcess = FindBrowserProcess();
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/Firefox/FirefoxBrowserSession.cs b/Remotion/Web/Development.WebTesting/BrowserSession/Firefox/FirefoxBrowserSession.cs
index f1cc3b1579..1a9011b876 100644
--- a/Remotion/Web/Development.WebTesting/BrowserSession/Firefox/FirefoxBrowserSession.cs
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/Firefox/FirefoxBrowserSession.cs
@@ -31,7 +31,7 @@ public static void ApplyDefaultWebTestFeatures (
ArgumentNullException.ThrowIfNull(features);
ArgumentNullException.ThrowIfNull(browserSession);
- features.Set(new BiDiBrowserLogProvider(browserSession.Driver));
+ features.Set(new BiDiBrowserLogProvider(browserSession));
}
public FirefoxBrowserSession (Coypu.BrowserSession value, IFirefoxConfiguration browserConfiguration, int driverProcessId, bool headless)
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/IBidiConnectionProvider.cs b/Remotion/Web/Development.WebTesting/BrowserSession/IBidiConnectionProvider.cs
new file mode 100644
index 0000000000..8b977f8151
--- /dev/null
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/IBidiConnectionProvider.cs
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: (c) RUBICON IT GmbH, www.rubicon.eu
+// SPDX-License-Identifier: LGPL-2.1-or-later
+using System;
+using System.Diagnostics.CodeAnalysis;
+using OpenQA.Selenium.BiDi;
+
+namespace Remotion.Web.Development.WebTesting.BrowserSession;
+///
+/// Provides access to the bidirectional webdriver connection to a browser
+///
+public interface IBidiConnectionProvider: IDisposable
+{
+ ///
+ /// Opens a Bidi Connection .
+ /// Note: In order to use Bidi you need to enable UseWebSocketUrl in the browser options for this session.
+ ///
+ [MemberNotNull(nameof(BiDiConnection))]
+ void OpenBidiConnection ();
+
+ ///
+ /// Gets the open Bidi connection. Trying to access this before calling
+ /// should result in a
+ ///
+ BiDi BiDiConnection { get; }
+
+ ///
+ /// Used for timeouts for opening connections and interactions with the browser
+ ///
+ TimeSpan DefaultBidiTimeout => TimeSpan.FromSeconds(1);
+}
diff --git a/Remotion/Web/Development.WebTesting/BrowserSession/IBrowserSession.cs b/Remotion/Web/Development.WebTesting/BrowserSession/IBrowserSession.cs
index d48978dc23..ad72a0c222 100644
--- a/Remotion/Web/Development.WebTesting/BrowserSession/IBrowserSession.cs
+++ b/Remotion/Web/Development.WebTesting/BrowserSession/IBrowserSession.cs
@@ -17,13 +17,14 @@
using System;
using System.Collections.Generic;
using Coypu;
+using OpenQA.Selenium.BiDi;
namespace Remotion.Web.Development.WebTesting.BrowserSession
{
///
/// Represents a wrapper around a which has additional cleanup routines via .
///
- public interface IBrowserSession : IDisposable
+ public interface IBrowserSession : IBidiConnectionProvider, IDisposable
{
///
/// The of this .
diff --git a/Remotion/Web/Development.WebTesting/CoypuElementScopeSelectExtensions.cs b/Remotion/Web/Development.WebTesting/CoypuElementScopeSelectExtensions.cs
index 5b26a8ff53..5d5948bef5 100644
--- a/Remotion/Web/Development.WebTesting/CoypuElementScopeSelectExtensions.cs
+++ b/Remotion/Web/Development.WebTesting/CoypuElementScopeSelectExtensions.cs
@@ -22,6 +22,7 @@
using Microsoft.Extensions.Logging;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
+using Remotion.Utilities;
using Remotion.Web.Contracts.DiagnosticMetadata;
using Remotion.Web.Development.WebTesting.ControlObjects;
using Remotion.Web.Development.WebTesting.Utilities;
@@ -64,7 +65,10 @@ public static OptionDefinition GetSelectedOption ([NotNull] this ElementScope sc
var select = new SelectElement(webElement);
var selectedOption = select.SelectedOption;
- return new OptionDefinition(selectedOption.GetAttribute("value"), -1, selectedOption.Text, selectedOption.Selected);
+ var itemID = selectedOption.GetAttribute("value");
+ Assertion.IsNotNull(itemID, "Failed to retrieve the 'value' attribute from the selected option.");
+
+ return new OptionDefinition(itemID, -1, selectedOption.Text, selectedOption.Selected);
});
}
diff --git a/Remotion/Web/Development.WebTesting/Utilities/ElementScopeExtensions.cs b/Remotion/Web/Development.WebTesting/Utilities/ElementScopeExtensions.cs
index 19c90ceee9..19854b22f2 100644
--- a/Remotion/Web/Development.WebTesting/Utilities/ElementScopeExtensions.cs
+++ b/Remotion/Web/Development.WebTesting/Utilities/ElementScopeExtensions.cs
@@ -21,6 +21,7 @@
using Coypu;
using JetBrains.Annotations;
using OpenQA.Selenium;
+using Remotion.Utilities;
using Remotion.Web.Development.WebTesting.ScreenshotCreation;
using Remotion.Web.Development.WebTesting.ScreenshotCreation.Drawing;
using Remotion.Web.Development.WebTesting.ScreenshotCreation.Resolvers;
@@ -42,8 +43,9 @@ public static Point GetScrollPosition ([NotNull] this ElementScope element)
var driver = ((IWrapsDriver)element.Native).WrappedDriver;
var jsExecutor = (IJavaScriptExecutor)driver;
- var rawData =
- (IReadOnlyList