For Japanese: 日本語
Friendly is a library for creating integration tests.
It has the ability to manipulate other processes.
It is currently designed for Windows Applications (WinForms, WPF, and Win32).
The name Friendly is derived from the C++ friend class.
Being friends gives you access to what you normally wouldn't be able to do.
Starting from .NET 9, BinaryFormatter will be deprecated. In Friendly, this is used for data serialization in inter-process communication. We have now enabled customization of serialization, so for the .NET environment please refer to this document.
Similar to the Selenium Page Object pattern, there is a recommended design policy for automated testing with Friendly.See here.
Friendly can also operate .NetCore WinForms and WPF apps. Tests can also be written in .NetCore, but a warning will appear, so please exclude 1701 and NU1701.
It's like a selenium's javascript execution.
All Methods, Properties and Fields can be called regardless of being public internal protected private.
It can inject .net assembly. And can execute inserted methods.
Install from NuGet
WPF
PM> Install-Package RM.Friendly.WPFStandardControls
WinForms
PM> Install-Package Ong.Friendly.FormsStandardControls
Win32
PM> Install-Package Codeer.Friendly.Windows.NativeStandardControls
http://blogs.msdn.com/b/mvpawardprogram/archive/2014/11/04/mvp-showcase-winners.aspx
Here is some sample code to show how you can get started with Friendly. This is a perfect ordinary Windows Application that is manipulation target. (There is no kind of trick.)
<Windowx:Class="Target.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow"Height="350"Width="525">
<Grid>
<TextBoxx:Name="_textBox"Text="{Binding Path=TextData}"/>
</Grid>
</Window>usingSystem.ComponentModel;usingSystem.Windows;namespaceTarget{publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();this.DataContext=newVM();}stringMyFunc(intvalue){returnvalue.ToString();}}classVM:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged=(_,__)=>{};string_textData;publicstringTextData{get{return_textData;}set{_textData=value;PropertyChanged(this,newPropertyChangedEventArgs(nameof(TextData)));}}}}This is a test application (using VSTest):
usingCodeer.Friendly.Dynamic;usingCodeer.Friendly.Windows;usingMicrosoft.VisualStudio.TestTools.UnitTesting;usingSystem.Diagnostics;usingSystem.IO;usingSystem.Windows;namespaceSample{[TestClass]publicclassTest{WindowsAppFriend_app;[TestInitialize]publicvoidTestInitialize(){//attach to target process!varpath=Path.GetFullPath("../../../Target/bin/Debug/Target.exe");_app=newWindowsAppFriend(Process.Start(path));}[TestCleanup]publicvoidTestCleanup(){Processprocess=Process.GetProcessById(_app.ProcessId);_app.Dispose();process.CloseMainWindow();}[TestMethod]publicvoidManipulate(){//static methoddynamicwindow=_app.Type<Application>().Current.MainWindow;//instance methodstringvalue=window.MyFunc(5);Assert.AreEqual("5",value);//instance propertywindow.DataContext.TextData="abc";//instance field.stringtext=window._textBox.Text;Assert.AreEqual("abc",text);}}}It's a very powerful feature. With .Net knowledge, most operations are possible.
However, since it is difficult to write all tests with just this, we have prepared more convenient libraries.
It call another process's api. And it injection .net dlls.
Other Friendly libraries are built on top of this feature.
TIt mainly provides a function to search for windows.
Others provide basic Win32 level operations for Window that have Window handles.
Low level key mouse emulation
Timing is adjusted using the Friendly feature.
Provides operations for basic controls such as Button, ListView, TreeView.
Friendly.Windows.NativeStandardControls(Win32)
Friendly.FormsStandardControls(WinForms)
Friendly.WPFStandardControls(WPF)
Friendly.XamControls(WPF)
Friendly.FarPoint(WinForms)
Friendly.C1.Win(WinForms)
For example, you'll use these when manuplate with WPF apps.
Friendly.Windows.Grasp
Friendly.WPFStandardControls
Friendly.NativeStandardContorls
usingSystem.Diagnostics;usingSystem.IO;usingCodeer.Friendly;usingCodeer.Friendly.Dynamic;usingCodeer.Friendly.Windows;usingCodeer.Friendly.Windows.Grasp;usingMicrosoft.VisualStudio.TestTools.UnitTesting;usingRM.Friendly.WPFStandardControls;namespaceScenario{[TestClass]publicclassTest{WindowsAppFriend_app;[TestInitialize]publicvoidTestInitialize(){varpath=Path.GetFullPath(Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location),"../../../DemoApp/bin/Debug/DemoApp.exe"));varinfo=newProcessStartInfo(path){WorkingDirectory=Path.GetDirectoryName(path)};_app=newWindowsAppFriend(Process.Start(info));}[TestCleanup]publicvoidTestCleanup()=>Process.GetProcessById(_app.ProcessId).Kill();[TestMethod]publicvoidSample(){// get by type full name from target process's windows.varwindow=_app.WaitForIdentifyFromTypeFullName("DemoApp.Views.MainWindow");// get by field.(Friendly's infrastructure function)AppVaruserControl=window.Dynamic()._userControl;// get by binding.WPFDataGriddataGrid=userControl.LogicalTree().ByBinding("SelectedItem.Value").Single().Dynamic();// edit.dataGrid.EmulateChangeCellText(1,2,"abc");// * This sample code was written mixed, // But usually, the scenario and specific logic of screen element are separated like the page object pattern of Selenium.}}}Here, we will explain the basic functions of Friendly. It's about another process's API call and DLL injection.
The target and test processes must use the same processor architectue.
If you are using VSTest, you can set this by using the Visual Studio menus as shown below.
usingCodeer.Friendly;usingCodeer.Friendly.Dynamic;usingCodeer.Friendly.Windows;Attach using WindowsAppFriend. Operations can be executed on the main window thread:
publicWindowsAppFriend(Process process);Operation can also be executed on a specified window thread:
publicWindowsAppFriend(IntPtr windowHandle);dynamicsampleForm1=_app.Type<Application>().Current.MainWindow;dynamicsampleForm2=_app.Type(typeof(Application)).Current.MainWindow;dynamicsampleForm4=_app.Type("System.Windows.Forms.Application").Current.MainWindow;//methodstringvalue=window.MyFunc(5);//propertywindow.DataContext.TextData="abc";//field.stringtext=window._textBox.Text;See here for more details on the interface.
dynamiclistBox1=_app.Type<ListBox>()();dynamiclistBox2=_app.Type(typeof(ListBox))();dynamiclistBox4=_app.Type("System.Windows.Controls.ListBox")();dynamiclist=_app.Type<List<int>>()(newint[]{1,2,3,4,5});You can use serializable objects / AppVar / DynamicAppVar / IAppVarOwner.
If you use serializable objects, they will be serialized and a copy will be sent to the target process.
See here for AppVar / DynamicAppVar / IAppVarOwner detail。
// serializable objectwindow.MyFunc(5);window.DataContext.TextData="abc";// new instance in target process. textBox is DynamicAppVar.dynamictextBox=_app.Type<TextBox>()();// DynamicAppVarwindow.Content.Children.Add(textBox);// DyanmicAppVar, referenced object exists in target process' memory. dynamicreference=window._textBox.Text;// when you perform a cast, it will be marshaled from the target process.stringtext=reference;// OKstringcast=(string)reference;// OKstringsubstitution=reference;// No good. Result is false.boolisString=referenceisstring;// No good. Result is null.stringtextAs=referenceasstring;// No good. Throws an exception.string.IsNullOrEmpty(reference);// OKstring.IsNullOrEmpty((string)reference);IEnumerable
foreach(varwin_app.Type<Application>().Current.Windows){}AppVar
dynamicwindow=_app.Type<Application>().Current.MainWindow;AppVarappVar=window;appVar["Title"]("abc");AppVar is part of the old style interface.
You will need to use AppVar if you use the old interface or if you can't use the .NET framework 4.0.
It will also use in the Friendly libraries interface. Please refer here.
DynamicAppVar can be implicitly converted to a class that has a constructor that takes AppVar as one argument.
varwindow=app.Type<Application>();//pulbic WPFDataGrid(AppVar src)WPFDataGriddataGrid=newWPFDataGrid(window._dataGrid);//can convert!WPFDataGriddataGrid=window._dataGrid;Friendly operations are executed synchronously. But you can use the Async class to execute them asynchronously.
// Async can be specified anywhere among the arguments.varasync=newAsync();window.MyFunc(async,5);// You can check whether it has completed.if(async.IsCompleted){//・・・}// You can wait for it to complete.async.WaitForCompletion();You can get the return value when the process is completed.
// Text will obtain its value when the operation completes.varasync=newAsync();vartext=window.MyFunc(async,5);// When the operation finishes, the value will be available.
async.WaitForCompletion();stringtextValue=(string)text;Dictionary<int,string>dic=newDictionary<int,string>();dic.Add(1,"1");// Object is serialized and a copy will be sent to the target process dynamicdicInTarget=_app.Copy(dic);// Null is useful for out argumentsdynamicvalue=_app.Null();dicInTarget.TryGetValue(1,value);Assert.AreEqual("1",(string)value);[TestMethod]publicvoidDllInjection(){dynamicwindow=_app.Type<Application>().Current.MainWindow;dynamictextBox=window._textBox;//Causes the specified assembly to be loaded into the target process._app.LoadAssembly(GetType().Assembly);//You can use the type contained in the loaded assembly in the target process.dynamicobserver=_app.Type<Observer>()(textBox);//Check change text.textBox.Text="abc";Assert.IsTrue((bool)observer.TextChanged);}classObserver{internalboolTextChanged{get;set;}internalObserver(TextBoxtextBox){textBox.TextChanged+=delegate{TextChanged=true;};}}Native dll methods.
[TestMethod]publicvoidDllInjectionPInvoke(){WindowsAppExpander.LoadAssembly(_app,GetType().Assembly);Processprocess=Process.GetProcessById(_app.ProcessId);_app.Type(GetType()).MoveWindow(process.MainWindowHandle,0,0,200,200,true);dynamicrectInTarget=_app.Type<RECT>()();_app.Type(GetType()).GetWindowRect(process.MainWindowHandle,rectInTarget);RECTrect=(RECT)rectInTarget;Assert.AreEqual(0,rect.left);Assert.AreEqual(0,rect.top);Assert.AreEqual(200,rect.right);Assert.AreEqual(200,rect.bottom);}[DllImport("User32.dll")]staticexternboolMoveWindow(IntPtrhandle,intx,inty,intwidth,intheight,boolredraw);[DllImport("user32.dll")][return:MarshalAs(UnmanagedType.Bool)]staticexternboolGetWindowRect(IntPtrhwnd,outRECTlpRect);[Serializable][StructLayout(LayoutKind.Sequential)]internalstructRECT{publicintleft;publicinttop;publicintright;publicintbottom;}Friendly was initially designed to work with .Net 2.0.
That's why we used to make calls like this.
//old styleAppVarmainWindow=app[typeof(Application),"Current"]()["MainWindow"]();stringtitle=(string)mainWindow["Title"]().Core;Since I started using dynamic in .Net 4.0, I can write like this.
//new styledynamicmainWindow=app.Type<Application>().Current.MainWindow;stringtitle=mainWindow.Title;You generally shouldn't need to use a version older than .Net 4.0, so you should write your code in the new style.
However, AppVar will continue to be used for the library interface because dynamic is not optimal for function arguments or return values.
Friendly finally executes the API in the target process using reflection.
Therefore, the operation is specified by a character string.
In the above example, it is a property, but for example, a function call is written like this.
The function name was specified as a character string in [], and the argument was passed in the following ().
mainWindow["MyFunc"](100);However, this is not very intuitive.
Since .Net4.0 can use dynamic, DynamicAppType and DynamicAppVar were introduced.
As a result, it became possible to write intuitively like the new style above.
usingCodeer.Friendly.Dynamic;Extension method can be used by using Codeer.Friendly.Dynamic namespace.
AppVarmainWindow1=app[typeof(Application),"Current"]()["MainWindow"]();//1. Dynamic() AppVar -> dynamic(DynamicAppVar)dynamicmainWindow2=mainWindow1.Dynamic();//2. Type()dynamicapplicationType=app.Type<Application>();dynamicmainWindow3=applicationType.Current.MainWindow;Both DynamicAppType and DynamicAppVar are returned as dynamic(DynamicAppVar) as the return value for API calls.
So after that you can call the API as if it were a normal .Net object in the same process.
DynamicAppVar and AppVar can be converted to each other.
There are some Friendly libraries that take AppVar as an argument.
If you put DynamicAppVar here, it works fine.
varwindow=app.Type<Application>();//pulbic WPFDataGrid(AppVar src)WPFDataGriddataGrid=newWPFDataGrid(window._dataGrid);dynamicmainWindow=app.Type<Application>().Current.MainWindow;//DynamicAppVar//title is in the target process at this pointdynamictitle=mainWindow.Title;//At this point, it will be serialized into a byte array and come to the this processstringtitleText=title;dynamicwindowSrc=app.Type<Application>().Current.MainWindow;//Oops! Exception occurred!WindowwindowDst=windowSrc;//[Codeer.Friendly.FriendlyOperationException]//Communication with the application failed.//The target applcation may be unreachable or you may be trying to send//data that cannot be serialized.This happens because the Window class cannot be serialized.
Please use such objects as DynamicAppVar.
IAppVarOwner is an interface to specify that the class has AppVar inside.
Classes that implement this interface have the following advantages.
- Can be passed as an argument as in AppVar
- Dynamic() extension method can be used
[TestMethod]publicvoidTest(){//WindowControl implementes IAppVarOwner.WindowControlwindow=_app.WaitForIdentifyFromTypeFullName("DemoApp.Views.MainWindow");//You can use the Dynamic() extension.//WPF TextBox also implements IAppVarOwner.WPFTextBoxtextBox=newWPFDataGrid(window.Dynamic()._textBox);//It can be passed to the Friendly interface just like AppVar._app.LoadAssembly(GetType().Assembly);dynamicobserver=_app.Type<Observer>()(textBox);//Check change text.textBox.EmulateChangeText("abc");Assert.IsTrue((bool)observer.TextChanged);}classObserver{internalboolTextChanged{get;set;}internalObserver(TextBoxtextBox){textBox.TextChanged+=delegate{TextChanged=true;};}}
