Cleanup playwright tests (#6700)

This commit is contained in:
Nicolas Dorier
2025-04-26 21:50:12 +09:00
committed by GitHub
parent ec2368829e
commit dae4cc67b2
7 changed files with 152 additions and 148 deletions

View File

@@ -35,24 +35,24 @@ public class MultisigTests : UnitTestBase
await s.StartAsync();
var network = s.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
var resp1 = generateWalletResp("tprv8ZgxMBicQKsPeGSkDtxjScBmmHP4rfSEPkf1vNmoqt5QjPTco2zPd6UVWkJf2fU8gdKPYRdDMizxtMRqmpVpxsWuqRxVs2d5VsEhwxaK3h7",
var resp1 = generateWalletResp("tprv8ZgxMBicQKsPeGSkDtxjScBmmHP4rfSEPkf1vNmoqt5QjPTco2zPd6UVWkJf2fU8gdKPYRdDMizxtMRqmpVpxsWuqRxVs2d5VsEhwxaK3h7",
"57b3f43a/84'/1'/0'", "tpubDCzBHRPRcv7Y3utw1hZVrCar21gsj8vsXcehAG4z3R4NnmdMAASQwYYxGBd2f4q5s5ZFGvQBBFs1jVcGsXYoSTA1YFQPwizjsQLU12ibLyu", network);
var resp2 = generateWalletResp("tprv8ZgxMBicQKsPeC6Xuw83UJHgjnszEUjwH9E5f5FZ3fHgJHBQApo8CmFCsowcdwbRM119UnTqSzVWUsWGtLsxc8wnZa5L8xmEsvEpiyRj4Js",
var resp2 = generateWalletResp("tprv8ZgxMBicQKsPeC6Xuw83UJHgjnszEUjwH9E5f5FZ3fHgJHBQApo8CmFCsowcdwbRM119UnTqSzVWUsWGtLsxc8wnZa5L8xmEsvEpiyRj4Js",
"ee7d36c4/84'/1'/0'", "tpubDCetxnEjn8HXA5NrDZbKKTUUYoWCVC2V3X7Kmh3o9UYTfh9c3wTPKyCyeUrLkQ8KHYptEsBoQq6AgqPZiW5neEgb2kjKEr41q1qSevoPFDM", network);
var resp3 = generateWalletResp("tprv8ZgxMBicQKsPekSniuKwLtXpB82dSDV8ZAK4uLUHxkiHWfDtR5yYwNZiicKdpT3UYwzTTMvXESCm45KyAiH7kiJY6yk51neC9ZvmwDpNsQh",
var resp3 = generateWalletResp("tprv8ZgxMBicQKsPekSniuKwLtXpB82dSDV8ZAK4uLUHxkiHWfDtR5yYwNZiicKdpT3UYwzTTMvXESCm45KyAiH7kiJY6yk51neC9ZvmwDpNsQh",
"6c014fb3/84'/1'/0'", "tpubDCaTgjJfS5UEim6h66VpQBEZ2Tj6hHk8TzvL81HygdW1M8vZCRhUZLNhb3WTimyP2XMQRA3QGZPwwxUsEFQYK4EoRUWTcb9oB237FJ112tN", network);
var multisigDerivationScheme = $"wsh(multi(2,[{resp1.AccountKeyPath}]{resp1.DerivationScheme}/0/*," +
$"[{resp2.AccountKeyPath}]{resp2.DerivationScheme}/0/*," +
$"[{resp3.AccountKeyPath}]{resp3.DerivationScheme}/0/*))";
var strategy = UIStoresController.ParseDerivationStrategy(multisigDerivationScheme, network);
strategy.Source = "ManualDerivationScheme";
var derivationScheme = strategy.AccountDerivation;
var testPSBT =
"cHNidP8BAIkCAAAAAQmiSunnaKN7F4Jv5uHROfYbIZOckCck/Wo7gAQmi9hfAAAAAAD9////AtgbZgAAAAAAIgAgWCUFlU9eWkyxn0l0yQxs2rXQZ7d9Ry8LaYECaVC0TUGAlpgAAAAAACIAIFZxT+UIdhHZC4qFPhPQ6IXdX+44HIxCYcoh/bNOhB0hAAAAAAABAStAAf8AAAAAACIAIL2DDkfKwKHxZj2EKxXUd4uwf0IvPaCxUtAPq9snpq9TAQDqAgAAAAABAVuHuou9E5y6zUJaUreQD0wUeiPnT2aY+YU7QaPJOiQCAAAAAAD9////AkAB/wAAAAAAIgAgvYMOR8rAofFmPYQrFdR3i7B/Qi89oLFS0A+r2yemr1PM5AYpAQAAABYAFIlFupZkD07+GRo24WRS3IFcf+EuAkcwRAIgGi9wAcTfc0d0+j+Vg82aYklXCUsPg+g3jS+PTBTSQwkCIAPh5CZF18DTBKqWU2qdhNCbZ8Tp/NCEHjLJRHcH0oluASECWnI1s9ozQRL2qbK6JbLHzj9LlU9Pras3nZfq/njBJwhwAAAAAQVpUiECMCCasr2FRmRMiWkM/l1iraFR18td5SZ2APyQiaI0yY8hA8K96vH64BelUJiEPGwM6UTwRSfAJUR2j8dkw7i31fFTIQMlHLlaAPxw3fl1vaM1EofIirt79MXOryM54zpHwu1GlVOuIgIDwr3q8frgF6VQmIQ8bAzpRPBFJ8AlRHaPx2TDuLfV8VNHMEQCIANnprskJz8oVsetqOEViHtzhmSG8c36r3zmUIHwIoOhAiAZ1jBqj40iu2S/nMfiGyuCC/jSiSGik7YVwiwN+bbxPAEiBgIwIJqyvYVGZEyJaQz+XWKtoVHXy13lJnYA/JCJojTJjxhXs/Q6VAAAgAEAAIAAAACAAAAAAAUAAAAiBgMlHLlaAPxw3fl1vaM1EofIirt79MXOryM54zpHwu1GlRhsAU+zVAAAgAEAAIAAAACAAAAAAAUAAAAiBgPCverx+uAXpVCYhDxsDOlE8EUnwCVEdo/HZMO4t9XxUxjufTbEVAAAgAEAAIAAAACAAAAAAAUAAAAAAQFpUiEDa/J6SaiRjP1jhq9jpNxFKovEuWBz28seNMvsn0JC/ZIhA7p3bS7vLYB5UxlNN6YqkEDITyaMlk/i450q6+4woveAIQPTchIOrd+TNGBOX6il1HRZnBndyRoUj/hahbjTaAGHglOuIgIDa/J6SaiRjP1jhq9jpNxFKovEuWBz28seNMvsn0JC/ZIYV7P0OlQAAIABAACAAAAAgAEAAAABAAAAIgIDundtLu8tgHlTGU03piqQQMhPJoyWT+LjnSrr7jCi94AY7n02xFQAAIABAACAAAAAgAEAAAABAAAAIgID03ISDq3fkzRgTl+opdR0WZwZ3ckaFI/4WoW402gBh4IYbAFPs1QAAIABAACAAAAAgAEAAAABAAAAAAEBaVIhA/fCRR3MWwCgNuXMvlWLonY+TurUKOHXOSHALCck62deIQPqeQXD8ws9SDEDXSyD6a3WFlIGH+gDUf2/xAfw8HxE8iEC3LBRJYYxRzIeg9NxLGvtfATvFaKsO9D7AUjoTLZzke5TriICAtywUSWGMUcyHoPTcSxr7XwE7xWirDvQ+wFI6Ey2c5HuGGwBT7NUAACAAQAAgAAAAIAAAAAADAAAACICA+p5BcPzCz1IMQNdLIPprdYWUgYf6ANR/b/EB/DwfETyGO59NsRUAACAAQAAgAAAAIAAAAAADAAAACICA/fCRR3MWwCgNuXMvlWLonY+TurUKOHXOSHALCck62deGFez9DpUAACAAQAAgAAAAIAAAAAADAAAAAA=";
var signedPsbt = SignWithSeed(testPSBT, derivationScheme, resp1);
s.TestLogs.LogInformation($"Signed PSBT: {signedPsbt}");
}
@@ -62,22 +62,22 @@ public class MultisigTests : UnitTestBase
public async Task CanEnableAndUseMultisigWallet()
{
var cryptoCode = "BTC";
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
await s.RegisterNewUser(true);
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
var resp1 = generateWalletResp("tprv8ZgxMBicQKsPeGSkDtxjScBmmHP4rfSEPkf1vNmoqt5QjPTco2zPd6UVWkJf2fU8gdKPYRdDMizxtMRqmpVpxsWuqRxVs2d5VsEhwxaK3h7",
var resp1 = generateWalletResp("tprv8ZgxMBicQKsPeGSkDtxjScBmmHP4rfSEPkf1vNmoqt5QjPTco2zPd6UVWkJf2fU8gdKPYRdDMizxtMRqmpVpxsWuqRxVs2d5VsEhwxaK3h7",
"57b3f43a/84'/1'/0'", "tpubDCzBHRPRcv7Y3utw1hZVrCar21gsj8vsXcehAG4z3R4NnmdMAASQwYYxGBd2f4q5s5ZFGvQBBFs1jVcGsXYoSTA1YFQPwizjsQLU12ibLyu", network);
var resp2 = generateWalletResp("tprv8ZgxMBicQKsPeC6Xuw83UJHgjnszEUjwH9E5f5FZ3fHgJHBQApo8CmFCsowcdwbRM119UnTqSzVWUsWGtLsxc8wnZa5L8xmEsvEpiyRj4Js",
var resp2 = generateWalletResp("tprv8ZgxMBicQKsPeC6Xuw83UJHgjnszEUjwH9E5f5FZ3fHgJHBQApo8CmFCsowcdwbRM119UnTqSzVWUsWGtLsxc8wnZa5L8xmEsvEpiyRj4Js",
"ee7d36c4/84'/1'/0'", "tpubDCetxnEjn8HXA5NrDZbKKTUUYoWCVC2V3X7Kmh3o9UYTfh9c3wTPKyCyeUrLkQ8KHYptEsBoQq6AgqPZiW5neEgb2kjKEr41q1qSevoPFDM", network);
var resp3 = generateWalletResp("tprv8ZgxMBicQKsPekSniuKwLtXpB82dSDV8ZAK4uLUHxkiHWfDtR5yYwNZiicKdpT3UYwzTTMvXESCm45KyAiH7kiJY6yk51neC9ZvmwDpNsQh",
var resp3 = generateWalletResp("tprv8ZgxMBicQKsPekSniuKwLtXpB82dSDV8ZAK4uLUHxkiHWfDtR5yYwNZiicKdpT3UYwzTTMvXESCm45KyAiH7kiJY6yk51neC9ZvmwDpNsQh",
"6c014fb3/84'/1'/0'", "tpubDCaTgjJfS5UEim6h66VpQBEZ2Tj6hHk8TzvL81HygdW1M8vZCRhUZLNhb3WTimyP2XMQRA3QGZPwwxUsEFQYK4EoRUWTcb9oB237FJ112tN", network);
var multisigDerivationScheme = $"wsh(multi(2,[{resp1.AccountKeyPath}]{resp1.DerivationScheme}/0/*," +
$"[{resp2.AccountKeyPath}]{resp2.DerivationScheme}/0/*," +
$"[{resp3.AccountKeyPath}]{resp3.DerivationScheme}/0/*))";
var strategy = UIStoresController.ParseDerivationStrategy(multisigDerivationScheme, network);
strategy.Source = "ManualDerivationScheme";
var derivationScheme = strategy.AccountDerivation;
@@ -89,7 +89,7 @@ public class MultisigTests : UnitTestBase
await s.Page.ClickAsync("#Continue");
await s.Page.ClickAsync("#Confirm");
s.TestLogs.LogInformation($"Multisig wallet setup: {multisigDerivationScheme}");
// fetch address from receive page
await s.Page.ClickAsync("#WalletNav-Receive");
@@ -106,15 +106,15 @@ public class MultisigTests : UnitTestBase
var amount = "0.1";
await s.Page.FillAsync("#Outputs_0__Amount", amount);
await s.Page.ClickAsync("#CreatePendingTransaction");
// validating the state of UI
Assert.Equal("0", await s.Page.TextContentAsync("#Sigs_0__Collected"));
Assert.Equal("2/3", await s.Page.TextContentAsync("#Sigs_0__Scheme"));
// now proceeding to click on sign button and sign transactions
await SignPendingTransactionWithKey(s, address, derivationScheme, resp1);
Assert.Equal("1", await s.Page.TextContentAsync("#Sigs_0__Collected"));
await SignPendingTransactionWithKey(s, address, derivationScheme, resp2);
Assert.Equal("2", await s.Page.TextContentAsync("#Sigs_0__Collected"));
@@ -122,10 +122,10 @@ public class MultisigTests : UnitTestBase
await s.Page.ClickAsync("//a[text()='Broadcast']");
await s.Page.ClickAsync("#BroadcastTransaction");
await s.FindAlertMessage(partialText: "Transaction broadcasted successfully");
// now that we broadcast transaction, there shouldn't be broadcast button
Assert.False(await s.Page.Locator("//a[text()='Broadcast']").IsVisibleAsync());
// Abort pending transaction flow
await s.Page.ClickAsync("#WalletNav-Send");
await s.Page.FillAsync("#Outputs_0__DestinationAddress", address);
@@ -136,7 +136,7 @@ public class MultisigTests : UnitTestBase
await s.Page.ClickAsync("#ConfirmContinue");
await s.FindAlertMessage(partialText: "Aborted Pending Transaction");
s.TestLogs.LogInformation($"Finished MultiSig Flow");
}
@@ -154,7 +154,7 @@ public class MultisigTests : UnitTestBase
await s.Page.ClickAsync("#ShowRawVersion");
var psbt = await s.Page.Locator("#psbt-base64").TextContentAsync();
// signing PSBT and entering it to submit
var signedPsbt = SignWithSeed(psbt, derivationScheme, signingKey);
@@ -169,10 +169,10 @@ public class MultisigTests : UnitTestBase
var key1 = new BitcoinExtKey(
ExtKey.Parse(tpriv, Network.RegTest),
Network.RegTest);
var parser = new DerivationSchemeParser(network);
var resp1 = new GenerateWalletResponse
{
MasterHDKey = key1,
@@ -192,13 +192,13 @@ public class MultisigTests : UnitTestBase
var strKeypath = resp.AccountKeyPath.ToStringWithEmptyKeyPathAware();
RootedKeyPath rootedKeyPath = RootedKeyPath.Parse(strKeypath);
if (rootedKeyPath.MasterFingerprint != extKey.GetPublicKey().GetHDFingerPrint())
throw new Exception("Master fingerprint mismatch. Ensure the wallet matches the PSBT.");
// finished setting variables, now onto signing
var psbt = PSBT.Parse(psbtBase64, Network.RegTest);
// Sign the PSBT
extKey = extKey.Derive(rootedKeyPath.KeyPath);
psbt.Settings.SigningOptions = new SigningOptions();

View File

@@ -23,7 +23,7 @@ namespace BTCPayServer.Tests
[Trait("Playwright", "Playwright")]
public async Task CanPlayWithPSBT()
{
using var s = CreatePlaywrightTester(newDb: true);
await using var s = CreatePlaywrightTester(newDb: true);
await s.StartAsync();
await s.RegisterNewUser(true);

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -19,7 +20,7 @@ using Xunit;
namespace BTCPayServer.Tests
{
public class PlaywrightTester : IDisposable
public class PlaywrightTester : IAsyncDisposable
{
public Uri ServerUri;
private string CreatedUser;
@@ -256,7 +257,7 @@ namespace BTCPayServer.Tests
{
await Page.ClickAsync("#ActionsDropdownToggle");
await Page.ClickAsync("#ChangeWalletLink");
await Page.Locator("#ConfirmInput").FillAsync("REPLACE");
await Page.FillAsync("#ConfirmInput", "REPLACE");
await Page.ClickAsync("#ConfirmContinue");
}
@@ -265,7 +266,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Progressing with existing seed");
await Page.ClickAsync("#ImportWalletOptionsLink");
await Page.ClickAsync("#ImportSeedLink");
await Page.Locator("#ExistingMnemonic").FillAsync(seed);
await Page.FillAsync("#ExistingMnemonic", seed);
await Page.Locator("#SavePrivateKeys").SetCheckedAsync(isHotWallet);
}
else
@@ -330,38 +331,46 @@ namespace BTCPayServer.Tests
}
public async Task LogIn(string user, string password = "123456")
{
await Page.Locator("#Email").FillAsync(user);
await Page.Locator("#Password").FillAsync(password);
await Page.Locator("#LoginButton").ClickAsync();
await Page.FillAsync("#Email", user);
await Page.FillAsync("#Password", password);
await Page.ClickAsync("#LoginButton");
}
public async Task GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
{
await Page.Locator("#Nav-Account").ClickAsync();
await Page.Locator("#Nav-ManageAccount").ClickAsync();
await Page.ClickAsync("#Nav-Account");
await Page.ClickAsync("#Nav-ManageAccount");
if (navPages != ManageNavPages.Index)
{
await Page.Locator($"#SectionNav-{navPages.ToString()}").ClickAsync();
await Page.ClickAsync($"#SectionNav-{navPages.ToString()}");
}
}
public async Task GoToServer(ServerNavPages navPages = ServerNavPages.Policies)
{
await Page.Locator("#Nav-ServerSettings").ClickAsync();
await Page.ClickAsync("#Nav-ServerSettings");
if (navPages != ServerNavPages.Policies)
{
await Page.Locator($"#SectionNav-{navPages}").ClickAsync();
await Page.ClickAsync($"#SectionNav-{navPages}");
}
}
public async Task ClickOnAllSectionLinks()
public async Task ClickOnAllSectionLinks(string sectionSelector = "#SectionNav")
{
var links = await Page.Locator("#SectionNav .nav-link").EvaluateAllAsync<string[]>("els => els.map(e => e.href)");
List<string> links = [];
foreach (var locator in await Page.Locator($"{sectionSelector} .nav-link").AllAsync())
{
var link = await locator.GetAttributeAsync("href");
if (link is null or "/logout")
continue;
Assert.NotNull(link);
links.Add(link);
}
Assert.NotEmpty(links);
foreach (var link in links)
{
TestLogs.LogInformation($"Checking no error on {link}");
await Page.GotoAsync(link);
await GoToUrl(link);
await Page.AssertNoError();
}
}
@@ -429,24 +438,29 @@ namespace BTCPayServer.Tests
}
public void Dispose()
public async ValueTask DisposeAsync()
{
static void Try(Action action)
static async Task Try(Func<Task> action)
{
try
{ action(); }
{ await action(); }
catch { }
}
Try(() =>
await Try(async () =>
{
Page?.CloseAsync().GetAwaiter().GetResult();
if (Page is null)
return;
await Page.CloseAsync();
Page = null;
});
Try(() =>
await Try(async () =>
{
Browser?.CloseAsync().GetAwaiter().GetResult();
if (Browser is null)
return;
await Browser.CloseAsync();
Browser = null;
});
Server?.Dispose();

View File

@@ -24,13 +24,13 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanNavigateServerSettings()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
await s.RegisterNewUser(true);
await s.GoToHome();
await s.GoToServer();
await s.Page.AssertNoError();
await s.ClickOnAllSectionLinks();
await s.ClickOnAllSectionLinks("#mainNavSettings");
await s.GoToServer(ServerNavPages.Services);
s.TestLogs.LogInformation("Let's check if we can access the logs");
await s.Page.GetByRole(AriaRole.Link, new() { Name = "Logs" }).ClickAsync();
@@ -42,24 +42,22 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanUseForms()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
await s.InitializeBTCPayServer();
// Point Of Sale
var appName = $"PoS-{Guid.NewGuid().ToString()[..21]}";
await s.Page.ClickAsync("#StoreNav-CreatePointOfSale");
await s.Page.Locator("#AppName").FillAsync(appName);
await s.Page.FillAsync("#AppName", appName);
await s.ClickPagePrimary();
var textContent = await (await s.FindAlertMessage()).TextContentAsync();
Assert.Contains("App successfully created", textContent);
await s.FindAlertMessage(partialText: "App successfully created");
await s.Page.SelectOptionAsync("#FormId", "Email");
await s.ClickPagePrimary();
textContent = await (await s.FindAlertMessage()).TextContentAsync();
Assert.Contains("App updated", textContent);
await s.FindAlertMessage(partialText: "App updated");
await s.Page.ClickAsync("#ViewApp");
var popOutPage = await s.Page.Context.WaitForPageAsync();
await popOutPage.Locator("button[type='submit']").First.ClickAsync();
await popOutPage.Locator("[name='buyerEmail']").FillAsync("aa@aa.com");
await popOutPage.FillAsync("[name='buyerEmail']", "aa@aa.com");
await popOutPage.ClickAsync("input[type='submit']");
await s.PayInvoiceAsync(popOutPage, true);
var invoiceId = popOutPage.Url[(popOutPage.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
@@ -71,8 +69,8 @@ namespace BTCPayServer.Tests
// Payment Request
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.ClickPagePrimary();
await s.Page.Locator("#Title").FillAsync("Pay123");
await s.Page.Locator("#Amount").FillAsync("700");
await s.Page.FillAsync("#Title", "Pay123");
await s.Page.FillAsync("#Amount", "700");
await s.Page.SelectOptionAsync("#FormId", "Email");
await s.ClickPagePrimary();
await s.Page.Locator("a[id^='Edit-']").First.ClickAsync();
@@ -81,7 +79,7 @@ namespace BTCPayServer.Tests
popOutPage = await s.Page.Context.WaitForPageAsync();
await popOutPage.ClickAsync("[data-test='form-button']");
Assert.Contains("Enter your email", await popOutPage.ContentAsync());
await popOutPage.Locator("input[name='buyerEmail']").FillAsync("aa@aa.com");
await popOutPage.FillAsync("input[name='buyerEmail']", "aa@aa.com");
await popOutPage.ClickAsync("#page-primary");
invoiceId = popOutPage.Url.Split('/').Last();
await popOutPage.CloseAsync();
@@ -96,19 +94,19 @@ namespace BTCPayServer.Tests
await s.GoToStore(StoreNavPages.Forms);
Assert.Contains("There are no forms yet.", await s.Page.ContentAsync());
await s.ClickPagePrimary();
await s.Page.Locator("[name='Name']").FillAsync("Custom Form 1");
await s.Page.Locator("#ApplyEmailTemplate").ClickAsync();
await s.Page.FillAsync("[name='Name']", "Custom Form 1");
await s.Page.ClickAsync("#ApplyEmailTemplate");
await s.Page.ClickAsync("#CodeTabButton");
await s.Page.Locator("#CodeTabPane").WaitForAsync();
var config = await s.Page.Locator("[name='FormConfig']").InputValueAsync();
Assert.Contains("buyerEmail", config);
await s.Page.Locator("[name='FormConfig']").ClearAsync();
await s.Page.Locator("[name='FormConfig']").FillAsync(config.Replace("Enter your email", "CustomFormInputTest"));
await s.Page.FillAsync("[name='FormConfig']", config.Replace("Enter your email", "CustomFormInputTest"));
await s.ClickPagePrimary();
await s.Page.ClickAsync("#ViewForm");
var formUrl = s.Page.Url;
Assert.Contains("CustomFormInputTest", await s.Page.ContentAsync());
await s.Page.Locator("[name='buyerEmail']").FillAsync("aa@aa.com");
await s.Page.FillAsync("[name='buyerEmail']", "aa@aa.com");
await s.Page.ClickAsync("input[type='submit']");
await s.PayInvoiceAsync(s.Page, true, 0.001m);
var result = await s.Server.PayTester.HttpClient.GetAsync(formUrl);
@@ -118,17 +116,17 @@ namespace BTCPayServer.Tests
await s.GoToStore(StoreNavPages.Forms);
Assert.Contains("Custom Form 1", await s.Page.ContentAsync());
await s.Page.GetByRole(AriaRole.Link, new() { Name = "Remove" }).ClickAsync();
await s.Page.Locator("#ConfirmInput").FillAsync("DELETE");
await s.Page.FillAsync("#ConfirmInput", "DELETE");
await s.Page.ClickAsync("#ConfirmContinue");
Assert.DoesNotContain("Custom Form 1", await s.Page.ContentAsync());
await s.ClickPagePrimary();
await s.Page.Locator("[name='Name']").FillAsync("Custom Form 2");
await s.Page.FillAsync("[name='Name']", "Custom Form 2");
await s.Page.ClickAsync("#ApplyEmailTemplate");
await s.Page.ClickAsync("#CodeTabButton");
await s.Page.Locator("#CodeTabPane").WaitForAsync();
await s.Page.Locator("input[type='checkbox'][name='Public']").SetCheckedAsync(true);
await s.Page.Locator("[name='FormConfig']").ClearAsync();
await s.Page.Locator("[name='FormConfig']").FillAsync(config.Replace("Enter your email", "CustomFormInputTest2"));
await s.Page.FillAsync("[name='FormConfig']", config.Replace("Enter your email", "CustomFormInputTest2"));
await s.ClickPagePrimary();
await s.Page.ClickAsync("#ViewForm");
formUrl = s.Page.Url;
@@ -140,7 +138,7 @@ namespace BTCPayServer.Tests
Assert.Contains("Custom Form 2", await s.Page.ContentAsync());
await s.Page.GetByRole(AriaRole.Link, new() { Name = "Custom Form 2" }).ClickAsync();
await s.Page.Locator("[name='Name']").ClearAsync();
await s.Page.Locator("[name='Name']").FillAsync("Custom Form 3");
await s.Page.FillAsync("[name='Name']", "Custom Form 3");
await s.ClickPagePrimary();
await s.GoToStore(StoreNavPages.Forms);
Assert.Contains("Custom Form 3", await s.Page.ContentAsync());
@@ -154,7 +152,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanChangeUserMail()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
var tester = s.Server;
var u1 = tester.NewAccount();
@@ -167,14 +165,13 @@ namespace BTCPayServer.Tests
await s.LogIn(u1.RegisterDetails.Email, u1.RegisterDetails.Password);
await s.GoToProfile();
await s.Page.Locator("#Email").ClearAsync();
await s.Page.Locator("#Email").FillAsync(u2.RegisterDetails.Email);
await s.Page.FillAsync("#Email", u2.RegisterDetails.Email);
await s.ClickPagePrimary();
var alert = await s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
Assert.Contains("The email address is already in use with an other account.", await alert.TextContentAsync());
await s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error, partialText: "The email address is already in use with an other account.");
await s.GoToProfile();
await s.Page.Locator("#Email").ClearAsync();
var changedEmail = Guid.NewGuid() + "@lol.com";
await s.Page.Locator("#Email").FillAsync(changedEmail);
await s.Page.FillAsync("#Email", changedEmail);
await s.ClickPagePrimary();
await s.FindAlertMessage();
var manager = tester.PayTester.GetService<UserManager<ApplicationUser>>();
@@ -185,7 +182,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanManageUsers()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
await s.RegisterNewUser();
var user = s.AsTestAccount();
@@ -198,67 +195,62 @@ namespace BTCPayServer.Tests
// Manage user password reset
await s.Page.Locator("#SearchTerm").ClearAsync();
await s.Page.Locator("#SearchTerm").FillAsync(user.RegisterDetails.Email);
await s.Page.FillAsync("#SearchTerm", user.RegisterDetails.Email);
await s.Page.Locator("#SearchTerm").PressAsync("Enter");
var rows = s.Page.Locator("#UsersList tr.user-overview-row");
Assert.Equal(1, await rows.CountAsync());
Assert.Contains(user.RegisterDetails.Email, await rows.First.TextContentAsync());
await s.Page.Locator("#UsersList tr.user-overview-row:first-child .reset-password").ClickAsync();
await s.Page.Locator("#Password").FillAsync("Password@1!");
await s.Page.Locator("#ConfirmPassword").FillAsync("Password@1!");
await s.Page.ClickAsync("#UsersList tr.user-overview-row:first-child .reset-password");
await s.Page.FillAsync("#Password", "Password@1!");
await s.Page.FillAsync("#ConfirmPassword", "Password@1!");
await s.ClickPagePrimary();
var passwordSetAlert = await s.FindAlertMessage();
Assert.Contains("Password successfully set", await passwordSetAlert.TextContentAsync());
await s.FindAlertMessage(partialText: "Password successfully set");
// Manage user status (disable and enable)
// Disable user
await s.Page.Locator("#SearchTerm").ClearAsync();
await s.Page.Locator("#SearchTerm").FillAsync(user.RegisterDetails.Email);
await s.Page.FillAsync("#SearchTerm", user.RegisterDetails.Email);
await s.Page.Locator("#SearchTerm").PressAsync("Enter");
rows = s.Page.Locator("#UsersList tr.user-overview-row");
Assert.Equal(1, await rows.CountAsync());
Assert.Contains(user.RegisterDetails.Email, await rows.First.TextContentAsync());
await s.Page.Locator("#UsersList tr.user-overview-row:first-child .disable-user").ClickAsync();
await s.Page.Locator("#ConfirmContinue").ClickAsync();
var disabledUserAlert = await s.FindAlertMessage();
Assert.Contains("User disabled", await disabledUserAlert.TextContentAsync());
await s.Page.ClickAsync("#UsersList tr.user-overview-row:first-child .disable-user");
await s.Page.ClickAsync("#ConfirmContinue");
await s.FindAlertMessage(partialText: "User disabled");
//Enable user
await s.Page.Locator("#SearchTerm").ClearAsync();
await s.Page.Locator("#SearchTerm").FillAsync(user.RegisterDetails.Email);
await s.Page.FillAsync("#SearchTerm", user.RegisterDetails.Email);
await s.Page.Locator("#SearchTerm").PressAsync("Enter");
rows = s.Page.Locator("#UsersList tr.user-overview-row");
Assert.Equal(1, await rows.CountAsync());
Assert.Contains(user.RegisterDetails.Email, await rows.First.TextContentAsync());
await s.Page.Locator("#UsersList tr.user-overview-row:first-child .enable-user").ClickAsync();
await s.Page.Locator("#ConfirmContinue").ClickAsync();
var enabledUserAlert = await s.FindAlertMessage();
Assert.Contains("User enabled", await enabledUserAlert.TextContentAsync());
await s.Page.ClickAsync("#UsersList tr.user-overview-row:first-child .enable-user");
await s.Page.ClickAsync("#ConfirmContinue");
await s.FindAlertMessage(partialText: "User enabled");
// Manage user details (edit)
await s.Page.Locator("#SearchTerm").ClearAsync();
await s.Page.Locator("#SearchTerm").FillAsync(user.RegisterDetails.Email);
await s.Page.FillAsync("#SearchTerm", user.RegisterDetails.Email);
await s.Page.Locator("#SearchTerm").PressAsync("Enter");
rows = s.Page.Locator("#UsersList tr.user-overview-row");
Assert.Equal(1, await rows.CountAsync());
Assert.Contains(user.RegisterDetails.Email, await rows.First.TextContentAsync());
await s.Page.Locator("#UsersList tr.user-overview-row:first-child .user-edit").ClickAsync();
await s.Page.Locator("#Name").FillAsync("Test User");
await s.Page.ClickAsync("#UsersList tr.user-overview-row:first-child .user-edit");
await s.Page.FillAsync("#Name", "Test User");
await s.ClickPagePrimary();
var editUserAlert = await s.FindAlertMessage();
Assert.Contains("User successfully updated", await editUserAlert.TextContentAsync());
await s.FindAlertMessage(partialText: "User successfully updated");
// Manage user deletion
await s.GoToServer(ServerNavPages.Users);
await s.Page.Locator("#SearchTerm").ClearAsync();
await s.Page.Locator("#SearchTerm").FillAsync(user.RegisterDetails.Email);
await s.Page.FillAsync("#SearchTerm", user.RegisterDetails.Email);
await s.Page.Locator("#SearchTerm").PressAsync("Enter");
rows = s.Page.Locator("#UsersList tr.user-overview-row");
Assert.Equal(1, await rows.CountAsync());
Assert.Contains(user.RegisterDetails.Email, await rows.First.TextContentAsync());
await s.Page.Locator("#UsersList tr.user-overview-row:first-child .delete-user").ClickAsync();
await s.Page.Locator("#ConfirmContinue").ClickAsync();
var userDeletionAlert = await s.FindAlertMessage();
Assert.Contains("User deleted", await userDeletionAlert.TextContentAsync());
await s.Page.ClickAsync("#UsersList tr.user-overview-row:first-child .delete-user");
await s.Page.ClickAsync("#ConfirmContinue");
await s.FindAlertMessage(partialText: "User deleted");
await s.Page.AssertNoError();
}
@@ -266,7 +258,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanUseSSHService()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
var settings = s.Server.PayTester.GetService<SettingsRepository>();
var policies = await settings.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
@@ -287,8 +279,8 @@ namespace BTCPayServer.Tests
await s.GoToUrl("/server/services/ssh");
await s.Page.AssertNoError();
await s.Page.Locator("#SSHKeyFileContent").ClearAsync();
await s.Page.Locator("#SSHKeyFileContent").FillAsync("tes't\r\ntest2");
await s.Page.Locator("#submit").ClickAsync();
await s.Page.FillAsync("#SSHKeyFileContent", "tes't\r\ntest2");
await s.Page.ClickAsync("#submit");
await s.Page.AssertNoError();
var text = await s.Page.Locator("#SSHKeyFileContent").TextContentAsync();
@@ -298,15 +290,15 @@ namespace BTCPayServer.Tests
Assert.True((await s.Page.ContentAsync()).Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
await s.Page.Locator("#SSHKeyFileContent").ClearAsync();
await s.Page.Locator("#submit").ClickAsync();
await s.Page.ClickAsync("#submit");
text = await s.Page.Locator("#SSHKeyFileContent").TextContentAsync();
Assert.DoesNotContain("test2", text);
// Let's try to disable it now
await s.Page.Locator("#disable").ClickAsync();
await s.Page.Locator("#ConfirmInput").FillAsync("DISABLE");
await s.Page.Locator("#ConfirmContinue").ClickAsync();
await s.Page.ClickAsync("#disable");
await s.Page.FillAsync("#ConfirmInput", "DISABLE");
await s.Page.ClickAsync("#ConfirmContinue");
await s.GoToUrl("/server/services/ssh");
Assert.True((await s.Page.ContentAsync()).Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
@@ -321,7 +313,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanSetupEmailServer()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
await s.RegisterNewUser(true);
await s.CreateNewStore();
@@ -331,8 +323,7 @@ namespace BTCPayServer.Tests
if (await s.Page.Locator("#ResetPassword").IsVisibleAsync())
{
await s.Page.ClickAsync("#ResetPassword");
var responseAlert = await s.FindAlertMessage();
Assert.Contains("Email server password reset", await responseAlert.TextContentAsync());
await s.FindAlertMessage(partialText: "Email server password reset");
}
await s.Page.Locator("#Settings_Login").ClearAsync();
await s.Page.Locator("#Settings_From").ClearAsync();
@@ -373,9 +364,9 @@ namespace BTCPayServer.Tests
await s.Page.ClickAsync("#CreateEmailRule");
await s.Page.Locator("#Trigger").SelectOptionAsync(new[] { "InvoicePaymentSettled" });
await s.Page.Locator("#To").FillAsync("test@gmail.com");
await s.Page.FillAsync("#To", "test@gmail.com");
await s.Page.ClickAsync("#CustomerEmail");
await s.Page.Locator("#Subject").FillAsync("Thanks!");
await s.Page.FillAsync("#Subject", "Thanks!");
await s.Page.Locator(".note-editable").FillAsync("Your invoice is settled");
await s.Page.ClickAsync("#SaveEmailRules");
// we now have a rule
@@ -389,7 +380,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task NewUserLogin()
{
using var s = CreatePlaywrightTester();
await using var s = CreatePlaywrightTester();
await s.StartAsync();
//Register & Log Out
var email = await s.RegisterNewUser();
@@ -403,8 +394,8 @@ namespace BTCPayServer.Tests
// We should be redirected to login
//Same User Can Log Back In
await s.Page.Locator("#Email").FillAsync(email);
await s.Page.Locator("#Password").FillAsync("123456");
await s.Page.FillAsync("#Email", email);
await s.Page.FillAsync("#Password", "123456");
await s.Page.ClickAsync("#LoginButton");
// We should be redirected to invoice
@@ -419,21 +410,21 @@ namespace BTCPayServer.Tests
//Change Password & Log Out
var newPassword = "abc???";
await s.GoToProfile(ManageNavPages.ChangePassword);
await s.Page.Locator("#OldPassword").FillAsync("123456");
await s.Page.Locator("#NewPassword").FillAsync(newPassword);
await s.Page.Locator("#ConfirmPassword").FillAsync(newPassword);
await s.Page.FillAsync("#OldPassword", "123456");
await s.Page.FillAsync("#NewPassword", newPassword);
await s.Page.FillAsync("#ConfirmPassword", newPassword);
await s.ClickPagePrimary();
await s.Logout();
await s.Page.AssertNoError();
//Log In With New Password
await s.Page.Locator("#Email").FillAsync(email);
await s.Page.Locator("#Password").FillAsync(newPassword);
await s.Page.Locator("#LoginButton").ClickAsync();
await s.Page.FillAsync("#Email", email);
await s.Page.FillAsync("#Password", newPassword);
await s.Page.ClickAsync("#LoginButton");
await s.GoToHome();
await s.GoToProfile();
await s.ClickOnAllSectionLinks();
await s.ClickOnAllSectionLinks("#mainNavSettings");
//let's test invite link
await s.Logout();
@@ -444,7 +435,7 @@ namespace BTCPayServer.Tests
await s.ClickPagePrimary();
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
await s.Page.Locator("#Email").FillAsync(usr);
await s.Page.FillAsync("#Email", usr);
await s.ClickPagePrimary();
var url = await s.Page.Locator("#InvitationUrl").GetAttributeAsync("data-text");
Assert.NotNull(url);
@@ -453,14 +444,12 @@ namespace BTCPayServer.Tests
Assert.Equal("hidden", await s.Page.Locator("#Email").GetAttributeAsync("type"));
Assert.Equal(usr, await s.Page.Locator("#Email").GetAttributeAsync("value"));
Assert.Equal("Create Account", await s.Page.Locator("h4").TextContentAsync());
var invitationAlert = await s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
Assert.Contains("Invitation accepted. Please set your password.", await invitationAlert.TextContentAsync());
await s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info, partialText: "Invitation accepted. Please set your password.");
await s.Page.Locator("#Password").FillAsync("123456");
await s.Page.Locator("#ConfirmPassword").FillAsync("123456");
await s.Page.FillAsync("#Password", "123456");
await s.Page.FillAsync("#ConfirmPassword", "123456");
await s.ClickPagePrimary();
var accountCreationAlert = await s.FindAlertMessage();
Assert.Contains("Account successfully created.", await accountCreationAlert.TextContentAsync());
await s.FindAlertMessage(partialText: "Account successfully created.");
// We should be logged in now
await s.GoToHome();
@@ -468,9 +457,9 @@ namespace BTCPayServer.Tests
//let's test delete user quickly while we're at it
await s.GoToProfile();
await s.Page.Locator("#delete-user").ClickAsync();
await s.Page.Locator("#ConfirmInput").FillAsync("DELETE");
await s.Page.Locator("#ConfirmContinue").ClickAsync();
await s.Page.ClickAsync("#delete-user");
await s.Page.FillAsync("#ConfirmInput", "DELETE");
await s.Page.ClickAsync("#ConfirmContinue");
Assert.Contains("/login", s.Page.Url);
}
@@ -478,19 +467,19 @@ namespace BTCPayServer.Tests
private static async Task CanSetupEmailCore(PlaywrightTester s)
{
await s.Page.Locator("#QuickFillDropdownToggle").ScrollIntoViewIfNeededAsync();
await s.Page.Locator("#QuickFillDropdownToggle").ClickAsync();
await s.Page.Locator("#quick-fill .dropdown-menu .dropdown-item:first-child").ClickAsync();
await s.Page.ClickAsync("#QuickFillDropdownToggle");
await s.Page.ClickAsync("#quick-fill .dropdown-menu .dropdown-item:first-child");
await s.Page.Locator("#Settings_Login").ClearAsync();
await s.Page.Locator("#Settings_Login").FillAsync("test@gmail.com");
await s.Page.FillAsync("#Settings_Login", "test@gmail.com");
await s.Page.Locator("#Settings_Password").ClearAsync();
await s.Page.Locator("#Settings_Password").FillAsync("mypassword");
await s.Page.FillAsync("#Settings_Password", "mypassword");
await s.Page.Locator("#Settings_From").ClearAsync();
await s.Page.Locator("#Settings_From").FillAsync("Firstname Lastname <email@example.com>");
await s.Page.FillAsync("#Settings_From", "Firstname Lastname <email@example.com>");
await s.ClickPagePrimary();
await s.FindAlertMessage(partialText: "Email settings saved");
Assert.Contains("Configured", await s.Page.ContentAsync());
await s.Page.Locator("#Settings_Login").ClearAsync();
await s.Page.Locator("#Settings_Login").FillAsync("test_fix@gmail.com");
await s.Page.FillAsync("#Settings_Login", "test_fix@gmail.com");
await s.ClickPagePrimary();
await s.FindAlertMessage(partialText: "Email settings saved");
Assert.Contains("Configured", await s.Page.ContentAsync());

View File

@@ -107,13 +107,13 @@
@if (ViewData.IsCategoryActive(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsPageActive([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsPageActive([StoreNavPages.OnchainSettings], categoryId))
{
@if (!scheme.ReadonlyWallet)
{
{
<li class="nav-item nav-item-sub">
<a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId" text-translate="true">Send</a>
</li>
}
<li class="nav-item nav-item-sub">
<a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId" text-translate="true">Receive</a>
</li>
@@ -192,8 +192,8 @@
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewPayouts">
<a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
<a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId=""
asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Payouts)" id="StoreNav-Payouts">
<vc:icon symbol="nav-payouts"/>
@@ -327,14 +327,14 @@
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Files" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Files)" asp-action="Files" text-translate="true">Files</a>
</li>
<vc:ui-extension-point location="server-nav" model="@Model"/>
}
<li class="nav-item dropup">
<a class="nav-link @ViewData.ActivePageClass(ManageNavPages.Index)" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" id="Nav-Account">
<vc:icon symbol="nav-account"/>
<span text-translate="true">Account</span>
</a>
</a>
<ul class="dropdown-menu py-0 w-100" aria-labelledby="Nav-Account">
<li class="p-3 border-bottom d-flex align-items-center gap-2">
@if (!string.IsNullOrEmpty(Model.UserImageUrl))

View File

@@ -258,7 +258,7 @@
/* Theme Switch */
.btcpay-theme-switch {
display: inline-flex;
align-items: center;
justify-content: space-between;
@@ -459,7 +459,7 @@
/* Prevent anchors from disappearing underneath the fixed header */
scroll-padding: var(--content-padding-top);
}
#mainMenu {
position: fixed;
top: 0;
@@ -508,7 +508,7 @@
margin: 0 auto;
max-width: 60vw;
}
#Notifications {
margin-left: var(--btcpay-space-s);
}
@@ -619,7 +619,7 @@
--content-padding-bottom: 5rem;
--content-padding-horizontal: 5rem;
}
#mainMenu {
position: fixed;
top: 0;
@@ -646,7 +646,7 @@
/* Make sure we are actually taking up all of the space or else you end up with this: https://github.com/btcpayserver/btcpayserver/issues/3972 */
min-width: 100%;
}
#mainMenuToggle,
#mainMenu .offcanvas-backdrop {
display: none !important;

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BTC/@EntryIndexedValue">BTC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HWI/@EntryIndexedValue">HWI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LNURL/@EntryIndexedValue">LNURL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NB/@EntryIndexedValue">NBX</s:String>