Specifying number of test devices when doing parallel testing using fastlane scan - ios

I haven't been able to get more than 2 concurrent simulator test devices when testing iOS apps using "fastlane scan".
Doing this "manually" using only xcodebuild works, something like this. This would fire up at most 4 devices:
xcodebuild -workspace myapp.xcworkspace -scheme somescheme_debug -destination 'platform=iOS Simulator,OS=12.1,name=iPhone X' build test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -parallel-testing-worker-count 4
Relevant (but closed(ignored?)) thread: https://github.com/fastlane/fastlane/issues/13394
Here is the lane I'm using
platform :ios do
desc "Test"
lane :test do |values|
maxconcurrenttestingcount = 4
schemefortesting = 'somescheme_debug'
thebranch = git_branch
ensure_git_status_clean
puts "Testing, using scheme: '#{schemefortesting}'"
scan(
scheme: schemefortesting,
devices: ['iPhone X'],
# devices: ['iPhone XS Max'], #, 'iPad Air'],
max_concurrent_simulators: maxconcurrenttestingcount,
xcargs: "-parallel-testing-enabled=YES -parallel-testing-worker-count=#{maxconcurrenttestingcount}" # hmm not really working?
)
reset_git_repo
end
end

Starting with fastlane 2.142, you can now specify concurrent_workers
Specify the exact number of test runners that will be spawned during parallel testing.
Equivalent to -parallel-testing-worker-count
scan(
concurrent_workers: 2
)

Related

How to use Fastlane with a CMake generated XCode project with dependency on WebP?

