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

REPLAY-1339 Support UITextField elements in SR #1191

Merged
merged 2 commits into from
Mar 6, 2023

Conversation

ncreated
Copy link
Member

@ncreated ncreated commented Mar 3, 2023

What and why?

📦 This PR enhances the recording of UITextField elements in session replay. Its preliminary support was introduced earlier, but here we solve remaining glitches, improve PIIs masking and adjust colors for light and dark mode.

On PIIs masking - following the SR Privacy Restrictions now we replace passwords, e-mails and phone numbers with "xxx".

With no masking:With masking:

How?

The work done to UITextFieldRecorder leverages concepts introduced earlier in #1175.

Unfortunately, the subtreeStrategy: .replace(subtreeNodes:) turned out to be limiting for text fields situation. Although we now use sub-recorders in UITextFieldRecorder:

/// `UIViewRecorder` for recording appearance of the text field.
private let backgroundViewRecorder: UIViewRecorder
/// `UIImageViewRecorder` for recording icons that are displayed in text field.
private let iconsRecorder: UIImageViewRecorder

the actual "text" information isn't available anywhere in TF's subtree. Instead, it is accessible through the root node (textField.text) and we can't use the strategy of replacing subtree nodes to replace the root one.

For that reason, I had to do one more iteration on .subtreeStrategy concept. What turns out to be sustainable (I made a PoC proving it to work for remaining Input Elements) is changing 1:1 relationship between a Node and NodeSemantics:

- node.semantics = <single semantics>

into 1:N relationship between NodeSemantics and Node:

+ semantics.nodes = <an array of nodes>

This works fine for text fields, as now the UITextFieldRecorder can return 2 nodes:

SpecificElementSemantics(nodes: [backgroundNode, textNode])

It doesn't change anything to other recorders, although I had to update their unit tests.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes

Custom CI job configuration (optional)

  • Run unit tests
  • Run integration tests
  • Run smoke tests

@ncreated ncreated self-assigned this Mar 3, 2023
@ncreated ncreated marked this pull request as ready for review March 3, 2023 14:06
@ncreated ncreated requested a review from a team as a code owner March 3, 2023 14:06
/ \
I1 V1
*/
func testFlattenNodes_withInvisibleNode() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invisible nodes are no longer passed to NodesFlattener - so this test doesn't make sense anymore 💡

/
V1
*/
func testFlattenNodes_withMixedVisibleAndInvisibleNodes() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invisible nodes are no longer passed to NodesFlattener - so this test doesn't make sense anymore 💡

XCTAssertEqual(builder.buildWireframes(with: WireframesBuilder()).count, 2)
}

func testWhenImageViewHasImageOrAppearance() throws {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was duplicating configuration and assertions from the tests defined above. Getting rid of it 🧽.

Copy link
Member

@maciejburda maciejburda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Great!

@@ -34,7 +34,8 @@ internal struct UISegmentRecorder: NodeRecorder {
}
}()
)
return SpecificElement(wireframesBuilder: builder, subtreeStrategy: .ignore)
let node = Node(viewAttributes: attributes, wireframesBuilder: builder)
return SpecificElement(subtreeStrategy: .ignore, nodes: [node])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like we could have convenience function that would allow us to initialise the SpecificElement with attributes, builder and subtree strategy 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THB I don't think this is good idea. Except saving few lines of code (in entire PR) this won't achieve any architectural goal, but only make things more coupled. SpecificElement would have to additionally depend on ViewAttributes and NodeWireframesBuilder - even if we call it convenience function, that would count as dependency and make concept less readable: Why passing strategy, attributes and builder to some SpecificElements vs strategy and nodes to others?

@ncreated ncreated merged commit e9f5ef9 into develop Mar 6, 2023
@ncreated ncreated deleted the ncreated/REPLAY-1339-support-textfields branch March 6, 2023 07:50
@ncreated ncreated mentioned this pull request Mar 31, 2023
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants