Skillshub clerk-ci-integration
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/clerk-ci-integration" ~/.claude/skills/comeonoliver-skillshub-clerk-ci-integration && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/clerk-ci-integration/SKILL.mdsource content
Clerk CI Integration
Overview
Set up CI/CD pipelines with Clerk authentication testing. Covers GitHub Actions workflows, Playwright E2E tests with Clerk auth, test user management, and CI secrets configuration.
Prerequisites
- GitHub repository with Actions enabled
- Clerk test API keys (
/pk_test_
)sk_test_ - npm/pnpm project configured
Instructions
Step 1: GitHub Actions Workflow
# .github/workflows/test.yml name: Test with Clerk Auth on: pull_request: branches: [main] push: branches: [main] jobs: test: runs-on: ubuntu-latest env: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.CLERK_PK_TEST }} CLERK_SECRET_KEY: ${{ secrets.CLERK_SK_TEST }} CLERK_WEBHOOK_SECRET: ${{ secrets.CLERK_WEBHOOK_SECRET_TEST }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - run: npm run build - run: npm test - name: Install Playwright run: npx playwright install --with-deps chromium - name: Run E2E tests run: npx playwright test env: CLERK_TEST_USER_EMAIL: ${{ secrets.CLERK_TEST_USER_EMAIL }} CLERK_TEST_USER_PASSWORD: ${{ secrets.CLERK_TEST_USER_PASSWORD }}
Step 2: Configure GitHub Secrets
Add these secrets in GitHub repo > Settings > Secrets:
| Secret | Value |
|---|---|
| from dev instance |
| from dev instance |
| from dev webhooks |
| |
| Strong test password |
Step 3: Playwright Auth Setup
// e2e/auth.setup.ts import { test as setup, expect } from '@playwright/test' import path from 'path' const authFile = path.join(__dirname, '.auth/user.json') setup('authenticate', async ({ page }) => { await page.goto('/sign-in') // Fill Clerk sign-in form await page.fill('input[name="identifier"]', process.env.CLERK_TEST_USER_EMAIL!) await page.click('button:has-text("Continue")') await page.fill('input[name="password"]', process.env.CLERK_TEST_USER_PASSWORD!) await page.click('button:has-text("Continue")') // Wait for redirect to authenticated page await page.waitForURL('/dashboard') await expect(page.locator('text=Dashboard')).toBeVisible() // Save auth state for reuse across tests await page.context().storageState({ path: authFile }) })
Step 4: Playwright Config with Auth State
// playwright.config.ts import { defineConfig } from '@playwright/test' export default defineConfig({ testDir: './e2e', projects: [ { name: 'setup', testMatch: 'auth.setup.ts' }, { name: 'authenticated', testMatch: '*.spec.ts', dependencies: ['setup'], use: { storageState: 'e2e/.auth/user.json', }, }, ], webServer: { command: 'npm run dev', port: 3000, reuseExistingServer: !process.env.CI, }, })
Step 5: E2E Test Examples
// e2e/dashboard.spec.ts import { test, expect } from '@playwright/test' test('authenticated user sees dashboard', async ({ page }) => { await page.goto('/dashboard') await expect(page.locator('h1')).toContainText('Dashboard') await expect(page.locator('[data-clerk-user-button]')).toBeVisible() }) test('unauthenticated user is redirected to sign-in', async ({ browser }) => { // Create fresh context without saved auth state const context = await browser.newContext() const page = await context.newPage() await page.goto('/dashboard') await expect(page).toHaveURL(/sign-in/) await context.close() }) test('protected API returns data', async ({ page }) => { const response = await page.request.get('/api/data') expect(response.status()).toBe(200) const data = await response.json() expect(data.userId).toBeTruthy() })
Step 6: Test User Seed Script for CI
// scripts/seed-ci-user.ts import { createClerkClient } from '@clerk/backend' const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY! }) async function ensureTestUser() { const email = process.env.CLERK_TEST_USER_EMAIL! const password = process.env.CLERK_TEST_USER_PASSWORD! const existing = await clerk.users.getUserList({ emailAddress: [email] }) if (existing.totalCount > 0) { console.log('Test user already exists') return } await clerk.users.createUser({ emailAddress: [email], password, firstName: 'CI', lastName: 'TestUser', }) console.log('Test user created') } ensureTestUser()
Output
- GitHub Actions workflow with Clerk env vars from secrets
- Playwright auth setup saving session state for reuse
- E2E tests covering authenticated and unauthenticated flows
- Test user seed script for CI environments
- Protected API endpoint test
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Secret not found in CI | Missing GitHub secret | Add in repo Settings > Secrets and variables > Actions |
| Test user sign-in fails | User not created or wrong password | Run seed script, verify credentials |
| Timeout on sign-in page | Clerk SDK not loaded in CI build | Ensure is set |
| E2E auth state stale | Cached session expired | Delete directory, re-run setup |
Examples
Vitest Unit Test with Mocked Clerk
// __tests__/api.test.ts import { describe, it, expect, vi } from 'vitest' vi.mock('@clerk/nextjs/server', () => ({ auth: vi.fn().mockResolvedValue({ userId: 'user_test_123', has: () => true }), })) describe('Protected API', () => { it('returns data for authenticated user', async () => { const { GET } = await import('@/app/api/data/route') const response = await GET() expect(response.status).toBe(200) }) })
Resources
Next Steps
Proceed to
clerk-deploy-integration for deployment platform setup.