diff --git a/scripts/android/tests/PostPrComment.java b/scripts/android/tests/PostPrComment.java index 943e50bebb..19c0e2453a 100644 --- a/scripts/android/tests/PostPrComment.java +++ b/scripts/android/tests/PostPrComment.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -324,7 +325,12 @@ private static Map publishPreviewsToBranch(Path previewDir, Stri try (var stream = Files.list(dest)) { stream.filter(Files::isRegularFile) .sorted() - .forEach(path -> urls.put(path.getFileName().toString(), rawBase + "/" + path.getFileName())); + .forEach(path -> { + String fileName = path.getFileName().toString(); + String url = rawBase + "/" + fileName; + urls.put(fileName, url); + urls.put(fileName.toLowerCase(Locale.ROOT), url); + }); } deleteRecursively(worktree); return urls; diff --git a/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl b/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl index 75a90b9d10..fb163f2df9 100644 --- a/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl +++ b/scripts/ios/tests/HelloCodenameOneUITests.swift.tmpl @@ -1,5 +1,6 @@ import XCTest import UIKit +import CoreGraphics final class HelloCodenameOneUITests: XCTestCase { private var app: XCUIApplication! @@ -37,7 +38,7 @@ final class HelloCodenameOneUITests: XCTestCase { } private func captureScreenshot(named name: String) throws { - let shot = XCUIScreen.main.screenshot() + let shot = waitForRenderedScreenshot(label: name) // Save into sandbox tmp (optional – mainly for local debugging) let pngURL = outputDirectory.appendingPathComponent("\(name).png") @@ -79,6 +80,85 @@ final class HelloCodenameOneUITests: XCTestCase { try captureScreenshot(named: "BrowserComponent") } + private func waitForRenderedScreenshot(label: String, timeout: TimeInterval = 10, poll: TimeInterval = 0.4) -> XCUIScreenshot { + let deadline = Date(timeIntervalSinceNow: timeout) + RunLoop.current.run(until: Date(timeIntervalSinceNow: 1.0)) + var attempt = 0 + var screenshot = XCUIScreen.main.screenshot() + while Date() < deadline { + if screenshotHasRenderableContent(screenshot) { + return screenshot + } + attempt += 1 + print("CN1SS:INFO:test=\(label) waiting_for_rendered_frame attempt=\(attempt)") + RunLoop.current.run(until: Date(timeIntervalSinceNow: poll)) + screenshot = XCUIScreen.main.screenshot() + } + return screenshot + } + + private func screenshotHasRenderableContent(_ screenshot: XCUIScreenshot) -> Bool { + guard let cgImage = screenshot.image.cgImage else { + return true + } + let width = cgImage.width + let height = cgImage.height + guard width > 0, height > 0 else { + return false + } + + let insetX = max(0, width / 8) + let insetY = max(0, height / 8) + let cropRect = CGRect( + x: insetX, + y: insetY, + width: max(1, width - insetX * 2), + height: max(1, height - insetY * 2) + ).integral + guard let cropped = cgImage.cropping(to: cropRect) else { + return true + } + + let sampleWidth = 80 + let sampleHeight = 80 + let bytesPerPixel = 4 + let bytesPerRow = sampleWidth * bytesPerPixel + guard let context = CGContext( + data: nil, + width: sampleWidth, + height: sampleHeight, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { + return true + } + context.interpolationQuality = .high + context.draw(cropped, in: CGRect(x: 0, y: 0, width: sampleWidth, height: sampleHeight)) + guard let data = context.data else { + return true + } + + let buffer = data.bindMemory(to: UInt8.self, capacity: sampleHeight * bytesPerRow) + var minLuma = 255 + var maxLuma = 0 + for y in 0.. maxLuma { maxLuma = luma } + } + } + + return maxLuma - minLuma > 12 + } + private func sanitizeTestName(_ name: String) -> String { let allowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-") let underscore: UnicodeScalar = "_"