|
| 1 | +--- |
| 2 | +name: performance-testing |
| 3 | +description: Create and review E2E performance tests that measure real user flows on real devices with TimerHelper and PerformanceTracker. Use when creating, editing, or reviewing performance tests, when the user mentions perf tests, timing measurements, performance thresholds, or files in tests/performance/. |
| 4 | +--- |
| 5 | + |
| 6 | +# E2E Performance Testing |
| 7 | + |
| 8 | +For the full reference guide with templates, examples, and decision trees, read [reference.md](reference.md). |
| 9 | + |
| 10 | +## Core Rules |
| 11 | + |
| 12 | +1. **Real device, real app, real network** — zero mocking |
| 13 | +2. **Actions OUTSIDE `measure()`, assertions INSIDE `measure()`** — measure app response time, not interactions |
| 14 | +3. **One `TimerHelper` per measurable step** with platform-specific thresholds `{ ios: <ms>, android: <ms> }` |
| 15 | +4. **User-centric timer descriptions**: _"Time since the user clicks X until Y is visible"_ |
| 16 | +5. **Screen Object pattern** — all UI via `wdio/screen-objects/`, never raw selectors |
| 17 | +6. **Every screen object** must have `device` assigned before use |
| 18 | +7. **Every test** must call `performanceTracker.addTimers()` + `performanceTracker.attachToTest(testInfo)` |
| 19 | +8. **Performance + team tags** are mandatory on every test |
| 20 | + |
| 21 | +## File Location |
| 22 | + |
| 23 | +| Starting condition | Folder | |
| 24 | +| -------------------------------------- | ------------------------------- | |
| 25 | +| User already has wallet (login screen) | `tests/performance/login/` | |
| 26 | +| Fresh install (onboarding) | `tests/performance/onboarding/` | |
| 27 | +| Dapp connection needed | `tests/performance/mm-connect/` | |
| 28 | + |
| 29 | +## Quick Template (login-based) |
| 30 | + |
| 31 | +```js |
| 32 | +import { test } from '../../framework/fixtures/performance'; |
| 33 | +import TimerHelper from '../../framework/TimerHelper'; |
| 34 | +import { login } from '../../framework/utils/Flows.js'; |
| 35 | +import { PerformanceLogin } from '../../tags.performance.js'; |
| 36 | + |
| 37 | +test.describe(`${PerformanceLogin}`, () => { |
| 38 | + test( |
| 39 | + 'Descriptive name', |
| 40 | + { tag: '@team-name' }, |
| 41 | + async ({ device, performanceTracker }, testInfo) => { |
| 42 | + ScreenA.device = device; |
| 43 | + ScreenB.device = device; |
| 44 | + |
| 45 | + await login(device); |
| 46 | + |
| 47 | + const timer = new TimerHelper( |
| 48 | + 'Time since the user clicks X until Y is visible', |
| 49 | + { ios: 2000, android: 3000 }, |
| 50 | + device, |
| 51 | + ); |
| 52 | + |
| 53 | + await ScreenA.tapButton(); // action OUTSIDE |
| 54 | + await timer.measure(() => ScreenB.isVisible()); // assertion INSIDE |
| 55 | + |
| 56 | + performanceTracker.addTimers(timer); |
| 57 | + await performanceTracker.attachToTest(testInfo); |
| 58 | + }, |
| 59 | + ); |
| 60 | +}); |
| 61 | +``` |
| 62 | + |
| 63 | +## Modifying Existing Tests |
| 64 | + |
| 65 | +When editing an existing test in `tests/performance/`, follow these rules: |
| 66 | + |
| 67 | +### Thresholds |
| 68 | + |
| 69 | +- **Never tighten thresholds without baseline data** — run the test 3+ times first to collect real timings |
| 70 | +- **Never remove thresholds** — every timer must keep `{ ios, android }` values |
| 71 | +- **Document why** in the PR if changing a threshold (e.g., "tightened after 10 runs averaging 1.2s") |
| 72 | +- If a test is consistently failing quality gates, **widen the threshold** rather than deleting the timer |
| 73 | + |
| 74 | +### Adding/Removing Timers |
| 75 | + |
| 76 | +- When adding a new timer to an existing test, register it in the existing `performanceTracker.addTimers()` call |
| 77 | +- When removing a timer, ensure it's also removed from `addTimers()` — orphaned timers cause silent failures |
| 78 | +- Never reduce a test to zero timers — if the flow no longer needs measurement, delete the test file |
| 79 | + |
| 80 | +### Screen Object Changes |
| 81 | + |
| 82 | +- If a screen object method is renamed or removed, **grep all `tests/performance/`** for usages before changing |
| 83 | +- When adding methods to a screen object in `wdio/screen-objects/`, ensure backward compatibility — existing tests must not break |
| 84 | +- New screen objects must follow the same `device` getter/setter pattern |
| 85 | + |
| 86 | +### UI Flow Changes |
| 87 | + |
| 88 | +- If the app UI changes (new screen, removed step, renamed button), update **all** perf tests that use that flow |
| 89 | +- Re-run the affected tests to verify timers still measure the intended transition |
| 90 | +- Update timer descriptions if the user-facing flow changed (e.g., button was renamed) |
| 91 | + |
| 92 | +### Refactoring |
| 93 | + |
| 94 | +- Extract shared flows into `tests/framework/utils/Flows.js` (e.g., `login`, `importSRPFlow`) |
| 95 | +- If multiple tests duplicate the same setup, create a helper that returns `TimerHelper[]` |
| 96 | +- Keep `screensSetup(device)` pattern for tests with many screen objects (see `perps-position-management.spec.js`) |
| 97 | + |
| 98 | +### Code Review Checklist for Modifications |
| 99 | + |
| 100 | +- [ ] No existing timer was accidentally removed or left unregistered |
| 101 | +- [ ] Threshold changes are justified with data |
| 102 | +- [ ] `attachToTest(testInfo)` is still called at the end |
| 103 | +- [ ] All screen objects still have `device` assigned |
| 104 | +- [ ] Timer descriptions still match the actual flow being measured |
| 105 | +- [ ] Actions are still OUTSIDE `measure()`, assertions INSIDE |
| 106 | + |
| 107 | +## Forbidden Patterns |
| 108 | + |
| 109 | +- `jest.mock(...)` — no mocking |
| 110 | +- `import { test } from 'appwright'` — always from `fixtures/performance` |
| 111 | +- Actions inside `measure()` — only assertions/waits |
| 112 | +- Missing `device` assignment on screen objects |
| 113 | +- Timers without thresholds |
| 114 | +- Missing team tag |
| 115 | +- Hardcoded passwords — use `getPasswordForScenario()` |
| 116 | + |
| 117 | +## Threshold Ranges |
| 118 | + |
| 119 | +| Action type | iOS | Android | |
| 120 | +| ------------------------------- | ------------- | -------------- | |
| 121 | +| Simple screen transition | 500–1500 ms | 600–1800 ms | |
| 122 | +| Data loading (API + render) | 1500–5000 ms | 2000–7000 ms | |
| 123 | +| Dapp connection (cross-context) | 8000–20000 ms | 12000–30000 ms | |
| 124 | +| Quote/swap execution | 7000–9000 ms | 7000–9000 ms | |
| 125 | + |
| 126 | +## Key References |
| 127 | + |
| 128 | +- Full guide: [reference.md](reference.md) |
| 129 | +- Performance fixture: `tests/framework/fixtures/performance/performance-fixture.ts` |
| 130 | +- TimerHelper: `tests/framework/TimerHelper.ts` |
| 131 | +- Tags: `tests/tags.performance.js` |
| 132 | +- Flows: `tests/framework/utils/Flows.js` |
| 133 | +- Screen objects: `wdio/screen-objects/` |
| 134 | +- Examples: `tests/performance/login/`, `tests/performance/onboarding/` |
0 commit comments