@@ -2,6 +2,7 @@ import XCTest
22import UIKit
33import CoreGraphics
44import Darwin
5+ import Foundation
56
67final class HelloCodenameOneUITests: XCTestCase {
78 private var app: XCUIApplication!
@@ -275,13 +276,10 @@ final class HelloCodenameOneUITests: XCTestCase {
275276 if let explicit = targetBundleIdentifier, !explicit.isEmpty {
276277 return explicit
277278 }
278- do {
279- let value = try app.value (forKey: " bundleID" )
280- if let actual = value as? String, !actual.isEmpty {
281- return actual
279+ if let bundle: String = dynamicAppValue(" bundleID" ) {
280+ if !bundle.isEmpty {
281+ return bundle
282282 }
283- } catch {
284- print (" CN1SS:WARN:ui_test_bundle_resolution_failed error=\( error)" )
285283 }
286284 return nil
287285 }
@@ -449,20 +447,16 @@ private final class CodenameOneMainInvoker {
449447 }
450448
451449 private func locateAppContainer(app: XCUIApplication, bundleIdentifier: String) -> String? {
452- do {
453- if let bundleURL = try app.value (forKey: " bundleURL" ) as? URL {
454- return bundleURL.path
455- }
456- } catch {
457- print (" CN1SS:WARN:codenameone_main_kvc_failed key=bundleURL bundle=\( bundleIdentifier) error=\( error)" )
450+ if let bundleURL: URL = dynamicAppValue(" bundleURL" ), !bundleURL.path.isEmpty {
451+ return bundleURL.path
458452 }
459453
460- do {
461- if let bundlePath = try app .value (forKey: " bundlePath " ) as? String {
462- return bundlePath
463- }
464- } catch {
465- print ( " CN1SS:WARN:codenameone_main_kvc_failed key=bundlePath bundle= \( bundleIdentifier) error= \( error) " )
454+ if let bundlePath: String = dynamicAppValue( " bundlePath " ), !bundlePath .isEmpty {
455+ return bundlePath
456+ }
457+
458+ if let container = locateViaSimctl(bundleIdentifier: bundleIdentifier) {
459+ return container
466460 }
467461
468462 if let fallback = Bundle.main.infoDictionary ?[" CFBundleExecutable" ] as? String {
@@ -486,6 +480,71 @@ private final class CodenameOneMainInvoker {
486480 return executable
487481 }
488482
483+ private func locateViaSimctl(bundleIdentifier: String) -> String? {
484+ let env = ProcessInfo.processInfo.environment
485+ guard let udid = env[" SIMULATOR_UDID" ], !udid.isEmpty else {
486+ return nil
487+ }
488+
489+ let task = Process()
490+ task.executableURL = URL(fileURLWithPath: " /usr/bin/xcrun" )
491+ task.arguments = [" simctl" , " get_app_container" , udid, bundleIdentifier]
492+
493+ let stdoutPipe = Pipe()
494+ let stderrPipe = Pipe()
495+ task.standardOutput = stdoutPipe
496+ task.standardError = stderrPipe
497+
498+ do {
499+ try task.run ()
500+ } catch {
501+ print (" CN1SS:WARN:codenameone_main_simctl_failed bundle=\( bundleIdentifier) error=\( error)" )
502+ return nil
503+ }
504+
505+ task.waitUntilExit ()
506+ if task.terminationStatus != 0 {
507+ let data = stderrPipe.fileHandleForReading.readDataToEndOfFile ()
508+ if let message = String(data: data, encoding: .utf8 )?.trimmingCharacters (in: .whitespacesAndNewlines ), !message.isEmpty {
509+ print (" CN1SS:WARN:codenameone_main_simctl_failed bundle=\( bundleIdentifier) status=\( task.terminationStatus) stderr=\( message)" )
510+ } else {
511+ print (" CN1SS:WARN:codenameone_main_simctl_failed bundle=\( bundleIdentifier) status=\( task.terminationStatus)" )
512+ }
513+ return nil
514+ }
515+
516+ let data = stdoutPipe.fileHandleForReading.readDataToEndOfFile ()
517+ guard let output = String(data: data, encoding: .utf8 )?.trimmingCharacters (in: .whitespacesAndNewlines ), !output.isEmpty else {
518+ print (" CN1SS:WARN:codenameone_main_simctl_empty bundle=\( bundleIdentifier)" )
519+ return nil
520+ }
521+
522+ return output
523+ }
524+
525+ private func dynamicAppValue<T>(_ selectorName: String) -> T? {
526+ let selector = NSSelectorFromString(selectorName)
527+ guard app.responds (to: selector) else {
528+ return nil
529+ }
530+ guard let unmanaged = app.perform (selector) else {
531+ return nil
532+ }
533+ let value = unmanaged.takeUnretainedValue ()
534+ switch value {
535+ case let typed as T:
536+ return typed
537+ case let number as NSNumber where T.self == Bool.self :
538+ return (number.boolValue as? T)
539+ case let string as NSString where T.self == String.self :
540+ return (string as String) as? T
541+ case let url as NSURL where T.self == URL.self :
542+ return (url as URL) as? T
543+ default:
544+ return nil
545+ }
546+ }
547+
489548 private struct InvocationContext {
490549 typealias InitConstantPoolFn = @convention(c) () -> Void
491550 typealias GetThreadLocalDataFn = @convention(c) () -> UnsafeMutableRawPointer?
0 commit comments