はじめに
CI/CDでE2Eテストの結果スクリーンショットをGitHub Issueに自動投稿したい。
そんな単純な要件なのに、実現しようとすると意外な壁にぶつかりました。GitHubにはIssueコメントに画像をアップロードするAPIが存在しないのです。
Web UIでは普通にドラッグ&ドロップで画像を貼り付けられるのに、APIからはできない。この制限を補うために作ったのが gh-attach です。
作ったもの
gh-attach は、GitHub Issue/PRのコメントに画像をアップロードするCLIツールです。
# 基本的な使い方
gh-attach --issue 123 --image ./screenshot.png
# コメント本文と一緒に
gh-attach --issue 123 --image ./e2e.png --body "E2E test result:"
# 複数画像をアップロード
gh-attach --issue 123
--image ./before.png
--image ./after.png
--body 'Before: <!-- gh-attach:IMAGE:1 -->
After: <!-- gh-attach:IMAGE:2 -->'
なぜAPIで画像をアップロードできないのか
GitHubのIssueコメントAPIは body パラメータにMarkdownテキストを受け取るだけで、ファイルをアップロードする機能がありません。
Web UIで画像を貼り付けると https://user-images.githubusercontent.com/... のようなURLに変換されますが、このアップロード先 uploads.github.com はOAuthトークンでの認証を受け付けず、ブラウザのセッションCookieが必要です。
つまり、公式にはAPI経由での画像アップロードはサポートされていないのです。
3つのアップロードモード
gh-attach には用途に応じた3つのアップロードモードがあります。
Browser mode(デフォルト)
APIがダメならブラウザを使えばいい。playwright-cli でブラウザを自動操作する方法です。
- プレースホルダー付きのコメントをAPIで作成
- playwright-cliでGitHubをブラウザで開く
- コメントの編集画面で画像をアップロード
- アップロードされた画像URLを取得
- APIでコメントを更新し、プレースホルダーを画像に置換
ヘッドレスモードで動作するため、CI環境でも使えます。初回のみ --headed オプションでブラウザを表示してGitHubにログインすれば、以降はセッションが保持されます。
Release mode(--release)
ブラウザ不要で動作するモードです。GitHub Releasesに画像をアップロードし、そのダウンロードURLを使います。
gh-attach --issue 123 --image ./screenshot.png --release
playwright-cliのセットアップが不要なため、CI環境での導入が手軽です。
Direct mode(GHE向け)
GitHub Enterpriseの upload/policies APIを利用するモードです。ブラウザでポリシーを取得し、curlでファイルをアップロードすることで user-attachments URLを生成します。
設定ファイル(~/.config/gh-attach/config)に対象ホストを登録すると自動で有効になります。
direct_hosts=your-ghe-host.com
gh-attach --issue 123 --image ./screenshot.png --host your-ghe-host.com --repo owner/repo
Release modeと違い、リリースアセットを汚さずに user-attachments URLが得られるのが利点です。
技術的なポイント
プレースホルダーによる画像位置の制御
複数画像をアップロードする際、どこに画像を挿入するかを制御できるようにしました。
テスト結果:
<!-- gh-attach:IMAGE:1 -->
修正後:
<!-- gh-attach:IMAGE:2 -->
プレースホルダーがない場合は、コメントの末尾に画像が追加されます。
GitHub Enterpriseサポート
--host オプションでGitHub Enterpriseにも対応しています。ホストを指定しない場合は、現在のリポジトリの設定から自動検出します。
gh-attach --issue 123 --image ./result.png --host github.mycompany.com
画像サイズの固定
アップロードされた画像は <img> タグで挿入され、デフォルトで幅800pxに固定されます。--width オプションで変更可能です。
Direct mode の仕組み
Direct modeは、GitHubのファイルアップロードの内部APIを逆解析して実現しました。
- playwright-cliでIssueページを開く
file-attachmentWeb Componentのattach()を呼び出してアップロードポリシーを取得window.fetchをインターセプトしてポリシーデータをキャプチャXMLHttpRequest.prototype.sendをブロックして、コンポーネントによるアップロード実行を防止- 取得したポリシー情報(URL、認証ヘッダー、フォームデータ)を使い、curlで実ファイルをアップロード
- レスポンスから
user-attachmentsURLを取得
ブラウザ内のJavaScript実行とcurlを組み合わせたハイブリッドなアプローチです。
インストール
Homebrewでインストールできます。
brew tap atani/tap
brew install gh-attach
# 初回はブラウザでGitHubにログイン
gh-attach --issue 1 --image ./test.png --headed
gh extensionとしてもインストールできます。
gh extension install atani/gh-attach
gh attach --issue 123 --image ./screenshot.png
おわりに
「APIにない機能はブラウザ自動化で補完する」という力技ですが、実用上は問題なく動作しています。
ブラウザ自動化は自身のアカウントでの操作に限定し、過度なリクエストを避けるなど、GitHubの利用規約に配慮した使い方を心がけてください。
CI/CDでのスクリーンショット投稿、テスト結果の可視化など、自動化の幅が広がるツールになりました。
リンク
- GitHub: https://github.com/atani/gh-attach
- Homebrew Formula: https://github.com/atani/homebrew-tap
