-
Notifications
You must be signed in to change notification settings - Fork 0
fix: merge .percy.yml config options with snapshot options for serializeDOM #430
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
base: master
Are you sure you want to change the base?
Changes from all commits
7b59181
a6fd4fa
6dabd1f
4f2e554
17070dd
042d9c8
558c9f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,11 +237,11 @@ | |
| }; | ||
| public class Region | ||
| { | ||
| public RegionElementSelector elementSelector { get; set; } | ||
| public RegionPadding padding { get; set; } | ||
| public string algorithm { get; set; } | ||
| public RegionConfiguration configuration { get; set; } | ||
| public RegionAssertion assertion { get; set; } | ||
|
|
||
| // Rename the nested class to RegionElementSelector or another unique name | ||
| public class RegionElementSelector | ||
|
|
@@ -1476,6 +1476,124 @@ | |
| return domSnapshots; | ||
| } | ||
|
|
||
| private static Dictionary<string, object> MergeSnapshotOptions(Dictionary<string, object>? options) | ||
| { | ||
| var merged = new Dictionary<string, object>(); | ||
| if (cliConfig != null) | ||
| { | ||
| try | ||
| { | ||
| JsonElement config = (JsonElement)cliConfig; | ||
| if (config.TryGetProperty("snapshot", out JsonElement snapshotElement) && | ||
| snapshotElement.ValueKind == JsonValueKind.Object) | ||
| { | ||
| foreach (JsonProperty prop in snapshotElement.EnumerateObject()) | ||
| { | ||
| // Recursively convert config values so nested JSON | ||
| // objects become Dictionary<string, object> and can be | ||
| // deep-merged with per-call options below. | ||
| var converted = JsonElementToObjectDeep(prop.Value); | ||
| if (converted != null) | ||
| merged[prop.Name] = converted; | ||
| } | ||
| } | ||
| } | ||
| catch (Exception) { } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Low] Empty catch swallows config-merge errors
Suggestion: Log at debug, matching the existing pattern: Reviewer: stack:code-review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Low] Silently swallowed merge error
Suggestion: Log at debug level inside the catch (the file already has a Reviewer: stack:code-review |
||
| } | ||
| if (options != null) | ||
| { | ||
| // Deep-merge: nested objects merge recursively (per-call wins at | ||
| // leaves), arrays/scalars replace. Matches the JS sdk-utils fix. | ||
| merged = DeepMerge(merged, options); | ||
| } | ||
| return merged; | ||
| } | ||
|
|
||
| // Recursively converts a JSON element into plain CLR objects: | ||
| // Object -> Dictionary<string, object> (recursive), | ||
| // Array -> List<object> (recursive), | ||
| // primitives -> bool / int / double / string. | ||
| private static object? JsonElementToObjectDeep(JsonElement el) | ||
| { | ||
| switch (el.ValueKind) | ||
| { | ||
| case JsonValueKind.Object: | ||
| var dict = new Dictionary<string, object>(); | ||
| foreach (JsonProperty prop in el.EnumerateObject()) | ||
| { | ||
| var converted = JsonElementToObjectDeep(prop.Value); | ||
| if (converted != null) | ||
| dict[prop.Name] = converted; | ||
| } | ||
| return dict; | ||
| case JsonValueKind.Array: | ||
| var items = el.EnumerateArray().ToList(); | ||
| // All-integer arrays (e.g. config `widths`) must stay List<int> so | ||
| // downstream consumers like CaptureResponsiveDom can use them directly. | ||
| if (items.Count > 0 && items.All(i => i.ValueKind == JsonValueKind.Number && i.TryGetInt32(out _))) | ||
| return items.Select(i => i.GetInt32()).ToList(); | ||
| var list = new List<object>(); | ||
| foreach (JsonElement item in items) | ||
| { | ||
| var converted = JsonElementToObjectDeep(item); | ||
| if (converted != null) | ||
| list.Add(converted); | ||
| } | ||
| return list; | ||
| case JsonValueKind.True: | ||
| return true; | ||
| case JsonValueKind.False: | ||
| return false; | ||
| case JsonValueKind.Number: | ||
| return el.TryGetInt32(out int intVal) ? (object)intVal : el.GetDouble(); | ||
| case JsonValueKind.String: | ||
| return el.GetString(); | ||
| default: | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // Deep-merges override into a copy of baseDict: when a key exists in both | ||
| // and both values are Dictionary<string, object>, recurse; otherwise the | ||
| // override value wins (arrays and scalars replace). | ||
| private static Dictionary<string, object> DeepMerge( | ||
| Dictionary<string, object> baseDict, Dictionary<string, object> overrideDict) | ||
| { | ||
| var result = new Dictionary<string, object>(baseDict); | ||
| foreach (var kvp in overrideDict) | ||
| { | ||
| if (result.TryGetValue(kvp.Key, out var existing) && | ||
| existing is Dictionary<string, object> existingDict && | ||
| kvp.Value is Dictionary<string, object> overrideNested) | ||
| { | ||
| result[kvp.Key] = DeepMerge(existingDict, overrideNested); | ||
| } | ||
| else | ||
| { | ||
| result[kvp.Key] = kvp.Value; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| private static object ConvertJsonArray(JsonElement array) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Low] The added test only covers scalar (bool/string) merge precedence; the widths-style array conversion path has no direct coverage. Suggestion: Add a case where Reviewer: stack:code-review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Low] Dead code:
Suggestion: Delete Reviewer: stack:code-review |
||
| { | ||
| var items = array.EnumerateArray().ToList(); | ||
| if (items.Count > 0 && items.All(item => item.ValueKind == JsonValueKind.Number && item.TryGetInt32(out _))) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Low] Empty config
Suggestion: Return Reviewer: stack:code-review |
||
| { | ||
| return items.Select(item => item.GetInt32()).ToList(); | ||
| } | ||
|
|
||
| return items.Select(item => item.ValueKind switch | ||
| { | ||
| JsonValueKind.True => true, | ||
| JsonValueKind.False => false, | ||
| JsonValueKind.Number => item.TryGetInt32(out int intVal) ? intVal : (object)item.GetDouble(), | ||
| JsonValueKind.String => item.GetString(), | ||
| _ => (object)item | ||
| }).ToList(); | ||
| } | ||
|
|
||
| private static bool isResponsiveSnapshotCapture(Dictionary<string, object>? options) | ||
| { | ||
| if (cliConfig == null) return false; | ||
|
|
@@ -1531,14 +1649,16 @@ | |
| // Non-Chrome browsers and missing ExecuteCdpCommand are no-ops. | ||
| ExposeClosedShadowRoots(driver); | ||
|
|
||
| // Merge .percy.yml config options with snapshot options (snapshot options take priority) | ||
| var mergedOptions = MergeSnapshotOptions(options); | ||
|
|
||
| var cookies = driver.Manage().Cookies.AllCookies; | ||
| string opts = JsonSerializer.Serialize(options); | ||
| dynamic domSnapshot = null; | ||
|
|
||
| if (isResponsiveSnapshotCapture(options)) { | ||
| domSnapshot = CaptureResponsiveDom(driver, cookies, options); | ||
| if (isResponsiveSnapshotCapture(mergedOptions)) { | ||
| domSnapshot = CaptureResponsiveDom(driver, cookies, mergedOptions); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [High] Config-seeded This now passes Suggestion: Coerce List<int> widths = new List<int>();
if (options != null && options.TryGetValue("widths", out var raw)) {
if (raw is List<int> ints) widths = ints;
else if (raw is List<object> objs) widths = objs.Select(Convert.ToInt32).ToList();
}or route array conversion through the (currently unused) Reviewer: stack:code-review |
||
| } else { | ||
| domSnapshot = getSerializedDom(driver, cookies, options, _dom); | ||
| domSnapshot = getSerializedDom(driver, cookies, mergedOptions, _dom); | ||
| } | ||
|
|
||
| Options snapshotOptions = new Options { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Medium] No tests for the new merge/array-typing logic
MergeSnapshotOptionsandConvertJsonArray(including the widths-typing fix that was the prior FAIL) ship with no unit tests, though the repo has aPercy.Testproject. Regressions in array typing or merge precedence would go uncaught.Suggestion: Add tests covering: config
widthsarray converting toList<int>consumable byCaptureResponsiveDom; per-call options overriding config; empty and mixed-type arrays; and absent/non-objectsnapshotconfig falling back cleanly.Reviewer: stack:code-review