Search code examples
javascriptc#asp.net-coreautomated-tests

How to provoke button click on web page from integration test


Our backend is thoroughly tested with unit tests. Now we need to pull all the pieces together with integration tests. I see lots of examples for how to "get" a page, but none for how to execute functionality on the page.
I have a standard HTML "Scan" page with a download button.
Looks like this:

        <li><img src="~/img/download.png" style="height: 1.5em; width: inherit; margin-right: 8px" /><a id="download-btn" href="#">Download</a></li>

Click the button and it downloads a PDF. Works fine.
The js starts like this:

$('#download-btn').click(function () { 
var qrKey = 'c40rj8f'; // Replace with your actual QRKey value

$.ajax({
    url: '/api/Scans/Download',
    method: 'GET',
    xhrFields: {
        responseType: 'blob'
    },
    data: { QRKey: qrKey },
    responseType: 'arraybuffer', // Important for handling binary data
    success: function (response) {
    .... 

Now I need to test that from my Microsoft.VisualStudio.TestTools.UnitTesting integration test.

Between stackoverflow, chatgpt and copilot, this is the closest I've been able to come up with, but it returns 400.

    [TestMethod]
    public async Task ScanDownloadTest()
    {
        // get page content
        var page = await httpClient.GetAsync("Scan");
        Assert.AreEqual(HttpStatusCode.OK, page.StatusCode);
        string resultContent = page.Content.ReadAsStringAsync().Result;
        // load the page into an HtmlDocument 
        var htmlDoc = new HtmlDocument();
        htmlDoc.LoadHtml(resultContent);
        var downloadBtn = htmlDoc.GetElementbyId("download-btn");
        // execute downloadBtn button click 
        var request = new HttpRequestMessage(HttpMethod.Post, "Scan");
        var content = new StringContent($"{{\"id\":1,\"method\":\"Runtime.evaluate\",\"params\":{{\"expression\":\"document.getElementById('{downloadBtn.Id}').click();\",\"returnByValue\":true}}}}");
        request.Content = content;
        var response = await httpClient.SendAsync(request); // returns 400 (Bad Request)
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
        Assert.IsNotNull(response.Content.Headers.ContentType);
        Assert.AreEqual("application/octet-stream", response.Content.Headers.ContentType.MediaType);
    }

Solution

  • It turns out that this is a much more difficult question to answer than it should be.
    I knew for certain that it could be done because I've done it many times in the past. Of course that was with WebBrowser, which of course was based on IE, which of course isn't supported, so of course isn't available in Core.
    Surely MS has some sort of replacement. Right? No. Not so much.
    There is, of course Selenium, and I did get that working from my NUnit tests. But you have to start your website independently, which I didn't get working.
    And there were numerous posts touting the WebApplicationFactory<T> as all you'll ever need for your End-to-End testing. But after hours of following tutorials, you find that it only calls your controllers. Not quite E2E.

    In the end, I used Martin Costello's dotnet-minimal-api-integration-testing project as a base. This uses WebApplicationFactory<T> combined with Playwright.

    Ideally, this would be converted into a NuGet plugin, but for now, there's about 4 hours work to convert it into a generically usable form and excise xunit.

    My code to test "login button works and takes you to the scan page," now looks like this:

        [Test]
        public void LoginSuccessTestRunner() {
            Run(LoginSuccessTest);
        }
    
        public async Task LoginSuccessTest(IPage page) {
            // Arrange
            Task scan = page.WaitForURLAsync(Fixture.ServerAddress + "Scan", WaitOptions);
            await page.GotoAsync(Fixture.ServerAddress + "Login");
            await page.WaitForLoadStateAsync(LoadState.Load);
            // Act
            await page.TypeAsync("id=username", "admin");
            await page.TypeAsync("id=password", "password");
            await page.ClickAsync("id=submit");
            await scan; // Wait for the Scan page to finish loading
    
            // Assert
            Assert.AreEqual("Scan - Frontend", await page.TitleAsync());
        }