SPFx Webパーツのデプロイを完全自動化する:Claude Code + GitHub Actions + Playwright

はじめに

SPFx(SharePoint Framework)でWebパーツを開発していると、毎回こんな作業が発生しませんか?

  1. gulp bundle --ship でビルド
  2. gulp package-solution --ship でパッケージ化
  3. SharePoint 管理センターを開いてアプリカタログにアップロード
  4. 「信頼して展開」をクリック
  5. 対象サイトを開いてアプリをインストール
  6. ページを開いてWebパーツを追加して動作確認

この手順をすべて Claude Code のカスタムコマンド一発 で自動化します。


全体アーキテクチャ

flowchart TD A[開発者: /deploy-spfx 実行] --> B[Phase 1: ビルド & 検証] B --> B1[npm ci] B --> B2[gulp bundle --ship] B --> B3[Jest ユニットテスト] B --> B4[ESLint 静的解析] B1 & B2 & B3 & B4 --> C[Phase 2: パッケージング] C --> C1[gulp package-solution --ship] C1 --> D[Phase 3: SPO デプロイ] D --> D1[m365 login 認証] D1 --> D2[App Catalog へアップロード] D2 --> D3[アプリ展開 deploy] D3 --> D4[サイトコレクションへインストール] D4 --> E[Phase 4: 動作確認] E --> E1[Playwright ブラウザ起動] E1 --> E2[SharePoint ページナビゲート] E2 --> E3[Webパーツ描画検証] E3 --> E4[スモークテスト実行] E4 --> F{結果} F -->|成功| G[✅ デプロイ完了通知] F -->|失敗| H[❌ ロールバック & エラー通知]

使用ツール

ツール役割
CLI for Microsoft 365 (m365)GUI不要でSPOを操作するCLI
GitHub ActionsCI/CDパイプライン
Claude Code カスタムコマンド/.claude/commands/ によるワンコマンド実行
PlaywrightSPO上のブラウザ動作確認
Azure AD アプリ登録非インタラクティブ認証

Step 1: Azure AD アプリ登録(非インタラクティブ認証の準備)

GUIなしでSPOにデプロイするには、証明書ベースの認証が必要です。

1-1. 証明書の生成

1
2
3
4
5
6
7
8
9
# 自己署名証明書を生成
openssl req -x509 -newkey rsa:2048 -keyout spfx-deploy-key.pem \
  -out spfx-deploy-cert.pem -days 365 -nodes \
  -subj "/CN=spfx-deploy"

# pfx形式に変換(Azureポータルへのアップロード用)
openssl pkcs12 -export -out spfx-deploy.pfx \
  -inkey spfx-deploy-key.pem -in spfx-deploy-cert.pem \
  -passout pass:""

1-2. Azure AD アプリ登録

  1. Azure PortalAzure Active Directoryアプリの登録新規登録
  2. 名前: spfx-deploy-automation、サポートされるアカウントの種類: この組織のディレクトリのみ
  3. 登録後、証明書とシークレット証明書のアップロードspfx-deploy-cert.pem をアップロード

1-3. API アクセス許可の追加

APIアクセス許可の種類権限用途
SharePointアプリケーションSites.FullControl.Allアプリカタログ操作
SharePointアプリケーションAppCatalog.ReadWrite.Allアプリ展開

重要: 追加後、管理者の同意を与える をクリックしてください。

1-4. 必要な情報をメモ

  • テナントID(ディレクトリID)
  • クライアントID(アプリケーションID)
  • 証明書のサムプリント

Step 2: GitHub Secrets の設定

リポジトリの SettingsSecrets and variablesActions で以下を登録します。

AZURE_TENANT_ID          = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID          = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CERT_THUMBPRINT    = ABC123...(証明書のサムプリント)
AZURE_CERT_PFX_B64       = $(base64 -w 0 spfx-deploy.pfx)
SPO_APP_CATALOG_URL      = https://tenant.sharepoint.com/sites/appcatalog
SPO_TARGET_SITE_URL      = https://tenant.sharepoint.com/sites/test

Step 3: GitHub Actions ワークフロー

.github/workflows/spfx-deploy.yml を作成します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
name: SPFx Deploy to SharePoint Online

on:
  push:
    branches: ["main"]
    paths: ["src/**", "sharepoint/**", "package.json"]
  workflow_dispatch:
    inputs:
      target_site:
        description: "デプロイ先サイトURL"
        required: false
        default: ""

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    outputs:
      package-name: ${{ steps.package-info.outputs.name }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm test -- --ci --coverage

      - name: Bundle (production)
        run: npx gulp bundle --ship

      - name: Package solution
        run: npx gulp package-solution --ship

      - name: Get package name
        id: package-info
        run: |
          PKG=$(ls sharepoint/solution/*.sppkg | head -1 | xargs basename)
          echo "name=$PKG" >> $GITHUB_OUTPUT          

      - name: Upload .sppkg artifact
        uses: actions/upload-artifact@v4
        with:
          name: spfx-package
          path: sharepoint/solution/*.sppkg

  deploy-to-spo:
    needs: build-and-test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Download .sppkg artifact
        uses: actions/download-artifact@v4
        with:
          name: spfx-package
          path: ./package

      - name: Install CLI for Microsoft 365
        run: npm install -g @pnp/cli-microsoft365

      - name: Setup certificate
        run: |
          echo "${{ secrets.AZURE_CERT_PFX_B64 }}" | base64 -d > /tmp/deploy.pfx          

      - name: Login to Microsoft 365
        run: |
          m365 login \
            --authType certificate \
            --certificateFile /tmp/deploy.pfx \
            --appId "${{ secrets.AZURE_CLIENT_ID }}" \
            --tenant "${{ secrets.AZURE_TENANT_ID }}"          

      - name: Upload to App Catalog
        id: upload-app
        run: |
          PKG_PATH=$(ls ./package/*.sppkg | head -1)
          APP_ID=$(m365 spo app add \
            --filePath "$PKG_PATH" \
            --appCatalogUrl "${{ secrets.SPO_APP_CATALOG_URL }}" \
            --overwrite \
            --output json | jq -r '.UniqueId')
          echo "app-id=$APP_ID" >> $GITHUB_OUTPUT
          echo "Uploaded app ID: $APP_ID"          

      - name: Deploy app (tenant-wide)
        run: |
          m365 spo app deploy \
            --id "${{ steps.upload-app.outputs.app-id }}" \
            --appCatalogUrl "${{ secrets.SPO_APP_CATALOG_URL }}"          

      - name: Install app to target site
        run: |
          TARGET="${{ github.event.inputs.target_site || secrets.SPO_TARGET_SITE_URL }}"
          m365 spo app install \
            --id "${{ steps.upload-app.outputs.app-id }}" \
            --siteUrl "$TARGET"          

      - name: Cleanup certificate
        if: always()
        run: rm -f /tmp/deploy.pfx

  functional-verify:
    needs: deploy-to-spo
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install test dependencies
        run: |
          npm ci
          npx playwright install chromium --with-deps          

      - name: Run Playwright smoke tests
        env:
          SPO_SITE_URL: ${{ secrets.SPO_TARGET_SITE_URL }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CERT_PFX_B64: ${{ secrets.AZURE_CERT_PFX_B64 }}
        run: npx playwright test --project=chromium e2e/spfx-smoke.spec.ts

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-results
          path: |
            playwright-report/
            test-results/            

Step 4: Playwright でSPO動作確認

認証設定(MSAL対応)

e2e/auth.setup.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { chromium, FullConfig } from "@playwright/test";
import * as msal from "@azure/msal-node";
import * as fs from "fs";

async function globalSetup(config: FullConfig) {
  const pfxBuffer = Buffer.from(process.env.AZURE_CERT_PFX_B64!, "base64");

  const pca = new msal.ConfidentialClientApplication({
    auth: {
      clientId: process.env.AZURE_CLIENT_ID!,
      authority: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}`,
      clientCertificate: {
        thumbprintSha256: process.env.AZURE_CERT_THUMBPRINT!,
        privateKey: pfxBuffer.toString(),
      },
    },
  });

  const result = await pca.acquireTokenByClientCredential({
    scopes: [`${process.env.SPO_SITE_URL}/.default`],
  });

  // アクセストークンをファイルに保存
  fs.writeFileSync("/tmp/spo-token.json", JSON.stringify(result));
}

export default globalSetup;

スモークテスト

e2e/spfx-smoke.spec.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { test, expect } from "@playwright/test";

const SPO_SITE = process.env.SPO_SITE_URL!;
const TEST_PAGE = `${SPO_SITE}/SitePages/webpart-test.aspx`;

test.describe("SPFx Webパーツ動作確認", () => {
  test("Webパーツが正常にレンダリングされる", async ({ page }) => {
    // SharePointページへナビゲート
    await page.goto(TEST_PAGE);

    // Webパーツのコンテナが表示されるまで待機
    await expect(
      page.locator('[data-sp-web-part-id]').first()
    ).toBeVisible({ timeout: 30000 });

    // Webパーツ固有の要素を確認(コンポーネント名に合わせて変更)
    await expect(
      page.locator(".your-webpart-root-class")
    ).toBeVisible();

    // エラーが発生していないことを確認
    const errorBoundary = page.locator('[class*="errorBoundary"]');
    await expect(errorBoundary).not.toBeVisible();
  });

  test("Webパーツのプロパティが機能する", async ({ page }) => {
    await page.goto(TEST_PAGE);

    // 具体的な機能テスト(プロジェクトに合わせてカスタマイズ)
    const webpart = page.locator("[data-sp-web-part-id]").first();
    await expect(webpart).not.toContainText("Error");

    // APIコールが成功しているか確認
    const responses = await page.waitForResponse(
      (resp) => resp.url().includes("/_api/") && resp.status() < 400
    );
    expect(responses).toBeTruthy();
  });
});

Step 5: Claude Code カスタムコマンド

.claude/commands/deploy-spfx.md を作成すると、/deploy-spfx で一発実行できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
SPFxプロジェクトの検証・デプロイ・動作確認を実行してください。

## 実行手順

1. **事前確認**
   - `git status` でコミット済みか確認
   - `package.json` のバージョンをチェック

2. **ビルド & テスト**
   ```bash
   npm ci
   npm run lint
   npm test
   npx gulp bundle --ship
   npx gulp package-solution --ship
  1. デプロイ前確認

    • ビルドエラーがないこと
    • sharepoint/solution/*.sppkg が生成されていること
    • ユーザーに「デプロイしてよいか」確認する
  2. SPO へデプロイ(m365 CLI使用)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    # ログイン状態確認
    m365 status
    
    # App Catalog へアップロード
    m365 spo app add --filePath ./sharepoint/solution/*.sppkg \
      --appCatalogUrl $SPO_APP_CATALOG_URL --overwrite
    
    # アプリを展開
    m365 spo app deploy --name <package-name> \
      --appCatalogUrl $SPO_APP_CATALOG_URL
    
  3. 動作確認

    1
    
    npx playwright test e2e/spfx-smoke.spec.ts --headed
    
  4. 結果報告

    • デプロイ成功/失敗を報告
    • Playwrightのスクリーンショットがあれば確認

---

## Step 6: ローカル開発での m365 CLI セットアップ

```bash
# インストール
npm install -g @pnp/cli-microsoft365

# 対話的ログイン(ローカル開発用)
m365 login

# 現在の接続状態確認
m365 status

# App Catalog 一覧確認
m365 spo app list --appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog

# 手動デプロイの例
m365 spo app add \
  --filePath ./sharepoint/solution/your-webpart.sppkg \
  --appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog \
  --overwrite

m365 spo app deploy \
  --name your-webpart.sppkg \
  --appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog

m365 spo app install \
  --id <app-id> \
  --siteUrl https://tenant.sharepoint.com/sites/test

Step 7: 環境変数の管理

プロジェクトルートに .env.local(gitignore 済み)を作成:

1
2
3
4
5
6
# .env.local
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CERT_THUMBPRINT=ABC123...
SPO_APP_CATALOG_URL=https://tenant.sharepoint.com/sites/appcatalog
SPO_TARGET_SITE_URL=https://tenant.sharepoint.com/sites/test

.gitignore に追加:

.env.local
*.pem
*.pfx
*.key
sharepoint/solution/*.sppkg

トラブルシューティング

m365 login でエラーが出る場合

1
2
3
# トークンキャッシュをクリア
m365 logout
m365 login --authType browser  # ブラウザ認証で再試行

App Catalog が見つからない場合

1
2
# テナントのApp Catalog URLを確認
m365 spo tenant appcatalogurl get

Playwright が認証できない場合

SPOはモダン認証(MSAL)を使用しているため、Cookie ベースの認証が必要です。 playwright-msal などのヘルパーライブラリの活用を検討してください。

GitHub Actions で Sites.FullControl.All が拒否される場合

Azure AD の API アクセス許可で「管理者の同意」が完了しているか確認してください。


まとめ

この構成により、以下がすべて自動化されます:

作業自動化前自動化後
ビルド & テスト手動 gulp コマンドGitHub Actions / /deploy-spfx
アプリカタログへのアップロードブラウザでGUI操作m365 spo app add
「信頼して展開」クリックブラウザでGUI操作m365 spo app deploy
サイトへのインストールブラウザでGUI操作m365 spo app install
動作確認手動でブラウザ確認Playwright スモークテスト

Claude Code の /deploy-spfx コマンドを使えば、ローカル開発中でもワンコマンドで全行程を実行できます。


参考リンク