Skip to content

Commit 70be1af

Browse files
authored
Рефактор ui-тестов (#287)
1 parent cb2256a commit 70be1af

36 files changed

+72
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ SwiftUI-WorkoutApp/.DS_Store
66
fastlane/report.xml
77
.DS_Store
88
SwiftUI-WorkoutApp.xcodeproj/xcuserdata/oleg991.xcuserdatad/xcschemes/xcschememanagement.plist
9+
fastlane/screenshots/screenshots.html

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ rbenv exec fastlane snapshot
7373
| <img src="./fastlane/screenshots/ru/iPhone 16 Pro Max-1-sportsGroundsList.png"> | <img src="./fastlane/screenshots/ru/iPhone 16 Pro Max-2-sportsGroundDetails.png"> | <img src="./fastlane/screenshots/ru/iPhone 16 Pro Max-3-pastEvents.png"> | <img src="./fastlane/screenshots/ru/iPhone 16 Pro Max-4-eventDetails.png"> | <img src="./fastlane/screenshots/ru/iPhone 16 Pro Max-5-profile.png"> |
7474

7575
#### Модели девайсов, используемые для скриншотов
76-
По состоянию на 2025 год Apple берет за основу скриншоты для диагонали 6.9 (или 6.7) дюймов и масштабирует их под все остальные размеры экранов. Поэтому для скриншотов используется только один симулятор:
76+
По состоянию на 2025 год Apple берет за основу скриншоты для диагонали 6.9 (или 6.7) дюймов и масштабирует их под все остальные размеры экранов, то есть при желании можно использовать для скриншотов только один девайс.
77+
Поскольку в этом проекте подключен и работает `fastlane snapshot`, используем три девайса:
7778
- iPhone 16 Pro Max
79+
- iPhone 16 pro
80+
- iPhone SE (3rd generation)
7881

7982
Список всех существующих девайсов есть [тут](https://www.ios-resolution.com).
8083

SwiftUI-WorkoutApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
67BD2D012AF7D21B00F44064 /* ParksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BD2D002AF7D21B00F44064 /* ParksManager.swift */; };
7979
67D322972AE993F90045B92F /* MapView991 in Frameworks */ = {isa = PBXBuildFile; productRef = 67D322962AE993F90045B92F /* MapView991 */; };
8080
67D67DE52AE8526600F7A8B0 /* SWDesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 67D67DE42AE8526600F7A8B0 /* SWDesignSystem */; };
81+
67D908E22D6A2BBD0018EAF9 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 67A64A232AC83A0F00CBDD5F /* Localizable.xcstrings */; };
8182
67D916812838E2460098D3CB /* DialogsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D916802838E2460098D3CB /* DialogsListScreen.swift */; };
8283
67D916862838F0DD0098D3CB /* DialogScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D916852838F0DD0098D3CB /* DialogScreen.swift */; };
8384
67D9169628396C1E0098D3CB /* SendMessageScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D9169528396C1E0098D3CB /* SendMessageScreen.swift */; };
@@ -574,6 +575,7 @@
574575
isa = PBXResourcesBuildPhase;
575576
buildActionMask = 2147483647;
576577
files = (
578+
67D908E22D6A2BBD0018EAF9 /* Localizable.xcstrings in Resources */,
577579
);
578580
runOnlyForDeploymentPostprocessing = 0;
579581
};

WorkoutAppUITests/Extensions.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import XCTest
22

