Skip to content

Commit

Permalink
feat(feedback): form ui iteration and tests (#4536)
Browse files Browse the repository at this point in the history
  • Loading branch information
armcknight authored Dec 7, 2024
1 parent 963b49c commit eba61d3
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 62 deletions.
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

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("andrew.mcknight@sentry.io")

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), "your.email@example.org")

// 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 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("andrew.mcknight@sentry.io")

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

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

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

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("andrew.mcknight@sentry.io")

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), "your.email@example.org")

// 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("andrew.mcknight@sentry.io")

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), "your.email@example.org")

// 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"]
}
}

//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

0 comments on commit eba61d3

Please sign in to comment.