読者です 読者をやめる 読者になる 読者になる

ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

Friendly.Windows.Graspとは

Friendly.Windows.Graspに関してです。
ハンズオンをやろうかなーと思ったのですが、ちょっと特殊なライブラリなので、先に少し解説します。まあここは「なんとなく分かった」ぐらいでいいかなーと。次回のハンズオンで手を動かすと問題なく理解できるはずです。

そもそも何?

f:id:ishikawa-tatsuya:20150104103057p:plain
他のと違ってちょっと分かりづらいですね。
Friendly.Windows.NativeStandardControls、Friendly.FormsStandardControls、Friendly.WPFStandardControlsがそれぞれのコントロールのラッパーを定義したライブラリなのですが、これは何なのでしょうか?

Windowハンドルを持つWindowのラッパー(みたいなもの

と捉えてください。
Win32、WinFormsはすべてのウィンドウ、コントロールにWindowハンドルが割り当たっています。それらをラップすることができます。WPFの場合はトップレベルウィンドウのみですね。
で、Windowのラッパーと言うと、やるべきことは膨大すぎますよね?当然、それらを全網羅とかしているわけではなく、テスト時の操作で必要と僕が判断した機能(経験上)を入れています。
機能を紹介していきます。

WindowControl

実はFriendly.Windows.Graspはこのクラスだけを提供しているライブラリです。(正確には例外クラスとか、引数のためのデータクラスとかありますけど)これだけ覚えたらOKです。

生成

取得に関しては以降でやるのですが、生成することも出来ます。
Windowハンドルから生成する方法と、.NetのWindowのオブジェクト(WinFormsのControl、WPFのWindow)から生成する方法があります。

WindowControl(AppVar windowObject);
WindowControl(WindowsAppFriend app, IntPtr windowHandle);

トップレベルウィンドウ取得

これは重要です。
うっかりするとタイミング依存の失敗が入り込むのです。マルチプロセスプログラミングなので、高速に動作させると、期待のウィンドウが出ていない間にアクセスして失敗という残念な結果になることがあります。そんなことにならないように、確実に待ち合わせる必要があります。
あと、タイミング依存と言うわけではないのですが、場合によって特定できないような手段は避けてください。確実に目的のWindowを取得できる手段を使ってください。もちろんそれはケースによります。複数用意していますので、その都度、最適なものを選んでください。
それから、.NetのWindowの場合は、WinFormsはApplication.OpenForms、WPFはApplication.Current.Windowsも利用できますし、独自の取得方法も定義できますので、それも考慮に入れつつで。

・最前面のウィンドウを取得

static WindowControl FromZTop(WindowsAppFriend app);

・有効なトップレベルウィンドウを全取得

static WindowControl[] GetTopLevelWindows(WindowsAppFriend app);

・.Netのタイプフルネームから取得

static WindowControl[] GetFromTypeFullName(WindowsAppFriend app, string typeFullName);
static WindowControl IdentifyFromTypeFullName(WindowsAppFriend app, string typeFullName);
static WindowControl WaitForIdentifyFromTypeFullName(WindowsAppFriend app, string typeFullName);
static WindowControl WaitForIdentifyFromTypeFullName(WindowsAppFriend app, string typeFullName, Async async);

・Windowテキストから取得(GetWindowTextで取得できる文字列)

static WindowControl[] GetFromWindowText(WindowsAppFriend app, string text);
static WindowControl IdentifyFromWindowText(WindowsAppFriend app, string text);
static WindowControl WaitForIdentifyFromWindowText(WindowsAppFriend app, string text);
static WindowControl WaitForIdentifyFromWindowText(WindowsAppFriend app, string text, Async async);

・Windowクラス名称から取得

static WindowControl[] GetFromWindowClass(WindowsAppFriend app, string className);
static WindowControl IdentifyFromWindowClass(WindowsAppFriend app, string className);
static WindowControl WaitForIdentifyFromWindowClass(WindowsAppFriend app, string className);
static WindowControl WaitForIdentifyFromWindowClass(WindowsAppFriend app, string className, Async async);

検索系の取得メソッドはそれぞれ4づつありますね。

メソッド 説明
GetFromXXX 指定の条件のウィンドウを全部取得する。
IdentifyFromXXX 指定の条件のウィンドウが一つだけであれば取得する。存在しなかったり、複数存在すれば例外発生。
WaitForIdentifyFromXXX 指定の条件のウィンドウが一つだけ表示されるまで無限待ち。
WaitForIdentifyFromXXX(Asyncあり) 指定の条件のウィンドウが一つだけ表示されるか、引数で渡したAsyncオブジェクトが完了するまで無限待ち。

WaitForIdentifyFromXXXがお勧めですね。
マルチプロセスプログラミングですので、確実に期待のWindowが出るまで待つことが重要ですね。無限待ちになっていますが、気にしないでください。タイムアウトはまた別途テスト全体にかければ良いのです(そのうちハンズオンでやります。)一つ一つ気にしてたら変なコードになってしまいます。ここでは、「このシナリオなら絶対にこのWindowが出るはずだ!」という感じで書きましょう。

子ウィンドウ取得

いくつか用意していますが、多くはネイティブ用ですね。
.Net用もありますが、.Netの場合はフィールドで取得するのがお勧めです。

・Windowテキストから取得

WindowControl[] GetFromWindowText(string text);
WindowControl IdentifyFromWindowText(string text);

・Windowクラス名称から取得

WindowControl[] GetFromWindowClass(string className);
WindowControl IdentifyFromWindowClass(string className);

・座標から取得

WindowControl[] GetFromBounds(int x, int y, int width, int height);
WindowControl IdentifyFromBounds(int x, int y, int width, int height);

・ダイアログIDから取得

WindowControl IdentifyFromDialogId(params int[] id);

・Zインデックスから取得

WindowControl IdentifyFromZIndex(params int[] zindex);

・.Netのタイプフルネームから取得

AppVar[] GetFromTypeFullName(string typeFullName);
AppVar IdentifyFromTypeFullName(string typeFullName);

・ロジカルツリーインデックスから取得

AppVar IdentifyFromLogicalTreeIndex(params int[] logicalTreeIndex);

・ヴィジュアルツリーインデックスから取得

AppVar IdentifyFromVisualTreeIndex(params int[] visualTreeIndex);

これもGetFromXXXとIdentifyFromXXXがありますね。トップレベルウィンドウの時と同じです。IdentifyFromXXXしかないのもありますね。これは複数とれるはずないものです。

メソッド 説明
GetFromXXX 指定の条件のウィンドウを全部取得する。
IdentifyFromXXX 指定の条件のウィンドウが一つだけであれば取得する。存在しなかったり、複数存在すれば例外発生。

モーダルウィンドウ取得

これ凄く重要です。
そして非常によく使います。

WindowControl WaitForNextModal();
WindowControl WaitForNextModal(Async async);

これもトップレベルウィンドウ取得の一つと言えるのですが、タイミング依存が発生しないように細心の注意を払う必要があります。と言うのはモーダルが表示されるケースは非同期処理を実行する必要があるので、ミスが発生しやすいのです。でも、メソッドを使っておけば、よほど変な実装をしたアプリケーションでない限り、正しくモーダルを取得できます。
後でハンズオンで詳しくやりますが、こんな感じです。

var w1 = WindowControl.FromZTop(app);
var button = new FormsButton(w1.Dynamic()._buttonOK);

//ボタンを押すとモーダルダイアログが出る
//そのため非同期で実行
var async = new Async();
button.EmulateClick(async));

