Skip to content

Commit f53a843

Browse files
committed
Stabilize iOS screenshot captures and preview publishing
1 parent 31c84fb commit f53a843

File tree

2 files changed

+88
-4
lines changed

2 files changed

+88
-4
lines changed

scripts/android/tests/PostPrComment.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.HashMap;
1515
import java.util.LinkedHashMap;
1616
import java.util.List;
17+
import java.util.Locale;
1718
import java.util.Map;
1819
import java.util.Objects;
1920
import java.util.Optional;
@@ -324,7 +325,12 @@ private static Map<String, String> publishPreviewsToBranch(Path previewDir, Stri
324325
try (var stream = Files.list(dest)) {
325326
stream.filter(Files::isRegularFile)
326327
.sorted()
327-
.forEach(path -> urls.put(path.getFileName().toString(), rawBase + "/" + path.getFileName()));
328+
.forEach(path -> {
329+
String fileName = path.getFileName().toString();
330+
String url = rawBase + "/" + fileName;
331+
urls.put(fileName, url);
332+
urls.put(fileName.toLowerCase(Locale.ROOT), url);
333+
});
328334
}
329335
deleteRecursively(worktree);
330336
return urls;

scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import XCTest
22
import UIKit
3+
import CoreGraphics
34

45
final class HelloCodenameOneUITests: XCTestCase {
56
private var app: XCUIApplication!
@@ -37,9 +38,7 @@ final class HelloCodenameOneUITests: XCTestCase {
3738
}
3839

3940
private func captureScreenshot(named name: String) throws {
40-
// Allow Codename One an extra moment to render before grabbing the frame.
41-
RunLoop.current.run(until: Date(timeIntervalSinceNow: 1.0))
42-
let shot = XCUIScreen.main.screenshot()
41+
let shot = waitForRenderedScreenshot(label: name)
4342

4443
// Save into sandbox tmp (optional – mainly for local debugging)
4544
let pngURL = outputDirectory.appendingPathComponent("\(name).png")
@@ -81,6 +80,85 @@ final class HelloCodenameOneUITests: XCTestCase {
8180
try captureScreenshot(named: "BrowserComponent")
8281
}
8382

83+
private func waitForRenderedScreenshot(label: String, timeout: TimeInterval = 10, poll: TimeInterval = 0.4) -> XCUIScreenshot {
84+
let deadline = Date(timeIntervalSinceNow: timeout)
85+
RunLoop.current.run(until: Date(timeIntervalSinceNow: 1.0))
86+
var attempt = 0
87+
var screenshot = XCUIScreen.main.screenshot()
88+
while Date() < deadline {
89+
if screenshotHasRenderableContent(screenshot) {
90+
return screenshot
91+
}
92+
attempt += 1
93+
print("CN1SS:INFO:test=\(label) waiting_for_rendered_frame attempt=\(attempt)")
94+
RunLoop.current.run(until: Date(timeIntervalSinceNow: poll))
95+
screenshot = XCUIScreen.main.screenshot()
96+
}
97+
return screenshot
98+
}
99+
100+
private func screenshotHasRenderableContent(_ screenshot: XCUIScreenshot) -> Bool {
101+
guard let cgImage = screenshot.image.cgImage else {
102+
return true
103+
}
104+
let width = cgImage.width
105+
let height = cgImage.height
106+
guard width > 0, height > 0 else {
107+
return false
108+
}
109+
110+
let insetX = max(0, width / 8)
111+
let insetY = max(0, height / 8)
112+
let cropRect = CGRect(
113+
x: insetX,
114+
y: insetY,
115+
width: max(1, width - insetX * 2),
116+
height: max(1, height - insetY * 2)
117+
).integral
118+
guard let cropped = cgImage.cropping(to: cropRect) else {
119+
return true
120+
}
121+
122+
let sampleWidth = 80
123+
let sampleHeight = 80
124+
let bytesPerPixel = 4
125+
let bytesPerRow = sampleWidth * bytesPerPixel
126+
guard let context = CGContext(
127+
data: nil,
128+
width: sampleWidth,
129+
height: sampleHeight,
130+
bitsPerComponent: 8,
131+
bytesPerRow: bytesPerRow,
132+
space: CGColorSpaceCreateDeviceRGB(),
133+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
134+
) else {
135+
return true
136+
}
137+
context.interpolationQuality = .high
138+
context.draw(cropped, in: CGRect(x: 0, y: 0, width: sampleWidth, height: sampleHeight))
139+
guard let data = context.data else {
140+
return true
141+
}
142+
143+
let buffer = data.bindMemory(to: UInt8.self, capacity: sampleHeight * bytesPerRow)
144+
var minLuma = 255
145+
var maxLuma = 0
146+
for y in 0..<sampleHeight {
147+
let row = y * bytesPerRow
148+
for x in 0..<sampleWidth {
149+
let idx = row + x * bytesPerPixel
150+
let r = Int(buffer[idx])
151+
let g = Int(buffer[idx + 1])
152+
let b = Int(buffer[idx + 2])
153+
let luma = (r * 299 + g * 587 + b * 114) / 1000
154+
if luma < minLuma { minLuma = luma }
155+
if luma > maxLuma { maxLuma = luma }
156+
}
157+
}
158+
159+
return maxLuma - minLuma > 12
160+
}
161+
84162
private func sanitizeTestName(_ name: String) -> String {
85163
let allowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-")
86164
let underscore: UnicodeScalar = "_"

0 commit comments

Comments
 (0)