From c73878ccca9e1709027bfb2bba9b91256d98b697 Mon Sep 17 00:00:00 2001 From: d11n Date: Wed, 17 Feb 2021 04:14:29 +0100 Subject: [PATCH] Fix missing hot wallet option on seed import (#2284) * Fix missing hot wallet option on seed import Thanks @kukks for spotting! * Tests: Wait for button to be ready for interaction * Camelcase test selectors * Tests: Remove general ImplicitWait * Tests: Add WaitForAndClick helper * Tests: Refactor SetCheckbox * Tests: Add WaitForElement helper * Tests: Refactor and use wait.UntilJsIsReady helper * Fix missing helper in ethereum tests * Tests: Some more refactorings --- .../AltcoinTests/EthereumTests.cs | 8 +-- BTCPayServer.Tests/ApiKeysTests.cs | 12 ++-- BTCPayServer.Tests/CheckoutUITests.cs | 2 +- BTCPayServer.Tests/Extensions.cs | 45 +++++++++++++ BTCPayServer.Tests/PayJoinTests.cs | 2 +- BTCPayServer.Tests/SeleniumTester.cs | 67 ++++++++----------- BTCPayServer.Tests/SeleniumTests.cs | 6 +- .../Views/Home/RecoverySeedBackup.cshtml | 18 ++--- .../Views/Stores/GenerateWalletOptions.cshtml | 4 +- .../Views/Stores/ImportWallet/Seed.cshtml | 1 + .../Views/Stores/ImportWalletOptions.cshtml | 10 +-- BTCPayServer/Views/Stores/ModifyWallet.cshtml | 2 +- BTCPayServer/Views/Stores/SetupWallet.cshtml | 4 +- .../Views/Stores/_GenerateWalletForm.cshtml | 2 +- 14 files changed, 108 insertions(+), 75 deletions(-) diff --git a/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs b/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs index 08550e154..55d387faa 100644 --- a/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/EthereumTests.cs @@ -73,14 +73,14 @@ namespace BTCPayServer.Tests var seed = new Mnemonic(Wordlist.English); s.Driver.FindElement(By.Id("ModifyETH")).Click(); s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString()); - s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true); - s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true); + s.Driver.SetCheckbox(By.Id("StoreSeed"), true); + s.Driver.SetCheckbox(By.Id("Enabled"), true); s.Driver.FindElement(By.Id("SaveButton")).Click(); s.FindAlertMessage(); s.Driver.FindElement(By.Id("ModifyUSDT20")).Click(); s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString()); - s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true); - s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true); + s.Driver.SetCheckbox(By.Id("StoreSeed"), true); + s.Driver.SetCheckbox(By.Id("Enabled"), true); s.Driver.FindElement(By.Id("SaveButton")).Click(); s.FindAlertMessage(); diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs index d10f51d6b..9d89a4a28 100644 --- a/BTCPayServer.Tests/ApiKeysTests.cs +++ b/BTCPayServer.Tests/ApiKeysTests.cs @@ -61,9 +61,9 @@ namespace BTCPayServer.Tests Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource); //server management should show now - s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true); - s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); - s.SetCheckbox(s, "btcpay.user.canviewprofile", true); + s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true); + s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true); + s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true); s.Driver.FindElement(By.Id("Generate")).Click(); var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; @@ -72,7 +72,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("AddApiKey")).Click(); - s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true); + s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true); s.Driver.FindElement(By.Id("Generate")).Click(); var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user, @@ -80,7 +80,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("AddApiKey")).Click(); - s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true); + s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true); s.Driver.FindElement(By.Id("Generate")).Click(); var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user, @@ -149,7 +149,7 @@ namespace BTCPayServer.Tests Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant()); Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant()); - s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", false); + s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false); Assert.Contains("change-store-mode", s.Driver.PageSource); s.Driver.FindElement(By.Id("consent-yes")).Click(); Assert.Equal(callbackUrl, s.Driver.Url); diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index d0c1b0369..575d53c59 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -113,7 +113,7 @@ namespace BTCPayServer.Tests var store = s.CreateNewStore(); s.AddInternalLightningNode("BTC"); s.GoToStore(store.storeId, StoreNavPages.Checkout); - s.SetCheckbox(s, "LightningAmountInSatoshi", true); + s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true); var command = s.Driver.FindElement(By.Name("command")); command.Click(); diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs index 83cd75179..abd228dcd 100644 --- a/BTCPayServer.Tests/Extensions.cs +++ b/BTCPayServer.Tests/Extensions.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; using Xunit; namespace BTCPayServer.Tests @@ -105,5 +106,49 @@ namespace BTCPayServer.Tests } Assert.False(true, "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)); + } + + 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 WaitForAndClick(this IWebDriver driver, By selector) + { + var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait); + wait.UntilJsIsReady(); + + var el = driver.FindElement(selector); + wait.Until(d => el.Displayed && el.Enabled); + el.Click(); + + wait.UntilJsIsReady(); + } + + public static void SetCheckbox(this IWebDriver driver, By selector, bool value) + { + var element = driver.FindElement(selector); + if ((value && !element.Selected) || (!value && element.Selected)) + { + driver.WaitForAndClick(selector); + } + + if (value != element.Selected) + { + Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again"); + driver.SetCheckbox(selector, value); + } + } } } diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index e31d92e5d..8e07f7619 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -237,7 +237,7 @@ namespace BTCPayServer.Tests s.GoToStore(receiver.storeId); //payjoin is not enabled by default. Assert.False(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected); - s.SetCheckbox(s, "PayJoinEnabled", true); + s.Driver.SetCheckbox(By.Id("PayJoinEnabled"), true); s.Driver.FindElement(By.Id("Save")).Click(); Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected); diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 63876bb00..da45b2c8b 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -32,6 +32,7 @@ namespace BTCPayServer.Tests public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) => new SeleniumTester { Server = ServerTester.Create(scope, newDb) }; + public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5); public async Task StartAsync() { @@ -73,20 +74,19 @@ namespace BTCPayServer.Tests Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}"); Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}"); Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}"); - Driver.Manage().Timeouts().ImplicitWait = ImplicitWait; GoToRegister(); Driver.AssertNoError(); } internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success) { - var el = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).FirstOrDefault(e => e.Displayed); + var className = $"alert-{StatusMessageModel.ToString(severity)}"; + var el = Driver.FindElement(By.ClassName(className)) ?? Driver.WaitForElement(By.ClassName(className)); if (el is null) - throw new NoSuchElementException($"Unable to find alert-{StatusMessageModel.ToString(severity)}"); + throw new NoSuchElementException($"Unable to find {className}"); return el; } - public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5); public string Link(string relativeLink) { return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash(); @@ -126,40 +126,47 @@ namespace BTCPayServer.Tests { Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); // Modify case - if (Driver.PageSource.Contains("id=\"change-wallet-link\"")) + if (Driver.PageSource.Contains("id=\"ChangeWalletLink\"")) { - Driver.FindElement(By.Id("change-wallet-link")).Click(); + Driver.FindElement(By.Id("ChangeWalletLink")).Click(); } if (string.IsNullOrEmpty(seed)) { - var option = privkeys ? "hotwallet" : "watchonly"; + var option = privkeys ? "Hotwallet" : "Watchonly"; Logs.Tester.LogInformation($"Generating new seed ({option})"); - Driver.FindElement(By.Id("generate-wallet-link")).Click(); - Driver.FindElement(By.Id($"generate-{option}-link")).Click(); + Driver.FindElement(By.Id("GenerateWalletLink")).Click(); + Driver.FindElement(By.Id($"Generate{option}Link")).Click(); } else { Logs.Tester.LogInformation("Progressing with existing seed"); - Driver.FindElement(By.Id("import-wallet-options-link")).Click(); - Driver.FindElement(By.Id("import-seed-link")).Click(); + Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click(); + Driver.FindElement(By.Id("ImportSeedLink")).Click(); Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed); - SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys); + Driver.SetCheckbox(By.Id("SavePrivateKeys"), privkeys); } Driver.FindElement(By.Id("ScriptPubKeyType")).Click(); Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click(); - Driver.FindElement(By.Id("advanced-settings-button")).Click(); - SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys); - Driver.FindElement(By.Id("advanced-settings-button")).Click(); // close settings again , otherwise the button might not be clickable for Selenium + Driver.FindElement(By.Id("AdvancedSettingsButton")).Click(); + Driver.SetCheckbox(By.Id("ImportKeysToRPC"), importkeys); + Driver.FindElement(By.Id("AdvancedSettingsButton")).Click(); // close again + + try + { + Driver.FindElement(By.Id("Continue")).Click(); + } + catch (ElementClickInterceptedException) + { + Driver.WaitForAndClick(By.Id("Continue")); + } - Logs.Tester.LogInformation("Trying to click Continue button"); - Driver.FindElement(By.Id("Continue")).Click(); // Seed backup page FindAlertMessage(); if (string.IsNullOrEmpty(seed)) { - seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic"); + seed = Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic"); } // Confirm seed backup @@ -173,8 +180,8 @@ namespace BTCPayServer.Tests public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]") { Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); - Driver.FindElement(By.Id("import-wallet-options-link")).Click(); - Driver.FindElement(By.Id("import-xpub-link")).Click(); + 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(); @@ -282,26 +289,6 @@ namespace BTCPayServer.Tests CheckForJSErrors(); } - - public void SetCheckbox(IWebElement element, bool value) - { - if ((value && !element.Selected) || (!value && element.Selected)) - { - element.Click(); - } - - if (value != element.Selected) - { - Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again"); - SetCheckbox(element, value); - } - } - - public void SetCheckbox(SeleniumTester s, string checkboxId, bool value) - { - SetCheckbox(s.Driver.FindElement(By.Id(checkboxId)), value); - } - public void GoToInvoices() { Driver.FindElement(By.Id("Invoices")).Click(); diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index e9183f8d8..2bd6c6587 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -113,7 +113,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Email")).SendKeys(u2.RegisterDetails.Email); s.Driver.FindElement(By.Id("save")).Click(); - s.FindAlertMessage(Abstractions.Models.StatusMessageModel.StatusSeverity.Error); + s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error); s.GoToProfile(ManageNavPages.Index); s.Driver.FindElement(By.Id("Email")).Clear(); @@ -578,7 +578,7 @@ namespace BTCPayServer.Tests await s.Server.ExplorerNode.GenerateAsync(1); s.GoToWallet(walletId); s.Driver.FindElement(By.Id("advancedSettings")).Click(); - s.Driver.FindElement(By.Id("toggleInputSelection")).Click(); + s.Driver.WaitForAndClick(By.Id("toggleInputSelection")); 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())); @@ -878,7 +878,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click(); // Seed backup page - var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic"); + var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic"); Assert.Equal(mnemonic.ToString(), recoveryPhrase); Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource); diff --git a/BTCPayServer/Views/Home/RecoverySeedBackup.cshtml b/BTCPayServer/Views/Home/RecoverySeedBackup.cshtml index 9e658ad1e..b86e2b2b6 100644 --- a/BTCPayServer/Views/Home/RecoverySeedBackup.cshtml +++ b/BTCPayServer/Views/Home/RecoverySeedBackup.cshtml @@ -9,13 +9,13 @@ }
@@ -33,7 +33,7 @@ Write them down on a piece of paper in the exact order.

-
    +
      @foreach (var word in Model.Words) {
    1. @@ -71,7 +71,7 @@ @if (Model.RequireConfirm) { -
      + diff --git a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml index c612bbe84..4c8d93f3d 100644 --- a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml +++ b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml @@ -16,7 +16,7 @@
      @if (Model.CanUseHotWallet) { - +
      @@ -45,7 +45,7 @@
      - +
      diff --git a/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml b/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml index 0fae9967e..56bd9a525 100644 --- a/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml +++ b/BTCPayServer/Views/Stores/ImportWallet/Seed.cshtml @@ -19,6 +19,7 @@
      @if (Model.CanUseHotWallet) { + ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet); ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport); ViewData.Add(nameof(Model.Method), Model.Method); diff --git a/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml b/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml index fb7e38004..cd59069d0 100644 --- a/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml +++ b/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml @@ -20,7 +20,7 @@ {