Skip to content

Commit fb6bb74

Browse files
author
greweb
committed
up
1 parent 2082550 commit fb6bb74

15 files changed

Lines changed: 3711 additions & 8239 deletions

.prototools

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
node = "22"
2-
yarn = "4"

example/.npmrc

Lines changed: 0 additions & 3 deletions
This file was deleted.

example/E2E-TESTING.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ The test suite validates:
6767
### Smart UI Interaction
6868

6969
The tests automatically:
70+
7071
- 🔍 **Scroll to find elements** that are off-screen
7172
- 📸 **Capture screenshots** of the app state
7273
- 📊 **Validate snapshots** against reference images
@@ -76,6 +77,7 @@ The tests automatically:
7677
### Snapshot Validation
7778

7879
Each test:
80+
7981
1. Navigates to a feature screen
8082
2. Scrolls to ensure all content is visible
8183
3. Triggers a screenshot capture
@@ -110,10 +112,12 @@ example/
110112
### Prerequisites
111113

112114
**iOS**:
115+
113116
- macOS with Xcode installed
114117
- iOS Simulator (iPhone 15 Pro or compatible)
115118

116119
**Android**:
120+
117121
- Android Studio
118122
- Android Emulator (API 34 recommended)
119123
- Emulator must be running before tests
@@ -143,26 +147,30 @@ Reference snapshots are "golden images" that represent the expected state of the
143147
### Why Scroll to See the Preview?
144148

145149
When you capture a screenshot in the app:
150+
146151
1. The capture happens
147152
2. A success message appears
148153
3. **The captured image preview appears below** (often off-screen!)
149154
4. You must **scroll down** to see the preview
150155
5. The test then takes a snapshot showing the preview
151156

152157
This ensures we're validating that:
158+
153159
- ✅ The capture succeeded
154160
- ✅ The preview is displayed correctly
155161
- ✅ The captured image looks correct
156162

157163
### When to Update References
158164

159165
Update reference snapshots when:
166+
160167
- 🎨 UI design changes legitimately
161168
- ✨ New features are added
162169
- 🐛 A bug fix changes the expected output
163170
- 📱 You change device/simulator size
164171

165172
**Don't update** if tests fail due to:
173+
166174
- ❌ Unexpected bugs
167175
- ❌ Broken functionality
168176
- ❌ Timing issues
@@ -192,12 +200,14 @@ await scrollToBottom(null, 1000); // Scroll more
192200
**Problem**: Tests report snapshot differences
193201

194202
**Causes**:
203+
195204
1. **Legitimate UI change** → Update references
196205
2. **Different device size** → Use consistent simulator
197206
3. **Timing issue** → Increase wait times
198207
4. **Animation in progress** → Wait longer before snapshot
199208

200209
**Solution**:
210+
201211
```bash
202212
# If changes are legitimate, update references
203213
npm run test:e2e:ios:update-snapshots
@@ -213,13 +223,14 @@ npm run test:e2e:ios:update-snapshots
213223

214224
```javascript
215225
// After capture, wait and scroll more
216-
await device.sleep(1500); // Increase wait time
217-
await scrollToBottom(null, 800); // Scroll further
226+
await device.sleep(1500); // Increase wait time
227+
await scrollToBottom(null, 800); // Scroll further
218228
```
219229

220230
### Detox Build Fails
221231

222232
**iOS**:
233+
223234
```bash
224235
# Clean build
225236
cd ios
@@ -229,6 +240,7 @@ npm run build:e2e:ios
229240
```
230241

231242
**Android**:
243+
232244
```bash
233245
cd android
234246
./gradlew clean
@@ -239,6 +251,7 @@ npm run build:e2e:android
239251
### Emulator/Simulator Not Found
240252

241253
**iOS**:
254+
242255
```bash
243256
# List available simulators
244257
xcrun simctl list devices
@@ -248,6 +261,7 @@ xcrun simctl list devices
248261
```
249262

