⚠️ ESSENTIAL: Read E2E Testing Overview for complete setup guide
- E2E Framework Structure
- E2E Testing Best Practices
- E2E Test Examples and Patterns
- E2E Testing Anti-Patterns (AVOID THESE)
- E2E Code Review Checklist
- AI E2E Testing System
- Regression Testing Scenarios (
tests/regression/) - Regression Test files organized by feature - Snoke Testing Scenarios (
tests/smoke/) - Smoke Test files organized by feature - TypeScript Framework (
tests/framework/): Modern testing framework with type safety - Page Objects (
tests/page-objects/): Page Object Model implementation - Selectors (
tests/selectors/): Element selectors organized by feature - Fixtures (
tests/framework/fixtures/): Test data and state management - API Mocking (
tests/api-mocking/): Comprehensive API mocking system
Core E2E Framework Classes:
Assertions.ts- Enhanced assertions with auto-retry and detailed error messagesGestures.ts- Robust user interactions with configurable element state checkingMatchers.ts- Type-safe element selectors with flexible optionsUtilities.ts- Core utilities with specialized element state checkingFixtureBuilder.ts- Builder pattern for creating test fixtures
Key E2E Directories:
tests/framework/- TypeScript framework foundation (USE THIS)tests/smoke/- Smoke Test files organized by featuretests/regression/- Regression Test files organized by featuretests/page-objects/- Page Object classes following POM patterntests/selectors/- Element selectors (avoid direct use in tests)tests/api-mocking/- API mocking utilities and responsestests/framework/fixtures/- Test fixtures and data
CRITICAL: Always Use withFixtures Pattern
Every E2E test MUST use withFixtures for proper test setup and cleanup:
import { withFixtures } from '../framework/fixtures/FixtureHelper';
import FixtureBuilder from '../framework/fixtures/FixtureBuilder';
await withFixtures(
{
fixture: new FixtureBuilder().build(),
restartDevice: true,
},
async () => {
// Test code here
},
);Framework Usage (MANDATORY):
- ✅ ALWAYS import from
tests/framework/index.ts(not individual files) - ✅ ALWAYS use modern TypeScript framework methods
- ❌ NEVER use legacy methods marked with
@deprecated - ❌ NEVER use
TestHelpers.delay()- use proper waiting instead
Page Object Model (REQUIRED):
- ✅ ALWAYS use Page Object pattern - no direct selectors in test specs
- ✅ ALWAYS define element selectors in page objects or selector files
- ✅ ALWAYS access UI elements through page object methods
- ❌ NEVER use
element(by.id())directly in test specs - ❌ NEVER use raw Detox assertions in test specs
Test Structure Requirements:
- Use descriptive test names without 'should' prefix
- Include
descriptionparameters in ALL assertions and gestures - Control application state programmatically via fixtures, not UI
- Use proper element state configuration (visibility, enabled, stability)
- Handle expected failures with try/catch, not broad timeouts
API Mocking (ESSENTIAL):
- All tests run with API mocking enabled by default
- Use
testSpecificMockparameter inwithFixturesfor test-specific mocks - Default mocks are loaded from
tests/api-mocking/mock-responses/defaults/ - Feature flags mocked via
setupRemoteFeatureFlagsMockhelper
Element State Configuration:
// Default behavior (recommended for most cases)
await Gestures.tap(button, { description: 'tap button' });
// checkVisibility: true, checkEnabled: true, checkStability: false
// For animated elements
await Gestures.tap(animatedButton, {
checkStability: true,
description: 'tap animated button',
});
// For loading/disabled elements
await Gestures.tap(loadingButton, {
checkEnabled: false,
description: 'tap loading button',
});- ALWAYS use
withFixtures- every test must use this pattern - Framework Migration: Use TypeScript framework (
tests/framework/) - API Mocking: All tests run with mocked APIs - use
testSpecificMockfor test-specific needs - Page Objects: Mandatory pattern - no direct element access in test specs
- Element State: Configure visibility, enabled, and stability checking appropriately
- Debugging: Check test output for unmocked API requests and framework warnings
- Performance: Use
checkStability: falseby default, enable only for animated elements - Check
.cursor/rules/e2e-testing-guidelines.mdcfor comprehensive testing guidelines
Basic E2E Test Structure:
import { SmokeE2E } from '../../tags';
import { loginToApp } from '../../tests/flows/wallet.flow';
import { withFixtures } from '../../framework/fixtures/FixtureHelper';
import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
import WalletView from '../../pages/wallet/WalletView';
import Assertions from '../../framework/Assertions';
describe(SmokeE2E('Feature Name'), () => {
beforeAll(async () => {
jest.setTimeout(150000);
});
it('performs expected behavior', async () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
restartDevice: true,
},
async () => {
await loginToApp();
// Use page object methods, never direct selectors
await WalletView.tapSendButton();
// Use framework assertions with descriptions
await Assertions.expectElementToBeVisible(WalletView.sendButton, {
description: 'send button should be visible',
});
},
);
});
});Advanced Test with API Mocking:
import { Mockttp } from 'mockttp';
import { setupRemoteFeatureFlagsMock } from '../../api-mocking/helpers/remoteFeatureFlagsHelper';
import { confirmationsRedesignedFeatureFlags } from '../../api-mocking/mock-responses/feature-flags-mocks';
const testSpecificMock = async (mockServer: Mockttp) => {
await setupRemoteFeatureFlagsMock(
mockServer,
Object.assign({}, ...confirmationsRedesignedFeatureFlags),
);
};
await withFixtures(
{
fixture: new FixtureBuilder()
.withGanacheNetwork()
.withPermissionControllerConnectedToTestDapp()
.build(),
restartDevice: true,
testSpecificMock,
},
async () => {
// Test implementation with mocked APIs
},
);FixtureBuilder Usage Patterns:
// Basic fixture
new FixtureBuilder().build();
// With popular networks
new FixtureBuilder().withPopularNetworks().build();
// With connected test dapp
new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp(buildPermissions(['0x539']))
.build();
// With tokens and contacts
new FixtureBuilder()
.withAddressBookControllerContactBob()
.withTokensControllerERC20()
.build();Page Object Implementation Example:
import { Matchers, Gestures, Assertions } from '../../framework';
import { WalletViewSelectors } from './WalletView.selectors';
class WalletView {
get sendButton() {
return Matchers.getElementByID(WalletViewSelectors.SEND_BUTTON);
}
get receiveButton() {
return Matchers.getElementByID(WalletViewSelectors.RECEIVE_BUTTON);
}
async tapSendButton(): Promise<void> {
await Gestures.tap(this.sendButton, {
description: 'tap send button',
});
}
async verifySendButtonVisible(): Promise<void> {
await Assertions.expectElementToBeVisible(this.sendButton, {
description: 'send button should be visible',
});
}
}
export default new WalletView();❌ PROHIBITED Patterns:
// DON'T: Use TestHelpers.delay()
await TestHelpers.delay(5000);
// DON'T: Use deprecated methods
await Assertions.checkIfVisible(element);
// DON'T: Use direct element selectors in tests
element(by.id('send-button')).tap();
// DON'T: Use raw Detox assertions
await waitFor(element).toBeVisible();
// DON'T: Missing descriptions
await Gestures.tap(button);
await Assertions.expectElementToBeVisible(element);
// DON'T: Manual retry loops
let attempts = 0;
while (attempts < 5) {
try {
await Gestures.tap(button);
break;
} catch {
attempts++;
}
}✅ CORRECT Patterns:
// DO: Use proper waiting with framework
await Assertions.expectElementToBeVisible(element, {
description: 'element should be visible',
});
// DO: Use modern framework methods
await Gestures.tap(button, { description: 'tap submit button' });
// DO: Use Page Object methods
await WalletView.tapSendButton();
// DO: Use framework retry mechanisms
await Utilities.executeWithRetry(
async () => {
await Gestures.tap(button, { timeout: 2000 });
await Assertions.expectElementToBeVisible(nextScreen, { timeout: 2000 });
},
{
timeout: 30000,
description: 'tap button and verify navigation',
},
);Before submitting any E2E test, verify:
- Uses
withFixturespattern for test setup - Imports from
tests/framework/index.ts(not individual files) - No usage of
TestHelpers.delay()orsetTimeout() - No deprecated methods (check
@deprecatedtags) - All assertions have descriptive
descriptionparameters - All gestures have descriptive
descriptionparameters - Page Object pattern used (no direct selectors in tests)
- Element selectors defined in page objects or selector files
- Appropriate timeouts for operations (not magic numbers)
- API mocking configured when needed
- Tests work on both iOS and Android platforms
- Test names are descriptive without 'should' prefix
- Uses FixtureBuilder for test data setup