3+
extension XCUIElementQuery {
4+
func element(for localizationKey: String) -> XCUIElement {
5+
let bundle = Bundle(for: WorkoutAppUITests.self)
6+
let localizedString = NSLocalizedString(localizationKey, bundle: bundle, comment: "")
7+
return self[localizedString]
8+
}
9+
}
10+
311
extension XCUIElement {
412
func tapElement() {
513
if isHittable {
Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
import XCTest
22

3+
@MainActor
34
final class WorkoutAppUITests: XCTestCase {
4-
@MainActor private let springBoard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
5+
private let springBoard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
56
private var app: XCUIApplication!
7+
private let login = "testuserapple"
8+
private let password = "111111"
9+
private let usernameForSearch = "Ninenineone"
610

7-
@MainActor
8-
override func setUp() {
9-
super.setUp()
11+
override func setUp() async throws {
1012
continueAfterFailure = false
1113
app = XCUIApplication()
1214
app.launchArguments.append("UITest")
1315
setupSnapshot(app)
1416
app.launch()
1517
}
1618

17-
@MainActor
18-
override func tearDown() {
19-
super.tearDown()
19+
override func tearDown() async throws {
20+
try super.tearDownWithError()
2021
app.launchArguments.removeAll()
2122
app = nil
2223
}
2324

24-
@MainActor
2525
func testMakeScreenshots() {
26-
waitAndTap(timeout: 5, element: grantLocationAccessButton)
27-
waitAndTap(timeout: 5, element: grantNotificationAccessButton)
26+
handleLocationAlert()
27+
handleNotificationAlert()
28+
checkParks()
29+
checkEvents()
30+
checkProfile()
31+
}
32+
33+
private func checkParks() {
34+
waitForServerResponse()
2835
waitAndTapOrFail(timeout: 10, element: parksListPickerButton)
2936
waitForServerResponse()
3037
snapshot("1-sportsGroundsList")
@@ -33,7 +40,9 @@ final class WorkoutAppUITests: XCTestCase {
3340
waitForServerResponse()
3441
snapshot("2-sportsGroundDetails")
3542
waitAndTapOrFail(timeout: 5, element: closeButton)
43+
}
3644

45+
private func checkEvents() {
3746
waitAndTapOrFail(timeout: 10, element: eventsTabButton)
3847
waitAndTapOrFail(timeout: 10, element: pastEventsPickerButton)
3948
waitForServerResponse()
@@ -43,64 +52,57 @@ final class WorkoutAppUITests: XCTestCase {
4352
waitForServerResponse()
4453
snapshot("4-eventDetails")
4554
waitAndTapOrFail(timeout: 5, element: closeButton)
55+
}
4656

57+
private func checkProfile() {
4758
waitAndTapOrFail(timeout: 5, element: profileTabButton)
4859
waitAndTapOrFail(element: authorizeButton)
4960
waitAndTapOrFail(element: loginField)
50-
loginField.typeText(Constants.login)
61+
loginField.typeText(login)
5162
waitAndTapOrFail(element: passwordField)
52-
passwordField.typeText(Constants.password)
63+
passwordField.typeText(password)
5364
waitAndTapOrFail(element: loginButton)
5465
waitAndTapOrFail(timeout: 10, element: searchUsersButton)
5566
waitAndTapOrFail(timeout: 10, element: searchUserField)
5667
sleep(1) // иногда симулятор начинает печатать раньше времени, поэтому ждем
57-
searchUserField.typeText(Constants.usernameForSearch)
68+
searchUserField.typeText(usernameForSearch)
5869
searchUserField.typeText("\n") // жмем "return", чтобы начать поиск
5970
waitAndTapOrFail(timeout: 10, element: firstFoundUserCell)
6071
waitForServerResponse()
6172
snapshot("5-profile")
6273
}
6374
}
6475

65-
@MainActor
6676
private extension WorkoutAppUITests {
67-
enum Constants {
68-
static let login = "testuserapple"
69-
static let password = "111111"
70-
static let usernameForSearch = "Ninenineone"
77+
func handleLocationAlert() {
78+
let alert = springBoard.alerts.firstMatch
79+
let button = alert.buttons.element(
80+
matching: NSPredicate(
81+
format:
82+
"label IN {'Allow While Using App', 'При использовании приложения'}"
83+
)
84+
)
85+
waitAndTap(timeout: 5, element: button)
7186
}
7287

73-
var grantLocationAccessButton: XCUIElement {
74-
let rusButton = springBoard.alerts.firstMatch.buttons["При использовании приложения"]
75-
let enButton = springBoard.alerts.firstMatch.buttons["Allow While Using App"]
76-
return rusButton.exists ? rusButton : enButton
88+
func handleNotificationAlert() {
89+
let alert = springBoard.alerts.firstMatch
90+
let button = alert.buttons.element(
91+
matching: NSPredicate(
92+
format:
93+
"label IN {'Allow', 'Разрешить'}"
94+
)
95+
)
96+
waitAndTap(timeout: 5, element: button)
7797
}
7898

79-
var grantNotificationAccessButton: XCUIElement {
80-
let rusButton = springBoard.alerts.firstMatch.buttons["Разрешить"]
81-
let enButton = springBoard.alerts.firstMatch.buttons["Allow"]
82-
return rusButton.exists ? rusButton : enButton
83-
}
84-
85-
var tabbar: XCUIElement {
86-
let rusButton = app.tabBars["Панель вкладок"]
87-
let enButton = app.tabBars["Tab Bar"]
88-
return rusButton.exists ? rusButton : enButton
89-
}
90-
91-
var parksListPickerButton: XCUIElement { app.segmentedControls.firstMatch.buttons["Список"] }
92-
var profileTabButton: XCUIElement {
93-
let rusButton = tabbar.buttons["Профиль"]
94-
let enButton = tabbar.buttons["Profile"]
95-
return rusButton.exists ? rusButton : enButton
96-
}
97-
98-
var eventsTabButton: XCUIElement {
99-
let rusButton = tabbar.buttons["Мероприятия"]
100-
let enButton = tabbar.buttons["Events"]
101-
return rusButton.exists ? rusButton : enButton
99+
var tabbar: XCUIElement { app.tabBars.firstMatch }
100+
var parksListPickerButton: XCUIElement {
101+
app.segmentedControls.firstMatch.buttons.element(for: "Список")
102102
}
103103

104+
var profileTabButton: XCUIElement { tabbar.buttons.element(for: "Профиль") }
105+
var eventsTabButton: XCUIElement { tabbar.buttons.element(for: "Мероприятия") }
104106
var authorizeButton: XCUIElement { app.buttons["authorizeButton"] }
105107
var loginField: XCUIElement { app.textFields["loginField"] }
106108
var passwordField: XCUIElement { app.secureTextFields["passwordField"] }
@@ -110,6 +112,9 @@ private extension WorkoutAppUITests {
110112
var searchUserField: XCUIElement { app.searchFields.firstMatch }
111113
var firstFoundUserCell: XCUIElement { app.buttons["UserViewCell"].firstMatch }
112114
var firstParkCell: XCUIElement { app.buttons["ParkViewCell"].firstMatch }
113-
var pastEventsPickerButton: XCUIElement { app.segmentedControls.firstMatch.buttons["Прошедшие"] }
115+
var pastEventsPickerButton: XCUIElement {
116+
app.segmentedControls.firstMatch.buttons.element(for: "Прошедшие")
117+
}
118+
114119
var firstEventViewCell: XCUIElement { app.buttons["EventViewCell"].firstMatch }
115120
}

fastlane/Snapfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A list of devices you want to take the screenshots from
2-
devices(["iPhone 16 Pro Max"])
2+
devices(["iPhone 16 Pro Max", "iPhone 16 Pro", "iPhone SE (3rd generation)"])
33

44
# A list of languages which should be used. See https://docs.fastlane.tools/actions/snapshot/#available-language-codes
55
languages(["en-US", "ru"])
@@ -8,13 +8,16 @@ languages(["en-US", "ru"])
88
ios_version("18.1")
99

1010
# Очистить папку билда перед стартом
11-
clean(true)
11+
# clean(true)
12+
13+
# Пропускаем открытие html-файла с результатами
14+
skip_open_summary(true)
1215

1316
# Переустановить приложение перед стартом
1417
reinstall_app(true)
1518

1619
# Стереть данные симулятора перед стартом
17-
erase_simulator(true)
20+
# erase_simulator(true)
1821

1922
# Enabling this option will configure the Simulator's system language
2023
localize_simulator(true)
1.17 KB
Loading
554 Bytes
Loading
708 Bytes
Loading
560 Bytes
Loading

0 commit comments

Comments
 (0)