250263
**Android**:
264+
251265
```bash
252266
# List AVDs
253267
emulator -list-avds
@@ -266,7 +280,7 @@ emulator -list-avds
266280
✅ should capture full screen correctly
267281
✅ should handle transparency correctly
268282
...
269-
283+
270284
Test Suites: 1 passed, 1 total
271285
Tests: 15 passed, 15 total
272286
Time: 12.5s
@@ -295,10 +309,10 @@ Time: 12.5s
295309
UI elements, especially capture buttons and preview images, are often below the fold:
296310

297311
```javascript
298-
await scrollToBottom(); // Before interacting
312+
await scrollToBottom(); // Before interacting
299313
await captureScreenshot('📸 Capture Screenshot', 'Success!');
300314
await device.sleep(1000);
301-
await scrollToBottom(); // To see the result
315+
await scrollToBottom(); // To see the result
302316
```
303317

304318
### 2. Wait for Content
@@ -413,4 +427,3 @@ A: Current implementation allows 1% difference. For stricter validation, conside
413427

414428
**Q: Why scroll so much?**
415429
A: Mobile screens are small. Buttons, previews, and results are often off-screen. Scrolling ensures we interact with the right elements and verify results properly.
416-

example/e2e/helpers/snapshot-matcher.js

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class SnapshotMatcher {
88
constructor() {
99
this.referenceDir = path.join(__dirname, '../snapshots/reference');
1010
this.outputDir = path.join(__dirname, '../snapshots/output');
11-
11+
1212
// Ensure directories exist
1313
if (!fs.existsSync(this.referenceDir)) {
1414
fs.mkdirSync(this.referenceDir, { recursive: true });
@@ -28,7 +28,10 @@ class SnapshotMatcher {
2828
async captureAndValidate(testName, element = null, updateReference = false) {
2929
const timestamp = Date.now();
3030
const sanitizedName = testName.replace(/[^a-zA-Z0-9-]/g, '_');
31-
const outputPath = path.join(this.outputDir, `${sanitizedName}_${timestamp}.png`);
31+
const outputPath = path.join(
32+
this.outputDir,
33+
`${sanitizedName}_${timestamp}.png`,
34+
);
3235
const referencePath = path.join(this.referenceDir, `${sanitizedName}.png`);
3336

3437
try {
@@ -42,7 +45,7 @@ class SnapshotMatcher {
4245
// On iOS/Android, Detox saves screenshots to artifacts directory
4346
// We need to find and copy the latest screenshot
4447
const artifactsPath = this.findLatestScreenshot(sanitizedName);
45-
48+
4649
if (artifactsPath && fs.existsSync(artifactsPath)) {
4750
// Copy to output directory
4851
fs.copyFileSync(artifactsPath, outputPath);
@@ -58,15 +61,21 @@ class SnapshotMatcher {
5861
// Compare with reference if it exists
5962
if (fs.existsSync(referencePath)) {
6063
const matched = await this.compareImages(outputPath, referencePath);
61-
console.log(`${matched ? '✅' : '❌'} Snapshot ${matched ? 'matches' : 'differs from'} reference`);
64+
console.log(
65+
`${matched ? '✅' : '❌'} Snapshot ${matched ? 'matches' : 'differs from'} reference`,
66+
);
6267
return { path: outputPath, matched, referencePath };
6368
} else {
6469
console.log(`⚠️ No reference snapshot found at: ${referencePath}`);
65-
console.log(`💡 Run with updateReference=true to create reference snapshot`);
70+
console.log(
71+
`💡 Run with updateReference=true to create reference snapshot`,
72+
);
6673
return { path: outputPath, matched: null, needsReference: true };
6774
}
6875
} else {
69-
throw new Error(`Could not find captured screenshot for ${sanitizedName}`);
76+
throw new Error(
77+
`Could not find captured screenshot for ${sanitizedName}`,
78+
);
7079
}
7180
} catch (error) {
7281
console.error(`❌ Error capturing screenshot: ${error.message}`);
@@ -76,27 +85,32 @@ class SnapshotMatcher {
7685

7786
/**
7887
* Find the latest screenshot file created by Detox
79-
* @param {string} name - Test name
88+
* @param {string} _name - Test name
8089
* @returns {string|null} Path to screenshot file
8190
*/
82-
findLatestScreenshot(name) {
91+
findLatestScreenshot(_name) {
8392
// Detox saves screenshots in artifacts directory
8493
const possiblePaths = [
85-
path.join(process.cwd(), 'artifacts', `${name}.png`),
86-
path.join(process.cwd(), 'e2e', 'artifacts', `${name}.png`),
87-
path.join(process.cwd(), 'artifacts', 'ios.sim.debug', `${name}.png`),
88-
path.join(process.cwd(), 'artifacts', 'android.emu.debug', `${name}.png`),
94+
path.join(process.cwd(), 'artifacts', `${_name}.png`),
95+
path.join(process.cwd(), 'e2e', 'artifacts', `${_name}.png`),
96+
path.join(process.cwd(), 'artifacts', 'ios.sim.debug', `${_name}.png`),
97+
path.join(
98+
process.cwd(),
99+
'artifacts',
100+
'android.emu.debug',
101+
`${_name}.png`,
102+
),
89103
];
90104

91105
// Also check for timestamped versions
92106
const artifactsDir = path.join(process.cwd(), 'artifacts');
93107
if (fs.existsSync(artifactsDir)) {
94108
const files = fs.readdirSync(artifactsDir, { recursive: true });
95109
const screenshots = files
96-
.filter(f => f.includes(name) && f.endsWith('.png'))
110+
.filter(f => f.includes(_name) && f.endsWith('.png'))
97111
.map(f => ({
98112
path: path.join(artifactsDir, f),
99-
mtime: fs.statSync(path.join(artifactsDir, f)).mtime
113+
mtime: fs.statSync(path.join(artifactsDir, f)).mtime,
100114
}))
101115
.sort((a, b) => b.mtime - a.mtime);
102116

@@ -129,7 +143,9 @@ class SnapshotMatcher {
129143

130144
// Basic size check
131145
if (image1.length !== image2.length) {
132-
console.log(`📏 Image sizes differ: ${image1.length} vs ${image2.length} bytes`);
146+
console.log(
147+
`📏 Image sizes differ: ${image1.length} vs ${image2.length} bytes`,
148+
);
133149
return false;
134150
}
135151

@@ -173,12 +189,12 @@ class SnapshotMatcher {
173189
});
174190

175191
// Keep only latest N for each test
176-
Object.entries(groups).forEach(([testName, fileList]) => {
192+
Object.entries(groups).forEach(([_testName, fileList]) => {
177193
if (fileList.length > keep) {
178194
const sorted = fileList
179195
.map(f => ({
180196
name: f,
181-
mtime: fs.statSync(path.join(this.outputDir, f)).mtime
197+
mtime: fs.statSync(path.join(this.outputDir, f)).mtime,
182198
}))
183199
.sort((a, b) => b.mtime - a.mtime);
184200

@@ -193,4 +209,3 @@ class SnapshotMatcher {
193209
}
194210

195211
module.exports = SnapshotMatcher;
196-

0 commit comments

Comments
 (0)