はじめに
GitHub Actionsを使って開発していると、いつの間にか無料枠が減っていた、という経験はありませんか?
私も個人開発で、まさに同じ問題に直面しました。
開発中のアプリでは、コードを変更するたびにGitHub Actionsでテストを実行していました。
最初は便利だったのですが、機能が増えるにつれてテスト時間も長くなり、気がつくと月間の無料枠をほぼ使い切っていました。
2026年10月9日時点で確認した使用量は、2,000分のうち1,806分。無料枠の約90%を消費している状態でした。
とはいえ、コストを抑えるためにテストを削減してしまうと、不具合を見逃す可能性があります。
そこで今回は、次の方法で解決することにしました。
GitHub Actionsは手動起動にし、普段のテストはWindows PCで実行する。
この記事では、実際に行った設定変更と、ローカル検証の仕組みについて紹介します。
今回の解決方法の全体像
まずは、今回行った変更を図で確認します。
変更前と変更後
今回のポイントは、テスト自体を減らさず、テストを実行する場所とタイミングを変更したことです。
さらに、ローカル検証を使いやすくするため、以下の3ステップで環境を整えました。
- GitHub Actionsの自動実行を停止する
- Windowsでテストをまとめて実行できるようにする
- テスト結果を保存・管理する仕組みを追加する
順番に説明していきます。
Step 1. GitHub Actionsの自動実行を停止する
まずは、GitHub Actionsについて
GitHub Actionsとは、GitHub上でビルドやテストなどの処理を自動実行できるサービスです。
例えば、Pull Requestが作成された際に、次のような処理を自動実行できます。
- コードの静的解析
- TypeScriptの型チェック
- 単体テスト
- E2Eテスト
- アプリケーションのビルド
このように、コードの変更に合わせて自動的に検証する仕組みは、CI(Continuous Integration:継続的インテグレーション)と呼ばれます。
非常に便利な仕組みですが、PrivateリポジトリでGitHubが提供するrunnerを使用する場合、プランに応じた無料利用枠があります。
GitHub Freeの場合、標準runnerの無料枠は月間2,000分です。
なお、Publicリポジトリの標準GitHub-hosted runnerは、通常無料で利用できます。
何が問題だったのか?
私の開発環境では、Pull Requestの更新に伴ってテストが自動実行されていました。
ところが、テストが失敗すると、
コードを変更
↓
GitHub Actionsで検証
↓
テスト失敗
↓
コードを修正
↓
GitHub Actionsで再検証
という流れが繰り返されます。
開発中はこのサイクルを何度も回すため、少しずつ無料枠を消費していました。
CIの履歴には、1回の実行で約16〜24分かかったものもありました。
そこで、まずはCIの自動起動を停止することにしました。
workflow_dispatchを設定する
GitHub Actionsの実行条件は、.github/workflows/以下のYAMLファイルで指定します。
今回は、既存のCI設定を残したまま、実行条件を次のように変更しました。
name: CI
on:
workflow_dispatch:
permissions:
contents: read
ここで使う技術:workflow_dispatchとは?
workflow_dispatchは、GitHub Actionsのワークフローを手動で実行できるようにするイベントです。
例えば、次のような設定では、コードがpushされた際にワークフローが起動します。
on:
push:
一方、次の設定では、手動起動だけが可能になります。
on:
workflow_dispatch:
この設定に変更することで、コードを更新するたびに対象のCIが自動起動することを防げます。
ただし、手動で実行した場合もGitHub Actionsの利用枠は消費します。
また、他のワークフローに自動起動の設定が残っている場合は、そちらも別途確認が必要です。
注意: GitHubのブランチ保護でCIの成功を必須条件にしている場合は、ワークフローを停止する前に運用を確認してください。必須チェックを迂回してマージすることは避けます。
これで、GitHub Actionsの自動実行を停止できました。
しかし、このままではコードを変更しても自動検証されません。
そこで次に、Windowsでテストを実行できる環境を用意します。
Step 2. Windowsでテストを実行する
使用した開発環境
今回対象としたのは、Next.jsで開発している家具配置アプリです。
| 項目 | 使用技術 |
|---|---|
| OS | Windows |
| Node.js | 24 |
| フレームワーク | Next.js 16 |
| UI | React 19 |
| 言語 | TypeScript |
| 静的解析 | ESLint |
| 単体テスト | Vitest |
| E2Eテスト | Playwright |
| iOS向け構成 | Capacitor 8 |
これらのテストをWindows上で実行するように変更しました。
まずは、必要な環境を準備する
PowerShellを開き、Node.jsとGitが利用できるか確認します。
node --version
git --version
続いて、依存関係をインストールします。
npm.cmd ci
ここで使う技術:npm ciとは?
npm ciは、package-lock.jsonに記録された依存関係を基に、パッケージをインストールするコマンドです。
一般的なnpm installとは異なり、ロックファイルとの整合性を重視したインストールを行います。
そのため、テスト環境を再現する場合に適しています。
今回は、ロックファイルが変更された場合などにnpm ciを実行する方針にしました。
続いて、E2Eテストで使用するブラウザを準備します。
npx.cmd playwright install chromium webkit
ここで使う技術:VitestとPlaywrightの違い
今回のプロジェクトでは、主に2種類のテストツールを使用しています。
Vitest(単体テスト)
関数やコンポーネントなど、プログラムの個々の機能が正しく動くかを確認します。
例えば、家具の座標を計算する関数に値を入力し、期待どおりの位置が返るか検証します。
Playwright(E2Eテスト)
実際のブラウザを操作し、ユーザーの操作が正しく機能するかを確認します。
例えば、家具を追加して位置を変更し、その状態を保存できるかを検証します。
この2種類を組み合わせることで、内部の計算処理と、実際の画面操作の両方を確認できます。
テストを一括実行する
当初は、各テストを個別に実行する必要がありました。
npm run lint
npm run typecheck
npm test
npm run build
npm run test:e2e
しかし、毎回これらを入力するのは面倒です。
そこで、Node.jsの検証スクリプトを作成し、一つのコマンドでテストを実行できるようにしました。
package.jsonには次のスクリプトを追加しています。
{
"scripts": {
"verify:local": "node scripts/verify-local.mjs",
"verify:local:quick": "node scripts/verify-local.mjs --quick",
"verify:local:status": "node scripts/verify-local.mjs --status",
"verify:local:errors": "node scripts/verify-local.mjs --errors"
}
}
※実際のpackage.jsonから、今回説明するスクリプトだけを抜粋しています。
これにより、次のコマンドで全検証を実行できます。
npm.cmd run verify:local
このコマンドでは、依存関係の監査からLint、型チェック、単体テスト、Webビルド、E2Eテストまで、計10ステージを順番に実行します。
開発中と最終確認で実行範囲を分ける
ただし、全テストを毎回実行するのは時間がかかります。
そこで、開発中の確認用にQuickモードを追加しました。
npm.cmd run verify:local:quick
2つのモードの違いは次のとおりです。
| 検証内容 | Quick | Full |
|---|---|---|
| 依存関係の監査 | ○ | ○ |
| Lint | ○ | ○ |
| 型チェック | ○ | ○ |
| 単体テスト | ○ | ○ |
| 検証ツール・iOS設定の静的検査 | ○ | ○ |
| Webビルド | ― | ○ |
| Web E2Eテスト | ― | ○ |
| 静的Webビルド・E2E | ― | ○ |
開発中はQuickモードで確認し、修正が完了したらFullモードで全テストを実行します。
これにより、開発中の待ち時間を減らしながら、最終確認では必要なテストを省略しない構成にできました。
なお、Quick検証では最終的な動作を保証できないため、Full検証の代用にはしていません。
Step 3. テスト結果を保存・管理する
ローカルでテストを実行できるようになりましたが、ここで新たな問題がありました。
それは、テストが成功したという記録だけでは、現在のコードが正しいか判断できないことです。
例えば、次のような状況を考えます。
① テスト成功
↓
② コードを変更
↓
③ テストは再実行しない
↓
④ 古い成功結果を確認
この場合、変更後のコードが正しいとは限りません。
そこで、検証結果とソースコードを対応付ける仕組みを追加しました。
ここで使う技術:SHA-256とは?
SHA-256は、入力データから256ビットのハッシュ値を計算するアルゴリズムです。
ハッシュ値は、データの内容が変更されたかどうかを確認する用途などに利用できます。
今回のスクリプトでは、Git管理対象ファイルと、Gitに除外されていない新規ファイルの内容からフィンガープリントを生成します。
そして、テストを実行した時点と現在のフィンガープリントを比較します。
実装では、次のような判定を行っています。
export function verificationStatus(report, snapshot) {
if (!report) return "missing";
if (!report.source?.fingerprint) {
return "unknown-source";
}
if (
report.source.fingerprint !== snapshot.fingerprint ||
report.sourceChanged
) {
return "stale";
}
if (report.status === "running") {
return "incomplete";
}
return report.success && report.status === "passed"
? "passed"
: "failed";
}
この処理により、ソースコードが変更された場合は、以前のテストが成功していてもstaleとして扱います。
また、Quick検証の成功結果がFull検証の記録を上書きしないようにしています。
現在の検証結果は、次のコマンドで確認できます。
npm.cmd run verify:local:status
ただし、ハッシュによる判定はコードの対応関係を確認するものです。OSや環境変数、ブラウザなどの変更まで保証するわけではありません。
エラーが出た場合も確認しやすくする
もう一つ工夫したのがログ管理です。
テスト結果の詳細はファイルに保存し、コンソールにはステージごとの要約だけを表示するようにしました。
エラーが発生した場合は、次のコマンドで失敗した箇所を確認できます。
npm.cmd run verify:local:errors
これはテストを再実行するコマンドではありません。
保存済みログから、失敗に関係する情報だけを取り出します。
CodexなどのAIコーディングエージェントを使う場合にも、大量の成功ログを毎回読み込む必要がなくなるため、扱いやすい構成になりました。
実際に試した結果
ここまでの変更を行ったあと、Windows環境でFull検証を実行しました。
開発記録に残っている実測結果は次のとおりです。
| 検証項目 | 結果 |
|---|---|
| 全検証ステージ | 10 / 10成功 |
| 単体テスト | 547件成功 |
| Web E2Eテスト | 205件成功 |
| Web E2Eの既存skip | 18件 |
| 静的Web E2Eテスト | 14件成功 |
| 全検証の所要時間 | 898.1秒(約15分) |
Lint、型チェック、Webビルドなども成功しました。
これにより、既存のWeb関連検証をWindows上で実行できることを確認しました。
ローカル検証そのものが消費するGitHub Actionsの実行時間は0分です。
また、対象のGitHub Actionsを手動起動専用にしたことで、普段のコード更新による自動実行を停止できました。
ただし、変更前後の厳密な費用削減率は計測していません。
また、今回の15分という実測値はローカル環境での結果であり、GitHub Actions上の実行時間と直接比較できるものではありません。
注意点:すべての検証を移行できたわけではない
今回の方法には制限もあります。
WindowsではWeb関連のテストを実行できましたが、次の項目は未検証です。
- XcodeによるiOSアプリのコンパイル
- iOS Simulatorでの動作確認
- iPhone実機での動作確認
- iOSアプリの署名・配布用ビルド
PlaywrightでWebKitのE2Eテストが成功していても、実際のiPhoneで正しく動作するとは限りません。
そのため、GitHub ActionsのmacOS向けジョブは削除せず、今後必要なタイミングで検証を再開できるようにしています。
また、ローカルのテスト結果はGitHub上の必須CIチェックには反映されません。
チーム開発などでCIの成功をマージ条件にしている場合は、この点にも注意が必要です。
まとめ
今回は、GitHub Actionsの無料枠を使い切ったことをきっかけに、Windowsでローカル検証を実行する環境を構築しました。
実施した内容をまとめると、次の3つです。
- GitHub Actionsを手動起動に変更し、自動実行による無料枠の消費を停止
- Quick・Fullの2種類の検証モードを用意し、開発中と最終確認を分離
- ログとソースのハッシュを管理し、古いテスト結果の誤使用を防止
個人開発では、できるだけ費用を抑えたい一方で、テストの品質も維持したいところです。
今回の方法は、GitHub Actionsを完全に置き換えるものではありませんが、日常的な開発を進めるうえでは有効な選択肢になりました。
同じようにGitHub Actionsの利用枠で困っている方の参考になれば幸いです。

