Trying to automate IIS (INetMgr) trying to use UIAutomation, i'm fixing mixed results. I'm able to get some if the screen elements good fine, others, even immediate children nodes, can't get either with a Find[First|All] or try in a treewalker (content|control|raw), just can't get the node(s) needed. Any suggestion what to use for driving the UI to automate it?
Window 10/11 desktop environment.
Here is a C# Console app that dumps (max 2 levels) of InetMgr's items from the "Connections" pane.
This must be started as Administrator otherwise it will fail (not immediately). In general UIA clients must run at same UAC level as automated apps.
To determine what to get from the tree or if something can be done, before any coding, we can use the Inspect tool from Windows SDK or the more recent Accessibility Insights.
Also, I use Windows' UIAutomationClient COM object, not the old one from Windows XP era as it misses lots of stuff.
The code iterates all tree items recursively and expand them if they are not expanded using the ExpandCollapse Control Pattern because InetMgr's tree has a lazy loading behavior as it can potentially contains hundreds of thousands of items (mapped to disk folders at some points).
class Program
{
// add a COM reference to UIAutomationClient (don't use .NET legacy UIA .dll)
private static readonly CUIAutomation8 _automation = new CUIAutomation8(); // using UIAutomationClient;
static void Main()
{
var process = Process.GetProcessesByName("InetMgr").FirstOrDefault();
if (process == null)
{
Console.WriteLine("InetMgr not started.");
return;
}
var inetmgr = _automation.ElementFromHandle(process.MainWindowHandle);
if (inetmgr == null)
return;
// note: set "Embed Interop Type" to false in UIAutomationClient reference node or redefine all UIA_* used constants manually
// also: you *must* this program as administrator to see this panel
var connections = inetmgr.FindFirst(TreeScope.TreeScope_Subtree,
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_AutomationIdPropertyId, "_hierarchyPanel"));
if (connections == null)
return;
var treeRoot = connections.FindFirst(TreeScope.TreeScope_Subtree,
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_TreeItemControlTypeId));
Dump(0, treeRoot);
}
static void Dump(int indent, IUIAutomationElement element)
{
var s = new string(' ', indent);
Console.WriteLine(s + "name: " + element.CurrentName);
// get expand/collapse pattern. All treeitem support that
var expandPattern = (IUIAutomationExpandCollapsePattern)element.GetCurrentPattern(UIA_PatternIds.UIA_ExpandCollapsePatternId);
if (expandPattern != null && expandPattern.CurrentExpandCollapseState != ExpandCollapseState.ExpandCollapseState_Expanded)
{
try
{
expandPattern.Expand();
}
catch
{
// cannot be expanded
}
}
// tree can be huge,only dump 2 levels max
if (indent > 2)
return;
var children = element.FindAll(TreeScope.TreeScope_Children, _automation.CreateTrueCondition());
for (var i = 0; i < children.Length; i++)
{
Dump(indent + 1, children.GetElement(i));
}
}
}
Related
I've been working for a few days on this and would like if nothing more some confirmation or a few hints as to where to go next on this task.
I've been tasked with hiding certain notifications within Chrome that Chrome doesn't provide a native option to hide - even in kiosk or incognito mode. The approach I have been using is Microsoft's Automation API to get access to these objects, which is relatively easy because I can find them by classname or by some of the text contained within the object - which is necessary. The challenge now is that I need to hide the element and/or it's container without closing Chrome :)
Easy enough to get the main handle to Chrome:
private IntPtr GetChromeHandle()
{
IntPtr chWnd = IntPtr.Zero;
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero)
{
continue;
}
else
{
chWnd = chrome.MainWindowHandle;
}
}
return chWnd;
}
I can get one of these elements pretty easily from there by doing the following:
PropertyCondition pcFullScreen = new PropertyCondition(AutomationElement.NameProperty, "F11", PropertyConditionFlags.IgnoreCase);
AutomationElement fsTest = chromeWindow.FindFirst(TreeScope.Descendants, pcFullScreen);
The challenge here is how to close that element or navigate to a high level that I can close it?
Alternate approach tried looks like this:
PropertyCondition pcTest = new PropertyCondition(AutomationElement.ClassNameProperty, "Intermediate D3D Window");
AutomationElement newTestElm = chromeWindow.FindFirst(TreeScope.Descendants, pcTest);
Problem here is that while I can "hide/close" by using the handle of the class, I can't seem to narrow it down to an instance of the class that contains the text I'm looking for. Any suggestions would be greatly appreciated.
Per a comment, tried to access via WindowPattern and get "null" based on this code:
private WindowPattern GetWindowPattern(AutomationElement targetControl)
{
WindowPattern windowPattern = null;
try
{
windowPattern =
targetControl.GetCurrentPattern(WindowPattern.Pattern)
as WindowPattern;
}
catch (InvalidOperationException)
{
// object doesn't support the WindowPattern control pattern
return null;
}
// Make sure the element is usable.
if (false == windowPattern.WaitForInputIdle(10000))
{
// Object not responding in a timely manner
return null;
}
return windowPattern;
}
I can run a very simple launch pipeline from the command line thus:
gst-launch-1.0 videotestsrc ! ximagesink
and, from gst-inspect-1.0, the ximagesink appears to support the GstVideoOverlay interface so that I can bind it to a specific Gtk widget.
However, when trying to do that from within some code I happened to find lying around on the net, it seems that the pipeline is not considered to be a bin (and hence, the widget ID is not being given to it).
The code to do it is as follows, first to create the pipeline and set it up to capture bus messages:
Gst.Element playbin = Gst.Parse.Launch("videotestsrc ! ximagesink");
Gst.Bus bus = playbin.Bus;
bus.AddSignalWatch();
bus.Message += MsgCallback;
Then to actually process the bus messages:
private void MsgCallback(object o, MessageArgs args) {
// Only care about window ID binding requests.
Gst.Message msg = args.Message;
if (! Gst.Video.Global.IsVideoOverlayPrepareWindowHandleMessage(msg))
return;
// Get source of message.
Gst.Element src = msg.Src as Gst.Element;
if (src == null)
return;
// Find element supporting interface and notify it to bind.
Gst.Element ov = null;
if (src is Gst.Bin) {
ov = ((Gst.Bin) src).GetByInterface(VideoOverlayAdapter.GType);
VideoOverlayAdapter ad = new VideoOverlayAdapter(ov.Handle);
ad.WindowHandle = windowXId;
}
}
Now, for some reason, the src is Gst.Bin is false, meaning that the windowXId (the widget ID I've previously set) is never being communicated to GStreamer.
However, if I provide a playbin pipeline (playbin uri=XYZZY videosink='videoconvert ! videoflip method=none ! videoconvert ! autovideosink', if you're interested), it works fine.
As far as I can tell from the documentation for Gst.Parse.Launch(), it should give me a pipeline which is a special case of a bin, as per here (after fixing the atrocious grammar):
Returns a new element on success, NULL on failure. If more than one top level element is specified by the pipeline description , all elements are put into a GstPipeline, which is then returned.
I'm pretty certain that videotestsrc and ximagesink are two separate top-level elements but, when I add the following code, after the check for src being null:
Console.WriteLine("is bin " + (src is Gst.Bin));
Console.WriteLine("is element " + (src is Gst.Element));
Console.WriteLine("is pipeline " + (src is Gst.Pipeline));
Console.WriteLine(type is " + src.GetType());
I see:
is bin False
is element True
is pipeline False
type is Gst.Element
for the problematic videotestsrc pipeline and the following for the good playbin one:
is bin True
is element True
is pipeline False
type is Gst.Bin
So everything points to the problematic one giving an element rather than a bin, despite what the documentation states.
What am I missing here? What is the difference between the two pipelines that would cause different behaviour?
Okay, it turns out that the Gst.Parse.Launch function is actually returning a pipeline. This can be seen if you copy those checking statements immediately after the bin creation:
Gst.Element playbin = Gst.Parse.Launch("videotestsrc ! ximagesink");
Console.WriteLine("is bin " + (playbin is Gst.Bin));
Console.WriteLine("is element " + (playbin is Gst.Element));
Console.WriteLine("is pipeline " + (playbin is Gst.Pipeline));
Console.WriteLine(type is " + playbin.GetType());
and you see:
is bin True
is element True
is pipeline True
type is Gst.Pipeline
It's just that, when we get the message in the callback, the source seems to be set to an element instead. I haven't actually figured out why the message source is coming through as an element (or even which element it is) but I at least have a workaround, thanks partly to my advanced intelligence but mostly to a sneaky look at the way Banshee does it :-)
Turns out that the callback can simply access the bin directly rather than trying to get it out of the source. This works in my case since I only have the one video stream (bin) per instance. If you have multiples, it may be a little more difficult.
To achieve this, you first make playbin a member variable (if it isn't already), and you can then modify the callback thus:
private void MsgCallback(object o, MessageArgs args) {
// Only care about window ID binding requests.
Gst.Message msg = args.Message;
if (! Gst.Video.Global.IsVideoOverlayPrepareWindowHandleMessage(msg))
return;
// Get instance bin, interface element, then notify it to bind.
Gst.Bin src = (Gst.Bin)(this.playbin);
if (src == null) return;
Gst.Element ov = src.GetByInterface(VideoOverlayAdapter.GType);
if (ov == null) return;
VideoOverlayAdapter ad = new VideoOverlayAdapter(ov.Handle);
if (ad == null) return;
ad.WindowHandle = windowXId;
}
I need to perform some simultaneous webdrivers manipulation, but I am uncertain as to how to do this.
What I am asking here is:
What is the correct way to achieve this ?
What is the reason for the exception I am getting (revealed below)
After some research I ended up with:
1. The way I see people doing this (and the one I ended up using after playing with the API, before searching) is to loop over the window handles my WebDriver has at hand, and perform a switch to and out of the window handle I want to process, closing it when I am finished.
2. Selenium Grid does not seem like an option fore me - am I wrong or it is intended for parallel processing ? Since am running everything in a single computer, it will be of no use for me.
In trying the 1st option, I have the following scenario (a code sample is available below, I skipped stuff that is not relevant/repeat itself (where ever I added 3 dots:
I have a html page, with several submit buttons, stacked.
Clicking each of them will open a new browser/tab (interestingly enough, using ChromeDriver opens tabs, while FirefoxDriver opens separate windows for each.)
As a side note: I can't determine the uris of each submit beforehand (they must be determined by javascript, and at this point, let's just assume I want to handle everything knowing nothing about the client code.
Now, after looping over all the submit buttons, and issuing webElement.Click() on the corresponding elements, the tabs/windows open. The code flows to create a list of tasks to be executed, one for each new tab/window.
The problem is: since all tasks all depend upon the same instance of webdriver to switch to the window handles, seems I will need to add resource sharing locks/control. I am uncertain as whether I am correct, since I saw no mention of locks/resource access control in searching for multi-threaded web driver examples.
On the other hand, if I am able to determine the tabs/windows uris beforehand, I would be able to skip all the automation steps needed to reach this point, and then creating a webDriver instance for each thread, via Navigate().GoToUrl() would be straightforward. But this looks like a deadlock! I don't see webDriver's API providing any access to the newly opened tab/window without performing a switch. And I only want to switch if I do not have to repeat all the automation steps that lead me to the current window !
...
In any case, I keep getting the exception:
Element belongs to a different frame than the current one - switch to its containing frame to use it
at
IWebElement element = cell.FindElement
inside the ToDictionary() block.
I obviously checked that all my selectors are returning results, in chrome's console.
foreach (WebElement resultSet in resultSets)
resultSet.Click();
foreach(string windowHandle in webDriver.WindowHandles.Skip(1))
{
dataCollectionTasks.Add(Task.Factory.StartNew<List<DataTable>>(obj =>
{
List<DataTable> collectedData = new List<DataTable>();
string window = obj as string;
if (window != null)
{
webDriver.SwitchTo().Window(windowHandle);
List<WebElement> dataSets = webDriver.FindElements(By.JQuerySelector(utils.GetAppSetting("selectors.ResultSetData"))).ToList();
DataTable data = null;
for (int i = 0; i < dataSets.Count; i += 2)
{
data = new DataTable();
data.Columns.Add("Col1", typeof(string));
data.Columns.Add("Col2", typeof(string));
data.Columns.Add("Col3", typeof(string));
///...
//data set header
if (i % 2 != 0)
{
IWebElement headerElement = dataSets[i].FindElement(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataHeader")));
data.TableName = string.Join(" ", headerElement.Text.Split().Take(3));
}
//data set records
else
{
Dictionary<string, string> cells = dataSets[i]
.FindElements(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataCell")))
.ToDictionary(
cell =>
{
IWebElement element = cell.FindElement(OpenQA.Selenium.By.CssSelector(utils.GetAppSetting("selectors.ResultSetDataHeaderColumn")));
return element == null ? string.Empty : element.Text;
},
cell =>
{
return cell == null ? string.Empty : cell.Text;
});
string col1Value, col2Value, col3Value; //...
cells.TryGetValue("Col1", out col1Value);
cells.TryGetValue("Col2", out col2Value);
cells.TryGetValue("Col3", out col3Value);
//...
data.Rows.Add(col1Value, col2Value, col3Value /*...*/);
}
}
collectedData.Add(data);
}
webDriver.SwitchTo().Window(mainWindow);
webDriver.Close();
return collectedData;
}, windowHandle));
} //foreach
Task.WaitAll(dataCollectionTasks.ToArray());
foreach (Task<List<DataTable>> dataCollectionTask in dataCollectionTasks)
{
results.AddRange(dataCollectionTask.Result);
}
return results;
I am trying to get the list of Tags from a OPC server, I am using the EasyDaClient from the QuickOPC. What I'm trying to do is this
try
{
//create the OPC Client object
OpcLabs.EasyOpc.DataAccess.EasyDAClient easyDAClient1 = new OpcLabs.EasyOpc.DataAccess.EasyDAClient();
//ServerDescriptor Declration
OpcLabs.EasyOpc.ServerDescriptor sd = new OpcLabs.EasyOpc.ServerDescriptor();
sd.MachineName = "W7VM";
sd.ServerClass = "OPC.Siemens.XML.1";
OpcLabs.EasyOpc.DataAccess.DANodeElementCollection allTags = easyDAClient1.BrowseLeaves(sd);
foreach (OpcLabs.EasyOpc.DataAccess.DANodeElement tag in allTags)
{
//if the value is a branch add it to the listbox.
if (tag.IsLeaf)
{
//add the fully qualified item id
listBox1.Items.Add(tag.ItemId.ToString());
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message.ToString());
}
I always get 0 elements from the BrowseLeaves method and I don't know what is my Channel_1.Device_1 so that I can use the other overload.
I am new to this can someone explain me how the OPC tags can be listed??
FYI: I can read the values from the tags using the:
easyDAClient1.ReadItem(MachineNameTextBox.Text,serverClassTextBox.Text,itemIdTextBox.Text);
So its not a Conenction problem
You are browsing the leaves at the root level, and there none there, that's why you get an empty collection.
What can you do? Several options there:
1) If you want to start the browsing at the root and get to the leaf level, you need to consider the branches as well. Use BrowseBranches method, or (maybe even better) use BrowseNodes, which returns both the branches and the leaves. When you get a branch node (you can test it using .IsBranch), you may decide to browse further into it.
2) If you want to get the leaves and know which branch they are at, you can pass in the branch name to the BrowseLeaves method as an additional parameter. However this is probably not your case, as I can guess form you saying "I don't know what is my Channel_1.Device_1 ", which is probably the branch ID that you do not "know" upfront.
Here is a complete example with recursive browsing:
// BrowseAndReadValues: Console application that recursively browses and displays the nodes in the OPC address space, and
// attempts to read and display values of all OPC items it finds.
using System.Diagnostics;
using JetBrains.Annotations;
using OpcLabs.EasyOpc;
using OpcLabs.EasyOpc.DataAccess;
using System;
namespace BrowseAndReadValues
{
class Program
{
const string ServerClass = "OPCLabs.KitServer.2";
[NotNull]
static readonly EasyDAClient Client = new EasyDAClient();
static void BrowseAndReadFromNode([NotNull] string parentItemId)
{
// Obtain all node elements under parentItemId
var nodeFilter = new DANodeFilter(); // no filtering whatsoever
DANodeElementCollection nodeElementCollection = Client.BrowseNodes("", ServerClass, parentItemId, nodeFilter);
// Remark: that BrowseNodes(...) may also throw OpcException; a production code should contain handling for it, here
// omitted for brevity.
foreach (DANodeElement nodeElement in nodeElementCollection)
{
Debug.Assert(nodeElement != null);
// If the node is a leaf, it might be possible to read from it
if (nodeElement.IsLeaf)
{
// Determine what the display - either the value read, or exception message in case of failure.
string display;
try
{
object value = Client.ReadItemValue("", ServerClass, nodeElement);
display = String.Format("{0}", value);
}
catch (OpcException exception)
{
display = String.Format("** {0} **", exception.GetBaseException().Message);
}
Console.WriteLine("{0} -> {1}", nodeElement.ItemId, display);
}
// If the node is not a leaf, just display its itemId
else
Console.WriteLine("{0}", nodeElement.ItemId);
// If the node is a branch, browse recursively into it.
if (nodeElement.IsBranch &&
(nodeElement.ItemId != "SimulateEvents") /* this branch is too big for the purpose of this example */)
BrowseAndReadFromNode(nodeElement);
}
}
static void Main()
{
Console.WriteLine("Browsing and reading values...");
// Set timeout to only wait 1 second - default would be 1 minute to wait for good quality that may never come.
Client.InstanceParameters.Timeouts.ReadItem = 1000;
// Do the actual browsing and reading, starting from root of OPC address space (denoted by empty string for itemId)
BrowseAndReadFromNode("");
Console.WriteLine("Press Enter to continue...");
Console.ReadLine();
}
}
}
Tech support: http://www.opclabs.com/forum/index
I have a custom sharepoint (2007) list (named testlist) on which I attached a test workflow (built with sharepoint designer 2007 and named testwf), which only task defined in the 'Actions' section at 'Step 1' is to wait until april 2014.
When I add a new item to the testlist the testwf will start and, when I switch to the grid view, the item has the field "testwf" as running.
Now I need to access the workflow associated with the item and then "complete" this task via code by changing its status but, using the following code, I always get the item.Tasks list empty (but I can see that the internal variable m_allTaskListTasks has 1 element).
using (SPSite site = new SPSite("http://mysp"))
{
site.AllowUnsafeUpdates = true;
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
foreach (SPList list in web.Lists)
{
if (list.Title != "testlist") continue;
foreach (SPListItem item in list.Items)
{
item.Web.AllowUnsafeUpdates = true;
if(item.Tasks.Count > 0)
//do work
}
}
}
Maybe I'm missing something...
I use this code to access my workflowtasks:
Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
// now you can access the workflows tasks
SPTask task = workflow.Tasks[item.UniqueId];
Cross-posted question.
#petauro, have you made any headway on this? I can corroborate #moontear's answer based on the following code that I have used with success in the past:
...
// get workflow tasks for SPListItem object item
if (item != null && item.Workflows != null && item.Workflows.Count > 0)
{
try
{
var workflows = site.WorkflowManager.GetItemActiveWorkflows(item);
foreach (SPWorkflow workflow in workflows)
{
// match on some indentifiable attribute of your custom workflow
// the history list title is used below as an example
if (workflow.ParentAssociation.HistoryListTitle.Equals(Constants.WORKFLOW_HISTORY_LIST_TITLE))
{
var workflowTasks = workflow.Tasks;
if (workflowTasks != null && workflowTasks.Count > 0)
{
// do work on the tasks
}
}
}
}
catch
{
// handle error
}
}
...
While only slightly different from the code you posted in your latest comment, see if it helps.
Another minor point: are there multiple instances of lists titled "testlist" within your SPWeb? If not, why iterate over web.Lists? Just get the one list directly and avoid some superfluous CPU cycles: SPWeb.GetList()
You have to go differently about this. You need to get the workflow task list and retrieve your task from there and finish it.
First you would need to check whether a workflow is running on your item: if (item.Workflows > 0) from there you could iterate through all the workflow instances on the list item, get the SPWorkflowAssociation and the associated task and history list. From there you would only need to find the task you are looking for in the associated task list.