From 8d14848671615766b08d4dedad1e2d796963bfea Mon Sep 17 00:00:00 2001 From: wellingtonbalbo Date: Sat, 12 Apr 2025 13:07:20 -0300 Subject: [PATCH 1/5] Fix adding a confirmation for deletion of Email Rules. Also added a template on how we could call the confirmation screen using Javascript, without refreshing the page. Finally, added a UX way to close the modal, pressing ESC. --- .../Views/UIStores/StoreEmailRulesList.cshtml | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index e949b7fbf..3f5fa6b57 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -28,6 +28,24 @@ Email rules allow BTCPay Server to send customized emails from your store based on events.

+ + + + + + @if (Model.Any()) {
@@ -52,8 +70,8 @@ Edit - -
- Delete + + Delete
@@ -68,3 +86,25 @@ else There are no rules yet.

} + + From 7f8f92ec7467f6245888a5f4df164554f097b553 Mon Sep 17 00:00:00 2001 From: wellingtonbalbo Date: Sat, 12 Apr 2025 15:39:16 -0300 Subject: [PATCH 2/5] Fixed selenium test for this specific feature. --- BTCPayServer.Tests/SeleniumTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index dee563d2d..475b8c0fe 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -906,11 +906,17 @@ namespace BTCPayServer.Tests deleteLinks[0].Click(); + var confirmDelete = s.Driver.FindElement(By.XPath("//*[@id='deleteConfirmation']//button[contains(text(), 'Delete')]")); + confirmDelete.Click(); + deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); // Refresh list Assert.True(deleteLinks.Count == 1, "Expected one delete button remaining."); deleteLinks[0].Click(); + confirmDelete = s.Driver.FindElement(By.XPath("//*[@id='deleteConfirmation']//button[contains(text(), 'Delete')]")); + confirmDelete.Click(); + // Validate that there are no more rules Assert.Contains("There are no rules yet.", s.Driver.PageSource); } From c6d5246720547de3cb85c391c287c522e109f16c Mon Sep 17 00:00:00 2001 From: wellingtonbalbo Date: Thu, 17 Apr 2025 22:32:07 -0300 Subject: [PATCH 3/5] - Removed the JS functions created by me - Used the same logic as the StoreUsers.cshtml page to design the remove modal - Removed the unit test part related to the deletion of email rules, since using this way of remove above is not possible to unit test, at least I didn't found an example to look at. --- BTCPayServer.Tests/SeleniumTests.cs | 20 ------ .../Views/UIStores/StoreEmailRulesList.cshtml | 64 ++++--------------- 2 files changed, 14 insertions(+), 70 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 475b8c0fe..29732a4b1 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -899,26 +899,6 @@ namespace BTCPayServer.Tests // Validate that the email is updated in the list of email rules Assert.Contains("changedagain@gmail.com", s.Driver.PageSource); Assert.DoesNotContain("statuschanged@gmail.com", s.Driver.PageSource); - - // Delete both email rules - var deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); - Assert.True(deleteLinks.Count == 2, "Expected exactly two delete buttons but found a different number."); - - deleteLinks[0].Click(); - - var confirmDelete = s.Driver.FindElement(By.XPath("//*[@id='deleteConfirmation']//button[contains(text(), 'Delete')]")); - confirmDelete.Click(); - - deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); // Refresh list - Assert.True(deleteLinks.Count == 1, "Expected one delete button remaining."); - - deleteLinks[0].Click(); - - confirmDelete = s.Driver.FindElement(By.XPath("//*[@id='deleteConfirmation']//button[contains(text(), 'Delete')]")); - confirmDelete.Click(); - - // Validate that there are no more rules - Assert.Contains("There are no rules yet.", s.Driver.PageSource); } [Fact(Timeout = TestTimeout)] diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 3f5fa6b57..c12df92ad 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -1,3 +1,4 @@ +@using BTCPayServer.Abstractions.Models @using BTCPayServer.Client @using BTCPayServer.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers @@ -17,10 +18,12 @@

@ViewData["Title"]

- - Create Email Rule - +
+ + Create Email Rule + +
@@ -28,24 +31,6 @@ Email rules allow BTCPay Server to send customized emails from your store based on events.

- - - - - - @if (Model.Any()) {
@@ -56,7 +41,7 @@ Customer Email To Subject - Actions + @@ -67,12 +52,11 @@ @(rule.value.CustomerEmail ? "Yes" : "No") @rule.value.To @rule.value.Subject - - Edit - - -
- Delete -
+ +
+ Edit + Remove +
} @@ -87,24 +71,4 @@ else

} - + From 61782940c5574ad21e8c4ab8d4297a4e0607ce0f Mon Sep 17 00:00:00 2001 From: wellingtonbalbo Date: Fri, 18 Apr 2025 07:54:11 -0300 Subject: [PATCH 4/5] Removed the form tag because isn`t necessary. --- BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index c12df92ad..8cb334bc5 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -18,12 +18,10 @@

@ViewData["Title"]

-
- - Create Email Rule - -
+ + Create Email Rule +
From 892dda0d3bb0b9ba62c0240defb420f4aea3d71f Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 20 Apr 2025 23:40:43 +0900 Subject: [PATCH 5/5] Fix tests --- BTCPayServer.Tests/SeleniumTester.cs | 39 ++++++++++++------- BTCPayServer.Tests/SeleniumTests.cs | 23 +++++++++++ .../UIStoresController.EmailRules.cs | 3 ++ .../Views/UIStores/StoreEmailRulesList.cshtml | 2 +- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 599aca5e9..7efe66746 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -138,26 +138,37 @@ retry: } public IWebElement FindAlertMessage(params StatusMessageModel.StatusSeverity[] severity) { - var className = string.Join(", ", severity.Select(statusSeverity => $".alert-{StatusMessageModel.ToString(statusSeverity)}")); - IWebElement el; + int retry = 0; + retry: 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) + 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; } - catch (NoSuchElementException) + // Selenium sometimes sucks... + catch (StaleElementReferenceException) when (retry < 5) { - el = Driver.WaitForElement(By.CssSelector(className)); + retry++; + goto retry; } - 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; } public string Link(string relativeLink) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 29732a4b1..74259cbed 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -866,6 +866,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); // Ensure that the rule is created + s.FindAlertMessage(); Assert.DoesNotContain("There are no rules yet.", s.Driver.PageSource); Assert.Contains("invoicecreated@gmail.com", s.Driver.PageSource); Assert.Contains("Invoice {Invoice.Id} created", s.Driver.PageSource); @@ -881,6 +882,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); // Validate the second rule is added + s.FindAlertMessage(); Assert.Contains("statuschanged@gmail.com", s.Driver.PageSource); Assert.Contains("Status changed!", s.Driver.PageSource); @@ -897,8 +899,29 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); // Validate that the email is updated in the list of email rules + s.FindAlertMessage(); Assert.Contains("changedagain@gmail.com", s.Driver.PageSource); Assert.DoesNotContain("statuschanged@gmail.com", s.Driver.PageSource); + + // Delete both email rules + var deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Remove')]")); + Assert.True(deleteLinks.Count == 2, "Expected exactly two delete buttons but found a different number."); + + deleteLinks[0].Click(); + s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REMOVE"); + s.Driver.FindElement(By.Id("ConfirmContinue")).Click(); + + s.FindAlertMessage(); + deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Remove')]")); // Refresh list + Assert.True(deleteLinks.Count == 1, "Expected one delete button remaining."); + + deleteLinks[0].Click(); + s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REMOVE"); + s.Driver.FindElement(By.Id("ConfirmContinue")).Click(); + + s.FindAlertMessage(); + // Validate that there are no more rules + Assert.Contains("There are no rules yet.", s.Driver.PageSource); } [Fact(Timeout = TestTimeout)] diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs index 5a66d0b22..9f960cbad 100644 --- a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs +++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs @@ -72,6 +72,7 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); + this.TempData.SetStatusSuccess(StringLocalizer["Email rule successfully created"]); return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } @@ -111,6 +112,7 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); + this.TempData.SetStatusSuccess(StringLocalizer["Email rule successfully updated"]); return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } @@ -128,6 +130,7 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); + this.TempData.SetStatusSuccess(StringLocalizer["Email rule successfully deleted"]); return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 8cb334bc5..cd86b799c 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -53,7 +53,7 @@