Skip to content

Repository files navigation

Friendly

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 support .NetCore.

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.

Features ...

Invoke separate process's API.

It's like a selenium's javascript execution.
All Methods, Properties and Fields can be called regardless of being public internal protected private.

DLL injection.

It can inject .net assembly. And can execute inserted methods.

Getting Started

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

We win 2nd place at Microsoft MVP Showcase. Thank you!

http://blogs.msdn.com/b/mvpawardprogram/archive/2014/11/04/mvp-showcase-winners.aspx

Simple sample

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);}}}

Friendly packages.

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.
Libraries.jpg

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.

Control Drivers

Provides operations for basic controls such as Button, ListView, TreeView.

basic

Friendly.Windows.NativeStandardControls(Win32)
Friendly.FormsStandardControls(WinForms)
Friendly.WPFStandardControls(WPF)

3rd party cotnrols

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.}}}

Friendly Infrastructure

Here, we will explain the basic functions of Friendly. It's about another process's API call and DLL injection.

Attention! Match the Processor Architecture. (x86 or x64)

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.
Match the Processor Architecture

Using Statements

usingCodeer.Friendly;usingCodeer.Friendly.Dynamic;usingCodeer.Friendly.Windows;

Connection to Execution Thread

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);

Invoking Static Operations(Any OK)

dynamicsampleForm1=_app.Type<Application>().Current.MainWindow;dynamicsampleForm2=_app.Type(typeof(Application)).Current.MainWindow;dynamicsampleForm4=_app.Type("System.Windows.Forms.Application").Current.MainWindow;

Invokeing Instance Operations

//methodstringvalue=window.MyFunc(5);//propertywindow.DataContext.TextData="abc";//field.stringtext=window._textBox.Text;

See here for more details on the interface.

Instantiating New Objects(Any OK)

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});

Rules for Arguments

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);

Rules for Return Values

// 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;

Note the Casting Behavior

// 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);

Special Convert

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;

Async

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;

Copy() and Null()

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);

Dll injection.

[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 interface

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.

Codeer.Friendly.Dynamic

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 & AppVar

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);

AppVar.jpg

Converting DynamicAppVar to other types

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;

Serialize.jpg

Frequently asked exceptions

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.
CantSerialize.jpg

IAppVarOwner

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;};}}

About

No description, website, or topics provided.

Resources

Stars

88 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages