diff --git a/Example/CreativeUITest/CreativeUITest-Bridging-Header.h b/Example/CreativeUITest/CreativeUITest-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/Example/CreativeUITest/CreativeUITest-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Example/CreativeUITest/CreativeUITest.m b/Example/CreativeUITest/CreativeUITest.m index b18744e..7225eb1 100644 --- a/Example/CreativeUITest/CreativeUITest.m +++ b/Example/CreativeUITest/CreativeUITest.m @@ -9,6 +9,7 @@ #import #import #import +#import "CreativeUITest-Swift.h" @interface CreativeUITest : XCTestCase @@ -38,46 +39,44 @@ + (void)tearDown { - (void)testLoadCreative_clickClose_closesCreative { [self launchAppWithMode:@"production"]; - [app.buttons[@"Push me for Creative!"] tap]; + [app.buttons[@"Push me for Creative!"] tapOnElement]; + + sleep(2); // Close the creative - XCTAssertTrue([app.webViews.buttons[@"Dismiss this popup"] waitForExistenceWithTimeout:5.0]); - [app.webViews.buttons[@"Dismiss this popup"] tap]; + [app.webViews.buttons[@"Dismiss this popup"] tapOnElement]; // Assert that the creative is closed - XCTAssertTrue([app.buttons[@"Push me for Creative!"] waitForExistenceWithTimeout:5.0]); + XCTAssertTrue([app.buttons[@"Push me for Creative!"] elementExists]); XCTAssertEqual(app.buttons[@"Push me for Creative!"].isHittable, true); } - (void)testLoadCreative_fillOutFormAndSubmit_launchesSmsAppWithPrePopulatedText { [self launchAppWithMode:@"production"]; - [app.buttons[@"Push me to clear the User!"] tap]; - [app.buttons[@"Push me for Creative!"] tap]; + [app.buttons[@"Push me to clear the User!"] tapOnElement]; + [app.buttons[@"Push me for Creative!"] tapOnElement]; // Fill in the email - XCTAssertTrue([app.webViews.textFields[@"Email Address"] waitForExistenceWithTimeout:5.0]); XCUIElement *emailField = app.webViews.textFields[@"Email Address"]; - [emailField tap]; + [emailField tapOnElement]; [emailField typeText:@"testemail@attentivemobile.com"]; // Tap something else on the creative to dismiss the keyboard - [app.staticTexts[@"10% OFF"] tap]; + [app.staticTexts[@"10% OFF"] tapOnElement]; // Submit email - XCTAssertTrue([app.buttons[@"CONTINUE"] waitForExistenceWithTimeout:5.0]); - [app.webViews.buttons[@"CONTINUE"] tap]; + [app.webViews.buttons[@"CONTINUE"] tapOnElement]; - // Click subscribe button - XCTAssertTrue([app.buttons[@"GET 10% OFF NOW when you sign up for email and texts"] waitForExistenceWithTimeout:5.0]); - [app.buttons[@"GET 10% OFF NOW when you sign up for email and texts"] tap]; + // Click subscribe button waitForExistenceWithTimeout:5.0]); + [app.buttons[@"GET 10% OFF NOW when you sign up for email and texts"] tapOnElement]; // Assert that the SMS app is opened with prepopulated text if running locally // (AWS Device Farm doesn't allow use of SMS apps) NSString *envHost = [[[NSProcessInfo processInfo] environment] objectForKey:@"COM_ATTENTIVE_EXAMPLE_HOST"]; if ([envHost isEqualToString:@"local"]) { XCUIApplication *smsApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.MobileSMS"]; - XCTAssertTrue([smsApp.textFields[@"Message"] waitForExistenceWithTimeout:5.0]); + XCTAssertTrue([smsApp.textFields[@"Message"] elementExists]); XCTAssertTrue([smsApp.textFields[@"Message"].value containsString:@"Send this text to subscribe to recurring automated personalized marketing alerts (e.g. cart reminders) from Attentive Mobile Apps Test"]); } } @@ -85,25 +84,24 @@ - (void)testLoadCreative_fillOutFormAndSubmit_launchesSmsAppWithPrePopulatedText - (void)testLoadCreative_clickPrivacyLink_opensPrivacyPageInWebApp { [self launchAppWithMode:@"production"]; - [app.buttons[@"Push me for Creative!"] tap]; + [app.buttons[@"Push me for Creative!"] tapOnElement]; // Click privacy link - XCTAssertTrue([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]); - [app.webViews.links[@"Privacy"] tap]; + [app.webViews.links[@"Privacy"] tapOnElement]; // Wait for a moment to allow the app to react sleep(5); // Check if the app is no longer active - XCTAssertFalse([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]); - + XCTAssertFalse([app.webViews.links[@"Privacy"] elementExists]); + // AWS Device Farm doesn't always acknowledge separate apps, leading to flakiness here NSString *envHost = [[[NSProcessInfo processInfo] environment] objectForKey:@"COM_ATTENTIVE_EXAMPLE_HOST"]; if ([envHost isEqualToString:@"local"]) { // Verify that the privacy page is visible in the external browser XCUIApplication *safariApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.mobilesafari"]; - BOOL privacyPolicyExists = [safariApp.staticTexts[@"Privacy Policy"] waitForExistenceWithTimeout:5.0]; - BOOL messagingPrivacyPolicyExists = [safariApp.staticTexts[@"Messaging Privacy Policy"] waitForExistenceWithTimeout:5.0]; + BOOL privacyPolicyExists = [safariApp.staticTexts[@"Privacy Policy"] elementExists]; + BOOL messagingPrivacyPolicyExists = [safariApp.staticTexts[@"Messaging Privacy Policy"] elementExists]; XCTAssertTrue(privacyPolicyExists || messagingPrivacyPolicyExists); } @@ -112,26 +110,25 @@ - (void)testLoadCreative_clickPrivacyLink_opensPrivacyPageInWebApp { - (void)testLoadCreative_inDebugMode_showsDebugMessage { [self launchAppWithMode:@"debug"]; - [app.buttons[@"Push me for Creative!"] tap]; + [app.buttons[@"Push me for Creative!"] tapOnElement]; // Verify debug page shows - XCTAssertTrue([app.staticTexts[@"Debug output JSON"] waitForExistenceWithTimeout:5.0]); + XCTAssertTrue([app.staticTexts[@"Debug output JSON"] elementExists]); } - (void)testLoadCreative_clickProductPage_closesCreative { [self launchAppWithMode:@"production"]; - [app.buttons[@"Push me for Creative!"] tap]; + [app.buttons[@"Push me for Creative!"] tapOnElement]; // Click privacy link - XCTAssertTrue([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]); - [app.tabBars.buttons[@"Product"] tap]; + [app.tabBars.buttons[@"Product"] tapOnElement]; // Verify that the product page is visible - XCTAssertTrue([app.buttons[@"Add To Cart"] waitForExistenceWithTimeout:5.0]); + XCTAssertTrue([app.buttons[@"Add To Cart"] elementExists]); // Nav back, and verify the creative is closed - [app.tabBars.buttons[@"Main"] tap]; - XCTAssertTrue([app.buttons[@"Push me for Creative!"] waitForExistenceWithTimeout:5.0]); + [app.tabBars.buttons[@"Main"] tapOnElement]; + XCTAssertTrue([app.buttons[@"Push me for Creative!"] elementExists]); } diff --git a/Example/CreativeUITest/XCUIElement+Extension.swift b/Example/CreativeUITest/XCUIElement+Extension.swift new file mode 100644 index 0000000..25a34ae --- /dev/null +++ b/Example/CreativeUITest/XCUIElement+Extension.swift @@ -0,0 +1,30 @@ +// +// XCUIElement+Extension.swift +// CreativeUITest +// +// Created by Vladimir - Work on 2024-05-22. +// + +import XCTest + +extension XCUIElement { + /// Verify element existence and then proceed with tapping on it + @objc func tapOnElement() { + guard elementExists() else { + XCTFail("Element with \(identifier) and type \(elementType) does not exists") + return + } + tap() + } + + /// Verify element existence on the app view hierarchy + @objc func elementExists() -> Bool { + elementExists(timeout: 5) + } +} + +fileprivate extension XCUIElement { + func elementExists(timeout: TimeInterval) -> Bool { + waitForExistence(timeout: timeout) + } +} diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index d56e261..106866c 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 69CCAF6228DCDE5A007620BD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 69CCAF6128DCDE5A007620BD /* main.m */; }; 69D3C14C299EF2D10027934F /* CreativeUITest.m in Sources */ = {isa = PBXBuildFile; fileRef = 69D3C14B299EF2D10027934F /* CreativeUITest.m */; }; C77C85E38A76EFB4A30A63ED /* Pods_Example___Pod.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E225173AF4DB16299D85458 /* Pods_Example___Pod.framework */; }; + FB51061A2BFE4A9700D9A72D /* XCUIElement+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB5106192BFE4A9700D9A72D /* XCUIElement+Extension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -81,6 +82,8 @@ 6F479792BC83079236F72CF1 /* Pods-Example - Pod.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example - Pod.debug.xcconfig"; path = "Target Support Files/Pods-Example - Pod/Pods-Example - Pod.debug.xcconfig"; sourceTree = ""; }; 7B8828B08D7E2DB6703354E8 /* Pods-Example - Pod.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example - Pod.release.xcconfig"; path = "Target Support Files/Pods-Example - Pod/Pods-Example - Pod.release.xcconfig"; sourceTree = ""; }; A6F5932642D2BF98834A4354 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; + FB5106182BFE4A9700D9A72D /* CreativeUITest-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CreativeUITest-Bridging-Header.h"; sourceTree = ""; }; + FB5106192BFE4A9700D9A72D /* XCUIElement+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extension.swift"; sourceTree = ""; }; FDEDF14001E311BED132C759 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -176,6 +179,8 @@ isa = PBXGroup; children = ( 69D3C14B299EF2D10027934F /* CreativeUITest.m */, + FB5106192BFE4A9700D9A72D /* XCUIElement+Extension.swift */, + FB5106182BFE4A9700D9A72D /* CreativeUITest-Bridging-Header.h */, ); path = CreativeUITest; sourceTree = ""; @@ -263,6 +268,7 @@ }; 69D3C148299EF2D10027934F = { CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1530; TestTargetID = 58317964292EEEAA0003D6B0; }; }; @@ -391,6 +397,7 @@ buildActionMask = 2147483647; files = ( 69D3C14C299EF2D10027934F /* CreativeUITest.m in Sources */, + FB51061A2BFE4A9700D9A72D /* XCUIElement+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -662,6 +669,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; @@ -690,6 +698,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "CreativeUITest/CreativeUITest-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "Example - Local"; }; @@ -699,6 +710,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; @@ -727,6 +739,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "CreativeUITest/CreativeUITest-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "Example - Local"; };