Playing with the new MongoDB driver (v2.0) has been quite challenging. Most of the examples you find on the web still refer to the legacy driver. The reference manual for v2.0 on the official Mongo site is "terse", to say the least.
I'm attempting to do a simple thing: detect when a collection has been changed in order to forward a C# event to my server application.
For doing so, I've found the following C# example (see below) that I'm trying to convert to the new API.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
namespace TestTailableCursor {
public static class Program {
public static void Main(string[] args) {
try {
var server = MongoServer.Create("mongodb://localhost/?safe=true");
var database = server["test"];
if (database.CollectionExists("capped")) {
database.DropCollection("capped");
}
var collectionOptions = CollectionOptions.SetCapped(true).SetMaxDocuments(5).SetMaxSize(10000);
var commandResult = database.CreateCollection("capped", collectionOptions);
var collection = database["capped"];
// to test the tailable cursor manually insert documents into the test.capped collection
// while this program is running and verify that they are echoed to the console window
// see: http://www.mongodb.org/display/DOCS/Tailable+Cursors for C++ version of this loop
BsonValue lastId = BsonMinKey.Value;
while (true) {
var query = Query.GT("_id", lastId);
var cursor = collection.Find(query)
.SetFlags(QueryFlags.TailableCursor | QueryFlags.AwaitData)
.SetSortOrder("$natural");
using (var enumerator = (MongoCursorEnumerator<BsonDocument>) cursor.GetEnumerator()) {
while (true) {
if (enumerator.MoveNext()) {
var document = enumerator.Current;
lastId = document["_id"];
ProcessDocument(document);
} else {
if (enumerator.IsDead) {
break;
}
if (!enumerator.IsServerAwaitCapable) {
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
}
}
}
}
} catch (Exception ex) {
Console.WriteLine("Unhandled exception:");
Console.WriteLine(ex);
}
Console.WriteLine("Press Enter to continue");
Console.ReadLine();
}
private static void ProcessDocument(BsonDocument document)
{
Console.WriteLine(document.ToJson());
}
}
}
A few (related) questions:
Is that the right approach with the new driver?
If so, how do I set collection options (like SetCap in the example above). The new API includes something called "CollectionSettings", which seems totally
unrelated.
Is my only option to rely on the legacy driver?
Thanks for your help.
Is my only option to rely on the legacy driver?
No.
[...] how do I set collection options (like SetCap in the example above). The new API includes something called "CollectionSettings", which seems totally unrelated.
There's CreateCollectionSettings now. CollectionSettings is a setting for the driver, i.e. a way to specify default behavior per-collection. CreateCollectionOptions can be used like this:
db.CreateCollectionAsync("capped", new CreateCollectionOptions
{ Capped = true, MaxDocuments = 5, MaxSize = 10000 }).Wait();
Is that the right approach with the new driver?
I think so, tailable cursors are a feature of the database, and avoiding polling always makes sense.
I converted the gist of the code and it appears to work on my machineā¢:
Be careful when using .Result and .Wait() in a web or UI application.
private static void ProcessDocument<T>(T document)where T : class
{
Console.WriteLine(document.ToJson());
}
static async Task Watch<T>(IMongoCollection<T> collection) where T: class
{
try {
BsonValue lastId = BsonMinKey.Value;
while (true) {
var query = Builders<T>.Filter.Gt("_id", lastId);
using (var cursor = await collection.FindAsync(query, new FindOptions<T> {
CursorType = CursorType.TailableAwait,
Sort = Builders<T>.Sort.Ascending("$natural") }))
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
foreach (var document in batch)
{
lastId = document.ToBsonDocument()["_id"];
ProcessDocument(document);
}
}
}
}
}
catch (Exception ex) {
Console.WriteLine("Unhandled exception:");
Console.WriteLine(ex);
}
}
Related
This seemingly simply example of paginated requests to an OData feed results in OutOfMemoryException, when calling Load(). The ODataService class is a generated code class, generated for an OData V3 feed using the Unchase OData Connected Service extension from Marketplace.
using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
namespace ODataToSqlMapper {
internal class ODataGetter {
public ODataService DataService { get; set; }
public ODataGetter() {
DataService = new ODataService(new Uri("my-url-here"));
DataService.Credentials = CredentialCache.DefaultCredentials;
DataService.Format.UseJson();
}
private void GetFiles(DataServiceQuery<File> query) {
DataServiceQueryContinuation<File> token = null;
Stopwatch timer = new Stopwatch();
int loadedCount = 0;
try {
timer.Start();
QueryOperationResponse<File> response = query.Execute() as QueryOperationResponse<File>;
long totalCount = response.TotalCount;
do {
Console.Write($"\rLoaded {loadedCount} / {totalCount} files ({(int)(100.0 * loadedCount / totalCount)} %) in {timer.ElapsedMilliseconds} ms. ");
if (token != null)
response = DataService.Execute<File>(token) as QueryOperationResponse<File>;
loadedCount += response.Count();
}
while ((token = response.GetContinuation()) != null);
}
catch (DataServiceQueryException ex) {
throw new ApplicationException("An error occurred during query execution.", ex);
}
}
internal void Load() {
DataServiceQuery<File> query = DataService.Files.IncludeTotalCount() as DataServiceQuery<File>;
GetFiles(query);
}
}
}
When taking a memory snapshot (see figure), I see that a lot of Uri objects (12.7 million of them) are taking up 575 MB of memory. I've tried splitting up my requests by modifying the query in Load() such that GetFiles() is called several times, however this doesn't seem to make any difference. Any ideas on how I can get around this?
I found this issue for a related library, which describes a similar problem. This was fixed in that particular library, but I can't use that, since it supports only OData V4 (and my feed is V3).
I am writing using C#, selenium chromeWebDriver. When I try to read the browser console log file with selenium I get:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
private void button1_Click(object sender, EventArgs e)
{
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.Warning);
IWebDriver driver = new ChromeDriver(options);
driver.Url = "https://www.google.com/";
var entries = driver.Manage().Logs.GetLog(LogType.Browser); // System.NullReferenceException
foreach (var entry in entries)
{
Console.WriteLine(entry.ToString());
}
}
This is my solution until Selenium 4 is out (will work also with Selenium 4).
It is quick and dirty and was design to demonstrate how it can be done. Feel free to alter and improve.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
using System.Text;
namespace GetChromeConsoleLog
{
internal static class Program
{
private static void Main()
{
// setup options
var options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
// do whatever actions
var driver = new ChromeDriver(options)
{
Url = "https://www.yahoo.com/"
};
var logs = driver.GetBrowserLogs();
// extract logs (using the extension method GetBrowserLogs)
foreach (var log in driver.GetBrowserLogs())
{
Console.WriteLine($"{log["timestamp"]}: {log["message"]}");
}
// cleanup
driver.Dispose();
// hold console
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
public static class WebDriverExtensions
{
public static IEnumerable<IDictionary<string, object>> GetBrowserLogs(this IWebDriver driver)
{
// not a chrome driver
if(driver.GetType() != typeof(ChromeDriver))
{
return Array.Empty<IDictionary<string, object>>();
}
// setup
var endpoint = GetEndpoint(driver);
var session = GetSession(driver);
var resource = $"{endpoint}session/{session}/se/log";
const string jsonBody = #"{""type"":""browser""}";
// execute
using (var httpClient = new HttpClient())
{
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = httpClient.PostAsync(resource, content).GetAwaiter().GetResult();
var responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return AsLogEntries(responseBody);
}
}
private static string GetEndpoint(IWebDriver driver)
{
// setup
const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
// get RemoteWebDriver type
var remoteWebDriver = GetRemoteWebDriver(driver.GetType());
// get this instance executor > get this instance internalExecutor
var executor = remoteWebDriver.GetField("executor", Flags).GetValue(driver) as ICommandExecutor;
// get URL
var uri = executor.GetType().GetField("remoteServerUri", Flags).GetValue(executor) as Uri;
// result
return uri.AbsoluteUri;
}
private static Type GetRemoteWebDriver(Type type)
{
if (!typeof(RemoteWebDriver).IsAssignableFrom(type))
{
return type;
}
while (type != typeof(RemoteWebDriver))
{
type = type.BaseType;
}
return type;
}
private static SessionId GetSession(IWebDriver driver)
{
if (driver is IHasSessionId id)
{
return id.SessionId;
}
return new SessionId($"gravity-{Guid.NewGuid()}");
}
private static IEnumerable<IDictionary<string, object>> AsLogEntries(string responseBody)
{
// setup
var value = $"{JToken.Parse(responseBody)["value"]}";
return JsonConvert.DeserializeObject<IEnumerable<Dictionary<string, object>>>(value);
}
}
}
get logs
I have previously used the property above but I cannot get it to work currently with the ChromeDriver 75+. I found issues related to it reported here.
This issue was supposedly fixed as it was reported in GitHub issue #7323 here, but I have actually attempted to test this fix in ChromeDriver Nuget version 77.0.3865.4000 and it still proves to be an issue.
Further experimentation with newer Chromedriver version 78.0.3904.7000 (currently Latest Stable version at the time of writing) shows that the issue still exists.
I have also experimented with using workarounds provided in other Selenium issue #7335 here back in September, and while this workaround does allow the driver to be instantiated, the logs are still inaccessible (and null).
Workaround when creating chromedriver instance: typeof(CapabilityType).GetField(nameof(CapabilityType.LoggingPreferences), BindingFlags.Static | BindingFlags.Public).SetValue(null, "goog:loggingPrefs");
Based on what MatthewSteeples said in that issue (see quote below), the fix is in place just not yet fully released to Nuget. Hopefully it will come in with the next release.
"The issue has been resolved but the fix is not (yet) available on NuGet so you'll need to roll your own if you need it before the next release is out" - MatthewSteeples September 25'th 2019
Edit: It may be worth mentioning the reason for using an older ChromeDriver Nuget is so that running automated tests locally and in the Hosted Azure Devops release pipeline is possible without manually modifying the nuget version locally.
iam quite desperate here. I couldn't find any example code for this in C#.
I want to rename BrowserSubProcess.exe and i want it to embed my main exe, if possible.
I am aware of this solution;
https://github.com/cefsharp/CefSharp/issues/1149#issuecomment-225547869
Rename CefSharp.BrowserSubprocess winforms
but i couldn't implemented it. I need sample program or code to understand. I hope #amaitland will see this and helps me.
I embed the BrowserSubProcess Program.cs to my Program.cs so it is embedded now.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Count() < 5)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new LoginForm());
}
else
{
MyBrowserSubProcess(args);
}
}
static int MyBrowserSubProcess(string[] args)
{
Debug.WriteLine("BrowserSubprocess starting up with command line: " + String.Join("\n", args));
SubProcess.EnableHighDPISupport();
int result;
var type = args.GetArgumentValue(CefSharpArguments.SubProcessTypeArgument);
var parentProcessId = -1;
// The Crashpad Handler doesn't have any HostProcessIdArgument, so we must not try to
// parse it lest we want an ArgumentNullException.
if (type != "crashpad-handler")
{
parentProcessId = int.Parse(args.GetArgumentValue(CefSharpArguments.HostProcessIdArgument));
if (args.HasArgument(CefSharpArguments.ExitIfParentProcessClosed))
{
Task.Factory.StartNew(() => AwaitParentProcessExit(parentProcessId), TaskCreationOptions.LongRunning);
}
}
// Use our custom subProcess provides features like EvaluateJavascript
if (type == "renderer")
{
var wcfEnabled = args.HasArgument(CefSharpArguments.WcfEnabledArgument);
var subProcess = wcfEnabled ? new WcfEnabledSubProcess(parentProcessId, args) : new SubProcess(args);
using (subProcess)
{
result = subProcess.Run();
}
}
else
{
result = SubProcess.ExecuteProcess();
}
Debug.WriteLine("BrowserSubprocess shutting down.");
return result;
}
private static async void AwaitParentProcessExit(int parentProcessId)
{
try
{
var parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.WaitForExit();
}
catch (Exception e)
{
//main process probably died already
Debug.WriteLine(e);
}
await Task.Delay(1000); //wait a bit before exiting
Debug.WriteLine("BrowserSubprocess shutting down forcibly.");
Environment.Exit(0);
}
}
And my BrowserSubprocessPath is my main exe.
settings.BrowserSubprocessPath = System.AppDomain.CurrentDomain.FriendlyName;
I finally managed to rename this sub process! Haven't found any solution how to do it through the CefSharp API, but found my own worked solution.
So, In your code that uses CefSharp add one setting to the Cef Settings, before Cef.Initialize()
using CefSharp;
using CefSharp.Wpf;
using System.IO;
using System.Reflection;
using System.Windows;
public App()
{
var settings = new CefSettings
{
BrowserSubprocessPath = Path.Combine(GetAppPath(), $#"runtimes\win-x64\native{ GetAppName() }.exe")
};
Cef.InitializeAsync(settings);
}
private static string GetAppPath()
{
return new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName;
}
private static string GetAppName()
{
return Assembly.GetExecutingAssembly().GetName().Name;
}
After this go to the bin\Debug\net6.0-windows\runtimes\win-x64\native\ and rename CefSharp.BrowserSubprocess.exe to Name you want to use.
Done. Now it will use this file with custom name you need.
P.S. For the auto name set you can always use Post-Build event with command to rename the file after project built and set the name same as your assembly name. I use this approach for my needs.
I am using Wait.Until method to check if my page is already loaded or if it still loading .
This is how it looks like :
protected IWebElement FindElement(By by, int timeoutInSeconds)
{
StackTrace stackTrace = new StackTrace();
string callingMethod = stackTrace.GetFrame(1).GetMethod().Name;
string message = "Error finding element in method: " + callingMethod;
if (timeoutInSeconds > 0)
{
try
{
WebDriverWait wait = new WebDriverWait(chromeDriver, TimeSpan.FromSeconds(timeoutInSeconds));
wait.Until(ExpectedConditions.ElementIsVisible(by));
Thread.Sleep(800);
}
catch (Exception)
{
Assert(false, message);
throw new Exception(message);
}
}
return chromeDriver.FindElement(by);
}
But now we want to change our automation pages and start using FindBy foe every element , like this :
[FindsBy(How = How.Id, Using = "username")]
public IWebElement _logInUserName;
but wait.until needs "by" element .
I saw the abstract solution for this problem , but it is no good for my case .
can anyone know another solution that i can use ?
There is a ByFactory class in Selenium .NET solution. I took this implementation to achieve what you want:
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace SeleniumPlayground
{
public static class SeleniumHelper
{
public static FindsByAttribute GetFindsByAttributeFromField(Type pageObject, string iwebElementFieldName)
{
FieldInfo fi = pageObject.GetField(iwebElementFieldName);
FindsByAttribute attr = (FindsByAttribute)fi.GetCustomAttributes(typeof(FindsByAttribute), false).FirstOrDefault();
return attr;
}
public static By GeyByFromFindsBy(FindsByAttribute attribute)
{
var how = attribute.How;
var usingValue = attribute.Using;
switch (how)
{
case How.Id:
return By.Id(usingValue);
case How.Name:
return By.Name(usingValue);
case How.TagName:
return By.TagName(usingValue);
case How.ClassName:
return By.ClassName(usingValue);
case How.CssSelector:
return By.CssSelector(usingValue);
case How.LinkText:
return By.LinkText(usingValue);
case How.PartialLinkText:
return By.PartialLinkText(usingValue);
case How.XPath:
return By.XPath(usingValue);
case How.Custom:
if (attribute.CustomFinderType == null)
{
throw new ArgumentException("Cannot use How.Custom without supplying a custom finder type");
}
if (!attribute.CustomFinderType.IsSubclassOf(typeof(By)))
{
throw new ArgumentException("Custom finder type must be a descendent of the By class");
}
ConstructorInfo ctor = attribute.CustomFinderType.GetConstructor(new Type[] { typeof(string) });
if (ctor == null)
{
throw new ArgumentException("Custom finder type must expose a public constructor with a string argument");
}
By finder = ctor.Invoke(new object[] { usingValue }) as By;
return finder;
}
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Did not know how to construct How from how {0}, using {1}", how, usingValue));
}
}
And here's an example usage:
public class Page
{
private IWebDriver driver;
[FindsBy(How = How.Id, Using = "content")]
public IWebElement ele;
public Page(IWebDriver _driver)
{
this.driver = _driver;
}
}
Use as follows:
Page page = PageFactory.InitElements<Page>(driver);
FindsByAttribute findsBy = SeleniumHelper.GetFindsByAttributeFromField(typeof(Page), "ele");
By by = SeleniumHelper.GeyByFromFindsBy(findsBy);
I found a way to o this :)
public static IWebElement FindElement( IWebElement element, int timeoutInSeconds)
{
if (timeoutInSeconds > 0)
{
var wait = new WebDriverWait(chromeDriver, TimeSpan.FromSeconds(timeoutInSeconds));
return wait.Until(drv => element);
}
return element;
}
We faced the same issue when using selenium for testing. So we created a mini framework on top of selenium which keeps trying to do (whatever you are trying to do with selenium). Or otherwise you can provide a custom pre or post condition.
https://github.com/LiquidThinking/Xenon
It is very simple to setup and all information is available on github, plus it comes with Screen objects which can help to reuse your code.
For example
new XenonTest(new SeleniumXenonBrowser())
.GoToUrl("http://www.google.co.uk", a => a.PageContains("google") );
So in this example, we added a pre wait condition which says "before going to google.co.uk make sure that the current page contains "google" in page source. Which is obviously incorrect way to do it but it explains how to use pre or post wait conditions.
If you do not specify any wait condition then for some actions, there is a default wait action. for e.g. https://github.com/LiquidThinking/Xenon/blob/master/Xenon/BaseXenonTest.cs#L72
See how we check if a customPreWait is available for "Click" and if not then we added a custom pre-wait to check if that css selectors exists on the page before performing the "actual click action".
Hope it will help you, it is on nuget or otherwise just use the code which you want. it is under MIT license.
This question already has answers here:
Calling a function from a string in C#
(5 answers)
Closed 9 years ago.
I'm running Selenium tests in C# and I'm looking for a way to externalize the list of tests to a text file so they can be easily edited without need to touch the code.
The problem is how to then call each line of the text file as a method? Actions seem to be the best solution but I'm having trouble converting my text string to an Action.
I'm open to all suggestions on how best to do this.
Thanks,
J.
Note: Even though using reflection and invoke was the solutions, it doesn't work when I have methods with different number of parameters.
using McAfeeQA.Core.Log;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Interactions;
using System;
namespace SeleniumProject
{
class Program
{
static void Main(string[] args)
{
try
{
// Read through all tests and run one after another
// Global.Tests is a list of strings that contains all the tests
foreach (string testcase in Global.tests)
{
// How to run each of the below commented methods in a foreach loop???
}
//Tests.CreateNewAdminUser("admin123", "password", "admin");
//Navigation.Login(Global.language, Global.username, Global.password);
//Tests.testPermissionSets();
//Navigation.Logoff();
}
catch (Exception ex)
{
Console.WriteLine("Program exception: " + ex.ToString());
}
}
}
}
Imperfect But simplifies:
private void StoreMetod(string FileName, Type classType)
{
using (var fileSt = new System.IO.StreamWriter(FileName))
{
foreach (var Method in classType.GetType().GetMethods())
{
fileSt.Write(Method.Name);
fileSt.Write("\t");
fileSt.Write(Method.ReturnType == null ? "" : Method.ReturnType.FullName );
fileSt.Write("\t");
foreach (var prm in Method.GetParameters())
{
//ect...
}
}
}
}
private void LoadMethod(string FileName, Object instant)
{
using (var fileSt = new System.IO.StreamReader (FileName))
{
while (!fileSt.EndOfStream)
{
var lineMethodArg = fileSt.ReadLine().Split('\t');
var methodName = lineMethodArg[0];
var typeReturnName = lineMethodArg[1];
//set parameters, Return type Ect...
var objectReturn = instant.GetType().GetMethod(methodName).Invoke(instant, {prms} );
}
}
}