Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
<PackageVersion Include="Remotion.TypePipe" Version="$(TypePipeVersion)" />
<PackageVersion Include="Remotion.TypePipe.Development" Version="$(TypePipeVersion)" />
<PackageVersion Include="Remotion.TypePipe.Documentation" Version="$(TypePipeVersion)" />
<PackageVersion Include="Selenium.Support" Version="4.28.0" />
<PackageVersion Include="Selenium.WebDriver" Version="4.28.0" />
<PackageVersion Include="Selenium.Support" Version="4.35.0" />
<PackageVersion Include="Selenium.WebDriver" Version="4.35.0" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.0" />
<PackageVersion Include="CycloneDX" Version="5.4.0" />
<PackageVersion Include="Testcontainers" Version="4.6.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ private static bool RelevantProcessFilter (Process process)
private Mock<ITestContext> _testContext;
private Dictionary<string,object> _testContextProperties;

[OneTimeSetUp]
public void OneTimeSetUp ()
{
var webTestHelper = WebTestHelper.CreateFromConfiguration<CustomWebTestConfigurationFactory>();
if (webTestHelper.BrowserConfiguration.IsFirefox())
{
Assert.Ignore();
Copy link
Member

Choose a reason for hiding this comment

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

Please add the jira issue number where this will get fixed and a short explanation in the ignore about why it's ignored.

}
}

[SetUp]
public void SetUp ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();

Expand All @@ -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.");
}
Expand All @@ -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;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -19,24 +16,20 @@ namespace Remotion.Web.Development.WebTesting.BrowserSession;
/// </remarks>
public class BiDiBrowserLogProvider : IBrowserLogProvider, IDisposable
{
private readonly IDriver _driver;

private readonly ConcurrentQueue<BrowserLogEntry> _logEntries = new();
private readonly Subscription _eventSubscription;
private readonly TimeSpan _bidiTimeout = TimeSpan.FromSeconds(1);
Copy link
Member

Choose a reason for hiding this comment

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

This is duplicated with BrowserSessionBase. should we move this into the IBrowserSession (or IBidiConnectionManager) interface to avoid duplication?


public BiDiBrowserLogProvider (IDriver driver)
public BiDiBrowserLogProvider (IBrowserSession browserSession)
Copy link
Member

Choose a reason for hiding this comment

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

design question: should we have an interface smaller than IBrowserSession (IBidiConnectionService) or some such?

{
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(browserSession);

// 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();
}
browserSession.OpenBidiConnection();
_eventSubscription = browserSession.BiDiConnection.Log.OnEntryAddedAsync(entry => _logEntries.Enqueue(new BrowserLogEntry(entry)), new SubscriptionOptions
{
Timeout = _bidiTimeout
}).GetAwaiter().GetResult();
}

/// <inheritdoc />
public IReadOnlyCollection<BrowserLogEntry> GetBrowserLogs ()
Expand All @@ -52,10 +45,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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -87,9 +92,26 @@ 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();

// 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();
Comment on lines +106 to +114
Copy link
Member

Choose a reason for hiding this comment

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

I think I remember a discussion about making this conditional based on there actually being a user propmt expected right now. Did we nix this completely or just deferr? if it's deferred we should create a jira ticket and refernece the issue number here with a short explaintion that this will get aligned with the correct handling semantics in a later iteration.

Copy link
Contributor

Choose a reason for hiding this comment

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

scope based handling of Userpromt in another task, add comment with ticket

}

public IDriver Driver
Expand Down Expand Up @@ -137,9 +159,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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void ApplyDefaultWebTestFeatures (
ArgumentNullException.ThrowIfNull(features);
ArgumentNullException.ThrowIfNull(browserSession);

features.Set<IBrowserLogProvider>(new BiDiBrowserLogProvider(browserSession.Driver));
features.Set<IBrowserLogProvider>(new BiDiBrowserLogProvider(browserSession));
}

public FirefoxBrowserSession (Coypu.BrowserSession value, IFirefoxConfiguration browserConfiguration, int driverProcessId, bool headless)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using System.Collections.Generic;
using Coypu;
using OpenQA.Selenium.BiDi;

namespace Remotion.Web.Development.WebTesting.BrowserSession
{
Expand All @@ -33,6 +34,14 @@ public interface IBrowserSession : IDisposable
/// <inheritdoc cref="Coypu.BrowserSession.Driver"/>
IDriver Driver { get; }

BiDi BiDiConnection { get; }

/// <summary>
/// Opens a Bidi Connection <see href="https://www.selenium.dev/documentation/webdriver/bidi/"/>.
/// Note: In order to use Bidi you need to enable UseWebSocketUrl in the browser options for this session.
/// </summary>
void OpenBidiConnection ();

/// <summary>
/// Indicates if the web browser is running without a user interfaces (headless mode).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<object>)jsExecutor.ExecuteScript("return [arguments[0].scrollLeft, arguments[0].scrollTop];", (IWebElement)element.Native);
var rawData = (IReadOnlyList<object>?)jsExecutor.ExecuteScript("return [arguments[0].scrollLeft, arguments[0].scrollTop];", (IWebElement)element.Native);
Assertion.IsNotNull(rawData, "Failed to retrieve the scroll position.");

return new Point((int)(long)rawData[0], (int)(long)rawData[1]);
}

Expand Down