refactor: Remove Selenium tests and testing infrastructure
What changed, and why it matters
This commit removes the old Selenium browser-automation test suite and its supporting infrastructure from the BTCPay Server project. It deletes test files, package references, Docker services, and CI job definitions, and updates two comments in production view files that previously mentioned Selenium. There is no change to the actual application code that users interact with, and no security fix or vulnerability is introduced.
No security action required. Reviewers may want to confirm that equivalent Playwright tests cover the scenarios previously exercised by the removed Selenium tests, but this is a quality/testing concern rather than a security one.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit is a pure refactoring/deletion of the legacy Selenium-based end-to-end testing stack. It removes SeleniumTester.cs, SeleniumTests.cs, Selenium package references from the test project, Selenium helper extension methods, the selenium_tests CircleCI job, the selenium Docker Compose service across all test compose files, and updates two Razor view comments from Selenium-specific wording to a generic explanation for the async script attribute. No runtime code paths in BTCPayServer are modified.
Changed components
BTCPayServer.Tests (test project only).circleci/config.ymlBTCPayServer.Tests/docker-compose*.ymlBTCPayServer/Views/UIInvoice/ListInvoices.cshtml (comment only)BTCPayServer/Views/UIWallets/WalletTransactions.cshtml (comment only)Inspect captured patch +2 / −1069
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7853f8d..4a85f90 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -36,14 +36,6 @@ jobs:
docker run --rm -v btcpayservertests_tests_datadir:/data -v /tmp/Artifacts:/host alpine sh -c "cp -r /data/. /host/"
- store_artifacts:
path: /tmp/Artifacts
- selenium_tests:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- command: |
- cd .circleci && ./run-tests.sh "Selenium=Selenium"
integration_tests:
machine:
image: ubuntu-2004:2024.11.1
@@ -96,7 +88,6 @@ workflows:
- fast_tests
- playwright_tests
- playwright_2_tests
- - selenium_tests
- integration_tests
publish:
jobs:
diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs
index f8ed348..cafd27d 100644
--- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs
+++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs
@@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json.Linq;
-using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
diff --git a/BTCPayServer.Tests/BTCPayServer.Tests.csproj b/BTCPayServer.Tests/BTCPayServer.Tests.csproj
index d66800b..aa2e75f 100644
--- a/BTCPayServer.Tests/BTCPayServer.Tests.csproj
+++ b/BTCPayServer.Tests/BTCPayServer.Tests.csproj
@@ -44,9 +44,6 @@
<PackageReference Include="Microsoft.Playwright" Version="1.52.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.16" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
- <PackageReference Include="Selenium.Support" Version="4.1.1" />
- <PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
- <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="133.0.6943.5300" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs
index 86c42aa..5274dd0 100644
--- a/BTCPayServer.Tests/Extensions.cs
+++ b/BTCPayServer.Tests/Extensions.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,9 +9,6 @@ using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Support.Extensions;
-using OpenQA.Selenium.Support.UI;
using Xunit;
namespace BTCPayServer.Tests
@@ -39,25 +35,6 @@ namespace BTCPayServer.Tests
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
public static string ToJson(this object o) => JsonConvert.SerializeObject(o, Formatting.None, JsonSettings);
- public static void LogIn(this SeleniumTester s, string email)
- {
- s.Driver.FindElement(By.Id("Email")).SendKeys(email);
- s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
- s.Driver.FindElement(By.Id("LoginButton")).Click();
- s.Driver.AssertNoError();
- }
-
- public static void AssertNoError(this IWebDriver driver)
- {
- if (driver.PageSource.Contains("alert-danger"))
- {
- foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
- Assert.False(dangerAlert.Displayed, $"No alert should be displayed, but found this on {driver.Url}: {dangerAlert.Text}");
- }
- Assert.DoesNotContain("errors", driver.Url);
- Assert.DoesNotContain("Error", driver.Title, StringComparison.OrdinalIgnoreCase);
- }
-
public static string NormalizeWhitespaces(this string input) =>
string.Concat((input??"").Where(c => !char.IsWhiteSpace(c)));
@@ -97,166 +74,5 @@ namespace BTCPayServer.Tests
var vr = Assert.IsType<ViewResult>(result);
return Assert.IsType<T>(vr.Model);
}
-
- // Sometimes, selenium is flaky...
- public static IWebElement FindElementUntilNotStaled(this IWebDriver driver, By by, Action<IWebElement> act)
- {
-retry:
- try
- {
- var el = driver.FindElement(by);
- act(el);
- return el;
- }
- catch (StaleElementReferenceException)
- {
- goto retry;
- }
- }
-
- public static void AssertElementNotFound(this IWebDriver driver, By by)
- {
- DateTimeOffset now = DateTimeOffset.Now;
- var wait = SeleniumTester.ImplicitWait;
-
- while (DateTimeOffset.UtcNow - now < wait)
- {
- try
- {
- var webElement = driver.FindElement(by);
- if (!webElement.Displayed)
- return;
- }
- catch (NoSuchWindowException)
- {
- return;
- }
- catch (NoSuchElementException)
- {
- return;
- }
- Thread.Sleep(50);
- }
- Assert.Fail("Elements was found");
- }
-
- public static void UntilJsIsReady(this WebDriverWait wait)
- {
- wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
- wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return typeof(jQuery) === 'undefined' || jQuery.active === 0").Equals(true));
- }
-
- // Open collapse via JS, because if we click the link it triggers the toggle animation.
- // This leads to Selenium trying to click the button while it is moving resulting in an error.
- public static void ToggleCollapse(this IWebDriver driver, string collapseId)
- {
- driver.ExecuteJavaScript($"document.getElementById('{collapseId}').classList.add('show')");
- }
-
- public static void SetAttribute(this IWebDriver driver, string element, string attribute, string value)
- {
- driver.ExecuteJavaScript($"document.getElementById('{element}').setAttribute('{attribute}', '{value}')");
- }
- public static void InvokeJSFunction(this IWebDriver driver, string element, string funcName)
- {
- driver.ExecuteJavaScript($"document.getElementById('{element}').{funcName}()");
- }
-
- public static void WaitWalletTransactionsLoaded(this IWebDriver driver)
- {
- var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
- wait.UntilJsIsReady();
- wait.Until(d => d.WaitForElement(By.CssSelector("#WalletTransactions[data-loaded='true']")));
- }
-
- public static IWebElement WaitForElement(this IWebDriver driver, By selector)
- {
- var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
- wait.UntilJsIsReady();
-
- var el = driver.FindElement(selector);
- wait.Until(d => el.Displayed);
-
- return el;
- }
-
- public static void FillIn(this IWebElement el, string text)
- {
- el.Clear();
- el.SendKeys(text);
- }
-
- public static void ScrollTo(this IWebDriver driver, IWebElement element)
- {
- driver.ExecuteJavaScript("arguments[0].scrollIntoView();", element);
- }
-
- public static void ScrollTo(this IWebDriver driver, By selector)
- {
- ScrollTo(driver, driver.FindElement(selector));
- }
-
- public static void WaitUntilAvailable(this IWebDriver driver, By selector, TimeSpan? waitTime = null)
- {
- // Try fast path
- var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
- try
- {
- var el = driver.FindElement(selector);
- wait.Until(_ => el.Displayed && el.Enabled);
- return;
- }
- catch { }
-
- // Sometimes, selenium complain, so we enter hack territory
- wait.UntilJsIsReady();
-
- int retriesLeft = 4;
-retry:
- try
- {
- var el = driver.FindElement(selector);
- wait.Until(_ => el.Displayed && el.Enabled);
- driver.ScrollTo(selector);
- driver.FindElement(selector);
- }
- catch (NoSuchElementException) when (retriesLeft > 0)
- {
- retriesLeft--;
- if (waitTime != null)
- Thread.Sleep(waitTime.Value);
- goto retry;
- }
- wait.UntilJsIsReady();
- }
-
- public static void WaitForAndClick(this IWebDriver driver, By selector)
- {
- driver.WaitUntilAvailable(selector);
- driver.FindElement(selector).Click();
- }
-
- public static bool ElementDoesNotExist(this IWebDriver driver, By selector)
- {
- Assert.Throws<NoSuchElementException>(
- [DebuggerStepThrough]
- () =>
- {
- driver.FindElement(selector);
- });
-
- return true;
- }
-
- public static bool SetCheckbox(this IWebDriver driver, By selector, bool value)
- {
- var element = driver.FindElement(selector);
- if (value != element.Selected)
- {
- driver.WaitForAndClick(selector);
- return true;
- }
- return false;
- }
}
}
diff --git a/BTCPayServer.Tests/FeatureTests/MultisigTests.cs b/BTCPayServer.Tests/FeatureTests/MultisigTests.cs
index 6812874..dea1e57 100644
--- a/BTCPayServer.Tests/FeatureTests/MultisigTests.cs
+++ b/BTCPayServer.Tests/FeatureTests/MultisigTests.cs
@@ -13,7 +13,6 @@ using BTCPayServer.Views.Wallets;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
-using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
diff --git a/BTCPayServer.Tests/PSBTTests.cs b/BTCPayServer.Tests/PSBTTests.cs
index 8284210..48512f5 100644
--- a/BTCPayServer.Tests/PSBTTests.cs
+++ b/BTCPayServer.Tests/PSBTTests.cs
@@ -10,8 +10,6 @@ using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Support.Extensions;
using Xunit;
using Xunit.Abstractions;
diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md
index 6911849..d383011 100644
--- a/BTCPayServer.Tests/README.md
+++ b/BTCPayServer.Tests/README.md
@@ -97,18 +97,3 @@ Or, uncheck the box that says, "Break when this exception type is thrown".
`docker-compose -f docker-compose.altcoins.yml up dev`
If you still have issues, try to restart docker.
-
-### How to run the Selenium test with a browser?
-
-Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser.
-
-To switch back to headless mode (recommended) you can run `dotnet user-secrets remove RunSeleniumInBrowser`.
-
-### Session not created: This version of ChromeDriver only supports Chrome version 88
-
-When you run tests for selenium, you may end up with this error.
-This happen when we update the selenium packages on BTCPay Server while you did not update your chrome version.
-
-If you want to use a older chrome driver on [this page](https://chromedriver.chromium.org/downloads) then point to it with
-
-`dotnet user-secrets set ChromeDriverDirectory "path/to/the/driver/directory"`
diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs
deleted file mode 100644
index 4e649c7..0000000
--- a/BTCPayServer.Tests/SeleniumTester.cs
+++ /dev/null
@@ -1,699 +0,0 @@
-using System;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using BTCPayServer.Abstractions.Extensions;
-using BTCPayServer.Abstractions.Models;
-using BTCPayServer.Lightning;
-using BTCPayServer.Lightning.CLightning;
-using BTCPayServer.Views.Manage;
-using BTCPayServer.Views.Server;
-using BTCPayServer.Views.Stores;
-using BTCPayServer.Views.Wallets;
-using Microsoft.Extensions.Configuration;
-using NBitcoin;
-using NBitcoin.RPC;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Chrome;
-using OpenQA.Selenium.Support.UI;
-using Xunit;
-
-namespace BTCPayServer.Tests
-{
- public class SeleniumTester : IDisposable
- {
- public IWebDriver Driver { get; set; }
- public ServerTester Server { get; set; }
- public WalletId WalletId { get; set; }
-
- public string StoreId { get; set; }
-
- public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5);
-
- public async Task StartAsync()
- {
- Server.PayTester.NoCSP = true;
- await Server.StartAsync();
-
- var windowSize = (Width: 1200, Height: 1000);
- var builder = new ConfigurationBuilder();
- builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
- var config = builder.Build();
-
- // Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser
- var runInBrowser = config["RunSeleniumInBrowser"] == "true";
- // Reset this using `dotnet user-secrets remove RunSeleniumInBrowser`
-
- var chromeDriverPath = config["ChromeDriverDirectory"] ?? (Server.PayTester.InContainer ? "/usr/bin" : TestUtils.TestDirectory);
-
- var options = new ChromeOptions();
- if (!runInBrowser)
- {
- options.AddArguments("headless");
- }
- options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
- options.AddArgument("shm-size=2g");
- options.AddArgument("start-maximized");
- options.AddArgument("disable-search-engine-choice-screen");
- if (Server.PayTester.InContainer)
- {
- // Shot in the dark to fix https://stackoverflow.com/questions/53902507/unknown-error-session-deleted-because-of-page-crash-from-unknown-error-cannot
- options.AddArgument("--disable-dev-shm-usage");
- Driver = new OpenQA.Selenium.Remote.RemoteWebDriver(new Uri("http://selenium:4444/wd/hub"), new RemoteSessionSettings(options));
- var containerIp = File.ReadAllText("/etc/hosts").Split('\n', StringSplitOptions.RemoveEmptyEntries).Last()
- .Split('\t', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
- TestLogs.LogInformation($"Selenium: Container's IP {containerIp}");
- }
- else
- {
- var cds = ChromeDriverService.CreateDefaultService(chromeDriverPath);
- cds.EnableVerboseLogging = true;
- cds.Port = Utils.FreeTcpPort();
- cds.HostName = "127.0.0.1";
- cds.Start();
- Driver = new ChromeDriver(cds, options,
- // A bit less than test timeout
- TimeSpan.FromSeconds(50));
- }
-
- ServerUri = Server.PayTester.ServerUri;
- Driver.Manage().Window.Maximize();
-
- TestLogs.LogInformation($"Selenium: Using {Driver.GetType()}");
- TestLogs.LogInformation($"Selenium: Browsing to {ServerUri}");
- TestLogs.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
- GoToRegister();
- Driver.AssertNoError();
- }
-
- public void PayInvoice(bool mine = false, decimal? amount = null)
- {
- if (amount is not null)
- {
- try
- {
- Driver.FindElement(By.Id("test-payment-amount")).Clear();
- }
- // Sometimes the element is not available after a window switch... retry
- catch (StaleElementReferenceException)
- {
- Driver.FindElement(By.Id("test-payment-amount")).Clear();
- }
- Driver.FindElement(By.Id("test-payment-amount")).SendKeys(amount.ToString());
- }
- Driver.WaitUntilAvailable(By.Id("FakePayment"));
- Driver.FindElement(By.Id("FakePayment")).Click();
- TestUtils.Eventually(() =>
- {
- Driver.WaitForElement(By.Id("CheatSuccessMessage"));
- });
- if (mine)
- {
- MineBlockOnInvoiceCheckout();
- }
- }
-
- public void MineBlockOnInvoiceCheckout()
- {
-retry:
- try
- {
- Driver.FindElement(By.CssSelector("#mine-block button")).Click();
- }
- catch (StaleElementReferenceException)
- {
- goto retry;
- }
- }
-
- /// <summary>
- /// Use this ServerUri when trying to browse with selenium
- /// Because for some reason, the selenium container can't resolve the tests container domain name
- /// </summary>
- public Uri ServerUri;
- public IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
- {
- return FindAlertMessage(new[] { severity });
- }
- public IWebElement FindAlertMessage(params StatusMessageModel.StatusSeverity[] severity)
- {
- int retry = 0;
- retry:
- try
- {
- var className = string.Join(", ", severity.Select(statusSeverity => $".alert-{StatusMessageModel.ToString(statusSeverity)}"));
- IWebElement el;
- try
- {
- var elements = Driver.FindElements(By.CssSelector(className));
- el = elements.FirstOrDefault(e => e.Displayed);
- if (el is null)
- el = elements.FirstOrDefault();
- if (el is null)
- el = Driver.WaitForElement(By.CssSelector(className));
- }
- catch (NoSuchElementException)
- {
- el = Driver.WaitForElement(By.CssSelector(className));
- }
- if (el is null)
- throw new NoSuchElementException($"Unable to find {className}");
- if (!el.Displayed)
- throw new ElementNotVisibleException($"{className} is present, but not displayed: {el.GetAttribute("id")} - Text: {el.Text}");
- return el;
- }
- // Selenium sometimes sucks...
- catch (StaleElementReferenceException) when (retry < 5)
- {
- retry++;
- goto retry;
- }
- }
-
- public string Link(string relativeLink)
- {
- return ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
- }
-
- public void GoToRegister()
- {
- Driver.Navigate().GoToUrl(Link("/register"));
- }
-
- public string RegisterNewUser(bool isAdmin = false)
- {
- var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
- TestLogs.LogInformation($"User: {usr} with password 123456");
- Driver.FindElement(By.Id("Email")).SendKeys(usr);
- Driver.FindElement(By.Id("Password")).SendKeys("123456");
- Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
- if (isAdmin)
- Driver.FindElement(By.Id("IsAdmin")).Click();
- Driver.FindElement(By.Id("RegisterButton")).Click();
- Driver.AssertNoError();
- CreatedUser = usr;
- Password = "123456";
- IsAdmin = isAdmin;
- return usr;
- }
- string CreatedUser;
-
- public string Password { get; private set; }
- public bool IsAdmin { get; private set; }
-
- public TestAccount AsTestAccount()
- {
- return new TestAccount(Server) { StoreId = StoreId, Email = CreatedUser, Password = Password, RegisterDetails = new Models.AccountViewModels.RegisterViewModel() { Password = "123456", Email = CreatedUser }, IsAdmin = IsAdmin };
- }
-
- public (string storeName, string storeId) CreateNewStore(bool keepId = true)
- {
- // If there's no store yet, there is no dropdown toggle
- if (Driver.PageSource.Contains("id=\"StoreSelectorToggle\""))
- {
- Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
- }
- GoToUrl("/stores/create");
- var name = "Store" + RandomUtils.GetUInt64();
- TestLogs.LogInformation($"Created store {name}");
- Driver.WaitForElement(By.Id("Name")).SendKeys(name);
- var rateSource = new SelectElement(Driver.FindElement(By.Id("PreferredExchange")));
- Assert.Equal("Recommendation (Kraken)", rateSource.SelectedOption.Text);
- rateSource.SelectByText("CoinGecko");
- Driver.WaitForElement(By.Id("Create")).Click();
- Driver.FindElement(By.Id("menu-item-General")).Click();
- var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
- if (keepId)
- StoreId = storeId;
- return (name, storeId);
- }
-
- public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool? importkeys = null, bool isHotWallet = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
- {
- var isImport = !string.IsNullOrEmpty(seed);
- GoToWalletSettings(cryptoCode);
- // Replace previous wallet case
- if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
- {
- Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
- Driver.WaitForElement(By.Id("ChangeWalletLink")).Click();
- Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE");
- Driver.FindElement(By.Id("ConfirmContinue")).Click();
- }
-
- if (isImport)
- {
- TestLogs.LogInformation("Progressing with existing seed");
- Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
- Driver.FindElement(By.Id("ImportSeedLink")).Click();
- Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
- Driver.SetCheckbox(By.Id("SavePrivateKeys"), isHotWallet);
- }
- else
- {
- var option = isHotWallet ? "Hotwallet" : "Watchonly";
- TestLogs.LogInformation($"Generating new seed ({option})");
- Driver.FindElement(By.Id("GenerateWalletLink")).Click();
- Driver.FindElement(By.Id($"Generate{option}Link")).Click();
- }
-
- Driver.FindElement(By.Id("ScriptPubKeyType")).Click();
- Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
-
- Driver.ToggleCollapse("AdvancedSettings");
- if (importkeys is bool v)
- Driver.SetCheckbox(By.Id("ImportKeysToRPC"), v);
- Driver.FindElement(By.Id("Continue")).Click();
-
- if (isImport)
- {
- // Confirm addresses
- Driver.FindElement(By.Id("Confirm")).Click();
- }
- else
- {
- // Seed backup
- FindAlertMessage();
- if (string.IsNullOrEmpty(seed))
- {
- seed = Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
- }
-
- // Confirm seed backup
- Driver.FindElement(By.Id("confirm")).Click();
- Driver.FindElement(By.Id("submit")).Click();
- }
-
- WalletId = new WalletId(StoreId, cryptoCode);
- return new Mnemonic(seed);
- }
-
- /// <summary>
- /// Assume to be in store's settings
- /// </summary>
- /// <param name="cryptoCode"></param>
- /// <param name="derivationScheme"></param>
- public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "tpubD6NzVbkrYhZ4XxNXjYTcRujMc8z8734diCthtFGgDMimbG5hUsKBuSTCuUyxWL7YwP7R4A5StMTRQiZnb6vE4pdHWPgy9hbiHuVJfBMumUu-[legacy]")
- {
- if (!Driver.PageSource.Contains($"Setup {cryptoCode} Wallet"))
- {
- GoToWalletSettings(cryptoCode);
- }
-
- Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
- Driver.FindElement(By.Id("ImportXpubLink")).Click();
- Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
- Driver.FindElement(By.Id("Continue")).Click();
- Driver.FindElement(By.Id("Confirm")).Click();
- FindAlertMessage();
- }
-
- public void AddLightningNode()
- {
- AddLightningNode(null, true);
- }
-
- public void AddLightningNode(string connectionType = null, bool test = true)
- {
- var cryptoCode = "BTC";
- if (!Driver.PageSource.Contains("Connect to a Lightning node"))
- {
- GoToLightningSettings();
- }
-
- var connectionString = connectionType switch
- {
- LightningConnectionType.CLightning =>
- $"type=clightning;server={((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri}",
- LightningConnectionType.LndREST =>
- $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true",
- _ => null
- };
-
- if (connectionString == null)
- {
- Assert.True(Driver.FindElement(By.Id("LightningNodeType-Internal")).Enabled, "Usage of the internal Lightning node is disabled.");
- Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Internal\"]")).Click();
- }
- else
- {
- Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
- Driver.WaitForElement(By.Id("ConnectionString")).Clear();
- Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
- if (test)
- {
- Driver.FindElement(By.Id("test")).Click();
- Assert.Contains("Connection to the Lightning node successful.", FindAlertMessage().Text);
- }
- }
-
- ClickPagePrimary();
- Assert.Contains($"{cryptoCode} Lightning node updated.", FindAlertMessage().Text);
-
- var enabled = Driver.FindElement(By.Id($"{cryptoCode}LightningEnabled"));
- if (enabled.Selected == false)
- {
- enabled.Click();
- ClickPagePrimary();
- Assert.Contains($"{cryptoCode} Lightning settings successfully updated", FindAlertMessage().Text);
- }
- }
-
- public Logging.ILog TestLogs => Server.TestLogs;
- public void ClickOnAllSectionLinks()
- {
- var links = Driver.FindElements(By.CssSelector("#SectionNav .nav-link")).Select(c => c.GetAttribute("href")).ToList();
- Driver.AssertNoError();
- foreach (var l in links)
- {
- TestLogs.LogInformation($"Checking no error on {l}");
- Driver.Navigate().GoToUrl(l);
- Driver.AssertNoError();
- }
- }
-
- public void Dispose()
- {
- if (Driver != null)
- {
- try
- {
- Driver.Quit();
- }
- catch
- {
- // ignored
- }
-
- Driver.Dispose();
- }
-
- Server?.Dispose();
- }
-
- internal void AssertNotFound()
- {
- Assert.Contains("404 - Page not found</h1>", Driver.PageSource);
- }
-
- internal void AssertAccessDenied()
- {
- Assert.Contains("- Denied</h", Driver.PageSource);
- }
-
- public void GoToHome()
- {
- Driver.Navigate().GoToUrl(ServerUri);
- if (Driver.PageSource.Contains("id=\"SkipWizard\""))
- {
- Driver.FindElement(By.Id("SkipWizard")).Click();
- }
- }
-
- public void Logout()
- {
- if (!Driver.PageSource.Contains("id=\"Nav-Logout\""))
- GoToUrl("/account");
- Driver.FindElement(By.Id("menu-item-Account")).Click();
- Driver.FindElement(By.Id("Nav-Logout")).Click();
- }
-
- public void LogIn()
- {
- LogIn(CreatedUser, "123456");
- }
- public void LogIn(string user, string password = "123456")
- {
- Driver.FindElement(By.Id("Email")).SendKeys(user);
- Driver.FindElement(By.Id("Password")).SendKeys(password);
- Driver.FindElement(By.Id("LoginButton")).Click();
- }
-
- public void GoToStore(StoreNavPages storeNavPage = StoreNavPages.General)
- {
- GoToStore(null, storeNavPage);
- }
-
- public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.General)
- {
- if (storeId is not null)
- {
- GoToUrl($"/stores/{storeId}/");
- StoreId = storeId;
- if (WalletId != null)
- WalletId = new WalletId(storeId, WalletId.CryptoCode);
- }
-
- if (storeNavPage != StoreNavPages.General)
- {
- Driver.FindElement(By.Id($"menu-item-{StoreNavPages.General}")).Click();
- }
- Driver.FindElement(By.Id($"menu-item-{storeNavPage}")).Click();
- }
-
- public void GoToWalletSettings(string cryptoCode = "BTC")
- {
- Driver.FindElement(By.CssSelector($"[data-testid=\"Wallet-{cryptoCode}\"] a")).Click();
- if (Driver.PageSource.Contains($"id=\"menu-item-Settings-{cryptoCode}\""))
- {
- Driver.FindElement(By.Id($"menu-item-Settings-{cryptoCode}")).Click();
- }
- }
-
- public void GoToLightningSettings(string cryptoCode = "BTC")
- {
- Driver.FindElement(By.CssSelector($"[data-testid=\"Lightning-{cryptoCode}\"]")).Click();
- // if Lightning is already set up we need to navigate to the settings
- if (Driver.PageSource.Contains($"id=\"menu-item-LightningSettings-{cryptoCode}\""))
- {
- Driver.FindElement(By.Id($"menu-item-LightningSettings-{cryptoCode}")).Click();
- }
- }
-
- public void SelectStoreContext(string storeId)
- {
- Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
- Driver.FindElement(By.Id($"StoreSelectorMenuItem-{storeId}")).Click();
- }
-
- public void GoToInvoiceCheckout(string invoiceId = null)
- {
- invoiceId ??= InvoiceId;
- Driver.FindElement(By.Id("menu-item-Invoices")).Click();
- Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click();
- CheckForJSErrors();
- Driver.WaitUntilAvailable(By.Id("Checkout"));
- }
-
- public void GoToInvoice(string id)
- {
- GoToUrl($"/invoices/{id}/");
- }
-
- public void GoToInvoices(string storeId = null)
- {
- if (storeId is null)
- {
- Driver.FindElement(By.Id("menu-item-Invoices")).Click();
- }
- else
- {
- GoToUrl(storeId == null ? "/invoices/" : $"/stores/{storeId}/invoices/");
- StoreId = storeId;
- }
- }
-
- public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
- {
- Driver.WaitForAndClick(By.Id("menu-item-Account"));
- Driver.WaitForAndClick(By.Id("Nav-ManageAccount"));
- if (navPages != ManageNavPages.Index)
- {
- Driver.WaitForAndClick(By.Id($"menu-item-{navPages.ToString()}"));
- }
- }
-
- public void GoToLogin()
- {
- GoToUrl("/login");
- }
-
- public string CreateInvoice(decimal? amount = 100,
- string currency = "USD",
- string refundEmail = "",
- string defaultPaymentMethod = null,
- StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
- )
- {
- return CreateInvoice(null, amount, currency, refundEmail, defaultPaymentMethod, expectedSeverity);
- }
-
- public string CreateInvoice(
- string storeId,
- decimal? amount = 100,
- string currency = "USD",
- string refundEmail = "",
- string defaultPaymentMethod = null,
- StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
- )
- {
- GoToInvoices(storeId);
-
- ClickPagePrimary();
- if (amount is decimal v)
- Driver.FindElement(By.Id("Amount")).SendKeys(v.ToString(CultureInfo.InvariantCulture));
- var currencyEl = Driver.FindElement(By.Id("Currency"));
- currencyEl.Clear();
- currencyEl.SendKeys(currency);
- Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
- if (defaultPaymentMethod is not null)
- new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod);
- ClickPagePrimary();
-
- var statusElement = FindAlertMessage(expectedSeverity);
- var inv = expectedSeverity == StatusMessageModel.StatusSeverity.Success ? statusElement.Text.Split(" ")[1] : null;
- InvoiceId = inv;
- TestLogs.LogInformation($"Created invoice {inv}");
- return inv;
- }
- string InvoiceId;
-
- public async Task<string> FundStoreWallet(WalletId walletId = null, int coins = 1, decimal denomination = 1m)
- {
- walletId ??= WalletId;
- GoToWallet(walletId, WalletsNavPages.Receive);
- var addressStr = Driver.FindElement(By.Id("Address")).GetAttribute("data-text");
- var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
- for (var i = 0; i < coins; i++)
- {
- bool mined = false;
-retry:
- try
- {
- await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination));
- }
- catch (RPCException) when (!mined)
- {
- mined = true;
- await Server.ExplorerNode.GenerateAsync(1);
- goto retry;
- }
- }
- Driver.Navigate().Refresh();
- Driver.FindElement(By.Id("CancelWizard")).Click();
- return addressStr;
- }
-
- private void CheckForJSErrors()
- {
- //wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
- // var errorStrings = new List<string>
- // {
- // "SyntaxError",
- // "EvalError",
- // "ReferenceError",
- // "RangeError",
- // "TypeError",
- // "URIError"
- // };
- //
- // var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
- //
- // if (jsErrors.Any())
- // {
- // TestLogs.LogInformation("JavaScript error(s):" + Environment.NewLine + jsErrors.Aggregate("", (s, entry) => s + entry.Message + Environment.NewLine));
- // }
- // Assert.Empty(jsErrors);
-
- }
-
- public void GoToWallet(WalletId walletId = null, WalletsNavPages navPages = WalletsNavPages.Send)
- {
- walletId ??= WalletId;
- Driver.Navigate().GoToUrl(new Uri(ServerUri, $"wallets/{walletId}"));
- if (navPages == WalletsNavPages.PSBT)
- {
- Driver.FindElement(By.Id($"menu-item-Send-{walletId.CryptoCode}")).Click();
- Driver.FindElement(By.Id("PSBT")).Click();
- }
- else if (navPages != WalletsNavPages.Transactions)
- {
- Driver.FindElement(By.Id($"menu-item-{navPages}-{walletId.CryptoCode}")).Click();
- }
- }
-
- public void GoToUrl(string relativeUrl)
- {
- Driver.Navigate().GoToUrl(new Uri(ServerUri, relativeUrl));
- }
-
- public void GoToServer(ServerNavPages navPages = ServerNavPages.Policies)
- {
- Driver.FindElement(By.Id("menu-item-Policies")).Click();
- if (navPages != ServerNavPages.Policies)
- {
- Driver.FindElement(By.Id($"menu-item-{navPages}")).Click();
- }
- }
-
- public void AddUserToStore(string storeId, string email, string role)
- {
- if (Driver.FindElements(By.Id("AddUser")).Count == 0)
- {
- GoToStore(storeId, StoreNavPages.Users);
- }
- Driver.FindElement(By.Id("Email")).SendKeys(email);
- new SelectElement(Driver.FindElement(By.Id("Role"))).SelectByValue(role);
- Driver.FindElement(By.Id("AddUser")).Click();
- Assert.Contains("The user has been added successfully", FindAlertMessage().Text);
- }
-
- public void AssertPageAccess(bool shouldHaveAccess, string url)
- {
- GoToUrl(url);
- Assert.DoesNotMatch("404 - Page not found</h", Driver.PageSource);
- if (shouldHaveAccess)
- {
- Assert.DoesNotMatch("- Denied</h", Driver.PageSource);
- // check associated link is active if present
- var sidebarLink = Driver.FindElements(By.CssSelector($"#mainNav a[href=\"{url}\"]")).FirstOrDefault();
- if (sidebarLink != null)
- {
- Assert.Contains("active", sidebarLink.GetAttribute("class"));
- }
- }
- else
- Assert.Contains("- Denied</h", Driver.PageSource);
- }
-
- public (string appName, string appId) CreateApp(string type, string name = null)
- {
- if (string.IsNullOrEmpty(name))
- name = $"{type}-{Guid.NewGuid().ToString()[..14]}";
- Driver.FindElement(By.Id($"menu-item-CreateApp-{type}")).Click();
- Driver.FindElement(By.Name("AppName")).SendKeys(name);
- ClickPagePrimary();
- Assert.Contains("App successfully created", FindAlertMessage().Text);
- var appId = Driver.Url.Split('/')[4];
- return (name, appId);
- }
-
- public void ClickPagePrimary()
- {
- try
- {
- Driver.FindElement(By.Id("page-primary")).Click();
- }
- catch (NoSuchElementException)
- {
- Driver.WaitForAndClick(By.Id("page-primary"));
- }
- }
-
- public void ClickCancel()
- {
- Driver.FindElement(By.Id("CancelWizard")).Click();
- }
- }
-}
diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs
deleted file mode 100644
index 3df7bf7..0000000
--- a/BTCPayServer.Tests/SeleniumTests.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Security.Cryptography;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using BTCPayServer.Abstractions.Models;
-using BTCPayServer.Client;
-using BTCPayServer.Client.Models;
-using BTCPayServer.Data;
-using BTCPayServer.Lightning;
-using BTCPayServer.NTag424;
-using BTCPayServer.Payments;
-using BTCPayServer.Services;
-using BTCPayServer.Services.Invoices;
-using BTCPayServer.Services.Rates;
-using BTCPayServer.Services.Wallets;
-using BTCPayServer.Views.Manage;
-using BTCPayServer.Views.Server;
-using BTCPayServer.Views.Stores;
-using BTCPayServer.Views.Wallets;
-using ExchangeSharp;
-using LNURL;
-using Microsoft.EntityFrameworkCore;
-using NBitcoin;
-using NBitcoin.DataEncoders;
-using NBitcoin.Payment;
-using NBXplorer.Models;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Support.Extensions;
-using OpenQA.Selenium.Support.UI;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace BTCPayServer.Tests
-{
- [Trait("Selenium", "Selenium")]
- [Collection(nameof(NonParallelizableCollectionDefinition))]
- public class ChromeTests : UnitTestBase
- {
- private const int TestTimeout = TestUtils.TestTimeout;
-
- public ChromeTests(ITestOutputHelper helper) : base(helper)
- {
- }
-
- private string RandomBytes(int count)
- {
- var c = RandomNumberGenerator.GetBytes(count);
- return Encoders.Hex.EncodeData(c);
- }
-
- // For god know why, selenium have problems clicking on the save button, resulting in ultimate hacks
- // to make it works.
- private void SudoForceSaveLightningSettingsRightNowAndFast(SeleniumTester s, string cryptoCode)
- {
- int maxAttempts = 5;
-retry:
- s.ClickPagePrimary();
- try
- {
- Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
- }
- catch (NoSuchElementException) when (maxAttempts > 0)
- {
- maxAttempts--;
- goto retry;
- }
- }
-
- private static string AssertUrlHasPairingCode(SeleniumTester s)
- {
- var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
- Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
- var pairingCode = regex.Groups[1].Value;
- return pairingCode;
- }
-
- private void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
- {
- s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
- var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
- amountElement.Clear();
- amountElement.SendKeys(amount.ToString(CultureInfo.InvariantCulture));
- var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
- if (checkboxElement.Selected != subtract)
- {
- checkboxElement.Click();
- }
- }
- }
-}
diff --git a/BTCPayServer.Tests/TestUtils.cs b/BTCPayServer.Tests/TestUtils.cs
index 65ed36d..1f2cbf8 100644
--- a/BTCPayServer.Tests/TestUtils.cs
+++ b/BTCPayServer.Tests/TestUtils.cs
@@ -6,7 +6,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-using OpenQA.Selenium;
using Xunit;
using Xunit.Sdk;
@@ -95,10 +94,6 @@ namespace BTCPayServer.Tests
act();
break;
}
- catch (WebDriverException) when (!cts.Token.IsCancellationRequested)
- {
- cts.Token.WaitHandle.WaitOne(500);
- }
catch (XunitException) when (!cts.Token.IsCancellationRequested)
{
cts.Token.WaitHandle.WaitOne(500);
diff --git a/BTCPayServer.Tests/UnitTestBase.cs b/BTCPayServer.Tests/UnitTestBase.cs
index 76fbdd0..6edc0eb 100644
--- a/BTCPayServer.Tests/UnitTestBase.cs
+++ b/BTCPayServer.Tests/UnitTestBase.cs
@@ -95,10 +95,6 @@ namespace BTCPayServer.Tests
{
return new ServerTester(scope, newDb, TestLogs, TestLogProvider, CreateNetworkProvider());
}
- public SeleniumTester CreateSeleniumTester([CallerMemberNameAttribute] string scope = null, bool newDb = false)
- {
- return new SeleniumTester() { Server = new ServerTester(scope, newDb, TestLogs, TestLogProvider, CreateNetworkProvider()) };
- }
public PlaywrightTester CreatePlaywrightTester([CallerMemberNameAttribute] string scope = null, bool newDb = false)
{
return new PlaywrightTester() { Server = new ServerTester(scope, newDb, TestLogs, TestLogProvider, CreateNetworkProvider()) };
diff --git a/BTCPayServer.Tests/UtilitiesTests.cs b/BTCPayServer.Tests/UtilitiesTests.cs
index 2ab1cf3..f3e9e4d 100644
--- a/BTCPayServer.Tests/UtilitiesTests.cs
+++ b/BTCPayServer.Tests/UtilitiesTests.cs
@@ -26,9 +26,6 @@ using Microsoft.Extensions.FileSystemGlobbing;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using OpenQA.Selenium;
-using OpenQA.Selenium.Chrome;
-using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
using static System.Net.Mime.MediaTypeNames;
diff --git a/BTCPayServer.Tests/docker-compose.altcoins.yml b/BTCPayServer.Tests/docker-compose.altcoins.yml
index 654455a..273e7e0 100644
--- a/BTCPayServer.Tests/docker-compose.altcoins.yml
+++ b/BTCPayServer.Tests/docker-compose.altcoins.yml
@@ -37,7 +37,6 @@ services:
- "80"
depends_on:
- dev
- - selenium
extra_hosts:
- "tests:127.0.0.1"
networks:
@@ -92,16 +91,6 @@ services:
- customer_lnd
- merchant_lnd
- selenium:
- image: selenium/standalone-chrome:125.0
- extra_hosts:
- - "tests:172.23.0.18"
- expose:
- - "4444"
- networks:
- default:
- custom:
-
mailpit:
image: axllent/mailpit:v1.27
ports:
diff --git a/BTCPayServer.Tests/docker-compose.mutinynet.yml b/BTCPayServer.Tests/docker-compose.mutinynet.yml
index 9a7bd39..874b6ae 100644
--- a/BTCPayServer.Tests/docker-compose.mutinynet.yml
+++ b/BTCPayServer.Tests/docker-compose.mutinynet.yml
@@ -51,16 +51,6 @@ services:
- customer_lnd
- merchant_lnd
- selenium:
- image: selenium/standalone-chrome:125.0
- extra_hosts:
- - "tests:172.23.0.18"
- expose:
- - "4444"
- networks:
- default:
- custom:
-
nbxplorer:
image: nicolasdorier/nbxplorer:2.5.29
restart: unless-stopped
diff --git a/BTCPayServer.Tests/docker-compose.testnet.yml b/BTCPayServer.Tests/docker-compose.testnet.yml
index 44e4094..bf375b4 100644
--- a/BTCPayServer.Tests/docker-compose.testnet.yml
+++ b/BTCPayServer.Tests/docker-compose.testnet.yml
@@ -46,16 +46,6 @@ services:
- customer_lnd
- merchant_lnd
- selenium:
- image: selenium/standalone-chrome:125.0
- extra_hosts:
- - "tests:172.23.0.18"
- expose:
- - "4444"
- networks:
- default:
- custom:
-
nbxplorer:
image: nicolasdorier/nbxplorer:2.5.29
restart: unless-stopped
diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml
index 8a7b1c1..3553084 100644
--- a/BTCPayServer.Tests/docker-compose.yml
+++ b/BTCPayServer.Tests/docker-compose.yml
@@ -34,7 +34,6 @@ services:
- "80"
depends_on:
- dev
- - selenium
extra_hosts:
- "tests:127.0.0.1"
networks:
@@ -88,16 +87,6 @@ services:
- customer_lnd
- merchant_lnd
- selenium:
- image: selenium/standalone-chrome:125.0
- extra_hosts:
- - "tests:172.23.0.18"
- expose:
- - "4444"
- networks:
- default:
- custom:
-
mailpit:
image: axllent/mailpit:v1.27
ports:
diff --git a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml
index c54bf4b..ba82741 100644
--- a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml
+++ b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml
@@ -54,7 +54,7 @@
}
@section PageFootContent {
- @*Without async, somehow selenium do not manage to click on links in this page*@
+ @*Script must be async to ensure proper page loading*@
<script src="~/modal/btcpay.js" asp-append-version="true" async></script>
@* Custom Range Modal *@
diff --git a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml
index 05a939d..18ecd8a 100644
--- a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml
+++ b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml
@@ -52,7 +52,7 @@
}
@section PageFootContent {
- @*Without async, somehow selenium do not manage to click on links in this page*@
+ @*Script must be async to ensure proper page loading*@
<script src="~/modal/btcpay.js" asp-append-version="true" async></script>
@* Custom Range Modal *@
<script>
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.