Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feedback): form ui iteration and tests #4536

Merged
merged 21 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions Samples/iOS-Swift/iOS-Swift-UITests/UserFeedbackUITests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//swiftlint:disable todo
armcknight marked this conversation as resolved.
Show resolved Hide resolved

import XCTest

class UserFeedbackUITests: BaseUITest {
override var automaticallyLaunchAndTerminateApp: Bool { false }

override func setUp() {
super.setUp()
app.launchArguments.append(contentsOf: [
"--io.sentry.iOS-Swift.auto-inject-user-feedback-widget",
"--io.sentry.iOS-Swift.user-feedback.all-defaults",
"--io.sentry.feedback.no-animations"
])
launchApp()
}

func testSubmitFullyFilledForm() throws {
widgetButton.tap()

nameField.tap()
nameField.typeText("Andrew")

emailField.tap()
emailField.typeText("[email protected]")

messageTextView.tap()
messageTextView.typeText("UITest user feedback")

app.staticTexts["Send Bug Report"].tap()

// displaying the form again ensures the widget button still works afterwards; also assert that the fields are in their default state to ensure the entered data is not persisted between displays

widgetButton.tap()

// the placeholder text is returned for XCUIElement.value
XCTAssertEqual(try XCTUnwrap(nameField.value as? String), "Your Name")
XCTAssertEqual(try XCTUnwrap(emailField.value as? String), "[email protected]")

// the UITextView doesn't hav a placeholder, it's a label on top of it. so it is actually empty
XCTAssertEqual(try XCTUnwrap(messageTextView.value as? String), "")
armcknight marked this conversation as resolved.
Show resolved Hide resolved
}

func testSubmitWithNoFieldsFilled() throws {
widgetButton.tap()

app.staticTexts["Send Bug Report"].tap()

XCTAssert(app.staticTexts["Error"].exists)

app.buttons["OK"].tap()
}

func testSubmitWithOnlyRequiredFieldsFilled() {
widgetButton.tap()

messageTextView.tap()
messageTextView.typeText("UITest user feedback")

app.staticTexts["Send Bug Report"].tap()

XCTAssert(widgetButton.waitForExistence(timeout: 1))
}

func testSubmitOnlyWithOptionalFieldsFilled() throws {
widgetButton.tap()

nameField.tap()
nameField.typeText("Andrew")

emailField.tap()
emailField.typeText("[email protected]")

app.staticTexts["Send Bug Report"].tap()

XCTAssert(app.staticTexts["Error"].exists)

app.buttons["OK"].tap()
armcknight marked this conversation as resolved.
Show resolved Hide resolved
}

func testCancelFromFormByButton() {
widgetButton.tap()

// fill out the fields; we'll assert later that the entered data does not reappear on subsequent displays
nameField.tap()
nameField.typeText("Andrew")

emailField.tap()
emailField.typeText("[email protected]")

messageTextView.tap()
messageTextView.typeText("UITest user feedback")

let cancelButton: XCUIElement = app.staticTexts["Cancel"]
cancelButton.tap()

// displaying the form again ensures the widget button still works afterwards; also assert that the fields are in their default state to ensure the entered data is not persisted between displays

widgetButton.tap()

// the placeholder text is returned for XCUIElement.value
XCTAssertEqual(try XCTUnwrap(nameField.value as? String), "Your Name")
XCTAssertEqual(try XCTUnwrap(emailField.value as? String), "[email protected]")

// the UITextView doesn't hav a placeholder, it's a label on top of it. so it is actually empty
XCTAssertEqual(try XCTUnwrap(messageTextView.value as? String), "")
}

func testCancelFromFormBySwipeDown() {
widgetButton.tap()

// fill out the fields; we'll assert later that the entered data does not reappear on subsequent displays
nameField.tap()
nameField.typeText("Andrew")

emailField.tap()
emailField.typeText("[email protected]")

messageTextView.tap()
messageTextView.typeText("UITest user feedback")

// the cancel gesture
app.swipeDown(velocity: .fast)
app.swipeDown(velocity: .fast)

// the swipe dismiss animation takes an extra moment, so we need to wait for the widget to be visible again
XCTAssert(widgetButton.waitForExistence(timeout: 1))

// displaying the form again ensures the widget button still works afterwards; also assert that the fields are in their default state to ensure the entered data is not persisted between displays

widgetButton.tap()

// the placeholder text is returned for XCUIElement.value
XCTAssertEqual(try XCTUnwrap(nameField.value as? String), "Your Name")
XCTAssertEqual(try XCTUnwrap(emailField.value as? String), "[email protected]")

// the UITextView doesn't hav a placeholder, it's a label on top of it. so it is actually empty
XCTAssertEqual(try XCTUnwrap(messageTextView.value as? String), "")
}

func testAddingAndRemovingScreenshots() {
widgetButton.tap()
addScreenshotButton.tap()
XCTAssert(removeScreenshotButton.isHittable)
XCTAssertFalse(addScreenshotButton.isHittable)
removeScreenshotButton.tap()
XCTAssert(addScreenshotButton.isHittable)
XCTAssertFalse(removeScreenshotButton.isHittable)
}

// MARK: Private

var widgetButton: XCUIElement {
app.otherElements["io.sentry.feedback.widget"]
}

var nameField: XCUIElement {
app.textFields["io.sentry.feedback.form.name"]
}

var emailField: XCUIElement {
app.textFields["io.sentry.feedback.form.email"]
}

var messageTextView: XCUIElement {
app.textViews["io.sentry.feedback.form.message"]
}

var addScreenshotButton: XCUIElement {
app.buttons["io.sentry.feedback.form.add-screenshot"]
}

var removeScreenshotButton: XCUIElement {
app.buttons["io.sentry.feedback.form.remove-screenshot"]
}
}
armcknight marked this conversation as resolved.
Show resolved Hide resolved

