Various UI test fixes (#370)

* Increase integration tests time limits again as they're still ocasionally failing

* Fixed NavigationRootCoordinator name in logs

* Refactor UI tests hierarchy and introduce new userFlowScreen

* Introduce a RoomTimelineControllerFactory so that it can be mocked in the UserFlow UI tests

* Start using a mock timeline controller for the UserSession flows

* Remove the WeakDictionary dependency and replce it with a plain NSMapTable in the BackgroundTaskService

* Allow multiple UITests screenshots per screen

* Prevent the view hierarchy changing when taking screenshots

* Add UserSessionScreen UI tests

* Fix label triaging workflow project identifier as per vector-im/element-ios/pull/7150

* Fix settings screen tests

* Fix roomPlainNoAvatar and roomEncryptedWithAvatar UI tests

* Fix modal server selection screen UI tests

* Fix bug report and login screen UI tests

* Fix text typing missing characters on UI tests

* Fix sliding sync configuration on integration tests

* Stop crashing if not finding a particular room through the MockClientProxy
This commit is contained in:
Stefan Ceriu
2022-12-15 15:22:39 +02:00
committed by GitHub
parent cfd563786d
commit 5b90f37f2e
66 changed files with 454 additions and 583 deletions
@@ -36,6 +36,7 @@ enum UITestScreenIdentifier: String {
case roomSmallTimelineIncomingAndSmallPagination
case roomSmallTimelineLargePagination
case sessionVerification
case userSessionScreen
}
extension UITestScreenIdentifier: CustomStringConvertible {
@@ -18,58 +18,64 @@ import SwiftUI
import UIKit
class UITestsAppCoordinator: AppCoordinatorProtocol {
private var currentRootCoordinator: CoordinatorProtocol?
private let navigationStackCoordinator: NavigationStackCoordinator
let notificationManager: NotificationManagerProtocol? = nil
private let navigationRootCoordinator: NavigationRootCoordinator
private var mockScreens: [MockScreen] = []
var notificationManager: NotificationManagerProtocol?
init() {
UIView.setAnimationsEnabled(false)
navigationStackCoordinator = NavigationStackCoordinator()
navigationRootCoordinator = NavigationRootCoordinator()
mockScreens = UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationRootCoordinator: navigationRootCoordinator) }
ServiceLocator.shared.register(userNotificationController: MockUserNotificationController())
}
func start() {
let screens = mockScreens()
let rootCoordinator = UITestsRootCoordinator(mockScreens: screens) { id in
guard let screen = screens.first(where: { $0.id == id }) else {
let rootCoordinator = UITestsRootCoordinator(mockScreens: mockScreens) { id in
guard let screen = self.mockScreens.first(where: { $0.id == id }) else {
fatalError()
}
// Store the initial coordinator so that it stays alive if drops it
// For example when replacing the root in the authentication flows
self.currentRootCoordinator = screen.coordinator
self.navigationStackCoordinator.setRootCoordinator(screen.coordinator)
self.navigationRootCoordinator.setRootCoordinator(screen.coordinator)
}
navigationStackCoordinator.setRootCoordinator(rootCoordinator)
navigationRootCoordinator.setRootCoordinator(rootCoordinator)
Bundle.elementFallbackLanguage = "en"
}
func toPresentable() -> AnyView {
navigationStackCoordinator.toPresentable()
}
private func mockScreens() -> [MockScreen] {
UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationStackCoordinator: navigationStackCoordinator) }
func toPresentable() -> AnyView {
navigationRootCoordinator.toPresentable()
}
}
@MainActor
class MockScreen: Identifiable {
let id: UITestScreenIdentifier
let navigationStackCoordinator: NavigationStackCoordinator
private let navigationRootCoordinator: NavigationRootCoordinator
private var retainedState = [Any]()
init(id: UITestScreenIdentifier, navigationRootCoordinator: NavigationRootCoordinator) {
self.id = id
self.navigationRootCoordinator = navigationRootCoordinator
}
lazy var coordinator: CoordinatorProtocol = {
switch id {
case .login:
return LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator))
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverSelection:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userNotificationController: MockUserNotificationController(),
isModallyPresented: true))
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userNotificationController: MockUserNotificationController(),
isModallyPresented: true))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverSelectionNonModal:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userNotificationController: MockUserNotificationController(),
@@ -78,8 +84,12 @@ class MockScreen: Identifiable {
return AnalyticsPromptCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
mediaProvider: MockMediaProvider())))
case .authenticationFlow:
return AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator)
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator)
retainedState.append(coordinator)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .softLogout:
let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org",
@@ -93,47 +103,66 @@ class MockScreen: Identifiable {
case .simpleUpgrade:
return TemplateCoordinator(parameters: .init(promptType: .upgrade))
case .home:
let navigationStackCoordinator = NavigationStackCoordinator()
let session = MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:matrix.org"),
mediaProvider: MockMediaProvider())
return HomeScreenCoordinator(parameters: .init(userSession: session,
attributedStringBuilder: AttributedStringBuilder(),
bugReportService: MockBugReportService(),
navigationStackCoordinator: navigationStackCoordinator))
let coordinator = HomeScreenCoordinator(parameters: .init(userSession: session,
attributedStringBuilder: AttributedStringBuilder(),
bugReportService: MockBugReportService(),
navigationStackCoordinator: navigationStackCoordinator))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .settings:
return SettingsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
userNotificationController: MockUserNotificationController(),
userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
mediaProvider: MockMediaProvider()),
bugReportService: MockBugReportService()))
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = SettingsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
userNotificationController: MockUserNotificationController(),
userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
mediaProvider: MockMediaProvider()),
bugReportService: MockBugReportService()))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .bugReport:
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
userNotificationController: MockUserNotificationController(),
screenshot: nil,
isModallyPresented: false))
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
userNotificationController: MockUserNotificationController(),
screenshot: nil,
isModallyPresented: true))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .bugReportWithScreenshot:
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
userNotificationController: MockUserNotificationController(),
screenshot: Asset.Images.appLogo.image,
isModallyPresented: false))
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
userNotificationController: MockUserNotificationController(),
screenshot: Asset.Images.appLogo.image,
isModallyPresented: false))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .onboarding:
return OnboardingCoordinator()
case .roomPlainNoAvatar:
let navigationStackCoordinator = NavigationStackCoordinator()
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(),
roomName: "Some room name",
roomAvatarUrl: nil,
emojiProvider: EmojiProvider())
return RoomScreenCoordinator(parameters: parameters)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomEncryptedWithAvatar:
let navigationStackCoordinator = NavigationStackCoordinator()
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(),
roomName: "Some room name",
roomAvatarUrl: "mock_url",
emojiProvider: EmojiProvider())
return RoomScreenCoordinator(parameters: parameters)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimeline:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
@@ -142,8 +171,12 @@ class MockScreen: Identifiable {
roomName: "New room",
roomAvatarUrl: "mock_url",
emojiProvider: EmojiProvider())
return RoomScreenCoordinator(parameters: parameters)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineIncomingAndSmallPagination:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk]
@@ -155,8 +188,13 @@ class MockScreen: Identifiable {
roomName: "Small timeline",
roomAvatarUrl: "mock_url",
emojiProvider: EmojiProvider())
return RoomScreenCoordinator(parameters: parameters)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineLargePagination:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
@@ -166,15 +204,28 @@ class MockScreen: Identifiable {
roomName: "Small timeline, paginating",
roomAvatarUrl: "mock_url",
emojiProvider: EmojiProvider())
return RoomScreenCoordinator(parameters: parameters)
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .sessionVerification:
let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy())
return SessionVerificationCoordinator(parameters: parameters)
case .userSessionScreen:
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: SplashScreenCoordinator())
let clientProxy = MockClientProxy(userIdentifier: "@mock:client.com", roomSummaryProvider: MockRoomSummaryProvider(state: .loaded))
let coordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()),
navigationSplitCoordinator: navigationSplitCoordinator,
bugReportService: MockBugReportService(),
roomTimelineControllerFactory: MockRoomTimelineControllerFactory())
coordinator.start()
retainedState.append(coordinator)
return navigationSplitCoordinator
}
}()
init(id: UITestScreenIdentifier, navigationStackCoordinator: NavigationStackCoordinator) {
self.id = id
self.navigationStackCoordinator = navigationStackCoordinator
}
}
@@ -31,8 +31,7 @@ struct UITestsRootCoordinator: CoordinatorProtocol {
}
.accessibilityIdentifier(coordinator.id.rawValue)
}
.padding(.top, 50) // Add some top padding so the iPad split screen button isn't tapped by mistake
.listStyle(.plain)
.navigationTitle("Screens")
.navigationViewStyle(.stack)
}
}