UI测试启动后无法修改launchEnvironment,求可行解决方案
解决UI测试运行时动态修改Mock URL的问题
你遇到的问题本质是:应用启动后,测试端修改XCUIApplication的launchEnvironment不会同步到已经运行的应用进程中——因为ProcessInfo的环境变量在应用启动时就被固化了,运行时无法修改。下面给你几个可行的实现方案:
方案一:用UserDefaults传递动态配置
利用UI测试可以通过XCUIApplication操作应用的UserDefaults的特性,把Mock URL配置存在UserDefaults里,应用端在发起请求时优先读取这里的值。
应用端修改
更新URLRequest扩展,优先从UserDefaults取替换URL,没有再回退到环境变量:
extension URLRequest { private var isUITestingLaunching: Bool { ProcessInfo.processInfo.arguments.contains("UI-TESTING") } mutating func tryReplaceUITestRequest() { #if DEBUG guard isUITestingLaunching, let path = url?.path else { return } // 优先从UserDefaults读取动态配置 let userDefaults = UserDefaults.standard if let substitutionUrlString = userDefaults.string(forKey: path) ?? ProcessInfo.processInfo.environment[path], let substitutionUrl = URL(string: substitutionUrlString) { url = substitutionUrl } #endif } }
测试端修改
在需要更新Mock URL时,直接通过XCUIApplication设置应用的UserDefaults:
func testExample() throws { // 1 启动应用,初始配置 let app = XCUIApplication() app.launchArguments += ["UI-TESTING"] // 初始配置同时写入UserDefaults(保证启动时生效) app.setPreferencesValue("http://localhost:9000/SomeUrlPath", forKey: "HomeSceneStructureUrlPath") app.launch() // 2 验证初始UI ... XCTAssertFalse(someElement.waitForExistence(timeout: 10)) // 3 动态更新Mock URL(直接修改应用的UserDefaults) app.setPreferencesValue("http://localhost:9000/OtherUrlPath", forKey: "HomeSceneStructureUrlPath") // 4 下拉刷新 let firstCell = app.tables["someTable"].cells.firstMatch let start = firstCell.coordinate(withNormalizedOffset: .zero) let finish = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 10)) start.press(forDuration: 0, thenDragTo: finish) // 5 验证更新后的UI ... XCTAssertTrue(settingsButton.waitForExistence(timeout: 10)) }
方案二:用自定义URL Scheme传递指令
给应用注册一个测试专用的URL Scheme,测试端通过发送URL指令,让应用在运行时更新Mock URL配置。
应用端配置
- 首先在项目Info的URL Types里添加一个自定义Scheme,比如
test-mock。 - 创建一个单例来存储动态Mock配置:
class MockURLManager { static let shared = MockURLManager() private var urlMappings: [String: String] = [:] func updateMapping(for path: String, url: String) { urlMappings[path] = url } func urlForPath(_ path: String) -> String? { urlMappings[path] } }
- 在
SceneDelegate或AppDelegate中处理URL Scheme:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let url = URLContexts.first?.url, url.scheme == "test-mock" else { return } if let path = url.queryParameters?["path"], let mockUrl = url.queryParameters?["url"] { MockURLManager.shared.updateMapping(for: path, url: mockUrl) } } // 给URL加个解析query参数的扩展 extension URL { var queryParameters: [String: String]? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { return nil } return queryItems.reduce(into: [String: String]()) { result, item in result[item.name] = item.value } } }
- 更新
URLRequest扩展,优先使用单例的配置:
mutating func tryReplaceUITestRequest() { #if DEBUG guard isUITestingLaunching, let path = url?.path else { return } let substitutionUrlString = MockURLManager.shared.urlForPath(path) ?? ProcessInfo.processInfo.environment[path] if let urlString = substitutionUrlString, let substitutionUrl = URL(string: urlString) { self.url = substitutionUrl } #endif }
测试端修改
在步骤3中,构造URL并发送给应用:
// 3 动态更新Mock URL let updateUrl = URL(string: "test-mock://update?path=HomeSceneStructureUrlPath&url=http://localhost:9000/OtherUrlPath")! app.open(updateUrl) // 等待配置生效(可选,根据实际情况调整) sleep(1)
方案三:重启应用(备选)
如果不需要保留当前页面状态,也可以直接重启应用并传入新的launchEnvironment:
// 3 重启应用,传入新配置 app.terminate() app.launchEnvironment["HomeSceneStructureUrlPath"] = "http://localhost:9000/OtherUrlPath" app.launch()
这个方案简单但会丢失应用状态,适合不需要保留上下文的测试场景。
内容的提问来源于stack exchange,提问作者Igor Niki
相关产品推荐
相关产品推荐

