diff --git a/.github/workflows/run-e2e-smoke-tests-android.yml b/.github/workflows/run-e2e-smoke-tests-android.yml index d4a98455b9e9..bbd5876b3ea0 100644 --- a/.github/workflows/run-e2e-smoke-tests-android.yml +++ b/.github/workflows/run-e2e-smoke-tests-android.yml @@ -14,109 +14,18 @@ permissions: id-token: write jobs: - trade-android-smoke: - strategy: - matrix: - split: [1] - fail-fast: false + screenshot-examples: + name: Screenshot Examples - ${{ inputs.platform }} uses: ./.github/workflows/run-e2e-workflow.yml with: - test-suite-name: trade-android-smoke-${{ matrix.split }} + test-suite-name: screenshot-examples platform: android - test_suite_tag: 'SmokeTrade' - split_number: ${{ matrix.split }} + test_suite_tag: 'ScreenshotExamples' + test-timeout-minutes: 30 + split_number: 1 total_splits: 1 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - wallet-platform-android-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: wallet-platform-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeWalletPlatform' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - identity-android-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: identity-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeIdentity' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - accounts-android-smoke: - strategy: - matrix: - split: [1] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: accounts-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeAccounts' - split_number: ${{ matrix.split }} - total_splits: 1 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - network-abstraction-android-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: network-abstraction-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeNetworkAbstractions' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - network-expansion-android-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: network-expansion-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeNetworkExpansion' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - confirmations-redesigned-android-smoke: - strategy: - matrix: - split: [1, 2, 3] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: confirmations-redesigned-android-smoke-${{ matrix.split }} - platform: android - test_suite_tag: 'SmokeConfirmationsRedesigned' - split_number: ${{ matrix.split }} - total_splits: 3 - changed_files: ${{ inputs.changed_files }} + build_type: main + metamask_environment: e2e secrets: inherit report-android-smoke-tests: @@ -124,13 +33,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !cancelled() }} needs: - - trade-android-smoke - - wallet-platform-android-smoke - - identity-android-smoke - - accounts-android-smoke - - network-abstraction-android-smoke - - network-expansion-android-smoke - - confirmations-redesigned-android-smoke + - screenshot-examples steps: - name: Checkout diff --git a/.github/workflows/run-e2e-smoke-tests-ios.yml b/.github/workflows/run-e2e-smoke-tests-ios.yml index cc432a148bd8..1a942619dbe9 100644 --- a/.github/workflows/run-e2e-smoke-tests-ios.yml +++ b/.github/workflows/run-e2e-smoke-tests-ios.yml @@ -14,109 +14,18 @@ permissions: id-token: write jobs: - confirmations-redesigned-ios-smoke: - strategy: - matrix: - split: [1, 2, 3] - fail-fast: false + screenshot-examples: + name: Screenshot Examples - ${{ inputs.platform }} uses: ./.github/workflows/run-e2e-workflow.yml with: - test-suite-name: confirmations-redesigned-ios-smoke-${{ matrix.split }} + test-suite-name: screenshot-examples platform: ios - test_suite_tag: 'SmokeConfirmationsRedesigned' - split_number: ${{ matrix.split }} - total_splits: 3 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - trade-ios-smoke: - strategy: - matrix: - split: [1] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: trade-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeTrade' - split_number: ${{ matrix.split }} + test_suite_tag: 'ScreenshotExamples' + test-timeout-minutes: 30 + split_number: 1 total_splits: 1 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - wallet-platform-ios-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: wallet-platform-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeWalletPlatform' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - identity-ios-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: identity-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeIdentity' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - accounts-ios-smoke: - strategy: - matrix: - split: [1] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: accounts-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeAccounts' - split_number: ${{ matrix.split }} - total_splits: 1 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - network-abstraction-ios-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: network-abstraction-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeNetworkAbstractions' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} - secrets: inherit - - network-expansion-ios-smoke: - strategy: - matrix: - split: [1, 2] - fail-fast: false - uses: ./.github/workflows/run-e2e-workflow.yml - with: - test-suite-name: network-expansion-ios-smoke-${{ matrix.split }} - platform: ios - test_suite_tag: 'SmokeNetworkExpansion' - split_number: ${{ matrix.split }} - total_splits: 2 - changed_files: ${{ inputs.changed_files }} + build_type: main + metamask_environment: e2e secrets: inherit report-ios-smoke-tests: @@ -124,13 +33,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !cancelled() }} needs: - - confirmations-redesigned-ios-smoke - - trade-ios-smoke - - wallet-platform-ios-smoke - - identity-ios-smoke - - accounts-ios-smoke - - network-abstraction-ios-smoke - - network-expansion-ios-smoke + - screenshot-examples steps: - name: Checkout diff --git a/.github/workflows/run-screenshot-examples.yml b/.github/workflows/run-screenshot-examples.yml new file mode 100644 index 000000000000..432c58b33441 --- /dev/null +++ b/.github/workflows/run-screenshot-examples.yml @@ -0,0 +1,32 @@ +# Workflow to run screenshot examples E2E tests +# This demonstrates the new screenshot capture functionality + +name: Screenshot Examples E2E Tests + +on: + workflow_dispatch: + inputs: + platform: + description: 'Platform to test (ios or android)' + required: true + type: choice + options: + - ios + - android + default: ios + +jobs: + screenshot-examples: + name: Screenshot Examples - ${{ inputs.platform }} + uses: ./.github/workflows/run-e2e-workflow.yml + with: + test-suite-name: screenshot-examples + platform: ${{ inputs.platform }} + test_suite_tag: 'ScreenshotExamples' + test-timeout-minutes: 30 + split_number: 1 + total_splits: 1 + build_type: main + metamask_environment: e2e + secrets: inherit + diff --git a/e2e/docs/SCREENSHOTS_QUICKSTART.md b/e2e/docs/SCREENSHOTS_QUICKSTART.md new file mode 100644 index 000000000000..9fe3046cde45 --- /dev/null +++ b/e2e/docs/SCREENSHOTS_QUICKSTART.md @@ -0,0 +1,193 @@ +# Screenshots for Navigation - Quick Start Guide + +## TL;DR + +You can now automatically capture screenshots during navigation in your E2E tests! + +## Quick Examples + +### 1. Simple Screenshot + +```typescript +import { Utilities } from '../framework'; + +await Utilities.takeScreenshot('home-screen'); +``` + +### 2. Wrap Navigation with Screenshots + +```typescript +import { withNavigationScreenshots } from '../framework'; +import TabBarComponent from '../pages/wallet/TabBarComponent'; + +await withNavigationScreenshots(() => TabBarComponent.tapBrowser(), { + name: 'navigate-to-browser', +}); +``` + +### 3. Multi-Step Flow + +```typescript +import { captureNavigationFlow } from '../framework'; + +await captureNavigationFlow('onboarding', [ + { name: 'step-1-welcome', action: () => WelcomeScreen.tapGetStarted() }, + { name: 'step-2-terms', action: () => TermsScreen.tapAgree() }, + { + name: 'step-3-password', + action: () => PasswordScreen.createPassword('secure123'), + }, +]); +``` + +## What Was Added + +### New Methods in `Utilities` class: + +- **`Utilities.takeScreenshot(name, options)`** - Capture a screenshot manually +- **`Utilities.executeWithScreenshot(operation, options)`** - Wrap any operation with automatic screenshots + +### New Helper Functions: + +- **`withNavigationScreenshots(fn, options)`** - Wrap navigation with screenshots +- **`captureNavigationFlow(name, steps)`** - Capture multi-step flows +- **`captureScreenshot(name, options)`** - Convenience wrapper +- **`createScreenshotNavigationWrapper(fn, name)`** - Create reusable wrappers + +## Import + +```typescript +import { + Utilities, + withNavigationScreenshots, + captureNavigationFlow, + captureScreenshot, + createScreenshotNavigationWrapper, +} from '../framework'; +``` + +## Common Patterns + +### Pattern 1: After Every Navigation + +```typescript +it('navigates through app', async () => { + await withFixtures( + { fixture: new FixtureBuilder().build(), restartDevice: true }, + async () => { + await loginToApp(); + + await withNavigationScreenshots(() => TabBarComponent.tapBrowser(), { + name: 'to-browser', + }); + + await withNavigationScreenshots(() => TabBarComponent.tapWallet(), { + name: 'to-wallet', + }); + }, + ); +}); +``` + +### Pattern 2: Before and After + +```typescript +await withNavigationScreenshots(() => CommonView.tapBackButton(), { + name: 'go-back', + captureBeforeAction: true, // Capture before + captureAfterAction: true, // Capture after +}); +``` + +### Pattern 3: Reusable Wrappers + +```typescript +// Define once +const navigateToSettings = createScreenshotNavigationWrapper(async () => { + await WalletView.tapBurgerIcon(); + await WalletView.tapSettings(); +}, 'navigate-to-settings'); + +// Use many times +await navigateToSettings(); // Auto-captures screenshot +``` + +## Screenshot Naming + +Screenshots are automatically named with timestamps: + +``` +navigation_2024-10-17T10-30-45-123Z_navigate-to-browser-after.png +``` + +Custom prefix: + +```typescript +await Utilities.takeScreenshot('error-state', { prefix: 'bug-123' }); +// Result: bug-123_2024-10-17T10-30-45-123Z_error-state.png +``` + +## Features + +✅ **Automatic failure screenshots** - If operation fails, screenshot is captured +✅ **Timestamped names** - Easy to track when screenshots were taken +✅ **Configurable** - Choose when to capture (before, after, or both) +✅ **Type-safe** - Full TypeScript support +✅ **Framework integrated** - Works with all existing E2E patterns + +## Screenshot Location + +Screenshots are saved by Detox in: + +- **iOS**: `e2e/artifacts/ios.sim.debug.MetaMask/` +- **Android**: `e2e/artifacts/android.emu.debug.MetaMask/` + +## Full Documentation + +See [screenshots-navigation.md](./screenshots-navigation.md) for complete documentation with advanced examples. + +## Example Test + +```typescript +describe('Browser Tests', () => { + it('connects to dApp with screenshots', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await Utilities.takeScreenshot('01-logged-in'); + + await captureNavigationFlow('connect-to-dapp', [ + { + name: 'open-browser', + action: () => TabBarComponent.tapBrowser(), + }, + { + name: 'navigate-to-dapp', + action: () => + Browser.navigateToURL('https://metamask.github.io/test-dapp/'), + }, + { + name: 'connect-wallet', + action: () => TestDApp.tapConnectButton(), + }, + ]); + + await Utilities.takeScreenshot('05-connected'); + }, + ); + }); +}); +``` + +## Next Steps + +1. Import the new functions from `'../framework'` +2. Add screenshot capture to your navigation tests +3. Run your tests and check the artifacts directory +4. Adjust screenshot frequency based on your needs + +For questions or issues, refer to the [full documentation](./screenshots-navigation.md). diff --git a/e2e/docs/SCREENSHOT_IMPLEMENTATION_SUMMARY.md b/e2e/docs/SCREENSHOT_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 000000000000..344882e4db73 --- /dev/null +++ b/e2e/docs/SCREENSHOT_IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,261 @@ +# Screenshot Implementation Summary + +## What Was Implemented + +Automatic screenshot capture functionality for navigation actions in E2E tests. + +## Files Added/Modified + +### Modified Files: + +1. **`e2e/framework/Utilities.ts`** + + - Added `takeScreenshot(name, options)` method + - Added `executeWithScreenshot(operation, options)` method + - Both methods include automatic timestamp generation and name sanitization + +2. **`e2e/framework/index.ts`** + - Exported new NavigationHelpers module + +### New Files: + +3. **`e2e/framework/NavigationHelpers.ts`** + + - `withNavigationScreenshots()` - Wrap navigation with automatic screenshots + - `captureNavigationFlow()` - Capture multi-step navigation flows + - `captureScreenshot()` - Convenience wrapper for Utilities.takeScreenshot + - `createScreenshotNavigationWrapper()` - Create reusable navigation wrappers + +4. **`e2e/docs/screenshots-navigation.md`** + + - Comprehensive documentation with examples + - API reference + - Best practices + - Troubleshooting guide + +5. **`e2e/docs/SCREENSHOTS_QUICKSTART.md`** + + - Quick start guide with common patterns + - Copy-paste ready examples + +6. **`e2e/docs/screenshot-examples.spec.ts`** + - Complete example test file + - 8 different usage patterns demonstrated + +## Key Features + +### 1. Automatic Screenshot Capture + +```typescript +await withNavigationScreenshots(() => TabBarComponent.tapBrowser(), { + name: 'navigate-to-browser', +}); +``` + +### 2. Before/After Capture + +```typescript +await withNavigationScreenshots(() => CommonView.tapBackButton(), { + name: 'go-back', + captureBeforeAction: true, + captureAfterAction: true, +}); +``` + +### 3. Multi-Step Flows + +```typescript +await captureNavigationFlow('settings-flow', [ + { name: 'open-menu', action: () => WalletView.tapBurgerIcon() }, + { name: 'open-settings', action: () => WalletView.tapSettings() }, +]); +``` + +### 4. Automatic Failure Capture + +If any operation fails, a screenshot is automatically captured with `-failed` suffix. + +### 5. Reusable Wrappers + +```typescript +const navigateToBrowser = createScreenshotNavigationWrapper( + () => TabBarComponent.tapBrowser(), + 'navigate-to-browser', +); + +// Later... +await navigateToBrowser(); // Automatically captures screenshot +``` + +## API Overview + +### Utilities Class Methods + +#### `Utilities.takeScreenshot(name, options)` + +```typescript +static async takeScreenshot( + name: string, + options: { prefix?: string; timestamp?: boolean } = {} +): Promise +``` + +#### `Utilities.executeWithScreenshot(operation, options)` + +```typescript +static async executeWithScreenshot( + operation: () => Promise, + options: { + name: string; + captureBeforeAction?: boolean; + captureAfterAction?: boolean; + screenshotPrefix?: string; + } & RetryOptions +): Promise +``` + +### Navigation Helper Functions + +#### `withNavigationScreenshots(fn, options)` + +```typescript +async function withNavigationScreenshots( + navigationFn: () => Promise, + options: NavigationWithScreenshotOptions, +): Promise; +``` + +#### `captureNavigationFlow(flowName, steps)` + +```typescript +async function captureNavigationFlow( + flowName: string, + steps: { name: string; action: () => Promise }[], +): Promise; +``` + +#### `createScreenshotNavigationWrapper(fn, name, options)` + +```typescript +function createScreenshotNavigationWrapper( + navigationFn: () => Promise, + name: string, + options?: Omit, +): () => Promise; +``` + +## Usage in Tests + +### Import Statement + +```typescript +import { + Utilities, + withNavigationScreenshots, + captureNavigationFlow, + captureScreenshot, + createScreenshotNavigationWrapper, +} from '../framework'; +``` + +### Basic Usage + +```typescript +it('navigates with screenshots', async () => { + await withFixtures( + { fixture: new FixtureBuilder().build(), restartDevice: true }, + async () => { + await loginToApp(); + + // Simple screenshot + await Utilities.takeScreenshot('after-login'); + + // Navigation with screenshot + await withNavigationScreenshots(() => TabBarComponent.tapBrowser(), { + name: 'to-browser', + }); + }, + ); +}); +``` + +## Screenshot Naming Convention + +### Default Format: + +``` +[prefix]_[timestamp]_[name]-[suffix].png +``` + +### Examples: + +- `navigation_2024-10-17T10-30-45-123Z_navigate-to-browser-after.png` +- `settings-flow_2024-10-17T10-30-45-123Z_step-1-open-menu-after.png` +- `navigation_2024-10-17T10-30-45-123Z_navigate-to-settings-failed.png` (on failure) + +### Custom Prefix: + +```typescript +await Utilities.takeScreenshot('error-state', { + prefix: 'bug-123', + timestamp: true, +}); +// Result: bug-123_2024-10-17T10-30-45-123Z_error-state.png +``` + +## Benefits + +1. **Better Debugging**: Visual record of test execution +2. **Documentation**: Screenshots serve as visual documentation of flows +3. **Failure Analysis**: Automatic screenshots on failure help diagnose issues +4. **Test Reports**: Can be attached to test reports for stakeholders +5. **Reproducibility**: Visual evidence of what the test saw + +## Integration with Existing Tests + +### No Breaking Changes + +- All existing tests continue to work without modification +- Screenshot capture is opt-in +- Works with existing Page Object Model pattern + +### Gradual Adoption + +You can add screenshots to tests incrementally: + +1. Start with critical user flows +2. Add to complex navigation sequences +3. Expand to all navigation tests as needed + +## Performance Considerations + +- Screenshots have minimal performance impact +- Default behavior: capture after action only +- Can disable for CI runs if needed using environment variables +- Selective capture recommended (not every action, just navigation) + +## Environment Control + +```typescript +// Optional: Control via environment variable +const ENABLE_SCREENSHOTS = process.env.E2E_SCREENSHOTS === 'true'; + +async function conditionalScreenshot(name: string) { + if (ENABLE_SCREENSHOTS) { + await Utilities.takeScreenshot(name); + } +} +``` + +## Next Steps + +1. Review the [Quick Start Guide](./SCREENSHOTS_QUICKSTART.md) +2. See [Full Documentation](./screenshots-navigation.md) for advanced usage +3. Check [Example Tests](./screenshot-examples.spec.ts) for patterns +4. Start adding screenshots to your navigation tests + +## Questions? + +- See documentation in `e2e/docs/screenshots-navigation.md` +- Check examples in `e2e/docs/screenshot-examples.spec.ts` +- Review the implementation in `e2e/framework/Utilities.ts` and `e2e/framework/NavigationHelpers.ts` diff --git a/e2e/docs/screenshot-examples.spec.ts b/e2e/docs/screenshot-examples.spec.ts new file mode 100644 index 000000000000..331aa4187a1e --- /dev/null +++ b/e2e/docs/screenshot-examples.spec.ts @@ -0,0 +1,289 @@ +/** + * Example Test File: Screenshots with Navigation + * + * This file demonstrates various patterns for capturing screenshots + * during navigation in E2E tests. + * + * NOTE: This is an example file for documentation purposes. + * You can copy these patterns into your actual test files. + */ + +import { withFixtures } from '../framework/fixtures/FixtureHelper'; +import FixtureBuilder from '../framework/fixtures/FixtureBuilder'; +import { + Utilities, + withNavigationScreenshots, + captureNavigationFlow, + captureScreenshot, + createScreenshotNavigationWrapper, +} from '../framework'; +import { loginToApp } from '../helpers'; +import TabBarComponent from '../pages/wallet/TabBarComponent'; +import WalletView from '../pages/wallet/WalletView'; +import SettingsView from '../pages/Settings/SettingsView'; +import BrowserView from '../pages/Browser/BrowserView'; +import { ScreenshotExamples } from '../tags'; + +describe(ScreenshotExamples('Screenshot Examples'), () => { + /** + * EXAMPLE 1: Manual Screenshots at Key Points + */ + it('Example 1: Manual screenshot capture', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Capture screenshot after login + await Utilities.takeScreenshot('01-after-login'); + + await TabBarComponent.tapBrowser(); + + // Capture screenshot after navigation + await Utilities.takeScreenshot('02-browser-screen'); + + await TabBarComponent.tapWallet(); + + // Capture screenshot with custom prefix + await Utilities.takeScreenshot('03-back-to-wallet', { + prefix: 'example-test', + }); + }, + ); + }); + + /** + * EXAMPLE 2: Automatic Screenshots on Navigation + */ + it('Example 2: Automatic screenshots with navigation wrapper', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Automatically capture screenshot after navigation + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + }, + { + name: 'navigate-to-browser', + captureAfterAction: true, + }, + ); + + // Capture before and after + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapWallet(); + }, + { + name: 'navigate-to-wallet', + captureBeforeAction: true, + captureAfterAction: true, + screenshotPrefix: 'wallet-nav', + }, + ); + }, + ); + }); + + /** + * EXAMPLE 3: Multi-Step Navigation Flow + */ + it('Example 3: Capture entire navigation flow', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Capture screenshots at each step of the flow + await captureNavigationFlow('settings-flow', [ + { + name: 'open-hamburger-menu', + action: async () => await WalletView.tapBurgerIcon(), + }, + { + name: 'tap-settings', + action: async () => await WalletView.tapSettings(), + }, + { + name: 'verify-settings-visible', + action: async () => { + // Just a verification step, but still captured + await new Promise((resolve) => setTimeout(resolve, 1000)); + }, + }, + ]); + }, + ); + }); + + /** + * EXAMPLE 4: Reusable Navigation Functions + */ + it('Example 4: Create reusable navigation with screenshots', async () => { + // Create reusable navigation functions + const navigateToBrowser = createScreenshotNavigationWrapper( + async () => await TabBarComponent.tapBrowser(), + 'navigate-to-browser', + ); + + const navigateToWallet = createScreenshotNavigationWrapper( + async () => await TabBarComponent.tapWallet(), + 'navigate-to-wallet', + ); + + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Use reusable functions - automatically captures screenshots + await navigateToBrowser(); + await navigateToWallet(); + }, + ); + }); + + /** + * EXAMPLE 5: Conditional Screenshot Capture + */ + it('Example 5: Conditional screenshots based on environment', async () => { + const ENABLE_SCREENSHOTS = process.env.E2E_SCREENSHOTS === 'true'; + + async function conditionalScreenshot(name: string): Promise { + if (ENABLE_SCREENSHOTS) { + await captureScreenshot(name); + } + } + + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await conditionalScreenshot('after-login'); + + await TabBarComponent.tapBrowser(); + await conditionalScreenshot('browser-opened'); + }, + ); + }); + + /** + * EXAMPLE 6: Screenshot with Error Handling + */ + it('Example 6: Automatic screenshot on failure', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // If this operation fails, a screenshot will be automatically captured + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + // If this assertion fails, screenshot is captured + await Utilities.waitForElementToBeVisible( + BrowserView.homeButton, + 5000, + ); + }, + { + name: 'navigate-to-browser-and-verify', + }, + ); + }, + ); + }); + + /** + * EXAMPLE 7: Complex Navigation with Multiple Screenshots + */ + it('Example 7: Complex flow with strategic screenshot placement', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await captureScreenshot('01-initial-wallet-view'); + + // Navigate to settings with screenshots + await captureNavigationFlow('navigate-to-security-settings', [ + { + name: 'open-menu', + action: () => WalletView.tapBurgerIcon(), + }, + { + name: 'open-settings', + action: () => WalletView.tapSettings(), + }, + { + name: 'open-security', + action: () => SettingsView.tapSecurity(), + }, + ]); + + await captureScreenshot('04-security-settings-view'); + + // Navigate back with screenshots + await withNavigationScreenshots(() => SettingsView.tapBackButton(), { + name: 'navigate-back-to-settings', + captureBeforeAction: true, + captureAfterAction: true, + }); + }, + ); + }); + + /** + * EXAMPLE 8: Using executeWithScreenshot for Any Operation + */ + it('Example 8: Wrap any operation with screenshot', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Wrap any operation, not just navigation + await Utilities.executeWithScreenshot( + async () => { + await TabBarComponent.tapBrowser(); + await BrowserView.tapUrlInputBox(); + // Do multiple actions + }, + { + name: 'open-browser-and-focus-url-input', + captureBeforeAction: false, + captureAfterAction: true, + screenshotPrefix: 'browser-setup', + timeout: 15000, + }, + ); + }, + ); + }); +}); diff --git a/e2e/docs/screenshots-navigation.md b/e2e/docs/screenshots-navigation.md new file mode 100644 index 000000000000..2c6270dd7922 --- /dev/null +++ b/e2e/docs/screenshots-navigation.md @@ -0,0 +1,461 @@ +# Automatic Screenshots for Navigation in E2E Tests + +This guide explains how to automatically capture screenshots during navigation actions in your E2E tests. + +## Overview + +The framework now provides built-in utilities for capturing screenshots during navigation: + +- `Utilities.takeScreenshot()` - Basic screenshot capture +- `Utilities.executeWithScreenshot()` - Execute any operation with automatic screenshots +- `withNavigationScreenshots()` - Wrapper specifically for navigation actions +- `captureNavigationFlow()` - Capture screenshots for multi-step navigation flows + +## Quick Start + +### 1. Simple Screenshot Capture + +```typescript +import { Utilities } from '../framework'; + +// Capture a screenshot manually +await Utilities.takeScreenshot('home-screen-loaded'); + +// With custom options +await Utilities.takeScreenshot('settings-screen', { + prefix: 'my-test', + timestamp: true, // default: true +}); +``` + +### 2. Wrap Navigation with Screenshots + +```typescript +import { withNavigationScreenshots } from '../framework'; +import TabBarComponent from '../pages/wallet/TabBarComponent'; + +// Automatically capture screenshot after navigation +await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + }, + { + name: 'navigate-to-browser', + captureAfterAction: true, + }, +); +``` + +### 3. Capture Before and After Navigation + +```typescript +import { withNavigationScreenshots } from '../framework'; +import CommonView from '../pages/CommonView'; + +await withNavigationScreenshots( + async () => { + await CommonView.tapBackButton(); + }, + { + name: 'navigate-back', + captureBeforeAction: true, // Capture before navigation + captureAfterAction: true, // Capture after navigation + screenshotPrefix: 'back-navigation', + }, +); +``` + +### 4. Multi-Step Navigation Flow + +```typescript +import { captureNavigationFlow } from '../framework'; +import WalletView from '../pages/wallet/WalletView'; +import SettingsView from '../pages/Settings/SettingsView'; +import Assertions from '../framework/Assertions'; + +await captureNavigationFlow('settings-flow', [ + { + name: 'tap-hamburger', + action: async () => await WalletView.tapBurgerIcon(), + }, + { + name: 'open-settings', + action: async () => await WalletView.tapSettings(), + }, + { + name: 'verify-settings-loaded', + action: async () => await Assertions.checkIfVisible(SettingsView.container), + }, +]); +``` + +## Usage Patterns + +### Pattern 1: Manual Screenshots at Key Points + +Use when you want full control over when screenshots are taken: + +```typescript +it('navigates through wallet screens', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await Utilities.takeScreenshot('01-after-login'); + + await TabBarComponent.tapBrowser(); + await Utilities.takeScreenshot('02-browser-screen'); + + await TabBarComponent.tapWallet(); + await Utilities.takeScreenshot('03-back-to-wallet'); + }, + ); +}); +``` + +### Pattern 2: Automatic Screenshots on Navigation + +Use when you want screenshots automatically captured for each navigation: + +```typescript +it('navigates through settings', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Each navigation automatically captures screenshots + await withNavigationScreenshots(() => WalletView.tapBurgerIcon(), { + name: 'open-hamburger-menu', + }); + + await withNavigationScreenshots(() => WalletView.tapSettings(), { + name: 'open-settings', + }); + + await withNavigationScreenshots(() => SettingsView.tapSecurity(), { + name: 'open-security-settings', + }); + }, + ); +}); +``` + +### Pattern 3: Create Reusable Navigation Functions + +Use when you have common navigation paths that should always capture screenshots: + +```typescript +import { createScreenshotNavigationWrapper } from '../framework'; + +// Create wrapper once +const navigateToBrowser = createScreenshotNavigationWrapper( + async () => await TabBarComponent.tapBrowser(), + 'navigate-to-browser', +); + +const navigateToSettings = createScreenshotNavigationWrapper( + async () => { + await WalletView.tapBurgerIcon(); + await WalletView.tapSettings(); + }, + 'navigate-to-settings', + { captureBeforeAction: true }, // Optional: capture before as well +); + +// Use in your tests +it('tests browser functionality', async () => { + await withFixtures( + { fixture: new FixtureBuilder().build(), restartDevice: true }, + async () => { + await loginToApp(); + await navigateToBrowser(); // Automatically captures screenshot + // ... rest of test + }, + ); +}); +``` + +### Pattern 4: Screenshot on Failure (Automatic) + +The `executeWithScreenshot` method automatically captures a screenshot if the operation fails: + +```typescript +await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + await Assertions.checkIfVisible(BrowserView.homeButton); + }, + { + name: 'navigate-to-browser-and-verify', + }, +); +// If this fails, a screenshot named 'navigation__navigate-to-browser-and-verify-failed' will be captured +``` + +## Advanced Usage + +### Custom Screenshot Naming + +```typescript +// Without timestamp +await Utilities.takeScreenshot('state-snapshot', { timestamp: false }); + +// With custom prefix +await Utilities.takeScreenshot('error-state', { + prefix: 'bug-123', + timestamp: true, +}); +// Result: bug-123_2024-10-17T10-30-45-123Z_error-state +``` + +### Integrate with Page Objects + +You can enhance your page objects to include screenshot capture: + +```typescript +class SettingsView { + // ... existing methods + + async navigateToSecurity( + options: { captureScreenshot?: boolean } = {}, + ): Promise { + if (options.captureScreenshot) { + await withNavigationScreenshots( + () => Gestures.waitAndTap(this.securityButton), + { name: 'navigate-to-security-settings' }, + ); + } else { + await Gestures.waitAndTap(this.securityButton); + } + } +} +``` + +### Environment-Based Screenshot Control + +Control screenshot capture based on environment variables: + +```typescript +const ENABLE_SCREENSHOTS = process.env.E2E_SCREENSHOTS === 'true'; + +async function conditionalScreenshot(name: string): Promise { + if (ENABLE_SCREENSHOTS) { + await Utilities.takeScreenshot(name); + } +} + +// Or create a wrapper +function maybeWithScreenshots( + fn: () => Promise, + name: string, +): Promise { + if (ENABLE_SCREENSHOTS) { + return withNavigationScreenshots(fn, { name }); + } + return fn(); +} +``` + +## Best Practices + +### DO: + +✅ Use descriptive, kebab-case names for screenshots +✅ Capture screenshots at significant navigation points +✅ Use `captureNavigationFlow` for multi-step flows +✅ Include prefixes for test-specific screenshot organization +✅ Leverage automatic failure screenshots for debugging + +### DON'T: + +❌ Don't capture screenshots in tight loops (performance impact) +❌ Don't use special characters in screenshot names (will be sanitized) +❌ Don't forget that screenshots are stored and can fill disk space +❌ Don't capture screenshots for every single action (be selective) + +## Screenshot Location + +Screenshots are saved by Detox to the default artifacts directory: + +- **iOS**: `artifacts/ios.sim.debug.MetaMask/` +- **Android**: `artifacts/android.emu.debug.MetaMask/` + +## Troubleshooting + +### Issue: Screenshots not being saved + +**Solution**: Ensure Detox is configured correctly in your `.detoxrc.json`: + +```json +{ + "artifacts": { + "rootDir": "./e2e/artifacts", + "plugins": { + "screenshot": "all" + } + } +} +``` + +### Issue: Screenshot names are sanitized unexpectedly + +**Solution**: Use alphanumeric characters, hyphens, and underscores only: + +```typescript +// Good +await Utilities.takeScreenshot('navigate-to-settings'); +await Utilities.takeScreenshot('step_01_login'); + +// Will be sanitized +await Utilities.takeScreenshot('navigate to settings'); // → navigate_to_settings +await Utilities.takeScreenshot('step#01 (login)'); // → step_01__login_ +``` + +### Issue: Too many screenshots slowing down tests + +**Solution**: Be selective about when to capture: + +```typescript +// Instead of capturing on every action +await Utilities.takeScreenshot('action-1'); +await SomeAction(); +await Utilities.takeScreenshot('action-2'); +await AnotherAction(); + +// Capture only at key milestones +await SomeAction(); +await AnotherAction(); +await Utilities.takeScreenshot('after-multiple-actions'); +``` + +## Examples from Real Tests + +### Example 1: Testing Browser Navigation + +```typescript +describe('Browser Navigation', () => { + it('navigates to dApp and connects wallet', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + await captureNavigationFlow('dapp-connection', [ + { + name: 'open-browser', + action: () => TabBarComponent.tapBrowser(), + }, + { + name: 'navigate-to-dapp', + action: () => + Browser.navigateToURL('https://metamask.github.io/test-dapp/'), + }, + { + name: 'connect-wallet', + action: () => TestDApp.tapConnectButton(), + }, + { + name: 'approve-connection', + action: () => PermissionSummaryBottomSheet.tapConnectButton(), + }, + ]); + + // Verify connection + await Assertions.checkIfVisible(TestDApp.connectedAccount); + }, + ); + }); +}); +``` + +### Example 2: Testing Settings Flow + +```typescript +describe('Settings', () => { + it('changes network settings', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + await Utilities.takeScreenshot('01-initial-state'); + + await withNavigationScreenshots( + async () => { + await WalletView.tapBurgerIcon(); + await WalletView.tapSettings(); + }, + { name: 'navigate-to-settings', captureAfterAction: true }, + ); + + await withNavigationScreenshots(() => SettingsView.tapNetworks(), { + name: 'open-network-settings', + }); + + await withNavigationScreenshots(() => NetworksView.tapAddNetwork(), { + name: 'open-add-network', + }); + + await Utilities.takeScreenshot('04-add-network-form'); + }, + ); + }); +}); +``` + +## API Reference + +### `Utilities.takeScreenshot(name, options)` + +- **name**: `string` - Name for the screenshot +- **options**: `{ prefix?: string, timestamp?: boolean }` +- **Returns**: `Promise` - Path to saved screenshot + +### `Utilities.executeWithScreenshot(operation, options)` + +- **operation**: `() => Promise` - Function to execute +- **options**: Configuration object + - `name`: `string` - Name describing the operation + - `captureBeforeAction?`: `boolean` - Capture before (default: false) + - `captureAfterAction?`: `boolean` - Capture after (default: true) + - `screenshotPrefix?`: `string` - Prefix for screenshots + - `timeout?`: `number` - Operation timeout +- **Returns**: `Promise` - Result of operation + +### `withNavigationScreenshots(navigationFn, options)` + +- **navigationFn**: `() => Promise` - Navigation function +- **options**: `NavigationWithScreenshotOptions` +- **Returns**: `Promise` - Result of navigation + +### `captureNavigationFlow(flowName, steps)` + +- **flowName**: `string` - Name for the flow +- **steps**: `Array<{ name: string, action: () => Promise }>` +- **Returns**: `Promise` + +### `createScreenshotNavigationWrapper(navigationFn, name, options)` + +- **navigationFn**: `() => Promise` - Navigation function +- **name**: `string` - Name for the navigation +- **options**: Optional configuration +- **Returns**: `() => Promise` - Wrapped function + +## Related Documentation + +- [E2E Testing Guidelines](./README.md) +- [Framework Documentation](../framework/README.md) +- [Detox Artifacts Documentation](https://wix.github.io/Detox/docs/api/artifacts/) diff --git a/e2e/framework/NavigationHelpers.ts b/e2e/framework/NavigationHelpers.ts new file mode 100644 index 000000000000..5c16ef0c4e05 --- /dev/null +++ b/e2e/framework/NavigationHelpers.ts @@ -0,0 +1,131 @@ +/** + * Navigation Helpers with Automatic Screenshot Capture + * + * This module provides utilities for wrapping navigation actions with automatic screenshot capture. + * Use these helpers when you want to document navigation flows in your tests. + */ + +import Utilities from './Utilities'; + +/** + * Options for navigation with screenshot capture + */ +export interface NavigationWithScreenshotOptions { + /** Name describing the navigation action (e.g., "navigate-to-settings") */ + name: string; + /** Whether to capture screenshot before navigation */ + captureBeforeAction?: boolean; + /** Whether to capture screenshot after navigation */ + captureAfterAction?: boolean; + /** Optional prefix for screenshot names */ + screenshotPrefix?: string; + /** Timeout for the navigation action */ + timeout?: number; +} + +/** + * Wraps a navigation function with automatic screenshot capture + * + * @example + * ```typescript + * await withNavigationScreenshots( + * async () => { + * await TabBarComponent.tapBrowser(); + * }, + * { + * name: 'navigate-to-browser', + * captureAfterAction: true, + * } + * ); + * ``` + */ +export async function withNavigationScreenshots( + navigationFn: () => Promise, + options: NavigationWithScreenshotOptions, +): Promise { + const { + name, + captureBeforeAction = false, + captureAfterAction = true, + screenshotPrefix = 'navigation', + timeout = 15000, + } = options; + + return Utilities.executeWithScreenshot(navigationFn, { + name, + captureBeforeAction, + captureAfterAction, + screenshotPrefix, + timeout, + }); +} + +/** + * Creates a navigation function wrapper that always captures screenshots + * Useful for creating reusable navigation methods that always document their actions + * + * @example + * ```typescript + * const navigateToBrowser = createScreenshotNavigationWrapper( + * async () => await TabBarComponent.tapBrowser(), + * 'navigate-to-browser' + * ); + * + * // Later in your test: + * await navigateToBrowser(); + * ``` + */ +export function createScreenshotNavigationWrapper( + navigationFn: () => Promise, + name: string, + options: Omit = {}, +): () => Promise { + return async () => + withNavigationScreenshots(navigationFn, { + name, + ...options, + }); +} + +/** + * Helper to capture a screenshot with a descriptive name + * This is a convenience wrapper around Utilities.takeScreenshot + * + * @example + * ```typescript + * await captureScreenshot('home-screen-loaded'); + * ``` + */ +export async function captureScreenshot( + name: string, + options: { prefix?: string; timestamp?: boolean } = {}, +): Promise { + return Utilities.takeScreenshot(name, options); +} + +/** + * Capture screenshots at multiple stages of a multi-step navigation + * + * @example + * ```typescript + * await captureNavigationFlow('settings-flow', [ + * { name: 'tap-hamburger', action: async () => await WalletView.tapBurgerIcon() }, + * { name: 'open-settings', action: async () => await WalletView.tapSettings() }, + * { name: 'settings-loaded', action: async () => await Assertions.checkIfVisible(SettingsView.container) }, + * ]); + * ``` + */ +export async function captureNavigationFlow( + flowName: string, + steps: { name: string; action: () => Promise }[], +): Promise { + for (let i = 0; i < steps.length; i++) { + const step = steps[i]; + await Utilities.executeWithScreenshot(step.action, { + name: `${flowName}-step-${i + 1}-${step.name}`, + captureAfterAction: true, + screenshotPrefix: flowName, + timeout: 15000, + }); + } +} diff --git a/e2e/framework/Utilities.ts b/e2e/framework/Utilities.ts index f2a036f922ea..f9df4db389c0 100644 --- a/e2e/framework/Utilities.ts +++ b/e2e/framework/Utilities.ts @@ -416,6 +416,92 @@ export default class Utilities { } } + /** + * Take a screenshot with a descriptive name + * @param name - Name for the screenshot (will be sanitized) + * @param options - Optional settings for the screenshot + * @returns Promise - Path to the saved screenshot + */ + static async takeScreenshot( + name: string, + options: { prefix?: string; timestamp?: boolean } = {}, + ): Promise { + const { prefix = '', timestamp = true } = options; + + // Sanitize the name to remove invalid characters + const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_'); + + // Build screenshot name + const parts = [ + prefix, + timestamp ? new Date().toISOString().replace(/[:.]/g, '-') : '', + sanitizedName, + ].filter(Boolean); + + const screenshotName = parts.join('_'); + + try { + const screenshotPath = await device.takeScreenshot(screenshotName); + logger.debug(`📸 Screenshot saved: ${screenshotName}`); + return screenshotPath; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logger.warn(`⚠️ Failed to take screenshot: ${errorMessage}`); + throw error; + } + } + + /** + * Execute an operation and automatically take screenshots before and/or after + * @param operation - The operation to execute + * @param options - Configuration including screenshot settings + */ + static async executeWithScreenshot( + operation: () => Promise, + options: { + name: string; + captureBeforeAction?: boolean; + captureAfterAction?: boolean; + screenshotPrefix?: string; + } & Omit, + ): Promise { + const { + name, + captureBeforeAction = false, + captureAfterAction = true, + screenshotPrefix = '', + ...retryOptions + } = options; + + if (captureBeforeAction) { + await this.takeScreenshot(`${name}-before`, { + prefix: screenshotPrefix, + }); + } + + try { + const result = await this.executeWithRetry(operation, { + ...retryOptions, + description: name, + }); + + if (captureAfterAction) { + await this.takeScreenshot(`${name}-after`, { + prefix: screenshotPrefix, + }); + } + + return result; + } catch (error) { + // Take a screenshot on failure + await this.takeScreenshot(`${name}-failed`, { + prefix: screenshotPrefix, + }); + throw error; + } + } + static async executeWithRetry( operation: () => Promise, options: RetryOptions, diff --git a/e2e/framework/index.ts b/e2e/framework/index.ts index 51f836de4e6a..24e347452801 100644 --- a/e2e/framework/index.ts +++ b/e2e/framework/index.ts @@ -5,6 +5,7 @@ export { default as Matchers } from './Matchers'; export { default as Utilities, BASE_DEFAULTS, sleep } from './Utilities'; export { Logger, createLogger, LogLevel, logger } from './logger'; export * from './types'; +export * from './NavigationHelpers'; // Example usage: // import { Assertions, Gestures, Matchers, sleep } from '../framework'; diff --git a/e2e/scripts/run-screenshot-examples.sh b/e2e/scripts/run-screenshot-examples.sh new file mode 100755 index 000000000000..92735c2bdc78 --- /dev/null +++ b/e2e/scripts/run-screenshot-examples.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Script to run screenshot examples E2E tests locally +# Usage: ./e2e/scripts/run-screenshot-examples.sh [ios|android] + +set -e + +PLATFORM="${1:-ios}" +TEST_TAG="ScreenshotExamples:" + +echo "🧪 Running Screenshot Examples E2E Tests" +echo "Platform: $PLATFORM" +echo "Test Tag: $TEST_TAG" +echo "" + +# Set environment variable +export TEST_SUITE_TAG="$TEST_TAG" + +# Clean previous test artifacts +echo "🧹 Cleaning previous test artifacts..." +rm -rf e2e/artifacts/* + +# Run tests based on platform +if [ "$PLATFORM" = "ios" ]; then + echo "📱 Running tests on iOS..." + yarn test:e2e:ios --testNamePattern="$TEST_TAG" +elif [ "$PLATFORM" = "android" ]; then + echo "📱 Running tests on Android..." + yarn test:e2e:android --testNamePattern="$TEST_TAG" +else + echo "❌ Invalid platform: $PLATFORM" + echo "Usage: $0 [ios|android]" + exit 1 +fi + +# Show artifacts location +echo "" +echo "✅ Tests complete!" +echo "" +echo "📸 Screenshots saved to:" +if [ "$PLATFORM" = "ios" ]; then + echo " e2e/artifacts/ios.sim.debug.MetaMask/" +else + echo " e2e/artifacts/android.emu.debug.MetaMask/" +fi +echo "" +echo "To view screenshots, check the artifacts directory above." + diff --git a/e2e/specs/SCREENSHOT_EXAMPLES_SETUP.md b/e2e/specs/SCREENSHOT_EXAMPLES_SETUP.md new file mode 100644 index 000000000000..33090d2d5ec6 --- /dev/null +++ b/e2e/specs/SCREENSHOT_EXAMPLES_SETUP.md @@ -0,0 +1,294 @@ +# Screenshot Examples Test Suite - Setup Complete ✅ + +## What Was Configured + +The screenshot examples test suite is now fully configured to run independently in your E2E workflow. + +## Files Modified/Created + +### Modified: + +1. **`e2e/tags.js`** + + - Added `screenshotExamples: 'ScreenshotExamples:'` tag + - Exported `ScreenshotExamples` function + +2. **`e2e/specs/screenshot-examples.spec.ts`** (moved from `e2e/docs/`) + - Added `ScreenshotExamples` tag to test suite + - Now runs with filtered execution + +### Created: + +3. **`.github/workflows/run-screenshot-examples.yml`** + + - Dedicated workflow for running screenshot examples + - Can be triggered manually from GitHub Actions UI + +4. **`e2e/scripts/run-screenshot-examples.sh`** + + - Bash script for running tests locally + - Handles cleanup and artifact location display + +5. **`e2e/specs/screenshot-examples.md`** + - Documentation for running the test suite + - Contains workflow configuration examples + +## How to Run + +### Option 1: GitHub Actions (CI/CD) + +#### Via GitHub UI: + +1. Go to **Actions** tab in GitHub +2. Select **Screenshot Examples E2E Tests** workflow +3. Click **Run workflow** +4. Choose platform (`ios` or `android`) +5. Click **Run workflow** button + +#### Via Another Workflow: + +```yaml +jobs: + test-screenshots: + uses: ./.github/workflows/run-screenshot-examples.yml + with: + platform: ios + secrets: inherit +``` + +### Option 2: Local Script + +```bash +# iOS +./e2e/scripts/run-screenshot-examples.sh ios + +# Android +./e2e/scripts/run-screenshot-examples.sh android +``` + +### Option 3: Direct Jest Command + +```bash +# iOS +export TEST_SUITE_TAG="ScreenshotExamples:" +yarn test:e2e:ios --testNamePattern="ScreenshotExamples:" + +# Android +export TEST_SUITE_TAG="ScreenshotExamples:" +yarn test:e2e:android --testNamePattern="ScreenshotExamples:" +``` + +### Option 4: Run Specific Example + +```bash +# Run only Example 1 +yarn test:e2e:ios --testNamePattern="Example 1: Manual screenshot capture" + +# Run only Example 3 +yarn test:e2e:ios --testNamePattern="Example 3: Capture entire navigation flow" +``` + +## Workflow Integration + +### Using the Dedicated Workflow + +The dedicated workflow `.github/workflows/run-screenshot-examples.yml` is configured to: + +- ✅ Run only screenshot example tests +- ✅ Support both iOS and Android +- ✅ Be manually triggered via GitHub Actions UI +- ✅ Use the main E2E workflow infrastructure + +### Calling from Another Workflow + +You can integrate it into your existing workflows: + +```yaml +name: My PR Tests +on: pull_request + +jobs: + screenshot-examples: + name: Run Screenshot Examples + uses: ./.github/workflows/run-screenshot-examples.yml + with: + platform: ios + secrets: inherit +``` + +### Using the Base Workflow Directly + +You can also call the base `run-e2e-workflow.yml` directly: + +```yaml +jobs: + my-screenshot-tests: + uses: ./.github/workflows/run-e2e-workflow.yml + with: + test-suite-name: screenshot-examples + platform: ios + test_suite_tag: 'ScreenshotExamples:' + test-timeout-minutes: 30 + split_number: 1 + total_splits: 1 + secrets: inherit +``` + +## Test Suite Structure + +### Test File Location + +``` +e2e/specs/screenshot-examples.spec.ts +``` + +### Tag Definition + +```javascript +// In e2e/tags.js +screenshotExamples: 'ScreenshotExamples:'; + +// Usage in test file +import { ScreenshotExamples } from '../tags'; + +describe(ScreenshotExamples('Screenshot Examples'), () => { + // 8 example tests demonstrating screenshot capture patterns +}); +``` + +### Examples Included + +1. ✅ Manual Screenshots at Key Points +2. ✅ Automatic Screenshots on Navigation +3. ✅ Multi-Step Navigation Flow +4. ✅ Reusable Navigation Functions +5. ✅ Conditional Screenshot Capture +6. ✅ Screenshot with Error Handling +7. ✅ Complex Navigation with Multiple Screenshots +8. ✅ Using executeWithScreenshot for Any Operation + +## Screenshot Artifacts + +After running tests, screenshots will be saved to: + +**iOS:** + +``` +e2e/artifacts/ios.sim.debug.MetaMask/ +``` + +**Android:** + +``` +e2e/artifacts/android.emu.debug.MetaMask/ +``` + +### Screenshot Naming Pattern + +``` +[prefix]_[timestamp]_[action-name]-[suffix].png +``` + +Examples: + +- `navigation_2024-10-17T10-30-45-123Z_navigate-to-browser-after.png` +- `settings-flow_2024-10-17T10-30-45-123Z_step-1-open-menu-after.png` +- `navigation_2024-10-17T10-30-45-123Z_navigate-to-settings-failed.png` + +## Verifying the Setup + +### Test Locally: + +```bash +# Quick test on iOS +./e2e/scripts/run-screenshot-examples.sh ios + +# Check that screenshots are generated +ls -la e2e/artifacts/ios.sim.debug.MetaMask/ +``` + +### Test in CI: + +1. Push changes to your branch +2. Go to GitHub Actions +3. Run **Screenshot Examples E2E Tests** workflow +4. Download artifacts after completion +5. Verify screenshots are present + +## Environment Variables + +The test suite respects the following environment variables: + +```bash +# Filter tests by tag (set automatically by script) +export TEST_SUITE_TAG="ScreenshotExamples:" + +# Optional: Enable/disable screenshots (for conditional capture) +export E2E_SCREENSHOTS="true" +``` + +## Troubleshooting + +### Tests Not Running + +**Issue**: No tests found or wrong tests running + +**Solution**: Ensure the tag is correctly set: + +```bash +export TEST_SUITE_TAG="ScreenshotExamples:" +# Note the colon at the end! +``` + +### Screenshots Not Generated + +**Issue**: Tests pass but no screenshots in artifacts + +**Solution**: Check that: + +1. Detox artifacts configuration is correct in `.detoxrc.json` +2. The app launches successfully on simulator/emulator +3. Tests are actually calling screenshot capture functions + +### Workflow Not Appearing + +**Issue**: New workflow doesn't show up in GitHub Actions + +**Solution**: + +1. Ensure the workflow file is in `.github/workflows/` +2. Push the file to GitHub +3. Refresh the Actions tab + +## Next Steps + +1. ✅ **Test Locally**: Run `./e2e/scripts/run-screenshot-examples.sh ios` +2. ✅ **Test in CI**: Trigger the workflow manually in GitHub Actions +3. ✅ **Review Screenshots**: Check the artifacts directory +4. ✅ **Copy Patterns**: Use examples in your own tests + +## Related Documentation + +- 📖 [Quick Start Guide](../docs/SCREENSHOTS_QUICKSTART.md) +- 📚 [Full Documentation](../docs/screenshots-navigation.md) +- 📋 [Implementation Summary](../docs/SCREENSHOT_IMPLEMENTATION_SUMMARY.md) +- 📝 [Running Instructions](./screenshot-examples.md) + +## Summary + +You now have: + +- ✅ A tagged test suite (`ScreenshotExamples:`) +- ✅ Dedicated GitHub Actions workflow +- ✅ Local execution script +- ✅ Complete documentation +- ✅ 8 working examples demonstrating screenshot patterns + +The test suite can now be run independently via: + +- GitHub Actions UI (manual trigger) +- Workflow calls from other workflows +- Local script execution +- Direct Jest commands + +**Ready to use!** 🚀 diff --git a/e2e/specs/screenshot-examples.md b/e2e/specs/screenshot-examples.md new file mode 100644 index 000000000000..58612dbee69b --- /dev/null +++ b/e2e/specs/screenshot-examples.md @@ -0,0 +1,135 @@ +# Running Screenshot Examples Tests + +## Overview + +The screenshot examples test suite demonstrates various patterns for capturing screenshots during navigation in E2E tests. This suite is tagged with `ScreenshotExamples:` and can be run independently. + +## Quick Start + +### Run Locally + +To run only the screenshot examples tests locally: + +```bash +# iOS +export TEST_SUITE_TAG="ScreenshotExamples:" +yarn test:e2e:ios --testNamePattern="ScreenshotExamples:" + +# Android +export TEST_SUITE_TAG="ScreenshotExamples:" +yarn test:e2e:android --testNamePattern="ScreenshotExamples:" +``` + +### Run Specific Example + +To run a specific example test: + +```bash +# Run only Example 1 +yarn test:e2e:ios --testNamePattern="Example 1: Manual screenshot capture" + +# Run only Example 3 +yarn test:e2e:ios --testNamePattern="Example 3: Capture entire navigation flow" +``` + +### View Screenshots + +After running the tests, screenshots will be available in: + +- **iOS**: `e2e/artifacts/ios.sim.debug.MetaMask/` +- **Android**: `e2e/artifacts/android.emu.debug.MetaMask/` + +## CI/CD Workflow + +To run this test suite in the GitHub Actions workflow, use the `run-e2e-workflow.yml` with: + +```yaml +with: + test-suite-name: 'screenshot-examples' + platform: 'ios' # or 'android' + test_suite_tag: 'ScreenshotExamples:' + test-timeout-minutes: 30 +``` + +### Manual Workflow Trigger + +You can also trigger the workflow manually via GitHub UI: + +1. Go to **Actions** tab +2. Select **Run E2E** workflow +3. Click **Run workflow** +4. Fill in: + - **test-suite-name**: `screenshot-examples` + - **platform**: `ios` or `android` + - **test_suite_tag**: `ScreenshotExamples:` + - **test-timeout-minutes**: `30` + +### Example Workflow Call + +```yaml +jobs: + screenshot-examples: + uses: ./.github/workflows/run-e2e-workflow.yml + with: + test-suite-name: screenshot-examples + platform: ios + test_suite_tag: 'ScreenshotExamples:' + test-timeout-minutes: 30 + split_number: 1 + total_splits: 1 +``` + +## Test Suite Contents + +The screenshot examples suite includes 8 different patterns: + +1. **Example 1**: Manual Screenshots at Key Points +2. **Example 2**: Automatic Screenshots on Navigation +3. **Example 3**: Multi-Step Navigation Flow +4. **Example 4**: Reusable Navigation Functions +5. **Example 5**: Conditional Screenshot Capture +6. **Example 6**: Screenshot with Error Handling +7. **Example 7**: Complex Navigation with Multiple Screenshots +8. **Example 8**: Using executeWithScreenshot for Any Operation + +## Expected Behavior + +When running this test suite: + +✅ Tests demonstrate screenshot capture patterns +✅ Screenshots are saved with timestamped names +✅ Both successful and failed operations capture screenshots +✅ All tests should pass and generate visual artifacts + +## Debugging + +If tests fail: + +1. Check the screenshot artifacts directory +2. Review the test logs for error messages +3. Verify the app is properly launched on the simulator/emulator +4. Ensure Detox is configured correctly + +## Test Tag + +This test suite uses the tag: **`ScreenshotExamples:`** + +The tag is defined in `e2e/tags.js`: + +```javascript +screenshotExamples: 'ScreenshotExamples:', +``` + +And applied to the test suite via: + +```javascript +describe(ScreenshotExamples('Screenshot Examples'), () => { + // tests... +}); +``` + +## Related Documentation + +- [Screenshot Quick Start Guide](../docs/SCREENSHOTS_QUICKSTART.md) +- [Full Screenshot Documentation](../docs/screenshots-navigation.md) +- [Implementation Summary](../docs/SCREENSHOT_IMPLEMENTATION_SUMMARY.md) diff --git a/e2e/specs/screenshot-examples.spec.ts b/e2e/specs/screenshot-examples.spec.ts new file mode 100644 index 000000000000..256e5905ae05 --- /dev/null +++ b/e2e/specs/screenshot-examples.spec.ts @@ -0,0 +1,289 @@ +/** + * Example Test File: Screenshots with Navigation + * + * This file demonstrates various patterns for capturing screenshots + * during navigation in E2E tests. + * + * NOTE: This is an example file for documentation purposes. + * You can copy these patterns into your actual test files. + */ + +import { withFixtures } from '../framework/fixtures/FixtureHelper'; +import FixtureBuilder from '../framework/fixtures/FixtureBuilder'; +import { + Utilities, + withNavigationScreenshots, + captureNavigationFlow, + captureScreenshot, + createScreenshotNavigationWrapper, +} from '../framework'; +import { loginToApp } from '../viewHelper'; +import TabBarComponent from '../pages/wallet/TabBarComponent'; +import WalletView from '../pages/wallet/WalletView'; +import SettingsView from '../pages/Settings/SettingsView'; +import BrowserView from '../pages/Browser/BrowserView'; +import { ScreenshotExamples } from '../tags'; + +describe(ScreenshotExamples('Screenshot Examples'), () => { + /** + * EXAMPLE 1: Manual Screenshots at Key Points + */ + it('Example 1: Manual screenshot capture', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Capture screenshot after login + await Utilities.takeScreenshot('01-after-login'); + + await TabBarComponent.tapBrowser(); + + // Capture screenshot after navigation + await Utilities.takeScreenshot('02-browser-screen'); + + await TabBarComponent.tapWallet(); + + // Capture screenshot with custom prefix + await Utilities.takeScreenshot('03-back-to-wallet', { + prefix: 'example-test', + }); + }, + ); + }); + + /** + * EXAMPLE 2: Automatic Screenshots on Navigation + */ + it('Example 2: Automatic screenshots with navigation wrapper', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Automatically capture screenshot after navigation + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + }, + { + name: 'navigate-to-browser', + captureAfterAction: true, + }, + ); + + // Capture before and after + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapWallet(); + }, + { + name: 'navigate-to-wallet', + captureBeforeAction: true, + captureAfterAction: true, + screenshotPrefix: 'wallet-nav', + }, + ); + }, + ); + }); + + /** + * EXAMPLE 3: Multi-Step Navigation Flow + */ + it('Example 3: Capture entire navigation flow', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Capture screenshots at each step of the flow + await captureNavigationFlow('settings-flow', [ + { + name: 'open-hamburger-menu', + action: async () => await WalletView.tapBurgerIcon(), + }, + { + name: 'tap-settings', + action: async () => await WalletView.tapSettings(), + }, + { + name: 'verify-settings-visible', + action: async () => { + // Just a verification step, but still captured + await new Promise((resolve) => setTimeout(resolve, 1000)); + }, + }, + ]); + }, + ); + }); + + /** + * EXAMPLE 4: Reusable Navigation Functions + */ + it('Example 4: Create reusable navigation with screenshots', async () => { + // Create reusable navigation functions + const navigateToBrowser = createScreenshotNavigationWrapper( + async () => await TabBarComponent.tapBrowser(), + 'navigate-to-browser', + ); + + const navigateToWallet = createScreenshotNavigationWrapper( + async () => await TabBarComponent.tapWallet(), + 'navigate-to-wallet', + ); + + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Use reusable functions - automatically captures screenshots + await navigateToBrowser(); + await navigateToWallet(); + }, + ); + }); + + /** + * EXAMPLE 5: Conditional Screenshot Capture + */ + it('Example 5: Conditional screenshots based on environment', async () => { + const ENABLE_SCREENSHOTS = process.env.E2E_SCREENSHOTS === 'true'; + + async function conditionalScreenshot(name: string): Promise { + if (ENABLE_SCREENSHOTS) { + await captureScreenshot(name); + } + } + + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await conditionalScreenshot('after-login'); + + await TabBarComponent.tapBrowser(); + await conditionalScreenshot('browser-opened'); + }, + ); + }); + + /** + * EXAMPLE 6: Screenshot with Error Handling + */ + it('Example 6: Automatic screenshot on failure', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // If this operation fails, a screenshot will be automatically captured + await withNavigationScreenshots( + async () => { + await TabBarComponent.tapBrowser(); + // If this assertion fails, screenshot is captured + await Utilities.waitForElementToBeVisible( + BrowserView.homeButton, + 5000, + ); + }, + { + name: 'navigate-to-browser-and-verify', + }, + ); + }, + ); + }); + + /** + * EXAMPLE 7: Complex Navigation with Multiple Screenshots + */ + it('Example 7: Complex flow with strategic screenshot placement', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + await captureScreenshot('01-initial-wallet-view'); + + // Navigate to settings with screenshots + await captureNavigationFlow('navigate-to-security-settings', [ + { + name: 'open-menu', + action: () => WalletView.tapBurgerIcon(), + }, + { + name: 'open-settings', + action: () => WalletView.tapSettings(), + }, + { + name: 'open-security', + action: () => SettingsView.tapSecurity(), + }, + ]); + + await captureScreenshot('04-security-settings-view'); + + // Navigate back with screenshots + await withNavigationScreenshots(() => SettingsView.tapBackButton(), { + name: 'navigate-back-to-settings', + captureBeforeAction: true, + captureAfterAction: true, + }); + }, + ); + }); + + /** + * EXAMPLE 8: Using executeWithScreenshot for Any Operation + */ + it('Example 8: Wrap any operation with screenshot', async () => { + await withFixtures( + { + fixture: new FixtureBuilder().build(), + restartDevice: true, + }, + async () => { + await loginToApp(); + + // Wrap any operation, not just navigation + await Utilities.executeWithScreenshot( + async () => { + await TabBarComponent.tapBrowser(); + await BrowserView.tapUrlInputBox(); + // Do multiple actions + }, + { + name: 'open-browser-and-focus-url-input', + captureBeforeAction: false, + captureAfterAction: true, + screenshotPrefix: 'browser-setup', + timeout: 15000, + }, + ); + }, + ); + }); +}); diff --git a/e2e/tags.js b/e2e/tags.js index 47be1a6466ac..2cd0613908c1 100644 --- a/e2e/tags.js +++ b/e2e/tags.js @@ -29,7 +29,7 @@ const tags = { FlaskBuildTests: 'FlaskBuildTests:', performance: 'Performance:', smokeCard: 'SmokeCard:', - regressionSampleFeature: 'RegressionSampleFeature:', + screenshotExamples: 'ScreenshotExamples:', }; const RegressionAccounts = (testName) => @@ -80,6 +80,8 @@ const FlaskBuildTests = (testName) => `${tags.FlaskBuildTests} ${testName}`; const RegressionSampleFeature = (testName) => `${tags.regressionSampleFeature} ${testName}`; const SmokePerformance = (testName) => `${tags.performance} ${testName}`; +const ScreenshotExamples = (testName) => + `${tags.screenshotExamples} ${testName}`; export { FlaskBuildTests, @@ -112,6 +114,6 @@ export { SmokeCard, SmokeWalletUX, RegressionWalletUX, - RegressionSampleFeature, + ScreenshotExamples, tags, };