2
votes

Avoir plusieurs fichiers Step ouvre plusieurs navigateurs

Le problème:

Si j'ai plus d'un fichier Steps, lorsque j'exécute des tests, il semble que la création de WebDriver pour chacun se déroule quel que soit le ou les tests que j'exécute.

Je voyais un navigateur Chrome apparemment aléatoire s'ouvrir chaque fois que j'exécutais mes tests. Pour tenter de voir s'il y avait une sorte d'incompatibilité entre SpecFlow et ChromeDriver (un long plan, je sais), j'ai changé le WebDriver pour mes tests de recherche en Firefox et laissé le WebDriver pour mes tests de connexion en tant que Chrome. Quels que soient les tests que j'ai exécutés, j'ai toujours vu 2 navigateurs ouverts; un Chrome et un Firefox.

Lorsque j'ai déplacé toutes les étapes de mon fichier SearchTestSteps.cs vers le fichier LoginTestSteps.cs, le problème a disparu.

Donc, oui, cela résout le problème immédiat, mais il est sous-optimal d'avoir toutes mes étapes dans un seul fichier. Cela peut rapidement devenir compliqué.

Étant donné que chaque ensemble d'étapes doit avoir son propre WebDriver, je suis perdu.

Cela pourrait-il avoir quelque chose à voir avec la structure des dossiers et l'emplacement de stockage des éléments? Voici à quoi ressemble le mien.

Login.feature
Feature: Login
    In order to be able to use Laserfiche
    As a legitimate user
    I want to be able to log into the repository

@SmokeTest
Scenario: Login with correct credentials
    Given I am on the Login page 
    And I have a good username/password combination
    And I select a repository
    When I fill out the form and submit
    Then I am taken to the repo page

---------------
LoginSteps.cs (I also have a SearchTestSteps.cs that looks very similar)
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using Selenium_C_Sharp_POC.Page_Object_Files.Pages;
using Selenium_C_Sharp_POC.Page_Object_Files.Test_Tools;
using TechTalk.SpecFlow;

namespace Selenium_C_Sharp_POC.StepDefinitions
{
    [Binding]
    public class LoginSteps
    {
        private static readonly IWebDriver Driver = new ChromeDriver();

        private static LoginPage _loginPage;
        private static string _username;
        private static string _password;
        private static string _repo;

        [AfterTestRun]
        public static void ShutDown()
        {
            Driver?.Close();
        }

        [Given(@"I am on the Login page")]
        public void GivenIAmOnTheLoginPage()
        {
            _loginPage = new LoginPage(Driver);
        }

        [Given(@"I have a good username/password combination")]
        public void GivenIHaveAGoodUsernamePasswordCombination()
        {
            _username = Nomenclature.WebClientPersonalUsername;
            _password = Nomenclature.WebClientPersonalPassword;
        }
        [Given(@"I select a repository")]
        public void GivenISelectARepository()
        {
            _repo = Nomenclature.RepoUnderTest;
        }

        [When(@"I fill out the form and submit")]
        public void WhenIFillOutTheFormAndSubmit()
        {
            _loginPage.Login(
                username: _username, 
                password: _password, 
                repo: _repo);
        }

        [Then(@"I am taken to the repo page")]
        public void ThenIAmTakenToTheRepoPage()
        {
            Assert.AreEqual(
                expected: _repo,
                actual: Driver.Title);

            HelperMethods.Logout(Driver);
        }
    }
}

Le code:

Root
 |-Page Object Files
      |- Page Components
      |- Pages
      |- Test Tools  
 |- Step Definitions
      |- <*Steps.cs>  
 |- TESTS
      |- BDD Tests
          |-<*.feature>
      |- *standard selenium test files*

0 commentaires

3 Réponses :


0
votes

Vous avez peut-être créé une instance de pilote dans chacun des fichiers .cs. Par exemple: dans LoginSteps.cs, vous créez un pilote chrome dans l'emplacement ci-dessous.

private static readonly IWebDriver Driver = new ChromeDriver();

Vous devez créer le pilote en dehors des fichiers xStep.cs et le transmettre simplement à la classe / méthode basée sur le cadre.


2 commentaires

Je suppose que c'est là que ma nouveauté à SpecFlow entre en jeu. Comment puis-je passer un pilote au fichier étapes du Gherkin. Il ne semble pas y avoir de moyen de le faire.


Pour être franc, je n'ai pas beaucoup de mains sur SpecFlow, mais vous devez utiliser "avant scénario" ou "hooks". Désolé, je ne suis pas en mesure de fournir plus d'informations. Consultez ce lien Documentation SpecFlow



1
votes

J'ai trouvé comment résoudre ce problème à l'aide des étendues de liaison.

Dans chacun des fichiers Steps, je peux faire ce qui suit:

  [BeforeFeature(), Scope(Feature = "SearchTests")]
  public static void Startup()
  {
      _driver = new ChromeDriver();
  }

  [AfterFeature()]
  public static void ShutDown()
  {
      _driver?.Close();
  }

