Best Practices and Tutorial for E2E Testing Using Synpress - globe-and-citizen/cnc-portal GitHub Wiki

End-to-end (E2E) testing ensures your application flows function as expected from the user's perspective. Synpress, a testing framework based on Playwright, simplifies the process of writing E2E tests for web3 applications. This guide provides best practices and a tutorial for using Synpress, with example code.


Best Practices for Writing Synpress Tests

1. Use Fixtures for Reusable Test Data

Fixtures allow you to set up common prerequisites for your tests. For example, a login fixture can handle authentication before each test.

import { test as login } from '../fixtures/login.fixture';
import { testWithSynpress } from '@synthetixio/synpress';

const test = testWithSynpress(login);

2. Mock External APIs

Mocking APIs is crucial to control and simulate responses for different scenarios like success, failure, or empty states.

await page.route('**/api/notification', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ success: true, data: [] })
  });
});

3. Simulate Network Delays for Realistic Testing

Introduce delays in mock API responses to test loading states.

await page.route('**/api/teams', async (route) => {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify(teams)
  });
});

4. Use Data-Test Attributes

Adding data-test attributes to HTML elements makes them easier to identify in tests.

<div data-test="loading-state">Loading...</div>

5. Clean Up After Tests

Ensure state is reset after each test to avoid side effects.

6. Group Related Tests

Use describe blocks to group tests by functionality.

describe('Team List', () => {
  test('should render teams correctly', async ({ page }) => {
    // Test implementation
  });
});

Tutorial: Writing Synpress Tests

  1. Follow the README.md file to set up the testing (I'm using node v21.7.1)
  2. Start creating testing. Here is the testing breakdown that I created in teams.spec.ts

Structure

The test suite has three primary sections:

  1. Team List Tests:
  • Verify loading state.
  • Test rendering of teams.
  • Handle the empty state scenario.
  1. Add Team Tests:
  • Simulate adding a new team.
  • Verify the success message.
  • Simulate error validation
  1. Reusable Fixtures:
  • Set up login and API routes.

Key Code Sections

1. Setup and Login Fixture

beforeEach(async ({ login, page }) => {
  await login();
  await page.route('**/api/notification', async (route) => {
    await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, data: [] }) });
  });
});
  • This ensures the user is logged in and mock data is loaded before each test.

2. Loading State Test

await page.goto('http://localhost:5173/teams');
expect(await page.isVisible('[data-test="loading-state"]')).toBeTruthy();
await page.waitForSelector('[data-test="loading-state"]', { state: 'hidden' });
  • Verifies the loading spinner appears and disappears correctly.

3. Rendering Teams

for (const team of teams) {
  expect(await page.isVisible(`[data-test="team-card-${team.id}"]`)).toBeTruthy();
}
  • Loops through mock data to ensure each team is rendered.

4. Empty State Test

await page.route('**/api/teams', async (route) => {
  await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) });
});

await page.goto('http://localhost:5173/teams');
await page.waitForSelector('[data-test="empty-state"]');
expect(await page.isVisible('[data-test="empty-state"]')).toBeTruthy();
  • Simulates a scenario with no teams and verifies the empty state message.

5. Adding a Team

await page.click('[data-test="add-team"]');
await page.fill('[data-test="team-name-input"]', 'Team 1');
await page.fill('[data-test="team-description-input"]', 'Description of Team 1');
await page.click('[data-test="submit"]');
await page.waitForSelector('[data-test="toast-container"]');
expect(await page.textContent('[data-test="toast-container"]')).toContain('Team created successfully');
  • Simulates user interaction for adding a new team and verifies success feedback.

Key Takeaways

  • Follow best practices like using fixtures, mocking APIs, and grouping tests.
  • Write tests that handle various states: loading, success, error, and empty states.
⚠️ **GitHub.com Fallback** ⚠️