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.
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);
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: [] })
});
});
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)
});
});
Adding data-test
attributes to HTML elements makes them easier to identify in tests.
<div data-test="loading-state">Loading...</div>
Ensure state is reset after each test to avoid side effects.
Use describe
blocks to group tests by functionality.
describe('Team List', () => {
test('should render teams correctly', async ({ page }) => {
// Test implementation
});
});
- Follow the
README.md
file to set up the testing (I'm using node v21.7.1) - Start creating testing. Here is the testing breakdown that I created in
teams.spec.ts
The test suite has three primary sections:
- Team List Tests:
- Verify loading state.
- Test rendering of teams.
- Handle the empty state scenario.
- Add Team Tests:
- Simulate adding a new team.
- Verify the success message.
- Simulate error validation
- Reusable Fixtures:
- Set up login and API routes.
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.
- Follow best practices like using fixtures, mocking APIs, and grouping tests.
- Write tests that handle various states: loading, success, error, and empty states.