Skip to content

Commit d13f672

Browse files
authored
Merge pull request #32 from thgO-O/chore/add-ci
[Chore] Add E2E, CI and update to BTCPay .NET 10
2 parents 600670a + 5e99808 commit d13f672

1,919 files changed

Lines changed: 779 additions & 265984 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/playwright.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Playwright UI Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
jobs:
12+
playwright:
13+
runs-on: ubuntu-22.04
14+
env:
15+
CI: true
16+
PLAYWRIGHT_HEADLESS: true
17+
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
with:
22+
submodules: recursive
23+
24+
- name: Initialize submodules
25+
run: git submodule update --init --recursive
26+
27+
- name: Setup .NET
28+
uses: actions/setup-dotnet@v4
29+
with:
30+
dotnet-version: 10.0.x
31+
32+
- name: Restore dependencies
33+
run: dotnet restore BTCPayServer.Plugins.Depix.Tests/BTCPayServer.Plugins.Depix.Tests.csproj /p:RazorCompileOnBuild=true
34+
35+
- name: Build BTCPay test host
36+
run: dotnet build submodules/btcpayserver/BTCPayServer.Tests/BTCPayServer.Tests.csproj -c Debug --no-restore /p:RazorCompileOnBuild=true -maxcpucount:1
37+
38+
- name: Build DePix plugin
39+
run: dotnet build BTCPayServer.Plugins.Depix/BTCPayServer.Plugins.Depix.csproj -c Debug --no-restore /p:RazorCompileOnBuild=true -maxcpucount:1
40+
41+
- name: Build DePix Playwright tests
42+
run: dotnet build BTCPayServer.Plugins.Depix.Tests/BTCPayServer.Plugins.Depix.Tests.csproj -c Debug --no-restore --no-dependencies -maxcpucount:1
43+
44+
- name: Get Playwright version
45+
id: playwright-version
46+
run: |
47+
PLAYWRIGHT_VERSION=$(grep -oP 'Microsoft\.Playwright" Version="\K[^"]+' BTCPayServer.Plugins.Depix.Tests/BTCPayServer.Plugins.Depix.Tests.csproj)
48+
echo "version=$PLAYWRIGHT_VERSION" >> "$GITHUB_OUTPUT"
49+
50+
- name: Cache Playwright browsers
51+
uses: actions/cache@v4
52+
id: playwright-cache
53+
with:
54+
path: ~/.cache/ms-playwright
55+
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}-${{ hashFiles('BTCPayServer.Plugins.Depix.Tests/BTCPayServer.Plugins.Depix.Tests.csproj') }}
56+
restore-keys: |
57+
${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}-
58+
59+
- name: Install Playwright browsers
60+
run: pwsh BTCPayServer.Plugins.Depix.Tests/bin/Debug/net10.0/playwright.ps1 install --with-deps
61+
62+
- name: Start BTCPay test infrastructure
63+
env:
64+
DOCKER_BUILDKIT: 1
65+
COMPOSE_DOCKER_CLI_BUILD: 1
66+
run: docker compose -f submodules/btcpayserver/BTCPayServer.Tests/docker-compose.yml up -d dev --build
67+
68+
- name: Run Playwright UI tests
69+
run: dotnet test BTCPayServer.Plugins.Depix.Tests/BTCPayServer.Plugins.Depix.Tests.csproj -c Debug --no-build --filter "Category=PlaywrightUITest"
70+
71+
- name: Cleanup Docker
72+
if: always()
73+
run: docker compose -f submodules/btcpayserver/BTCPayServer.Tests/docker-compose.yml down --volumes

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[submodule "btcpayserver"]
2-
path = btcpayserver
2+
path = submodules/btcpayserver
33
url = https://github.com/btcpayserver/btcpayserver
44
[submodule "submodules/liquidplus"]
55
path = submodules/liquidplus
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
11+
<PackageReference Include="Microsoft.Playwright" Version="1.57.0" />
12+
<PackageReference Include="xunit" Version="2.9.3" />
13+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
14+
<PrivateAssets>all</PrivateAssets>
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
16+
</PackageReference>
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<ProjectReference Include="..\BTCPayServer.Plugins.Depix\BTCPayServer.Plugins.Depix.csproj" />
21+
<ProjectReference Include="..\submodules\btcpayserver\BTCPayServer.Tests\BTCPayServer.Tests.csproj">
22+
<AdditionalProperties>RazorCompileOnBuild=true</AdditionalProperties>
23+
</ProjectReference>
24+
</ItemGroup>
25+
</Project>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Net.Http;
3+
using System.Reflection;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using BTCPayServer.Tests;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Playwright;
9+
10+
namespace BTCPayServer.Plugins.Depix.Tests;
11+
12+
public class DepixPlaywrightTester : PlaywrightTester
13+
{
14+
private static readonly FieldInfo BrowserBackingField =
15+
typeof(PlaywrightTester).GetField("<Browser>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)
16+
?? throw new InvalidOperationException("Could not access PlaywrightTester.Browser backing field.");
17+
18+
public new async Task StartAsync()
19+
{
20+
Server.PayTester.NoCSP = true;
21+
TaskCanceledException? syncTimeout = null;
22+
23+
try
24+
{
25+
await Server.StartAsync();
26+
}
27+
catch (TaskCanceledException ex)
28+
{
29+
syncTimeout = ex;
30+
}
31+
32+
if (syncTimeout is not null)
33+
{
34+
if (!await ServerRespondsAsync())
35+
throw syncTimeout;
36+
37+
TestLogs.LogInformation("BTCPay host is reachable; continuing after sync wait timeout caused by DePix chain registration.");
38+
}
39+
40+
var builder = new ConfigurationBuilder();
41+
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
42+
builder.AddEnvironmentVariables();
43+
var conf = builder.Build();
44+
var playwright = await Playwright.CreateAsync();
45+
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
46+
{
47+
Headless = Server.PayTester.InContainer || conf["PLAYWRIGHT_HEADLESS"] == "true",
48+
ExecutablePath = conf["PLAYWRIGHT_EXECUTABLE"],
49+
SlowMo = 0,
50+
Args = ["--disable-frame-rate-limit"]
51+
});
52+
BrowserBackingField.SetValue(this, browser);
53+
54+
var context = await browser.NewContextAsync();
55+
Page = await context.NewPageAsync();
56+
ServerUri = Server.PayTester.ServerUri;
57+
58+
TestLogs.LogInformation($"Playwright: Using {Page.GetType()}");
59+
TestLogs.LogInformation($"Playwright: Browsing to {ServerUri}");
60+
61+
await GoToRegister();
62+
await Page.AssertNoError();
63+
}
64+
65+
private async Task<bool> ServerRespondsAsync()
66+
{
67+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
68+
69+
while (!cts.IsCancellationRequested)
70+
{
71+
try
72+
{
73+
using var response = await Server.PayTester.HttpClient.GetAsync("/", cts.Token);
74+
return (int)response.StatusCode < 500;
75+
}
76+
catch (OperationCanceledException) when (cts.IsCancellationRequested)
77+
{
78+
break;
79+
}
80+
catch (HttpRequestException)
81+
{
82+
}
83+
84+
await Task.Delay(250, cts.Token);
85+
}
86+
87+
return false;
88+
}
89+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using BTCPayServer.Abstractions.Models;
4+
using BTCPayServer.Tests;
5+
using Microsoft.Playwright;
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
namespace BTCPayServer.Plugins.Depix.Tests;
10+
11+
[Collection(SharedPluginTestCollection.CollectionName)]
12+
public class PixServerSettingsTests : PlaywrightBaseTest
13+
{
14+
public PixServerSettingsTests(SharedPluginTestFixture fixture, ITestOutputHelper output)
15+
: base(fixture, output)
16+
{
17+
}
18+
19+
[Fact(Timeout = TestUtils.TestTimeout)]
20+
[Trait("Category", "PlaywrightUITest")]
21+
public async Task CannotSaveServerSettingsWithoutApiKey()
22+
{
23+
await InitializeAdminAsync();
24+
await GoToPixServerSettingsAsync();
25+
26+
await Page.GetByRole(AriaRole.Heading, new() { Name = "Pix Server Settings" }).WaitForAsync();
27+
await Page.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
28+
29+
await Tester.FindAlertMessage(StatusMessageModel.StatusSeverity.Error, "Set the DePix API key first.");
30+
Assert.Contains("/server/depix/settings", Page.Url);
31+
}
32+
33+
[Fact(Timeout = TestUtils.TestTimeout)]
34+
[Trait("Category", "PlaywrightUITest")]
35+
public async Task CanRegenerateServerWebhookSecret()
36+
{
37+
await InitializeAdminAsync();
38+
await SeedValidServerPixConfigAsync(useWhitelist: true, passFeeToCustomer: true);
39+
await GoToPixServerSettingsAsync();
40+
41+
Assert.True(await Page.Locator("#UseWhitelist").IsCheckedAsync());
42+
Assert.True(await Page.Locator("#PassFeeToCustomer").IsCheckedAsync());
43+
44+
await Page.Locator("#RegenerateWebhookSecret").SetCheckedAsync(true);
45+
await Page.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
46+
47+
await Tester.FindAlertMessage(partialText: "DePix server configuration applied");
48+
49+
var oneShotSecret = await Page.Locator("#OneShotSecret").InputValueAsync();
50+
Assert.Matches("^[0-9a-f]{64}$", oneShotSecret);
51+
52+
await Page.ReloadAsync();
53+
54+
Assert.Equal(0, await Page.Locator("#OneShotSecret").CountAsync());
55+
Assert.Contains("stored securely", await Page.Locator("#WebhookSecretDisplay").InputValueAsync(), StringComparison.OrdinalIgnoreCase);
56+
Assert.True(await Page.Locator("#UseWhitelist").IsCheckedAsync());
57+
Assert.True(await Page.Locator("#PassFeeToCustomer").IsCheckedAsync());
58+
}
59+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Threading.Tasks;
2+
using BTCPayServer.Tests;
3+
using Microsoft.Playwright;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace BTCPayServer.Plugins.Depix.Tests;
8+
9+
[Collection(SharedPluginTestCollection.CollectionName)]
10+
public class PixSettingsTests : PlaywrightBaseTest
11+
{
12+
public PixSettingsTests(SharedPluginTestFixture fixture, ITestOutputHelper output)
13+
: base(fixture, output)
14+
{
15+
}
16+
17+
[Fact(Timeout = TestUtils.TestTimeout)]
18+
[Trait("Category", "PlaywrightUITest")]
19+
public async Task CanOpenPixSettingsWhenPluginLoaded()
20+
{
21+
await InitializeStoreOwnerAsync();
22+
await GoToPixSettingsAsync();
23+
24+
await Page.GetByRole(AriaRole.Heading, new() { Name = "Pix Settings" }).WaitForAsync();
25+
await Page.Locator("#ApiKey").WaitForAsync();
26+
await Page.GetByText("DePix is not configured.", new() { Exact = false }).WaitForAsync();
27+
28+
Assert.Contains($"/stores/{Tester.StoreId}/pix/settings", Page.Url);
29+
}
30+
31+
[Fact(Timeout = TestUtils.TestTimeout)]
32+
[Trait("Category", "PlaywrightUITest")]
33+
public async Task CanSaveAndPersistPixStoreSettings()
34+
{
35+
await InitializeStoreOwnerAsync();
36+
await SeedValidStorePixConfigAsync();
37+
await GoToPixSettingsAsync();
38+
39+
await Page.Locator("#IsEnabled").SetCheckedAsync(true);
40+
await Page.Locator("#PassFeeToCustomer").SetCheckedAsync(true);
41+
await Page.Locator("#UseWhitelist").SetCheckedAsync(true);
42+
await Page.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
43+
44+
await Tester.FindAlertMessage(partialText: "Pix configuration applied");
45+
46+
Assert.True(await Page.Locator("#IsEnabled").IsCheckedAsync());
47+
Assert.True(await Page.Locator("#PassFeeToCustomer").IsCheckedAsync());
48+
Assert.True(await Page.Locator("#UseWhitelist").IsCheckedAsync());
49+
}
50+
51+
[Fact(Timeout = TestUtils.TestTimeout)]
52+
[Trait("Category", "PlaywrightUITest")]
53+
public async Task CanEnablePixUsingServerLevelConfiguration()
54+
{
55+
await InitializeStoreOwnerAsync();
56+
await SeedValidServerPixConfigAsync();
57+
await GoToPixSettingsAsync();
58+
59+
await Page.GetByText("server-level DePix Api Key configuration", new() { Exact = false }).WaitForAsync();
60+
Assert.Equal(0, await Page.Locator("#PassFeeToCustomer").CountAsync());
61+
Assert.Equal(0, await Page.Locator("#UseWhitelist").CountAsync());
62+
63+
await Page.Locator("#IsEnabled").SetCheckedAsync(true);
64+
await Page.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
65+
66+
await Tester.FindAlertMessage(partialText: "Pix configuration applied");
67+
Assert.True(await Page.Locator("#IsEnabled").IsCheckedAsync());
68+
69+
var storeConfig = await GetStorePixConfigAsync();
70+
Assert.NotNull(storeConfig);
71+
Assert.True(storeConfig!.IsEnabled);
72+
Assert.Null(storeConfig.EncryptedApiKey);
73+
Assert.Null(storeConfig.WebhookSecretHashHex);
74+
}
75+
}

0 commit comments

Comments
 (0)