Cette opération ouvre et ferme le pilote uniquement pour le fichier de test que je souhaite. Je peux également choisir d'étendre la balise avant chaque test si j'ai besoin d'être plus précis.


3 commentaires

Cool. Mais je recommanderais de créer un browser_steps.cs avec la méthode "open". Avec dans la méthode, vérifiez si le pilote est nul, puis créez l'instance de pilote en fonction du navigateur que vous passez à la méthode ou dans la variable globale. De cette façon, vous n'avez pas à gérer le démarrage et le démontage du pilote dans chacun des fichiers step.cs.


c'était aussi ma réticence à le faire de cette façon. J'ai créé une classe baseSteps qui contient les créations de pilotes avec des méthodes de fonctionnalités avant et après ... Jusqu'à présent, tout fonctionne parfaitement.


Cool. Bonne chance!



0
votes

En fin de compte, cela est dû à la création des pilotes Web sous forme de champs statiques sur vos classes de définition d'étape. Vous devez centraliser cette logique.

Vous voulez créer votre pilote Web avant la fonctionnalité, l'enregistrer avec le conteneur d'injection de dépendances SpecFlow, puis transmettre cet objet IWebDriver à vos définitions d'étape.

I J'ai trouvé que c'était une bonne idée d'implémenter un pilote Web "paresseux", de sorte que la fenêtre du navigateur ne soit générée que lorsque votre code C # a réellement besoin d'interagir avec lui. Cette classe LazyWebDriver implémente l'interface IWebDriver et est un wrapper pour un vrai pilote Web.

LazyWebDriver.cs

[Binding]
public class LoginSteps
{
    private readonly IWebDriver Driver;
    private LoginPage _loginPage;

    private static string _username;
    private static string _password;
    private static string _repo;

    public LoginSteps(IWebDriver driver)
    {
        Driver = driver;
    }

    [Given(@"I am on the Login page")]
    public void GivenIAmOnTheLoginPage()
    {
        _loginPage = new LoginPage(Driver);
    }

    [Given(@"I have a good username/password combination")]
    public void GivenIHaveAGoodUsernamePasswordCombination()
    {
        _username = Nomenclature.WebClientPersonalUsername;
        _password = Nomenclature.WebClientPersonalPassword;
    }
    [Given(@"I select a repository")]
    public void GivenISelectARepository()
    {
        _repo = Nomenclature.RepoUnderTest;
    }

    [When(@"I fill out the form and submit")]
    public void WhenIFillOutTheFormAndSubmit()
    {
        _loginPage.Login(
            username: _username, 
            password: _password, 
            repo: _repo);
    }

    [Then(@"I am taken to the repo page")]
    public void ThenIAmTakenToTheRepoPage()
    {
        Assert.AreEqual(
            expected: _repo,
            actual: Driver.Title);

        HelperMethods.Logout(Driver);
    }
}

Ensuite, en utilisant les hooks SpecFlow (qui est juste un discours sophistiqué pour les "événements" dans SpecFlow), vous pouvez créer votre vrai pilote Web et le pilote Web paresseux et l'enregistrer avec le framework SpecFlow:

SeleniumHooks.cs
[Binding]
public sealed class SeleniumHooks
{
    private readonly IObjectContainer objectContainer;

    public SeleniumHooks(IObjectContainer objectContainer)
    {
        this.objectContainer = objectContainer;
    }

    [BeforeFeature]
    public void RegisterWebDriver()
    {
        objectContainer.RegisterInstanceAs<IWebDriver>(new LazyWebDriver(CreateWebDriver));
    }

    private IWebDriver CreateWebDriver()
    {
        return new ChromeDriver();
    }

    [AfterFeature]
    public void DestroyWebDriver()
    {
        objectContainer.Resolve<IWebDriver>()?.Close();
    }
}

Enfin, quelques modifications à votre fichier LoginSteps.cs :

public sealed class LazyWebDriver : IWebDriver
{
    private readonly Lazy<IWebDriver> driver;

    public string Title => driver.Value.Title;

    // Other properties defined in IWebDriver just pass through to driver.Value.Property

    public LazyWebDriver(Func<IWebDriver> driverFactory)
    {
        driver = new Lazy<IWebDriver>(driverFactory);
    }

    public IWebElement FindElement(By by)
    {
        return driver.Value.FindElement(by);
    }

    public void Close()
    {
        driver.Value.Close();
    }

    // other methods defined in IWebDriver just pass through to driver.Value.Method(...)
}

Notez que l'objet IWebDriver est passé en tant qu'argument constructeur à LoginSteps. SpecFlow est livré avec un framework d'injection de dépendances, qui est suffisamment intelligent pour transmettre le LazyWebDriver que vous avez enregistré dans SeleniumHooks en tant qu'argument IWebDriver au constructeur LoginSteps.

Assurez-vous de remplir le champ _loginPage un champ d'instance plutôt que statique.


0 commentaires