💫

vscode拡張機能でGUIベースのアプリ配置ツールを作るための機能名

2024/08/07に公開

vscodeでアプリの配置を変更する拡張機能を作ろうと思ったときにAIに質問した内容をメモとして残します
(動作未確認を含む)

要件
・macOS上で機能するvscode拡張機能を提供する
・web版のvscodeで動作する
・HTMLベースのGUIを実装する
・.pycetra.mdのファイルを開いたとき、自動的にGUIを開く
・.pycetra.mdのファイルを開いたとき、そのファイルのデータをGUIに表示する
・GUI側からvscode拡張側へデータを送る
・起動しているアプリの座標を取得する
・起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
・アプリの使用中に、ターミナルを開かない

技術選定
・GUIのe2eのテストをするためのライブラリについて

アプリのGUIとしてweb viewを使用

・要件:HTMLベースのGUIを実装する

const panel = vscode.window.createWebviewPanel

web版のvscodeでアプリが動くようにする設定

・要件: web版のvscodeで動作する
package.json

"browser": "./dist/extension.js",

テキストドキュメントが開かれたときに発生するイベント

・要件:.pycetra.mdのファイルを開いたとき、自動的にGUIを開く

vscode.workspace.onDidOpenTextDocument

vscode拡張側からGUI側へデータを送る

・要件:.pycetra.mdのファイルを開いたとき、そのファイルのデータをGUIに表示する

const panel = vscode.window.createWebviewPanel
panel.webview.postMessage

apple scriptの結果をpythonへ渡す

・要件:起動しているアプリの座標を取得する
・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する

const { exec } = require('child_process');

async function runAppleScript(scriptPath) {
    return new Promise((resolve, reject) => {
        exec(`osascript ${scriptPath}`, (error, stdout, stderr) => {
            if (error) {
                reject(`エラー: ${error.message}`);
            } else if (stderr) {
                reject(`標準エラー: ${stderr}`);
            } else {
                resolve(stdout.trim());
            }
        });
    });
}

async function runPythonScript(scriptPath, arg) {
    return new Promise((resolve, reject) => {
        exec(`python ${scriptPath} "${arg}"`, (error, stdout, stderr) => {
            if (error) {
                reject(`エラー: ${error.message}`);
            } else if (stderr) {
                reject(`標準エラー: ${stderr}`);
            } else {
                resolve(stdout.trim());
            }
        });
    });
}


const appleScriptResult = await runAppleScript('path/to/your/example.scpt');
console.log(`AppleScript Result: ${appleScriptResult}`);

const pythonResult = await runPythonScript('path/to/your/script.py', appleScriptResult);
console.log(`Python Script Result: ${pythonResult}`);

(Pythonの処理の中でアプリの座標を計算して座標を更新する。)

ファイルの内容を同期的に読み取る

fs.readFileSync

macで特定のアプリを移動する

・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
例: Finder

osascript -e 'tell application "Finder" to set bounds of window 3 to {100, 100, 800, 600}'

起動中のアプリ名を取得

・要件:起動しているアプリの座標を取得する

例1: すべてのアプリ

osascript -e 'tell application "System Events" to get {name} of (every process whose background only is false)'


{name} のみでなく、{name, index}など複数の情報にするとアクセシビリティが必要になってしまうためNG

例2: Finder

osascript -e 'tell application "Finder" to get {index, name} of windows'

操作スペースをまたいでアプリを移動

・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
操作スペース(Spaces)を跨いで移動する機能をosascriptに見つけられなかった。
他のツールですが、yabaiを使うとアプリをスペースを跨いで移動させることができました。

brew install koekeishiya/formulae/yabai
brew services start yabai
brew install jq

// yabaiへアクセシビリティを許可しておく
yabai -m query --windows | jq -r '.[] | select(.app == "Finder") | .id' | xargs -I{} yabai -m window {} --space 3

ターミナルを開かずにコマンドを実行

・要件:アプリの使用中に、ターミナルを開かない

const { execSync, exec } = require('child_process');

web view側からvscode側へpostMessage

・要件:GUI側からvscode拡張側へデータを送る

document.addEventListener('DOMContentLoaded', (event) => {
    vscode.postMessage({
        command: 'init_message',
    });
});

GUI の E2E テストを行うためのライブラリについて

  • 目的
    1. VSCode 拡張の操作から始まり、WebView 上の操作までを一連の流れとしてテストしたい。
    2. ローカルのファイルへアクセスできること。
    3. yabai コマンドの実行を含む macOS 固有の外部処理もテストしたい。

