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など) に許可があること
インタプリタの起動
導入部分は次の記事が詳しいので参考にしてください。
とりあえず今回はコンソールからインタラクティブモードで実行させます。
$ osascript -l JavaScript -i
インタプリタ上で次のようにするだけで、AppStoreアプリが起動します。
>> Application("App Store").activate() => true
最小化ボタンのクリック
AppStore.appではapp.buttons[1]
が最小化ボタンのようだったので、試しにスクリプトで最小化ボタンをクリックさせてみました。
>> 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要素のツリー内容がダンプされます。このコマンドで操作したい対象の見当をつけるとよいでしょう。
>> 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()
してみます。
>> Application("System Events").applicationProcesses.byName("App Store").windows.byName("App Store").toolbars.at(0).groups.at(4).radioButtons.at(0).description() => "Purchases"
Purchases
なので、これをクリックさせてみましょう。
>> Application("System Events").applicationProcesses.byName("App Store").windows.byName("App Store").toolbars.at(0).groups.at(4).radioButtons.at(0).click()
ちなみに、上記の式は次の式と等価です。
>> app.toolbars[0].groups[4].radioButtons[0].click()
これで、「購入済み」タブに移動して、例えば次のような購入済みのアプリの一覧が表示されるはずです。
Installボタンのクリック
「購入済み」タブで、再度app.entireContents()
してみます。
>> 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"), :
適当にアプリ名を取ってみましょう。
>> 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)
あたりの要素をクリックしてもうまくいかなかったので、アプリ詳細ページに飛ばして、そこでインストールすることにしました。
>> 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é")
移動後に、
>> 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()
で数秒待ったりしてやって、できたスクリプトが次の通りです。
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に怒られちゃうかもしれない
(おまけ) アプリの検索
検索フィールドの文字列を取ったり、
>> app.toolbars.at(0).groups.at(6).textFields.at(0).value() => "foobar" // 検索フィールドにてきとうに入れてみた文字列
検索フィールドに文字列を入れたり、
>> app.toolbars.at(0).groups.at(6).textFields.at(0).value = "newValue!!" => "newValue!!"
検索フィールドの文字列で検索できたりします。
>> 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 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。