Source: Google Today I’m trying to run some UITest on my app, which uses Facebook login. And here are some of my notes on it. Challenges The challenges with Facebook is it uses , we we deal mostly with for now. Starting from iOS 9+, Facebook decided to use instead of to avoid app switching. You can read the detail here Safari controller web view safari native facebook app Building the Best Facebook Login Experience for People on iOS 9 It does not have wanted or accessibilityIdentifier accessibilityLabel The webview content may change in the future 😸 Create a Facebook test user Luckily, you don’t have to create your own Facebook user to test. Facebook supports test users that you can manage permissions and friends, very handy When creating the test user, you have the option to select language. That will be the displayed language in Safari web view. I choose 🇳🇴 for now Norwegian Click the login button and show Facebook login Here we use the default FBSDKLoginButton var showFacebookLoginFormButton: XCUIElement {return buttons["Continue with Facebook"]} And then tap it app.showFacebookLoginFormButton.tap() Check login status When going to safari Facebook form, user may have already logged in or not. So we need to handle these 2 cases. When user has logged in, Facebook will say something like “you have already logged in” or the button. OK The advice here is to put breakpoint and , to see which UI elements are at a certain point. po app.staticTexts po app.buttons You can check for the static text, or simply just the button OK var isAlreadyLoggedInSafari: Bool {return buttons["OK"].exists || staticTexts["Du har allerede godkjent Blue Sea."].exists} Wait and refresh But Facebook form is a webview, so its content is a bit dynamic. And UITest seems to cache content for fast query, so before checking , we need to and staticTexts wait refresh the cache app.clearCachedStaticTexts() This is the function wait extension XCTestCase {func wait(for duration: TimeInterval) {let waitExpectation = expectation(description: "Waiting") let when = DispatchTime.now() + duration DispatchQueue.main.asyncAfter(deadline: when) { waitExpectation.fulfill() } // We use a buffer here to avoid flakiness with Timer on CI waitForExpectations(timeout: duration + 0.5) }} Wait for element to appear But a more solid approach would be to wait for element to appear. For Facebook login form, they should display a label after loading. So we should wait for this element Facebook extension XCTestCase {/// Wait for element to appearfunc wait(for element: XCUIElement, timeout duration: TimeInterval) {let predicate = NSPredicate(format: "exists == true")let _ = expectation(for: predicate, evaluatedWith: element, handler: nil) // Here we don't need to call \`waitExpectation.fulfill()\` // We use a buffer here to avoid flakiness with Timer on CI waitForExpectations(timeout: duration + 0.5) }} And call this before you do any further inspection on elements in Facebook login form wait(for: app.staticTexts["Facebook"], timeout: 5) If user is logged in After login, my app shows the main controller with a map view inside. So a basic test would be to check the existence of that map if app.isAlreadyLoggedInSafari {app.okButton.tap() handleLocationPermission()// Check for the mapXCTAssertTrue(app.maps.element(boundBy: 0).exists)} Handle interruption You know that when showing the map with location, will ask for permission. So we need to handle that interruption as well. You need to ensure to call it early before the alert happens Core Location fileprivate func handleLocationPermission() {addUIInterruptionMonitor(withDescription: "Location permission", handler: { alert inalert.buttons.element(boundBy: 1).tap()return true})} There is another problem, this won't be called. So the workaround is to call again when the alert will happen. In my case, I call when my has been shown for 1,2 seconds, just to make sure is called after alert is shown monitor app.tap() app.tap() map app.tap() For a more detailed guide, please read #48 If user is not logged in In this case, we need to fill in email and password. You can take a look at the section below. When things don't work or does not show you the elements you needed, it's probably because of caching or you need to wait until dynamic content finishes rendering. The full source code po You need to wait for element to appear Tap on the text field You may get , here are the workaround Neither element nor any descendant has keyboard focus If you test on Simulator, make sure is not checked Simulator -> Hardware -> Keyboard -> Connect Hardware Keyboard a bit after tap wait app.emailTextField.tap() Clear all the text The idea is to move the caret to the end of the textField, then apply each for each character, then type the next text delete key extension XCUIElement {func deleteAllText() {guard let string = value as? String else {return} let lowerRightCorner = coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9)) lowerRightCorner.tap() let deletes = string.characters.map({ \_ in XCUIKeyboardKeyDelete }).joined(separator: "") typeText(deletes) }} Change language For my case, I want to test in Norwegian, so we need to find the option and tap on that. It is identified as by Norwegian static text UI Test var norwegianText: XCUIElement {return staticTexts["Norsk (bokmål)"]} wait(for: app.norwegianText, timeout: 1)app.norwegianText.tap() The email text field Luckily, email text field is detected by as element, so we can query for that. This uses predicate UI Test text field var emailTextField: XCUIElement {let predicate = NSPredicate(format: "placeholderValue == %@", "E-post eller mobil")return textFields.element(matching: predicate)} The password text field can't seem to identify the password text field, so we need to search for it by UI Test coordinate var passwordCoordinate: XCUICoordinate {let vector = CGVector(dx: 1, dy: 1.5)return emailTextField.coordinate(withNormalizedOffset: vector)} This is the document for func coordinate(withNormalizedOffset normalizedOffset: CGVector) -> XCUICoordinate Creates and returns a new coordinate with a normalized offset.The coordinate’s screen point is computed by adding normalizedOffset multiplied by the size of the element’s frame to the origin of the element’s frame. Then type the password app.passwordCoordinate.tap()app.typeText("My password") We should not use because it will point to email text field ❗️ 😢 app.passwordCoordinate.referencedElement Run that test again Go to to run the previous test again Xcode -> Product -> Perform Actions -> Test Again Here are the full source code One more thing Thanks to the helpful feedback on my article Original story , here are some more ideas https://github.com/onmyway133/blog/issues/44 To look for password text fields, we can actually use instead of coordinate secureTextFields The function should be made as an extension to so other element can use that. Or you can just use the old style, which does not involve a hardcoded interval value. wait XCUIElement expectation Where to go from here I found these guides to cover many aspects of UITests, worth taking a look UI-Testing-Cheat-Sheet Everything About Xcode UI Testing