Skip to content

Use traces to validate UX flows, analytics, or event buses

License

Notifications You must be signed in to change notification settings

KeepSafe/Tracer-iOS

Repository files navigation

Tracer

CircleCI Apache 2.0 licensed CocoaPods Swift 4+ iOS

What it does

Tracer acts as an in-memory log which can run time-scoped traces to validate UX flows, analytic events, or any other kind of serial event bus.

For example, if you're expecting EventOne, EventTwo and EventThree to be fired during a sign-up flow, Tracer can help your development or QA team validate those analytics don't break at some point.

Anything that can be represented as Equatable can be logged and validated during a trace. Common examples of this would be strings, arrays or dictionaries (especially with conditional conformance in Swift 4.1) but you can also pass in your own custom structs or classes as long as they are Equatable.

You can either run the traces manually, using the built-in UI that floats over top of your app, or have important traces run automatically during unit or UI tests.

Example UI Usage

See example gifs below or see the Examples folder for demonstrations of using it.

API Usage

Or jump into the API section below if you're ready to implement.

Installation

Quickly install using CocoaPods:

pod 'Tracer/UI'

# or optionally don't include the UI component
pod 'Tracer/Core'

Or Carthage:

github "KeepSafe/Tracer-iOS"

Or manually install it

Examples

Running a trace

After you tell the trace tool about your traces, it will list them all and enable you or your QA team to start one manually.

Here's a simple example of a trace where order matters:

passinginorder

And here's the same trace failing when an event is fired out of order (in this case, it fails all events before it that haven't already been matched since they would also be considered out-of-order).

failingoutoforder

Move it around

You can expand or collapse the UI at any time, or even move it around the screen or drag the top of it to resize how much screen real estate it covers when expanded.

moveitaround

Logger

Logging uses the debugDescription of each item to display it on screen, so be sure your more complex data structures implement this for how you'd like to show it.

tracetool

Traces

Traces have multiple options like enforceOrder, allowDuplicates or assertOnFailure that you can configure for specific scenarios. You can even perform programmatic setup steps (like resetting your app to a certain configuration) before each trace is started or list optional setup steps for someone to do so manually.

Example 1: Order matters, duplicates don't

configone

(passing/failing this trace was shown in the first example section above)

Example 2: No duplicates, order doesn't matter

configtwo

Order doesn't matter for this trace, so passing it would look like:

passingoutoforder

But, duplicates do matter so if we fired any during the trace it would fail:

failingduplicate

Custom Settings

Optionally, you can also add your own custom settings to the trace tool (or even show custom emojis for a logged item):

customsettings

API

UI

If you're using the UI frontend, you'll be interacting directly through TraceUI.swift. See the TraceUIExample.

let traceUI = TraceUI()
traceUI.show()

You can also toggle this to .hide() via a debug setting if you'd like.

Note: If you're starting this tool in your AppDelegate, you should lazily load it after your app window loads to ensure the tool starts properly. The best thing to do is listen for .UIWindowDidBecomeKey and only show it after that happens.

Configuration

Load traces into the UI using:

traceUI.add(traces: [MyTraces.traceOne, MyTraces.traceTwo])

Logging & Validation

If you're logging an item specifically to be validated against for a trace, use:

traceUI.log(traceItem: Event.three.toTraceItem)

Here we're just creating an Event enum with the events we're firing within the app and then using the .toTraceItem property to transform it:

enum Event: String {
    case one
    case two
    case three
    
    var uxFlowHint: String {
        switch self {
        case .one: return "Press the 'Fire event 1' button"
        case .two: return "Press the 'Fire event 2' button"
        case .three: return "Press the 'Fire event 3' button"
        }
    }
    
    var toTraceItem: TraceItem {
        return TraceItem(type: "event", itemToMatch: AnyTraceEquatable(self), uxFlowHint: uxFlowHint)
    }
}

Otherwise, you can also generically log (without it validating against an ongoing trace) by using:

traceUI.log(genericItem: AnyTraceEquatable("Moooooooooo"), emojiToPrepend: "🐄")

All Logging

You can see all logging functions and their documentation in TraceUI.swift:

public func log(traceItem: TraceItem, verboseLog: AnyTraceEquatable? = nil, emojiToPrepend: String? = "⚡️") {}

public func logVerbose(traceItem: TraceItem, emojiToPrepend: String? = "⚡️") { }

public func log(genericItem: AnyTraceEquatable, properties: LoggedItemProperties? = nil, emojiToPrepend: String? = "⚡️") { }

Programmatic API

If you'd rather not use the built-in UI frontend, you can set up your traces to run manually, such as during unit or UI tests. See the AnalyticsTraceExample.

Feel free to have a look through the unit tests for examples.

Creating and starting a trace

// This is an individual item to match and could be in multiple traces
let answerTraceItem = TraceItem(type: "The answer to the universe", itemToMatch: AnyTraceEquatable(42))
// This is a trace with an array of items it needs to match in order to pass
let trace = Trace(name: "Find the answer", itemsToMatch: [answerTraceItem])
// And this is the time-scoped tracer that handles logging and creating pass/fail results
let tracer = Tracer(trace: trace)

// Starting a trace returns a tuple with the current state and two signals to listen to
let (currentState, stateChangedSignal, itemLoggedSignal) = tracer.start()

print("\n\n---> TRACE STARTED: \(analyticsTrace.name)")
print("---> Current trace state: \(currentState)")

// Optionally, listen to changes in this trace (and you can remove the listener at any point)
itemLoggedListener = itemLoggedSignal.listen { traceItem in
    print("---> Trace item logged: \(traceItem)")
}
stateChangedListener = stateChangedSignal.listen { traceState in
    print("---> Trace state updated to: \(traceState.rawValue)")
    print("---> Trace state description: \(traceState)")
}

Logging

Logging during a trace is simple:

tracer.log(item: answerTraceItem)

// After an item is logged, your trace will immediately either be passing or failing.
// Optionally, you can set `assertOnFailure` to `true` on your `Trace` instance to stop 
// app execution as soon as a trace fails so you can debug.

Or you can even hook it into your Analytics struct:

Analytics.log(event: .thirdViewSeen)

/// See example app for this setup
struct Analytics {
    
    static func log(event: AnalyticsEvent) {
        print("\n\nANALYTICS: \(event.rawValue) logged")
        
        Tracers.analytics.activeTracer?.log(item: event.toTraceItem)
    }
    
}

Stopping & Reporting

Stopping a trace returns a TraceReport with raw or summary versions of the results

// FYI: signal listeners are automatically removed when stopped
let report = tracer.stop()
print(report.summary)
print(report.rawLog)

// Or manually parse the results yourself
let results = report.result

Manual Installation

  1. Clone this repository and drag the Tracer.xcodeproj into the Project Navigator of your application's Xcode project.
  • It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.
  1. Select the Tracer.xcodeproj in the Project Navigator and verify the deployment target matches that of your application target.
  2. Select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the Targets heading in the sidebar.
  3. In the tab bar at the top of that window, open the General panel.
  4. Click on the + button under the Embedded Binaries section.
  5. Search for and select the top Tracer.framework.

And that's it!

The Tracer.framework is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.

Issues & Bugs

Please use the Github issue tracker to let us know about any issues you may be experiencing.

License

Tracer for iOS is licensed under the Apache Software License, 2.0 ("Apache 2.0")

Authors

Tracer for iOS is brought to you by Rob Phillips and the rest of the Keepsafe team. We'd love to have you contribute or join us.

About

Use traces to validate UX flows, analytics, or event buses

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages