From 34d7cd1cd4a58e7673373f82b90a490d5664769c Mon Sep 17 00:00:00 2001 From: Vladimir Espinola Date: Thu, 23 May 2024 15:34:38 -0400 Subject: [PATCH] updated UI Test cases --- .github/workflows/ci.yml | 18 ++++++ .../CreativeUITest-Bridging-Header.h | 4 ++ Example/CreativeUITest/CreativeUITest.m | 60 ++++++++++--------- .../XCUIElement+Extension.swift | 30 ++++++++++ Example/Example.xcodeproj/project.pbxproj | 14 +++++ .../xcschemes/CreativeUITest.xcscheme | 55 +++++++++++++++++ 6 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 Example/CreativeUITest/CreativeUITest-Bridging-Header.h create mode 100644 Example/CreativeUITest/XCUIElement+Extension.swift create mode 100644 Example/Example.xcodeproj/xcshareddata/xcschemes/CreativeUITest.xcscheme diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2c39f0..2c04405 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,12 @@ on: push: branches: - main + - develop - feature/* pull_request: branches: - main + - develop - feature/* workflow_dispatch: @@ -26,6 +28,22 @@ jobs: - name: "Run tests" run: xcodebuild -project "attentive-ios-sdk.xcodeproj" -scheme "attentive-ios-sdk-framework" -destination "platform=iOS Simulator,OS=16.1,name=iPhone 13 Pro" test + ui-tests: + name: Run iOS UI Tests + runs-on: macOS-14 + env: + DEVELOPER_DIR: "/Applications/Xcode_15.3.app/Contents/Developer" + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: "Run UI Tests" + run: xcodebuild -workspace "attentive-ios-sdk.xcworkspace" -scheme "CreativeUITest" -destination "platform=iOS Simulator,OS=17.4,name=iPhone 15 Pro" -derivedDataPath build/ -resultBundlePath ui-test-results.xcresult test | xcpretty + - name: Upload Test Results + uses: actions/upload-artifact@v3 + with: + name: ui-test-results + path: ui-test-results.xcresult lint: name: Run lint runs-on: ubuntu-20.04 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..5b8d79e 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,49 @@ + (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]; + [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]); + + // Close Info Screen when SMS App is opened for the first time + if ([smsApp.buttons[@"OK"] elementExists]) { + [smsApp.buttons[@"OK"] tapOnElement]; + } + + 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 +89,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 +115,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..2d9ac61 --- /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("\(description) 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 de4bbfb..7e91fc7 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 69CCAF5F28DCDE5A007620BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69CCAF5D28DCDE5A007620BD /* LaunchScreen.storyboard */; }; 69CCAF6228DCDE5A007620BD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 69CCAF6128DCDE5A007620BD /* main.m */; }; 69D3C14C299EF2D10027934F /* CreativeUITest.m in Sources */ = {isa = PBXBuildFile; fileRef = 69D3C14B299EF2D10027934F /* CreativeUITest.m */; }; + FB51061A2BFE4A9700D9A72D /* XCUIElement+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB5106192BFE4A9700D9A72D /* XCUIElement+Extension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,6 +77,8 @@ 69CCAF6128DCDE5A007620BD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 69D3C149299EF2D10027934F /* CreativeUITest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CreativeUITest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 69D3C14B299EF2D10027934F /* CreativeUITest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CreativeUITest.m; 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -168,6 +171,8 @@ isa = PBXGroup; children = ( 69D3C14B299EF2D10027934F /* CreativeUITest.m */, + FB5106192BFE4A9700D9A72D /* XCUIElement+Extension.swift */, + FB5106182BFE4A9700D9A72D /* CreativeUITest-Bridging-Header.h */, ); path = CreativeUITest; sourceTree = ""; @@ -249,6 +254,7 @@ }; 69D3C148299EF2D10027934F = { CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1530; TestTargetID = 58317964292EEEAA0003D6B0; }; }; @@ -335,6 +341,7 @@ buildActionMask = 2147483647; files = ( 69D3C14C299EF2D10027934F /* CreativeUITest.m in Sources */, + FB51061A2BFE4A9700D9A72D /* XCUIElement+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -604,6 +611,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*]" = "-"; @@ -632,6 +640,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"; }; @@ -641,6 +652,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*]" = "-"; @@ -669,6 +681,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"; }; diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/CreativeUITest.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/CreativeUITest.xcscheme new file mode 100644 index 0000000..c977179 --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/CreativeUITest.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + +