Open6

XCUITestをダークモードで実行する

kabeyakabeya

iOSの場合、Appの起動前にデバイスに対して設定します。
(起動後でもよいです。その場合、設定したタイミングで変わります)

func testSomethingWithDarkMode() throws{
    XCUIDevice.shared.appearance = .dark // .lightでライトモードになります。
    let app = XCUIApplication()
    app.launch()
    
   // test something
}

launchArgumentsを探したりしたのですが(実際macOSには、-AppleInterfaceStyle Dark、という設定がありそうです)、iOSの場合は上記のようにすればよいようです。

Xcode 14.3、iOS 16.4(シミュレータ)です。

kabeyakabeya

同様に、向きを変えるなら
XCUIDevice.shared.orientation = .landscapeLeft
のように指定します。

ただ、シミュレータの中の表示の中身の向きが変わるだけで、シミュレータ全体は縦のままです。
自動テスト上は問題ないのですが、実行しているさまを眺めるのには首(顔?)をかしげる必要があります。
(事前にシミュレータ自体をランドスケープにしておけば、かしげずに確認できます)

シミュレータ全体の向きをテスト実行中に変える方法は分かりませんでした。

kabeyakabeya

AppStore用のスクリーンショット撮るとなると、同じ画面を日本語・英語などの対応言語数×用意する画面サイズ数、って感じで撮らなくてはなりません。
英語・日本語、iPhone 14 Pro/iPhone 8 Plus/iPad Pro 6th (+iPad Pro 2nd)ってなると、同じ画面やシーンの設定違いを6枚(or 8枚)撮ります。基本的にはその6枚は、同じ設定で同じ操作をした状態にしたいところです。

設定と操作はXCUITestでできるし、そのままスクリーンショットも撮れるから、XCUITest使えばいいじゃん、ってなってます。

で、ダークモードを切り替えたり、向きを変えたりまでは分かりました。
あとは撮ったスクリーンショットに自動で名前をつけて保存したいところですが、その際、上記の6種類を識別できるように命名したい。なるべくテストケース側にそういうのを書くのではなく、テストケース側で勝手にシミュレータの情報を判断して、「(画面名/シーン名)-(iPhone/iPad)-(世代)-(Light/Dark)-(Landscape/Portrait).png」とかって付けて欲しい。

iPhone/iPadの区別はシミュレータでのXCUITest実行時でもテストケース内でUIDevice.current.modelを参照すれば取れることが分かりましたが、iPhone 14 ProかiPhone 8 Plusかが分からない。画面サイズとしてUIScreen.main.nativeBounds.sizeをとっても同じ値(960×1440)が返ってきてしまいます。
あるいはiPad Pro 6thか2ndかが分からない。そもそもこの2つは画面サイズも同じ。

ここまで書いて思いましたが、ハードウェアボタンの有無、ということで分けとけば良い気がしました。

また通常のUITestとはターゲット(UITest Bundle)を分けて管理すると良さそうです。テストはテスト用、スクリーンショットはスクリーンショット用のターゲットに分ける。
テストはやはりすべて一括実行とかしたいですし。

kabeyakabeya

なんでだか、XcodeからiPhone 8/iPhone 8 PlusのXCUITestが実行できません。Building <ターゲット名>...から進まない。

コマンドラインツールであるxcodebuildからはビルド、テスト実行ができます。
iPhone 14 ProとかiPad Pro 6th genとかはXcodeからXCUITestを実行できます。
XCUITestでない普通のデバッグ実行はiPhone 8/8 Plusでもできます。

環境はmacOS 13.3.1/Xcode 14.3/iOS 16.4 (simulator)/M1 Mac Book Proです。

kabeyakabeya

ここまで書いて思いましたが、ハードウェアボタンの有無、ということで分けとけば良い気がしました。

ほんとかよ、という気がしますが、XCUIDevice.shared.hasHardwareButton(.home)は、シミュレータをiPhone 14 Pro Maxで実行するとtrue、iPhone 8 Plusで実行するとfalseを返します。

マジ?

kabeyakabeya

あと、XCUIDevice.shared.appearanceXCUIDevice.shared.orientationはenumなので、そのままファイル名に使えないかなということでString(describing: XCUIDevice.shared.appearance)とやりましたが、ダメですね。
"XCUIDeviceAppearance(rawValue: 0)"みたいな文字列が返ってきてしまいます。
普通のenumの場合は、enumのcase名がそのまま文字列で返ってきます。

@objcというのが指定されてるenumはダメ、というのは色々なところに書いてあるのを見ます。
今回のは、XCUIDevice自体がNSObjectの派生クラスで、そのなかにAppearanceというenumが定義されています。直接は@objcというのは指定されてないように見えます。
とはいえ、包含クラスは明らかにObjective-Cのクラスなので、それが原因かも知れません。詳しいルールはよく分かりません。

コードで比較して、.darkなら"Dark"とかつって、愚直にやります。