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.
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.
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.
I have been spending the last 3 Months teaching myself Automated Testing. Having had no previous experience (manual tester who had never coded in my life) I have (with the help of this Board) managed to create a selenium Object Based Framework, written tests that link to this framework and got all my tests to run.
However I now need to take my test suite and pass it through multiple environments on a cloud based service. The problem I have is that I have to define the environment each time I run the test set. Ideally I want to be able to define the environments I run my tests on (Firefox, Chrome, IE etc), and then set the test suite off to run my set of 17 tests 3 times across each browser.
I Don't want to set up a system to run this locally but instead have a method that calls my different methods for my Cloud Service (currently trialling a couple)
a Sample of my code is as follows
Test Code - Login
namespace Kukd_Consumer_Test
{
[TestFixture]
public class Login : Consumer_Standard_Functionality
{
[Test]
public void User_Can_Login()
{
LoginPage.LoginAs("xxxxxxxxxx").WithPassword("xxxxxxx").Login();
Assert.IsTrue(AccountPageBtns.IsAtAccountPage("Hi Richard"), "Failed to login");
AccountPageBtns.Logout();
}
}
}
My standard Functionality (call driver go to homepage, quit etc) Currently I have to comment all but one of my Driver Methods. I want to be able to define multiples here Ideally so I can define which environments I run my tests on (locally or cloud based)
public class Consumer_Standard_Functionality
{
[SetUp]
public void Init()
{
// currently have to comment out all but the one I want to run.
driver.InitializeChrome();
//driver.InitializeFireFox();
//CBT_Driver.InitialiseChromeCBT(testName);
//CBT_Driver.InitialiseFFCBT(testName);
//CBT_Driver.InitialiseIECBT(testName);
//driver.InitialiseBrowserStack();
Homepage.GoTo_HomePage();
}
[TearDown]
public void Cleanup()
{
driver.Quit();
}
My local Driver Class (plus browserstack)
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Remote;
using System;
namespace Kukd_Consumer_Tests
{
public class driver
{
public static IWebDriver Instance { get; set; }
public static void InitializeChrome()
{
Instance = new ChromeDriver(#"C:\Users\richard.cariven\Documents\Visual Studio 2015\Drivers\Chrome");
Instance.Manage().Window.Position = new System.Drawing.Point(2192, -963);
Instance.Manage().Window.Maximize();
Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
}
public static void InitializeFireFox()
{
Instance = new FirefoxDriver();
Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
}
public static string BSusername = "xxxxxxxxxxx";
public static string BSpassword = "xxxxxxxxxxx";
public static void InitialiseBrowserstack()
{
IWebDriver driver;
DesiredCapabilities caps = new DesiredCapabilities();
//caps.SetCapability("name", TestContext.TestName);
caps.SetCapability("browser", "IE");
caps.SetCapability("browser_version", "11.0");
caps.SetCapability("os", "Windows");
caps.SetCapability("os_version", "10");
caps.SetCapability("resolution", "1024x768");
caps.SetCapability("browserstack.user", BSusername);
caps.SetCapability("browserstack.key", BSpassword);
driver = new RemoteWebDriver
(new Uri("http://hub-cloud.browserstack.com/wd/hub/"), caps);
Instance = driver;
}
public static void refresh()
{
Instance.Navigate().Refresh();
}
public static void Quit()
{
Instance.Quit();
}
}
}
CBT Class - Set up to pick each Cross Browser testing Parameter
namespace Kukd_Consumer_Tests
{
public class CBT_driver
{
public static IWebDriver Instance { get; set; }
public static string CBTusername = "xxxxxxxx";
public static string CBTpassword = "xxxxxxxxxx";
public static void InitialiseChromeCBT(string testName)
{
IWebDriver driver;
var caps = new DesiredCapabilities();
caps.SetCapability("name", testName);
caps.SetCapability("build", "1.0");
caps.SetCapability("browser_api_name", "Chrome56x64");
caps.SetCapability("os_api_name", "Win8");
caps.SetCapability("screen_resolution", "1366x768");
caps.SetCapability("record_video", "true");
caps.SetCapability("record_network", "true");
caps.SetCapability("username", CBTusername);
caps.SetCapability("password", CBTpassword);
driver = new RemoteWebDriver
(new Uri("http://hub.crossbrowsertesting.com:80/wd/hub"), caps);
Instance = driver;
}
public static void InitialiseFFCBT()
{
IWebDriver driver;
var caps = new DesiredCapabilities();
caps.SetCapability("name", "test name");
caps.SetCapability("build", "1.0");
caps.SetCapability("browser_api_name", "FF46x64");
caps.SetCapability("os_api_name", "Win8");
caps.SetCapability("screen_resolution", "1366x768");
caps.SetCapability("record_video", "true");
caps.SetCapability("record_network", "true");
caps.SetCapability("username", CBTusername);
caps.SetCapability("password", CBTpassword);
driver = new RemoteWebDriver
(new Uri("http://hub.crossbrowsertesting.com:80/wd/hub"), caps);
Instance = driver;
}
public static void InitialiseIECBT(string testName)
{
IWebDriver driver;
var caps = new DesiredCapabilities();
caps.SetCapability("name", testName);
caps.SetCapability("build", "1.0");
caps.SetCapability("browser_api_name", "IE10");
caps.SetCapability("os_api_name", "Win8");
caps.SetCapability("screen_resolution", "1366x768");
caps.SetCapability("record_video", "true");
caps.SetCapability("record_network", "true");
caps.SetCapability("username", CBTusername);
caps.SetCapability("password", CBTpassword);
driver = new RemoteWebDriver
(new Uri("http://hub.crossbrowsertesting.com:80/wd/hub"), caps);
Instance = driver;
}
}
}
So what I need to be able to do is, in my common test so it applies to every test, loop through each of my Browsers/CBT Environments for all tests.
Am currently set up using NUnit (changed from MSTest because I have read it is easier to do this kind of thing in NUnit)
Any advice greatly appreciated, I have been advised that because I have used so many statics in my tests this may not be possible.
Regards
Richard
After a lot of searching and investigation I have decided to scrap my current framework and rewrite it using Xunit and a Factory Model. Once I have got this structure completed it should be easier to maintain and less brittle in future :)
I am currently trying to create some automation tests for a website, I am having one issue which is whenever I call my base class it creates a new instance of the FirefoxDriver. So whenever I call a page in my steps which inherits the base class it loads a new instance of the driver so it is no longer automating on the previous driver.
namespace RAA_AutomationTests
{
using OpenQA.Selenium.Firefox;
public abstract class BasePage
{
protected static IWebDriver driver;
protected BasePage()
{
//cant keep creating a new driver need to change this
driver = new FirefoxDriver();
}
public void click(By locator)
{
Find(locator).Click();
}
public IWebElement Find(By locator)
{
//ValidateSelector(locator); will update css selectors, however not to cause any extra delays by checking this programmatically.
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));
wait.Until(ExpectedConditions.ElementIsVisible(locator));
return driver.FindElement(locator);
}
}
}
This is all the code inside of my base class I just need to find a way of using the already open driver instead of creating a new one.
I have a hooks file which opens the browser for me so I just need the driver in the base class to use that one.
I'm not a big fan of the Page objects (it's a major violation of the SRP), but I think that your design can use a bit different approach. Take a look at the Selenium's home page examples:
/**
* Page Object encapsulates the Home Page
*/
public class HomePage {
private WebDriver selenium;
public HomePage(WebDriver selenium) {
if (!selenium.getTitle().equals("Home Page of logged in user")) {
throw new IllegalStateException("This is not Home Page of logged in user, current page" +
"is: " +selenium.getLocation());
}
}
public HomePage manageProfile() {
// Page encapsulation to manage profile functionality
return new HomePage(selenium);
}
/*More methods offering the services represented by Home Page
of Logged User. These methods in turn might return more Page Objects
for example click on Compose mail button could return ComposeMail class object*/
}
As you can see there is no reason to keep a static instance in your Pages, instead you could utilize IoC and a Constructor DI for the WebDriver object.
If I had to do it, I would create the Driver via Factory, put the actions(click, type etc.) in Actions class and keep Pages clean of such logic as much as possible.
The most simple solution is to create a webdrive instance in another place and pass it to your base class via constructor.
So in a Test class you should create a new driver (perhaps in SetUp fixture) and pass it to constructor of your base class:
driver = new FireFoxDriver();
BasePage base = new BasePage(driver);
In your base class the constructor should be:
public class BasePage(IWebDriver driver)
{
this.driver = driver;
}
This will solve your problem.
Also consider the more heavy but maybe better way - create WebDriver Factory. How to do it using C# is shown here: https://github.com/FriendlyTester/WebDriverFactoryExample/blob/master/WebDriverDriverFactory/WebDriverDriverFactory/WebDriverFactory.cs
In the Java perspective, let's say I have one class which open the browser
public class Hook {
public static WebDriver driver;
#BeforeSuite
public void startBrowser(){
driver=new FirefoxDriver();
}
}
To use this browser in another class, simply i inherit it to other classes and i will not initiate any drivers in those.
public class TestClass extends Hook {
#Test
public void toNavigate(){
driver.get("http://www.myurl.com");
}
}
The alternative way is to create one method in Hook which returns driver and need to collect that driver in another classes
something like this in Hook
public WebDriver startBrowser(){
return driver=new FirefoxDriver();
}
getting this driver in other classes
public class TestClass {
WebDriver driver;
#Test
public void toNavigate(){
Hook h=new Hook();
driver=h.startBrowser();
driver.get("http://www.myurl.com");
}
}
Thank You,
Murali