I'm currently trying to implement a scroll feature for my SpecFlow tests I'm running through Selenium to test a website. I need to be able to scroll down so the driver can see certain elements and test them. Basically, I've coded a scroll feature (using webdriver as IJavaSriptExecutor) but when I implemented that step to my tests it would try and open a separate webdriver. I need this scroll feature to execute on the driver that is currently open testing other features. Basically I need everything to be in unison if that makes sense. Anyway here is my code with the error at the bottom. I have no idea what the issue is.
namespace (mynamespace)
{
[Binding]
public class SeleniumContext
{
public SeleniumContext()
{
//create the selenium context
WebDriver = new ChromeDriver();
}
public IWebDriver WebDriver { get; private set; }
}
public class BeforeAllTests
{
private readonly IWebDriver objectContainer;
private static SeleniumContext seleniumContext;
public BeforeAllTests(IWebDriver container)
{
this.objectContainer = objectContainer;
}
[BeforeTestRun]
public static void RunBeforeAllTests()
{
seleniumContext = new SeleniumContext();
}
[BeforeScenario]
public static void RunBeforeScenario()
{
objectContainer.RegisterInstanceAs<SeleniumContext>(seleniumContext);
}
[Then(#"I scroll down")]
public void ThenIScrollDown()
{
ScenarioContext.Current.Pending();
}
}
}
Severity Code Description Project File Line Suppression State
Error CS0120 An object reference is required for the non-static field, method, or property 'BeforeAllTests.objectContainer'
Related
I created a xunit project and added specflow with Gherkin and selenium web drivers to the project. I've following StepDefinition file,
[Binding]
public class MyPageStepDefinition
{
private readonly UserContext _userContext;
private readonly ConfigurationDriver cd;
public MyPageStepDefinition(UserContext userContext)
{
_userContext = userContext;
cd = new ConfigurationDriver();
}
// Many Given, when and Then
}
I've UserContext file as follows,
public class UserContext
{
public string email { get; set; }
}
I'm using IObjectContainer to get help with ContextInjection in specflow as follows,
[Binding]
public class BrowserDriver
{
private readonly IObjectContainer _objectContainer;
// BrowserType is enum
public Dictionary<BrowserType, IWebDriver> _drivers;
public BrowserDriver(IObjectContainer objectContainer) {
_objectContainer = objectContainer;
_drivers = new Dictionary<BrowserType, IWebDriver>();
}
[BeforeScenario]
public void BeforeScenario()
{
_objectContainer.RegisterInstanceAs(_drivers);
}
}
My browser initialization code,
public IWebDriver InitBrowser(BrowserType browser)
{
IWebDriver driver = null;
switch (browser)
{
case BrowserType.Chrome:
ChromeOptions chromeOptions = new ChromeOptions();
if (!Debugger.IsAttached)
{
chromeOptions.AddArgument("headless");
chromeOptions.AddArguments("disable-gpu");
chromeOptions.AddArguments("window-size=1900,1280");
chromeOptions.AddArguments("--no-sandbox");
chromeOptions.AddArguments("--ignore-certificate-errors");
chromeOptions.AddArguments("--disable-dev-shm-usage");
}
driver = new ChromeDriver(chromeOptions);
break;
}
driver.Manage().Window.Maximize();
//driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
return driver;
}
I call above method as follows,
SupportedBrowserList().ForEach(b => _drivers.Add(b, _testContext.InitBrowser(b)));
public List<BrowserType> SupportedBrowserList()
{
return new List<BrowserType>
{
BrowserType.Chrome,
};
}
and I use _drivers as follows,
GetCurrentDriverList(_drivers).ForEach(d =>
{
// Do something on webpage, like d.FindElement(..);
});
public List<IWebDriver> GetCurrentDriverList(Dictionary<BrowserType, IWebDriver> drivers)
{
return new List<IWebDriver>(drivers.Values);
}
I've 2 features files to handle 2 flows. Most of the steps are common so they both call MyPageStepDefinition constructor. When I run the feature files parallelly (default behavior of xunit) then one or the other test case fail, saying the email is not matching. I was able to debug and understand, that the email of one test is going in another. Looks like there is some race condition in Context Injection in specflow in parallel execution.
To validate my hypothesis, I made the test run sequentially using xunit.runner.json file with following configuration,
{
"parallelizeTestCollections": false
}
and the code worked flawlessly. Now, if I run again in parallel it fails. It confirmed my hypothesis that specflow+xunit in parallel run is causing some issues. I'm pretty sure someone must have faced similar issue and fixed it, but I can't seem to figure it out. Can someone please help me in this? And How can I fix this?
Note: I've made the file simple, the UserContext has more data fields. None of the fields are static in UserContext.
So as we know when you use SpecFlow if you reuse a step from another test it automatically pulls it in and reuses it... however, I have the issue whereby Test A logs me in and test B logs in and confirms the home page is correct but as test A is initialising ChromeDriver when I come to use Test B my Driver wants to open another webpage causing the test to fail as its open an empty webpage.
My question is - How do I use the driver without it opening another instance of Chrome. Here is what I have code wise for my 'generic login:'
private LandingPageCode landingPage;
private HomePageCode HomePage;
[Given(#"I have entered my username, password selected login")]
public void GivenIHaveEnteredMyUsernamePasswordSelectedLogin()
{
driver = new ChromeDriver();
driver.Url = ("my URL");
landingPage = new LandingPageCode(driver);
HomePage = new HomePageCode(driver);
The code I have on test B which validates the homepage once logged in:
{
private ChromeDriver driver;
private HomePageCode HomePage;
private LandingPageCode landingPage;
[Given(#"Successfully log into Cal's website (.*)")]
public void GivenSuccessfullyLogIntoOptix(Decimal p0)
{
driver = new ChromeDriver();
HomePage = new HomePageCode(driver);
landingPage = new LandingPageCode(driver);
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
Assert.AreEqual("HomePage", driver.Title);
I see this question or related ones very frequently (How to properly manage and access webdriver instances to avoid problems with parallel execution of tests?). Integrating Selenium and SpecFlow can be tricky. You need to leverage the dependency injection framework that comes with SpecFlow, and use before and after scenario hooks to initialize the web driver, then register it with the dependency injection container. Later on in your step definition classes, you need to specify a constructor for those classes that accepts an IWebDriver object as a constructor parameter, and assign it to a field on each step definition class.
[Binding]
public class WebDriverHooks
{
private readonly IObjectContainer container;
public WebDriverHooks(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
// or new FirefoxDriver or new WhateverDriver as long as it implements
// the IWebDriver interface
ChromeDriver driver = new ChromeDriver();
// Make 'driver' available for DI
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
if (driver != null)
{
driver.Quit();
driver.Dispose();
}
}
}
And a sample step definition file:
[Binding]
public class LoginSteps
{
private readonly IWebDriver driver;
private readonly LoginPage loginPage;
public LoginSteps(IWebDriver driver)
{
// Assign 'driver' to private field or use it to initialize a page object
this.driver = driver;
// Initialize Selenium page object
this.loginPage = new LoginPage(driver);
}
[When(#"I go to the login page")]
public void WhenIGoToTheLoginPage()
{
// Use 'driver' in step definition
driver.FindElement(By.LinkText("Sign In")).Click();
}
[When(#"I log in")]
public void WhenILogIn()
{
// Use Selenium page object in step definition
loginPage.LogIn("testUser", "testPassword");
}
}
This not only allows you to share web driver instances across step definition files, but it centralizes the logic of creating and disposing of these objects, and brings you one step closer allowing parallel tests execution.
See also: Context Injection on SpecFlow.org.
You could remove your driver code from your tests and set up a framework for your code to run on. Using NUnit, you could develop a framework for yourself to run the tests in parallel. There are tones of online tutorials for this. [https://nunit.org/][1]
You could create a driver.cs class that looks like this which pulls the base URL from a config class.:
public static class Driver
{
public static IWebDriver driver = new ChromeDriver();
public static void InitializedDriver()
{
Driver.driver.Navigate().GoToUrl(Config.BaseURL);
Driver.driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
}
}
Then in your test class, you can use OneTimeSetUp to initialise your web driver:
[OneTimeSetUp]
public void Initialize()
{
Driver.InitializedDriver();
}
After your test codes, you can then tear down using:
[OneTimeTearDown]
public void CleanUp()
{
Driver.driver.Quit();
}
This would allow your tests to run on the same Chrome instance.
I'm pretty new to Specflow and C#, so I'm facing an issue with specflow hooks.
The problem is: when I use [BeforeScenario], the method is not even called while debugging.
Removing these hooks and replacing it by [TestInitialize], it works perfectly.
I searched here for solution in many questions, but I didn't find any problem besides something about private methods, which not seems to be my case.
I have 4 classes: Tests, Steps, PageObjects, and Hooks (which contains driver and hooks).
'Tests' class inherits from 'Steps', which inherits from 'PageObjects', which inherits from 'Hooks'.
Every call is public and I'm writing down some code from 'Hooks' class:
namespace AutomationPractice.Helper
{
[Binding]
public class Hooks
{
public IWebDriver _driver;
[BeforeFeature]
public void BeforeScenario()
{
if (_driver == null)
{
_driver = new ChromeDriver();
}
else { throw new Exception("Couldn't initialize the driver"); }
}
[AfterFeature]
public void AfterScenario()
{
if (_driver != null)
{
_driver.Quit();
}
else throw new Exception("There was an error while trying to close the driver");
}
}
}
'PageObjects' class:
namespace AutomationPractice.PageObjects
{
[Binding]
public class GoogleSearchPageObjects : Hooks
{
public string goToGooglePage(string url)
{
return _driver.Url = url;
}
public IWebElement GetTxtSearch()
{
return _driver.FindElement(By.Name("q"));
}
public void fillTxtSearch(string search)
{
GetTxtSearch().SendKeys(search);
}
}
}
'Steps' class:
namespace AutomationPractice.Steps
{
[Binding]
public class GoogleSearchSteps : GoogleSearchPageObjects
{
[Given(#"I am on google home page")]
public void GivenIAmOnGoogleHomePage(string url)
{
goToGooglePage(url);
}
[When(#"I fill the '(.*)' field")]
public void WhenIFillTheField(string search)
{
fillTxtSearch(search);
}
Every class is rounded by [Binding] though.
Thanks in advance!
You have too many things going on in the same class hierarchy. It would be much simpler to decouple the following things:
The Web Driver
The page objects
Step definitions
You can use SpecFlow's dependency injection framework to wire these things together using constructor arguments.
First your Hooks class where you manage the web driver instance for all step definitions and page objects:
[Binding]
public class Hooks
{
private IObjectContainer container;
public Hooks(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
var driver = new ChromeDriver();
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
driver.Quit();
driver.Dispose();
}
}
And the google search page object becomes a separate class that receives a web driver object as a constructor parameter, which decouples it from SpecFlow all together.
public class GoogleSearchPage
{
private readonly IWebDriver driver;
private IWebElement TxtSearch => driver.FindElement(By.Name("q"));
public GoogleSearchPage(IWebDriver driver)
{
this.driver = driver;
}
public void EnterSearchTerm(string searchTerm)
{
TxtSearch.SendKeys(searchTerm);
}
}
And finally the step definition class, which is where everything gets wired together via the dependency injection framework that comes with SpecFlow:
[Binding]
public class GoogleSearchSteps
{
private GoogleSearchPage googleSearch;
public GoogleSearchSteps(IWebDriver driver)
{
googleSearch = new GoogleSearchPage(driver);
}
[When(#"I fill the '(.*)' field")]
public void WhenIFillTheField(string search)
{
googleSearch.EnterSearchTerm(search);
}
}
Part of the problem you have right now is the class hierarchy. You are mixing classes that should be separated, but coordinated. By separating the step definitions from the initialization of the web driver, and keeping the page object in its own class you keep the dependencies between these objects organized and limited to exactly what they need (decoupling), and yet still allow them to work together (cohesion).
Your methods are names BeforeScenario and AfterScenario, but you are using the attributes for BeforeFeature and AfterFeature.
These have to be static that they will be called.
You need to change the attributes.
I am launching the amazon url using chrome drive instance and I want to share the same session in which I have launched the amazon in all test methods. Here is the code for the same.
Can somebody tell me how to share the session from one method to another?
public class UnitTest1
{
IWebDriver chromeDriver = null;
[Fact]
public void Launch_Amazon_WithSearching()
{
amazonUrl = "https://www.amazon.in/";
chromeDriver = new ChromeDriver(#"C:\Projects\Install\ChromDriver");
chromeDriver.Navigate().GoToUrl(amazonUrl);
chromeDriver.Manage().Window.Maximize();
//here we are looking for search textbox and then entering a new value
IWebElement searchElement = chromeDriver.FindElement(By.Id("twotabsearchtextbox"));
searchElement.SendKeys("bluetooth earphones");
searchElement.SendKeys(Keys.Enter);
}
[Fact]
public void Amazon_OpenSearchedItem_And_AddToCart()
{
//here we are finding the elements from the searched results.
IWebElement searchedElement = chromeDriver.FindElement(By.XPath("//*[#id='search']//span[contains(text(),'Raging Red')]"));
searchedElement.Click();
//Here we are getting the new window name and then setting the chrome driver window to that new window
string newWindowName = chromeDriver.WindowHandles.Where(x => x != chromeDriver.CurrentWindowHandle).FirstOrDefault();
chromeDriver.SwitchTo().Window(newWindowName);
//this is for clicking add to cart button
IWebElement addToCartElement = chromeDriver.FindElement(By.Id("add-to-cart-button"));
addToCartElement.Click();
Assert.NotNull(addToCartElement);
}
}
The usage of FactAttribute reveals that you're using xUnit.
xUnit documentation:
xUnit.net creates a new instance of the test class for every test that is run, so any code which is placed into the constructor of the test class will be run for every single test.
That means fields won't help you to share the Chromedriver instance between test methods.
What you need is a fixture class - xUnit will make sure to create an instance of this class shared across tests methods. If your fixture class implements IDisposable, xUnit will call the dispose method after the methods of the class have run.
public class ChromeDriverFixture
{
public ChromeDriverFixture()
{
Driver = new ChromeDriver(#"C:\Path\To\ChromeDriver");
Driver.Manage().Window.Maximize();
}
public IWebDriver Driver { get; }
}
public class UnitTest1 : IClassFixture<ChromeDriverFixture>
{
private ChromeDriverFixture _fixture;
public UnitTest1(ChromeDriverFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void Launch_Amazon_WithSearching()
{
const string amazonUrl = "https://www.amazon.in/";
_fixture.Driver.Navigate().GoToUrl(amazonUrl);
// ...
}
}
I am new to C#, i am not able to make driver thread safe. I am able to open the two browser as soon as second browser opens, first driver lose its references.
below is my code i have three class
namespace TestAutomation{
[TestFixture]
[Parallelizable(ParallelScope.Children)]
public class UnitTest1 : Setup
{
[Test, Property("TestCaseID","123")]
public void TestMethod1(this IWebDriver driver1)
{
driver1.Navigate().GoToUrl("https://www.google.com");
driver1.FindElement(By.Name("q")).SendKeys("test1");
Thread.Sleep(10000);
}
[Test, Property("TestCaseID", "234")]
public void TestMethod2()
{
driver.Navigate().GoToUrl("https://www.google.com");
driver.FindElement(By.Name("q")).SendKeys("test2");
Thread.Sleep(15000);
}
}}
Setup Class
namespace TestAutomation{
public class Setup:WebDriverManager
{
[SetUp]
public void setupBrowser()
{
driver = new ChromeDriver("C:\\Users\\Downloads\\chromedriver_win32");
}
[TearDown]
public void CloseBrowser()
{
driver.Close();
driver.Quit();
// driver.Close();
//driver.Quit;
}
}}
Webdrivermanager
namespace TestAutomation{
public class WebDriverManager
{
public IWebDriver driver { get; set; }
}
}
i am looking for a solution like ThreadLocal injava where i can get and set the driver for each thread in the setup method
Remove the SetUp & TearDown Attributes for the methods and call them explicitly. When you use these method attributes, it starts sharing resources across tests in the same class or inherited classes.
The below solution works perfectly fine. I have developed a project in which you can execute browser tests in parallel (method level parallelization). You can modify the project as per your needs.
Project Link: www.github.com/atmakur
[TestFixture]
class Tests
{
[Test]
public void Test1
{
using(var testInst = new TestCase())
{
testInst
.Init()
.NavigateToHomePage();
}
}
}
public class TestBase:IDisposable
{
private IWebDriver BaseWebDriver;
private TestContext _testContext;
public NavigatePage Init()
{
_testContext = TestContext.CurrentTestContext;
BaseWebDriver = new ChromeDriver();
.
.
.
}
public override void Dispose()
{
//Kill Driver here
//TestContext instance will have the AssertCounts
//But The Testcontext instance will have the result as Inconclusive.
}
}
You are doing two contradictory things:
Using a new browser for each test.
Sharing the browser property between the tests.
You should do one or the other. If you want to create a new browser for each test, don't store a reference to it where the other test also accesses it.
Alternatively, use OneTimeSetUp and OneTimeTearDown and only create the browser once. However, in that case, you can't run the tests in parallel.