I have a project written in C++ where CMake is used to generate the build system for various platforms including iOS. The project has a dependency on WebP. You can find an example project on GitHub here that can be used to reproduce things & I've included the relevant source files at the end of this post for completeness.
The Xcode build system for iOS is generated using CMake as follows:
cmake -G Xcode -DCMAKE_TOOLCHAIN_FILE=third_party/ios-cmake/ios.toolchain.cmake -DPLATFORM=OS64 -DDEPLOYMENT_TARGET=15.0 -DENABLE_BITCODE=0 -S . -B cmake-build-release
We can now attempt to build/archive the app using Fastlane with the command from within the generated cmake-build-release directory:
bundle exec fastlane ios beta
However this fails due to being unable to locate various webp object files (that based on console output it appears to have previously successfully compiled):
...
▸ Compiling buffer_dec.c
▸ Compiling alpha_dec.c
▸ Building library libwebpdsp.a
...
** ARCHIVE FAILED **
▸ The following build commands failed:
▸ Libtool /Users/dbotha/Library/Developer/Xcode/DerivedData/CMakeFastlaneWebpTest-dlwvukebfiwjqvaqiepshuxqklhh/ArchiveIntermediates/CMakeFastlaneWebpTest/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/libwebpdecoder.a normal (in target 'webpdecoder' from project 'CMakeFastlaneWebpTest')
▸ (1 failure)
▸ ❌ error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: can't open file: /Users/dbotha/CLionProjects/CMakeFastlaneWebpTest/cmake-build-release/third_party/libwebp/CMakeFastlaneWebpTest.build/Release-iphoneos/webpdecode.build/Objects-normal/arm64/alpha_dec.o (No such file or directory)
▸ ❌ error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: can't open file: /Users/dbotha/CLionProjects/CMakeFastlaneWebpTest/cmake-build-release/third_party/libwebp/CMakeFastlaneWebpTest.build/Release-iphoneos/webpdecode.build/Objects-normal/arm64/buffer_dec.o (No such file or directory)
...
Internally Fastlane attempted to build/archive the project with the following command:
xcodebuild -scheme CMakeFastlaneWebpTest -project ./CMakeFastlaneWebpTest.xcodeproj -configuration Release -destination 'generic/platform=iOS' -archivePath ./out.xcarchive archive
Interestingly an archive can be successfully generated if I use the following xcodebuild command (note how -target flag is used instead of -scheme):
xcodebuild -project CMakeFastlaneWebpTest.xcodeproj archive -target CMakeFastlaneWebpTest -configuration Release
After this successful attempt bundle exec fastlane ios beta will now also succeed as the compiled object files are where it expected them to be.
Now I'd happily workaround this issue using my xcodebuild + -target flag approach and then use the fastlane command to push to Testflight, etc. but the real project (not this toy example) takes a very long time to build so building it twice is really wasteful from a cost point of view on CI platforms.
Does anyone have any idea what's going on here & how I can successfully build things in the first instance using fastlane without my own explicit call to xcodebuild first? Or alternatively how can I have Fastlane use the successfully built objects from my workaround so it doesn't need to rebuild the entire project from scratch?
CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(CMakeFastlaneWebpTest)
set(CMAKE_CXX_STANDARD 14)
add_executable(CMakeFastlaneWebpTest src/main.cpp)
# Skip building of unused webp tools which fail for me under ios:
set(WEBP_BUILD_ANIM_UTILS OFF)
set(WEBP_BUILD_CWEBP OFF)
set(WEBP_BUILD_DWEBP OFF)
set(WEBP_BUILD_GIF2WEBP OFF)
set(WEBP_BUILD_IMG2WEBP OFF)
set(WEBP_BUILD_VWEBP OFF)
set(WEBP_BUILD_WEBPINFO OFF)
set(WEBP_BUILD_WEBPMUX OFF)
set(WEBP_BUILD_EXTRAS OFF)
set(WEBP_BUILD_WEBP_JS OFF)
add_subdirectory(third_party/libwebp EXCLUDE_FROM_ALL)
target_link_libraries(CMakeFastlaneWebpTest PRIVATE webpdecoder webpdemux)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/libwebp/src)
configure_file(${CMAKE_SOURCE_DIR}/fastlane/Appfile ${CMAKE_BINARY_DIR}/fastlane/Appfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/fastlane/Fastfile ${CMAKE_BINARY_DIR}/fastlane/Fastfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/Gemfile ${CMAKE_BINARY_DIR}/Gemfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/Gemfile.lock ${CMAKE_BINARY_DIR}/Gemfile.lock COPYONLY)
set_target_properties(CMakeFastlaneWebpTest PROPERTIES
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/src/iOS-Info.plist.in
MACOSX_BUNDLE_GUI_IDENTIFIER com.dbotha.CMakeFastlaneWebpTest
MACOSX_BUNDLE_BUNDLE_NAME CMakeFastlaneWebpTest
MACOSX_BUNDLE_BUNDLE_VERSION "0.1"
MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1"
)
set_xcode_property(CMakeFastlaneWebpTest PRODUCT_BUNDLE_IDENTIFIER "com.dbotha.CMakeFastlaneWebpTest" All)
set_xcode_property(CMakeFastlaneWebpTest CODE_SIGN_IDENTITY "iPhone Developer" All)
set_xcode_property(CMakeFastlaneWebpTest DEVELOPMENT_TEAM "GFP63373B2" All)
fastlane/Appfile
app_identifier("com.dbotha.CMakeFastlaneWebpTest") # The bundle identifier of your app
apple_id("REPLACE_ME") # Your Apple Developer Portal username
itc_team_id("REPLACE_ME") # App Store Connect Team ID
team_id("REPLACE_ME") # Developer Portal Team ID
fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
build_app(scheme: "CMakeFastlaneWebpTest", configuration: "Release")
upload_to_testflight
end
end
src/main.cpp
#include <iostream>
#include <webp/demux.h>
int main() {
WebPAnimDecoderOptions decOptions;
(void)decOptions;
std::cout << "Hello, World!" << std::endl;
return 0;
}
I'm not an expert in this topic but according to the documentation you should provide workspace to build your scheme.
To build an Xcode workspace, you must pass both the -workspace and
-scheme options to define the build. The parameters of the scheme will
control which targets are built and how they are built, although you may
pass other options to xcodebuild to override some parameters of the
scheme.
Scheme controls what target will be build, and guessing by your example, since you do not provide a workspace, it gets lost in the process.
The scheme is not lost anymore if you build the target manually. Since it is already build the scheme does not have to do a thing.
My proposals:
Option 1: Try adding workspace to build_app parameters in Fastfile.
Option 2: Don't bother with building scheme just use target in the
build_app parameters in Fastfile like so: build_app(target: "CMakeFastlaneWebpTest", configuration: "Release")

