mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
Tracing and integration test tweaks (#3336)
* Disable image and document picker integration tests as they randomly fail to load and are flakey. * Delete any pre-existing log files * Various tracing tweaks and fixes: - delete the custom tracing log levels as we can't control their ouput - implement comparable on them - change default levels only if the new chosen level increases their verbosity * Make logging targets mandatory and fix their logging levels * Switch back to using the `run_tests` reset simulator flag as `fastlane snapshot reset_simulators` was too generic and slow * Switch all integration test taps to `tapCenter` (nee forceTap) after noticing missed taps on CI. * Make the logging file prefix explicit, let the main app not use one. * Rename tracing configuration `target` to `currentTarget`
This commit is contained in:
@@ -30,8 +30,8 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
source ci_scripts/ci_common.sh && setup_github_actions_environment
|
source ci_scripts/ci_common.sh && setup_github_actions_environment
|
||||||
|
|
||||||
- name: Reset simulators
|
- name: Delete old log files
|
||||||
run: SNAPSHOT_FORCE_DELETE=1 bundle exec fastlane snapshot reset_simulators
|
run: find '/Users/Shared' -name 'console*' -delete
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: bundle exec fastlane integration_tests
|
run: bundle exec fastlane integration_tests
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
run: (grep ' TRACE ' /Users/Shared -qR)
|
run: (grep ' TRACE ' /Users/Shared -qR)
|
||||||
|
|
||||||
- name: Check logs don't contain private messages
|
- name: Check logs don't contain private messages
|
||||||
run: (! grep 'Go down in flames' /Users/Shared -qR)
|
run: "! grep 'Go down in flames' /Users/Shared -R"
|
||||||
|
|
||||||
- name: Zip results # for faster upload
|
- name: Zip results # for faster upload
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
swiftformat --lint .
|
swiftformat --lint .
|
||||||
|
|
||||||
- name: Reset simulators
|
|
||||||
run: SNAPSHOT_FORCE_DELETE=1 bundle exec fastlane snapshot reset_simulators
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: bundle exec fastlane unit_tests
|
run: bundle exec fastlane unit_tests
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ jobs:
|
|||||||
- name: SwiftFormat
|
- name: SwiftFormat
|
||||||
run: swiftformat --lint .
|
run: swiftformat --lint .
|
||||||
|
|
||||||
- name: Reset simulators
|
|
||||||
run: SNAPSHOT_FORCE_DELETE=1 bundle exec fastlane snapshot reset_simulators
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: bundle exec fastlane unit_tests skip_previews:true
|
run: bundle exec fastlane unit_tests skip_previews:true
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
|
|
||||||
let appSettings = appHooks.appSettingsHook.configure(AppSettings())
|
let appSettings = appHooks.appSettingsHook.configure(AppSettings())
|
||||||
|
|
||||||
MXLog.configure(logLevel: appSettings.logLevel)
|
MXLog.configure(currentTarget: "elementx", filePrefix: nil, logLevel: appSettings.logLevel)
|
||||||
|
|
||||||
let appName = InfoPlistReader.main.bundleDisplayName
|
let appName = InfoPlistReader.main.bundleDisplayName
|
||||||
let appVersion = InfoPlistReader.main.bundleShortVersionString
|
let appVersion = InfoPlistReader.main.bundleShortVersionString
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import XCTest
|
|||||||
|
|
||||||
extension XCUIElement {
|
extension XCUIElement {
|
||||||
func clearAndTypeText(_ text: String) {
|
func clearAndTypeText(_ text: String) {
|
||||||
forceTap()
|
tapCenter()
|
||||||
|
|
||||||
guard let currentValue = value as? String else {
|
guard let currentValue = value as? String else {
|
||||||
XCTFail("Tried to clear and type text into a non string value")
|
XCTFail("Tried to clear and type text into a non string value")
|
||||||
@@ -24,7 +24,7 @@ extension XCUIElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func forceTap() {
|
func tapCenter() {
|
||||||
let coordinate: XCUICoordinate = coordinate(withNormalizedOffset: .init(dx: 0.5, dy: 0.5))
|
let coordinate: XCUICoordinate = coordinate(withNormalizedOffset: .init(dx: 0.5, dy: 0.5))
|
||||||
coordinate.tap()
|
coordinate.tap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,21 +22,18 @@ enum MXLog {
|
|||||||
private static var didConfigureOnce = false
|
private static var didConfigureOnce = false
|
||||||
|
|
||||||
private static var rootSpan: Span!
|
private static var rootSpan: Span!
|
||||||
private static var target: String!
|
private static var currentTarget: String!
|
||||||
|
|
||||||
static func configure(target: String? = nil,
|
static func configure(currentTarget: String,
|
||||||
|
filePrefix: String?,
|
||||||
logLevel: TracingConfiguration.LogLevel) {
|
logLevel: TracingConfiguration.LogLevel) {
|
||||||
guard !didConfigureOnce else { return }
|
guard !didConfigureOnce else { return }
|
||||||
|
|
||||||
RustTracing.setup(configuration: .init(logLevel: logLevel, target: target))
|
RustTracing.setup(configuration: .init(logLevel: logLevel, currentTarget: currentTarget, filePrefix: filePrefix))
|
||||||
|
|
||||||
if let target {
|
self.currentTarget = currentTarget
|
||||||
self.target = target
|
|
||||||
} else {
|
|
||||||
self.target = Constants.target
|
|
||||||
}
|
|
||||||
|
|
||||||
rootSpan = Span(file: #file, line: #line, level: .info, target: self.target, name: "root")
|
rootSpan = Span(file: #file, line: #line, level: .info, target: self.currentTarget, name: "root")
|
||||||
rootSpan.enter()
|
rootSpan.enter()
|
||||||
|
|
||||||
didConfigureOnce = true
|
didConfigureOnce = true
|
||||||
@@ -138,7 +135,7 @@ enum MXLog {
|
|||||||
rootSpan.enter()
|
rootSpan.enter()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Span(file: file, line: UInt32(line), level: level, target: target, name: name)
|
return Span(file: file, line: UInt32(line), level: level, target: currentTarget, name: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// periphery:ignore:parameters function,column,context
|
// periphery:ignore:parameters function,column,context
|
||||||
@@ -157,6 +154,6 @@ enum MXLog {
|
|||||||
rootSpan.enter()
|
rootSpan.enter()
|
||||||
}
|
}
|
||||||
|
|
||||||
logEvent(file: (file as NSString).lastPathComponent, line: UInt32(line), level: level, target: target, message: "\(message)")
|
logEvent(file: (file as NSString).lastPathComponent, line: UInt32(line), level: level, target: currentTarget, message: "\(message)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ enum RustTracing {
|
|||||||
// Log everything on integration tests to check whether
|
// Log everything on integration tests to check whether
|
||||||
// the logs contain any sensitive data. See `UserFlowTests.swift`
|
// the logs contain any sensitive data. See `UserFlowTests.swift`
|
||||||
let filter = if ProcessInfo.isRunningIntegrationTests {
|
let filter = if ProcessInfo.isRunningIntegrationTests {
|
||||||
TracingConfiguration(logLevel: .trace, target: nil).filter
|
TracingConfiguration(logLevel: .trace, currentTarget: "integrationtests", filePrefix: nil).filter
|
||||||
} else {
|
} else {
|
||||||
configuration.filter
|
configuration.filter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import Collections
|
|||||||
// We can filter by level, crate and even file. See more details here:
|
// We can filter by level, crate and even file. See more details here:
|
||||||
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
|
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
|
||||||
struct TracingConfiguration {
|
struct TracingConfiguration {
|
||||||
enum LogLevel: Codable, Hashable {
|
enum LogLevel: String, Codable, Hashable, Comparable {
|
||||||
case error, warn, info, debug, trace
|
case error, warn, info, debug, trace
|
||||||
case custom(String)
|
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -27,34 +26,32 @@ struct TracingConfiguration {
|
|||||||
return "Debug"
|
return "Debug"
|
||||||
case .trace:
|
case .trace:
|
||||||
return "Trace"
|
return "Trace"
|
||||||
case .custom:
|
|
||||||
return "Custom"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var rawValue: String {
|
static func < (lhs: TracingConfiguration.LogLevel, rhs: TracingConfiguration.LogLevel) -> Bool {
|
||||||
switch self {
|
switch (lhs, rhs) {
|
||||||
case .error:
|
case (.error, _):
|
||||||
return "error"
|
true
|
||||||
case .warn:
|
case (.warn, .error):
|
||||||
return "warn"
|
false
|
||||||
case .info:
|
case (.warn, _):
|
||||||
return "info"
|
true
|
||||||
case .debug:
|
case (.info, .error), (.info, .warn):
|
||||||
return "debug"
|
false
|
||||||
case .trace:
|
case (.info, _):
|
||||||
return "trace"
|
true
|
||||||
case .custom(let filter):
|
case (.debug, .error), (.debug, .warn), (.debug, .info):
|
||||||
return filter
|
false
|
||||||
|
case (.debug, _):
|
||||||
|
true
|
||||||
|
case (.trace, _):
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Target: String {
|
enum Target: String {
|
||||||
case common = ""
|
|
||||||
|
|
||||||
case elementx
|
|
||||||
|
|
||||||
case hyper, matrix_sdk_ffi, matrix_sdk_crypto
|
case hyper, matrix_sdk_ffi, matrix_sdk_crypto
|
||||||
|
|
||||||
case matrix_sdk_client = "matrix_sdk::client"
|
case matrix_sdk_client = "matrix_sdk::client"
|
||||||
@@ -66,9 +63,8 @@ struct TracingConfiguration {
|
|||||||
case matrix_sdk_ui_timeline = "matrix_sdk_ui::timeline"
|
case matrix_sdk_ui_timeline = "matrix_sdk_ui::timeline"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The `common` target is excluded because 3rd-party crates might end up logging user data.
|
||||||
static let targets: OrderedDictionary<Target, LogLevel> = [
|
static let targets: OrderedDictionary<Target, LogLevel> = [
|
||||||
.common: .info, // Never set this lower than info - 3rd-party crates may start logging user data.
|
|
||||||
.elementx: .info,
|
|
||||||
.hyper: .warn,
|
.hyper: .warn,
|
||||||
.matrix_sdk_ffi: .info,
|
.matrix_sdk_ffi: .info,
|
||||||
.matrix_sdk_client: .trace,
|
.matrix_sdk_client: .trace,
|
||||||
@@ -92,41 +88,38 @@ struct TracingConfiguration {
|
|||||||
/// - Parameter logLevel: the desired log level
|
/// - Parameter logLevel: the desired log level
|
||||||
/// - Parameter target: the name of the target being configured
|
/// - Parameter target: the name of the target being configured
|
||||||
/// - Returns: a custom tracing configuration
|
/// - Returns: a custom tracing configuration
|
||||||
init(logLevel: LogLevel, target: String?) {
|
init(logLevel: LogLevel, currentTarget: String, filePrefix: String?) {
|
||||||
fileName = if let target {
|
fileName = if let filePrefix {
|
||||||
"\(RustTracing.filePrefix)-\(target)"
|
"\(RustTracing.filePrefix)-\(filePrefix)"
|
||||||
} else {
|
} else {
|
||||||
RustTracing.filePrefix
|
RustTracing.filePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .custom(filter) = logLevel {
|
|
||||||
self.filter = filter
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let overrides = Self.targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in
|
let overrides = Self.targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in
|
||||||
// Keep the defaults here
|
// Keep the defaults here
|
||||||
let ignoredTargets: [Target] = [.common, // Never remove common from the ignored targets (see above for more info).
|
let ignoredTargets: [Target] = [.hyper]
|
||||||
.hyper,
|
|
||||||
.matrix_sdk_ffi,
|
|
||||||
.matrix_sdk_oidc,
|
|
||||||
.matrix_sdk_client,
|
|
||||||
.matrix_sdk_crypto,
|
|
||||||
.matrix_sdk_crypto_account,
|
|
||||||
.matrix_sdk_http_client]
|
|
||||||
if ignoredTargets.contains(target) {
|
if ignoredTargets.contains(target) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let defaultTargetLogLevel = Self.targets[target] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only change the targets that have default values
|
||||||
|
// smaller than the desired log level
|
||||||
|
if defaultTargetLogLevel < logLevel {
|
||||||
partialResult[target] = logLevel
|
partialResult[target] = logLevel
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var newTargets = Self.targets
|
var newTargets = Self.targets
|
||||||
for (target, logLevel) in overrides {
|
for (target, logLevel) in overrides {
|
||||||
newTargets.updateValue(logLevel, forKey: target)
|
newTargets.updateValue(logLevel, forKey: target)
|
||||||
}
|
}
|
||||||
|
|
||||||
let components = newTargets.map { (target: Target, logLevel: LogLevel) in
|
var components = newTargets.map { (target: Target, logLevel: LogLevel) in
|
||||||
guard !target.rawValue.isEmpty else {
|
guard !target.rawValue.isEmpty else {
|
||||||
return logLevel.rawValue
|
return logLevel.rawValue
|
||||||
}
|
}
|
||||||
@@ -134,6 +127,10 @@ struct TracingConfiguration {
|
|||||||
return "\(target.rawValue)=\(logLevel.rawValue)"
|
return "\(target.rawValue)=\(logLevel.rawValue)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With `common` not being used we manually need to specify the log
|
||||||
|
// level for passed in targets
|
||||||
|
components.append("\(currentTarget)=\(logLevel.rawValue)")
|
||||||
|
|
||||||
filter = components.joined(separator: ",")
|
filter = components.joined(separator: ",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-26
@@ -135,18 +135,6 @@ struct DeveloperOptionsScreen: View {
|
|||||||
private struct LogLevelConfigurationView: View {
|
private struct LogLevelConfigurationView: View {
|
||||||
@Binding var logLevel: TracingConfiguration.LogLevel
|
@Binding var logLevel: TracingConfiguration.LogLevel
|
||||||
|
|
||||||
@State private var customTracingConfiguration: String
|
|
||||||
|
|
||||||
init(logLevel: Binding<TracingConfiguration.LogLevel>) {
|
|
||||||
_logLevel = logLevel
|
|
||||||
|
|
||||||
if case .custom(let configuration) = logLevel.wrappedValue {
|
|
||||||
customTracingConfiguration = configuration
|
|
||||||
} else {
|
|
||||||
customTracingConfiguration = TracingConfiguration(logLevel: .info, target: nil).filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Picker(selection: $logLevel) {
|
Picker(selection: $logLevel) {
|
||||||
ForEach(logLevels, id: \.self) { logLevel in
|
ForEach(logLevels, id: \.self) { logLevel in
|
||||||
@@ -156,24 +144,11 @@ private struct LogLevelConfigurationView: View {
|
|||||||
Text("Log level")
|
Text("Log level")
|
||||||
Text("Requires app reboot")
|
Text("Requires app reboot")
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .custom = logLevel {
|
|
||||||
TextEditor(text: $customTracingConfiguration)
|
|
||||||
.textInputAutocapitalization(.never)
|
|
||||||
.autocorrectionDisabled()
|
|
||||||
.onChange(of: customTracingConfiguration) { newValue in
|
|
||||||
logLevel = .custom(newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows the picker to work with associated values
|
/// Allows the picker to work with associated values
|
||||||
private var logLevels: [TracingConfiguration.LogLevel] {
|
private var logLevels: [TracingConfiguration.LogLevel] {
|
||||||
if case let .custom(filter) = logLevel {
|
[.error, .warn, .info, .debug, .trace]
|
||||||
return [.error, .warn, .info, .debug, .trace, .custom(filter)]
|
|
||||||
} else {
|
|
||||||
return [.error, .warn, .info, .debug, .trace, .custom("")]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate
|
|||||||
|
|
||||||
windowManager.delegate = self
|
windowManager.delegate = self
|
||||||
|
|
||||||
MXLog.configure(logLevel: .debug)
|
MXLog.configure(currentTarget: "uitests", filePrefix: nil, logLevel: .debug)
|
||||||
|
|
||||||
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ extension XCUIApplication {
|
|||||||
let getStartedButton = buttons[A11yIdentifiers.authenticationStartScreen.signIn]
|
let getStartedButton = buttons[A11yIdentifiers.authenticationStartScreen.signIn]
|
||||||
|
|
||||||
XCTAssertTrue(getStartedButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(getStartedButton.waitForExistence(timeout: 10.0))
|
||||||
getStartedButton.tap()
|
getStartedButton.tapCenter()
|
||||||
|
|
||||||
// Get started is network bound, wait for the change homeserver button for longer
|
|
||||||
let changeHomeserverButton = buttons[A11yIdentifiers.serverConfirmationScreen.changeServer]
|
let changeHomeserverButton = buttons[A11yIdentifiers.serverConfirmationScreen.changeServer]
|
||||||
XCTAssertTrue(changeHomeserverButton.waitForExistence(timeout: 30.0))
|
XCTAssertTrue(changeHomeserverButton.waitForExistence(timeout: 10.0))
|
||||||
changeHomeserverButton.tap()
|
changeHomeserverButton.tapCenter()
|
||||||
|
|
||||||
let homeserverTextField = textFields[A11yIdentifiers.changeServerScreen.server]
|
let homeserverTextField = textFields[A11yIdentifiers.changeServerScreen.server]
|
||||||
XCTAssertTrue(homeserverTextField.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(homeserverTextField.waitForExistence(timeout: 10.0))
|
||||||
@@ -26,7 +25,7 @@ extension XCUIApplication {
|
|||||||
|
|
||||||
let confirmButton = buttons[A11yIdentifiers.changeServerScreen.continue]
|
let confirmButton = buttons[A11yIdentifiers.changeServerScreen.continue]
|
||||||
XCTAssertTrue(confirmButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(confirmButton.waitForExistence(timeout: 10.0))
|
||||||
confirmButton.tap()
|
confirmButton.tapCenter()
|
||||||
|
|
||||||
// Wait for server confirmation to finish
|
// Wait for server confirmation to finish
|
||||||
let doesNotExistPredicate = NSPredicate(format: "exists == 0")
|
let doesNotExistPredicate = NSPredicate(format: "exists == 0")
|
||||||
@@ -35,7 +34,7 @@ extension XCUIApplication {
|
|||||||
|
|
||||||
let continueButton = buttons[A11yIdentifiers.serverConfirmationScreen.continue]
|
let continueButton = buttons[A11yIdentifiers.serverConfirmationScreen.continue]
|
||||||
XCTAssertTrue(continueButton.waitForExistence(timeout: 30.0))
|
XCTAssertTrue(continueButton.waitForExistence(timeout: 30.0))
|
||||||
continueButton.tap()
|
continueButton.tapCenter()
|
||||||
|
|
||||||
let usernameTextField = textFields[A11yIdentifiers.loginScreen.emailUsername]
|
let usernameTextField = textFields[A11yIdentifiers.loginScreen.emailUsername]
|
||||||
XCTAssertTrue(usernameTextField.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(usernameTextField.waitForExistence(timeout: 10.0))
|
||||||
@@ -51,7 +50,7 @@ extension XCUIApplication {
|
|||||||
XCTAssertTrue(nextButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(nextButton.waitForExistence(timeout: 10.0))
|
||||||
XCTAssertTrue(nextButton.isEnabled)
|
XCTAssertTrue(nextButton.isEnabled)
|
||||||
|
|
||||||
nextButton.tap()
|
nextButton.tapCenter()
|
||||||
|
|
||||||
// Wait for login to finish
|
// Wait for login to finish
|
||||||
currentTestCase.expectation(for: doesNotExistPredicate, evaluatedWith: usernameTextField)
|
currentTestCase.expectation(for: doesNotExistPredicate, evaluatedWith: usernameTextField)
|
||||||
@@ -63,7 +62,7 @@ extension XCUIApplication {
|
|||||||
// Tapping the sheet button while animating upwards fails. Wait for it to settle
|
// Tapping the sheet button while animating upwards fails. Wait for it to settle
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
savePasswordButton.tap()
|
savePasswordButton.tapCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the home screen to become visible.
|
// Wait for the home screen to become visible.
|
||||||
@@ -80,17 +79,17 @@ extension XCUIApplication {
|
|||||||
let profileButton = buttons[A11yIdentifiers.homeScreen.userAvatar]
|
let profileButton = buttons[A11yIdentifiers.homeScreen.userAvatar]
|
||||||
|
|
||||||
// `Failed to scroll to visible (by AX action) Button` https://stackoverflow.com/a/33534187/730924
|
// `Failed to scroll to visible (by AX action) Button` https://stackoverflow.com/a/33534187/730924
|
||||||
profileButton.forceTap()
|
profileButton.tapCenter()
|
||||||
|
|
||||||
// Logout
|
// Logout
|
||||||
let logoutButton = buttons[A11yIdentifiers.settingsScreen.logout]
|
let logoutButton = buttons[A11yIdentifiers.settingsScreen.logout]
|
||||||
XCTAssertTrue(logoutButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(logoutButton.waitForExistence(timeout: 10.0))
|
||||||
logoutButton.tap()
|
logoutButton.tapCenter()
|
||||||
|
|
||||||
// Confirm logout
|
// Confirm logout
|
||||||
let alertLogoutButton = alerts.firstMatch.buttons["Sign out"]
|
let alertLogoutButton = alerts.firstMatch.buttons["Sign out"]
|
||||||
XCTAssertTrue(alertLogoutButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(alertLogoutButton.waitForExistence(timeout: 10.0))
|
||||||
alertLogoutButton.tap()
|
alertLogoutButton.tapCenter()
|
||||||
|
|
||||||
// Check that we're back on the login screen
|
// Check that we're back on the login screen
|
||||||
let getStartedButton = buttons[A11yIdentifiers.authenticationStartScreen.signIn]
|
let getStartedButton = buttons[A11yIdentifiers.authenticationStartScreen.signIn]
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ class UserFlowTests: XCTestCase {
|
|||||||
// And open it
|
// And open it
|
||||||
let firstRoom = app.buttons.matching(NSPredicate(format: "identifier CONTAINS %@", Self.integrationTestsRoomName)).firstMatch
|
let firstRoom = app.buttons.matching(NSPredicate(format: "identifier CONTAINS %@", Self.integrationTestsRoomName)).firstMatch
|
||||||
XCTAssertTrue(firstRoom.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(firstRoom.waitForExistence(timeout: 10.0))
|
||||||
firstRoom.tap()
|
firstRoom.tapCenter()
|
||||||
|
|
||||||
sendMessages()
|
sendMessages()
|
||||||
|
|
||||||
checkPhotoSharing()
|
// Intentionally disabled as they're super flakey on iOS 18 simulators
|
||||||
|
// checkPhotoSharing()
|
||||||
checkDocumentSharing()
|
// checkDocumentSharing()
|
||||||
|
|
||||||
checkLocationSharing()
|
checkLocationSharing()
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
// Cancel initial the room search
|
// Cancel initial the room search
|
||||||
let searchCancelButton = app.buttons["Cancel"].firstMatch
|
let searchCancelButton = app.buttons["Cancel"].firstMatch
|
||||||
XCTAssertTrue(searchCancelButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(searchCancelButton.waitForExistence(timeout: 10.0))
|
||||||
searchCancelButton.forceTap()
|
searchCancelButton.tapCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessages() {
|
private func sendMessages() {
|
||||||
@@ -67,7 +67,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
|
|
||||||
var sendButton = app.buttons[A11yIdentifiers.roomScreen.sendButton].firstMatch
|
var sendButton = app.buttons[A11yIdentifiers.roomScreen.sendButton].firstMatch
|
||||||
XCTAssertTrue(sendButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(sendButton.waitForExistence(timeout: 10.0))
|
||||||
sendButton.tap()
|
sendButton.tapCenter()
|
||||||
|
|
||||||
sleep(10) // Wait for the message to be sent
|
sleep(10) // Wait for the message to be sent
|
||||||
|
|
||||||
@@ -81,12 +81,12 @@ class UserFlowTests: XCTestCase {
|
|||||||
|
|
||||||
sendButton = app.buttons[A11yIdentifiers.roomScreen.sendButton].firstMatch
|
sendButton = app.buttons[A11yIdentifiers.roomScreen.sendButton].firstMatch
|
||||||
XCTAssertTrue(sendButton.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(sendButton.waitForExistence(timeout: 10.0))
|
||||||
sendButton.tap()
|
sendButton.tapCenter()
|
||||||
|
|
||||||
sleep(5) // Wait for the message to be sent
|
sleep(5) // Wait for the message to be sent
|
||||||
|
|
||||||
// Close the formatting options
|
// Close the formatting options
|
||||||
app.buttons[A11yIdentifiers.roomScreen.composerToolbar.closeFormattingOptions].tap()
|
app.buttons[A11yIdentifiers.roomScreen.composerToolbar.closeFormattingOptions].tapCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func checkPhotoSharing() {
|
private func checkPhotoSharing() {
|
||||||
@@ -98,7 +98,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
// Tap on the second image. First one is always broken on simulators.
|
// Tap on the second image. First one is always broken on simulators.
|
||||||
let secondImage = app.scrollViews.images.element(boundBy: 1)
|
let secondImage = app.scrollViews.images.element(boundBy: 1)
|
||||||
XCTAssertTrue(secondImage.waitForExistence(timeout: 20.0)) // Photo library takes a bit to load
|
XCTAssertTrue(secondImage.waitForExistence(timeout: 20.0)) // Photo library takes a bit to load
|
||||||
secondImage.tap()
|
secondImage.tapCenter()
|
||||||
|
|
||||||
// Wait for the image to be processed and the new screen to appear
|
// Wait for the image to be processed and the new screen to appear
|
||||||
sleep(10)
|
sleep(10)
|
||||||
@@ -129,7 +129,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
// Handle map loading errors (missing credentials)
|
// Handle map loading errors (missing credentials)
|
||||||
let alertOkButton = app.alerts.firstMatch.buttons["OK"].firstMatch
|
let alertOkButton = app.alerts.firstMatch.buttons["OK"].firstMatch
|
||||||
if alertOkButton.waitForExistence(timeout: 10.0) {
|
if alertOkButton.waitForExistence(timeout: 10.0) {
|
||||||
alertOkButton.tap()
|
alertOkButton.tapCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
allowLocationPermissionOnce()
|
allowLocationPermissionOnce()
|
||||||
@@ -141,7 +141,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
||||||
let notificationAlertAllowButton = springboard.buttons["Allow Once"].firstMatch
|
let notificationAlertAllowButton = springboard.buttons["Allow Once"].firstMatch
|
||||||
if notificationAlertAllowButton.waitForExistence(timeout: 10.0) {
|
if notificationAlertAllowButton.waitForExistence(timeout: 10.0) {
|
||||||
notificationAlertAllowButton.tap()
|
notificationAlertAllowButton.tapCenter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
// Open the room details
|
// Open the room details
|
||||||
let roomHeader = app.staticTexts[A11yIdentifiers.roomScreen.name]
|
let roomHeader = app.staticTexts[A11yIdentifiers.roomScreen.name]
|
||||||
XCTAssertTrue(roomHeader.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(roomHeader.waitForExistence(timeout: 10.0))
|
||||||
roomHeader.tap()
|
roomHeader.tapCenter()
|
||||||
|
|
||||||
// Open the room member details
|
// Open the room member details
|
||||||
tapOnButton(A11yIdentifiers.roomDetailsScreen.people)
|
tapOnButton(A11yIdentifiers.roomDetailsScreen.people)
|
||||||
@@ -186,7 +186,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
// Open the first member's details. Loading members for big rooms can take a while.
|
// Open the first member's details. Loading members for big rooms can take a while.
|
||||||
let firstRoomMember = app.scrollViews.buttons.firstMatch
|
let firstRoomMember = app.scrollViews.buttons.firstMatch
|
||||||
XCTAssertTrue(firstRoomMember.waitForExistence(timeout: 1000.0))
|
XCTAssertTrue(firstRoomMember.waitForExistence(timeout: 1000.0))
|
||||||
firstRoomMember.tap()
|
firstRoomMember.tapCenter()
|
||||||
|
|
||||||
// Go back to the room member details
|
// Go back to the room member details
|
||||||
tapOnBackButton("People")
|
tapOnBackButton("People")
|
||||||
@@ -206,7 +206,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
let profileButton = app.buttons[A11yIdentifiers.homeScreen.userAvatar]
|
let profileButton = app.buttons[A11yIdentifiers.homeScreen.userAvatar]
|
||||||
|
|
||||||
// `Failed to scroll to visible (by AX action) Button` https://stackoverflow.com/a/33534187/730924
|
// `Failed to scroll to visible (by AX action) Button` https://stackoverflow.com/a/33534187/730924
|
||||||
profileButton.forceTap()
|
profileButton.tapCenter()
|
||||||
|
|
||||||
// Open analytics
|
// Open analytics
|
||||||
tapOnButton(A11yIdentifiers.settingsScreen.analytics)
|
tapOnButton(A11yIdentifiers.settingsScreen.analytics)
|
||||||
@@ -233,7 +233,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
private func tapOnButton(_ identifier: String, waitForDisappearance: Bool = false) {
|
private func tapOnButton(_ identifier: String, waitForDisappearance: Bool = false) {
|
||||||
let button = app.buttons[identifier]
|
let button = app.buttons[identifier]
|
||||||
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
||||||
button.tap()
|
button.tapCenter()
|
||||||
|
|
||||||
if waitForDisappearance {
|
if waitForDisappearance {
|
||||||
let doesNotExistPredicate = NSPredicate(format: "exists == 0")
|
let doesNotExistPredicate = NSPredicate(format: "exists == 0")
|
||||||
@@ -245,7 +245,7 @@ class UserFlowTests: XCTestCase {
|
|||||||
private func tapOnMenu(_ identifier: String) {
|
private func tapOnMenu(_ identifier: String) {
|
||||||
let button = app.buttons[identifier]
|
let button = app.buttons[identifier]
|
||||||
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
||||||
button.forceTap()
|
button.tapCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Taps on a back button that the system configured with a label but no identifier.
|
/// Taps on a back button that the system configured with a label but no identifier.
|
||||||
@@ -255,6 +255,6 @@ class UserFlowTests: XCTestCase {
|
|||||||
private func tapOnBackButton(_ label: String = "Back") {
|
private func tapOnBackButton(_ label: String = "Back") {
|
||||||
let button = app.buttons.matching(NSPredicate(format: "label == %@ && identifier == ''", label)).firstMatch
|
let button = app.buttons.matching(NSPredicate(format: "label == %@ && identifier == ''", label)).firstMatch
|
||||||
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
XCTAssertTrue(button.waitForExistence(timeout: 10.0))
|
||||||
button.tap()
|
button.tapCenter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ enum NSELogger {
|
|||||||
}
|
}
|
||||||
isConfigured = true
|
isConfigured = true
|
||||||
|
|
||||||
MXLog.configure(target: "nse", logLevel: logLevel)
|
MXLog.configure(currentTarget: "nse", filePrefix: "nse", logLevel: logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func logMemory(with tag: String) {
|
static func logMemory(with tag: String) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class LoggingTests: XCTestCase {
|
|||||||
let target = "tests"
|
let target = "tests"
|
||||||
XCTAssertTrue(RustTracing.logFiles.isEmpty)
|
XCTAssertTrue(RustTracing.logFiles.isEmpty)
|
||||||
|
|
||||||
MXLog.configure(target: target, logLevel: .info)
|
MXLog.configure(currentTarget: target, filePrefix: target, logLevel: .info)
|
||||||
|
|
||||||
// There is something weird with Rust logging where the file writing handle doesn't
|
// There is something weird with Rust logging where the file writing handle doesn't
|
||||||
// notice that the file it is writing to was deleted, so we can't run these checks
|
// notice that the file it is writing to was deleted, so we can't run these checks
|
||||||
@@ -168,7 +168,7 @@ class LoggingTests: XCTestCase {
|
|||||||
content: .init(body: "FileString", source: nil, thumbnailSource: nil, contentType: nil))
|
content: .init(body: "FileString", source: nil, thumbnailSource: nil, contentType: nil))
|
||||||
|
|
||||||
// When logging that value
|
// When logging that value
|
||||||
MXLog.configure(logLevel: .info)
|
MXLog.configure(currentTarget: "tests", filePrefix: nil, logLevel: .info)
|
||||||
|
|
||||||
MXLog.info(textMessage)
|
MXLog.info(textMessage)
|
||||||
MXLog.info(noticeMessage)
|
MXLog.info(noticeMessage)
|
||||||
|
|||||||
@@ -10,14 +10,35 @@ import XCTest
|
|||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
|
||||||
class TracingConfigurationTests: XCTestCase {
|
class TracingConfigurationTests: XCTestCase {
|
||||||
func testConfiguration() {
|
func testConfiguration() { // swiftlint:disable line_length
|
||||||
let configuration = TracingConfiguration(logLevel: .trace, target: nil)
|
var filter = TracingConfiguration(logLevel: .error, currentTarget: "tests", filePrefix: nil).filter
|
||||||
|
|
||||||
let filterComponents = configuration.filter.components(separatedBy: ",")
|
XCTAssertEqual(filter, "hyper=warn,matrix_sdk_ffi=info,matrix_sdk::client=trace,matrix_sdk_crypto=debug,matrix_sdk_crypto::olm::account=trace,matrix_sdk::oidc=trace,matrix_sdk::http_client=debug,matrix_sdk::sliding_sync=info,matrix_sdk_base::sliding_sync=info,matrix_sdk_ui::timeline=info,tests=error")
|
||||||
XCTAssertEqual(filterComponents.first, "info")
|
|
||||||
XCTAssertTrue(filterComponents.contains("matrix_sdk_base::sliding_sync=trace"))
|
filter = TracingConfiguration(logLevel: .warn, currentTarget: "tests", filePrefix: nil).filter
|
||||||
XCTAssertTrue(filterComponents.contains("matrix_sdk::http_client=debug"))
|
|
||||||
XCTAssertTrue(filterComponents.contains("matrix_sdk_crypto=debug"))
|
XCTAssertEqual(filter, "hyper=warn,matrix_sdk_ffi=info,matrix_sdk::client=trace,matrix_sdk_crypto=debug,matrix_sdk_crypto::olm::account=trace,matrix_sdk::oidc=trace,matrix_sdk::http_client=debug,matrix_sdk::sliding_sync=info,matrix_sdk_base::sliding_sync=info,matrix_sdk_ui::timeline=info,tests=warn")
|
||||||
XCTAssertTrue(filterComponents.contains("hyper=warn"))
|
|
||||||
|
filter = TracingConfiguration(logLevel: .info, currentTarget: "tests", filePrefix: nil).filter
|
||||||
|
|
||||||
|
XCTAssertEqual(filter, "hyper=warn,matrix_sdk_ffi=info,matrix_sdk::client=trace,matrix_sdk_crypto=debug,matrix_sdk_crypto::olm::account=trace,matrix_sdk::oidc=trace,matrix_sdk::http_client=debug,matrix_sdk::sliding_sync=info,matrix_sdk_base::sliding_sync=info,matrix_sdk_ui::timeline=info,tests=info")
|
||||||
|
|
||||||
|
filter = TracingConfiguration(logLevel: .debug, currentTarget: "tests", filePrefix: nil).filter
|
||||||
|
|
||||||
|
XCTAssertEqual(filter, "hyper=warn,matrix_sdk_ffi=debug,matrix_sdk::client=trace,matrix_sdk_crypto=debug,matrix_sdk_crypto::olm::account=trace,matrix_sdk::oidc=trace,matrix_sdk::http_client=debug,matrix_sdk::sliding_sync=debug,matrix_sdk_base::sliding_sync=debug,matrix_sdk_ui::timeline=debug,tests=debug")
|
||||||
|
|
||||||
|
filter = TracingConfiguration(logLevel: .trace, currentTarget: "tests", filePrefix: nil).filter
|
||||||
|
|
||||||
|
XCTAssertEqual(filter, "hyper=warn,matrix_sdk_ffi=trace,matrix_sdk::client=trace,matrix_sdk_crypto=trace,matrix_sdk_crypto::olm::account=trace,matrix_sdk::oidc=trace,matrix_sdk::http_client=trace,matrix_sdk::sliding_sync=trace,matrix_sdk_base::sliding_sync=trace,matrix_sdk_ui::timeline=trace,tests=trace")
|
||||||
|
} // swiftlint:enable line_length
|
||||||
|
|
||||||
|
func testLevelOrdering() {
|
||||||
|
var logLevels: [TracingConfiguration.LogLevel] = [.info, .error, .trace, .debug, .warn]
|
||||||
|
|
||||||
|
XCTAssertEqual(logLevels.sorted(), [.error, .warn, .info, .debug, .trace])
|
||||||
|
|
||||||
|
logLevels = [.warn, .error, .debug, .trace, .info, .error]
|
||||||
|
|
||||||
|
XCTAssertEqual(logLevels.sorted(), [.error, .error, .warn, .info, .debug, .trace])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -80,12 +80,15 @@ lane :alpha do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :unit_tests do |options|
|
lane :unit_tests do |options|
|
||||||
|
reset_simulator = ENV.key?('CI')
|
||||||
|
|
||||||
run_tests(
|
run_tests(
|
||||||
scheme: "UnitTests",
|
scheme: "UnitTests",
|
||||||
destination: "platform=iOS Simulator,name=iPhone 16,OS=18.0",
|
destination: "platform=iOS Simulator,name=iPhone 16,OS=18.0",
|
||||||
ensure_devices_found: true,
|
ensure_devices_found: true,
|
||||||
result_bundle: true,
|
result_bundle: true,
|
||||||
number_of_retries: 3,
|
number_of_retries: 3,
|
||||||
|
reset_simulator: reset_simulator
|
||||||
)
|
)
|
||||||
|
|
||||||
if !options[:skip_previews]
|
if !options[:skip_previews]
|
||||||
@@ -95,6 +98,7 @@ lane :unit_tests do |options|
|
|||||||
ensure_devices_found: true,
|
ensure_devices_found: true,
|
||||||
result_bundle: true,
|
result_bundle: true,
|
||||||
number_of_retries: 3,
|
number_of_retries: 3,
|
||||||
|
reset_simulator: reset_simulator
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,10 +106,6 @@ lane :unit_tests do |options|
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :ui_tests do |options|
|
lane :ui_tests do |options|
|
||||||
# Use a fresh simulator state to ensure hardware keyboard isn't attached.
|
|
||||||
# Not necessary when running on GitHub.
|
|
||||||
# reset_simulator_contents()
|
|
||||||
|
|
||||||
create_simulator_if_necessary(
|
create_simulator_if_necessary(
|
||||||
name: "iPhone 16",
|
name: "iPhone 16",
|
||||||
type: "com.apple.CoreSimulator.SimDeviceType.iPhone-16",
|
type: "com.apple.CoreSimulator.SimDeviceType.iPhone-16",
|
||||||
@@ -124,6 +124,8 @@ lane :ui_tests do |options|
|
|||||||
test_to_run = nil
|
test_to_run = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reset_simulator = ENV.key?('CI')
|
||||||
|
|
||||||
run_tests(
|
run_tests(
|
||||||
scheme: "UITests",
|
scheme: "UITests",
|
||||||
devices: ["iPhone 16", "iPad (10th generation)"],
|
devices: ["iPhone 16", "iPad (10th generation)"],
|
||||||
@@ -132,6 +134,7 @@ lane :ui_tests do |options|
|
|||||||
result_bundle: true,
|
result_bundle: true,
|
||||||
only_testing: test_to_run,
|
only_testing: test_to_run,
|
||||||
number_of_retries: 3,
|
number_of_retries: 3,
|
||||||
|
reset_simulator: reset_simulator
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -145,12 +148,14 @@ lane :integration_tests do
|
|||||||
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
runtime: "com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reset_simulator = ENV.key?('CI')
|
||||||
|
|
||||||
run_tests(
|
run_tests(
|
||||||
scheme: "IntegrationTests",
|
scheme: "IntegrationTests",
|
||||||
destination: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0",
|
destination: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.0",
|
||||||
ensure_devices_found: true,
|
ensure_devices_found: true,
|
||||||
result_bundle: true,
|
result_bundle: true,
|
||||||
reset_simulator: true
|
reset_simulator: reset_simulator
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user