diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 00000000..806d6281 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,42 @@ +name: Run tests + +on: + pull_request: + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + + - name: Set timestamp variable + id: timestamp + run: echo "datetime=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_OUTPUT + + # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/9.0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Install wasm-tools + run: dotnet workload install wasm-tools + + - name: Build & Install + run: dotnet build + - name: Ensure browsers are installed + run: pwsh AudioCuesheetEditor.End2EndTests/bin/Debug/net9.0/playwright.ps1 install --with-deps + + - name: Start App + run: dotnet run --project AudioCuesheetEditor & + + - name: Run tests + run: dotnet test + + - name: Upload traces + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-traces-${{ steps.timestamp.outputs.datetime }} + path: AudioCuesheetEditor.End2EndTests/bin/Debug/net9.0/playwright-traces/ + if-no-files-found: ignore diff --git a/.github/workflows/run_unit_tests.yml b/.github/workflows/run_unit_tests.yml deleted file mode 100644 index b6dfd7d9..00000000 --- a/.github/workflows/run_unit_tests.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Run unit tests - -# Run workflow on every push to the master branch -on: - pull_request: - -jobs: - run-unit-tests: - # use ubuntu-latest image to run steps on - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4.2.2 - - # sets up .NET - # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/9.0 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '9.0.101' - - - name: Install wasm-tools - run: dotnet workload install wasm-tools - - # Only publish when unit tests are ok - - name: Run Unit Tests - run: dotnet test diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj new file mode 100644 index 00000000..3c389015 --- /dev/null +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -0,0 +1,30 @@ + + + + net9.0 + latest + enable + enable + true + Exe + true + + true + + + + + + + + + + + + + + + diff --git a/AudioCuesheetEditor.End2EndTests/Pages/AboutTest.cs b/AudioCuesheetEditor.End2EndTests/Pages/AboutTest.cs new file mode 100644 index 00000000..f043afd9 --- /dev/null +++ b/AudioCuesheetEditor.End2EndTests/Pages/AboutTest.cs @@ -0,0 +1,44 @@ +using Microsoft.Playwright; +using Microsoft.Playwright.MSTest; + +namespace AudioCuesheetEditor.End2EndTests.Pages +{ + [TestClass] + public class AboutTest : PageTest + { + [TestInitialize] + public async Task TestInitialize() + { + await Context.Tracing.StartAsync(new() + { + Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}", + Screenshots = true, + Snapshots = true, + Sources = true + }); + } + + [TestCleanup] + public async Task TestCleanup() + { + var failed = new[] { UnitTestOutcome.Failed, UnitTestOutcome.Error, UnitTestOutcome.Timeout, UnitTestOutcome.Aborted }.Contains(TestContext.CurrentTestOutcome); + + await Context.Tracing.StopAsync(new() + { + Path = failed ? Path.Combine( + Environment.CurrentDirectory, + "playwright-traces", + $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}.zip" + ) : null, + }); + } + + [TestMethod] + public async Task HasTitle() + { + await Page.GotoAsync("http://localhost:5132/about"); + await Expect(Page).ToHaveTitleAsync("AudioCuesheetEditor"); + await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "About AudioCuesheetEditor" })).ToBeVisibleAsync(); + } + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor.End2EndTests/Pages/IndexTest.cs b/AudioCuesheetEditor.End2EndTests/Pages/IndexTest.cs new file mode 100644 index 00000000..df5977ef --- /dev/null +++ b/AudioCuesheetEditor.End2EndTests/Pages/IndexTest.cs @@ -0,0 +1,93 @@ +using Microsoft.Playwright; +using Microsoft.Playwright.MSTest; +using System.Text.RegularExpressions; + +namespace AudioCuesheetEditor.End2EndTests.Pages +{ + [TestClass] + public class IndexTest : PageTest + { + [TestInitialize] + public async Task TestInitialize() + { + await Context.Tracing.StartAsync(new() + { + Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}", + Screenshots = true, + Snapshots = true, + Sources = true + }); + } + + [TestCleanup] + public async Task TestCleanup() + { + var failed = new[] { UnitTestOutcome.Failed, UnitTestOutcome.Error, UnitTestOutcome.Timeout, UnitTestOutcome.Aborted }.Contains(TestContext.CurrentTestOutcome); + + await Context.Tracing.StopAsync(new() + { + Path = failed ? Path.Combine( + Environment.CurrentDirectory, + "playwright-traces", + $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}.zip" + ) : null, + }); + } + + [TestMethod] + public async Task HasTitle() + { + await Page.GotoAsync("http://localhost:5132/"); + await Expect(Page).ToHaveTitleAsync("AudioCuesheetEditor"); + await Expect(Page.GetByRole(AriaRole.Button, new() { Name = "AudioCuesheetEditor" })).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task CheckSettings() + { + await Page.GotoAsync("http://localhost:5132/"); + await Page.GetByRole(AriaRole.Toolbar).GetByRole(AriaRole.Button).Filter(new() { HasTextRegex = new Regex("^$") }).Nth(3).ClickAsync(); + await Page.Locator("div").Filter(new() { HasTextRegex = new Regex("^Settings$") }).ClickAsync(); + await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Settings" })).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Record() + { + await Page.GotoAsync("http://localhost:5132/"); + await Page.GetByText("Record view").ClickAsync(); + await Page.GetByRole(AriaRole.Button, new() { Name = "Start recording" }).ClickAsync(); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Artist", Exact = true }).ClickAsync(); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Artist", Exact = true }).FillAsync("Test Track 1 Artist"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Artist", Exact = true }).PressAsync("Tab"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).FillAsync("Test Track 1 Title"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).PressAsync("Tab"); + await Page.GetByRole(AriaRole.Button, new() { Name = "Add track" }).ClickAsync(); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Artist", Exact = true }).FillAsync("Test Track 2 Artist"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Artist", Exact = true }).PressAsync("Tab"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).FillAsync("Test Track 2 Title"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).PressAsync("Tab"); + await Page.GetByRole(AriaRole.Button, new() { Name = "Add track" }).ClickAsync(); + await Page.Locator(".mud-overlay").ClickAsync(); + await Page.GetByRole(AriaRole.Button, new() { Name = "Stop recording" }).ClickAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = "Test Track 1 Artist Clear" })).ToBeVisibleAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = "Test Track 1 Title Clear" })).ToBeVisibleAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = "Test Track 2 Artist Clear" })).ToBeVisibleAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = "Test Track 2 Title Clear" })).ToBeVisibleAsync(); + } + + [TestMethod] + public async Task Import() + { + await Page.GotoAsync("http://localhost:5132/"); + await Page.GetByText("Import view").ClickAsync(); + await Page.GetByRole(AriaRole.Button, new() { Name = "Choose File" }).SetInputFilesAsync(new[] { "../../../../AudioCuesheetEditor/wwwroot/samples/Sample_Inputfile.txt" }); + await Page.GetByRole(AriaRole.Button, new() { Name = "Complete" }).ClickAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = "Sample Artist 1 Clear" })).ToBeVisibleAsync(); + await Expect(Page.GetByRole(AriaRole.Cell, new() { Name = ":20:13" }).Nth(1)).ToBeVisibleAsync(); + await Expect(Page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet artist" })).ToHaveValueAsync("CuesheetArtist"); + await Page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet title" }).ClickAsync(); + await Expect(Page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile Search" }).Locator("input[type=\"file\"]")).ToBeEmptyAsync(); + } + } +} diff --git a/AudioCuesheetEditor.sln b/AudioCuesheetEditor.sln index bbea4d79..bf6847c0 100644 --- a/AudioCuesheetEditor.sln +++ b/AudioCuesheetEditor.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioCuesheetEditor", "Audi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioCuesheetEditor.Tests", "AudioCuesheetEditor.Tests\AudioCuesheetEditor.Tests.csproj", "{12CA5D1F-D758-4016-85D0-A045AC06CFE6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioCuesheetEditor.End2EndTests", "AudioCuesheetEditor.End2EndTests\AudioCuesheetEditor.End2EndTests.csproj", "{7297DF58-3F83-49E9-B714-B2BE29D08180}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {12CA5D1F-D758-4016-85D0-A045AC06CFE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {12CA5D1F-D758-4016-85D0-A045AC06CFE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {12CA5D1F-D758-4016-85D0-A045AC06CFE6}.Release|Any CPU.Build.0 = Release|Any CPU + {7297DF58-3F83-49E9-B714-B2BE29D08180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7297DF58-3F83-49E9-B714-B2BE29D08180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7297DF58-3F83-49E9-B714-B2BE29D08180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7297DF58-3F83-49E9-B714-B2BE29D08180}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE