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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal enum Fixture: CaseIterable {
case segments
case pickers
case switches
case textFields

var menuItemTitle: String {
switch self {
Expand All @@ -28,6 +29,8 @@ internal enum Fixture: CaseIterable {
return "Pickers"
case .switches:
return "Switches"
case .textFields:
return "Text Fields"
}
}

Expand All @@ -45,6 +48,8 @@ internal enum Fixture: CaseIterable {
return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "Pickers")
case .switches:
return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "Switches")
case .textFields:
return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "TextFields")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,122 @@
<point key="canvasLocation" x="138" y="6"/>
</scene>
<!--View Controller-->
<scene sceneID="7SG-Ig-YgK">
<objects>
<viewController storyboardIdentifier="TextFields" id="Jf0-qn-anK" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="nob-qf-eI7">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="IEn-Sf-HvM">
<rect key="frame" x="8" y="67" width="377" height="552.33333333333337"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Default (with value)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3uU-aL-MXT">
<rect key="frame" x="0.0" y="0.0" width="377" height="20.333333333333332"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="foo bar" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Go-nf-cqb">
<rect key="frame" x="0.0" y="28.333333333333329" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Default (with placeholder)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="39A-6B-Wot">
<rect key="frame" x="0.0" y="70.333333333333343" width="377" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="fizz buzz" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="a4u-id-7wT">
<rect key="frame" x="0.0" y="98.666666666666657" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password (Secure Text Entry)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VMI-1y-b5c">
<rect key="frame" x="0.0" y="140.66666666666666" width="377" height="20.333333333333343"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="p4ssw0rd" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="qPH-8B-ZFh">
<rect key="frame" x="0.0" y="169" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email (textContentType = .emailAddress)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ttw-nr-d6T">
<rect key="frame" x="0.0" y="211" width="377" height="20.333333333333343"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="[email protected]" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="c7u-bm-2SR">
<rect key="frame" x="0.0" y="239.33333333333331" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" textContentType="email"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone no. (textContentType = .telephoneNumber)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IEz-NU-2Bj">
<rect key="frame" x="0.0" y="281.33333333333331" width="377" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="+01200000000" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="rn0-SO-4R1">
<rect key="frame" x="0.0" y="307.33333333333331" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" textContentType="tel"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Default (clear button always visible)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uvb-ej-PYu">
<rect key="frame" x="0.0" y="349.33333333333331" width="377" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="bizz bazz" borderStyle="roundedRect" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="4Br-MY-Lqa">
<rect key="frame" x="0.0" y="377.66666666666669" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<textInputTraits key="textInputTraits"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YoQ-B3-Fio">
<rect key="frame" x="0.0" y="419.66666666666669" width="377" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="disabled text" borderStyle="roundedRect" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="YaX-lF-HRj">
<rect key="frame" x="0.0" y="448" width="377" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<textInputTraits key="textInputTraits"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Custom #1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s7U-uC-auS">
<rect key="frame" x="0.0" y="490" width="377" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="custom text 1" borderStyle="roundedRect" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="9ga-bn-yoe">
<rect key="frame" x="0.0" y="518.33333333333337" width="377" height="34"/>
<color key="backgroundColor" systemColor="systemYellowColor"/>
<fontDescription key="fontDescription" type="system" pointSize="10"/>
<textInputTraits key="textInputTraits"/>
</textField>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="Pv1-hQ-MV4"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="IEn-Sf-HvM" firstAttribute="leading" secondItem="Pv1-hQ-MV4" secondAttribute="leading" constant="8" id="9pJ-tn-XG8"/>
<constraint firstItem="Pv1-hQ-MV4" firstAttribute="trailing" secondItem="IEn-Sf-HvM" secondAttribute="trailing" constant="8" id="CtW-zm-TRk"/>
<constraint firstItem="IEn-Sf-HvM" firstAttribute="top" secondItem="Pv1-hQ-MV4" secondAttribute="top" constant="8" id="u5q-Qt-PEA"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="mBJ-FW-Es7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="135" y="675"/>
</scene>
<!--View Controller-->
<scene sceneID="EM5-Q5-n2I">
<objects>
<viewController storyboardIdentifier="Segments" id="Uyj-Ig-4Fe" sceneMemberID="viewController">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,22 @@ final class SRSnapshotTests: SnapshotTestCase {
record: recordingMode
)
}

