|
1 | 1 | import XCTest |
2 | 2 | import UIKit |
| 3 | +import CoreGraphics |
3 | 4 |
|
4 | 5 | final class HelloCodenameOneUITests: XCTestCase { |
5 | 6 | private var app: XCUIApplication! |
@@ -37,9 +38,7 @@ final class HelloCodenameOneUITests: XCTestCase { |
37 | 38 | } |
38 | 39 |
|
39 | 40 | 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) |
43 | 42 |
|
44 | 43 | // Save into sandbox tmp (optional – mainly for local debugging) |
45 | 44 | let pngURL = outputDirectory.appendingPathComponent("\(name).png") |
@@ -81,6 +80,85 @@ final class HelloCodenameOneUITests: XCTestCase { |
81 | 80 | try captureScreenshot(named: "BrowserComponent") |
82 | 81 | } |
83 | 82 |
|
| 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 | + |
84 | 162 | private func sanitizeTestName(_ name: String) -> String { |
85 | 163 | let allowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-") |
86 | 164 | let underscore: UnicodeScalar = "_" |
|
0 commit comments