|
7 | 7 | const { mkdir, copyFile } = require('fs/promises'); |
8 | 8 | const fs = require('fs'); |
9 | 9 | const path = require('path'); |
10 | | -const { execSync, execFileSync } = require('child_process'); |
| 10 | +const { promisify } = require('util'); |
| 11 | +const { execSync, execFileSync, execFile } = require('child_process'); |
11 | 12 |
|
12 | 13 | const { device } = require('detox'); |
13 | 14 |
|
@@ -83,8 +84,7 @@ async function setSampleLocation() { |
83 | 84 | execSync(`xcrun simctl location ${device.id} set ${latitude},${longitude}`); |
84 | 85 | } |
85 | 86 |
|
86 | | -async function saveImage(basePath, suffix = null) { |
87 | | - const imagePath = await device.takeScreenshot(`example-${suffix || ''}`); |
| 87 | +async function saveImage(imagePath, basePath, suffix = null) { |
88 | 88 | const destDir = path.dirname(basePath); |
89 | 89 | await mkdir(destDir, { recursive: true }); |
90 | 90 | var fullDestPath = basePath; |
@@ -124,6 +124,44 @@ async function savePercyImage(imagePath, groupName, name) { |
124 | 124 | ]); |
125 | 125 | } |
126 | 126 |
|
| 127 | +const STABLE_INTERVAL_MS = 500; |
| 128 | +const STABLE_TIMEOUT_MS = 10000; |
| 129 | +const STABLE_MAX_CHANGED_PIXELS = 10; |
| 130 | + |
| 131 | +// Compared in a separate process for the same reason as savePercyImage. |
| 132 | +async function changedPixels(aPath, bPath) { |
| 133 | + const { frame, safeAreaInsets } = rootViewAttributes; |
| 134 | + const { stdout } = await promisify(execFile)(process.execPath, [ |
| 135 | + path.join(__dirname, 'changedPixels.js'), |
| 136 | + aPath, |
| 137 | + bPath, |
| 138 | + String(safeAreaInsets.top), |
| 139 | + String(frame.width), |
| 140 | + ]); |
| 141 | + return Number(stdout); |
| 142 | +} |
| 143 | + |
| 144 | +// Labels and tiles can still change after the maps report idle, so wait until two |
| 145 | +// consecutive screenshots match. |
| 146 | +async function takeStableScreenshot(name) { |
| 147 | + const start = Date.now(); |
| 148 | + let previous = await device.takeScreenshot(`${name}-0`); |
| 149 | + for (let i = 1; ; i++) { |
| 150 | + await new Promise((resolve) => setTimeout(resolve, STABLE_INTERVAL_MS)); |
| 151 | + const current = await device.takeScreenshot(`${name}-${i}`); |
| 152 | + if ((await changedPixels(previous, current)) <= STABLE_MAX_CHANGED_PIXELS) { |
| 153 | + return current; |
| 154 | + } |
| 155 | + if (Date.now() - start >= STABLE_TIMEOUT_MS) { |
| 156 | + console.warn( |
| 157 | + `${name}: screenshot still changing after ${STABLE_TIMEOUT_MS} ms, keeping the last one.`, |
| 158 | + ); |
| 159 | + return current; |
| 160 | + } |
| 161 | + previous = current; |
| 162 | + } |
| 163 | +} |
| 164 | + |
127 | 165 | async function wait(ms) { |
128 | 166 | try { |
129 | 167 | await waitFor(element(by.id('no-such-view'))) |
@@ -207,7 +245,11 @@ class ExampleScreenshots { |
207 | 245 | screenshotsDict[basename] = { images: [] }; |
208 | 246 | } |
209 | 247 |
|
210 | | - const imgPath = await saveImage(path.join(groupDir, basename), suffix); |
| 248 | + const imgPath = await saveImage( |
| 249 | + await takeStableScreenshot(basename), |
| 250 | + path.join(groupDir, basename), |
| 251 | + suffix, |
| 252 | + ); |
211 | 253 | screenshotsDict[basename].images.push( |
212 | 254 | path.relative(docSiteRootPath, path.resolve(imgPath)), |
213 | 255 | ); |
@@ -255,9 +297,7 @@ describe('Example screenshots', () => { |
255 | 297 | cellularBars: '4', |
256 | 298 | }); |
257 | 299 | await setSampleLocation(); |
258 | | - if (percyScreenshotsPath) { |
259 | | - await readRootViewAttributes(); |
260 | | - } |
| 300 | + await readRootViewAttributes(); |
261 | 301 |
|
262 | 302 | await waitFor(element(by.text(groupMetadata.title))) |
263 | 303 | .toBeVisible() |
|
0 commit comments