diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index 4a6270e8f..e857067be 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -479,21 +479,21 @@ namespace BTCPayServer.Tests //there should be three now invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); s.GoToInvoiceCheckout(invoiceId); - var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies")); Assert.Contains("BTC", currencyDropdownButton.Text); currencyDropdownButton.Click(); var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); Assert.Equal(3, elements.Count); elements.Single(element => element.Text.Contains("LTC")).Click(); - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies")); Assert.Contains("LTC", currencyDropdownButton.Text); currencyDropdownButton.Click(); elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); elements.Single(element => element.Text.Contains("Lightning")).Click(); - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies")); Assert.Contains("Lightning", currencyDropdownButton.Text); s.Driver.Quit(); diff --git a/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs b/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs index c5f2b19fe..b6a539c15 100644 --- a/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs @@ -86,7 +86,7 @@ namespace BTCPayServer.Tests var invoiceId = s.CreateInvoice(store.storeName, 10); s.GoToInvoiceCheckout(invoiceId); - var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies")); Assert.Contains("ETH", currencyDropdownButton.Text); s.Driver.FindElement(By.Id("copy-tab")).Click(); diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs index 043dbaae8..d10f51d6b 100644 --- a/BTCPayServer.Tests/ApiKeysTests.cs +++ b/BTCPayServer.Tests/ApiKeysTests.cs @@ -65,7 +65,7 @@ namespace BTCPayServer.Tests s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); s.SetCheckbox(s, "btcpay.user.canviewprofile", true); s.Driver.FindElement(By.Id("Generate")).Click(); - var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; //this api key has access to everything await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile); @@ -74,7 +74,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("AddApiKey")).Click(); s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true); s.Driver.FindElement(By.Id("Generate")).Click(); - var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user, Policies.CanModifyServerSettings); @@ -82,7 +82,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("AddApiKey")).Click(); s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); s.Driver.FindElement(By.Id("Generate")).Click(); - var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user, Policies.CanModifyStoreSettings); @@ -94,13 +94,13 @@ namespace BTCPayServer.Tests var storeId = option.GetAttribute("value"); option.Click(); s.Driver.FindElement(By.Id("Generate")).Click(); - var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user, Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString()); s.Driver.FindElement(By.Id("AddApiKey")).Click(); s.Driver.FindElement(By.Id("Generate")).Click(); - var noPermissionsApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user); await Assert.ThrowsAnyAsync(async () => @@ -188,7 +188,7 @@ namespace BTCPayServer.Tests checkbox.Click(); } s.Driver.FindElement(By.Id("Generate")).Click(); - var allAPIKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text; + var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; var apikeydata = await TestApiAgainstAccessToken(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient); Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length); } diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index 05f23f3c0..d0c1b0369 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -33,7 +33,7 @@ namespace BTCPayServer.Tests s.AddDerivationScheme("BTC"); s.GoToStore(store.storeId, StoreNavPages.Checkout); s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click(); - s.Driver.FindElement(By.Name("command")).ForceClick(); + s.Driver.FindElement(By.Name("command")).Click(); var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com"); s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId); @@ -116,7 +116,7 @@ namespace BTCPayServer.Tests s.SetCheckbox(s, "LightningAmountInSatoshi", true); var command = s.Driver.FindElement(By.Name("command")); - command.ForceClick(); + command.Click(); var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); s.GoToInvoiceCheckout(invoiceId); Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text); diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs index 78a8cb3b6..737a6500c 100644 --- a/BTCPayServer.Tests/Extensions.cs +++ b/BTCPayServer.Tests/Extensions.cs @@ -20,41 +20,25 @@ namespace BTCPayServer.Tests var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings); return res; } - public static void ScrollTo(this IWebDriver driver, By by) + + public static void WaitForPageLoad(this IWebDriver driver) { - var element = driver.FindElement(by); - } - /// - /// Sometimes the chrome driver is fucked up and we need some magic to click on the element. - /// - /// - public static void ForceClick(this IWebElement element) - { - element.SendKeys(Keys.Return); + // Yes, this sucks big time, but it ensures that the JS after it + // does not get executed in the old page context. Bad, I know. + // Thread.Sleep(TimeSpan.FromMilliseconds(250)); + // + // new WebDriverWait(driver, SeleniumTester.ImplicitWait) + // .Until(d=>((IJavaScriptExecutor)d).ExecuteScript("return (document.readyState === 'complete' && (typeof(jQuery) === 'undefined' || jQuery.isReady))").Equals(true)); } - /// - /// Utility method to wait until timeout for element to be present (optionally displayed) - /// - /// Wait context - /// How we search for element - /// Flag to wait for element to be displayed or just present - /// How long to wait for element to be present/displayed - /// Element we were waiting for - public static IWebElement WaitForElement(this IWebDriver context, By by, bool displayed = true, uint timeout = 10) + public static void LogIn(this SeleniumTester s, string email) { - var wait = new DefaultWait(context); - wait.Timeout = TimeSpan.FromSeconds(timeout); - wait.IgnoreExceptionTypes(typeof(NoSuchElementException)); - return wait.Until(ctx => - { - var elem = ctx.FindElement(by); - if (displayed && !elem.Displayed) - return null; - - return elem; - }); + 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) { try diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index f9d8b3c0a..8973a9097 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -25,6 +25,7 @@ using NBXplorer.DerivationStrategy; using NBXplorer.Models; using Newtonsoft.Json.Linq; using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; @@ -239,6 +240,7 @@ namespace BTCPayServer.Tests s.SetCheckbox(s, "PayJoinEnabled", true); s.Driver.FindElement(By.Id("Save")).Click(); Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected); + var sender = s.CreateNewStore(); var senderSeed = s.GenerateWallet("BTC", "", true, true, format); var senderWalletId = new WalletId(sender.storeId, "BTC"); @@ -257,16 +259,21 @@ namespace BTCPayServer.Tests s.Driver.SwitchTo().Alert().Accept(); Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinBIP21")) .GetAttribute("value"))); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); - s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click(); + s.Driver.FindElement(By.Id("SendMenu")).Click(); + s.Driver.WaitForPageLoad(); + + var nbxSeedButton = s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")); + new WebDriverWait(s.Driver, SeleniumTester.ImplicitWait).Until(d=> nbxSeedButton.Enabled); + nbxSeedButton.Click(); + s.Driver.WaitForPageLoad(); + await s.Server.WaitForEvent(() => { - s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click(); return Task.CompletedTask; }); //no funds in receiver wallet to do payjoin - s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Warning); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Warning); await TestUtils.EventuallyAsync(async () => { var invoice = await s.Server.PayTester.GetService().GetInvoice(invoiceId); @@ -294,15 +301,14 @@ namespace BTCPayServer.Tests .GetAttribute("value"))); s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear(); s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2"); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); + s.Driver.FindElement(By.Id("SendMenu")).Click(); s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click(); var txId = await s.Server.WaitForEvent(() => { - s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click(); return Task.CompletedTask; }); - s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Success); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success); await TestUtils.EventuallyAsync(async () => { var invoice = await invoiceRepository.GetInvoice(invoiceId); diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 4f8053d94..173d868c4 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -20,6 +20,7 @@ using NBitcoin; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Support.UI; using Xunit; namespace BTCPayServer.Tests @@ -28,16 +29,10 @@ namespace BTCPayServer.Tests { public IWebDriver Driver { get; set; } public ServerTester Server { get; set; } + public WalletId WalletId { get; set; } - public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) - { - var server = ServerTester.Create(scope, newDb); - return new SeleniumTester() - { - Server = server - }; - } - + public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) => + new SeleniumTester { Server = ServerTester.Create(scope, newDb) }; public async Task StartAsync() { @@ -48,7 +43,6 @@ namespace BTCPayServer.Tests // this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789 options.AddArgument("no-sandbox"); } - var isDebug = !Server.PayTester.InContainer; if (!isDebug) { @@ -70,22 +64,10 @@ namespace BTCPayServer.Tests Driver.AssertNoError(); } - internal IWebElement AssertHappyMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success) - { - using var cts = new CancellationTokenSource(20_000); - while (!cts.IsCancellationRequested) - { - var result = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).Where(el => el.Displayed); - if (result.Any()) - return result.First(); - Thread.Sleep(100); - } - Logs.Tester.LogInformation(this.Driver.PageSource); - Assert.True(false, $"Should have shown {severity} message"); - return null; - } + internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success) => + Driver.FindElement(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")); - public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10); + public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5); public string Link(string relativeLink) { return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash(); @@ -93,8 +75,9 @@ namespace BTCPayServer.Tests public void GoToRegister() { - Driver.Navigate().GoToUrl(this.Link("/Account/Register")); + Driver.Navigate().GoToUrl(Link("/Account/Register")); } + public string RegisterNewUser(bool isAdmin = false) { var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com"; @@ -111,30 +94,38 @@ namespace BTCPayServer.Tests public (string storeName, string storeId) CreateNewStore() { - var usr = "Store" + RandomUtils.GetUInt64().ToString(); Driver.FindElement(By.Id("Stores")).Click(); + Driver.WaitForPageLoad(); + Driver.FindElement(By.Id("CreateStore")).Click(); - Driver.FindElement(By.Id("Name")).SendKeys(usr); + Driver.WaitForPageLoad(); + + var name = "Store" + RandomUtils.GetUInt64(); + Driver.FindElement(By.Id("Name")).SendKeys(name); Driver.FindElement(By.Id("Create")).Click(); + Driver.WaitForPageLoad(); + StoreId = Driver.FindElement(By.Id("Id")).GetAttribute("value"); - return (usr, StoreId); + return (name, StoreId); } public string StoreId { get; set; } public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit) { - Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick(); - Driver.FindElement(By.Id("import-from-btn")).ForceClick(); - Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick(); - Driver.WaitForElement(By.Id("ExistingMnemonic")).SendKeys(seed); - SetCheckbox(Driver.WaitForElement(By.Id("SavePrivateKeys")), privkeys); - SetCheckbox(Driver.WaitForElement(By.Id("ImportKeysToRPC")), importkeys); - Driver.WaitForElement(By.Id("ScriptPubKeyType")).Click(); - Driver.WaitForElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click(); + Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); + Driver.WaitForPageLoad(); + + Driver.FindElement(By.Id("import-from-btn")).Click(); + Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).Click(); + Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed); + SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys); + SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys); + Driver.FindElement(By.Id("ScriptPubKeyType")).Click(); + Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click(); Logs.Tester.LogInformation("Trying to click btn-generate"); - Driver.WaitForElement(By.Id("btn-generate")).ForceClick(); + Driver.FindElement(By.Id("btn-generate")).Click(); // Seed backup page - AssertHappyMessage(); + FindAlertMessage(); if (string.IsNullOrEmpty(seed)) { seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic"); @@ -146,14 +137,20 @@ namespace BTCPayServer.Tests WalletId = new WalletId(StoreId, cryptoCode); return new Mnemonic(seed); } - public WalletId WalletId { get; set; } + public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]") { - Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick(); + Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); + Driver.WaitForPageLoad(); Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme); - Driver.FindElement(By.Id("Continue")).ForceClick(); - Driver.FindElement(By.Id("Confirm")).ForceClick(); - AssertHappyMessage(); + Driver.FindElement(By.Id("Continue")).Click(); + Driver.WaitForPageLoad(); + + var confirmButton = Driver.FindElement(By.Id("Confirm")); + new WebDriverWait(Driver, ImplicitWait).Until(d=>confirmButton.Enabled); + confirmButton.Click(); + Driver.WaitForPageLoad(); + FindAlertMessage(); } public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType) @@ -168,16 +165,16 @@ namespace BTCPayServer.Tests else throw new NotSupportedException(connectionType.ToString()); - Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick(); + Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click(); Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString); - Driver.FindElement(By.Id($"save")).ForceClick(); + Driver.FindElement(By.Id($"save")).Click(); } public void AddInternalLightningNode(string cryptoCode) { - Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick(); - Driver.FindElement(By.Id($"internal-ln-node-setter")).ForceClick(); - Driver.FindElement(By.Id($"save")).ForceClick(); + Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click(); + Driver.FindElement(By.Id($"internal-ln-node-setter")).Click(); + Driver.FindElement(By.Id($"save")).Click(); } public void ClickOnAllSideMenus() @@ -189,19 +186,18 @@ namespace BTCPayServer.Tests { Logs.Tester.LogInformation($"Checking no error on {l}"); Driver.Navigate().GoToUrl(l); + Driver.WaitForPageLoad(); Driver.AssertNoError(); } } - - public void Dispose() { if (Driver != null) { try { - Driver.Close(); + Driver.Quit(); } catch { } Driver.Dispose(); @@ -240,17 +236,24 @@ namespace BTCPayServer.Tests public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index) { Driver.FindElement(By.Id("Stores")).Click(); + Driver.WaitForPageLoad(); + Driver.FindElement(By.Id($"update-store-{storeId}")).Click(); + Driver.WaitForPageLoad(); + if (storeNavPage != StoreNavPages.Index) { Driver.FindElement(By.Id(storeNavPage.ToString())).Click(); + Driver.WaitForPageLoad(); } } public void GoToInvoiceCheckout(string invoiceId) { Driver.FindElement(By.Id("Invoices")).Click(); + Driver.WaitForPageLoad(); Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click(); + Driver.WaitForPageLoad(); CheckForJSErrors(); } @@ -271,14 +274,7 @@ namespace BTCPayServer.Tests public void SetCheckbox(SeleniumTester s, string checkboxId, bool value) { - SetCheckbox(s.Driver.WaitForElement(By.Id(checkboxId)), value); - } - - public void ScrollToElement(IWebElement element) - { - Actions actions = new Actions(Driver); - actions.MoveToElement(element); - actions.Perform(); + SetCheckbox(s.Driver.FindElement(By.Id(checkboxId)), value); } public void GoToInvoices() @@ -300,16 +296,11 @@ namespace BTCPayServer.Tests Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "Account/Login")); } - public void GoToCreateInvoicePage() - { - GoToInvoices(); - Driver.FindElement(By.Id("CreateNewInvoice")).Click(); - } - public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "") { GoToInvoices(); - Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element + Driver.FindElement(By.Id("CreateNewInvoice")).Click(); + Driver.WaitForPageLoad(); Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture)); var currencyEl = Driver.FindElement(By.Id("Currency")); currencyEl.Clear(); @@ -318,7 +309,7 @@ namespace BTCPayServer.Tests Driver.FindElement(By.Name("StoreId")).SendKeys(storeName); Driver.FindElement(By.Id("Create")).Click(); - AssertHappyMessage(); + FindAlertMessage(); var statusElement = Driver.FindElement(By.ClassName("alert-success")); var id = statusElement.Text.Split(" ")[1]; return id; @@ -331,7 +322,7 @@ namespace BTCPayServer.Tests Driver.FindElement(By.Id("generateButton")).Click(); var addressStr = Driver.FindElement(By.Id("address")).GetProperty("value"); var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork); - for (int i = 0; i < coins; i++) + for (var i = 0; i < coins; i++) { await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination)); } @@ -348,15 +339,11 @@ namespace BTCPayServer.Tests Driver.FindElement(By.Id("bip21parse")).Click(); Driver.SwitchTo().Alert().SendKeys(bip21); Driver.SwitchTo().Alert().Accept(); - Driver.ScrollTo(By.Id("SendMenu")); - Driver.FindElement(By.Id("SendMenu")).ForceClick(); + Driver.FindElement(By.Id("SendMenu")).Click(); Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click(); - Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); + Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); } - - - private void CheckForJSErrors() { //wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste @@ -384,25 +371,29 @@ namespace BTCPayServer.Tests { walletId ??= WalletId; Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}")); + Driver.WaitForPageLoad(); if (navPages != WalletsNavPages.Transactions) { Driver.FindElement(By.Id($"Wallet{navPages}")).Click(); + Driver.WaitForPageLoad(); } } public void GoToUrl(string relativeUrl) { Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, relativeUrl)); + Driver.WaitForPageLoad(); } public void GoToServer(ServerNavPages navPages = ServerNavPages.Index) { Driver.FindElement(By.Id("ServerSettings")).Click(); + Driver.WaitForPageLoad(); if (navPages != ServerNavPages.Index) { Driver.FindElement(By.Id($"Server-{navPages}")).Click(); + Driver.WaitForPageLoad(); } - } public void GoToInvoice(string id) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 399f1edc7..68858ed92 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -33,7 +33,8 @@ namespace BTCPayServer.Tests [Trait("Selenium", "Selenium")] public class ChromeTests { - public const int TestTimeout = TestUtils.TestTimeout; + private const int TestTimeout = TestUtils.TestTimeout; + public ChromeTests(ITestOutputHelper helper) { Logs.Tester = new XUnitLog(helper) { Name = "Tests" }; @@ -85,7 +86,7 @@ namespace BTCPayServer.Tests Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase); s.Driver.FindElement(By.Id("delete")).Click(); s.Driver.FindElement(By.Id("continue")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); seedEl = s.Driver.FindElement(By.Id("SeedTextArea")); Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase); } @@ -150,7 +151,7 @@ namespace BTCPayServer.Tests var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com"; s.Driver.FindElement(By.Id("Email")).SendKeys(usr); s.Driver.FindElement(By.Id("Save")).Click(); - var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text; + var url = s.FindAlertMessage().FindElement(By.TagName("a")).Text; ; s.Logout(); s.Driver.Navigate().GoToUrl(url); @@ -160,7 +161,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Password")).SendKeys("123456"); s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456"); s.Driver.FindElement(By.Id("SetPassword")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); s.Driver.FindElement(By.Id("Email")).SendKeys(usr); s.Driver.FindElement(By.Id("Password")).SendKeys("123456"); s.Driver.FindElement(By.Id("LoginButton")).Click(); @@ -170,13 +171,6 @@ namespace BTCPayServer.Tests } } - static void LogIn(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(); - } [Fact(Timeout = TestTimeout)] public async Task CanUseSSHService() { @@ -185,8 +179,9 @@ namespace BTCPayServer.Tests await s.StartAsync(); var alice = s.RegisterNewUser(isAdmin: true); s.Driver.Navigate().GoToUrl(s.Link("/server/services")); + s.Driver.WaitForPageLoad(); Assert.Contains("server/services/ssh", s.Driver.PageSource); - using (var client = await s.Server.PayTester.GetService().SSHSettings.ConnectAsync()) + using (var client = await s.Server.PayTester.GetService().SSHSettings.ConnectAsync()) { var result = await client.RunBash("echo hello"); Assert.Equal(string.Empty, result.Error); @@ -197,7 +192,7 @@ namespace BTCPayServer.Tests s.Driver.AssertNoError(); s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear(); s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2"); - s.Driver.FindElement(By.Id("submit")).ForceClick(); + s.Driver.FindElement(By.Id("submit")).Click(); s.Driver.AssertNoError(); var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text; @@ -207,7 +202,7 @@ namespace BTCPayServer.Tests Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase)); s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear(); - s.Driver.FindElement(By.Id("submit")).ForceClick(); + s.Driver.FindElement(By.Id("submit")).Click(); text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text; Assert.DoesNotContain("test2", text); @@ -225,7 +220,7 @@ namespace BTCPayServer.Tests if (s.Driver.PageSource.Contains("Configured")) { s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit(); - s.AssertHappyMessage(); + s.FindAlertMessage(); } CanSetupEmailCore(s); s.CreateNewStore(); @@ -234,26 +229,6 @@ namespace BTCPayServer.Tests } } - private static void CanSetupEmailCore(SeleniumTester s) - { - s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click(); - s.Driver.FindElement(By.ClassName("dropdown-item")).Click(); - s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com"); - s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); - s.AssertHappyMessage(); - s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword"); - s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); - Assert.Contains("Configured", s.Driver.PageSource); - s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com"); - s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); - Assert.Contains("Configured", s.Driver.PageSource); - Assert.Contains("test_fix", s.Driver.PageSource); - s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit(); - s.AssertHappyMessage(); - Assert.DoesNotContain("Configured", s.Driver.PageSource); - Assert.Contains("test_fix", s.Driver.PageSource); - } - [Fact(Timeout = TestTimeout)] public async Task CanUseDynamicDns() { @@ -290,6 +265,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Settings_Hostname")).SendKeys("pouet.hello.com"); s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("MyLog"); s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("MyLog" + Keys.Enter); + s.Driver.WaitForPageLoad(); s.Driver.AssertNoError(); Assert.Contains("This hostname already exists", s.Driver.PageSource); @@ -337,22 +313,24 @@ namespace BTCPayServer.Tests s.ClickOnAllSideMenus(); s.GoToInvoices(); var invoiceId = s.CreateInvoice(storeData.storeName); - s.AssertHappyMessage(); + s.FindAlertMessage(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.ClassName("invoice-details-link")).Click(); var invoiceUrl = s.Driver.Url; //let's test archiving an invoice Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); s.Driver.FindElement(By.Id("btn-archive-toggle")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); Assert.Contains("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); //check that it no longer appears in list s.GoToInvoices(); + Assert.DoesNotContain(invoiceId, s.Driver.PageSource); //ok, let's unarchive and see that it shows again s.Driver.Navigate().GoToUrl(invoiceUrl); s.Driver.FindElement(By.Id("btn-archive-toggle")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); s.GoToInvoices(); Assert.Contains(invoiceId, s.Driver.PageSource); @@ -374,14 +352,14 @@ namespace BTCPayServer.Tests s.Logout(); // Let's add Bob as a guest to alice's store - LogIn(s, alice); + s.LogIn(alice); s.Driver.Navigate().GoToUrl(storeUrl + "/users"); s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter); Assert.Contains("User added successfully", s.Driver.PageSource); s.Logout(); // Bob should not have access to store, but should have access to invoice - LogIn(s, bob); + s.LogIn(bob); s.Driver.Navigate().GoToUrl(storeUrl); Assert.Contains("ReturnUrl", s.Driver.Url); s.Driver.Navigate().GoToUrl(invoiceUrl); @@ -389,8 +367,9 @@ namespace BTCPayServer.Tests // Alice should be able to delete the store s.Logout(); - LogIn(s, alice); + s.LogIn(alice); s.Driver.FindElement(By.Id("Stores")).Click(); + s.Driver.WaitForPageLoad(); // there shouldn't be any hints now Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point"); @@ -422,7 +401,7 @@ namespace BTCPayServer.Tests string pairingCode = AssertUrlHasPairingCode(s); s.Driver.FindElement(By.Id("ApprovePairing")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); Assert.Contains(pairingCode, s.Driver.PageSource); var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri); @@ -454,15 +433,6 @@ namespace BTCPayServer.Tests } } - 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; - } - - [Fact(Timeout = TestTimeout)] public async Task CanCreateAppPoS() { @@ -470,29 +440,31 @@ namespace BTCPayServer.Tests { await s.StartAsync(); s.RegisterNewUser(); - var store = s.CreateNewStore(); + var (storeName, _) = s.CreateNewStore(); s.Driver.FindElement(By.Id("Apps")).Click(); s.Driver.FindElement(By.Id("CreateNewApp")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid()); - s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale" + Keys.Enter); - s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter); + s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale"); + s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName); s.Driver.FindElement(By.Id("Create")).Click(); - s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart" + Keys.Enter); - s.Driver.FindElement(By.Id("SaveSettings")).ForceClick(); - s.Driver.FindElement(By.Id("ViewApp")).ForceClick(); + s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart"); + s.Driver.FindElement(By.Id("SaveSettings")).Click(); + s.Driver.FindElement(By.Id("ViewApp")).Click(); var posBaseUrl = s.Driver.Url.Replace("/Cart", ""); Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS"); Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view"); s.Driver.Url = posBaseUrl + "/static"; + s.Driver.WaitForPageLoad(); Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view"); s.Driver.Url = posBaseUrl + "/cart"; + s.Driver.WaitForPageLoad(); Assert.True(s.Driver.PageSource.Contains("Cart"), "Cart PoS not showing correct view"); - - s.Driver.Quit(); } } @@ -503,24 +475,24 @@ namespace BTCPayServer.Tests { await s.StartAsync(); s.RegisterNewUser(); - var store = s.CreateNewStore(); + var (storeName, _) = s.CreateNewStore(); s.AddDerivationScheme(); s.Driver.FindElement(By.Id("Apps")).Click(); s.Driver.FindElement(By.Id("CreateNewApp")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Name("Name")).SendKeys("CF" + Guid.NewGuid()); - s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund" + Keys.Enter); - s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter); + s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund"); + s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName); s.Driver.FindElement(By.Id("Create")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter"); s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC"); s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY"); s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700"); - s.Driver.FindElement(By.Id("SaveSettings")).ForceClick(); - s.Driver.FindElement(By.Id("ViewApp")).ForceClick(); - s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last()); - Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF"); - s.Driver.Quit(); + s.Driver.FindElement(By.Id("SaveSettings")).Click(); + s.Driver.FindElement(By.Id("ViewApp")).Click(); + Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text); } } @@ -539,11 +511,13 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123"); s.Driver.FindElement(By.Id("Amount")).SendKeys("700"); s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC"); - s.Driver.FindElement(By.Id("SaveButton")).ForceClick(); - s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return); + s.Driver.FindElement(By.Id("SaveButton")).Click(); + s.Driver.WaitForPageLoad(); + + s.Driver.FindElement(By.Name("ViewAppButton")).Click(); s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last()); - Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request"); - s.Driver.Quit(); + s.Driver.WaitForPageLoad(); + Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text); } } @@ -560,6 +534,7 @@ namespace BTCPayServer.Tests var walletId = new WalletId(storeId, "BTC"); s.GoToWallet(walletId, WalletsNavPages.Receive); s.Driver.FindElement(By.Id("generateButton")).Click(); + s.Driver.WaitForPageLoad(); var addressStr = s.Driver.FindElement(By.Id("address")).GetProperty("value"); var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork); await s.Server.ExplorerNode.GenerateAsync(1); @@ -584,11 +559,13 @@ namespace BTCPayServer.Tests await s.Server.ExplorerNode.GenerateAsync(1); s.GoToWallet(walletId, WalletsNavPages.Send); s.Driver.FindElement(By.Id("advancedSettings")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Id("toggleInputSelection")).Click(); - s.Driver.WaitForElement(By.Id(spentOutpoint.ToString())); + s.Driver.FindElement(By.Id(spentOutpoint.ToString())); Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant()); var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString())); s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click(); + s.Driver.WaitForPageLoad(); var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs")); Assert.Single(inputSelectionSelect.FindElements(By.CssSelector("[selected]"))); @@ -596,8 +573,8 @@ namespace BTCPayServer.Tests SetTransactionOutput(s, 0, bob, 0.3m); s.Driver.FindElement(By.Id("SendMenu")).Click(); s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click(); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); - var happyElement = s.AssertHappyMessage(); + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); + var happyElement = s.FindAlertMessage(); var happyText = happyElement.Text; var txid = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value; @@ -614,13 +591,14 @@ namespace BTCPayServer.Tests { await s.StartAsync(); s.RegisterNewUser(true); - var store = s.CreateNewStore(); - s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks); + var (storeName, storeId) = s.CreateNewStore(); + s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks); Logs.Tester.LogInformation("Let's create two webhooks"); - for (int i = 0; i < 2; i++) + for (var i = 0; i < 2; i++) { s.Driver.FindElement(By.Id("CreateWebhook")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}"); new SelectElement(s.Driver.FindElement(By.Name("Everything"))) .SelectByValue("false"); @@ -636,7 +614,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("continue")).Click(); deletes = s.Driver.FindElements(By.LinkText("Delete")); Assert.Single(deletes); - s.AssertHappyMessage(); + s.FindAlertMessage(); Logs.Tester.LogInformation("Let's try to update one of them"); s.Driver.FindElement(By.LinkText("Modify")).Click(); @@ -648,7 +626,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Name("Secret")).Clear(); s.Driver.FindElement(By.Name("Secret")).SendKeys("HelloWorld"); s.Driver.FindElement(By.Name("update")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); s.Driver.FindElement(By.LinkText("Modify")).Click(); foreach (var value in Enum.GetValues(typeof(WebhookEventType))) { @@ -664,13 +642,13 @@ namespace BTCPayServer.Tests Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource); s.Driver.FindElement(By.Name("update")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource); Logs.Tester.LogInformation("Let's see if we can generate an event"); - s.GoToStore(store.storeId); + s.GoToStore(storeId); s.AddDerivationScheme(); - s.CreateInvoice(store.storeName); + s.CreateInvoice(storeName); var request = await server.GetNextRequest(); var headers = request.Request.Headers; var actualSig = headers["BTCPay-Sig"].First(); @@ -681,21 +659,23 @@ namespace BTCPayServer.Tests server.Done(); Logs.Tester.LogInformation("Let's make a failed event"); - s.CreateInvoice(store.storeName); + s.CreateInvoice(storeName); request = await server.GetNextRequest(); request.Response.StatusCode = 404; server.Done(); // The delivery is done asynchronously, so small wait here await Task.Delay(500); - s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks); + s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks); s.Driver.FindElement(By.LinkText("Modify")).Click(); var elements = s.Driver.FindElements(By.ClassName("redeliver")); // One worked, one failed s.Driver.FindElement(By.ClassName("fa-times")); s.Driver.FindElement(By.ClassName("fa-check")); elements[0].Click(); - s.AssertHappyMessage(); + s.Driver.WaitForPageLoad(); + + s.FindAlertMessage(); request = await server.GetNextRequest(); request.Response.StatusCode = 404; server.Done(); @@ -708,32 +688,23 @@ namespace BTCPayServer.Tests CanBrowseContent(s); var element = s.Driver.FindElement(By.ClassName("redeliver")); element.Click(); - s.AssertHappyMessage(); + s.Driver.WaitForPageLoad(); + + s.FindAlertMessage(); request = await server.GetNextRequest(); request.Response.StatusCode = 404; server.Done(); Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside"); - s.GoToStore(store.storeId); + s.GoToStore(storeId); s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);"); s.Driver.FindElement(By.Id("danger-zone-expander")).Click(); s.Driver.FindElement(By.Id("delete-store")).Click(); s.Driver.FindElement(By.Id("continue")).Click(); - s.AssertHappyMessage(); + s.FindAlertMessage(); } } - private static void CanBrowseContent(SeleniumTester s) - { - s.Driver.FindElement(By.ClassName("delivery-content")).Click(); - var windows = s.Driver.WindowHandles; - Assert.Equal(2, windows.Count); - s.Driver.SwitchTo().Window(windows[1]); - JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text); - s.Driver.Close(); - s.Driver.SwitchTo().Window(windows[0]); - } - [Fact(Timeout = TestTimeout)] public async Task CanManageWallet() { @@ -749,23 +720,34 @@ namespace BTCPayServer.Tests //let's test quickly the receive wallet page s.Driver.FindElement(By.Id("Wallets")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.LinkText("Manage")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Id("WalletSend")).Click(); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); + s.Driver.WaitForPageLoad(); + + s.Driver.FindElement(By.Id("SendMenu")).Click(); //you cant use the Sign with NBX option without saving private keys when generating the wallet. Assert.DoesNotContain("nbx-seed", s.Driver.PageSource); s.Driver.FindElement(By.Id("WalletReceive")).Click(); + s.Driver.WaitForPageLoad(); + //generate a receiving address s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click(); Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed); var receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value"); + //unreserve s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click(); - //generate it again, should be the same one as before as nothign got used in the meantime + s.Driver.WaitForPageLoad(); + + //generate it again, should be the same one as before as nothing got used in the meantime s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click(); + s.Driver.WaitForPageLoad(); + Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed); Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value")); @@ -779,6 +761,8 @@ namespace BTCPayServer.Tests await Task.Delay(200); s.Driver.Navigate().Refresh(); s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click(); + s.Driver.WaitForPageLoad(); + Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value")); receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value"); @@ -787,8 +771,12 @@ namespace BTCPayServer.Tests s.GenerateWallet("BTC", "", true, false); s.Driver.FindElement(By.Id("Wallets")).Click(); s.Driver.FindElement(By.LinkText("Manage")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.Id("WalletReceive")).Click(); + s.Driver.WaitForPageLoad(); s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click(); + s.Driver.WaitForPageLoad(); + Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value")); var invoiceId = s.CreateInvoice(storeId.storeName); @@ -818,16 +806,18 @@ namespace BTCPayServer.Tests s.ClickOnAllSideMenus(); // Make sure we can rescan, because we are admin! - s.Driver.FindElement(By.Id("WalletRescan")).ForceClick(); + s.Driver.FindElement(By.Id("WalletRescan")).Click(); Assert.Contains("The batch size make sure", s.Driver.PageSource); // We setup the fingerprint and the account key path - s.Driver.FindElement(By.Id("WalletSettings")).ForceClick(); + s.Driver.FindElement(By.Id("WalletSettings")).Click(); // s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160"); // s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter); // Check the tx sent earlier arrived - s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick(); + s.Driver.FindElement(By.Id("WalletTransactions")).Click(); + s.Driver.WaitForPageLoad(); + var walletTransactionLink = s.Driver.Url; Assert.Contains(tx.ToString(), s.Driver.PageSource); @@ -838,17 +828,18 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("WalletSend")).Click(); var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest); SetTransactionOutput(s, 0, bob, 1); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); + s.Driver.FindElement(By.Id("SendMenu")).Click(); s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click(); + s.Driver.WaitForPageLoad(); // Input the seed - s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + Keys.Enter); + s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter); // Broadcast Assert.Contains(bob.ToString(), s.Driver.PageSource); Assert.Contains("1.00000000", s.Driver.PageSource); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); + s.Driver.WaitForPageLoad(); Assert.Equal(walletTransactionLink, s.Driver.Url); } @@ -860,18 +851,17 @@ namespace BTCPayServer.Tests var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest); SetTransactionOutput(s, 0, jack, 0.01m); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); + s.Driver.FindElement(By.Id("SendMenu")).Click(); s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click(); Assert.Contains(jack.ToString(), s.Driver.PageSource); Assert.Contains("0.01000000", s.Driver.PageSource); - s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).Click(); Assert.EndsWith("psbt", s.Driver.Url); - s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick(); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("#OtherActions")).Click(); + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); Assert.EndsWith("psbt/ready", s.Driver.Url); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); Assert.Equal(walletTransactionLink, s.Driver.Url); var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21; @@ -884,14 +874,14 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("bip21parse")).Click(); s.Driver.SwitchTo().Alert().SendKeys(bip21); s.Driver.SwitchTo().Alert().Accept(); - s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Info); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info); Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value")); Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value")); s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings); var walletUrl = s.Driver.Url; - s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick(); + s.Driver.FindElement(By.Id("SettingsMenu")).Click(); s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click(); // Seed backup page @@ -905,18 +895,6 @@ namespace BTCPayServer.Tests Assert.Equal(walletUrl, s.Driver.Url); } } - 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()); - var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput")); - if (checkboxElement.Selected != subtract) - { - checkboxElement.Click(); - } - } [Fact] [Trait("Selenium", "Selenium")] @@ -932,39 +910,49 @@ namespace BTCPayServer.Tests await s.FundStoreWallet(denomination: 50.0m); s.GoToWallet(navPages: WalletsNavPages.PullPayments); s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.FindElement(By.Id("Amount")).Clear(); - s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter); + s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");; + s.Driver.FindElement(By.Id("Create")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.LinkText("View")).Click(); - Thread.Sleep(1000); s.GoToWallet(navPages: WalletsNavPages.PullPayments); + s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.Id("Name")).SendKeys("PP2"); s.Driver.FindElement(By.Id("Amount")).Clear(); - s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0" + Keys.Enter); + s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0"); + s.Driver.FindElement(By.Id("Create")).Click(); + s.Driver.WaitForPageLoad(); + // This should select the first View, ie, the last one PP2 s.Driver.FindElement(By.LinkText("View")).Click(); + s.Driver.WaitForPageLoad(); - Thread.Sleep(1000); var address = await s.Server.ExplorerNode.GetNewAddressAsync(); s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter); - s.AssertHappyMessage(); + s.FindAlertMessage(); // We should not be able to use an address already used s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); - s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Error); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error); address = await s.Server.ExplorerNode.GetNewAddressAsync(); s.Driver.FindElement(By.Id("Destination")).Clear(); s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString()); s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); - s.AssertHappyMessage(); + s.FindAlertMessage(); Assert.Contains("Awaiting Approval", s.Driver.PageSource); var viewPullPaymentUrl = s.Driver.Url; @@ -982,11 +970,18 @@ namespace BTCPayServer.Tests Assert.DoesNotContain("No payout waiting for approval", s.Driver.PageSource); s.Driver.FindElement(By.Id("selectAllCheckbox")).Click(); s.Driver.FindElement(By.Id("payCommand")).Click(); - s.Driver.ScrollTo(By.Id("SendMenu")); - s.Driver.FindElement(By.Id("SendMenu")).ForceClick(); + s.Driver.WaitForPageLoad(); + + s.Driver.FindElement(By.Id("SendMenu")).Click(); + s.Driver.WaitForPageLoad(); + s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click(); - s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); - s.AssertHappyMessage(); + s.Driver.WaitForPageLoad(); + + s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click(); + s.Driver.WaitForPageLoad(); + + s.FindAlertMessage(); TestUtils.Eventually(() => { @@ -1027,5 +1022,59 @@ namespace BTCPayServer.Tests }); } } + + private static void CanBrowseContent(SeleniumTester s) + { + s.Driver.FindElement(By.ClassName("delivery-content")).Click(); + s.Driver.WaitForPageLoad(); + var windows = s.Driver.WindowHandles; + Assert.Equal(2, windows.Count); + s.Driver.SwitchTo().Window(windows[1]); + JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text); + s.Driver.Close(); + s.Driver.SwitchTo().Window(windows[0]); + } + + private static void CanSetupEmailCore(SeleniumTester s) + { + s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click(); + s.Driver.FindElement(By.ClassName("dropdown-item")).Click(); + s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com"); + s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); + s.FindAlertMessage(); + s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword"); + s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); + Assert.Contains("Configured", s.Driver.PageSource); + s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com"); + s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); + s.Driver.WaitForPageLoad(); + Assert.Contains("Configured", s.Driver.PageSource); + Assert.Contains("test_fix", s.Driver.PageSource); + s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit(); + s.FindAlertMessage(); + Assert.DoesNotContain("Configured", s.Driver.PageSource); + Assert.Contains("test_fix", s.Driver.PageSource); + } + + 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/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml index 6bb6bb74e..7dcc07508 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml @@ -15,19 +15,19 @@ @Model.Title @if (!Model.Started && Model.StartDate.HasValue) { - + Starts @Model.StartDate.Value.Subtract(DateTime.Now.ToUniversalTime()) } else if (Model.Started && !Model.Ended && Model.EndDate.HasValue) { - + Ends @Model.EndDate.Value.Subtract(DateTime.Now.ToUniversalTime()) } else if (Model.Started && !Model.Ended && !Model.EndDate.HasValue) { - + Currently Active! } diff --git a/BTCPayServer/Views/PaymentRequest/ViewPaymentRequest.cshtml b/BTCPayServer/Views/PaymentRequest/ViewPaymentRequest.cshtml index 16410d04a..54c245af3 100644 --- a/BTCPayServer/Views/PaymentRequest/ViewPaymentRequest.cshtml +++ b/BTCPayServer/Views/PaymentRequest/ViewPaymentRequest.cshtml @@ -216,7 +216,7 @@
@Model.AmountDueFormatted
-
Amount due
+
Amount due