Running selenium against multiple browsers with MSTEST - selenium

Im using selenium and using mstest to drive it. My problem is i want my entire suite to run against 3 different browsers(IE,Firefox and chrome).
What i can't figure out is how to data drive my test on a suite level or how to rerun the suite with different paramateres.
I know i can add a datasource to all my tests and have the individual test run against multiple browsers but then i would have to duplicate the 2 lines for the datasource for every single test which i don't think is very good solution.
So anybody knows how i can data drive my assembly initialize? or if there's another solution.

This is what I did. The benefit of this approach is that it will work for any test framework (mstest, nunit, etc) and the tests themselves don't need to be concerned with or know anything about browsers. You just need to make sure that the method name only occurs in the inheritance hierarchy once. I have used this approach for hundreds of tests and it works for me.
Have all tests inherit from a base test class (e.g. BaseTest).
BaseTest keeps all driver objects (IE, FireFox, Chrome) in an array (multiDriverList in my example below).
Have the following methods in BaseTest:
public void RunBrowserTest( [CallerMemberName] string methodName = null )
{
foreach( IDriverWrapper driverWrapper in multiDriverList ) //list of browser drivers - Firefox, Chrome, etc. You will need to implement this.
{
var testMethods = GetAllPrivateMethods( this.GetType() );
MethodInfo dynMethod = testMethods.Where(
tm => ( FormatReflectionName( tm.Name ) == methodName ) &&
( FormatReflectionName( tm.DeclaringType.Name ) == declaringType ) &&
( tm.GetParameters().Where( pm => pm.GetType() == typeof( IWebDriver ) ) != null ) ).Single();
//runs the private method that has the same name, but taking a single IWebDriver argument
dynMethod.Invoke( this, new object[] { driverWrapper.WebDriver } );
}
}
//helper method to get all private methods in hierarchy, used in above method
private MethodInfo[] GetAllPrivateMethods( Type t )
{
var testMethods = t.GetMethods( BindingFlags.NonPublic | BindingFlags.Instance );
if( t.BaseType != null )
{
var baseTestMethods = GetAllPrivateMethods( t.BaseType );
testMethods = testMethods.Concat( baseTestMethods ).ToArray();
}
return testMethods;
}
//Remove formatting from Generic methods
string FormatReflectionName( string nameIn )
{
return Regex.Replace( nameIn, "(`.+)", match => "" );
}
Use as follows:
[TestMethod]
public void RunSomeKindOfTest()
{
RunBrowserTest(); //calls method in step 3 above in the base class
}
private void RunSomeKindOfTest( IWebDriver driver )
{
//The test. This will be called for each browser passing in the appropriate driver in each case
...
}

To do this, we wrote a wrapper around webdriver and we use a switch statement based on a property to select the browser type.
Here's a snippet. Using the DesiredCapabilities, you can tell grid which browsers to execute against.
switch (Controller.Instance.Browser)
{
case BrowserType.Explorer:
var capabilities = DesiredCapabilities.InternetExplorer();
capabilities.SetCapability("ignoreProtectedModeSettings", true);
Driver = new ScreenShotRemoteWebDriver(new Uri(uri), capabilities, _commandTimeout);
break;
case BrowserType.Chrome:
Driver = new ScreenShotRemoteWebDriver(new Uri(uri), DesiredCapabilities.Chrome(), _commandTimeout);
break;
}

This idea is better for an automated CI scenario rather than interactive UI, but you can use a runsettings file and declare a parameter in that:
<?xml version='1.0' encoding='utf-8'?>
<RunSettings>
<TestRunParameters>
<Parameter name="SELENIUM_BROWSER" value="Firefox" />
</TestRunParameters>
</RunSettings>
You'll need a TestContext on your Test class
public TestContext TestContext { get; set; }
Then in your MSTest when you initialise the Driver you can check which browser you want to run
switch (TestContext.Properties["SELENIUM_BROWSER"]?.ToString())
{
case BrowserType.Chrome:
return new ChromeDriver();
case BrowserType.Edge:
return new EdgeDriver();
case BrowserType.Firefox:
return new FirefoxDriver();
}
You would then run the suite of tests n times, once for each runsettings file