func testTextFields() throws {
show(fixture: .textFields)

var image = try takeSnapshot(configuration: .init(privacy: .allowAll))
DDAssertSnapshotTest(
newImage: image,
snapshotLocation: .folder(named: snapshotsFolderName, fileNameSuffix: "-allowAll-privacy"),
record: recordingMode
)

image = try takeSnapshot(configuration: .init(privacy: .maskAll))
DDAssertSnapshotTest(
newImage: image,
snapshotLocation: .folder(named: snapshotsFolderName, fileNameSuffix: "-maskAll-privacy"),
record: recordingMode
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ private extension SRImageWireframe {
y: CGFloat(y),
width: CGFloat(width),
height: CGFloat(height),
style: frameStyle(border: border, style: shapeStyle),
content: .init(text: "IMG", textColor: .black, font: .systemFont(ofSize: 8))
style: .init(lineWidth: 1, lineColor: .black, fillColor: .red),
annotation: .init(
text: "IMG \(width) x \(height)",
style: .init(size: .small, position: .top, alignment: .trailing)
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,20 @@ internal struct NodesFlattener {
var flattened: [Node] = []

for nextNode in snapshot.nodes {
// Skip invisible nodes:
if !(nextNode.semantics is InvisibleElement) {
// When accepting nodes, remove ones that are covered by another opaque node:
flattened = flattened.compactMap { previousNode in
let previousFrame = previousNode.semantics.wireframesBuilder?.wireframeRect ?? .zero
let nextFrame = nextNode.semantics.wireframesBuilder?.wireframeRect ?? .zero
// When accepting nodes, remove ones that are covered by another opaque node:
flattened = flattened.compactMap { previousNode in
let previousFrame = previousNode.wireframesBuilder.wireframeRect
let nextFrame = nextNode.wireframesBuilder.wireframeRect

// Drop previous node when:
let dropPreviousNode = nextFrame.contains(previousFrame) // its rect is fully covered by the next node
&& nextNode.viewAttributes.hasAnyAppearance // and the next node brings something visual
&& !nextNode.viewAttributes.isTranslucent // and the next node is opaque
// Drop previous node when:
let dropPreviousNode = nextFrame.contains(previousFrame) // its rect is fully covered by the next node
&& nextNode.viewAttributes.hasAnyAppearance // and the next node brings something visual
&& !nextNode.viewAttributes.isTranslucent // and the next node is opaque

return dropPreviousNode ? nil : previousNode
}

flattened.append(nextNode)
return dropPreviousNode ? nil : previousNode
}

flattened.append(nextNode)
}

return flattened
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal protocol TextObfuscating {
func mask(text: String) -> String
}

/// Text obfuscator which replaces all readable characters with `"x"`.
/// Text obfuscator which replaces all readable characters with space-preserving `"x"` characters.
internal struct TextObfuscator: TextObfuscating {
/// The character to mask text with.
let maskCharacter: UnicodeScalar = "x"
Expand All @@ -39,6 +39,17 @@ internal struct TextObfuscator: TextObfuscating {
}
}

/// Text obfuscator which replaces the whole text with fixed-width `"xxx"` mask value.
///
/// It should be used **by default** for input elements that bring sensitive information (such as passwords).
/// It shuold be used for input elements that can't safely use space-preserving masking (such as date pickers, where selection can be still
/// inferred by counting the number of x-es in the mask).
internal struct InputTextObfuscator: TextObfuscating {
private static let maskedString = "xxx"

func mask(text: String) -> String { Self.maskedString }
}

/// Text obfuscator which only returns the original text.
internal struct NOPTextObfuscator: TextObfuscating {
func mask(text: String) -> String {
Expand Down
2 changes: 1 addition & 1 deletion DatadogSessionReplay/Sources/Processor/Processor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class Processor: Processing {
private func processSync(viewTreeSnapshot: ViewTreeSnapshot, touchSnapshot: TouchSnapshot?) {
let flattenedNodes = nodesFlattener.flattenNodes(in: viewTreeSnapshot)
let wireframes: [SRWireframe] = flattenedNodes
.compactMap { node in node.semantics.wireframesBuilder }
.map { node in node.wireframesBuilder }
.flatMap { nodeBuilder in nodeBuilder.buildWireframes(with: wireframesBuilder) }

#if DEBUG
Expand Down
Loading