ann-flutter-flavor 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +271 -0
  5. data/Rakefile +6 -0
  6. data/ann-flutter-flavor.gemspec +25 -0
  7. data/lib/ann-flutter-flavor.rb +10 -0
  8. data/lib/ann_flutter_flavor/version.rb +3 -0
  9. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_compile_build_action.rb +219 -0
  10. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_app_store_action.rb +183 -0
  11. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_play_store_action.rb +168 -0
  12. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_emulators_action.rb +180 -0
  13. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_run_tests_action.rb +128 -0
  14. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_setup_action.rb +137 -0
  15. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_app_store_action.rb +280 -0
  16. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_firebase_action.rb +199 -0
  17. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_play_store_action.rb +248 -0
  18. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_android.rb +120 -0
  19. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_annai.rb +502 -0
  20. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_ios.rb +157 -0
  21. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_setup.rb +161 -0
  22. data/lib/fastlane/plugin/ann_flutter_flavor/helper/podspec_bridge.rb +153 -0
  23. data/lib/fastlane/plugin/ann_flutter_flavor/helper/test_integration.rb +346 -0
  24. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_project_config.rb +96 -0
  25. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb +363 -0
  26. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_status.rb +115 -0
  27. data/lib/fastlane/plugin/ann_flutter_flavor.rb +23 -0
  28. data/plugin_manager.sh +140 -0
  29. metadata +112 -0