Unable to find a destination matching the provided destination specifier

Currently, I am struggling with the following issue and it's kind of big blocker for our project.
We need to run ui tests for apple watch test target in the iOS App with Watch App using terminal and we receive such error, which is quite frustrating
Command I use:
xcodebuild test -workspace WatchTesterApp.xcworkspace -scheme 'Watch' -destination 'id=F35DCC98-0F7D-460E-A49F-A446FD5FB4BE'
xcodebuild: error: Unable to find a destination matching the provided destination specifier:
{ id:F35DCC98-0F7D-460E-A49F-A446FD5FB4BE }
The requested device could not be found because no available devices matched the request.
Available destinations for the "Watch" scheme:
{ platform:iOS Simulator, id:F35DCC98-0F7D-460E-A49F-A446FD5FB4BE, OS:14.5, name:iPhone 12 Pro }
Ineligible destinations for the "Watch" scheme:
{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device }
{ platform:watchOS, id:dvtdevice-DVTiOSDevicePlaceholder-watchos:placeholder, name:Any watchOS Device }
Destination is iPhone's simulator id which is paired with apple watch
Has anyone faced such issue?
So, after a long investigation and a long list of possible fixes that resolved nothing, I updated Xcode and tried to run the same command, which didn't work either and only after creating new clean project using new Xcode it finally worked.
I used:
xcodebuild test -workspace WatchTesterAppExample.xcworkspace -scheme 'WatchTesterAppExample WatchKit App' -destination 'platform=WatchOS Simulator,name=Apple Watch Series 7 - 45mm' &
xcodebuild test -workspace WatchTesterAppExample.xcworkspace -scheme 'WatchTesterAppExample' -destination 'platform=iOS Simulator,name=iPhone 13'
It worked for me on the simulators, didn't try it on real device yet
Interesting fact: I am still able to see this error in some case, especially when I don't specify platform in the 'destination'

Can I specify a platform target when running swift test from the CLI?

My Package.swift looks something like
let package = Package(
name: "MyPackage",
platforms: [
.iOS(.v13)
],
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"])
],
dependencies: [
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.0"),
],
targets: [
.target(
name: "MyPackage",
dependencies: [
"SnapKit",
]),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"])
]
)
When I run swift test I get
error: the library 'MyPackage' requires macos 10.10, but depends on the product 'SnapKit' \
which requires macos 10.12; consider changing the library 'SurfUIKit' to require macos 10.12 \
or later, or the product 'SnapKit' to require macos 10.10 or earlier.
Why is swift running tests for macos that is not listed as a supported platform? Can I get swift to run the tests for iOS, ideally specifying some version target? What alternative do I have using xcode in the CLI?
The trick is:
you think that .iOS(...) is here to restrict the compilation to only one platform
while it actually is a line used to specify what minimum version is going to be supported by your product for this platform
It does not say: only compile for .iOS X.y, but .iOS min version is X.y
SPM is a tool for Swift first, thus wants to build for all~ platforms, and has currently no way of using a ~system only parameter~ (I know me sad too).
Now if you want to have an iOS only Package it's still possible but you'll have to compil through xcodebuild commands (and you don't need a xcodeproj file for that).
// Compile and test on iOS simulator
xcodebuild -list
xcodebuild -scheme <scheme> -destination 'generic/platform=iOS'
xcodebuild -scheme <scheme> -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11'
xcodebuild -scheme <scheme> test -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11'
SPM documentation usage about "Depending on Apple modules"
From my experience I would also say that you could have a folder hierarchy as follow (with xCode 12):
| Root Project folder/
Source/ // folder of your sources
Example/ // create an xcodeproj in it
Tests/ // Your tests files ~
YourPackage.xcworkspace
add the root folder in it to be able to access your Package targets
add your example project (and add your Package to its dependencies)
finally create a new scheme in which you select the Test target of your package
Now you're all setup to develop your Package in parallele with your example and tests.
And remember that Swift Package Manager currently (12/2020) has no parameter to only build on one platform.
Hope it's clear enough.

Can you pass environment variables into xcodebuild?

I have two Jenkins jobs that run our functional tests. One job is for whenever something is submitted for code review and the other job runs whenever something is pushed to master.
Because these are functional tests they test entire flows of the application which end up modifiying the user state. The issue we are having right is that every job uses the same account so whenever two Jenkins jobs are running in parallel they modify the same account which can put them in an unexpected state and fail the test.
My plan was to use Jenkins' BUILD_NUMBER environment variable and by applying a bit of arthimetic to it I could have a guaranteed unique number for the job. This unique number could then be passed into xcodebuild as an environment variable and the tests could use this number to ensure that every Jenkins' is working on a unique account.
The problem is that I cannot find any way to pass environment variables into xcodebuild. I know it is possible for you to pass in user-defined build settings via xcodebuild (or xcargs if you're using Fastlane) but those values do not seem to be accessible as environment variables. They are accessible by the preprocessor and so you could use it to export the value to your Info.plist and then read it from there. But then you have baked these value into your binary and it cannot be changed unless you rebuild it which is not ideal. Also at this point in time I could just have Jenkins write to a file on disk and have the tests read from it. It is essentially the same functionality and saves me from having to pass in build settings.
I remember using something like GCC_PREPROCESSOR_DEFINITIONS to pass my own var
I had to shell escape the quotes. I ended up coding it into my fastlane build file.
in ruby it looked like this:
tmp_other_flags = {
GCC_PREPROCESSOR_DEFINITIONS: '"DISABLE_PUSH_NOTIFICATIONS=1"',
TARGETED_DEVICE_FAMILY: '1',
DEBUG: '1'
}
other_flags = tmp_other_flags.map do |k, v|
"#{k.to_s.shellescape}=#{v.shellescape}"
end.join ' '
puts "___ Custom Flags also know as xcargs:"
puts other_flags
gym(
clean: true,
silent: false,
project: proj_xcodeproj_file,
archive_path: "build-ios-xcarchive",
destination: 'generic/platform=iOS',
use_legacy_build_api: true,
output_directory: 'build-ios',
output_name: "MyApp.ipa",
export_method: 'ad-hoc',
codesigning_identity: 'iPhone Distribution: company (12345)',
provisioning_profile_path: './dl_profile_com.company.myapp.iphone.prod_ad_hoc.mobileprovision',
scheme: 'MyApp',
configuration: 'Debug',
xcargs: other_flags
)
it ended up getting called in the shell something like this:
set -o pipefail && xcodebuild -scheme 'MyApp' -project 'platforms/ios/MyApp.xcodeproj' -configuration 'Debug' -destination 'generic/platform=iOS' -archivePath 'build-ios-xcarchive.xcarchive' GCC_PREPROCESSOR_DEFINITIONS=\"DISABLE_PUSH_NOTIFICATIONS\=1\" TARGETED_DEVICE_FAMILY=1 DEBUG=1 clean archive CODE_SIGN_IDENTITY='iPhone Distribution: My Company (Blah)' | tee '/Users/andxyz/Library/Logs/gym/MyApp-MyApp.log' | xcpretty
xcodebuild - how to define preprocessor macro?
So, perhaps you could pull in your own environment variable using ruby inside of fastlane. by adding your var into the GCC_PREPROCESSOR_DEFINITIONS section
ruby can access the environment, for example:
ENV.fetch('TERM_PROGRAM') #returns "iTerm.app" on my machine
so following along with above:
tmp_other_flags = {
GCC_PREPROCESSOR_DEFINITIONS: "MY_VARIABLE=#{ENV.fetch('MY_VARIABLE')}" ,
TARGETED_DEVICE_FAMILY: '1',
DEBUG: '1'
}
HTH
Via #alisoftware, you can use xcargs to pass additional variables in:
gym(
scheme: scheme,
xcargs: {
:PROVISIONING_PROFILE => 'profile-uuid',
:PROVISIONING_PROFILE_SPECIFIER => 'match AppStore com.bigco.App'
},
codesigning_identity: "iPhone Distribution: BigCo, Inc. ()",
)
emits this during the build:
+---------------------+-------------------------------------------------------------------------------------------------+
| Summary for gym 2.53.1 |
+---------------------+-------------------------------------------------------------------------------------------------+
| scheme | Bespoke-iOS |
| xcargs | PROVISIONING_PROFILE=profile-uuid PROVISIONING_PROFILE_SPECIFIER=match\ AppStore\ com.bigco.App |
…

How to execute several times a job with a changing parameter on gitlab-ci

I would like to execute the same testing job for several iOS version on gitlab-ci.
My testing job is composed of the following command:
xcodebuild test -workspace myproject.xcworkspace -scheme myScheme -destination 'platform=iOS Simulator,name=iPhone 6S,OS=9.3'
Is it possible to create a kind of loop to execute this command for different OS versions and iPhone/iPad?
For exemple, doing an xcodebuild test for :
iPhone 6 / iOS 9.3
iPhone 6 / iOS 10.0
iPhone 7 / iOS 10.0
iPad Mini / iOS 8.2
iPad Mini / iOS 9.3 ....
Thanks
With versions >=13.5 you can use a parallel:matrix to execute a job many times in parallel with values.
For your example:
Test:ios:
stage: test
script:
- xcodebuild test -workspace myproject.xcworkspace -scheme myScheme -destination 'platform=iOS Simulator,name=${DEVICE},OS=${VERSION}'
parallel:
matrix:
- DEVICE: 'iPhone 6'
VERSION: ['9.3', '10.0']
- DEVICE: 'iPhone 7'
VERSION: ['10.0']
- DEVICE: 'iPad Mini'
VERSION: ['8.2', '9.3']
Use gitlab-ci template and define one variable which contains the name of the simulator, for example, with an iPhone 5S:
# Job
.test_ios_job: &test_ios_job_def
stage: test_ios
script:
- xcodebuild test -workspace myproject.xcworkspace -scheme myScheme -destination 'platform=iOS Simulator,name=$SIMULATOR'
# iPhone 5S
test_ios_iPhone5S_9.1:
<<: *test_ios_job_def
variables:
SIMULATOR: "iPhone 5S,OS=9.1"
test_ios_iPhone5S_10.1:
<<: *test_ios_job_def
variables:
SIMULATOR: "iPhone 5S,OS=10.1"
I would recommend to use fastlane, plugin scan to simplify the work:
# Job
.test_ios_job: &test_ios_job_def
stage: test_ios
script:
- fastlane testios emulatorname:"$SIMULATOR"
test_ios_iPhone5S_9.1:
<<: *test_ios_job_def
variables:
SIMULATOR: "iPhone 5s (9.1)"
test_ios_iPhone5S_10.1:
<<: *test_ios_job_def
variables:
SIMULATOR: "iPhone 5s (10.1)"
fastlane job:
lane :testios do |options|
emulatorname = options[:emulatorname]
#Scan
scan(
scheme: S_APP_SCHEME_TEST,
clean: true,
device: "#{emulatorname.to_s}",
)
end
I am using something like this, it is pseudo-code and not the dry-est but it works, i.e. it runs same job in sequence with different parameters:
job: # generic job
variables:
X: "x"
job-y: # job runs with variable = y
extends: job
variables:
X: "y"
job-z: # job runs with variable = z
extends: job
variables:
X: "z"
job-yz: # execs job sequentially with 2 variable values
extends:
- job-y
- job-z
Use stages:
stages:
- buildForiPhone
- buildForiPad
build_project:
stage: buildForiPhone
script:
- xcodebuild for iPhone here
tags:
- Swift
- iOS9
build_project:
stage: buildForiPad
script:
- xcodebuild for iPad here
tags:
- Swift
- iOS9
One stage for one build_project,and they are isolated.

Resources