//モーダルダイアログ表示を確実に待ち合わせて取得する
var modal = w1.WaitForNextModal();

破棄待ち合わせ

void WaitForDestroy();
void WaitForDestroy(Async async);

連続でモーダルダイアログが表示される場合に使います。
今はそんなのあるんだーくらいで。

.Netのオブジェクト操作

対象のウィンドウが.Netのオブジェクトの場合はそれが使えるようになっています。

WindowsAppFriend App { get; }
AppVar AppVar { get; }
FriendlyOperation this[string operation] { get; }
FriendlyOperation this[string operation, Async async] { get; }
FriendlyOperation this[string operation, OperationTypeInfo operationTypeInfo] { get; }
FriendlyOperation this[string operation, OperationTypeInfo operationTypeInfo, Async async] { get; }
//拡張
dynamic Dynamic();

Window操作

WindowsAPIのラッパーですね。
特徴的なのは対象プロセスで実行させることですね。
まあ、それほどは使わないです。

bool IsWindow();
IntPtr SetFocus();
IntPtr SendMessage(int message, IntPtr wparam, IntPtr lparam);
AppVar SendMessage(int message, IntPtr wparam, IntPtr lparam, Async async);
void SequentialMessage(params MessageInfo[] info);
void SequentialMessage(Async async, params MessageInfo[] info);

情報取得

Window情報が取得できます。

int DialogId { get; }
IntPtr Handle { get; }
WindowControl ParentWindow { get; }
string TypeFullName { get; }
string WindowClassName { get; }

その他

void Refresh();
bool AutoRefresh { get; set; }

重要度が下がる部分はサラッといきましたが

APIリファレンスも参照お願いします。
WindowControl - 株式会社Codeer (コーディア)

重要なのは、次からのハンズオンで重点的にやります。