@@ -0,0 +1,161 @@
1
+ require 'fileutils'
2
+ require 'fastlane/action'
3
+
4
+ module FastlaneFlutterFlavor
5
+ # Helper class containing maintenance and environment setup logic
6
+ class SetupLanes
7
+ # (upgrade_setup and cleanup) are inherently cross-platform.
8
+ def initialize(root_folder:, status_manager:)
9
+ @root_folder = root_folder
10
+ @status_manager = status_manager
11
+ end
12
+
13
+ # ------------------------------------
14
+ # Core Maintenance and Setup
15
+ # ------------------------------------
16
+
17
+ def upgrade_setup(force: false)
18
+ begin
19
+ Fastlane::UI.message("🚀 Starting maintenance and environment checks...")
20
+
21
+ # 1. Update Flutter SDK
22
+ Fastlane::UI.message("-> Step 1: Running 'flutter upgrade' to update SDK.")
23
+ Fastlane::Actions::sh("flutter", "upgrade")
24
+
25
+ # 2. Aggressive Cleanup: Delete lock files and call cleanup lane to remove build artifacts
26
+ if force
27
+ Fastlane::UI.message("-> Step 2: Running aggressive pre-resolution cleanup (deleting lock files and build artifacts)...")
28
+ Dir.chdir @root_folder do
29
+ pubspec_lock_path = File.join(@root_folder, "pubspec.lock")
30
+ gemfile_lock_path = File.join(@root_folder, "Gemfile.lock")
31
+
32
+ # Delete Dart lock file
33
+ if File.exist?(pubspec_lock_path)
34
+ Fastlane::UI.message(" -> Deleting pubspec.lock to force full Flutter dependency re-resolution.")
35
+ FileUtils.rm_f(pubspec_lock_path)
36
+ end
37
+
38
+ # Delete Ruby lock file
39
+ if File.exist?(gemfile_lock_path)
40
+ Fastlane::UI.message(" -> Deleting Gemfile.lock to force full Ruby dependency re-resolution.")
41
+ FileUtils.rm_f(gemfile_lock_path)
42
+ end
43
+ end
44
+
45
+ # Call cleanup which, with force: true, deletes ios/Podfile.lock, ios/Pods, and all build folders
46
+ cleanup(force: force)
47
+ end
48
+
49
+ # 3. Resolve Dependencies: Ruby first, then Flutter (Root task)
50
+ Dir.chdir @root_folder do
51
+ Fastlane::UI.message("-> Step 3: Resolving Ruby and Dart dependencies...")
52
+
53
+ # Resolve Ruby dependencies (Gemfile.lock is regenerated here)
54
+ bundle_install_command = ["bundle", "install"]
55
+ bundle_update_command = ["bundle", "update"]
56
+ bundle_install_command << "--force" if force
57
+ bundle_update_command << "--force" if force
58
+
59
+ Fastlane::UI.message(" -> Running 'bundle install' and 'bundle update'#{force ? ' (with --force)' : ''}...")
60
+ Fastlane::Actions::sh(*bundle_install_command)
61
+ Fastlane::Actions::sh(*bundle_update_command)
62
+
63
+ # Resolve Dart dependencies (pubspec.lock is regenerated here)
64
+ Fastlane::UI.message(" -> Running 'flutter pub get' to re-resolve Dart dependencies.")
65
+ Fastlane::Actions::sh("flutter", "pub", "get")
66
+ end
67
+
68
+ # 4. Resolve Native iOS Dependencies
69
+ ios_dir = File.join(@root_folder, "ios")
70
+ if File.directory?(ios_dir)
71
+ Fastlane::UI.message("-> Step 4: Running 'pod install' in the 'ios' directory to ensure native dependencies are linked.")
72
+
73
+ # Use --repo-update when force is true to fix "CocoaPods could not find compatible versions" errors.
74
+ pod_command = ["bundle", "exec", "pod", "install"]
75
+ if force
76
+ pod_command << "--repo-update"
77
+ Fastlane::UI.message(" -> Adding '--repo-update' flag to pod install for aggressive source update.")
78
+ end
79
+
80
+ Dir.chdir ios_dir do
81
+ Fastlane::Actions::sh(*pod_command)
82
+ end
83
+ else
84
+ Fastlane::UI.important(" -> Skipped 'pod install': 'ios' directory not found (likely not an iOS-enabled project).")
85
+ end
86
+
87
+ # 5. Doctor check (Root task)
88
+ Fastlane::UI.message("-> Step 5: Running 'flutter doctor' for final verification.")
89
+ Fastlane::Actions::sh("flutter", "doctor")
90
+
91
+ # Pass nil for platform since this is a cross-platform action
92
+ @status_manager.logSuccess nil, "upgrade_setup", nil
93
+ return true
94
+ rescue => e
95
+ Fastlane::UI.error "❌ Error during environment setup or upgrade: #{e}"
96
+ Fastlane::UI.error e
97
+ # Pass nil for platform since this is a cross-platform action
98
+ @status_manager.logError nil, "upgrade_setup", nil, e
99
+ return false
100
+ end
101
+ end
102
+
103
+ # ------------------------------------
104
+ # Core Cleanup
105
+ # ------------------------------------
106
+
107
+ def cleanup(force: false)
108
+ begin
109
+ Fastlane::UI.message("🧹 Cleaning build artifacts...")
110
+
111
+ # 1. Standard Flutter clean (Root task)
112
+ Fastlane::UI.message("-> Running 'flutter clean'...")
113
+ Dir.chdir @root_folder do
114
+ Fastlane::Actions::sh("flutter", "clean")
115
+ end
116
+
117
+ if force
118
+ Fastlane::UI.message("-> Performing aggressive cleanup (deleting platform-specific build folders)...")
119
+ # 2. Aggressive cleanup of general Flutter build folder and platform-specific folders
120
+ dirs_to_delete = [
121
+ "build", # General Flutter build
122
+ "android/.gradle",
123
+ "android/build",
124
+ "ios/Pods",
125
+ "ios/Podfile.lock", # Crucial for forcing a fresh dependency graph on next install
126
+ "ios/build",
127
+ "web/build"
128
+ ]
129
+
130
+ # Change directory temporarily to simplify paths for deletion
131
+ Dir.chdir @root_folder do
132
+ dirs_to_delete.each do |dir|
133
+ full_path = File.join(dir)
134
+
135
+ # Only delete if the file/directory exists
136
+ if File.exist?(full_path)
137
+ Fastlane::UI.message("Deleting artifact: #{dir}")
138
+ FileUtils.rm_rf(full_path)
139
+ Fastlane::UI.success("Successfully deleted: #{dir}")
140
+ end
141
+ end
142
+ end
143
+ Fastlane::UI.success("Successfully performed aggressive cleanup.")
144
+ else
145
+ Fastlane::UI.important("Skipped aggressive deletion of build directories and native lock files. Pass 'force: true' to perform full cleanup.")
146
+ end
147
+
148
+ # Pass nil for platform since this is a cross-platform action
149
+ @status_manager.logSuccess nil, "cleanup", nil
150
+ return true
151
+ rescue => e
152
+ Fastlane::UI.error "❌ Error during cleanup of build artifacts: #{e}"
153
+ Fastlane::UI.error e
154
+ # Pass nil for platform since this is a cross-platform action
155
+ @status_manager.logError nil, "cleanup", nil, e
156
+ return false
157
+ end
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,153 @@
1
+ module FastlaneFlutterFlavor
2
+ class PodspecBridge
3
+
4
+ # Static entry point to initialize the bridge.
5
+ # @param config_name [String] optional specific Xcode configuration name (e.g., "Debug-ledger_in")
6
+ def self.current_config(config_name = nil)
7
+ # 1. FLAVOR DETECTION: Prioritize Flutter CLI, fallback to config name parsing
8
+ flavor = ENV['FLUTTER_APP_FLAVOR']
9
+ if flavor.nil? || flavor.empty?
10
+ active_config = config_name || ENV['CONFIGURATION'] || ""
11
+ flavor = active_config.split('-').last
12
+ end
13
+
14
+ # 2. SMART ROOT DETECTION
15
+ # Start from the current directory and walk up until we find annspec.yaml
16
+ search_dir = Dir.pwd
17
+ spec_file = 'annspec.yaml'
18
+ root = nil
19
+
20
+ # Walk up to 10 levels to find the project root
21
+ 10.times do
22
+ if File.exist?(File.join(search_dir, spec_file))
23
+ root = search_dir
24
+ break
25
+ end
26
+ search_dir = File.expand_path('..', search_dir)
27
+ end
28
+
29
+ if root.nil?
30
+ raise "Could not find #{spec_file} in #{Dir.pwd} or any parent directories. " +
31
+ "Ensure you are running pod install from your Flutter project's /ios folder."
32
+ end
33
+
34
+ loader = YamlSpecLoader.new(root, spec_file)
35
+ self.new(loader, flavor, config_name)
36
+ end
37
+
38
+ # Injects YAML values into the Xcode Project Build Settings
39
+ def self.apply_build_settings(installer)
40
+ project = nil
41
+
42
+ installer.aggregate_targets.each do |target|
43
+ target.user_project.targets.each do |user_target|
44
+ # Only apply to the main app target (Runner)
45
+ next unless user_target.name == 'Runner'
46
+ project = target.user_project
47
+
48
+ user_target.build_configurations.each do |config|
49
+ # Initialize bridge per configuration iteration
50
+ bridge = self.current_config(config.name)
51
+ s = config.build_settings
52
+
53
+ # Injection Mapping: YAML -> Xcode Build Settings (User-Defined)
54
+ s['ASSET_PREFIX'] = bridge.asset_prefix
55
+ s['APP_DISPLAY_NAME'] = bridge.display_name
56
+ s['PRODUCT_BUNDLE_IDENTIFIER'] = bridge.bundle_id
57
+ s['MARKETING_VERSION'] = bridge.version_name
58
+ s['CURRENT_PROJECT_VERSION'] = bridge.version_code
59
+ s['GMS_ADS_ID'] = bridge.gms_ads_id
60
+ s['REVERSED_CLIENT_ID'] = bridge.auth_reversed_client_id
61
+ s['GOOGLE_APP_ID'] = bridge.google_app_id
62
+
63
+ # Optional: Auto-set development team from YAML if present
64
+ s['DEVELOPMENT_TEAM'] = bridge.team_id if bridge.team_id
65
+ end
66
+ end
67
+ end
68
+
69
+ installer.pods_project.targets.each do |pod_target|
70
+ pod_target.build_configurations.each do |config|
71
+ bridge = self.current_config(config.name)
72
+ s = config.build_settings
73
+
74
+ # We push the specific keys needed by the podscripts
75
+ s['GMS_ADS_ID'] = bridge.gms_ads_id
76
+ s['APP_DISPLAY_NAME'] = bridge.display_name
77
+ s['GOOGLE_APP_ID'] = bridge.google_app_id
78
+ s['REVERSED_CLIENT_ID'] = bridge.auth_reversed_client_id
79
+
80
+ end
81
+ end
82
+
83
+ # Save the project once after all configurations are updated
84
+ project.save if project
85
+
86
+ log_flavor = ENV['FLUTTER_APP_FLAVOR'] || "detected"
87
+ puts "[Annai] Successfully injected flavor '#{log_flavor}' settings into Xcode configurations."
88
+ end
89
+
90
+ def initialize(loader, flavor, config_name = nil)
91
+ @loader = loader
92
+ @flavor = flavor
93
+ @platform = :ios
94
+
95
+ # BUILD TYPE DETECTION: Prioritize Flutter CLI mode, fallback to config name regex
96
+ flutter_mode = ENV['FLUTTER_BUILD_MODE']&.downcase
97
+ active_config = config_name || ENV['CONFIGURATION'] || ""
98
+
99
+ if flutter_mode && !flutter_mode.empty?
100
+ # Profile mode maps to release for production-like behavior
101
+ @build_type = (flutter_mode == 'profile') ? 'release' : flutter_mode
102
+ elsif active_config.match?(/debug/i)
103
+ @build_type = 'debug'
104
+ else
105
+ @build_type = 'release'
106
+ end
107
+ end
108
+
109
+ # --- Interface Methods (Proxy to YamlSpecLoader) ---
110
+
111
+ def asset_prefix
112
+ @flavor
113
+ end
114
+
115
+ def display_name
116
+ @loader.get_display_name(@platform, @flavor)
117
+ end
118
+
119
+ def bundle_id
120
+ @loader.get_package_id(@platform, @flavor)
121
+ end
122
+
123
+ def version_name
124
+ @loader.get_version_name(@platform, @flavor)
125
+ end
126
+
127
+ def version_code
128
+ @loader.get_version_code(@platform, @flavor)
129
+ end
130
+
131
+ def gms_ads_id
132
+ @loader.get_gms_ads_id(@platform, @flavor)
133
+ end
134
+
135
+ def team_id
136
+ @loader.get_team_id(@platform, @flavor)
137
+ end
138
+
139
+ def auth_client_id
140
+ @loader.get_auth_client_id(@platform, @flavor, @build_type)
141
+ end
142
+
143
+ def auth_reversed_client_id
144
+ @loader.get_auth_reversed_client_id(@platform, @flavor, @build_type)
145
+ end
146
+
147
+ def google_app_id
148
+ # Manual lookup for nested Firebase IDs (using build_type for debug/release)
149
+ config = @loader.get_merged_flavor_config(@platform, @flavor)
150
+ config.dig('firebase', @build_type, 'firebase_app_id') || ''
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,346 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'fastlane/action'
4
+
5
+ module FastlaneFlutterFlavor
6
+
7
+ # ------------------------------------
8
+ # IntegrationTest (Helper Class)
9
+ # ------------------------------------
10
+ class IntegrationTest
11
+
12
+ def initialize(lane: , isAndroid: , isIos: , test_spec_file: , statusManager:, root_folder:) # Added root_folder
13
+ @lane = lane
14
+ @isAndroid = isAndroid
15
+ @isIos = isIos
16
+ @test_spec_file = test_spec_file
17
+ @loaded = false
18
+ @statusManager = statusManager
19
+ @root_folder = root_folder # Store root folder
20
+ end
21
+
22
+ def cleanup
23
+ end
24
+
25
+ def openEmulators(options: )
26
+
27
+ load_file()
28
+
29
+ @emulators.each_pair { |emulator,port|
30
+ if emulator == options.fetch(:emulator, emulator)
31
+ wipe_data = options.fetch(:wipe_data, true)
32
+ if @isAndroid
33
+ openAndroidEmulator(emulatorName: emulator, port: port, wipe_data: wipe_data, dns_server: options.fetch(:dns_server, ""))
34
+ elsif @isIos
35
+ openIosEmulator(emulatorName: emulator, wipe_data: wipe_data)
36
+ end
37
+ end
38
+ }
39
+ end
40
+
41
+ def closeEmulators(options: )
42
+
43
+ load_file()
44
+
45
+ @emulators.each_pair { |emulator,port|
46
+ if emulator == options.fetch(:emulator, emulator)
47
+ if @isAndroid
48
+ closeAndroidEmulator(emulatorName: emulator, port: port)
49
+ elsif @isIos
50
+ closeIosEmulator(emulatorName: emulator)
51
+ end
52
+ end
53
+ }
54
+ end
55
+
56
+ def runTests(flavor: nil, emulator: nil, open_emulator: true, close_emulator: true, wipe_data: true, skip_screenshots: nil, skip_sound_null_safety: false)
57
+
58
+ load_file()
59
+
60
+ openedEmulator = {}
61
+
62
+ # Determine target emulators: nil => all; string (comma-separated) or array => only those entries
63
+ target_emulators = if emulator.nil?
64
+ @emulators
65
+ else
66
+ emulators_list = emulator.is_a?(String) ? emulator.split(",").map(&:strip) : Array(emulator)
67
+ found = emulators_list.select { |e| @emulators.key?(e) }
68
+ missing = emulators_list - found
69
+
70
+ if missing.any?
71
+ Fastlane::UI.error("Emulator(s) not found in the test spec: #{missing.join(', ')}. Available: #{@emulators.keys.join(', ')}")
72
+ end
73
+
74
+ if found.empty?
75
+ Fastlane::UI.error("None of the specified emulators were found. Aborting runTests.")
76
+ return
77
+ end
78
+
79
+ @emulators.select { |k,_| found.include?(k) }
80
+ end
81
+
82
+ target_emulators.each_pair { |emulator_name, port|
83
+ # already limited to chosen emulators; treat as valid
84
+ isValidEmulator = true
85
+ isEmulatorAlreadyOpen = openedEmulator.fetch(emulator_name, false)
86
+
87
+ if open_emulator && isValidEmulator && !isEmulatorAlreadyOpen
88
+ if @isAndroid
89
+ openAndroidEmulator(emulatorName: emulator_name, port: port, wipe_data: wipe_data, dns_server: "")
90
+ elsif @isIos
91
+ openIosEmulator(emulatorName: emulator_name, wipe_data: wipe_data)
92
+ end
93
+ openedEmulator[emulator_name] = true
94
+ end
95
+
96
+ @flavorDevices.each_pair { |key,value|
97
+ device = key[0]
98
+ flavor_name = key[1]
99
+ next unless emulator_name == device
100
+
101
+ # Determine which flavors to run: if a filter was provided use it, otherwise default to the flavor from the spec
102
+ flavors = if flavor.nil?
103
+ [flavor_name]
104
+ else
105
+ flavor.split(",")
106
+ end
107
+ isValidFlavor = flavors.include?(flavor_name)
108
+ next unless isValidFlavor
109
+
110
+ deviceName = device
111
+ if @isAndroid
112
+ deviceName = "emulator-#{port}"
113
+ end
114
+
115
+ test_cases = value['tests']
116
+ test_cases.each_pair { |testcaseName,parameters|
117
+ begin
118
+ resolved_skip_screenshots = skip_screenshots.nil? ? !@enable_screenshots : skip_screenshots
119
+
120
+ runTest(
121
+ driver_file: value['driver_file'],
122
+ test_file: value['test_file'],
123
+ device_id: deviceName,
124
+ flavor: flavor_name,
125
+ testcase_name: testcaseName,
126
+ parameters: parameters,
127
+ skip_screenshots: resolved_skip_screenshots,
128
+ skip_sound_null_safety: skip_sound_null_safety
129
+ )
130
+
131
+ @statusManager.logSuccess flavor_name, "runTests", emulator_name
132
+ rescue => e
133
+ Fastlane::UI.error "Error while running tests for flavor #{flavor_name}"
134
+
135
+ @statusManager.logError flavor_name, "runTests", emulator_name, e
136
+ end
137
+ }
138
+ }
139
+
140
+ if close_emulator && isValidEmulator
141
+ if @isAndroid
142
+ closeAndroidEmulator(emulatorName: emulator_name, port: port)
143
+ elsif @isIos
144
+ closeIosEmulator(emulatorName: emulator_name)
145
+ end
146
+ openedEmulator[emulator_name] = false
147
+ end
148
+ }
149
+ end
150
+
151
+ private
152
+
153
+ def load_file()
154
+ @emulators = {}
155
+ @flavors = []
156
+ @flavorDevices = {}
157
+ @enable_screenshots = false
158
+
159
+ if File.exist?(@test_spec_file) && !@loaded
160
+ @annaiTestSpecFile = YAML.load_file(@test_spec_file)
161
+ end
162
+
163
+ if !@loaded && @annaiTestSpecFile['annai_app_tests']['enable_screenshots'] != nil
164
+ @enable_screenshots = @annaiTestSpecFile['annai_app_tests']['enable_screenshots']
165
+ end
166
+
167
+ if !@loaded && @isAndroid
168
+ @androidTestSpec = @annaiTestSpecFile['annai_app_tests']['android']
169
+ port = 5554
170
+
171
+ @androidTestSpec['flavor'].each_pair { |flavor,devices|
172
+ @flavors.push(flavor)
173
+ devices['devices'].each_pair { |device,value|
174
+ driver_file = value.fetch('driver_file', @androidTestSpec['default']['driver_file'])
175
+ test_file = value.fetch('test_file', @androidTestSpec['default']['test_file'])
176
+ port_number = value['port_number']
177
+
178
+ @flavorDevices[[device, flavor]] = {
179
+ 'test_file' => test_file,
180
+ 'driver_file' => driver_file,
181
+ 'tests' => value['tests']
182
+ }
183
+ unless @emulators.has_key?(device)
184
+ if(port_number != nil)
185
+ @emulators[device] = port_number
186
+ else
187
+ @emulators[device] = port
188
+ port += 2
189
+ end
190
+ end
191
+ }
192
+ }
193
+ elsif !@loaded && @isIos
194
+ @iosTestSpec = @annaiTestSpecFile['annai_app_tests']['ios']
195
+
196
+ @iosTestSpec['flavor'].each_pair { |flavor,devices|
197
+ @flavors.push(flavor)
198
+ devices['devices'].each_pair { |device,value|
199
+ driver_file = value.fetch('driver_file', @iosTestSpec['default']['driver_file'])
200
+ test_file = value.fetch('test_file', @iosTestSpec['default']['test_file'])
201
+
202
+ @flavorDevices[[device, flavor]] = {
203
+ 'test_file' => test_file,
204
+ 'driver_file' => driver_file,
205
+ 'tests' => value['tests']
206
+ }
207
+ unless @emulators.has_key?(device)
208
+ @emulators[device] = 0
209
+ end
210
+ }
211
+ }
212
+ end
213
+ @loaded = true
214
+ #puts @emulators
215
+ #puts @flavors
216
+ #puts @flavorDevices
217
+ end
218
+
219
+ def openAndroidEmulator(emulatorName: , port: ,wipe_data: true, dns_server: "")
220
+
221
+ if wipe_data
222
+ if dns_server != ""
223
+ Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-wipe-data", "-dns-server", dns_server)
224
+ else
225
+ Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-wipe-data")
226
+ end
227
+ else
228
+ if dns_server != ""
229
+ Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-dns-server", dns_server)
230
+ else
231
+ Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName)
232
+ end
233
+ end
234
+
235
+ sleep(40)
236
+ end
237
+
238
+ def closeAndroidEmulator(emulatorName: , port:)
239
+ Fastlane::Actions::sh("screen", "-dmS", emulatorName, "adb", "-s", "emulator-#{port}", "emu", "kill")
240
+ sleep(5)
241
+ end
242
+
243
+ def openIosEmulator(emulatorName: ,wipe_data: true)
244
+ res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
245
+ udid = res.match(/"udid"\s+:\s+"(.*)"/)[1]
246
+ if wipe_data
247
+ Fastlane::Actions::sh("xcrun", "simctl", "erase", udid, log: false)
248
+ sleep(10)
249
+ end
250
+
251
+ Fastlane::Actions::sh("open", "-a", "Simulator", "--args", "-CurrentDeviceUDID", udid)
252
+ sleep(10)
253
+ res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
254
+ state = res.match(/"state"\s+:\s+"(.*)"/)[1]
255
+ if state == "Shutdown"
256
+ Fastlane::Actions::sh("xcrun", "simctl", "boot", udid)
257
+ sleep(10)
258
+ end
259
+ end
260
+
261
+ def closeIosEmulator(emulatorName:)
262
+ res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
263
+ udid = res.match(/"udid"\s+:\s+"(.*)"/)[1]
264
+ state = res.match(/"state"\s+:\s+"(.*)"/)[1]
265
+ unless state == "Shutdown"
266
+ Fastlane::Actions::sh("xcrun", "simctl", "shutdown", udid)
267
+ sleep(5)
268
+ end
269
+ end
270
+
271
+ def runTest(driver_file:, test_file:, device_id:, flavor: "", testcase_name: "", parameters:, skip_sound_null_safety: false, skip_screenshots: true)
272
+
273
+ dart_define_option = Array.new(10) {|i| '--dart-define=' }
274
+ index = 0
275
+ if testcase_name != ""
276
+ dart_define_option[index] = "--dart-define=testcase_name=#{testcase_name}"
277
+ index += 1
278
+ end
279
+
280
+ flavorOption = ""
281
+ if flavor != ""
282
+ flavorOption = "--flavor"
283
+ dart_define_option[index] = "--dart-define=flavor=#{flavor}"
284
+ index += 1
285
+ end
286
+
287
+ parameters.each_pair { |key,value|
288
+ dart_define_option[index] = "--dart-define=#{key}=#{value}"
289
+ index += 1
290
+ }
291
+
292
+ nullSafety = ""
293
+ if skip_sound_null_safety
294
+ nullSafety = "--no-sound-null-safety"
295
+ end
296
+
297
+ device_option = ""
298
+ if device_id != ""
299
+ device_option = "-d"
300
+ end
301
+
302
+ # Changed Dir.chdir ".." to use the dynamically found @root_folder
303
+ if skip_screenshots
304
+ Dir.chdir @root_folder do
305
+ Fastlane::Actions::sh(
306
+ "flutter", "test",
307
+ test_file,
308
+ flavorOption, flavor,
309
+ dart_define_option[0],
310
+ dart_define_option[1],
311
+ dart_define_option[2],
312
+ dart_define_option[3],
313
+ dart_define_option[4],
314
+ dart_define_option[5],
315
+ dart_define_option[6],
316
+ dart_define_option[7],
317
+ dart_define_option[8],
318
+ dart_define_option[9],
319
+ device_option, device_id
320
+ )
321
+ end
322
+ else
323
+ Dir.chdir @root_folder do
324
+ Fastlane::Actions::sh(
325
+ "flutter", "drive",
326
+ "--driver=#{driver_file}",
327
+ "--target=#{test_file}",
328
+ flavorOption, flavor,
329
+ nullSafety,
330
+ dart_define_option[0],
331
+ dart_define_option[1],
332
+ dart_define_option[2],
333
+ dart_define_option[3],
334
+ dart_define_option[4],
335
+ dart_define_option[5],
336
+ dart_define_option[6],
337
+ dart_define_option[7],
338
+ dart_define_option[8],
339
+ dart_define_option[9],
340
+ device_option, device_id
341
+ )
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end