VSCode 拡張 + WebView E2E テスト 構成案 3 選

VSCode 拡張で下記 3 つの要素をテストする場合に考えられる代表的な 3 つのパターンを表にまとめました。

  1. (1) VSCode API のテスト
  2. (2) WebView 内部の E2E テスト
  3. (3) yabai コマンドやローカルファイル操作など外部処理
構成案 対応ステップ 使用技術 メリット デメリット
案1: (1 と 2 のみ)
vscode-test-web + Playwright
(1) VSCode 拡張 API
(2) WebView のみ対応
- vscode-test-web
- Playwright
- ブラウザ上で VSCode 拡張を動かしつつ WebView の DOM 操作が可能
- セットアップが比較的軽量で、Playwright によるテストコードも書きやすい
- (3) にあたる macOS 特有の yabai コマンドなどローカル依存処理は動かせない
- Web 版 VSCode は機能制限があり、ネイティブの VSCode とは挙動が異なるケースあり
案2: (1,2,3 フルカバー)
vscode-test + Playwright + Node.js exec
(1) VSCode 拡張 API
(2) WebView
(3) 外部処理
- vscode-test
- Playwright
- Node.js の exec / execSync
- ローカルで Electron 版 VS Code を起動し、拡張機能テストと WebView E2E テストを一括管理できる
- Node.js を経由して yabai コマンドやファイル操作なども実行可能
- VS Code 全体を外部ブラウザから操作するための公式サポートがなく、仕組みが非常に複雑
- WebView パネルの URL を取得し、Playwright と連携させる実装が必要
- テスト実行時に VS Code を起動しっぱなしにするため、環境構築やメンテナンスのコストが高い
- バージョンアップや Electron の変更で動かなくなるリスクもある
案3: (1,2,3 可能・ただしホットリロード不可)
Open VSCode Server + Playwright
(1) VSCode 拡張 API
(2) WebView
(3) 外部処理
- open vscode server
- Playwright
- サーバ上で「本物の」VS Code インスタンスを起動するため (3) の yabai やローカルアクセスも実施可能
- ブラウザから Playwright を使って一括で E2E テストを実行できる
- ホットリロードが難しく、拡張の修正ごとに再起動が必要

・案2はAPI制限のないvscode-testの実行のためとても魅力的でしたが、
vscode内のwebviewをPlaywrightから操作する方法を見つけることができず断念しました。

・現状案1(vscode-test-web + Playwright)をメインにこぼれた機能のテストを案3(Open VSCode Server + Playwrightでカバーする形


補足

  • (1) VSCode API のテスト
    拡張機能のアクティベーションやコマンド実行、vscode.workspacevscode.window などを利用する機能の検証。

  • (2) WebView 内部 E2E テスト
    vscode.window.createWebviewPanel で生成される WebView 内にロードした HTML/JS の動作を検証。
    画面遷移やフォーム入力、DOM 検証などを Playwright / Puppeteer / Cypress などで実行。

  • (3) yabai やファイル操作を含む外部処理
    macOS 固有のウィンドウ管理ツール yabaifs モジュールによるファイル操作など、OS やローカル環境に依存する処理。


まとめ

  • 案 3 は (1) と (2) に加えて (3) のローカル依存処理まで一括で検証できる点が魅力です。しかし VS Code 全体を外部ツールで操作するための公式サポートが無く、非常に高度な連携実装が必要 で、バージョンアップの影響も受けやすいなど、運用コストは高めです。
  • Web 版 VSCode を利用する 案 1案 5 は機能制限がある一方、ブラウザベースで動かせる軽快さがメリットです。
  • 案 2案 4 のように VSCode サーバ/ローカル環境にフォーカスする方法もありますが、それぞれホットリロード不可やセットアップの複雑さなどのデメリットがあります。

上記を踏まえ、どのステップ (1)(2)(3) をどこまで網羅したいか、また 開発・テスト環境をどこまで複雑に許容できるか を考慮しながら構成を選択するとよいでしょう。

キーワード

  • ウィンドウマネージャ: yabai, Amethyst, xmonad
  • ステージマネージャ: macOS Venturaの組み込みのウィンドウマネージャ
  • 画面分割ツール: ShiftIt, Spectacle, Magnet, BetterSnapTool
GitHubで編集を提案

Discussion