Related

Unable to download testng from http://beust.com/eclipse/ due to company policy .

I have created a maven project , with cucumber BDD and testNG . However to use testng i need to install the testng pluggin from eclipse help . The problem is my company has blocked usage of such external connections . Is there an alternative for this .
Following are your options.
See if you can get an approval from your company's IT department to whitelist the eclipse plugin download site so that you can install it via eclipse (or) have them download the eclipse plugin jar separately, and you can drop the jar in the dropins folder so that eclipse is aware of it. For more information, please refer to the answers of this stackoverflow question.
If available, resort to using an alternative IDE such as IntelliJ. IntelliJ unlike eclipse, comes pre-installed with the TestNG plugin and should suffice.
You leverage the build tool such as Maven/Ant/Gradle to run your tests from the command prompt. Both Maven and Gradle lets you run even 1 single test also at a given time. So you should be able to easily run tests without the IDE, from the command prompt (which is eventually how your tests would be run in a Continuous Integration environment such as Jenkins)
You create a main() method housing class, which would use the TestNG APIs directly to create tests. So every-time you want to run a TestNG test class or a suite etc., you would merely go back to your runner class, update it with the details and then run via it [ To me this option should be your last resort]
Here's a full fledged sample for option (4), which you should be able to start tweaking for your own use.
public class Practice {
public static void main(String[] args) {
for (String each : new String[]{"A", "B"}) {
runWith(each);
}
}
private static void runWith(String group) {
TestNG testNG = new TestNG();
XmlSuite xmlSuite = new XmlSuite();
xmlSuite.setName("suite");
XmlTest xmlTest = new XmlTest(xmlSuite);
xmlTest.setName("test");
xmlTest.addIncludedGroup(group);
XmlClass clazz = new XmlClass(Practice.class);
clazz.loadClasses();
xmlTest.getClasses().add(clazz);
testNG.setXmlSuites(Collections.singletonList(xmlSuite));
System.out.println(xmlSuite.toXml());
testNG.run();
}
#Test(dataProvider = "SearchProvider", groups = "A")
public void testMethodA(String author, String searchKey) {
System.out.println("testMethodA :" + author + ", " + searchKey);
}
#Test(dataProvider = "SearchProvider", groups = "B")
public void testMethodB(String searchKey) {
System.out.println("testMethodB :" + searchKey);
}
#DataProvider(name = "SearchProvider")
public Object[][] getDataFromDataprovider(ITestContext c) {
Object[][] groupArray = null;
for (String group : c.getIncludedGroups()) {
if (group.equalsIgnoreCase("A")) {
groupArray = new Object[][]{
{"Guru99", "India"},
{"Krishna", "UK"},
{"Bhupesh", "USA"}
};
break;
} else if (group.equalsIgnoreCase("B")) {
groupArray = new Object[][]{
{"Canada"},
{"Russia"},
{"Japan"}
};
}
break;
}
//return groupArray;
return groupArray;
}
}

Pass dynamic value to test method parameter using TestNG class

