Rearchitect UI test suite for complete, non-flaky coverage against live API
- Migrate Suite4-10, SmokeTests, NavigationCriticalPathTests to AuthenticatedTestCase with seeded admin account and real backend login - Add 34 accessibility identifiers across 11 app views (task completion, profile, notifications, theme, join residence, manage users, forms) - Create FeatureCoverageTests (14 tests) covering previously untested features: profile edit, theme selection, notification prefs, task completion, manage users, join residence, task templates - Create MultiUserSharingTests (18 API tests) and MultiUserSharingUITests (8 XCUI tests) for full cross-user residence sharing lifecycle - Add cleanup infrastructure: SuiteZZ_CleanupTests auto-wipes test data after runs, cleanup_test_data.sh script for manual reset via admin API - Add share code API methods to TestAccountAPIClient (generateShareCode, joinWithCode, getShareCode, listResidenceUsers, removeUser) - Fix app bugs found by tests: - ResidencesListView join callback now uses forceRefresh:true - APILayer invalidates task cache when residence count changes - AllTasksView auto-reloads tasks when residence list changes - Fix test quality: keyboard focus waits, Save/Add button label matching, Documents tab label (Docs), remove API verification from UI tests - DataLayerTests and PasswordResetTests now verify through UI, not API calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,52 @@ struct TestDocument: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
struct TestShareCode: Decodable {
|
||||
let id: Int
|
||||
let code: String
|
||||
let residenceId: Int
|
||||
let isActive: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, code
|
||||
case residenceId = "residence_id"
|
||||
case isActive = "is_active"
|
||||
}
|
||||
}
|
||||
|
||||
struct TestGenerateShareCodeResponse: Decodable {
|
||||
let message: String
|
||||
let shareCode: TestShareCode
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case message
|
||||
case shareCode = "share_code"
|
||||
}
|
||||
}
|
||||
|
||||
struct TestGetShareCodeResponse: Decodable {
|
||||
let shareCode: TestShareCode
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case shareCode = "share_code"
|
||||
}
|
||||
}
|
||||
|
||||
struct TestJoinResidenceResponse: Decodable {
|
||||
let message: String
|
||||
let residence: TestResidence
|
||||
}
|
||||
|
||||
struct TestResidenceUser: Decodable {
|
||||
let id: Int
|
||||
let username: String
|
||||
let email: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, username, email
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - API Client
|
||||
|
||||
enum TestAccountAPIClient {
|
||||
@@ -350,7 +396,7 @@ enum TestAccountAPIClient {
|
||||
|
||||
// MARK: - Document CRUD
|
||||
|
||||
static func createDocument(token: String, residenceId: Int, title: String, documentType: String = "Other", fields: [String: Any] = [:]) -> TestDocument? {
|
||||
static func createDocument(token: String, residenceId: Int, title: String, documentType: String = "general", fields: [String: Any] = [:]) -> TestDocument? {
|
||||
var body: [String: Any] = ["residence_id": residenceId, "title": title, "document_type": documentType]
|
||||
for (k, v) in fields { body[k] = v }
|
||||
return performRequest(method: "POST", path: "/documents/", body: body, token: token, responseType: TestDocument.self)
|
||||
@@ -372,6 +418,49 @@ enum TestAccountAPIClient {
|
||||
return result.succeeded
|
||||
}
|
||||
|
||||
// MARK: - Residence Sharing
|
||||
|
||||
static func generateShareCode(token: String, residenceId: Int) -> TestShareCode? {
|
||||
let wrapped: TestGenerateShareCodeResponse? = performRequest(
|
||||
method: "POST", path: "/residences/\(residenceId)/generate-share-code/",
|
||||
body: [:], token: token,
|
||||
responseType: TestGenerateShareCodeResponse.self
|
||||
)
|
||||
return wrapped?.shareCode
|
||||
}
|
||||
|
||||
static func getShareCode(token: String, residenceId: Int) -> TestShareCode? {
|
||||
let wrapped: TestGetShareCodeResponse? = performRequest(
|
||||
method: "GET", path: "/residences/\(residenceId)/share-code/",
|
||||
token: token, responseType: TestGetShareCodeResponse.self
|
||||
)
|
||||
return wrapped?.shareCode
|
||||
}
|
||||
|
||||
static func joinWithCode(token: String, code: String) -> TestJoinResidenceResponse? {
|
||||
let body: [String: Any] = ["code": code]
|
||||
return performRequest(
|
||||
method: "POST", path: "/residences/join-with-code/",
|
||||
body: body, token: token,
|
||||
responseType: TestJoinResidenceResponse.self
|
||||
)
|
||||
}
|
||||
|
||||
static func removeUser(token: String, residenceId: Int, userId: Int) -> Bool {
|
||||
let result: APIResult<TestMessageResponse> = performRequestWithResult(
|
||||
method: "DELETE", path: "/residences/\(residenceId)/users/\(userId)/",
|
||||
token: token, responseType: TestMessageResponse.self
|
||||
)
|
||||
return result.succeeded
|
||||
}
|
||||
|
||||
static func listResidenceUsers(token: String, residenceId: Int) -> [TestResidenceUser]? {
|
||||
return performRequest(
|
||||
method: "GET", path: "/residences/\(residenceId)/users/",
|
||||
token: token, responseType: [TestResidenceUser].self
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Raw Request (for custom/edge-case assertions)
|
||||
|
||||
/// Make a raw request and return the full APIResult with status code.
|
||||
|
||||
Reference in New Issue
Block a user