Skriva användargränssnittstesterna

Slutförd

I det här avsnittet hjälper du Andy och Amita att skriva Selenium-tester som verifierar de användargränssnittsbeteenden som Amita beskrev.

Amita kör normalt tester på Chrome, Firefox och Microsoft Edge. Här gör du samma sak. Den Microsoft-värdbaserade agent som du använder är förkonfigurerad för att fungera med var och en av dessa webbläsare.

Hämta grenen från GitHub

I det här avsnittet hämtar du grenen selenium från GitHub. Sedan checkar du ut eller växlar till den grenen. Innehållet i grenen hjälper dig att följa med i de tester som Andy och Amita skriver.

Den här grenen innehåller space game-projektet som du arbetade med i tidigare moduler. Den innehåller också en Azure Pipelines-konfiguration att börja med.

  1. Öppna den integrerade terminalen i Visual Studio Code.

  2. Om du vill ladda ned en gren med namnet selenium från Microsoft-lagringsplatsen växlar du till den grenen och kör följande git fetch kommandon och git checkout kommandon:

    git fetch upstream selenium
    git checkout -B selenium upstream/selenium
    

    Dricks

    Om du följde amitas manuella test i föregående lektion kan du redan ha kört dessa kommandon. Om du redan körde dem i föregående lektion kan du fortfarande köra dem igen nu.

    Kom ihåg att uppströms refererar till Microsoft GitHub-lagringsplatsen. Ditt projekts Git-konfiguration förstår den överordnade fjärrplatsen eftersom du har konfigurerat den relationen. Du konfigurerade det när du förgrenade projektet från Microsoft-lagringsplatsen och klonade det lokalt.

    Snart kommer du att push-överföra grenen till din GitHub-lagringsplats med namnet origin.

  3. Du kan också öppna filen azure-pipelines.yml i Visual Studio Code. Bekanta dig med den inledande konfigurationen.

    Konfigurationen liknar de som du skapade i föregående moduler i den här utbildningsvägen. Den skapar endast programmets versionskonfiguration. För korthet utelämnar den även utlösare, manuella godkännanden och tester som du har konfigurerat i tidigare moduler.

    Kommentar

    En mer robust konfiguration kan ange de grenar som deltar i byggprocessen. För att till exempel verifiera kodkvaliteten kan du köra enhetstester varje gång du push-överför en ändring på en gren. Du kan också distribuera programmet till en miljö som utför mer omfattande testning. Men du gör bara den här distributionen när du har en pull-begäran, när du har en versionskandidat eller när du sammanfogar kod till main.

    Mer information finns i Implementera ett kodarbetsflöde i din byggpipeline med hjälp av Git- och GitHub - och Build-pipelineutlösare.

Skriva enhetstestkoden

Amita är glad över att lära sig att skriva kod som styr webbläsaren.

Hon och Andy kommer att arbeta tillsammans för att skriva Selenium-testerna. Andy har redan konfigurerat ett tomt NUnit-projekt. Under hela processen refererar de till Selenium-dokumentationen, några online-självstudier och de anteckningar som de tog när Amita gjorde testerna manuellt. I slutet av den här modulen hittar du fler resurser som hjälper dig att ta dig igenom processen.

Nu ska vi gå igenom den process som Andy och Amita använder för att skriva sina tester. Du kan följa med genom att öppna HomePageTest.cs i katalogen Tailspin.SpaceGame.Web.UITests i Visual Studio Code.

Definiera klassen HomePageTest

Andy: Det första vi behöver göra är att definiera vår testklass. Vi kan välja att följa någon av flera namngivningskonventioner. Låt oss kalla vår klass HomePageTest. I den här klassen placerar vi alla våra tester som är relaterade till startsidan.

Andy lägger till den här koden i HomePageTest.cs:

public class HomePageTest
{
}

Andy: Vi måste markera den här klassen så public att den är tillgänglig för NUnit-ramverket.

Lägg till medlemsvariabeln IWebDriver

Andy: Nu behöver vi en IWebDriver medlemsvariabel. IWebDriver är det programmeringsgränssnitt som du använder för att starta en webbläsare och interagera med webbsidans innehåll.

Amita: Jag har hört talas om gränssnitt inom programmering. Kan du berätta mer?

Andy: Tänk på ett gränssnitt som en specifikation eller skiss för hur en komponent ska bete sig. Ett gränssnitt innehåller den komponentens metoder eller beteenden. Men gränssnittet innehåller ingen underliggande information. Du eller någon annan skulle skapa en eller flera konkreta klasser som implementerar gränssnittet. Selen ger de betongklasser som vi behöver.

Det här diagrammet visar IWebDriver gränssnittet och några av de klasser som implementerar det här gränssnittet:

Diagram of the IWebDriver interface, its methods, and concrete classes.

Diagrammet visar tre av de metoder som IWebDriver tillhandahåller: Navigate, FindElementoch Close.

De tre klasser som visas här, ChromeDriver, FirefoxDriveroch EdgeDriver, implementerar IWebDriver var och en och dess metoder. Det finns andra klasser, till exempel , som SafariDriveräven implementerar IWebDriver. Varje drivrutinsklass kan styra webbläsaren som den representerar.

