Add comprehensive iOS unit and UI test suites for greenfield test plan
- Create unit tests: DataLayerTests (27 tests for DATA-001–007), DataManagerExtendedTests (20 tests for TASK-005, TASK-012, TCOMP-003, THEME-001, QA-002), plus ValidationHelpers, TaskMetrics, StringExtensions, DoubleExtensions, DateUtils, DocumentHelpers, ErrorMessageParser - Create UI tests: AuthenticationTests, PasswordResetTests, OnboardingTests, TaskIntegration, ContractorIntegration, ResidenceIntegration, DocumentIntegration, DataLayer, Stability - Add UI test framework: AuthenticatedTestCase, ScreenObjects, TestFlows, TestAccountManager, TestAccountAPIClient, TestDataCleaner, TestDataSeeder - Add accessibility identifiers to password reset views for UI test support - Add greenfield test plan CSVs and update automated column for 27 test IDs - All 297 unit tests pass across 60 suites Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,19 @@ struct UITestID {
|
||||
static let progressIndicator = "Onboarding.ProgressIndicator"
|
||||
}
|
||||
|
||||
struct PasswordReset {
|
||||
static let emailField = "PasswordReset.EmailField"
|
||||
static let sendCodeButton = "PasswordReset.SendCodeButton"
|
||||
static let backToLoginButton = "PasswordReset.BackToLoginButton"
|
||||
static let codeField = "PasswordReset.CodeField"
|
||||
static let verifyCodeButton = "PasswordReset.VerifyCodeButton"
|
||||
static let resendCodeButton = "PasswordReset.ResendCodeButton"
|
||||
static let newPasswordField = "PasswordReset.NewPasswordField"
|
||||
static let confirmPasswordField = "PasswordReset.ConfirmPasswordField"
|
||||
static let resetButton = "PasswordReset.ResetButton"
|
||||
static let returnToLoginButton = "PasswordReset.ReturnToLoginButton"
|
||||
}
|
||||
|
||||
struct Auth {
|
||||
static let usernameField = "Login.UsernameField"
|
||||
static let passwordField = "Login.PasswordField"
|
||||
@@ -275,3 +288,124 @@ struct RegisterScreen {
|
||||
cancelButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Password Reset Screens
|
||||
|
||||
struct ForgotPasswordScreen {
|
||||
let app: XCUIApplication
|
||||
|
||||
private var emailField: XCUIElement { app.textFields[UITestID.PasswordReset.emailField] }
|
||||
private var sendCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.sendCodeButton] }
|
||||
private var backToLoginButton: XCUIElement { app.buttons[UITestID.PasswordReset.backToLoginButton] }
|
||||
|
||||
func waitForLoad(timeout: TimeInterval = 15) {
|
||||
// Wait for the email field or the "Forgot Password?" title
|
||||
let emailLoaded = emailField.waitForExistence(timeout: timeout)
|
||||
if !emailLoaded {
|
||||
let title = app.staticTexts.containing(
|
||||
NSPredicate(format: "label CONTAINS[c] 'Forgot Password'")
|
||||
).firstMatch
|
||||
XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected forgot password screen to load")
|
||||
}
|
||||
}
|
||||
|
||||
func enterEmail(_ email: String) {
|
||||
emailField.waitUntilHittable(timeout: 10).tap()
|
||||
emailField.typeText(email)
|
||||
}
|
||||
|
||||
func tapSendCode() {
|
||||
sendCodeButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
|
||||
func tapBackToLogin() {
|
||||
backToLoginButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
}
|
||||
|
||||
struct VerifyResetCodeScreen {
|
||||
let app: XCUIApplication
|
||||
|
||||
private var codeField: XCUIElement { app.textFields[UITestID.PasswordReset.codeField] }
|
||||
private var verifyCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.verifyCodeButton] }
|
||||
private var resendCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.resendCodeButton] }
|
||||
|
||||
func waitForLoad(timeout: TimeInterval = 15) {
|
||||
let codeLoaded = codeField.waitForExistence(timeout: timeout)
|
||||
if !codeLoaded {
|
||||
let title = app.staticTexts.containing(
|
||||
NSPredicate(format: "label CONTAINS[c] 'Check Your Email'")
|
||||
).firstMatch
|
||||
XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected verify reset code screen to load")
|
||||
}
|
||||
}
|
||||
|
||||
func enterCode(_ code: String) {
|
||||
codeField.waitUntilHittable(timeout: 10).tap()
|
||||
codeField.typeText(code)
|
||||
}
|
||||
|
||||
func tapVerify() {
|
||||
verifyCodeButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
|
||||
func tapResendCode() {
|
||||
resendCodeButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
}
|
||||
|
||||
struct ResetPasswordScreen {
|
||||
let app: XCUIApplication
|
||||
|
||||
// The new password field may be a SecureField or TextField depending on visibility toggle
|
||||
private var newPasswordSecureField: XCUIElement { app.secureTextFields[UITestID.PasswordReset.newPasswordField] }
|
||||
private var newPasswordVisibleField: XCUIElement { app.textFields[UITestID.PasswordReset.newPasswordField] }
|
||||
private var confirmPasswordSecureField: XCUIElement { app.secureTextFields[UITestID.PasswordReset.confirmPasswordField] }
|
||||
private var confirmPasswordVisibleField: XCUIElement { app.textFields[UITestID.PasswordReset.confirmPasswordField] }
|
||||
private var resetButton: XCUIElement { app.buttons[UITestID.PasswordReset.resetButton] }
|
||||
private var returnToLoginButton: XCUIElement { app.buttons[UITestID.PasswordReset.returnToLoginButton] }
|
||||
|
||||
func waitForLoad(timeout: TimeInterval = 15) {
|
||||
let loaded = newPasswordSecureField.waitForExistence(timeout: timeout)
|
||||
|| newPasswordVisibleField.waitForExistence(timeout: 3)
|
||||
if !loaded {
|
||||
let title = app.staticTexts.containing(
|
||||
NSPredicate(format: "label CONTAINS[c] 'Set New Password'")
|
||||
).firstMatch
|
||||
XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected reset password screen to load")
|
||||
}
|
||||
}
|
||||
|
||||
func enterNewPassword(_ password: String) {
|
||||
if newPasswordSecureField.exists {
|
||||
newPasswordSecureField.waitUntilHittable(timeout: 10).tap()
|
||||
newPasswordSecureField.typeText(password)
|
||||
} else {
|
||||
newPasswordVisibleField.waitUntilHittable(timeout: 10).tap()
|
||||
newPasswordVisibleField.typeText(password)
|
||||
}
|
||||
}
|
||||
|
||||
func enterConfirmPassword(_ password: String) {
|
||||
if confirmPasswordSecureField.exists {
|
||||
confirmPasswordSecureField.waitUntilHittable(timeout: 10).tap()
|
||||
confirmPasswordSecureField.typeText(password)
|
||||
} else {
|
||||
confirmPasswordVisibleField.waitUntilHittable(timeout: 10).tap()
|
||||
confirmPasswordVisibleField.typeText(password)
|
||||
}
|
||||
}
|
||||
|
||||
func tapReset() {
|
||||
resetButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
|
||||
func tapReturnToLogin() {
|
||||
returnToLoginButton.waitUntilHittable(timeout: 10).tap()
|
||||
}
|
||||
|
||||
var isResetButtonEnabled: Bool {
|
||||
resetButton.waitForExistenceOrFail(timeout: 10)
|
||||
return resetButton.isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user