Have a single page application sitting on top of an ASP.NET Core project and want to run end-to-end tests on any pull request? In this post we’ll start with nothing and finish with end-to-end tests using Playwright and Github Actions.

Getting started#

You can follow along here to build this functionality from nothing, or, you can see the completed source here. Starting from nothing, we’ll create a React project and an XUnit project to run our tests:

bash
dotnet new react -o Web dotnet new xunit -o Test

We need a couple more packages in the test project so:

bash
cd Test dotnet add package PlaywrightSharp --version 0.162.1 dotnet add package Shouldly --version 4.0.1

Playwright for .NET is a .NET library that lets us automate browsers and Shouldly is my favorite assertion library.

Now we’ll go back to the root install Tye locally. Tye will let us quickly bring up our Web project in a container. The tool manifest will let us run dotnet tool restore so we can reinstall Tye anywhere, for example in a Github Action.

bash
cd.. dotnet new tool-manifest dotnet tool install Microsoft.Tye --version "0.5.0-alpha.20555.1"

Tye will bring up our Web project on a random port, but we want it to be the same so the tests can always find it. Create a file called tye.yaml and stick this in it:

yaml
services: - name: Web project: Web/Web.csproj bindings: - port: 5000

We can test the Web project by starting it with Tye and browsing to http://localhost:8000. Doing so shows us our Web project is running on http://localhost:5000.

bash
dotnet tye run

Writing the test#

In our test, we’ll browse to the Counter component, click it three times, and assert that the count has updated to the value 3. Find Counter.js and update this line…

html
<p aria-live="polite">Current count: <strong>{this.state.currentCount}</strong></p>

…to include an id we can use to select later:

html
<p aria-live="polite">Current count: <strong id='count'>{this.state.currentCount}</strong></p>

Now we can write our test. Change the contents of UnitTest1.cs to the following:

csharp
using System; using System.Threading.Tasks; using PlaywrightSharp; using Shouldly; using Xunit; namespace Test { public class UnitTest1 { [Fact] public async Task Test1() { using var playwright = await Playwright.CreateAsync(); await using var browser = await playwright.Chromium.LaunchAsync(); var page = await browser.NewPageAsync(); await page.GoToAsync("http://0.0.0.0:5000"); await page.ClickAsync("text='Counter'"); await page.ClickAsync("text='Increment'"); await page.ClickAsync("text='Increment'"); await page.ClickAsync("text='Increment'"); var count = await page.GetTextContentAsync("#count"); count.ShouldBe("3"); } } }

We should now be able to dotnet tye run Web from the root, bringing up our Web project, and in the Test folder we can run dotnet test. The result should be one passing test! If the test doesn’t pass, we can change our test to display the browser for us by using .LaunchAsync(headless: false). In the future we could control such behavior with environment variables as mentioned in this prior post.

Building the action#

At this point we should push all our code to a git repository and we can start on the workflow that will be triggered on pull request. Add the following to .github/workflows/end2end.yml. I’ve commented the steps here to explain what’s happening.

yaml
name: End-to-end Tests # only triggers on pull requests targeting the main branch on: pull_request: branches: [main] jobs: test: runs-on: ubuntu-16.04 steps: # grab our code - name: Checkout git repository uses: actions/checkout@v2 # ensure the correct version of .net core is installed - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 # ensure the correct version of node is installed - name: Setup Node uses: actions/setup-node@v1 with: node-version: 12 # this step restores tye for us - name: Install dotnet tools run: dotnet tool restore working-directory: Web # builds our Web project into a docker container - name: Build run: dotnet tye build Web -v Debug # starting the container we just built in detached mode - name: Start container run: docker run -d -p 5000:80 web:1.0.0 # executes tests against the running container - name: Run end-to-end tests run: dotnet test working-directory: Test

Conclusion#

In a single Tye command we’ve restored our .NET and npm dependencies, built and published the Web project and copied the result into a container image. Once we start the container we can run our tests as part of our workflow where Playwright will ensure any needed Chrome dependencies are installed. You can see the all the source here and the result of the actions running here.