Configure Clerk CI/CD integration with GitHub Actions and testing. Use when setting up automated testing, configuring CI pipelines, or integrating Clerk tests into your build process. Trigger with phrases like "clerk CI", "clerk GitHub Actions", "clerk automated tests", "CI clerk", "clerk pipeline".
80
77%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./plugins/saas-packs/clerk-pack/skills/clerk-ci-integration/SKILL.mdSet 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.
pk_test_ / sk_test_)# .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 }}Add these secrets in GitHub repo > Settings > Secrets:
| Secret | Value |
|---|---|
CLERK_PK_TEST | pk_test_... from dev instance |
CLERK_SK_TEST | sk_test_... from dev instance |
CLERK_WEBHOOK_SECRET_TEST | whsec_... from dev webhooks |
CLERK_TEST_USER_EMAIL | ci-test@yourapp.com |
CLERK_TEST_USER_PASSWORD | Strong test password |
// 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 })
})// 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,
},
})// 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()
})// 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()| 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 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is set |
| E2E auth state stale | Cached session expired | Delete .auth/ directory, re-run setup |
// __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)
})
})Proceed to clerk-deploy-integration for deployment platform setup.
3e83543
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.