diff --git a/.github/workflows/accessibility_tests.yml b/.github/workflows/accessibility_tests.yml new file mode 100644 index 000000000..05a6bc595 --- /dev/null +++ b/.github/workflows/accessibility_tests.yml @@ -0,0 +1,47 @@ +name: Accessibility Tests + +on: + workflow_dispatch: + + schedule: + - cron: '0 2 * * 1-5' + +jobs: + tests: + name: Tests + runs-on: macos-15 + + concurrency: + # Only allow a single run of this workflow on each branch, automatically cancelling older runs. + group: ${{ format('accessibility-tests-{0}', github.ref) }} + cancel-in-progress: true + + steps: + - uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 #v1.2.3 + + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Setup environment + run: source ci_scripts/ci_common.sh && setup_github_actions_environment + + - name: Run tests + run: bundle exec fastlane accessibility_tests + + - name: Zip results # for faster upload + if: failure() + working-directory: fastlane/test_output + run: zip -r AccessibilityTests.xcresult.zip AccessibilityTests.xcresult + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: Results + path: fastlane/test_output/AccessibilityTests.xcresult.zip + retention-days: 7 + if-no-files-found: ignore diff --git a/AccessibilityTests/Sources/AccessibilityTests.swift b/AccessibilityTests/Sources/AccessibilityTests.swift new file mode 100644 index 000000000..4a8182ef7 --- /dev/null +++ b/AccessibilityTests/Sources/AccessibilityTests.swift @@ -0,0 +1,119 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import XCTest + +@MainActor +final class AccessibilityTests: XCTestCase { + var app: XCUIApplication! + + func performAccessibilityAudit(named name: String) async throws { + let client = try UITestsSignalling.Client(mode: .tests) + app = Application.launch(viewID: name) + await client.waitForApp() + defer { try? client.stop() } + + // To handle system interrupts + _ = addUIInterruptionMonitor(withDescription: "Location access alert handler") { alert in + let alwaysAllowButton = alert.buttons["Allow While Using App"] + if alwaysAllowButton.exists { + alwaysAllowButton.tap() + return true + } + return false + } + // This interaction is needed to have the UIInterruptionMonitor work properly. + app.tap() + + try client.send(.accessibilityAudit(.nextPreview)) + forLoop: for await signal in client.signals.values { + switch signal { + case .accessibilityAudit(let auditSignal): + switch auditSignal { + case .nextPreviewReady(let name): + performAccessibilityAuditForPreview(named: name) + try client.send(.accessibilityAudit(.nextPreview)) + case .noMorePreviews: + break forLoop + default: + XCTFail("Unhandled signal") + } + default: + XCTFail("Unhandled signal") + } + } + + app.terminate() + } + + private func performAccessibilityAuditForPreview(named name: String) { + // Alows us to log the name of the preview that is being tested + XCTContext.runActivity(named: name) { _ in + do { + // We have removed `textClipped` and `contrast` for now + try app.performAccessibilityAudit(for: [.dynamicType, .elementDetection, .hitRegion, .sufficientElementDescription, .trait]) { issue in + // Remove false positives for null elements + guard let element = issue.element else { + return true + } + + // We are fine with elements that only partially support dynamic types + guard issue.compactDescription != Self.partiallyUnsupportedDynamicTypeMessage else { + return true + } + + // We can filter out matrix entities from the non human-readable error + if issue.compactDescription == Self.notHumanReadableMessage, Self.isMatrixIdentifier(element.label) { + return true + } + + // Additional filters for specific elements that lead to false positives or neglectable issues. + if Self.ignoredA11yIdentifiers[element.identifier]?.isAccessibilityIssueFiltered(issue) == true { + return true + } + + return false + } + } catch { + XCTFail("Failed to perform the accessibility audit: \(error)") + } + } + } + + private static func isMatrixIdentifier(_ string: String) -> Bool { + MatrixEntityRegex.isMatrixRoomAlias(string) || MatrixEntityRegex.isMatrixUserIdentifier(string) || string == PillUtilities.atRoom + } + + private static let partiallyUnsupportedDynamicTypeMessage = "Dynamic Type font sizes are partially unsupported" + private static let notHumanReadableMessage = "Label not human-readable" + + /// Use this array to filter add specific filters to ignore specific issues for certain elements + private static let ignoredA11yIdentifiers: [String: [FilterType]] = [A11yIdentifiers.authenticationStartScreen.appVersion: [.auditType(.hitRegion)]] +} + +private enum FilterType { + /// Filter by the content of the compactDescription of the issue + case compactDescription(String) + /// Filter by the type of the issue + case auditType(XCUIAccessibilityAuditType) +} + +private extension Array where Element == FilterType { + func isAccessibilityIssueFiltered(_ issue: XCUIAccessibilityAuditIssue) -> Bool { + for filter in self { + switch filter { + case .auditType(issue.auditType): + return true + case .compactDescription(issue.compactDescription): + return true + default: + break + } + } + return false + } +} diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift new file mode 100644 index 000000000..a99fa66dc --- /dev/null +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -0,0 +1,768 @@ +// Generated using Sourcery 2.2.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + +// swiftlint:disable all +// swiftformat:disable all + + +extension AccessibilityTests { + + func testAdvancedSettingsScreen() async throws { + try await performAccessibilityAudit(named: "AdvancedSettingsScreen_Previews") + } + + func testAnalyticsPromptScreen() async throws { + try await performAccessibilityAudit(named: "AnalyticsPromptScreen_Previews") + } + + func testAnalyticsSettingsScreen() async throws { + try await performAccessibilityAudit(named: "AnalyticsSettingsScreen_Previews") + } + + func testAppLockScreen() async throws { + try await performAccessibilityAudit(named: "AppLockScreen_Previews") + } + + func testAppLockSetupBiometricsScreen() async throws { + try await performAccessibilityAudit(named: "AppLockSetupBiometricsScreen_Previews") + } + + func testAppLockSetupPINScreen() async throws { + try await performAccessibilityAudit(named: "AppLockSetupPINScreen_Previews") + } + + func testAppLockSetupSettingsScreen() async throws { + try await performAccessibilityAudit(named: "AppLockSetupSettingsScreen_Previews") + } + + func testAudioMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "AudioMediaEventsTimelineView_Previews") + } + + func testAudioRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "AudioRoomTimelineView_Previews") + } + + func testAuthenticationStartScreen() async throws { + try await performAccessibilityAudit(named: "AuthenticationStartScreen_Previews") + } + + func testAvatarHeaderView() async throws { + try await performAccessibilityAudit(named: "AvatarHeaderView_Previews") + } + + func testBadgeLabel() async throws { + try await performAccessibilityAudit(named: "BadgeLabel_Previews") + } + + func testBigIcon() async throws { + try await performAccessibilityAudit(named: "BigIcon_Previews") + } + + func testBlockedUsersScreen() async throws { + try await performAccessibilityAudit(named: "BlockedUsersScreen_Previews") + } + + func testBugReportScreen() async throws { + try await performAccessibilityAudit(named: "BugReportScreen_Previews") + } + + func testCallInviteRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "CallInviteRoomTimelineView_Previews") + } + + func testCallNotificationRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "CallNotificationRoomTimelineView_Previews") + } + + func testCollapsibleRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "CollapsibleRoomTimelineView_Previews") + } + + func testCompletionSuggestion() async throws { + try await performAccessibilityAudit(named: "CompletionSuggestion_Previews") + } + + func testComposerToolbar() async throws { + try await performAccessibilityAudit(named: "ComposerToolbar_Previews") + } + + func testCreateRoom() async throws { + try await performAccessibilityAudit(named: "CreateRoom_Previews") + } + + func testDeactivateAccountScreen() async throws { + try await performAccessibilityAudit(named: "DeactivateAccountScreen_Previews") + } + + func testDeclineAndBlockScreen() async throws { + try await performAccessibilityAudit(named: "DeclineAndBlockScreen_Previews") + } + + func testEditRoomAddressScreen() async throws { + try await performAccessibilityAudit(named: "EditRoomAddressScreen_Previews") + } + + func testElementTextFieldStyle() async throws { + try await performAccessibilityAudit(named: "ElementTextFieldStyle_Previews") + } + + func testEmojiPickerScreenHeaderView() async throws { + try await performAccessibilityAudit(named: "EmojiPickerScreenHeaderView_Previews") + } + + func testEmojiPickerScreen() async throws { + try await performAccessibilityAudit(named: "EmojiPickerScreen_Previews") + } + + func testEmoteRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "EmoteRoomTimelineView_Previews") + } + + func testEncryptedRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "EncryptedRoomTimelineView_Previews") + } + + func testEncryptionResetPasswordScreen() async throws { + try await performAccessibilityAudit(named: "EncryptionResetPasswordScreen_Previews") + } + + func testEncryptionResetScreen() async throws { + try await performAccessibilityAudit(named: "EncryptionResetScreen_Previews") + } + + func testEstimatedWaveformView() async throws { + try await performAccessibilityAudit(named: "EstimatedWaveformView_Previews") + } + + func testFileMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "FileMediaEventsTimelineView_Previews") + } + + func testFileRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "FileRoomTimelineView_Previews") + } + + func testFormButtonStyles() async throws { + try await performAccessibilityAudit(named: "FormButtonStyles_Previews") + } + + func testFormattedBodyText() async throws { + try await performAccessibilityAudit(named: "FormattedBodyText_Previews") + } + + func testFormattingToolbar() async throws { + try await performAccessibilityAudit(named: "FormattingToolbar_Previews") + } + + func testFullscreenDialog() async throws { + try await performAccessibilityAudit(named: "FullscreenDialog_Previews") + } + + func testGlobalSearchScreenListRow() async throws { + try await performAccessibilityAudit(named: "GlobalSearchScreenListRow_Previews") + } + + func testGlobalSearchScreen() async throws { + try await performAccessibilityAudit(named: "GlobalSearchScreen_Previews") + } + + func testHighlightedTimelineItemModifier() async throws { + try await performAccessibilityAudit(named: "HighlightedTimelineItemModifier_Previews") + } + + func testHomeScreenEmptyStateView() async throws { + try await performAccessibilityAudit(named: "HomeScreenEmptyStateView_Previews") + } + + func testHomeScreenInviteCell() async throws { + try await performAccessibilityAudit(named: "HomeScreenInviteCell_Previews") + } + + func testHomeScreenKnockedCell() async throws { + try await performAccessibilityAudit(named: "HomeScreenKnockedCell_Previews") + } + + func testHomeScreenRecoveryKeyConfirmationBanner() async throws { + try await performAccessibilityAudit(named: "HomeScreenRecoveryKeyConfirmationBanner_Previews") + } + + func testHomeScreenRoomCell() async throws { + try await performAccessibilityAudit(named: "HomeScreenRoomCell_Previews") + } + + func testHomeScreen() async throws { + try await performAccessibilityAudit(named: "HomeScreen_Previews") + } + + func testIdentityConfirmationScreen() async throws { + try await performAccessibilityAudit(named: "IdentityConfirmationScreen_Previews") + } + + func testIdentityConfirmedScreen() async throws { + try await performAccessibilityAudit(named: "IdentityConfirmedScreen_Previews") + } + + func testImageMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "ImageMediaEventsTimelineView_Previews") + } + + func testImageRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "ImageRoomTimelineView_Previews") + } + + func testInviteUsersScreenSelectedItem() async throws { + try await performAccessibilityAudit(named: "InviteUsersScreenSelectedItem_Previews") + } + + func testInviteUsersScreen() async throws { + try await performAccessibilityAudit(named: "InviteUsersScreen_Previews") + } + + func testJoinRoomByAddressView() async throws { + try await performAccessibilityAudit(named: "JoinRoomByAddressView_Previews") + } + + func testJoinRoomScreen() async throws { + try await performAccessibilityAudit(named: "JoinRoomScreen_Previews") + } + + func testKnockRequestCell() async throws { + try await performAccessibilityAudit(named: "KnockRequestCell_Previews") + } + + func testKnockRequestsBannerView() async throws { + try await performAccessibilityAudit(named: "KnockRequestsBannerView_Previews") + } + + func testKnockRequestsListEmptyStateView() async throws { + try await performAccessibilityAudit(named: "KnockRequestsListEmptyStateView_Previews") + } + + func testKnockRequestsListScreen() async throws { + try await performAccessibilityAudit(named: "KnockRequestsListScreen_Previews") + } + + func testLegalInformationScreen() async throws { + try await performAccessibilityAudit(named: "LegalInformationScreen_Previews") + } + + func testLoadableImage() async throws { + try await performAccessibilityAudit(named: "LoadableImage_Previews") + } + + func testLocationMarkerView() async throws { + try await performAccessibilityAudit(named: "LocationMarkerView_Previews") + } + + func testLocationRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "LocationRoomTimelineView_Previews") + } + + func testLoginScreen() async throws { + try await performAccessibilityAudit(named: "LoginScreen_Previews") + } + + func testLongPressWithFeedback() async throws { + try await performAccessibilityAudit(named: "LongPressWithFeedback_Previews") + } + + func testManageRoomMemberSheetView() async throws { + try await performAccessibilityAudit(named: "ManageRoomMemberSheetView_Previews") + } + + func testMapLibreStaticMapView() async throws { + try await performAccessibilityAudit(named: "MapLibreStaticMapView_Previews") + } + + func testMatrixUserPermalink() async throws { + try await performAccessibilityAudit(named: "MatrixUserPermalink_Previews") + } + + func testMediaEventsTimelineScreen() async throws { + try await performAccessibilityAudit(named: "MediaEventsTimelineScreen_Previews") + } + + func testMediaUploadPreviewScreen() async throws { + try await performAccessibilityAudit(named: "MediaUploadPreviewScreen_Previews") + } + + func testMentionSuggestionItemView() async throws { + try await performAccessibilityAudit(named: "MentionSuggestionItemView_Previews") + } + + func testMessageComposerTextField() async throws { + try await performAccessibilityAudit(named: "MessageComposerTextField_Previews") + } + + func testMessageComposer() async throws { + try await performAccessibilityAudit(named: "MessageComposer_Previews") + } + + func testMessageForwardingScreen() async throws { + try await performAccessibilityAudit(named: "MessageForwardingScreen_Previews") + } + + func testMessageText() async throws { + try await performAccessibilityAudit(named: "MessageText_Previews") + } + + func testNoticeRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "NoticeRoomTimelineView_Previews") + } + + func testNotificationPermissionsScreen() async throws { + try await performAccessibilityAudit(named: "NotificationPermissionsScreen_Previews") + } + + func testNotificationSettingsEditScreenRoomCell() async throws { + try await performAccessibilityAudit(named: "NotificationSettingsEditScreenRoomCell_Previews") + } + + func testNotificationSettingsEditScreen() async throws { + try await performAccessibilityAudit(named: "NotificationSettingsEditScreen_Previews") + } + + func testNotificationSettingsScreen() async throws { + try await performAccessibilityAudit(named: "NotificationSettingsScreen_Previews") + } + + func testPINTextField() async throws { + try await performAccessibilityAudit(named: "PINTextField_Previews") + } + + func testPaginationIndicatorRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "PaginationIndicatorRoomTimelineView_Previews") + } + + func testPillViewOnBubble() async throws { + try await performAccessibilityAudit(named: "PillViewOnBubble_Previews") + } + + func testPillView() async throws { + try await performAccessibilityAudit(named: "PillView_Previews") + } + + func testPinnedEventsTimelineScreen() async throws { + try await performAccessibilityAudit(named: "PinnedEventsTimelineScreen_Previews") + } + + func testPinnedItemsBannerView() async throws { + try await performAccessibilityAudit(named: "PinnedItemsBannerView_Previews") + } + + func testPinnedItemsIndicatorView() async throws { + try await performAccessibilityAudit(named: "PinnedItemsIndicatorView_Previews") + } + + func testPlaceholderAvatarImage() async throws { + try await performAccessibilityAudit(named: "PlaceholderAvatarImage_Previews") + } + + func testPlaceholderScreen() async throws { + try await performAccessibilityAudit(named: "PlaceholderScreen_Previews") + } + + func testPollFormScreen() async throws { + try await performAccessibilityAudit(named: "PollFormScreen_Previews") + } + + func testPollOptionView() async throws { + try await performAccessibilityAudit(named: "PollOptionView_Previews") + } + + func testPollRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "PollRoomTimelineView_Previews") + } + + func testPollView() async throws { + try await performAccessibilityAudit(named: "PollView_Previews") + } + + func testQRCodeLoginScreen() async throws { + try await performAccessibilityAudit(named: "QRCodeLoginScreen_Previews") + } + + func testReactionsSummaryView() async throws { + try await performAccessibilityAudit(named: "ReactionsSummaryView_Previews") + } + + func testReadMarkerRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "ReadMarkerRoomTimelineView_Previews") + } + + func testReadReceiptCell() async throws { + try await performAccessibilityAudit(named: "ReadReceiptCell_Previews") + } + + func testReadReceiptsSummaryView() async throws { + try await performAccessibilityAudit(named: "ReadReceiptsSummaryView_Previews") + } + + func testRedactedRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "RedactedRoomTimelineView_Previews") + } + + func testReportContentScreen() async throws { + try await performAccessibilityAudit(named: "ReportContentScreen_Previews") + } + + func testReportRoomScreen() async throws { + try await performAccessibilityAudit(named: "ReportRoomScreen_Previews") + } + + func testResolveVerifiedUserSendFailureScreen() async throws { + try await performAccessibilityAudit(named: "ResolveVerifiedUserSendFailureScreen_Previews") + } + + func testRoomAttachmentPicker() async throws { + try await performAccessibilityAudit(named: "RoomAttachmentPicker_Previews") + } + + func testRoomAvatarImage() async throws { + try await performAccessibilityAudit(named: "RoomAvatarImage_Previews") + } + + func testRoomChangePermissionsScreen() async throws { + try await performAccessibilityAudit(named: "RoomChangePermissionsScreen_Previews") + } + + func testRoomChangeRolesScreenRow() async throws { + try await performAccessibilityAudit(named: "RoomChangeRolesScreenRow_Previews") + } + + func testRoomChangeRolesScreenSelectedItem() async throws { + try await performAccessibilityAudit(named: "RoomChangeRolesScreenSelectedItem_Previews") + } + + func testRoomChangeRolesScreen() async throws { + try await performAccessibilityAudit(named: "RoomChangeRolesScreen_Previews") + } + + func testRoomDetailsEditScreen() async throws { + try await performAccessibilityAudit(named: "RoomDetailsEditScreen_Previews") + } + + func testRoomDetailsScreen() async throws { + try await performAccessibilityAudit(named: "RoomDetailsScreen_Previews") + } + + func testRoomDirectorySearchCell() async throws { + try await performAccessibilityAudit(named: "RoomDirectorySearchCell_Previews") + } + + func testRoomDirectorySearchScreen() async throws { + try await performAccessibilityAudit(named: "RoomDirectorySearchScreen_Previews") + } + + func testRoomHeaderView() async throws { + try await performAccessibilityAudit(named: "RoomHeaderView_Previews") + } + + func testRoomInviterLabel() async throws { + try await performAccessibilityAudit(named: "RoomInviterLabel_Previews") + } + + func testRoomListFilterView() async throws { + try await performAccessibilityAudit(named: "RoomListFilterView_Previews") + } + + func testRoomListFiltersEmptyStateView() async throws { + try await performAccessibilityAudit(named: "RoomListFiltersEmptyStateView_Previews") + } + + func testRoomListFiltersView() async throws { + try await performAccessibilityAudit(named: "RoomListFiltersView_Previews") + } + + func testRoomMemberDetailsScreen() async throws { + try await performAccessibilityAudit(named: "RoomMemberDetailsScreen_Previews") + } + + func testRoomMembersListMemberCell() async throws { + try await performAccessibilityAudit(named: "RoomMembersListMemberCell_Previews") + } + + func testRoomMembersListScreen() async throws { + try await performAccessibilityAudit(named: "RoomMembersListScreen_Previews") + } + + func testRoomNotificationSettingsCustomSectionView() async throws { + try await performAccessibilityAudit(named: "RoomNotificationSettingsCustomSectionView_Previews") + } + + func testRoomNotificationSettingsScreen() async throws { + try await performAccessibilityAudit(named: "RoomNotificationSettingsScreen_Previews") + } + + func testRoomNotificationSettingsUserDefinedScreen() async throws { + try await performAccessibilityAudit(named: "RoomNotificationSettingsUserDefinedScreen_Previews") + } + + func testRoomPollsHistoryScreen() async throws { + try await performAccessibilityAudit(named: "RoomPollsHistoryScreen_Previews") + } + + func testRoomRolesAndPermissionsScreen() async throws { + try await performAccessibilityAudit(named: "RoomRolesAndPermissionsScreen_Previews") + } + + func testRoomScreenFooterView() async throws { + try await performAccessibilityAudit(named: "RoomScreenFooterView_Previews") + } + + func testRoomScreen() async throws { + try await performAccessibilityAudit(named: "RoomScreen_Previews") + } + + func testRoomSelectionScreen() async throws { + try await performAccessibilityAudit(named: "RoomSelectionScreen_Previews") + } + + func testSFNumberedListView() async throws { + try await performAccessibilityAudit(named: "SFNumberedListView_Previews") + } + + func testSecureBackupKeyBackupScreen() async throws { + try await performAccessibilityAudit(named: "SecureBackupKeyBackupScreen_Previews") + } + + func testSecureBackupLogoutConfirmationScreen() async throws { + try await performAccessibilityAudit(named: "SecureBackupLogoutConfirmationScreen_Previews") + } + + func testSecureBackupRecoveryKeyScreen() async throws { + try await performAccessibilityAudit(named: "SecureBackupRecoveryKeyScreen_Previews") + } + + func testSecureBackupScreen() async throws { + try await performAccessibilityAudit(named: "SecureBackupScreen_Previews") + } + + func testSecurityAndPrivacyScreen() async throws { + try await performAccessibilityAudit(named: "SecurityAndPrivacyScreen_Previews") + } + + func testSendInviteConfirmationView() async throws { + try await performAccessibilityAudit(named: "SendInviteConfirmationView_Previews") + } + + func testSeparatorMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "SeparatorMediaEventsTimelineView_Previews") + } + + func testSeparatorRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "SeparatorRoomTimelineView_Previews") + } + + func testServerConfirmationScreen() async throws { + try await performAccessibilityAudit(named: "ServerConfirmationScreen_Previews") + } + + func testServerSelection() async throws { + try await performAccessibilityAudit(named: "ServerSelection_Previews") + } + + func testSessionVerificationRequestDetailsView() async throws { + try await performAccessibilityAudit(named: "SessionVerificationRequestDetailsView_Previews") + } + + func testSessionVerification() async throws { + try await performAccessibilityAudit(named: "SessionVerification_Previews") + } + + func testSettingsScreen() async throws { + try await performAccessibilityAudit(named: "SettingsScreen_Previews") + } + + func testShimmerOverlay() async throws { + try await performAccessibilityAudit(named: "ShimmerOverlay_Previews") + } + + func testSoftLogoutScreen() async throws { + try await performAccessibilityAudit(named: "SoftLogoutScreen_Previews") + } + + func testSplashScreen() async throws { + try await performAccessibilityAudit(named: "SplashScreen_Previews") + } + + func testStackedAvatarsView() async throws { + try await performAccessibilityAudit(named: "StackedAvatarsView_Previews") + } + + func testStartChatScreen() async throws { + try await performAccessibilityAudit(named: "StartChatScreen_Previews") + } + + func testStateRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "StateRoomTimelineView_Previews") + } + + func testStaticLocationScreenViewer() async throws { + try await performAccessibilityAudit(named: "StaticLocationScreenViewer_Previews") + } + + func testStickerRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "StickerRoomTimelineView_Previews") + } + + func testSwipeRightAction() async throws { + try await performAccessibilityAudit(named: "SwipeRightAction_Previews") + } + + func testSwipeToReplyView() async throws { + try await performAccessibilityAudit(named: "SwipeToReplyView_Previews") + } + + func testTextRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "TextRoomTimelineView_Previews") + } + + func testThreadDecorator() async throws { + try await performAccessibilityAudit(named: "ThreadDecorator_Previews") + } + + func testTimelineDeliveryStatusView() async throws { + try await performAccessibilityAudit(named: "TimelineDeliveryStatusView_Previews") + } + + func testTimelineItemBubbledStylerView() async throws { + try await performAccessibilityAudit(named: "TimelineItemBubbledStylerView_Previews") + } + + func testTimelineItemDebugView() async throws { + try await performAccessibilityAudit(named: "TimelineItemDebugView_Previews") + } + + func testTimelineItemMenu() async throws { + try await performAccessibilityAudit(named: "TimelineItemMenu_Previews") + } + + func testTimelineItemSendInfoLabel() async throws { + try await performAccessibilityAudit(named: "TimelineItemSendInfoLabel_Previews") + } + + func testTimelineItemStyler() async throws { + try await performAccessibilityAudit(named: "TimelineItemStyler_Previews") + } + + func testTimelineMediaPreviewDetailsView() async throws { + try await performAccessibilityAudit(named: "TimelineMediaPreviewDetailsView_Previews") + } + + func testTimelineMediaPreviewRedactConfirmationView() async throws { + try await performAccessibilityAudit(named: "TimelineMediaPreviewRedactConfirmationView_Previews") + } + + func testTimelineReactionView() async throws { + try await performAccessibilityAudit(named: "TimelineReactionView_Previews") + } + + func testTimelineReadReceiptsView() async throws { + try await performAccessibilityAudit(named: "TimelineReadReceiptsView_Previews") + } + + func testTimelineReplyView() async throws { + try await performAccessibilityAudit(named: "TimelineReplyView_Previews") + } + + func testTimelineStartRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "TimelineStartRoomTimelineView_Previews") + } + + func testTimelineThreadSummaryView() async throws { + try await performAccessibilityAudit(named: "TimelineThreadSummaryView_Previews") + } + + func testTimelineView() async throws { + try await performAccessibilityAudit(named: "TimelineView_Previews") + } + + func testTombstonedAvatarImage() async throws { + try await performAccessibilityAudit(named: "TombstonedAvatarImage_Previews") + } + + func testTypingIndicatorView() async throws { + try await performAccessibilityAudit(named: "TypingIndicatorView_Previews") + } + + func testUnsupportedRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "UnsupportedRoomTimelineView_Previews") + } + + func testUserDetailsEditScreen() async throws { + try await performAccessibilityAudit(named: "UserDetailsEditScreen_Previews") + } + + func testUserIndicatorModalView() async throws { + try await performAccessibilityAudit(named: "UserIndicatorModalView_Previews") + } + + func testUserIndicatorToastView() async throws { + try await performAccessibilityAudit(named: "UserIndicatorToastView_Previews") + } + + func testUserProfileCell() async throws { + try await performAccessibilityAudit(named: "UserProfileCell_Previews") + } + + func testUserProfileScreen() async throws { + try await performAccessibilityAudit(named: "UserProfileScreen_Previews") + } + + func testVerificationBadge() async throws { + try await performAccessibilityAudit(named: "VerificationBadge_Previews") + } + + func testVideoMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "VideoMediaEventsTimelineView_Previews") + } + + func testVideoRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "VideoRoomTimelineView_Previews") + } + + func testVisualListItem() async throws { + try await performAccessibilityAudit(named: "VisualListItem_Previews") + } + + func testVoiceMessageButton() async throws { + try await performAccessibilityAudit(named: "VoiceMessageButton_Previews") + } + + func testVoiceMessageMediaEventsTimelineView() async throws { + try await performAccessibilityAudit(named: "VoiceMessageMediaEventsTimelineView_Previews") + } + + func testVoiceMessagePreviewComposer() async throws { + try await performAccessibilityAudit(named: "VoiceMessagePreviewComposer_Previews") + } + + func testVoiceMessageRecordingButton() async throws { + try await performAccessibilityAudit(named: "VoiceMessageRecordingButton_Previews") + } + + func testVoiceMessageRecordingComposer() async throws { + try await performAccessibilityAudit(named: "VoiceMessageRecordingComposer_Previews") + } + + func testVoiceMessageRecordingView() async throws { + try await performAccessibilityAudit(named: "VoiceMessageRecordingView_Previews") + } + + func testVoiceMessageRoomPlaybackView() async throws { + try await performAccessibilityAudit(named: "VoiceMessageRoomPlaybackView_Previews") + } + + func testVoiceMessageRoomTimelineView() async throws { + try await performAccessibilityAudit(named: "VoiceMessageRoomTimelineView_Previews") + } + + func testWaveformCursorView() async throws { + try await performAccessibilityAudit(named: "WaveformCursorView_Previews") + } +} + +// swiftlint:enable all +// swiftformat:enable all diff --git a/AccessibilityTests/Sources/Test.swift b/AccessibilityTests/Sources/Test.swift deleted file mode 100644 index 8c5e3ae70..000000000 --- a/AccessibilityTests/Sources/Test.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright 2025 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. -// - -import XCTest - -@MainActor -class Test: XCTestCase { - var app: XCUIApplication! - - func test() async throws { - let client = try UITestsSignalling.Client(mode: .tests) - app = Application.launch(viewID: "SecureBackupLogoutConfirmationScreen_Previews") - await client.waitForApp() - defer { try? client.stop() } - - try client.send(.accessibilityAudit(.nextPreview)) - forLoop: for await signal in client.signals.values { - switch signal { - case .accessibilityAudit(let auditSignal): - switch auditSignal { - case .nextPreviewReady(let name): - try? app.performAccessibilityAudit { issue in - XCTFail("\(name): \(issue)") - return true - } - try? client.send(.accessibilityAudit(.nextPreview)) - case .noMorePreviews: - break forLoop - default: - fatalError("Unhandled signal") - } - default: - fatalError("Unhandled signal") - } - } - - app.terminate() - } -} diff --git a/AccessibilityTests/SupportingFiles/target.yml b/AccessibilityTests/SupportingFiles/target.yml index b80f00867..30b0b22a8 100644 --- a/AccessibilityTests/SupportingFiles/target.yml +++ b/AccessibilityTests/SupportingFiles/target.yml @@ -48,4 +48,8 @@ targets: - path: ../Sources - path: ../SupportingFiles - path: ../../ElementX/Sources/UITests/UITestsSignalling.swift + - path: ../../ElementX/Sources/Other/AccessibilityIdentifiers.swift + - path: ../../ElementX/Sources/Other/MatrixEntityRegex.swift + - path: ../../ElementX/Sources/Other/Pills/PillUtilities.swift + - path: ../../ElementX/Sources/Other/Extensions/NSRegularExpresion.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 395172b35..3aae24f37 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -503,6 +503,7 @@ 60ED66E63A169E47489348A8 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; }; 611BEE29B8B622204E1E6B04 /* SecurityAndPrivacyScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F2F6B6E56055EF173A2DD3 /* SecurityAndPrivacyScreenCoordinator.swift */; }; 617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; }; + 6183BD3916CAA7134B6070D2 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; 6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */; }; 61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; }; 61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; }; @@ -644,6 +645,7 @@ 7B66DA4E7E5FE4D1A0FCEAA4 /* JoinRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAB5662310AE73D93815134 /* JoinRoomScreenViewModelProtocol.swift */; }; 7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; }; 7BD2123144A32F082CECC108 /* AudioRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EAFFD44F81F86012D6EC27 /* AudioRoomTimelineView.swift */; }; + 7BDC3976D88D40D2A45BEB8C /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; 7BF368A78E6D9AFD222F25AF /* SecureBackupScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE094FCB6387D268C436161 /* SecureBackupScreenViewModel.swift */; }; 7C0E29E0279866C62EC67A28 /* JoinRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5127D6EA05B2E45D0A7D59 /* JoinRoomScreenViewModelTests.swift */; }; 7C164A642E8932B5F9004550 /* test_voice_message.m4a in Resources */ = {isa = PBXBuildFile; fileRef = DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */; }; @@ -734,6 +736,7 @@ 8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */; }; 8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; }; 8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */; }; + 8BD22815DC34D7F9E9C1F2FE /* PillUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C537DE821FED94D23467B6C4 /* PillUtilities.swift */; }; 8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */; }; 8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; }; 8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; }; @@ -847,7 +850,6 @@ A1672EF491FE6F3BBF7878BE /* test_apple_image.heic in Resources */ = {isa = PBXBuildFile; fileRef = BB576F4118C35E6B5124FA22 /* test_apple_image.heic */; }; A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; }; A1BA8D6BABAFA9BAAEAA3FFD /* NotificationSettingsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */; }; - A1C8951A632AC5F350FE0A6C /* Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932E4E8BB476283CBF219427 /* Test.swift */; }; A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; }; A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */; }; A2172B5A26976F9174228B8A /* AppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */; }; @@ -991,6 +993,7 @@ BDA68E8D95B2B24B28825B8B /* LoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C368CAB3063EF275357ECD4 /* LoginScreenViewModel.swift */; }; BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */; }; BDED6DA7AD1E76018C424143 /* LegalInformationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */; }; + BDFF0AEBF57B5B124062DAEF /* GeneratedAccessibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */; }; BE8E5985771DF9137C6CE89A /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077B01C13BBA2996272C5FB5 /* ProcessInfo.swift */; }; BEC6DFEA506085D3027E353C /* MediaEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */; }; BED59052E5C5163D2B065CA6 /* EventTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A81FD0C60175FA081EB19AD /* EventTimelineItem.swift */; }; @@ -1041,6 +1044,7 @@ C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; }; C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */; }; C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; }; + C8D1D18E22672D48C11A5366 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; }; C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; }; C8E1E4E06B7C7A3A8246FC9B /* MediaEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */; }; C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; }; @@ -1211,6 +1215,7 @@ ED635D7F00FA07E94D3CE1E8 /* PreviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B796D347E53631576F631C /* PreviewTests.swift */; }; ED90A59F068FD0CA27E602ED /* UserProfileListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */; }; EDB6915EC953BB2A44AA608E /* EditRoomAddressScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */; }; + EDD45A73B688B87D84B9C2E9 /* AccessibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */; }; EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */; }; EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */; }; EE56238683BC3ECA9BA00684 /* GlobalSearchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */; }; @@ -1989,6 +1994,7 @@ 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = ""; }; 79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = ""; }; 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = ""; }; + 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = ""; }; 7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = ""; }; 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientBuilderHook.swift; sourceTree = ""; }; 7AC1E3FE9B59EA094867863E /* TimelineControllerFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineControllerFactoryProtocol.swift; sourceTree = ""; }; @@ -2122,7 +2128,6 @@ 922E498EB74CF6F5CC236F81 /* AdvancedSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenModels.swift; sourceTree = ""; }; 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAuthenticationPresenter.swift; sourceTree = ""; }; 92DB574F954CC2B40F7BE892 /* QRCodeScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerView.swift; sourceTree = ""; }; - 932E4E8BB476283CBF219427 /* Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test.swift; sourceTree = ""; }; 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaFrame.swift; sourceTree = ""; }; 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenModels.swift; sourceTree = ""; }; @@ -2620,6 +2625,7 @@ F409D44C2E6BE50462E82F8A /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = ""; }; F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionMock.swift; sourceTree = ""; }; F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsView.swift; sourceTree = ""; }; + F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModel.swift; sourceTree = ""; }; @@ -6040,8 +6046,9 @@ EB9C851423AA9BC8CEC381DC /* Sources */ = { isa = PBXGroup; children = ( + 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */, 2D9DBE1C69F3A60D93B2203F /* Application.swift */, - 932E4E8BB476283CBF219427 /* Test.swift */, + F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */, ); path = Sources; sourceTree = ""; @@ -6861,7 +6868,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which sourcery >/dev/null; then\n sourcery --config Tools/Sourcery/TestablePreviewsDictionary.yml\nelse\n echo \"warning: Sourcery not installed, run swift run tools setup-project\"\nfi\n"; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which sourcery >/dev/null; then\n sourcery --config Tools/Sourcery/TestablePreviewsDictionary.yml\n sourcery --config Tools/Sourcery/AccessibilityTests.yml\nelse\n echo \"warning: Sourcery not installed, run swift run tools setup-project\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -7116,8 +7123,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C8D1D18E22672D48C11A5366 /* AccessibilityIdentifiers.swift in Sources */, + EDD45A73B688B87D84B9C2E9 /* AccessibilityTests.swift in Sources */, F996259EAE68664BC345C197 /* Application.swift in Sources */, - A1C8951A632AC5F350FE0A6C /* Test.swift in Sources */, + BDFF0AEBF57B5B124062DAEF /* GeneratedAccessibilityTests.swift in Sources */, + 6183BD3916CAA7134B6070D2 /* MatrixEntityRegex.swift in Sources */, + 7BDC3976D88D40D2A45BEB8C /* NSRegularExpresion.swift in Sources */, + 8BD22815DC34D7F9E9C1F2FE /* PillUtilities.swift in Sources */, E323A54F317604BDD6968D79 /* UITestsSignalling.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ElementX/Sources/AccessibilityTests/AccessibilityTestsAppCoordinator.swift b/ElementX/Sources/AccessibilityTests/AccessibilityTestsAppCoordinator.swift index 3f0dd0c4e..ee2120c17 100644 --- a/ElementX/Sources/AccessibilityTests/AccessibilityTestsAppCoordinator.swift +++ b/ElementX/Sources/AccessibilityTests/AccessibilityTestsAppCoordinator.swift @@ -6,6 +6,7 @@ // import Combine +import CoreLocation import SwiftUI class AccessibilityTestsAppCoordinator: AppCoordinatorProtocol { @@ -53,6 +54,8 @@ class AccessibilityTestsAppCoordinator: AppCoordinatorProtocol { previewsWrapper = .init(name: name, previews: previewType._allPreviews) setupSignalling() + // Used to perform the request check before the tests run on CI, so it can be immediately dismissed. + CLLocationManager().requestWhenInUseAuthorization() } func toPresentable() -> AnyView { @@ -143,7 +146,12 @@ struct PreviewsWrapperView: View { switch fulfillmentSource { case .publisher(let publisher): - _ = await publisher.values.first { $0 == true } + _ = await publisher + // Not sure whye byt some publisher seem to not properly comunicate their completion, + // so we added a timeout. Since we are going to migrate from publishers to stream, + // this is a temporary solution + .timeout(.seconds(1), scheduler: DispatchQueue.main) + .values.first { $0 == true } case .stream(let stream): _ = await stream.first { $0 == true } case .none: diff --git a/ElementX/Sources/Other/MatrixEntityRegex.swift b/ElementX/Sources/Other/MatrixEntityRegex.swift index 8b6b43672..66fdf53f8 100644 --- a/ElementX/Sources/Other/MatrixEntityRegex.swift +++ b/ElementX/Sources/Other/MatrixEntityRegex.swift @@ -6,7 +6,6 @@ // import Foundation -import MatrixRustSDK // https://spec.matrix.org/latest/appendices/#identifier-grammar enum MatrixEntityRegex: String { diff --git a/ElementX/Sources/Other/Pills/PillUtilities.swift b/ElementX/Sources/Other/Pills/PillUtilities.swift index 8d92ee6dc..31ffa0f41 100644 --- a/ElementX/Sources/Other/Pills/PillUtilities.swift +++ b/ElementX/Sources/Other/Pills/PillUtilities.swift @@ -9,9 +9,6 @@ import Foundation enum PillUtilities { static let atRoom = "@room" - static var everyone: String { - L10n.commonEveryone - } /// Used by the WYSIWYG as the urlString value to identify @room mentions static let composerAtRoomURLString = "#" diff --git a/ElementX/Sources/Other/SwiftUI/Views/RoomHeaderView.swift b/ElementX/Sources/Other/SwiftUI/Views/RoomHeaderView.swift index 03f1d8fc2..68b27e4a9 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/RoomHeaderView.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/RoomHeaderView.swift @@ -21,6 +21,9 @@ struct RoomHeaderView: View { // https://github.com/element-hq/element-x-ios/issues/4180 // Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSLayoutConstraint constant is not finite! content + } else if ProcessInfo.isRunningAccessibilityTests { + // Accessibility tests scale up the dynamic size in real time which may break the view + content } else { content // Take up as much space as possible, with a leading alignment for use in the principal toolbar position diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift index 9cf01599a..addcc5481 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift @@ -168,3 +168,9 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol { return roomName.localizedStandardContains(searchText) || roomAlias.localizedStandardContains(searchText) } } + +extension PillUtilities { + static var everyone: String { + L10n.commonEveryone + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 82e5c3440..b6eea3413 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -124,7 +124,8 @@ struct RoomScreen: View { private var composer: some View { if context.viewState.hasSuccessor { tombstonedDialogue - } else if context.viewState.canSendMessage { + } else if context.viewState.canSendMessage, !ProcessInfo.isRunningAccessibilityTests { + // We are not sure why but when wrapped in the room screen the composer toolbar breaks the accessibility tests composerToolbar } else { ComposerDisabledView() diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index f39936796..7c48e7c16 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -189,6 +189,7 @@ targets: export PATH="$PATH:/opt/homebrew/bin" if which sourcery >/dev/null; then sourcery --config Tools/Sourcery/TestablePreviewsDictionary.yml + sourcery --config Tools/Sourcery/AccessibilityTests.yml else echo "warning: Sourcery not installed, run swift run tools setup-project" fi diff --git a/Tools/Sourcery/AccessibilityTests.stencil b/Tools/Sourcery/AccessibilityTests.stencil new file mode 100644 index 000000000..8baa83ff9 --- /dev/null +++ b/Tools/Sourcery/AccessibilityTests.stencil @@ -0,0 +1,31 @@ +// swiftlint:disable all +// swiftformat:disable all + +{% if argument.mainTarget %} +@testable import {{ argument.mainTarget }} +{% endif %} +{% for import in argument.imports %} +{% if import != "last" %} +import {{ import }} +{% endif %} +{% endfor %} +{% for import in argument.testableImports %} +{% if import != "last" %} +@testable import {{ import }} +{% endif %} +{% endfor %} + +extension AccessibilityTests { + + {% for type in types.types where (type.implements.TestablePreview or type.based.TestablePreview or type|annotated:"TestablePreview") and type.name != "TestablePreview" %} + func test{{ type.name|replace:"_Previews", "" }}() async throws { + try await performAccessibilityAudit(named: "{{ type.name }}") + } + {%- if not forloop.last %} + + {% endif %} + {% endfor %} +} + +// swiftlint:enable all +// swiftformat:enable all diff --git a/Tools/Sourcery/AccessibilityTests.yml b/Tools/Sourcery/AccessibilityTests.yml new file mode 100644 index 000000000..8e7b7a3b9 --- /dev/null +++ b/Tools/Sourcery/AccessibilityTests.yml @@ -0,0 +1,7 @@ +sources: + include: + - ../../ElementX +templates: + - AccessibilityTests.stencil +output: + ../../AccessibilityTests/Sources/GeneratedAccessibilityTests.swift diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 92c749519..9da3d3002 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -85,6 +85,20 @@ lane :ui_tests do |options| ) end +lane :accessibility_tests do |options| + reset_simulator = ENV.key?('CI') + + run_tests( + scheme: "AccessibilityTests", + device: "iPhone 16 (18.5)", + ensure_devices_found: true, + prelaunch_simulator: false, + result_bundle: true, + number_of_retries: 0, + reset_simulator: reset_simulator + ) +end + lane :integration_tests do clear_derived_data()