I am automating a web page which runs in multi-threading environment, so I am exporting every test result into a file system and I wanted to maintain every test result uniquely for the future reference. So is there a way to pass file name as parameter to a test method dynamically while calling it from TestNG class.
I know we can pass parameters from .xml file but if I do that the values will more like static and can be seen by all the thread running parallel.
Test class will be called from main method as bellow
public class Test {
public static void main(String[] args) throws ParseException {
try
{
TestNG testng = new TestNG();
testng.setTestClasses(new Class[] { Testing.class });
testng.run();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
Bellow code is my test method
public class Testing {
#Test
#Parameters("filename")
public void testMethod(String fileName){
System.out.println("filename is: "+fileName);
// ---- remaining test logic -----
}
}
Or can we use TestListenerAdapter onStart() method to inject parameter values...?.
If you want unique file name you can just add it a time stamp
Date date = new Date();
Format formatter = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
String timeStamp = formatter.format(date);
String fileName = "TestResults-" + timeStamp;
You can store your values into ITestContext which will be available for all tests.
You can set up the values in a configuration method (#BeforeSuite for example) or a listener.
Pass Dynamic Parameters to TestNG suite during runtime
What the below code does:
I want to add a list of parameters to each test during runtime. These parameters are passed as maven runtime arguments. They are read using System.getProperty() method as shown below. Then these parameters are added to the test inside suite and testng is ran successfully. This can be really useful in other scenarios as well.
The below code reads the testng.xml file and adds parameter to
List<String> parameters = new ArrayList<>();
parameters = Arrays.asList(System.getProperty("parameters").split(",");
TestNG tng = new TestNG();
File initialFile = new File("testng.xml");
InputStream inputStream = FileUtils.openInputStream(initialFile);
Parser p = new Parser(inputStream);
List<XmlSuite> suites = p.parseToList();
for(XmlSuite suite:suites){
List<XmlTest> tests = suite.getTests();
for (XmlTest test : tests) {
for (int i = 0; i < parameters.size(); i++) {
HashMap<String, String> parametersMap = new HashMap<>();
parametersMap.put("parameter",parameters.get(i));
test.setParameters(parametersMap);
}
}
}
tng.setXmlSuites(suites);
tng.run();

How do I replace one test in an XML-defined suite with three others in TestNG?

Our team uses TestNG to run some tests in Selenium. We need to be able to run a given test on 3 different browsers (Chrome, Firefox, and [sadly] IE). We have a browser parameter on our base test class and really we could just declare three tests, one each for each browser; however, we'd really like to just be able to specify the browser value as "Standard 3" and have that run the test on each browser automatically.
So, I've built a class that implements ISuiteListener and attempts to create the new tests on the fly. However, any way I try to add tests fails. That is, no new tests I try to add will be executed by the suite. It's like nothing I did actually changed anything.
Here's my code:
public class Standard3BrowserSuiteListener implements ISuiteListener {
#Override
public void onStart(final ISuite suite) {
final XmlSuite xmlSuite = suite.getXmlSuite();
final Map<String, String> suiteParameters = xmlSuite.getParameters();
final List<XmlTest> currentTests = new ArrayList<XmlTest>(xmlSuite.getTests());
final ArrayList<XmlTest> testsToRun = new ArrayList<XmlTest>(currentTests.size());
for (final XmlTest test : currentTests) {
final Browser browser;
final Map<String, String> testParameters = test.getAllParameters();
{
String browserParameter = testParameters.get("browser");
if (browserParameter == null) {
browserParameter = suiteParameters.get("browser");
}
browser = Util.Enums.getEnumValueByName(browserParameter, Browser.class);
}
if (browser == Browser.STANDARD_3) {
XmlTest nextTest = cloneTestAndSetNameAndBrowser(xmlSuite, test, testParameters, "Chrome");
xmlSuite.addTest(nextTest);
testsToRun.add(nextTest); // alternate I've tried to no avail
nextTest = cloneTestAndSetNameAndBrowser(xmlSuite, test, testParameters, "Firefox");
xmlSuite.addTest(nextTest);
testsToRun.add(nextTest); // alternate I've tried to no avail
nextTest = cloneTestAndSetNameAndBrowser(xmlSuite, test, testParameters, "IE");
xmlSuite.addTest(nextTest);
testsToRun.add(nextTest); // alternate I've tried to no avail
} else {
testsToRun.add(test);
}
}
// alternate to xmlSuite.addTest I've tried to no avail
testsToRun.trimToSize();
currentTests = xmlSuite.getTests();
currentTests.clear();
currentTests.addAll(testsToRun);
}
private XmlTest cloneTestAndSetNameAndBrowser(final XmlSuite xmlSuite, final XmlTest test,
final Map<String, String> testParameters, final String browserName) {
final XmlTest nextTest = (XmlTest) test.clone();
final Map<String, String> nextParameters = new TreeMap<String, String>(testParameters);
nextParameters.put("browser", browserName.toUpperCase());
nextTest.setName(browserName);
final List<XmlClass> testClasses = new ArrayList<XmlClass>(test.getClasses());
nextTest.setClasses(testClasses);
return nextTest;
}
#Override
public void onFinish(final ISuite suite) {}
}
How can I replace the test with the browser value "Standard 3" with 3 tests and have it run properly? Thanks!
Here's what you need to do :
Upgrade to the latest released version of TestNG.
Build an implementation of org.testng.IAlterSuiteListener
Move your implementation that you created in ISuiteListener into this listener implementation.
Wire in this listener via the <listeners> tag in your suite XML File (or) via ServiceLoaders (As described in the javadocs of this interface)

ElasticClient settings not applied when running in separate tests

We are running multiple unit tests, each of them creates it's own instance of ElasticClient. Some of the tests use "StringEnumConverter" to convert enums, other will use the default convertor (into integers). The problem is that it seems the settings of connection is cached somewhere and it's only the first test that decides how enums will be converted.
This is not a threading issue, since we are running the tests sequentially.
The constructor of the TestBase class looks like this:
public TestsBase(bool serializeEnumsToString = false)
{
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(
node,
defaultIndex: "my-application" + Guid.NewGuid().ToString()
);
if (serializeEnumsToString)
{
Console.Write("Setting up the enums convertor");
settings.AddContractJsonConverters(type =>
{
// TypeCheck to return StringEnumConverter for Enums and Nullable<T> where T : Enum
if (type.IsEnum ||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Nullable<>) &&
type.GetGenericArguments().First().IsEnum))
{
return new StringEnumConverter();
}
return null;
});
}
client = new ElasticClient(settings);
}
Is it possible that there is a cache (on appdomain level?) that would be used any time new instance of ElasticClient is created?

Plugin: How to insert new method in existing PHP class?

I'm trying to create a IntelliJ plugin (mostly for learning purposes). My aim is that by pressing a keyboard shortcut the plugin will generate a corresponding PHP unit test method stub in the test file.
So let's say Db.php is open, the upon pressing Ctrl+Shift+U the plugin will create a unit test stub in DbTest.php.
So far I've figured out how to get the method name at cursor and how to locate the corresponding Unit test file (i.e. Db => DbTest) as PsiFile.
PsiFile[] search = FilenameIndex.getFilesByName(project, testFileName, scope); //scope is the test directory
PsiFile testFile = search[0];
What I cannot figure out is how to insert the generated new method stub this in testFile and then save the changes?
P.S. I see there exists a createMethodFromText function but how do I get the PsiClass from PsiFile? Also how do I save the changes?
There're just a few simple steps.
Find PhpClass you want to insert a new method in. As you already have PsiFile you can either traverse a tree manually or use PhpElementVisitor.
1.1. To travers a tree manually you can use PsiTreeUtil#findChildOfType method. In your case you'll need to find GroupStatement first, then the class you need.
1.2. Invoke PsiElement#accept method (PsiFile is an instance of PsiElement) provided with PhpElementVisitor with overridden #visitPhpGroupStatement and #visitPhpClass methods.
Use PhpPsiElementFactory#createMethod to create the new method from text. Note that this class isn't a part of the public API, so theoretically it can be easily changed/moved/removed/whatever in the future.
Use PsiElement#add (PhpClass is also an instance of PsiElement) to insert the method into the class.
That's all. You don't need to explicitly save the changes.
Here is what worked for me in the end. Thanks everyone for the help
for (int i = 0; i < found.getTextLength(); i++) {
PsiElement ele = found.findElementAt(i);
PhpClass phpClass = PsiTreeUtil.getParentOfType(ele, PhpClass.class);
if (phpClass != null) {
Method methodExists = findMethod(phpClass, methodName);
if (methodExists == null) {
new WriteCommandAction.Simple(phpClass.getProject(), phpClass.getContainingFile()) {
#Override
protected void run() throws Throwable {
PsiElement brace = phpClass.getLastChild();
if (brace != null) {
Method method = PhpPsiElementFactory.createMethod(phpClass.getProject(), "public function " + methodName + "() {\n\n}");
CodeStyleManager styleManager = CodeStyleManager.getInstance(getProject());
styleManager.reformat(method);
PsiElement newMethod = phpClass.addBefore(method, brace);
PsiNavigateUtil.navigate(newMethod);
}
}
}.execute();
} else {
PsiNavigateUtil.navigate(methodExists);
}
break;
}
}