// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import XCTest class AuthenticationExampleUITests: XCTestCase { var app: XCUIApplication! override func setUp() { super.setUp() continueAfterFailure = false app = XCUIApplication() app.launchArguments.append("--uitesting-reset-auth") app.launch() } func dismissSavePasswordPrompt() { let passwordSheet = app.sheets.firstMatch if passwordSheet.waitForExistence(timeout: 3.0) { let notNowOption = passwordSheet.buttons["Not Now"] if notNowOption.exists { notNowOption.tap() } else if passwordSheet.staticTexts["Not Now"].exists { // Fallback: If Apple literally made it StaticText instead of a Button, // you can often just tap the text directly! passwordSheet.staticTexts["Not Now"].tap() } let predicate = NSPredicate(format: "exists == false") expectation(for: predicate, evaluatedWith: passwordSheet) waitForExpectations(timeout: 5.0) } // Handle system or compromised/weak password alerts (in-app or SpringBoard). let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") for container in [ app.alerts.firstMatch, springboard.sheets.firstMatch, springboard.alerts.firstMatch, ] where container.exists { for label in ["Not Now", "Close", "OK"] { if container.buttons[label].exists { container.buttons[label].tap() let dismissed = XCTNSPredicateExpectation( predicate: NSPredicate(format: "exists == false"), object: container ) _ = XCTWaiter.wait(for: [dismissed], timeout: 5.0) break } } } } private func dismissErrorAlertAndPopToAuthMenu() { app.alerts.buttons["OK"].tap() let alertDismissed = XCTNSPredicateExpectation( predicate: NSPredicate(format: "exists == false"), object: app.alerts.firstMatch ) _ = XCTWaiter.wait(for: [alertDismissed], timeout: 5.0) app.navigationBars.buttons.firstMatch.tap() _ = app.navigationBars["Firebase Auth"].waitForExistence(timeout: 5.0) } private func navigateToUserTab(timeout: TimeInterval = 10.0) { dismissSavePasswordPrompt() let userTabButton = app.tabBars.firstMatch.buttons.element(boundBy: 1) userTabButton.tap() if !app.navigationBars["User"].waitForExistence(timeout: 3.0) { // A late AutoFill / Save Password sheet can overlap the bottom tab bar // and swallow the initial tap on cold simulator runs. dismissSavePasswordPrompt() userTabButton.tap() } wait(forElement: app.navigationBars["User"], timeout: timeout) } func testAuth() { // Verify that Auth Example app launched successfully XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) } func testAuthAnonymously() { app.staticTexts["Anonymous Authentication"].tap() wait(forElement: app.navigationBars["User"], timeout: 5.0) XCTAssertTrue(app.navigationBars["User"].exists) let isAnonymousCell = app.cells.containing(.staticText, identifier: "Is User Anonymous?") .element XCTAssertTrue(isAnonymousCell.staticTexts["Yes"].exists, "The user should be anonymous") app.staticTexts["Sign Out"].tap() XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in after tapping Sign Out." ) } func testAuthExistingAccount() { // Setup existing user for duplicate test below. let existingEmail = "existing@test.com" let existingPassword = "existingPW" app.staticTexts["Email & Password Login"].tap() let testEmail = existingEmail app.textFields["Email"].tap() app.textFields["Email"].typeText(testEmail) let testPassword = existingPassword app.secureTextFields["Password"].tap() app.secureTextFields["Password"].typeText(testPassword) app.buttons["Login"].tap() wait(forElement: app.navigationBars["User"], timeout: 10.0) dismissSavePasswordPrompt() XCTAssertTrue(app.navigationBars["User"].exists) XCTAssertTrue( app.staticTexts[testEmail].exists, "The user should be signed in and the email field should display their email." ) } func testAuthExistingAccountWrongPassword() { app.staticTexts["Email & Password Login"].tap() let testEmail = "test@test.com" app.textFields["Email"].tap() app.textFields["Email"].typeText(testEmail) app.secureTextFields["Password"].tap() app.secureTextFields["Password"].typeText("wrong password") app.buttons["Login"].tap() wait(forElement: app.alerts.staticTexts["Error"], timeout: 10.0) XCTAssertTrue(app.alerts.staticTexts["Error"].exists) // Dismiss alert that password was incorrect, go back, and verify no user is signed in dismissErrorAlertAndPopToAuthMenu() navigateToUserTab() XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } func testCreateAccountBadPassword() { app.staticTexts["Email & Password Login"].tap() let testEmail = "test@test.com" app.textFields["Email"].tap() app.textFields["Email"].typeText(testEmail) app.secureTextFields["Password"].tap() // Enter an invalid password that is "too short" app.secureTextFields["Password"].typeText("2shrt") app.buttons["Create Account"].tap() wait(forElement: app.alerts.staticTexts["Error"], timeout: 10.0) XCTAssertTrue(app.alerts.staticTexts["Error"].exists) // Dismiss alert that password was incorrect, go back, and verify no user is signed in dismissErrorAlertAndPopToAuthMenu() navigateToUserTab() XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } func testCreateAlreadyExistingAccount() { app.staticTexts["Email & Password Login"].tap() let testEmail = "test@test.com" app.textFields["Email"].tap() app.textFields["Email"].typeText(testEmail) let testPassword = "test12" app.secureTextFields["Password"].tap() app.secureTextFields["Password"].typeText(testPassword) app.buttons["Create Account"].tap() wait(forElement: app.alerts.staticTexts["Error"], timeout: 10.0) XCTAssertTrue(app.alerts.staticTexts["Error"].exists) // Dismiss alert that password was incorrect, go back, and verify no user is signed in dismissErrorAlertAndPopToAuthMenu() navigateToUserTab() XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } func testCreateAccountCorrectPassword() { app.staticTexts["Email & Password Login"].tap() let newEmail = "\(Date().timeIntervalSince1970)_test@test.com" app.textFields["Email"].tap() app.typeText(newEmail) let newPassword = "new password" app.secureTextFields["Password"].tap() app.typeText(newPassword) app.buttons["Create Account"].tap() wait(forElement: app.navigationBars["User"], timeout: 10.0) dismissSavePasswordPrompt() XCTAssertTrue(app.navigationBars["User"].exists) XCTAssertTrue( app.staticTexts[newEmail].exists, "The user should be signed into the new account." ) } func DRAFT_testGoogleSignInAndLinkAccount() { let interruptionMonitor = addUIInterruptionMonitor(withDescription: "Sign in with Google") { alert -> Bool in alert.buttons["Continue"].tap() return true } app.staticTexts["Google"].tap() app.tap() // Triggers the UIInterruptionMonitor let testEmail = "" let testPassword = "" let firstTimeLogin = app.webViews.containing(.textField, identifier: "Email or phone") .element.exists if firstTimeLogin { app.webViews.textFields.firstMatch.tap() app.webViews.textFields.firstMatch.typeText(testEmail) app.buttons["Done"].tap() // Dismiss keyboard app.buttons["Next"].tap() // Transition to Google sign in password page app.webViews.secureTextFields.firstMatch.tap() app.webViews.secureTextFields.firstMatch.typeText(testPassword) app.buttons["Done"].tap() // Dismiss keyboard app.buttons["Next"].tap() // Complete sign in } else { app.webViews.staticTexts[testEmail].tap() } wait(forElement: app.navigationBars["User"], timeout: 5.0) XCTAssertTrue(app.navigationBars["User"].exists) XCTAssertTrue(app.staticTexts[testEmail].exists) // Cleanup removeUIInterruptionMonitor(interruptionMonitor) } func testEmailLinkSentSuccessfully() { app.staticTexts["Email Link/Passwordless"].tap() let testEmail = "test@test.com" app.textFields["Enter Authentication Email"].tap() app.textFields["Enter Authentication Email"].typeText(testEmail) app.buttons["return"].tap() // Dismiss keyboard app.buttons["Send Sign In Link"].tap() // Wait for the error message to appear (if there is an error) let errorAlert = app.alerts.staticTexts["Error"] let errorExists = errorAlert.waitForExistence(timeout: 5.0) let errorMessage = errorExists ? app.alerts.firstMatch.staticTexts.allElementsBoundByIndex.map(\.label) .joined(separator: " | ") : "Error" // Assert that there is no error message (success case) // The email sign in link is sent successfully if no error message appears XCTAssertFalse(errorExists, errorMessage) app.swipeDown(velocity: .fast) // Go back and check that there is no user that is signed in app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() wait(forElement: app.navigationBars["User"], timeout: 5.0) XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } func testResetPasswordLinkCustomDomain() { // assuming action type is in-app + continue URL everytime the app launches // set Authorized Domain as Continue URL let testContinueURL = "fir-ios-auth-sample.firebaseapp.com" app.staticTexts["Continue URL"].tap() app.alerts.textFields.element.typeText(testContinueURL) app.buttons["Save"].tap() // set Custom Hosting Domain as Link Domain let testLinkDomain = "http://firebaseiosauthsample.testdomaindonotuse.com" app.staticTexts["Link Domain"].tap() app.alerts.textFields.element.typeText(testLinkDomain) app.buttons["Save"].tap() app.staticTexts["Request Password Reset"].tap() let testEmail = "test@test.com" app.alerts.textFields.element.typeText(testEmail) app.buttons["Save"].tap() // Go back and check that there is no user that is signed in app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() wait(forElement: app.navigationBars["User"], timeout: 5.0) XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } func testResetPasswordLinkDefaultDomain() { // assuming action type is in-app + continue URL everytime the app launches // set Authorized Domain as Continue URL let testContinueURL = "fir-ios-auth-sample.firebaseapp.com" app.staticTexts["Continue URL"].tap() app.alerts.textFields.element.typeText(testContinueURL) app.buttons["Save"].tap() app.staticTexts["Request Password Reset"].tap() let testEmail = "test@test.com" app.alerts.textFields.element.typeText(testEmail) app.buttons["Save"].tap() // Go back and check that there is no user that is signed in app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() wait(forElement: app.navigationBars["User"], timeout: 5.0) XCTAssertEqual( app.cells.count, 0, "The user shouldn't be signed in and the user view should have no cells." ) } } extension XCTestCase { func wait(forElement element: XCUIElement, timeout: TimeInterval) { let predicate = NSPredicate(format: "exists == 1") expectation(for: predicate, evaluatedWith: element) waitForExpectations(timeout: timeout) } }