Quit browser in [AfterScenario] - c#

I am trying to learn C# and Selenium and for my first project I am trying to write the code for [BeforeScenario] and [AfterScenario]. I have manage to get the [BeforeScenario] working, however I am struggle to quit the browser in the [AfterScenario].
[BeforeScenario]
[BeforeScenario]
public void BeforeScenario()
{
ChromeOptions options = new ChromeOptions();
options.AddArguments("--incognito");
options.AddArguments("start-maximized");
IWebDriver webDriver = new ChromeDriver(options);
webDriver.Navigate().GoToUrl("URL");
}
[AfterScenario]
[AfterScenario]
public void AfterScenario()
{
webDriver.Quit()
}
Why does my webDriver.Quit() always throw an error
Error on webDriver
'the name 'webDriver' does not exist in the current context'
New Code:
public class Steps
{
public IWebDriver webDriver = null;
[BeforeScenario]
public void BeforeScenario()
{
// BeforeScenario code
ChromeOptions options = new ChromeOptions();
options.AddArguments("--incognito");
options.AddArguments("start-maximized");
IWebDriver webDriver = new ChromeDriver(options);
webDriver.Navigate().GoToUrl("http://10.118.88.50");
}
[AfterScenario]
public void AfterScenario()
{
// AfterScenario code
if (webDriver == null)
{
throw new Exception("Driver is null, call BeforeScenario() first.");
}
webDriver.Quit();
}
After following the suggestions in the comment below now have the code above. The error has been removed, however when the code gets to AfterScenario it is always null. when the code is in BeforeScenario the webDriver is fine.
What am I missing here

You create the webDriver variable inside your BeforeScenario() function
IWebDriver webDriver = new ChromeDriver(options);
and when you use it in AfterScenario(), the function does not know what "webDriver" is. Make a class variable that can be accessed by both functions
public IWebDriver webDriver;
and initialize it in BeforeScenario so you can use it in AfterScenario.
A good thing is to initialize your webdriver in your class as null, so you can catch an exception if AfterScenario() is run before BeforeScenario(), like so:
public IWebDriver driver = null;
[BeforeScenario]
public void BeforeScenario()
{
ChromeOptions options = new ChromeOptions();
options.AddArguments("--incognito");
options.AddArguments("start-maximized");
webDriver = new ChromeDriver(options);
webDriver.Navigate().GoToUrl("URL");
}
[AfterScenario]
public void AfterScenario()
{
if (driver == null)
{
throw new Exception("Driver is null, call BeforeScenario() first.");
}
driver.Quit();
}

Related

Migrating to NUnit from SpecRunner - Parallels Execution with Selenium

I am migrating my test framework to NUnit from SpecRunner since SpecRunner is not longer maintained.
So far i am able to run the tests in parrallel by using the assembly code [assembly: Parallelizable(ParallelScope.Fixtures)]
However I am using Selenium to run my UI tests, currently when i run the tests only one browser instance in open (just testing on Chrome) for the two tests causing one of them to fail.
In SpecRunner I could specify the number of threads by specifying them in the default.srprofile file using the testThreadCount attribute.
<Execution stopAfterFailures="0" retryFor="Failing" testThreadCount="3" testSchedulingMode="Sequential" />
And that would open 3 instances of Chrome locally on my machine, and on the CI machine.
My question is : Can I do something similar in NUnit3 to run a chrome instance per thread?
UPDATE
GetWebDriver.cs
public class GetWebDriver
{
private static TestContextModified context;
public static string url;
public static IWebDriver WebDriver(string browserName)
{
IWebDriver driver;
context = TestContextModified.GetContextInstance();
url = context.AppSetting["url"];
var seleniumHubURL = context.AppSetting["seleniumGridServer"];
switch (browserName)
{
case "IE":
try
{
if (context.AppSetting["REMOTE"] == "true")
{
InternetExplorerOptions options = new InternetExplorerOptions();
options.AddAdditionalCapability("ignoreProtectedModeSettings", true);
options.AddAdditionalCapability(CapabilityType.AcceptSslCertificates, true);
options.AddAdditionalCapability(CapabilityType.IsJavaScriptEnabled, true);
options.AddAdditionalCapability(CapabilityType.Platform,
new Platform(PlatformType.Windows));
driver = new RemoteWebDriver(
new Uri(seleniumHubURL), options.ToCapabilities(), TimeSpan.FromSeconds(600))
{ Url = url};
}
else
{
return new InternetExplorerDriver(new InternetExplorerOptions { IgnoreZoomLevel = true }) { Url = url};
}
return driver;
}
catch
{
string strCmdText;
strCmdText = #"/C cd bin\Debug&taskkill /im IEDriverServer.exe /f";
System.Diagnostics.Process.Start("CMD.exe", strCmdText);
Console.WriteLine("Deleted Chromedriver becouse Exception was raised");
return null;
}
case "Chrome":
try
{
if (context.AppSetting["REMOTE"] == "true")
{
ChromeOptions options = new ChromeOptions();
options.AddArguments("--window-size=1920,1080");
options.AddArguments("--start-maximized");
options.AddArguments("--headless");
options.AddArgument("--disable-gpu");
driver = new RemoteWebDriver(
new Uri(seleniumHubURL), options)
{ Url = url};
}
else
{
return new ChromeDriver { Url = url};
}
return driver;
}
catch
{
string strCmdText;
strCmdText = #"/C cd bin\Debug&taskkill /im chromedriver.exe /f";
System.Diagnostics.Process.Start("CMD.exe", strCmdText);
Console.WriteLine("Deleted Chromedriver becouse Exception was raised");
return null;
}
case "Firefox":
try
{
if (context.AppSetting["REMOTE"] == "true")
{
FirefoxOptions options = new FirefoxOptions();
driver = new RemoteWebDriver(
new Uri(seleniumHubURL), options);
return driver;
}
else
{
return new FirefoxDriver { Url = url};
}
}
catch
{
string strCmdText;
strCmdText = #"/C cd bin\Debug&taskkill /im geckodriver.exe /f";
System.Diagnostics.Process.Start("CMD.exe", strCmdText);
Console.WriteLine("Deleted Chromedriver becouse Exception was raised");
return null;
}
case string browser: throw new NotSupportedException($"{browser} is not a supported browser");
default: throw new NotSupportedException("not supported browser: <null>");
}
}
}
}
IntiliazeWebdriver.cs
public class IntializeWebdriver
{
public static IWebDriver driver;
public IntializeWebdriver()
{
}
public static IWebDriver webDriver()
{
if (driver == null)
{
driver = GetWebDriver.WebDriver("Chrome");
return driver;
}
else
{
return driver;
}
}
}
And then in my steps:
public class Steps: IntializeWebdriver
{
public Steps stepsTest;
private ScenarioContext scenarioContext;
public Steps(ScenarioContext _scenarioContext)
{
scenarioContext = _scenarioContext;
stepsTest= new Step(webDriver());
}
...
Kumar's answer leads you in the right direction. The web driver cannot be static, because then multiple threads (hence multiple tests) share the same instance of web driver. Instead, each scenario needs to create its own web driver instance and register it with the dependency injection framework. Unfortunately with how your code is currently, this will involve a good amount of refactoring. Despite this being a moderate amount of work, it is worth the effort.
Initialize web driver in a [BeforeScenario] hook (see SpecFlow docs):
[Binding]
public class SpecFlowHooks
{
private readonly IObjectContainer container;
public SpecFlowHooks(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
// Initialize whichever web driver you want to use, however
// you want to initialize it
var driver = new ChromeDriver(...);
// Register the web driver with SpecFlow's dependency injection framework
container.RegisterInstanceAs<IWebDriver>(driver);
}
}
Refactor step definition classes to accept an IWebDriver object as a constructor parameter:
[Binding]
public class YouStepDefinitions
{
private readonly IWebDriver driver;
public YouStepDefinitions(IWebDriver driver)
{
this.driver = driver;
}
[Given("...")]
public void GivenX()
{
driver.FindElement(...)
}
}
This ensures you initialize one web driver per scenario, which should allow your tests to run in parallel.
At the end of the test, you can use an [AfterScenario] hook to destroy the driver and even take a screenshot:
[Binding]
public class SpecFlowHooks
{
private readonly IObjectContainer container;
...
[AfterScenario]
public void CreateWebDriver()
{
var driver = container.Resolve<IWebDriver>();
var photographer = (ITakeScreenShot)driver;
// take screenshot with 'photographer' variable
driver.Quit();
driver.Dispose();
}
}
Looks like this line is causing issue :-
public static IWebDriver driver;
Can you try by not making this static? I remember that I faced issue in parallel execution via Nunit when the driver was static.

How can I run some test cases with chrome and come with Firefox using C# mstest?

There are total 10 test cases. I need to run some testcases with chrome lets say 1-3 and 6-8 test cases with chrome and 4-5 and 9-10 test cases with firefox.
The code in helper file is
static public IWebDriver GetWebDriverC(browserType brt)
{
ChromeOptions cOption = new ChromeOptions();
cOption.AddArgument("no-sandbox");
driver = new ChromeDriver(cOption);
driver.Navigate().GoToUrl(url);
return driver;
}
static public IWebDriver GetWebDriverFF(browserType brt, string url, string username, string password)
{
FirefoxDriverService service = FirefoxDriverService.CreateDefaultService();
service.FirefoxBinaryPath = #"path";
driver = new FirefoxDriver(service);
driver.Navigate().GoToUrl(url);
bool ele1 = AutoItX.WinExists("[CLASS:MozillaDialogClass]") == 1;
if (ele1)
{
AutoItX.WinActivate("[CLASS:MozillaDialogClass]");
AutoItX.Send(username);
AutoItX.Send("{TAB}");
AutoItX.Send(password);
AutoItX.Send("{ENTER}");
}
return driver;
}
Username and password is different for different users.
There are multiple testcases in main file
[TestInitialize]
public void initilize()
{
Webd= Helper.GetWebDriverC(Chrome);
}
[Priority(0)]
[TestMethod]
public void 1()
{
}
[Priority(2)]
[TestMethod]
public void 2()
{
}
.
.
.
[Priority(10)]
[TestMethod]
public void 10()
{
}
There is only one driver in Test initialize that is chrome and chrome gets open before each testcase run. I want that when testcases run on the basis of some condition as mentioned earlier testcases run on the desired Browsers.
How can I achieve that using C# mstest?
The problem here is that you're setting the WebDriver in the TestInitialize step, so any tests within the same class will be using the same driver, in this case Chrome.
One approach would be to have separate test classes for FireFox and Chrome.
Chrome Tests:
public class ChromeTests
{
[TestInitialize]
public void initilize()
{
Webd = Helper.GetWebDriverC(Chrome);
}
[TestMethod]
public void 1()
{
}
}
FireFox tests
public class FireFoxTests
{
[TestInitialize]
public void initilize()
{
Webd = Helper.GetWebDriverFF(Chrome);
}
[TestMethod]
public void 1()
{
}
}
Alternative solutions would include:
Having two WebDrivers in your test class, one for Chrome and one for FireFox, and then you can decide in you tests which driver you wish to use.
Creating a new WebDriver instance in each test

when using chromedriver, with specflow, a new browser instance open per step

I'm having an issue with Specflow. For some reason, a new instance of chrome is started per step.
can you help with this?
For follow test, 3 steps, 3 chrome instance are started.
Feature: Home
Background:
Given The QA Department site
Scenario: QA Department title
When QA site open
Then QA site tile must be "UI Testing Site"
The first instance has the content i want but the other two, that i don´t know why are started, are empty.
namespace QAtests.SpecFlow_Steps.Home
{
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TechTalk.SpecFlow;
[Binding]
class HomeTestSteps
{
private readonly MainPage mainPage = new MainPage();
private readonly PageHelper pageHelper = new PageHelper();
public HomeTestSteps()
{
}
[When(#"QA Department site open")]
public void GivenQADepartmentSiteOpen()
{
var url = this.mainPage.ValiteSiteAddress();
Assert.AreEqual(url, this.mainPage.siteURL);
}
[Then(#"QA Department site tile must be ""(.*)""")]
public void ThenQADepartmentSiteTileMustBe(string pageTile)
{
var title = this.pageHelper.GetElementTitle();
Assert.AreEqual(pageTile, title);
}
}
}
The main page class just sets the url and checks elements
namespace QAtests.Pages.Main
{
public class MainPage
{
private ChromeDriver driver = new ChromeDriver();
private readonly string elementTitle = "//title";
public readonly string siteURL = "http://uitest.duodecadits.com/";
public MainPage()
{
}
public void StartSite()
{
try
{
this.driver.Navigate().GoToUrl(siteURL);
}
catch (Exception e)
{
throw new Exception("Can´t set address.", e);
}
}
public void Close()
{
this.driver.Close();
}
public string ValiteSiteAddress()
{
return this.driver.Url;
}
public IWebElement ElementTitleisVisible()
{
try
{
return this.driver.FindElement(By.XPath(elementTitle));
}
catch(Exception e){
throw new Exception("Element tile not found.", e);
}
}
}
}
I think that is related with the declaration of
private ChromeDriver driver = new ChromeDriver();
You are instantiating a new ChromeDriver instance each time you instantiate a page.
As suggested, you will want to setup your webdriver in a BeforeScenario step.
Also, you could make use of the built in IOC container and register your web driver instance. Then that instance can be injected into any of your page objects, as and when they are needed.
Something like...
[Binding]
public class BaseScenarioHooks
{
protected IObjectContainer Container;
public BaseScenarioHooks(IObjectContainer container)
{
Container = container;
}
[BeforeScenario(Order = 3)]
public void SetupDrivers()
{
var webDriver = new WebDriver(browser, seleniumHub, url, Context.ScenarioInfo.Title,
Context["UniqueTestName"].ToString(), FrameworkConfiguration.VideoRecording);
Container.RegisterInstanceAs<WebDriver>(webDriver);
}
}
Have a look at https://github.com/techtalk/SpecFlow/wiki/Context-Injection.
Also have a look here for ideas on how to organize your driver class
Not seeing your thread get a lot of love here. In all honesty, I would look for a SpecFlow framework online and then start building yours out from there. SpecFlow uses hooks so you are probably going to want to invoke your browser with a BeforeScenario so that it is called once for each test. Put your cleanup in an AfterScenario hook.
If you use the nuget chromedriver and have it set always output to your bin debug folder, you can use the GetBasePath call so it always calls from that directory. You can remove the call and hard code the chromedrive.exe path though. This just makes it easier if it is going into source control.
Hopefully this will help you get started.
ex.
MyFile.cs
[BeforeScenario]
public static void Startup()
{
Browser.StartWebDriver();
}
Browser.cs
public class Browser
{
static IWebDriver _Driver;
static WebDriverWait _wait;
public static void StartWebDriver()
{
ChromeDriverService service = ChromeDriverService.CreateDefaultService(Path.Combine(GetBasePath, #"bin\Debug\"));
ChromeOptions options = new ChromeOptions();
options.AddArguments("--disable-extensions");
options.AddAdditionalCapability("useAutomationExtension", false);
options.AddExcludedArgument("enable-automation");
_Driver = new ChromeDriver(service, options);
_wait = new WebDriverWait(_Driver, TimeSpan.FromSeconds(10));
}
public static string GetBasePath
{
get
{
var basePath =
System.IO.Path.GetDirectoryName((System.Reflection.Assembly.GetExecutingAssembly().Location));
basePath = basePath.Substring(0, basePath.Length - 10);
return basePath;
}
}
}
Yes. This line
private ChromeDriver driver = new ChromeDriver();
is definitely your problem. Every time you access this property you are creating a new driver.
Here's a web hook class i use in my tests.
[Binding]
internal sealed class WebHooks
{
private readonly IObjectContainer _objectContainer;
public WebHooks(IObjectContainer objectContainer)
{
_objectContainer = objectContainer;
}
[BeforeScenario("web")]
public void BeforeWebScenario()
{
var options = new ChromeOptions();
options.AddArgument("--start-maximized");
options.AddAdditionalCapability("useAutomationExtension", false);
options.AddArgument("no-sandbox");
//HACK: this fixes issue with not being able to find chromedriver.exe
//https://stackoverflow.com/questions/47910244/selenium-cant-find-chromedriver-exe
var webDriver = new ChromeDriver(".", options);
webDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
_objectContainer.RegisterInstanceAs<IWebDriver>(webDriver);
}
[AfterScenario("web")]
public void AfterWebScenario()
{
var webDriver = _objectContainer.Resolve<IWebDriver>();
if (webDriver == null) return;
webDriver.Close();
webDriver.Dispose();
}
}
So this registers the webdriver with the internal SpecFlow DI container so that it can now be called in you step definitions.
Then modify your step definition class to access the webdriver as a constructor parameter
[Binding]
public class YourSteps
{
private readonly IWebDriver _webDriver;
public CanvasSteps(IWebDriver webDriver)
{
_webDriver = webDriver;
}
}
Specflow's internal DI automagically wires everything up and you should now have 1 webdriver instance per scenario. It also creates a better design because your cross-cutting concern of a webdriver instance has been extracted from your steps and placed in a Hook where is belongs. Just tag your scenario with the #web hook

Run Selenium tests in multiple browsers with C#

I have a method that creates 2 remote web drivers. one with chrome and another with firefox:
Driver.cs
public class Driver
{
public static IWebDriver Instance { get; set; }
public static void Initialize()
{
DesiredCapabilities[] browsers = {DesiredCapabilities.Firefox(),DesiredCapabilities.Chrome()};
foreach (DesiredCapabilities browser in browsers)
{
if (browser == DesiredCapabilities.Chrome())
{
var browser = DesiredCapabilities.Chrome();
System.Environment.SetEnvironmentVariable("webdriver.chrome.driver", "C:/Users/jm/Documents/Visual Studio 2013/Projects/VNextAutomation - Copy - Copy (3)/packages/WebDriverChromeDriver.2.10/tools/chromedriver.exe");
ChromeOptions options = new ChromeOptions() { BinaryLocation = "C:/Users/jm/AppData/Local/Google/Chrome/Application/chrome.exe" };
browser.SetCapability(ChromeOptions.Capability, options);
Console.Write("Testing in Browser: " + browser.BrowserName);
Instance = new RemoteWebDriver(new Uri("http://127.0.0.1:4444/wd/hub"), browser);
} else {
Console.Write("Testing in Browser: "+ browser.BrowserName);
Instance = new RemoteWebDriver(new Uri("http://127.0.0.1:4444/wd/hub"), browser);
}
}
Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
}
Then I have a Test class:
[TestClass]
public class LoginTests
{
[TestInitialize]
public void Init()
{
Driver.Initialize();
}
[TestMethod]
public void Failed_login()
{
LoginPage.GoTo();
LoginPage.LoginAs("user").WithPassword("pass").WithDatasource("db").Login();
Assert.IsTrue(LoginFail.IsAt, "Login failure is incorrect");
}
[TestMethod]
public void Admin_User_Can_Login()
{
LoginPage.GoTo();
LoginPage.LoginAs("user").WithPassword("pass").WithDatasource("db").Login();
Assert.IsTrue(HomePage.IsAt, "Failed to login.");
}
[TestCleanup]
public void Cleanup()
{
Driver.Close();
}
}
}
The problem is when Driver.Intialize gets called it doesn't run both chrome and firefox. What I want to happen is that when the Init method gets called it starts both browsers and runs the Test Methods in each browser.
The way I am currently doing this is with NUnit.
I had the same problem and could not find a good way to do it with MSTest.
What I am doing would be:
As you can see I just create a new TestFixture for each browser.
[TestFixture(typeof(ChromeDriver))]
[TestFixture(typeof(InternetExplorerDriver))]
[TestFixture(typeof(FirefoxDriver))]
public class LoginTests<TWebDriver> where TWebDriver : IWebDriver, new()
{
[SetUp]
public void Init()
{
Driver.Initialize<TWebDriver>();
}
[Test]
public void Failed_login()
{
LoginPage.GoTo();
LoginPage.LoginAs("user").WithPassword("pass").WithDatasource("db").Login();
Assert.IsTrue(LoginFail.IsAt, "Login failure is incorrect");
}
[Test]
public void Admin_User_Can_Login()
{
LoginPage.GoTo();
LoginPage.LoginAs("user").WithPassword("pass").WithDatasource("db").Login();
Assert.IsTrue(HomePage.IsAt, "Failed to login.");
}
[TearDown]
public void Cleanup()
{
Driver.Close();
}
}
}
Driver Class
public class Driver<TWebDriver> where TWebDriver : IWebDriver, new()
{
public static IWebDriver Instance { get; set; }
public static void Initialize()
{
if (typeof(TWebDriver) == typeof(ChromeDriver))
{
var browser = DesiredCapabilities.Chrome();
System.Environment.SetEnvironmentVariable("webdriver.chrome.driver", "C:/Users/jm/Documents/Visual Studio 2013/Projects/VNextAutomation - Copy - Copy (3)/packages/WebDriverChromeDriver.2.10/tools/chromedriver.exe");
ChromeOptions options = new ChromeOptions() { BinaryLocation = "C:/Users/jm/AppData/Local/Google/Chrome/Application/chrome.exe" };
browser.SetCapability(ChromeOptions.Capability, options);
Console.Write("Testing in Browser: " + browser.BrowserName);
Instance = new RemoteWebDriver(new Uri("http://127.0.0.1:4444/wd/hub"), browser);
} else {
Console.Write("Testing in Browser: "+ browser.BrowserName);
Instance = new RemoteWebDriver(new Uri("http://127.0.0.1:4444/wd/hub"), browser);
}
}
Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(15));
}
}
I have tried to fit it around your code.
If you wanted to be able to specify a browser to run a test on an adhoc basis rather than all of them every time using TestFixtures, Richard Bradshaw has an excellent tutorial here.
The idea is to use an app config file (and Factory Pattern) that houses values such as the browser, version, platform, selenium hub and port information (as well as any other data you might require in your Hub/Node implementation on Grid) and pull it in at the time of testing to create a WebDriver instance. You can then modify this file in between tests to spin up a WebDriver of a different type if necessary.
We use this to run tests sequentially with NUnit and it has proven quite effective.
an easier and a little bit more straight forward solution using NUnit:
namespace Test
{
//add all browser you want to test here
[TestFixture(typeof(FirefoxDriver))]
[TestFixture(typeof(ChromeDriver))]
public class SkillTest<TWebDriver> where TWebDriver : IWebDriver, new()
{
private IWebDriver _driver;
private string driverPath;
[SetUp]
public void Init()
{
_driver = new TWebDriver();
_driver.Navigate().GoToUrl("https://localhost:5001/");
}
[Test]
public void your_test_case()
{
//some test logic
} 
[TearDown]
public void CleanUp()
{
_driver.Quit();
_driver.Dispose();
}
}
}

Running PhantomJS Selenium Web Driver on local IIS stays on about:blank

I'm having a very frustrating issue with the Selenium PhantomJS driver in my UI tests in that all the other drivers work as expected but Phantom refuses to navigate anywhere when the address is localhost. Phantom works as expected when using an external URL.
PhantomJS 1.9.7, Selenium Web Driver 2.42.0, IIS 7.5
My very simple test class:
[TestFixture]
public class AcceptanceTests
{
private IWebDriver driver;
[Test]
public void HomePageChrome() // works
{
driver = new ChromeDriver();
driver.Navigate().GoToUrl("http://localhost/MySite");
Assert.AreEqual("My Project Name", driver.Title);
}
[Test]
public void HomePagePhantom() // does not work
{
driver = new PhantomJSDriver();
driver.Navigate().GoToUrl("http://localhost/MySite");
Debug.WriteLine(driver.Url); // about:blank
Assert.AreEqual("My Project Name", driver.Title);
}
[Test]
public void ExternalPagePhantom() // works
{
driver = new PhantomJSDriver();
driver.Navigate().GoToUrl("http://www.google.co.uk");
Assert.AreEqual("Google", driver.Title);
}
[TearDown]
public void TearDown()
{
if (driver != null)
{
driver.Quit();
driver.Dispose();
}
}
I've tried adding wait statements after driver.Navigate()... to no avail (either an explicit wait or waiting for a class or id to be found).
What am I missing here? Many thanks.

Categories

Resources