End-to-end (E2E) tests drive a real browser through complete user flows, login, checkout, form submission, verifying the whole application works together correctly. This lesson covers writing them well.
What Is End-to-End Testing?
An E2E test opens a real (often headless) browser, navigates to your running application, and simulates real user actions: clicking buttons, filling in forms, and asserting on what appears on screen, exactly as a real user would experience it.
Because E2E tests exercise the whole stack, frontend, routing, and often a real or test backend, they provide the highest confidence that a feature works end to end, at the cost of being slower and sometimes flakier than unit or component tests.
// Cypress example
describe('Login flow', () => {
it('logs in with valid credentials and redirects to the dashboard', () => {
cy.visit('/login');
cy.get('[data-testid="email"]').type('ada@example.com');
cy.get('[data-testid="password"]').type('secret123');
cy.get('[data-testid="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome, Ada').should('be.visible');
});
});
The test interacts with the app exactly as a real user would, typing into fields and clicking a real button.
Cypress and Playwright are the two most widely used E2E frameworks for Angular applications today.
cy.visit()/page.goto() navigates to a URL in the app under test.
Selecting elements by dedicated test attributes (like data-testid) is more resilient than relying on CSS classes that may change for styling reasons.
Assertions check both visible content and application state, like the current URL after a navigation.
E2E Testing Cheatsheet
Key concepts and best practices for reliable E2E tests.
Concept
Guidance
Element selection
Prefer data-testid attributes over CSS classes
Waiting for async UI
Use the tool's built-in retry/waiting (avoid arbitrary fixed delays)
Test independence
Each test should set up its own state, not depend on test run order
Test data
Use dedicated test accounts/seeded data, not production data
Speed
Run E2E tests against a realistic but fast test environment
Scope
Cover critical user journeys, not every possible UI permutation
Writing Resilient Element Selectors
Styling classes change often as a UI evolves, breaking tests that select elements by CSS class. Dedicated data-testid attributes are stable identifiers meant purely for testing, decoupled from styling and even from visible text (which might change for localization).
<button data-testid="submit-order">Place Order</button>
// test
cy.get('[data-testid="submit-order"]').click();
Avoiding Flaky Tests
Flaky tests (ones that intermittently fail without a real underlying bug) are the biggest threat to trusting an E2E suite. Most flakiness comes from racing against asynchronous UI updates, use the testing tool's built-in automatic waiting/retrying assertions instead of fixed sleep/wait(ms) calls.
// avoid: arbitrary fixed wait
cy.wait(2000);
cy.get('.result').should('contain', 'Loaded');
// prefer: built-in retrying assertion, waits as long as actually needed
cy.get('.result').should('contain', 'Loaded');
Deciding What to Cover With E2E Tests
Because E2E tests are the slowest and most expensive layer to maintain, focus them on critical, high-value user journeys, sign up, login, checkout, core workflows, rather than trying to E2E-test every possible UI state, which is better covered by component tests.
Common Mistakes
Selecting elements by CSS classes tied to styling, causing tests to break whenever the design changes.
Using fixed-duration waits instead of the testing tool's built-in automatic retrying assertions.
Writing E2E tests that depend on a specific execution order or leftover state from a previous test.
Trying to E2E test every UI permutation instead of reserving E2E for critical, high-value user journeys.
Key Takeaways
E2E tests drive a real browser through complete user flows, providing the highest overall confidence.
Cypress and Playwright are the most common E2E tools used with Angular applications.
Stable data-testid selectors and built-in automatic waiting reduce flakiness significantly.
Reserve E2E tests for critical user journeys; rely on component and unit tests for broader coverage.
Pro Tip
Add data-testid attributes to key interactive elements as you build features, rather than retrofitting them later, it keeps your E2E test suite resilient to routine styling and copy changes without any extra effort down the line.
You now understand end-to-end testing. Next, review Angular Best Practices to bring together everything you've learned so far.