Highlight text using Selenium - c#

I have a context sensitive menu that needs text to be hightlighted in order for it to work. However, I'm having problems with selecting the text using Selenium. I start by finding the WebElement I'm looking for, before trying to interact with it using the different mouse events available.
When I'm trying to select parts of the text, it doesn't appear to do anything other than placing the marker at the end of the string.
This is what my textbox looks like:
This is what I need it to look like, or in other words, what I need Selenium to select (Just did it manually for the purpose of illustration:
This is along the lines of what I'm trying to do in code:
public static async Task HighlightElementByCssSelector(this RemoteWebDriver #this, string cssSelector, TimeSpan? timeout = null, TimeSpan? interval = null)
{
var element = await #this.FindElementByCssSelectorAsync(".testmarker-registryentryedit .testmarker-title-field");
Actions action = new Actions(#this).MoveToElement(element).ClickAndHold(element).MoveByOffset(10,0).Release();
action.Build().Perform();
}
#this represents the Driver in this case, and the FindElementByCssSelectorAsync being part of a 'wrapper-framework'.
I've tried different combinations of MoveToElement, DragAndDrop, ClickAndHold etc, but I just can't get it to work. Any pointers as to what might be wrong here?

According to what I understood about your problem I tried to solve it on my local machine (first day of vacation, lol). Sorry, I don't have VS on that machine so I wrote it in Java. The code should be self-explanatory:
#org.junit.Test
public void doTest(){
String query = "This is a test";
WebDriver driver = new FirefoxDriver();
driver.get("https://www.google.com");
WebElement searchBox = new WebDriverWait(driver, 10).until(ExpectedConditions.visibilityOfElementLocated(By.id("lst-ib")));
searchBox.sendKeys(query);
// make sure it has focus
searchBox.click();
Actions actions = new Actions(driver);
// go to the beginning of input
actions.sendKeys(Keys.HOME).build().perform();
int length = query.substring(0, query.indexOf("a")).length();
actions.keyDown(Keys.LEFT_SHIFT);
for (int i = 0; i < length; i++){
actions.sendKeys(Keys.ARROW_RIGHT);
}
actions.keyUp(Keys.LEFT_SHIFT);
actions.build().perform();
}
Results in:
Is this what you wanted?

Related

Can the PageSource property in Selenium be updated as JavaScript loads data?

I'm trying to determine if there's specific text on the page. I'm doing this:
public static void WaitForPageToLoad(this IWebDriver driver, string textOnPage)
{
var pageSource = driver.PageSource.ToLower();
var timeOut = 0;
while (timeOut < 60)
{
Thread.Sleep(1000);
if (pageSource.Contains(textOnPage.ToLower()))
{
timeOut = 60;
}
}
}
The problem is that the web driver's PageSource property isn't updated after the initial load. The page I'm navigating to loads a bunch of data via JS after the page has already loaded. I don't control the site, so I'm trying to figure out a method to get the updated HTML.
You are trying to solve the wrong problem. You need to wait for the text to appear using an XPath locator:
var wait = new WebDriverWait(driver);
var xpath = $"//*[contains(., '{textOnPage}')]";
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath(xpath));
Do you really need to search entire page?
I'll reference you to here: https://stackoverflow.com/a/41223770/1387701
with this code:
String Verifytext= driver.findElement(By.tagName("body")).getText().trim();
You can then check to see if the Verifytext contains the string you're checking for.
This works MUCH better if you can narrow the location of the text down to a particular webElement other than the body.

Selenium can't handle multiple ChromiumWebBrowser instances in C#

I have two instances of the ChromiumWebBrowser in my WinForms project (Visual Studio 2012). My goal is to have the second browser instance "copy" the behavior of the user input in the first browser instance. I can successfully retrieve the input from the first browser, and I managed to hook up Selenium in the project as well.
However, I'm having one issue. Whenever Selenium sends its commands, the first browser is the one that responds to them. For the life of me, I can't seem to figure out how to make the second browser respond. Whenever I completely remove the first browser, the second one starts responding correctly, but adding the first browser again will make only have the first browser use the Selenium commands. I even tried to switch out the moments the browsers are added to the form, but to no avail: whenever there are two available, the wrong one is responsive.
Relevant code:
public BrowserManager(Controller controller, string startingUrl)
{
_controller = controller;
var settings = new CefSettings { RemoteDebuggingPort = 9515 };
Cef.Initialize(settings);
// Input browser
inputBrowser = new ChromiumWebBrowser(startingUrl);
var obj = new XPathHelper(this);
inputBrowser.RegisterJsObject("bound", obj); //Standard object registration
inputBrowser.FrameLoadEnd += obj.OnFrameLoadEnd;
// Output browser
var browserSettings = new BrowserSettings();
var requestContextSettings = new RequestContextSettings { CachePath = "" };
var requestContext = new RequestContext(requestContextSettings);
outputBrowser = new ChromiumWebBrowser(startingUrl);
outputBrowser.RequestContext = requestContext;
outputBrowser.AddressChanged += InitializeOutputBrowser;
outputBrowser.Enabled = false;
outputBrowser.Name = "outputBrowser";
}
The selenium part:
public class SeleniumHelper
{
public SeleniumHelper()
{
DoWorkAsync();
}
private Task DoWorkAsync()
{
Task.Run(() =>
{
string chromeDriverDir = #"ActionRecorder\bin\x64\Debug\Drivers";
var chromeDriverService = ChromeDriverService.CreateDefaultService(chromeDriverDir);
chromeDriverService.HideCommandPromptWindow = true;
ChromeOptions options = new ChromeOptions();
options.BinaryLocation = #"ActionRecorder\bin\x64\Debug\ActionRecorder.exe";
options.DebuggerAddress = "127.0.0.1:9515";
options.AddArguments("--enable-logging");
using (IWebDriver driver = new OpenQA.Selenium.Chrome.ChromeDriver(chromeDriverService, options))
{
driver.Navigate().GoToUrl("http://www.google.com");
var query = driver.FindElement(By.Name("q"));
query.SendKeys("A google search test");
query.Submit();
}
});
return null;
}
}
And finally, a screenshot for some visualization:
Some help with the issue would be very much appreciated. If i missed some crucial info, feel free to ask for it. Thanks in advance!
Greetz,
Tybs
The behavior is correct. You have one debug address and you can only have one debug address for CEF. Which means when you use Selenium it is only seeing one browser.
By default Selenium will send an command to current active Tab or Window. Now in your case you have multiple Chrome view embedded, but they are technically Chrome Tab/Windows which you have placed on the same form.
So if you are in luck below code in should be able to move you to the Window you are interested in
driver.SwitchTo().Window(driver.WindowHandles.Last());
See if it works. If it doesn't then your only other workaround would be to change the order of Adding ChromiumWebBrowser and that should reverse the window it works on.
Below are some important threads that you should read from top to bottom. Very relevant to your issue/request
https://code.google.com/archive/p/chromiumembedded/issues/421
https://github.com/cefsharp/CefSharp/issues/1076

NoSuchElementException in c# for google search box using selenium webdriver

I'm trying to automate a simple search using selenium webdriver. My code is to launch the google.co.uk page in IE, wait until the search box has appeared, locate the search box and input "compare the market". However I keep getting a NoSuchElementException from the application, even though I can see it on the page.
My code:
class Program
{
static void Main(string[] args)
{
string searchEngine = "www.google.co.uk";
IWebDriver IEbrowser = new InternetExplorerDriver(#"C:\Drivers");
IEbrowser.Url = searchEngine;
WebDriverWait wait = new WebDriverWait(IEbrowser, TimeSpan.FromSeconds(25));
IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
{
return d.FindElement(By.Name("q"));
});
var searchBox = IEbrowser.FindElement(By.Id("lst-ib"));
searchBox.SendKeys("compare the market");
}
}
Error Message:
An exception of type 'OpenQA.Selenium.NoSuchElementException' occurred in WebDriver.dll but was not handled in user code
Additional information: Unable to find element with name == q
HTML info of the search box:
I get the same error message if I try to use any of the other page element locators like Id. But this only appears to error out using the google search page.
You've kinda overengineered this task. :) The code below works fine.
string searchEngine = "www.google.co.uk";
IWebDriver IEbrowser = InternetExplorerDriver(#"C:\Drivers");
IEbrowser.Navigate().GoToUrl(searchEngine);
IEbrowser.FindElement(By.Id("lst-ib")).SendKeys("compare the market");
If you want to wait for an element, there is a simpler way to do this using ExpectedConditions. See an example below. Read the linked docs to see the other available conditions that can be used.
WebDriverWait wait = new WebDriverWait(IEbrowser, TimeSpan.FromSeconds(25));
wait.Until(ExpectedConditions.ElementExists(By.Name("q")));

Selenium Drag and drop not working in Chrome or Firefox

I use the Drag and drop code below to drag a picture in to a photofield. This works fine in InternetExplorer, but doesn't work in Firefox or Chrome.
I don't understand why not.
As you can see in the code below I've tried a lot of different ways to do the drag and drop, but none of them works.
The main problem is that the target is not updated after releasing the image.
I see the drop happen but no update.
Does anyone have any idea why this is? I'm using C# and the latest Selenium driver 2.39, chrome driver 2.8.
public static void DoDragAndDrop(IWebDriver driver, string dragImageId, string dropFieldId)
{
Console.WriteLine("Drag and drop image '{0}' to the editor {1}..", dragImageId, dropFieldId);
IWebElement dragElement = WebDriverExtensions.TryFindElement(By.Id(dragImageId));
IWebElement dropElement = WebDriverExtensions.TryFindElement(By.Id(dropFieldId));
if(dragElement == null)
Console.WriteLine("dragElement is null");
if(dropElement == null)
Console.WriteLine("dropElement is null");
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView();", dragElement);
Thread.Sleep(500);
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView();", dropElement);
Thread.Sleep(200);
Console.WriteLine("Drag and drop 1");
var builder1 = new Actions(driver);
builder1.MoveToElement(dragElement).ClickAndHold();
builder1.MoveToElement(dropElement).Build().Perform();
Thread.Sleep(2000);
Console.WriteLine("Drag and drop 2");
var builder2 = new Actions(driver);
builder2.DragAndDrop(dragElement, dropElement);
Thread.Sleep(2000);
Console.WriteLine("Drag and drop 3");
var builder3 = new Actions(driver);
builder3.DragAndDrop(dragElement, dropElement).Build().Perform();
IAction dragAndDrop = builder3.ClickAndHold(dragElement)
.MoveToElement(dropElement)
.Release(dropElement)
.Build();
dragAndDrop.Perform();
Thread.Sleep(2000);
Thread.Sleep(1000);
Console.WriteLine("Drag and drop succeeded..");
}
This is how I've got it working in FireFox now. Chrome still fails.
The only difference is that I've added offsets in the MoveToElement method, as seen in The Rookies comment.
var builder = new Actions(driver);
builder.ClickAndHold(dragElement);
builder.MoveToElement(dropElement, 5, 5);
builder.Perform();
Thread.Sleep(250);
builder.Release(dropElement);
builder.Perform();
Tried below sample code with chromedriver:2.15, chrome:v43 and is working fine as expected.
Sample Code:
System.setProperty("webdriver.chrome.driver","drivers/chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(1,TimeUnit.MINUTES);
driver.get("http://jqueryui.com/droppable");
driver.switchTo().frame(0);
WebElement dragElement = driver.findElement(By.id("draggable"));
WebElement dropElement = driver.findElement(By.id("droppable"));
Actions builder = new Actions(driver);
builder.clickAndHold(dragElement).moveToElement(dropElement).release().build().perform();
For firefox you can use following but it is in ruby
panel = driver.find_element(:id, ' (panel around the picture)')
target = panel.find_element(:xpath, ' ')
source = panel.find_element(:xpath, ' ')
driver.action.click_and_hold(source).move_to(target, 400, 150).release(target).perform
Hope it helps
ChromeDriver doesn't yet support the Actions commands. The Java
language binding translates the Actions requests into corresponding
mouse events before sending them to ChromeDriver, however there is no
guarantee the translated mouse events are completely equivalent to the
original Actions request.
Source: Chromium bugtacker

using C# and UI Automation to grab contents of unknown control-type

In the image below there is an area, which has an unknown (custom) class. That's not a Grid or a Table.
I need to be able:
to select Rows in this area
to grab a Value from each cell
The problem is since that's not a common type element - I have no idea how to google this problem or solve it myself. So far the code is following:
Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);
I've already tried to threat this Area as a textbox, as a grid, as a combobox, but nothing solved my problem so far. Does anybody have any advice how to grab data from this area and iterate through rows?
EDIT: sorry I've made a wrong assumption. Actually, the header(column 1, column 2, column 3) and the "lower half" of this area are different control-types!!
Thanks to Wininspector I was able to dig more information regarding these control types:
The header has following properties: HeaderControl 0x056407DC (90441692) Atom: #43288 0xFFFFFFFF (-1)
and the lower half has these: ListControl 0x056408A4 (90441892) Atom: #43288 0x02A6FDA0 (44498336)
The code that I've showed earlier - retrieved the "List" element only, so here is the update:
Process[] proc = Process.GetProcessesByName("programname");
AutomationElement window = AutomationElement.FromHandle(proc [0].MainWindowHandle);
//getting the header
PropertyCondition xEllist3 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomHeaderClass", PropertyConditionFlags.IgnoreCase);
AutomationElement headerEl = XElAE.FindFirst(TreeScope.Children, xEllist3);
//getting the list
PropertyCondition xEllist2 = new PropertyCondition(AutomationElement.ClassNameProperty, "CustomListClass", PropertyConditionFlags.IgnoreCase);
AutomationElement targetElement = window.FindFirst(TreeScope.Children, xEllist2);
After giving it a further thought I've tried to get all column names:
AutomationElementCollection headerLines = headerEl.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.HeaderItem));
string headertest = headerLines[0].GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
textBox2.AppendText("Header 1: " + headertest + Environment.NewLine);
Unfortunately in debug mode element count in "headerLines" is 0 so the program throws an error.
Edit 2: Thanks to the answer below - I've installed Unmanaged UI Automation, which holds better possibilities than the default UIA. http://uiacomwrapper.codeplex.com/
How do you use the legacy pattern to grab data from unknown control-type?
if((bool)datagrid.GetCurrentPropertyValue(AutomationElementIdentifiers.IsLegacyIAccessiblePatternAvailableProperty))
{
var pattern = ((LegacyIAccessiblePattern)datagrid.GetCurrentPattern(LegacyIAccessiblePattern.Pattern));
var state = pattern.Current.State;
}
Edit 3. IUIAutoamtion approach (non-working as of now)
_automation = new CUIAutomation();
cacheRequest = _automation.CreateCacheRequest();
cacheRequest.AddPattern(UiaConstants.UIA_LegacyIAccessiblePatternId);
cacheRequest.AddProperty(UiaConstants.UIA_LegacyIAccessibleNamePropertyId);
cacheRequest.TreeFilter = _automation.ContentViewCondition;
trueCondition = _automation.CreateTrueCondition();
Process[] ps = Process.GetProcessesByName("program");
IntPtr hwnd = ps[0].MainWindowHandle;
IUIAutomationElement elementMailAppWindow = _automation.ElementFromHandle(hwnd);
List<IntPtr> ls = new List<IntPtr>();
ls = GetChildWindows(hwnd);
foreach (var child in ls)
{
IUIAutomationElement iuiae = _automation.ElementFromHandle(child);
if (iuiae.CurrentClassName == "CustomListClass")
{
var outerArayOfStuff = iuiae.FindAllBuildCache(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition, cacheRequest.Clone());
var outerArayOfStuff2 = iuiae.FindAll(interop.UIAutomationCore.TreeScope.TreeScope_Children, trueCondition);
var countOuter = outerArayOfStuff.Length;
var countOuter2 = outerArayOfStuff2.Length;
var uiAutomationElement = outerArayOfStuff.GetElement(0); // error
var uiAutomationElement2 = outerArayOfStuff2.GetElement(0); // error
//...
//I've erased what's followed next because the code isn't working even now..
}
}
The code was implemented thanks to this issue:
Read cell Items from data grid in SysListView32 of another application using C#
As the result:
countOuter and countOuter2 lengths = 0
impossible to select elements (rows from list)
impossible to get ANY value
nothing is working
You might want to try using the core UI automation classes. It requires that you import the dll to use it in C#. Add this to your pre-build event (or do it just once, etc):
"%PROGRAMFILES%\Microsoft SDKs\Windows\v7.0A\bin\tlbimp.exe" %windir%\system32\UIAutomationCore.dll /out:..\interop.UIAutomationCore.dll"
You can then use the IUIAutomationLegacyIAccessiblePattern.
Get the constants that you need for the calls from:
C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\UIAutomationClient.h
I am able to read Infragistics Ultragrids this way.
If that is too painful, try using MSAA. I used this project as a starting point with MSAA before converting to all UIA Core: MSSA Sample Code
----- Edited on 6/25/12 ------
I would definitely say that finding the proper 'identifiers' is the most painful part of using the MS UIAutomation stuff. What has helped me very much is to create a simple form application that I can use as 'location recorder'. Essentially, all you need are two things:
a way to hold focus even when you are off of your form's window Holding focus
a call to ElementFromPoint() using the x,y coordinates of where the mouse is. There is an implementation of this in the CUIAutomation class.
I use the CTRL button to tell my app to grab the mouse coordinates (System.Windows.Forms.Cursor.Position). I then get the element from the point and recursively get the element's parent until I reach the the desktop.
var desktop = auto.GetRootElement();
var walker = GetRawTreeWalker();
while (true)
{
element = walker.GetParentElement(element);
if (auto.CompareElements(desktop, element) == 1){ break;}
}
----- edit on 6/26/12 -----
Once you can recursively find automation identifiers and/or names, you can rather easily modify the code here: http://blog.functionalfun.net/2009/06/introduction-to-ui-automation-with.html to be used with the Core UI Automation classes. This will allow you to build up a string as you recurse which can be used to identify a control nested in an application with an XPath style syntax.

Categories