//swiftlint:enable todo
6 changes: 6 additions & 0 deletions Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
84BA72DE2C9391920045B828 /* GitInjections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA72A52C93698E0045B828 /* GitInjections.swift */; };
84BE546F287503F100ACC735 /* SentrySDKPerformanceBenchmarkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84BE546E287503F100ACC735 /* SentrySDKPerformanceBenchmarkTests.m */; };
84BE547E287645B900ACC735 /* SentryProcessInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 84BE54792876451D00ACC735 /* SentryProcessInfo.m */; };
84DBC6252CE6D321000C4904 /* UserFeedbackUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DBC61F2CE6D31C000C4904 /* UserFeedbackUITests.swift */; };
84FB812A284001B800F3A94A /* SentryBenchmarking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84FB8129284001B800F3A94A /* SentryBenchmarking.mm */; };
84FB812B284001B800F3A94A /* SentryBenchmarking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84FB8129284001B800F3A94A /* SentryBenchmarking.mm */; };
8E8C57AF25EF16E6001CEEFA /* TraceTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8C57AE25EF16E6001CEEFA /* TraceTestViewController.swift */; };
Expand Down Expand Up @@ -288,6 +289,7 @@
84BE546E287503F100ACC735 /* SentrySDKPerformanceBenchmarkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySDKPerformanceBenchmarkTests.m; sourceTree = "<group>"; };
84BE54782876451D00ACC735 /* SentryProcessInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryProcessInfo.h; sourceTree = "<group>"; };
84BE54792876451D00ACC735 /* SentryProcessInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryProcessInfo.m; sourceTree = "<group>"; };
84DBC61F2CE6D31C000C4904 /* UserFeedbackUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFeedbackUITests.swift; sourceTree = "<group>"; };
84FB8125284001B800F3A94A /* SentryBenchmarking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryBenchmarking.h; sourceTree = "<group>"; };
84FB8129284001B800F3A94A /* SentryBenchmarking.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryBenchmarking.mm; sourceTree = "<group>"; };
84FB812C2840021B00F3A94A /* iOS-Swift-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iOS-Swift-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -505,6 +507,7 @@
D8C33E2529FBB8D90071B75A /* UIEventBreadcrumbTests.swift */,
84A5D72C29D2708D00388BFA /* UITestHelpers.swift */,
84A5D72529D2705000388BFA /* ProfilingUITests.swift */,
84DBC61F2CE6D31C000C4904 /* UserFeedbackUITests.swift */,
84B527B728DD24BA00475E8D /* SentryDeviceTests.mm */,
84B527BB28DD25E400475E8D /* SentryDevice.h */,
84B527BC28DD25E400475E8D /* SentryDevice.mm */,
Expand Down Expand Up @@ -1144,6 +1147,7 @@
62C07D5C2AF3E3F500894688 /* BaseUITest.swift in Sources */,
84A5D72629D2705000388BFA /* ProfilingUITests.swift in Sources */,
84A5D72D29D2708D00388BFA /* UITestHelpers.swift in Sources */,
84DBC6252CE6D321000C4904 /* UserFeedbackUITests.swift in Sources */,
84B527B928DD24BA00475E8D /* SentryDeviceTests.mm in Sources */,
7B64386B26A6C544000D0F65 /* LaunchUITests.swift in Sources */,
84B527BD28DD25E400475E8D /* SentryDevice.mm in Sources */,
Expand Down Expand Up @@ -1657,6 +1661,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = TEST;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VALIDATE_PRODUCT = YES;
Expand Down Expand Up @@ -1895,6 +1900,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = TESTCI;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VALIDATE_PRODUCT = YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
argument = "--disable-file-io-tracing"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--io.sentry.feedback.no-animations"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--io.sentry.iOS-Swift.user-feedback.no-widget-icon"
isEnabled = "NO">
Expand Down
2 changes: 2 additions & 0 deletions Samples/iOS-Swift/iOS-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
return
}
config.animations = !args.contains("--io.sentry.feedback.no-animations")
config.useShakeGesture = true
config.showFormForScreenshots = true
config.configureWidget = { widget in
Expand Down Expand Up @@ -200,6 +201,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
config.configureForm = { uiForm in
uiForm.formTitle = "Jank Report"
uiForm.isEmailRequired = true
uiForm.submitButtonLabel = "Report that jank"
uiForm.addScreenshotButtonLabel = "Show us the jank"
uiForm.messagePlaceholder = "Describe the nature of the jank. Its essence, if you will."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import UIKit
@available(iOS 13.0, *)
@objcMembers
public class SentryUserFeedbackConfiguration: NSObject {
/**
* Whether or not to show animations, like for presenting and dismissing the form.
* - note: Default: `true`.
*/
public var animations: Bool = true

/**
* Configuration settings specific to the managed widget that displays the UI form.
* - note: Default: `nil` to use the default widget settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class SentryUserFeedbackFormConfiguration: NSObject {
* The label of the button to add a screenshot to the form.
* - note: Default: `"Add a screenshot"`
* - note: ignored if `enableScreenshot` is `false`.`
* - warning: If you support adding screenshots using the button, you need to add `NSPhotoLibraryUsageDescription` to your app's Info.plist.
*/
public var addScreenshotButtonLabel: String = "Add a screenshot"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ public class SentryUserFeedbackWidgetConfiguration: NSObject {
*/
public var autoInject: Bool = true

/**
* Whether or not to show animations, like for presenting and dismissing the form.
* - note: Default: `true`.
*/
public var animations: Bool = true

let defaultLabelText = "Report a Bug"

/**
Expand Down
Loading
Loading