SpecFlow writes its output into Console like this:
Given the "TestOperator" user is logged in
-> done: WebUserSteps.GivenTheUserIsLoggedIn("TestOperator", "") (9.5s)
How can we make it use NLog to configure where it should write?
With this:
public class CustomListener : ITraceListener
{
private Logger Log = LogManager.GetLogger("SPECFLOW");
public void WriteTestOutput(string message)
{
Log.Trace(message);
}
public void WriteToolOutput(string message)
{
Log.Trace(message);
}
}
And
[Binding]
public class ScenarioContextInitializer
{
private readonly IObjectContainer _container;
public ScenarioContextInitializer(ScenarioContext scenarioContext)
{
_container = (scenarioContext ?? throw new ArgumentNullException(nameof(scenarioContext))).ScenarioContainer;
}
[Before]
protected void Load()
{
_container.RegisterTypeAs<CustomListener, ITraceListener>();
}
}
It didn't work. I know there is ability to add plugins but that seems too much overhead.
Also we use ObjectivityLtd Test.Automation extensions.
It works via xunit tests generated by SpecFlow.xUnit
The issue is probably that NLog cannot find it's config.
When running unit tests, the dlls are moved and not the nlog.config
There are multiple solutions:
Load the config from a fixed path, e.g.
LogManager.Configuration = new XmlLoggingConfiguration("c:/mydir/nlog.config");
Or setup from code instead of config, e.g.:
var config = new LoggingConfiguration();
config.AddRuleForAllLevels(new FileTarget()
{
FileName = "c:/temp/logfile.log"
});
LogManager.Configuration = config; //apply config
See wiki
If that is still an issue, check the internal log
I've done similar thing in my framework with this workaround:
Add hook [AfterStep], that calls Console.WriteLine() [or your logger] with the name of the step + if passed or not (if test error != null, that means failed, o.w passed)
Please note that this works perfect in parallel execution. (the correct output goes to each test)
Here is the example:
https://github.com/LirazShay/SpecFlowDemo/blob/master/src/SpecFlowDemo/Hooks.cs
Something like this:
[AfterStep]
public void LogStepResult()
{
string stepText = StepContext.StepInfo.StepDefinitionType + " " + StepContext.StepInfo.Text;
Console.WriteLine(stepText);
var stepTable = StepContext.StepInfo.Table;
if (stepTable != null && stepTable.ToString() != "") Console.WriteLine(stepTable);
var error = ScenarioContext.TestError;
Console.WriteLine(error != null ? "-> error: " + error.Message : "-> done.");
}
Related
I'm attempting to write a generic .Net Core 2.2 Console Application that allows me to use Identity. Specifically I have a database and am simply tring to call SignInManager.PasswordSignInAsync() to authenticate the username/password against my DB.
If I run this in a full blown .NetCore WebApp, where the HttpContext and DI are all built out, it works fine. If I strip it down and simply call the base services I get the same error every time.
I've been trying variants for a few days now and simply cannot figure out what I'm missing.
Any assistance would be greatly appreciated.
I have a class which manages the buildout of the services available for the console app.
public class FXLoginProvider
{
private readonly IServiceCollection _svccoll;
private UserManager<FXUser> _um = null;
private SignInManager<FXUser> _sm = null;
public UserManager<FXUser> UserMgr
{
get { return _um ?? (_um = _svccoll.BuildServiceProvider().GetService<UserManager<FXUser>>()); }
}
public SignInManager<FXUser> SignInMgr
{
get { return _sm ?? (_sm = _svccoll.BuildServiceProvider().GetService<SignInManager<FXUser>>()); }
}
public FXLoginProvider()
{
string s = "Data Source=.\\SQLEXPRESS;Initial catalog=csNextGen;Integrated Security=True;TrustServerCertificate=True;ApplicationIntent=ReadWrite";
_svccoll = new ServiceCollection();
_svccoll.AddDbContext<FXDataContext>(options => { options.UseSqlServer(s); });
_svccoll.AddIdentity<FXUser, FXUserRole>().AddDefaultTokenProviders();
_svccoll.AddTransient<IUserStore<FXUser>, FXUserStore>();
_svccoll.AddTransient<IRoleStore<FXUserRole>, FXRoleStore>();
_svccoll.AddLogging();
_svccoll.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
}
Then in my main app...
class Program
{
static void Main(string[] args)
{
try
{
FXUser uu = null;
string sUsername = "user";
string sPassword = "P$sSw0rrD#!";
// create the service provider
FXLoginProvider icp = new FXLoginProvider();
// grab the sign in manager
SignInManager<FXUser> sm1 = icp.SignInMgr;
// fetch the user from the db, this works.
uu = icp.UserMgr.FindByNameAsync(sUsername).Result;
// update my security stamp, this works too
sm1.UserManager.UpdateSecurityStampAsync(uu).GetAwaiter().GetResult();
// I was receiving a Null context error, so I added a default context.
sm1.Context = new DefaultHttpContext();
var r = sm1.PasswordSignInAsync(sUsername, sPassword, false, false).GetAwaiter().GetResult();
Console.WriteLine(r);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
and it always throws the same exception:
Value cannot be null.\r\nParameter name: provider
I do see in the StackTrace it is throwing down in DependencyInjection.ServiceProviderServiceExtensions (source code for DI.SPSE) because the IServiceProvider is null; so I guess I'm missing a service in my list?
I was able to figure out the problem with my implementation. My error was simply that I had not completely filled in the default http context properly.
sm1.Context = new DefaultHttpContext();
should have been
sm1.Context = new DefaultHttpContext() { RequestServices = icp._svccoll.BuildServiceProvider() };
Note: I needed to change the access level of the _svccoll too.
With this change in place I was able to use the SignInManager to authenticate against my back end database.
I battled this problem for days so I'm happy to share my solution (solution is available on GitHub). I hope this helps!
SingInManager relies on cookie, you can’t use it in console app. Instead of it use UserManager<> there a method to verify password
I have been bouncing between guides and YouTube videos trying to implement Selenium Grid 2 on an existing project for a couple of days and I've gotten stuck, please help!
Our framework is Specflow 3.0.220, Selenium WebDriver 3.141.0, C#, NUnit 3.12.0, Selenium Grid selenium-server-standalone-3.141.59.
My initial objectives to implement Selenium Grid 2 are as follows:
Set up a hub and node(s) on my local machine = done.
Run a test through one of the nodes = done.
Run a test on all nodes simultaneously = headache.
Regarding item 2, I have set up two nodes, one is a Chrome node and one is a Firefox node. I can run a test through both of them, but not simultaneously.
I feel that I missing a piece of the puzzle here.
Here's the set-up:
Scenario Outline: Log in
Given I launch the site for <profile> and <environment> and <parallelEnvironment>
When I log in to the Normal account
Then I see that I am logged in
Examples:
| profile | environment | parallelEnvironment |
| parallel | Chrome75 | grid |
If profile is parallel and parallelEnvironment is grid, environment is ignored. The reason for parallelEnvironment is because we may still use Browserstack in the interim while setting up Selenium Grid.
These steps use relevant step files etc and page files (but not using Page Object Model as that's been deprecated).
The driver set-up is as follows:
namespace OurAutomation
{
[Binding]
public sealed class BrowserStack
{
private BrowserStackDriver bsDriver;
public static BrowserStackDriver bdriver;
[BeforeScenario]
public void BeforeScenario()
{
bsDriver = new BrowserStackDriver();
bdriver = bsDriver;
}
[AfterScenario]
public void AfterScenario()
{
bsDriver.Cleanup();
}
}
public class CustomRemoteWebDriver : RemoteWebDriver
{
public CustomRemoteWebDriver(Uri remoteAddress, ChromeOptions options) : base(remoteAddress, options)
{
}
public string getSessionID()
{
return base.SessionId.ToString();
}
}
public class BrowserStackDriver
{
private IWebDriver driver;
public static bool isBrowserStack = false;
public static string Platform;
public static string theEnvironment;
public static string sessionId;
public BrowserStackDriver()
{
}
public string GetString(string property)
{
if (TestContext.Parameters[property] == null)
{
throw new ArgumentException("Property does not exist, does not have a value, or a test setting is not selected. You may need to add the .runsettings file in Visual Studio (Test > Test Settings > Select Test Settings File).");
}
else
{
return TestContext.Parameters[property].ToString();
}
}
public IWebDriver Init(string profile, string environment, string parallelEnvironment)
{
String testString = GetString("BuildNumber");
theEnvironment = environment;
NameValueCollection caps = ConfigurationManager.GetSection("capabilities/" + profile) as NameValueCollection;
NameValueCollection settings = ConfigurationManager.GetSection("environments/" + environment) as NameValueCollection;
ChromeOptions chromeOptions = new ChromeOptions();
if (profile == "single")
{
// logic to invoke relevant browser locally based on Specflow parameter 'profile'
Thread.Sleep(3000);
}
else if (profile == "parallel")
{
if (parallelEnvironment == "browserstack")
{
foreach (string key in caps.AllKeys)
{
chromeOptions.AddAdditionalCapability(key, caps[key]);
}
foreach (string key in settings.AllKeys)
{
chromeOptions.AddAdditionalCapability(key, settings[key]);
}
string username = Environment.GetEnvironmentVariable("BROWSERSTACK_USERNAME");
if (username == null)
{
username = ConfigurationManager.AppSettings.Get("user");
}
string accesskey = Environment.GetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY");
if (accesskey == null)
{
accesskey = ConfigurationManager.AppSettings.Get("key");
}
chromeOptions.AddAdditionalCapability("browserstack.user", username);
chromeOptions.AddAdditionalCapability("browserstack.key", accesskey);
chromeOptions.AddAdditionalCapability("browserstack.local", "true");
chromeOptions.AddAdditionalCapability("build", GetString("BuildNumber"));
chromeOptions.AddAdditionalCapability("name", TestContext.CurrentContext.Test.MethodName);
chromeOptions.AddAdditionalCapability("project", GetString("Project"));
BrowserStackDriver.isBrowserStack = true;
driver = new CustomRemoteWebDriver(
new Uri("http://" + ConfigurationManager.AppSettings.Get("server") + "/wd/hub/"), chromeOptions);
CustomRemoteWebDriver browserRemoteDriver = driver as CustomRemoteWebDriver;
sessionId = browserRemoteDriver.getSessionID();
}
else if (parallelEnvironment == "grid")
{
driver = new RemoteWebDriver(new Uri("http://000.00.00.00:4444/wd/hub"), chromeOptions);
}
}
return driver;
}
public void Cleanup()
{
Thread.Sleep(2000);
if (isBrowserStack)
{
Log.Status status = (TestContext.CurrentContext.Result.Message == null) ? Log.Status.Passed : Log.Status.Failed;
string reason = (TestContext.CurrentContext.Result.Message == null) ? "Passed" : "Error see exception";
Log.UpdateTestStatus(status, reason, sessionId);
}
driver.Quit();
driver = null;
}
}
}
So in here...
else if (parallelEnvironment == "grid")
{
driver = new RemoteWebDriver(new Uri("http://000.00.00.00:4444/wd/hub"), chromeOptions);
}
...I enter the address of one of the nodes and the test gets conducted. However, I just want to send the test to the hub and for it to then execute that one test on all active nodes in their related browsers simultaneously. How do I achieve that? The guides and videos only seem to be taking me so far.
thank you
UPDATE:
So I'm inching further in the right direction I think. Had to roll this back to basics, so I can see how to implement this in my existing project. I've made this work in my grid: https://github.com/teixeira-fernando/Parallel-Execution-with-Selenium-Grid
However I note that I need to add attributes to the tests (to run one test on multiple browsers simultaneously)...
namespace Tutorial_parallel_execution
{
[TestFixture(BrowserType.Chrome)]
[TestFixture(BrowserType.Firefox)]
[TestFixture(BrowserType.Opera)]
[TestFixture(BrowserType.IE)]
[Parallelizable(ParallelScope.Fixtures)]
public class GoogleTesting : Hooks
{
public GoogleTesting(BrowserType browser) : base(browser)
{
}
[Test]
public void GoogleTest()
{
Driver.Navigate().GoToUrl("http://www.google.com");
Driver.FindElement(By.Name("q")).SendKeys("selenium");
Driver.FindElement(By.Name("btnK")).Click();
Assert.That(Driver.PageSource.Contains("Selenium"), Is.EqualTo(true),
"The text selenium doenst exist");
}
}
}
However, since my project started complaining similarly to this SpecFlow Visual Studio extension attempted to use SpecFlow code-behind generator 1.9, I started using SpecFlow.Tools.MsBuild.Generation and lost access to the tests (the code-behind files) in order to add the attributes. The only attribute I can add is [Parallelizable(ParallelScope.Fixtures)] but I have to put this in AssemblyInfo.cs - the other attributes can't be added there.
Do I need to be downgrading the versions of Specflow/Selenium etc in order to make this work??
I was able to strip out the code necessary to implement parallel execution using ThreadLocal from https://github.com/minhhoangvn/AutomationFramework
Add this to your AssemblyInfo.cs file:
[assembly: Parallelizable(ParallelScope.Fixtures)]
[assembly: LevelOfParallelism(4)]
Where you see 4 is the number of tests you want to run at the same time. So if you have 2 nodes, but you want to run 4 tests at the same time, then each node will get 2 chrome browsers.
When you use MsBuild.Generation the feature.cs files are still there, they just don't show up in visual studio.
You could try adding this to your Hooks.cs file when creating the driver:
ScenarioContext _scenarioContext;
IWebDriver _currentWebDriver;
_currentWebDriver = new RemoteWebDriver(new Uri(Utilities.SeleniumHub), options.ToCapabilities(), TimeSpan.FromMinutes(3));
_scenarioContext.ScenarioContainer.RegisterInstanceAs<IWebDriver>(_currentWebDriver);
And then this when you are done with the scenario:
[AfterScenario]
public void CloseBrowserAfterScenario()
{
string driver_process_name = null;
string browser_process_name = null;
switch (browser)
{
case "Chrome":
driver_process_name = "chromedriver.exe";
break;
case "IEX64":
case "IEX86":
driver_process_name = "IEDriverServer.exe";
break;
case "Edge":
driver_process_name = "MicrosoftWebDriver.exe";
browser_process_name = "MicrosoftEdge.exe";
break;
case "Firefox":
driver_process_name = "geckodriver.exe";
break;
default:
LogMessage(browser + "is not found or not supported... Please update the TestUI.dll.Config File");
break;
}
System.Diagnostics.Process[] process = System.Diagnostics.Process.GetProcessesByName(driver_process_name);
foreach (System.Diagnostics.Process app_process in process)
{
if (!string.IsNullOrEmpty(app_process.ProcessName))
{
try
{
app_process.Kill();
}
catch
{
FunctionalUtil.LogMessage("app_process.Kill(); failed in CloseBrowserAfterScenario");
}
}
}
Here's the situation.
I have an application which for all intents and purposes I have to treat like a black box.
I need to be able to open multiple instances of this application each with a set of files. The syntax for opening this is executable.exe file1.ext file2.ext.
If I run executable.exe x amount of times with no arguments, new instances open fine.
If I run executable.exe file1.ext followed by executable.exe file2.ext then the second call opens file 2 in the existing window rather than creating a new instance. This interferes with the rest of my solution and is the problem.
My solution wraps this application and performs various management operations on it, here's one of my wrapper classes:
public class myWrapper
{
public event EventHandler<IntPtr> SplashFinished;
public event EventHandler ProcessExited;
private const string aaTrendLocation = #"redacted";
//private const string aaTrendLocation = "notepad";
private readonly Process _process;
private readonly Logger _logger;
public myWrapper(string[] args, Logger logger =null)
{
_logger = logger;
_logger?.WriteLine("Intiialising new wrapper object...");
if (args == null || args.Length < 1) args = new[] {""};
ProcessStartInfo info = new ProcessStartInfo(aaTrendLocation,args.Aggregate((s,c)=>$"{s} {c}"));
_process = new Process{StartInfo = info};
}
public void Start()
{
_logger?.WriteLine("Starting process...");
_logger?.WriteLine($"Process: {_process.StartInfo.FileName} || Args: {_process.StartInfo.Arguments}");
_process.Start();
Task.Run(()=>MonitorSplash());
Task.Run(() => MonitorLifeTime());
}
private void MonitorLifeTime()
{
_logger?.WriteLine("Monitoring lifetime...");
while (!_process.HasExited)
{
_process.Refresh();
Thread.Sleep(50);
}
_logger?.WriteLine("Process exited!");
_logger?.WriteLine("Invoking!");
ProcessExited?.BeginInvoke(this, null, null, null);
}
private void MonitorSplash()
{
_logger?.WriteLine("Monitoring Splash...");
while (!_process.MainWindowTitle.Contains("Trend"))
{
_process.Refresh();
Thread.Sleep(500);
}
_logger?.WriteLine("Splash finished!");
_logger?.WriteLine("Invoking...");
SplashFinished?.BeginInvoke(this,_process.MainWindowHandle,null,null);
}
public void Stop()
{
_logger?.WriteLine("Killing trend...");
_process.Kill();
}
public IntPtr GetHandle()
{
_logger?.WriteLine("Fetching handle...");
_process.Refresh();
return _process.MainWindowHandle;
}
public string GetMainTitle()
{
_logger?.WriteLine("Fetching Title...");
_process.Refresh();
return _process.MainWindowTitle;
}
}
My wrapper class all works fine until I start providing file arguments and this unexpected instancing behaviour kicks in.
I can't modify the target application and nor do I have access to its source to determine whether this instancing is managed with Mutexes or through some other feature. Consequently, I need to determine if there is a way to prevent the new instance seeing the existing one. Would anyone have any suggestions?
TLDR: How do I prevent an application that is limited to a single instance determining that there is already an instance running
To clarify (following suspicious comments), my company's R&D team wrote executable.exe but I don't have time to wait for their help in this matter (I have days not months) and have permission to do whatever required to deliver the required functionality (there's a lot more to my solution than this question mentions) swiftly.
With some decompiling work I can see that the following is being used to find the existing instance.
Process[] processesByName = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
Is there any way to mess with this short of creating multiple copies of the application with different names? I looked into renaming the Process on the fly but apparently this isn't possible short of writing kernel exploits...
I have solved this problem in the past by creating copies of the source executable. In your case, you could:
Save the 'original.exe' in a specific location.
Each time you need to call it, create a copy of original.exe and name it 'instance_xxxx.exe', where xxxx is a unique number.
Execute your new instance exe as required, and when it completes you can delete it
You could possibly even re-use the instances by creating a pool of them
Building on Dave Lucre's answer I solved it by creating new instances of the executable bound to my wrapper class. Initially, I inherited IDisposable and removed the temporary files in the Disposer but for some reason that was causing issues where the cleanup would block the application, so now my main program performs cleanup at the end.
My constructor now looks like:
public AaTrend(string[] args, ILogger logger = null)
{
_logger = logger;
_logger?.WriteLine("Initialising new aaTrend object...");
if (args == null || args.Length < 1) args = new[] { "" };
_tempFilePath = GenerateTempFileName();
CreateTempCopy(); //Needed to bypass lazy single instance checks
HideTempFile(); //Stops users worrying
ProcessStartInfo info = new ProcessStartInfo(_tempFilePath, args.Aggregate((s, c) => $"{s} {c}"));
_process = new Process { StartInfo = info };
}
With the two new methods:
private void CreateTempCopy()
{
_logger?.WriteLine("Creating temporary file...");
_logger?.WriteLine(_tempFilePath);
File.Copy(AaTrendLocation, _tempFilePath);
}
private string GenerateTempFileName(int increment = 0)
{
string directory = Path.GetDirectoryName(AaTrendLocation); //Obtain pass components.
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(AaTrendLocation);
string extension = Path.GetExtension(AaTrendLocation);
string tempName = $"{directory}\\{fileNameWithoutExtension}-{increment}{extension}"; //Re-assemble path with increment inserted.
return File.Exists(tempName) ? GenerateTempFileName(++increment) : tempName; //If this name is already used, increment an recurse otherwise return new path.
}
Then in my main program:
private static void DeleteTempFiles()
{
string dir = Path.GetDirectoryName(AaTrend.AaTrendLocation);
foreach (string file in Directory.GetFiles(dir, "aaTrend-*.exe", SearchOption.TopDirectoryOnly))
{
File.Delete(file);
}
}
As a side-note, this approach will only work for applications with (lazy) methods of determining instancing that rely on Process.GetProcessByName(); it won't work if a Mutex is used or if the executable name is explicitly set in the manifests.
I'm using more NLog instances within single project (see my previous question Nlog config file priority). However, it doesn't work as expected.
If I call method that logs in second project, it's logged properly, but even after returning to previous project, items are being logged at wrong place.
So, for example Project1 has set to log in Project1.log, same way for second one. I can do method that simply calls:
Project1.Log.Write("1");
Project2.Log.Write("2");
Project1.Log.Write("3");
When I check logs, project1 contains "1", project 2 contains "2" and "3".
Exact (bit simplified) logger classes looks like:
public static class Log
{
private static readonly Lazy<Logger> Logger = new Lazy<Logger>(CreateLogger);
private static Logger CreateLogger()
{
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
LogManager.Configuration = new XmlLoggingConfiguration(assemblyFolder + "\\ProjectX.exe.nlog", true); //X means project id
return LogManager.GetCurrentClassLogger();
}
public static void Write(object log)
{
Logger.Value.Debug(log);
}
}
What am I doing wrong?
Your previous question talks about the application should have priority in loading a single configuration for the entire application:
Application-specific exe.nlog
Fallback to global nlog.config
Now you are talking about having multiple assemblies in the same application, that wants to load their individual NLog-configuration side-by-side.
When using the static LogManager.Configuration then you are modifying the global configuration for the entire application. If two project-assemblies are changing the global configuration, then it will of course have side-effects for others.
Maybe your CreateLogger could look like this:
private static Logger CreateLogger()
{
// Check for global NLog-configuration (Maybe your don't want this at all?)
var configuration = LogManager.Configuration;
if (configuration?.AllTarget.Count > 0)
return LogManager.GetCurrentClassLogger();
// Create assembly-specific NLog-configuration
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
LogFactory logFactory = new LogFactory();
logFactory.Configuration = new XmlLoggingConfiguration(assemblyFolder + "\\ProjectX.exe.nlog", true, logFactory); //X means project id
return logFactory.GetCurrentClassLogger();
}
See also https://github.com/NLog/NLog/wiki/Configure-component-logging
I want to build an automation testing, so I have to know the errors that appear in the console of chrome.
there is an option to get the error lines that appear in the console?
In order to see the console: right click somewhere in the page, click "inspect element" and then go to "console".
I don't know C# but here's Java code that does the job, I hope you can translate it to C#
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class ChromeConsoleLogging {
private WebDriver driver;
#BeforeMethod
public void setUp() {
System.setProperty("webdriver.chrome.driver", "c:\\path\\to\\chromedriver.exe");
DesiredCapabilities caps = DesiredCapabilities.chrome();
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
caps.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
driver = new ChromeDriver(caps);
}
#AfterMethod
public void tearDown() {
driver.quit();
}
public void analyzeLog() {
LogEntries logEntries = driver.manage().logs().get(LogType.BROWSER);
for (LogEntry entry : logEntries) {
System.out.println(new Date(entry.getTimestamp()) + " " + entry.getLevel() + " " + entry.getMessage());
//do something useful with the data
}
}
#Test
public void testMethod() {
driver.get("http://mypage.com");
//do something on page
analyzeLog();
}
}
Pay attention to setUp method in above code. We use LoggingPreferences object to enable logging. There are a few types of logs, but if you want to track console errors then LogType.BROWSER is the one that you should use. Then we pass that object to DesiredCapabilities and further to ChromeDriver constructor and voila - we have an instance of ChromeDriver with logging enabled.
After performing some actions on page we call analyzeLog() method. Here we simply extract the log and iterate through its entries. Here you can put assertions or do any other reporting you want.
My inspiration was this code by Michael Klepikov that explains how to extract performance logs from ChromeDriver.
You can get logs this way:
Driver().Manage().Logs.GetLog();
By specifying what log you are interested in you can get the browser log, that is:
Driver().Manage().Logs.GetLog(LogType.Browser);
Also remember to setup your driver accordingly:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
driver = new ChromeDriver("path to driver", options);
This is the c# code for logging the brower log from chrome.
private void CheckLogs()
{
List<LogEntry> logs = Driver.Manage().Logs.GetLog(LogType.Browser).ToList();
foreach (LogEntry log in logs)
{
Log(log.Message);
}
}
here is my code for the actual log:
public void Log(string value, params object[] values)
{
// allow indenting
if (!String.IsNullOrEmpty(value) && value.Length > 0 && value.Substring(0, 1) != "*")
{
value = " " + value;
}
// write the log
Console.WriteLine(String.Format(value, values));
}
As per issue 6832 logging is not implemented yet for C# bindings. So there might not be an easy way to get this working as of now.
Here is a solution to get Chrome logs using the C#, Specflow and Selenium 4.0.0-alpha05.
Pay attention that the same code doesn't work with Selenium 3.141.0.
[AfterScenario]
public void AfterScenario(ScenarioContext context)
{
if (context.TestError != null)
{
GetChromeLogs(context); //Chrome logs are taken only if test fails
}
Driver.Quit();
}
private void GetChromeLogs()
{
var chromeLogs = Driver.Manage().Logs.GetLog(LogType.Browser).ToList();
}
public void Test_DetectMissingFilesToLoadWebpage()
{
try
{
List<LogEntry> logs = driver.Manage().Logs.GetLog(LogType.Browser).ToList();
foreach (LogEntry log in logs)
{
while(logs.Count > 0)
{
String logInfo = log.ToString();
if (log.Message.Contains("Failed to load resource: the server responded with a status of 404 (Not Found)"))
{
Assert.Fail();
}
else
{
Assert.Pass();
}
}
}
}
catch (NoSuchElementException e)
{
test.Fail(e.StackTrace);
}
}
You could do something like this in C#. It is a complete test case. Then print the console output as String i.e logInfo in your report. For some reason, Log(log.Message) from the solution above this one gave me build errors.So, I replaced it.
C# bindings to the Chrome console logs are finally available in Selenium 4.0.0-alpha05. Selenium 3.141.0 and prior do not have support.
Before instantiating a new ChromeDriver object, set the logging preference in a ChromeOptions object and pass that into ChromeDriver:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
ChromeDriver driver = new ChromeDriver(options);
Then, to write the Chrome console logs to a flat file:
public void WriteConsoleErrors()
{
string strPath = "C:\\ConsoleErrors.txt";
if (!File.Exists(strPath))
{
File.Create(strPath).Dispose();
}
using (StreamWriter sw = File.AppendText(strPath))
{
var entries = driver.Manage().Logs.GetLog(LogType.Browser);
foreach (var entry in entries)
{
sw.WriteLine(entry.ToString());
}
}
}
driver.manage().logs().get("browser")
Gets all logs printed on the console. I was able to get all logs except Violations. Please have a look here Chrome Console logs not printing Violations