Running Cypress in CI requires starting both the Angular dev server and the ASP.NET Core API before tests run, then executing Cypress headlessly. GitHub Actions provides all the infrastructure needed — Node.js for Angular and Cypress, .NET SDK for the API, Chrome for headless execution. Cypress Cloud adds parallel execution across multiple agents, dramatically reducing the total E2E test time for larger test suites.
GitHub Actions CI for Cypress
// ── .github/workflows/e2e.yml ─────────────────────────────────────────────
// name: E2E Tests
// on: [push, pull_request]
// jobs:
// e2e:
// runs-on: ubuntu-latest
// steps:
// - uses: actions/checkout@v4
//
// - uses: actions/setup-node@v4
// with: { node-version: '20' }
//
// - uses: actions/setup-dotnet@v4
// with: { dotnet-version: '8.0.x' }
//
// - name: Restore .NET packages
// run: dotnet restore
//
// - name: Build .NET API
// run: dotnet build --no-restore
//
// - name: Start API server
// run: dotnet run --project BlogApp.Api &
// env:
// ASPNETCORE_URLS: http://localhost:5000
// ConnectionStrings__Default: ${{ secrets.TEST_DB_CONNECTION }}
//
// - name: Install Angular dependencies
// run: npm ci
//
// - uses: cypress-io/github-action@v6
// with:
// start: ng serve --configuration=test
// wait-on: 'http://localhost:4200, http://localhost:5000/api/health'
// wait-on-timeout: 120
// browser: chrome
// record: true # Requires CYPRESS_RECORD_KEY secret
// parallel: true # Requires Cypress Cloud plan
// env:
// CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
// CYPRESS_ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }}
//
// - uses: actions/upload-artifact@v4
// if: failure()
// with:
// name: cypress-screenshots
// path: cypress/screenshots
//
// - uses: actions/upload-artifact@v4
// if: always()
// with:
// name: cypress-videos
// path: cypress/videos
// ── Cypress Cloud parallel configuration ──────────────────────────────────
// cypress.config.ts:
// export default defineConfig({
// projectId: 'abc123', // from Cypress Cloud
// e2e: {
// ...
// },
// });
// The CI matrix runs 3 agents in parallel:
// strategy:
// matrix:
// containers: [1, 2, 3]
// Each agent runs a subset of spec files (balanced by Cypress Cloud)
Cypress Component Testing
// ── cypress.config.ts — add component testing ─────────────────────────────
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'angular',
bundler: 'webpack',
},
specPattern: '**/*.cy.ts', // matches component specs too
},
e2e: { /* ... existing e2e config ... */ },
});
// ── PostCardComponent.cy.ts — component test with Cypress ─────────────────
import { mount } from 'cypress/angular';
import { PostCardComponent } from './post-card.component';
describe('PostCardComponent', () => {
const post: PostSummaryDto = {
id: 1, title: 'Test Post', slug: 'test-post',
viewCount: 1234, authorName: 'Alice',
publishedAt: '2024-07-15T00:00:00Z',
};
it('displays post title and author', () => {
mount(PostCardComponent, {
componentProperties: { post },
});
cy.get('[data-cy="post-title"]').should('contain', 'Test Post');
cy.get('[data-cy="author-name"]').should('contain', 'Alice');
cy.get('[data-cy="view-count"]').should('contain', '1,234');
});
it('navigates to post on card click', () => {
mount(PostCardComponent, {
componentProperties: { post },
imports: [RouterTestingModule.withRoutes([])],
});
cy.get('[data-cy="read-more-btn"]').click();
cy.location('pathname').should('eq', '/posts/test-post');
});
});
wait-on in the GitHub Action pauses the Cypress step until both servers respond with a successful health check. Without wait-on, Cypress might start before the Angular dev server has compiled and the ASP.NET Core API has finished initializing — causing the first few tests to fail with connection errors. The wait-on-timeout: 120 allows up to 2 minutes for both servers to start — more than enough for a typical compilation and startup.--spec patterns: Agent 1 runs --spec 'cypress/e2e/public/**', Agent 2 runs --spec 'cypress/e2e/admin/**'. This is less optimal than Cypress Cloud’s intelligent balancing but requires no subscription.Complete CI Pipeline Summary
| Stage | Type | Tool | Time |
|---|---|---|---|
| Unit tests (.NET) | Unit | xUnit + Moq | ~30s |
| Unit tests (Angular) | Unit | Jest | ~20s |
| Integration tests | Integration | WebApplicationFactory | ~2min |
| E2E tests | E2E | Cypress | ~5min (parallel) |
| Total CI time | — | — | ~8min |
Common Mistakes
Mistake 1 — No wait-on for server startup (Cypress starts before servers ready)
❌ Wrong — no wait-on; Cypress visits / before Angular compiles; GET http://localhost:4200 fails; all tests fail.
✅ Correct — wait-on: 'http://localhost:4200, http://localhost:5000/api/health'; Cypress only starts after both servers respond.
Mistake 2 — Missing screenshot artifacts on failure (no evidence of what failed)
❌ Wrong — no artifact upload; test fails in CI; no screenshot to diagnose what the UI showed at failure.
✅ Correct — actions/upload-artifact with if: failure() for screenshots; always visible in GitHub Actions run.