OS X 10.10からJavaScript for Automation (通称 JXA)が導入されて、オートメーションのためのスクリプトがより書きやすくなりました。
そのJXAを使って、「AppStore.appを起動させてから、購入済みのアプリをInstalllさせる」というスクリプトを書いてみました。
その結果が次の地味なgifです。購入済みリストからMouseposéを探しだしてインストールさせています。
JXAスクリプトとChefやAnsibleなんかを組み合わせると、自動でストアアプリも入れられるようになる…かもしれません。
前提
- OS X 10.10 以降であること
- AppStore.appが特定のApple IDでサインイン済みであること
- Accessibilityでスクリプトを実行するアプリケーション (Terminal.appやScript Editor.appなど) に許可があること
インタプリタの起動
導入部分は次の記事が詳しいので参考にしてください。
とりあえず今回はコンソールからインタラクティブモードで実行させます。
1 | $ osascript -l JavaScript -i |
インタプリタ上で次のようにするだけで、AppStoreアプリが起動します。
1 2 | >> Application( "App Store" ).activate() => true |
最小化ボタンのクリック
AppStore.appではapp.buttons[1]
が最小化ボタンのようだったので、試しにスクリプトで最小化ボタンをクリックさせてみました。
1 2 3 4 5 6 7 8 | >> system = Application( "System Events" ) => Application( "System Events" ) >> app = system.processes[ "App Store" ].windows[ "App Store" ] => Application( "System Events" ).processes.byName( "App Store" ).windows.byName( "App Store" ) >> app.buttons[1].description() => "minimize button" >> app.buttons[1].click() => Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).buttons.at(1) |
Application('System Events')
はUIオートメーションのためのアプリケーションで、このアプリから他のプロセスのウィンドウを触りにいっています。
UI要素の列挙
app.entireContents()
でUI要素のツリー内容がダンプされます。このコマンドで操作したい対象の見当をつけるとよいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >> app.entireContents() # ↓ 実際は改行や整列なし => [Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(0).groups.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(0).groups.at(0).buttons.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(0).groups.at(0).buttons.at(1), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(1), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(1).radioButtons.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(2), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(2).radioButtons.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(3), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(3).radioButtons.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(4), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(4).radioButtons.at(0), Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(5), : |
なお、properties()
やattributes()
で、オブジェクトの属性などの列挙もできるようです。
- app.properties()
- app.attributes()
Purchasesボタンのクリック
上のタブから「購入済み」ボタンをクリックさせてみます。先ほどのダンプからそれっぽいものを見つけて、description()
してみます。
1 2 | >> Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(4).radioButtons.at(0).description() => "Purchases" |
Purchases
なので、これをクリックさせてみましょう。
1 | >> Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).toolbars.at(0).groups.at(4).radioButtons.at(0).click() |
ちなみに、上記の式は次の式と等価です。
1 | >> app.toolbars[0].groups[4].radioButtons[0].click() |
これで、「購入済み」タブに移動して、例えば次のような購入済みのアプリの一覧が表示されるはずです。
Installボタンのクリック
「購入済み」タブで、再度app.entireContents()
してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | >> app.entireContents() => [... app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).uiElements.byName( "Mouseposé" ), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).uiElements.byName( "Mouseposé" ).groups.at(0), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).uiElements.byName( "Mouseposé" ).groups.at(0).images.at(0), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.byName( "Mouseposé" ), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.byName( "Mouseposé" ).uiElements.byName( "Mouseposé" ), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.byName( "Mouseposé" ).uiElements.byName( "Mouseposé" ).staticTexts.byName( "Mouseposé" ), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(1), app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(1).staticTexts.byName( "Boinx Software" ), : |
適当にアプリ名を取ってみましょう。
1 2 | >> app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.name()[0] => "Mouseposé" |
このリストから、直でボタンクリックしたかったのですが、columns.at(0).uiElements.at(8)
あたりの要素をクリックしてもうまくいかなかったので、アプリ詳細ページに飛ばして、そこでインストールすることにしました。
1 2 | >> app.groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.byName( "Mouseposé" ).uiElements.byName( "Mouseposé" ).staticTexts.byName( "Mouseposé" ).click() => Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).tables.at(0).columns.at(0).uiElements.at(8).lists.at(0).groups.at(0).uiElements.byName( "Mouseposé" ).uiElements.byName( "Mouseposé" ).staticTexts.byName( "Mouseposé" ) |
移動後に、
1 2 3 4 | >> Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).groups.at(0).groups.at(0).buttons.at(0).description() => "Install, Mouseposé, Free" >> Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).groups.at(0).groups.at(0).buttons.at(0).click() => Application( "System Events" ).applicationProcesses.byName( "App Store" ).windows.byName( "App Store" ).groups.at(0).groups.at(0).scrollAreas.at(0).uiElements.at(0).groups.at(0).groups.at(0).buttons.at(0) |
でインストールすることができました。
まとめ
上記の結果をまとめて関数化してみました。
通信状態によって、画面遷移に時間がかかることがあるので、てきとうにdelay()
で数秒待ったりしてやって、できたスクリプトが次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function installFromAppStore(targetAppName) { var AppStoreApp = "App Store" var app = Application( "System Events" ).processes[AppStoreApp].windows[AppStoreApp] Application(AppStoreApp).activate() delay(3) app.toolbars[0].groups[4].radioButtons[0].click() delay(3) var tableRows = app.groups[0].groups[0].scrollAreas[0].uiElements[0].tables[0].columns[0].uiElements var len = tableRows.length for ( var i = 1; i < len; ++i) { var elem = tableRows[i].lists[0].groups[0].uiElements var name = elem.name()[0] if (name === targetAppName) { elem.byName(targetAppName).uiElements.byName(targetAppName).staticTexts.byName(targetAppName).click() delay(3) app.groups[0].groups[0].scrollAreas[0].uiElements[0].groups[0].groups[0].buttons[0].click() delay(3) break } } } installFromAppStore( "Mouseposé" ) |
問題点
上記のスクリプトでストアアプリをインストールすることはできましたが、今のところ次のような問題があります。
- 現状、最大1つしかインストールできない (同時にやって大丈夫かどうか不明)
- インストール完了を知る術がない
- AppStoreのレイアウトが変わるたびに追従させる必要がある
- インストール自動化したりしたら、Appleに怒られちゃうかもしれない
(おまけ) アプリの検索
検索フィールドの文字列を取ったり、
1 2 | >> app.toolbars.at(0).groups.at(6).textFields.at(0).value() => "foobar" // 検索フィールドにてきとうに入れてみた文字列 |
検索フィールドに文字列を入れたり、
1 2 | >> app.toolbars.at(0).groups.at(6).textFields.at(0).value = "newValue!!" => "newValue!!" |
検索フィールドの文字列で検索できたりします。
1 | >> app.toolbars.at(0).groups.at(6).textFields.at(0).buttons.at(0).click() |
関連リンク
- 知らないうちにMacがシステム標準でJavaScriptで操作できるようになってた (JXA) - Qiita
- JavaScript for Automation Release Notes
- Getting Started with JavaScript for Automation on Yosemite – MacStories
- Home · dtinth/JXA-Cookbook Wiki · GitHub
- JavaScript for OSX Automation AppleScriptの代替をJavaScriptでやるサンプル Advent Calendar 2014 - Qiita
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。