Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci
run: npm install

- name: Type check
run: npm run type-check
Expand Down Expand Up @@ -188,13 +188,59 @@ jobs:
echo "Preview failed to become ready"
exit 1

- name: Detect changed areas for E2E test selection
if: github.actor != 'dependabot[bot]'
id: test-tags
run: |
# Get changed files vs base branch (PR diff) or last commit (push)
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...${{ github.event.pull_request.head.sha }} 2>/dev/null || git diff --name-only HEAD~1)
else
CHANGED=$(git diff --name-only HEAD~1 2>/dev/null || echo "")
fi

echo "Changed files:"
echo "$CHANGED"

# Always include smoke
TAGS="@smoke"

# Map changed paths to feature tags
if echo "$CHANGED" | grep -qE 'admin-media|services/media|media-documents'; then
TAGS="$TAGS|@media"
fi
if echo "$CHANGED" | grep -qE 'admin-content|services/documents|document-repository|document-projection'; then
TAGS="$TAGS|@content"
fi
if echo "$CHANGED" | grep -qE 'routes/api|api-content|api-documents|api-media'; then
TAGS="$TAGS|@api"
fi
if echo "$CHANGED" | grep -qE 'middleware/auth|admin-settings|api-keys|services/api-key'; then
TAGS="$TAGS|@auth|@api-keys"
fi
if echo "$CHANGED" | grep -qE 'admin-database|database-tools'; then
TAGS="$TAGS|@database"
fi
if echo "$CHANGED" | grep -qE 'collection-registry|admin-collections'; then
TAGS="$TAGS|@collections"
fi
if echo "$CHANGED" | grep -qE 'migrations/'; then
TAGS="$TAGS|@content|@media|@api"
fi

# Deduplicate tags
TAGS=$(echo "$TAGS" | tr '|' '\n' | sort -u | tr '\n' '|' | sed 's/|$//')

echo "Running E2E with tag filter: $TAGS"
echo "grep_pattern=$TAGS" >> $GITHUB_OUTPUT

- name: Install Playwright browsers
if: github.actor != 'dependabot[bot]'
run: npx playwright install --with-deps chromium

- name: Run E2E tests against preview
if: github.actor != 'dependabot[bot]'
run: npm run e2e
run: npx playwright test --grep "${{ steps.test-tags.outputs.grep_pattern }}"
env:
CI: true
BASE_URL: ${{ steps.deploy.outputs.preview_url }}
Expand Down
40 changes: 38 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,48 @@ Anti-patterns: `grep -r` across repo when codegraph answers; reading 5+ files to

Every feature/fix ships with a Playwright spec in `tests/e2e/`. Write the spec, but **do NOT run E2E tests locally** — they require a running wrangler dev server, consume excessive memory, and are validated by CI on PR. Running them locally is too expensive.

### CI test selection strategy (dynamic)

CI runs **smoke tests + tests tagged to changed areas only** — not the full suite. This keeps PR feedback fast.

**Tag every test** with `@smoke` (critical path, always runs) or a feature tag (`@media`, `@content`, `@auth`, `@api-keys`, `@database`, `@collections`, etc.):

```typescript
test('does the thing @media', async ({ page }) => { … })
// or on the describe block:
test.describe('Media Upload @media', () => { … })
```

**Tag mapping** (CI uses `git diff --name-only` → selects tags to run):

| Changed path pattern | Tags to run |
|---|---|
| `src/routes/admin-content*` or `src/services/documents*` | `@smoke @content` |
| `src/routes/admin-media*` or `src/services/media*` | `@smoke @media` |
| `src/routes/api*` | `@smoke @api` |
| `src/middleware/auth*` or `src/routes/admin-settings*` | `@smoke @auth` |
| `src/services/api-keys*` or related | `@smoke @api-keys` |
| `src/routes/admin-database*` | `@smoke @database` |
| `packages/core/migrations/*` | `@smoke @content @media @api` |
| Anything else / multiple areas | `@smoke` only |

CI invocation: `npx playwright test --grep "@smoke|@<detected-tag>"`.

**When writing a new spec**: choose the tightest matching feature tag. If it spans multiple features, use the primary one + `@smoke` if it tests login/nav/core flow.

Workflow:
1. Implement
2. Add `tests/e2e/<NN>-<slug>.spec.ts` (NN = next sequential; current floor 68 — R11)
3. Commit implementation + tests together — CI runs E2E
3. Tag tests with `@smoke` and/or appropriate feature tag
4. Commit implementation + tests together — CI selects and runs relevant tests

Spec skeleton:

```typescript
import { test, expect } from '@playwright/test'
import { loginAsAdmin } from './utils/test-helpers'

test.describe('Feature Name', () => {
test.describe('Feature Name @feature-tag', () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page)
})
Expand Down Expand Up @@ -216,6 +246,12 @@ cd packages/core && npm run generate:migrations

After **any** `packages/core/migrations/*.sql` change: regenerate the bundle, re-sync `my-sonicjs-app/migrations/`, commit the regenerated `migrations-bundle.ts` (R9).

**Lock file trap (macOS → Linux CI):** Any `npm install` on macOS regenerates `package-lock.json` without Linux optional packages (`@emnapi/runtime`, `@emnapi/core`, `wrangler/node_modules/esbuild`), breaking `npm ci` in CI. After any `npm install` that touches `package-lock.json`, immediately run a full delete-and-reinstall to restore cross-platform entries:
```bash
rm -rf node_modules package-lock.json && npm install
```
Then commit the regenerated `package-lock.json`. Never commit a lock file produced by `npm install --workspace=...` directly.

D1's **100 bound params/statement** and **100 columns/table** limits do not reproduce on local SQLite — cover with logic unit tests (chunk counts, column budget).

## Development Workflow
Expand Down
Loading
Loading