Screenshot tests (#130)

* #9 Add snapshot testing library

* #9 Create script to boot test simulators

* #9 Create the UI test plan

* #9 Create shared schemes for test targets

* #9 Disable split view for UI tests

* #9 Fix fastlane dependencies

* #9 Add snapshot testing to the application

* #9 assert screenshots

* #9 fix swipe gestures on iPad

* #9 Fix accessing items in session verification screen

* #9 Workaround for flaky unit test

* #9 Specify scheme for alpha build

* #9 Add reference screenshots

* Update python script path and check assets for png check

* Update script path

* Use static timezone for simulator time

* Fix build after SwiftFormat

* Add changelog

* Upload failed screenshots artifact

* Always upload artifacts

* Update boot simulator script

* Update simulator overridden time

* Install pytz before tests

* Get time from Ruby script

* Disable SwiftUI animation when running UI tests

* Update screenshots after animation setting

* Include reference images in the artifact

* Update matching precision

* Update image matching precision & revert artifact content

* Include Xcode result in the artifact

* Update test output directory

* Disable gradient on splash screen for tests

* Tap next button explicitly

* Wait a bit before checking alert

* Wait 1 second

* Run SwiftFormat on project

* Ignore temporary screenshots

* Fix most of the PR remarks

* Fix conflicts

* Bump Python version to 3

* Update reference screenshots for authentication screens

* Update SwiftFormat

* Fix flakey session verification test.

* Update scheme.

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
ismailgulek
2022-08-11 15:02:47 +03:00
committed by GitHub
parent 207cbdebfd
commit 2cb6dc1cd9
127 changed files with 944 additions and 83 deletions
@@ -51,7 +51,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
func displayError(_ type: ServerSelectionErrorType) {
switch type {
case .footerMessage(let message):
withAnimation {
withElementAnimation {
state.footerErrorMessage = message
}
}
@@ -62,6 +62,6 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
/// Clear any errors shown in the text field footer.
private func clearFooterError() {
guard state.footerErrorMessage != nil else { return }
withAnimation { state.footerErrorMessage = nil }
withElementAnimation { state.footerErrorMessage = nil }
}
}
@@ -64,7 +64,7 @@ struct HomeScreen: View {
Spacer()
}
.transition(.slide)
.animation(.default, value: context.viewState.showSessionVerificationBanner)
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
.ignoresSafeArea(.all, edges: .bottom)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -72,11 +72,11 @@ struct HomeScreen: View {
Button { context.send(viewAction: .tapUserAvatar) } label: {
HStack {
userAvatarImage
.animation(.default, value: context.viewState.userAvatar)
.animation(.elementDefault, value: context.viewState.userAvatar)
.transition(.opacity)
userDisplayNameView
.animation(.default, value: context.viewState.userDisplayName)
.animation(.elementDefault, value: context.viewState.userDisplayName)
.transition(.opacity)
}
}
@@ -147,7 +147,7 @@ struct RoomCell: View {
}
}
}
.animation(.default, value: room)
.animation(.elementDefault, value: room)
.frame(minHeight: 60.0)
.task {
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
@@ -36,7 +36,7 @@ struct MessageComposer: View {
.padding(.bottom, 6.0)
.disabled(disabled)
.opacity(disabled ? 0.5 : 1.0)
.animation(.default, value: disabled)
.animation(.elementDefault, value: disabled)
.keyboardShortcut(.return, modifiers: [.command])
}
}
@@ -57,7 +57,7 @@ struct MessageComposerTextField: View {
.background(placeholderView, alignment: .topLeading)
.clipShape(rect)
.overlay(rect.stroke(borderColor, lineWidth: borderWidth))
.animation(.default, value: isEditing)
.animation(.elementDefault, value: isEditing)
}
@ViewBuilder
@@ -42,7 +42,7 @@ struct ImageRoomTimelineView: View {
}
}
.id(timelineItem.id)
.animation(.default, value: timelineItem.image)
.animation(.elementDefault, value: timelineItem.image)
.frame(maxHeight: 1000.0)
} else {
TimelineStyler(timelineItem: timelineItem) {
@@ -37,7 +37,7 @@ struct TimelineItemList: View {
Spacer()
ProgressView()
.opacity(context.viewState.isBackPaginating ? 1.0 : 0.0)
.animation(.default, value: context.viewState.isBackPaginating)
.animation(.elementDefault, value: context.viewState.isBackPaginating)
Spacer()
}
.listRowBackground(Color.clear)
@@ -40,6 +40,6 @@ struct TimelineSenderAvatarView: View {
.stroke(Color.element.background, lineWidth: 2)
)
.animation(.default, value: timelineItem.senderAvatar)
.animation(.elementDefault, value: timelineItem.senderAvatar)
}
}
@@ -47,7 +47,7 @@ struct TimelineView: View {
scollToBottomButtonVisible = !visible
})
.opacity(scollToBottomButtonVisible ? 1.0 : 0.0)
.animation(.default, value: scollToBottomButtonVisible)
.animation(.elementDefault, value: scollToBottomButtonVisible)
}
}
@@ -50,6 +50,11 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
switch callback {
case .receivedVerificationData(let emojis):
guard self.stateMachine.state == .requestingVerification else {
MXLog.warning("[SessionVerificationViewModel] callbacks: Ignoring receivedVerificationData due to invalid state.")
return
}
self.stateMachine.processEvent(.didReceiveChallenge(emojis: emojis))
case .finished:
self.stateMachine.processEvent(.didAcceptChallenge)
@@ -53,6 +53,9 @@ struct SplashScreenViewState: BindableState {
/// The background gradient for all 4 pages and the hidden page at the start of the carousel.
var backgroundGradient: Gradient {
if Tests.isRunningUITests {
return Gradient(colors: [.white])
}
// Include the extra stop for the hidden page at the start of the carousel.
// (The last color is the right-hand stop, but we need the left-hand stop,
// so take the last but one color from the array).
@@ -123,11 +123,11 @@ struct SplashScreen: View {
if context.pageIndex == pageCount - 1 {
showHiddenPage()
withAnimation(.easeInOut(duration: 0.7)) {
withElementAnimation(.easeInOut(duration: 0.7)) {
showNextPage()
}
} else {
withAnimation(.easeInOut(duration: 0.7)) {
withElementAnimation(.easeInOut(duration: 0.7)) {
showNextPage()
}
}
@@ -185,7 +185,7 @@ struct SplashScreen: View {
stopTimer()
// Animate the change over a few frames to smooth out any stuttering.
withAnimation(.linear(duration: 0.05)) {
withElementAnimation(.linear(duration: 0.05)) {
dragOffset = isLeftToRight ? drag.translation.width : -drag.translation.width
}
}
@@ -195,11 +195,11 @@ struct SplashScreen: View {
private func handleDragGestureEnded(_ drag: DragGesture.Value, viewSize: CGSize) {
guard shouldSwipeForTranslation(drag.predictedEndTranslation.width) else {
// Reset the offset just in case.
withAnimation { dragOffset = 0 }
withElementAnimation { dragOffset = 0 }
return
}
withAnimation(.easeInOut(duration: 0.2)) {
withElementAnimation(.easeInOut(duration: 0.2)) {
if drag.predictedEndTranslation.width < -viewSize.width / 2 {
showNextPage()
} else if drag.predictedEndTranslation.width > viewSize.width / 2 {