From 8e8e8eb37ace5184a1466a05c2f8b99c32c983c5 Mon Sep 17 00:00:00 2001 From: Pierre Slamich Date: Wed, 10 Feb 2021 00:13:06 +0100 Subject: [PATCH] Screenshot automation (#885) * Screenshot automation * Update screenshots.yml * Update screenshots.yml * Update screenshots.yml * Update screenshots.yml * Update screenshots.yml * Rename disabletravis.txt to travis.yml * Delete travis.yml * Update screenshots.yml * Update project.yml * Update Snapfile * Update screenshots.yml * Update SnapshotHelper.swift * Update screenshots.yml * Update Snapfile * Update Snapfile * Update setup.sh * Update screenshots.yml * Update setup.sh * Update setup.sh * mock conform to protocols * use a lane for snapshots * setup.sh should not build carthage in CI mode, as it is done by fastlane * Update screenshots.yml Co-authored-by: Philippe Auriach --- .github/workflows/screenshots.yml | 57 +++++++++++++++++ Snapshots/SnapshotHelper.swift | 78 ++++++++++++----------- Tests/Common/DataManagerMock.swift | 5 ++ Tests/Models/PersistenceManagerMock.swift | 2 + fastlane/Fastfile | 6 ++ fastlane/Snapfile | 9 ++- 6 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/screenshots.yml diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml new file mode 100644 index 00000000000..7bea49f1284 --- /dev/null +++ b/.github/workflows/screenshots.yml @@ -0,0 +1,57 @@ +name: Screenshot automation +on: + push: + branches: + - 'screenshots/*' + +jobs: + screenshot-automation: + name: Screenshot automation + runs-on: macos-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Decrypt AuthKey file + run: cd ./fastlane/envfiles && ./decrypt_secrets.sh + env: + AUTH_KEY_FILE_DECRYPTKEY: ${{ secrets.AUTH_KEY_FILE_DECRYPTKEY }} + + - uses: actions/cache@v2 + id: cache-carthage + with: + path: ~/Carthage + key: ${{ runner.os }}-carthage-${{ hashFiles('Cartfile.resolved') }} + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install sentry-cli + run: curl -sL https://sentry.io/get-cli/ | bash + + - name: Install Dependencies + run: gem install bundler:1.17.3 && bundle install + + - name: Prepare xcodeproj + run: sh scripts/setup.sh + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run Fastlane snapshot + run: bundle exec fastlane perform_snapshots + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + FASTLANE_USER: ${{ secrets.FASTLANE_USER }} + FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + MATCH_KEYCHAIN_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + PILOT_APPLE_ID: ${{ secrets.PILOT_APPLE_ID }} + SPACESHIP_CONNECT_API_ISSUER_ID: ${{ secrets.SPACESHIP_CONNECT_API_ISSUER_ID }} + SPACESHIP_CONNECT_API_KEY_ID: ${{ secrets.SPACESHIP_CONNECT_API_KEY_ID }} + SPACESHIP_CONNECT_API_KEY_FILEPATH: envfiles/AuthKey_KDAUTTM76R.p8 + CI_RELEASE: true + - uses: actions/upload-artifact@v2 + with: + name: screenshots + path: fastlane/screenshots/ # or path/to/artifact diff --git a/Snapshots/SnapshotHelper.swift b/Snapshots/SnapshotHelper.swift index aaa2a9a9234..25f8e5e4a21 100644 --- a/Snapshots/SnapshotHelper.swift +++ b/Snapshots/SnapshotHelper.swift @@ -38,22 +38,13 @@ func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { } enum SnapshotError: Error, CustomDebugStringConvertible { - case cannotDetectUser - case cannotFindHomeDirectory case cannotFindSimulatorHomeDirectory - case cannotAccessSimulatorHomeDirectory(String) case cannotRunOnPhysicalDevice var debugDescription: String { switch self { - case .cannotDetectUser: - return "Couldn't find Snapshot configuration files - can't detect current user " - case .cannotFindHomeDirectory: - return "Couldn't find Snapshot configuration files - can't detect `Users` dir" case .cannotFindSimulatorHomeDirectory: return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." - case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome): - return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?" case .cannotRunOnPhysicalDevice: return "Can't use Snapshot on a physical device." } @@ -75,7 +66,7 @@ open class Snapshot: NSObject { Snapshot.waitForAnimations = waitForAnimations do { - let cacheDir = try pathPrefix() + let cacheDir = try getCacheDirectory() Snapshot.cacheDirectory = cacheDir setLanguage(app) setLocale(app) @@ -174,6 +165,12 @@ open class Snapshot: NSObject { } let screenshot = XCUIScreen.main.screenshot() + #if os(iOS) + let image = XCUIDevice.shared.orientation.isLandscape ? fixLandscapeOrientation(image: screenshot.image) : screenshot.image + #else + let image = screenshot.image + #endif + guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } do { @@ -183,7 +180,11 @@ open class Snapshot: NSObject { simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") - try screenshot.pngRepresentation.write(to: path) + #if swift(<5.0) + UIImagePNGRepresentation(image)?.write(to: path, options: .atomic) + #else + try image.pngData()?.write(to: path, options: .atomic) + #endif } catch let error { NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") NSLog(error.localizedDescription) @@ -191,6 +192,19 @@ open class Snapshot: NSObject { #endif } + class func fixLandscapeOrientation(image: UIImage) -> UIImage { + if #available(iOS 10.0, *) { + let format = UIGraphicsImageRendererFormat() + format.scale = image.scale + let renderer = UIGraphicsImageRenderer(size: image.size, format: format) + return renderer.image { context in + image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) + } + } else { + return image + } + } + class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { #if os(tvOS) return @@ -206,40 +220,28 @@ open class Snapshot: NSObject { _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) } - class func pathPrefix() throws -> URL? { - let homeDir: URL + class func getCacheDirectory() throws -> URL { + let cachePath = "Library/Caches/tools.fastlane" // on OSX config is stored in /Users//Library // and on iOS/tvOS/WatchOS it's in simulator's home dir #if os(OSX) - guard let user = ProcessInfo().environment["USER"] else { - throw SnapshotError.cannotDetectUser - } - - guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else { - throw SnapshotError.cannotFindHomeDirectory + let homeDir = URL(fileURLWithPath: NSHomeDirectory()) + return homeDir.appendingPathComponent(cachePath) + #elseif arch(i386) || arch(x86_64) || arch(arm64) + guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { + throw SnapshotError.cannotFindSimulatorHomeDirectory } - - homeDir = usersDir.appendingPathComponent(user) + let homeDir = URL(fileURLWithPath: simulatorHostHome) + return homeDir.appendingPathComponent(cachePath) #else - #if arch(i386) || arch(x86_64) - guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { - throw SnapshotError.cannotFindSimulatorHomeDirectory - } - guard let homeDirUrl = URL(string: simulatorHostHome) else { - throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome) - } - homeDir = URL(fileURLWithPath: homeDirUrl.path) - #else - throw SnapshotError.cannotRunOnPhysicalDevice - #endif + throw SnapshotError.cannotRunOnPhysicalDevice #endif - return homeDir.appendingPathComponent("Library/Caches/tools.fastlane") } } private extension XCUIElementAttributes { var isNetworkLoadingIndicator: Bool { - if hasWhiteListedIdentifier { return false } + if hasAllowListedIdentifier { return false } let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) @@ -247,10 +249,10 @@ private extension XCUIElementAttributes { return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize } - var hasWhiteListedIdentifier: Bool { - let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] + var hasAllowListedIdentifier: Bool { + let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] - return whiteListedIdentifiers.contains(identifier) + return allowListedIdentifiers.contains(identifier) } func isStatusBar(_ deviceWidth: CGFloat) -> Bool { @@ -300,4 +302,4 @@ private extension CGFloat { // Please don't remove the lines below // They are used to detect outdated configuration files -// SnapshotHelperVersion [1.21] +// SnapshotHelperVersion [1.24] diff --git a/Tests/Common/DataManagerMock.swift b/Tests/Common/DataManagerMock.swift index 34f5622ed62..f2e5041f53c 100644 --- a/Tests/Common/DataManagerMock.swift +++ b/Tests/Common/DataManagerMock.swift @@ -12,6 +12,7 @@ import RealmSwift import UIKit class DataManagerMock: DataManagerProtocol { + // Search var query: String? var page: Int? @@ -123,6 +124,10 @@ class DataManagerMock: DataManagerProtocol { func allergen(forTag: Tag) -> Allergen? { return nil } + + func label(forTag: String) -> Label? { + return nil + } func additive(forTag: Tag) -> Additive? { return nil diff --git a/Tests/Models/PersistenceManagerMock.swift b/Tests/Models/PersistenceManagerMock.swift index 80244d94cbb..d44e2d9a3c7 100644 --- a/Tests/Models/PersistenceManagerMock.swift +++ b/Tests/Models/PersistenceManagerMock.swift @@ -10,6 +10,8 @@ import RealmSwift class PersistenceManagerMock: PersistenceManagerProtocol { + var labelsIsEmpty: Bool = false + // =======------------------======= // // TODO: implement code below if needed (for now only add the function/propety signatures, so it builds) // =======------------------======= // diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 07433be58ce..79dcefea7f1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -150,3 +150,9 @@ desc "Upload metadata" lane :metadata do deliver(skip_binary_upload: true) end + +lane :perform_snapshots do + carthage(command: "bootstrap", platform: "iOS", cache_builds: true) + + snapshot +end \ No newline at end of file diff --git a/fastlane/Snapfile b/fastlane/Snapfile index dbaa6e3fceb..66c590f9418 100644 --- a/fastlane/Snapfile +++ b/fastlane/Snapfile @@ -9,13 +9,15 @@ devices([ # "iPhone Xs Max", # "iPad Pro (12.9-inch) (2nd generation)", -"iPad Pro (12.9-inch) (3rd generation)" +"iPad Pro (12.9-inch) (4th generation)" #"iPad Pro (12.9-inch)", # "iPad Pro (9.7-inch)", # "Apple TV 1080p" ]) + + languages([ "en-US", "fr-FR" @@ -171,7 +173,10 @@ scheme("OpenFoodFactsSnapshots") output_directory("./fastlane/screenshots") # remove the '#' to clear all previously generated screenshots before creating new ones -# clear_previous_screenshots(true) +clear_previous_screenshots(true) + +override_status_bar(true) +localize_simulator(true) # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments launch_arguments(["debug"])