Andy lägger till en medlemsvariabel med namnet driver i klassen, som den HomePageTest här koden:

public class HomePageTest
{
    private IWebDriver driver;
}

Definiera testfixturerna

Andy: Vi vill köra hela uppsättningen tester på Chrome, Firefox och Edge. I NUnit kan vi använda testfixturer för att köra hela uppsättningen tester flera gånger, en gång för varje webbläsare som vi vill testa på.

I NUnit använder TestFixture du attributet för att definiera dina testfixturer. Andy lägger till dessa tre testfixturer i HomePageTest klassen:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private IWebDriver driver;
}

Andy: Sedan måste vi definiera en konstruktor för vår testklass. Konstruktorn anropas när NUnit skapar en instans av den här klassen. Som argument tar konstruktorn strängen som vi kopplade till våra testarmaturer. Så här ser koden ut:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private string browser;
    private IWebDriver driver;

    public HomePageTest(string browser)
    {
        this.browser = browser;
    }
}

Andy: Vi har lagt till browser medlemsvariabeln så att vi kan använda det aktuella webbläsarnamnet i vår installationskod. Nu ska vi skriva installationskoden.

Definiera installationsmetoden

Andy: Sedan måste vi tilldela vår IWebDriver medlemsvariabel till en klassinstans som implementerar det här gränssnittet för webbläsaren som vi testar på. Klasserna ChromeDriver, FirefoxDriveroch EdgeDriver implementerar det här gränssnittet för Chrome, Firefox respektive Edge.

Nu ska vi skapa en metod med namnet Setup som anger variabeln driver . Vi använder OneTimeSetUp attributet för att be NUnit att köra den här metoden en gång per testfixtur.

[OneTimeSetUp]
public void Setup()
{
}

Setup I metoden kan vi använda en switch -instruktion för att tilldela driver medlemsvariabeln till lämplig konkret implementering baserat på webbläsarnamnet. Nu ska vi lägga till koden.

// Create the driver for the current browser.
switch(browser)
{
    case "Chrome":
    driver = new ChromeDriver(
        Environment.GetEnvironmentVariable("ChromeWebDriver")
    );
    break;
    case "Firefox":
    driver = new FirefoxDriver(
        Environment.GetEnvironmentVariable("GeckoWebDriver")
    );
    break;
    case "Edge":
    driver = new EdgeDriver(
        Environment.GetEnvironmentVariable("EdgeWebDriver"),
        new EdgeOptions
        {
            UseChromium = true
        }
    );
    break;
    default:
    throw new ArgumentException($"'{browser}': Unknown browser");
}

Konstruktorn för varje drivrutinsklass tar en valfri sökväg till drivrutinsprogramvaran som Selenium behöver för att styra webbläsaren. Senare diskuterar vi rollen för de miljövariabler som visas här.

I det här exemplet EdgeDriver kräver konstruktorn även ytterligare alternativ för att ange att vi vill använda Chromium-versionen av Edge.

Definiera hjälpmetoderna

Andy: Jag vet att vi måste upprepa två åtgärder under testerna:

  • Hitta element på sidan, till exempel länkarna som vi klickar på och de modala fönster som vi förväntar oss ska visas
  • Klicka på element på sidan, till exempel länkarna som visar de modala fönstren och knappen som stänger varje modal

Nu ska vi skriva två hjälpmetoder, en för varje åtgärd. Vi börjar med metoden som hittar ett element på sidan.

Skriva hjälpmetoden FindElement

När du hittar ett element på sidan svarar det vanligtvis på någon annan händelse, till exempel sidinläsningen eller användaren som anger information. Selenium tillhandahåller WebDriverWait klassen, vilket gör att du kan vänta på att ett villkor ska vara sant. Om villkoret inte är sant inom den angivna tidsperioden utlöser WebDriverWait ett undantag eller fel. Vi kan använda WebDriverWait klassen för att vänta tills ett visst element visas och vara redo att ta emot användarindata.

Om du vill hitta ett element på sidan använder du By klassen . Klassen By innehåller metoder som gör att du kan hitta ett element efter dess namn, dess CSS-klassnamn, dess HTML-tagg eller i vårt fall med dess id attribut.

Andy och Amita kodar FindElement upp hjälpmetoden. Det ser ut som den här koden:

private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
    // WebDriverWait enables us to wait for the specified condition to be true
    // within a given time period.
    return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
        .Until(c => {
            IWebElement element = null;
            // If a parent was provided, find its child element.
            if (parent != null)
            {
                element = parent.FindElement(locator);
            }
            // Otherwise, locate the element from the root of the DOM.
            else
            {
                element = driver.FindElement(locator);
            }
            // Return true after the element is displayed and is able to receive user input.
            return (element != null && element.Displayed && element.Enabled) ? element : null;
        });
}

Skriva hjälpmetoden ClickElement

Andy: Nu ska vi skriva en hjälpmetod som klickar på länkar. Selenium innehåller några sätt att skriva den metoden. En av dem är IJavaScriptExecutor gränssnittet. Med den kan vi programmatiskt klicka på länkar med hjälp av JavaScript. Den här metoden fungerar bra eftersom den kan klicka på länkar utan att först rulla dem i vyn.

ChromeDriver, FirefoxDriveroch EdgeDriver varje implementerar IJavaScriptExecutor. Vi måste skicka drivrutinen till det här gränssnittet och sedan anropa ExecuteScript för att köra JavaScript-metoden click() på det underliggande HTML-objektet.

Andy och Amita kodar ClickElement upp hjälpmetoden. Det ser ut som den här koden:

private void ClickElement(IWebElement element)
{
    // We expect the driver to implement IJavaScriptExecutor.
    // IJavaScriptExecutor enables us to execute JavaScript code during the tests.
    IJavaScriptExecutor js = driver as IJavaScriptExecutor;

    // Through JavaScript, run the click() method on the underlying HTML object.
    js.ExecuteScript("arguments[0].click();", element);
}

Amita: Jag gillar tanken på att lägga till dessa hjälpmetoder. De verkar allmänt nog att använda i nästan alla test. Vi kan lägga till fler hjälpmetoder senare när vi behöver dem.

Definiera testmetoden

Andy: Nu är vi redo att definiera testmetoden. Baserat på de manuella tester som vi körde tidigare ska vi anropa den här metoden ClickLinkById_ShouldDisplayModalById. Det är en bra idé att ge testmetoder beskrivande namn som definierar exakt vad testet åstadkommer. Här vill vi klicka på en länk som definieras av dess id attribut. Sedan vill vi kontrollera att rätt modalfönster visas, även med hjälp av dess id attribut.

Andy lägger till startkod för testmetoden:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}

Andy: Innan vi lägger till mer kod ska vi definiera vad det här testet ska göra.

Amita: Jag kan hantera den här delen. Vi vill:

  1. Leta upp länken efter dess id attribut och klicka sedan på länken.
  2. Leta upp den resulterande modalen.
  3. Stäng modalen.
  4. Kontrollera att modalen har visats.

Andy: Bra. Vi måste också hantera några andra saker. Vi behöver till exempel ignorera testet om drivrutinen inte kunde läsas in, och vi behöver bara stänga modalen om modalen har visats.

Efter att ha fyllt på sina kaffemuggar lägger Andy och Amita till kod i sin testmetod. De använder hjälpmetoderna som de skrev för att hitta sidelement och klicka på länkar och knappar. Här är resultatet:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
    // Skip the test if the driver could not be loaded.
    // This happens when the underlying browser is not installed.
    if (driver == null)
    {
        Assert.Ignore();
        return;
    }

    // Locate the link by its ID and then click the link.
    ClickElement(FindElement(By.Id(linkId)));

    // Locate the resulting modal.
    IWebElement modal = FindElement(By.Id(modalId));

    // Record whether the modal was successfully displayed.
    bool modalWasDisplayed = (modal != null && modal.Displayed);

    // Close the modal if it was displayed.
    if (modalWasDisplayed)
    {
        // Click the close button that's part of the modal.
        ClickElement(FindElement(By.ClassName("close"), modal));

        // Wait for the modal to close and for the main page to again be clickable.
        FindElement(By.TagName("body"));
    }

    // Assert that the modal was displayed successfully.
    // If it wasn't, this test will be recorded as failed.
    Assert.That(modalWasDisplayed, Is.True);
}

Amita: Kodningen ser bra ut hittills. Men hur ansluter vi det här testet till de id attribut som vi samlade in tidigare?

Andy: Bra fråga. Vi tar hand om det härnäst.

Definiera testfallsdata

Andy: I NUnit kan du tillhandahålla data till dina tester på några olika sätt. Här använder vi attributet TestCase . Det här attributet tar argument som senare skickas tillbaka till testmetoden när det körs. Vi kan ha flera TestCase attribut som var och en testar en annan funktion i vår app. Varje TestCase attribut genererar ett testfall som ingår i rapporten som visas i slutet av en pipelinekörning.

Andy lägger till dessa TestCase attribut i testmetoden. Dessa attribut beskriver knappen Ladda ned spel , en av spelskärmarna och den bästa spelaren på rankningslistan. Varje attribut anger två id attribut: ett för länken som ska klickas och ett för motsvarande modalfönster.

// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{

...

Andy: För varje TestCase attribut är id den första parametern attributet för länken som du vill klicka på. Den andra parametern är id attributet för det modala fönster som vi förväntar oss ska visas. Du kan se hur dessa parametrar motsvarar argumenten med två strängar i vår testmetod.

Amita: Jag ser det. Med lite övning tror jag att jag kan lägga till mina egna tester. När kan vi se de här testerna köras i pipelinen?

Andy: Innan vi skickar ändringar via pipelinen ska vi först kontrollera att koden kompileras och körs lokalt. Vi checkar in och push-överför ändringar till GitHub och ser dem gå igenom pipelinen först när vi har kontrollerat att allt fungerar. Nu ska vi köra testerna lokalt.