Skip to content

Commit 0bec057

Browse files
using cli changes for cross origin iframe
1 parent 4b77d63 commit 0bec057

2 files changed

Lines changed: 54 additions & 106 deletions

File tree

percy/snapshot.py

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import os
22
import platform
33
import json
4-
import re
54
from contextlib import contextmanager
65
from functools import lru_cache
76
from time import sleep
@@ -86,7 +85,7 @@ def fetch_percy_dom():
8685
response.raise_for_status()
8786
return response.text
8887

89-
# pylint: disable=too-many-arguments, too-many-branches, too-many-locals
88+
# pylint: disable=too-many-arguments, too-many-branches, too-many-locals, too-many-positional-arguments
9089
def create_region(
9190
boundingBox=None,
9291
elementXpath=None,
@@ -175,10 +174,6 @@ def process_frame(driver, frame_element, options, percy_dom_script):
175174
}
176175

177176

178-
def _replace_iframe_with_srcdoc(match, srcdoc_value):
179-
return f'{match.group(1)} srcdoc="{srcdoc_value}">'
180-
181-
182177
def _is_unsupported_iframe_src(frame_src):
183178
return (
184179
not frame_src or
@@ -193,55 +188,6 @@ def _get_origin(url):
193188
parsed = urlparse(url)
194189
return f"{parsed.scheme}://{parsed.netloc}"
195190

196-
def stitch_cors_iframes(dom_snapshot, processed_frames):
197-
html = dom_snapshot.get("html")
198-
if not isinstance(html, str):
199-
return dom_snapshot
200-
201-
combined_resources = []
202-
if isinstance(dom_snapshot.get("resources"), list):
203-
combined_resources = dom_snapshot["resources"][:]
204-
205-
seen_resource_urls = {
206-
resource.get("url") for resource in combined_resources
207-
if isinstance(resource, dict) and isinstance(resource.get("url"), str)
208-
}
209-
210-
for frame in processed_frames:
211-
iframe_data = frame.get("iframeData", {})
212-
iframe_snapshot = frame.get("iframeSnapshot", {})
213-
percy_element_id = iframe_data.get("percyElementId")
214-
iframe_html = iframe_snapshot.get("html") if isinstance(iframe_snapshot, dict) else None
215-
216-
if not percy_element_id or not iframe_html:
217-
continue
218-
219-
srcdoc_value = iframe_html.replace("&", "&").replace('"', """)
220-
escaped_id = re.escape(percy_element_id)
221-
iframe_regex = re.compile(
222-
rf'(<iframe\b[^>]*?data-percy-element-id="{escaped_id}"[^>]*?)(/?>)',
223-
re.DOTALL
224-
)
225-
226-
html = iframe_regex.sub(
227-
lambda match, srcdoc=srcdoc_value: _replace_iframe_with_srcdoc(match, srcdoc),
228-
html,
229-
count=1
230-
)
231-
232-
resources = iframe_snapshot.get("resources") if isinstance(iframe_snapshot, dict) else None
233-
if isinstance(resources, list):
234-
for resource in resources:
235-
if not isinstance(resource, dict):
236-
continue
237-
url = resource.get("url")
238-
if not isinstance(url, str) or url in seen_resource_urls:
239-
continue
240-
combined_resources.append(resource)
241-
seen_resource_urls.add(url)
242-
243-
return {**dom_snapshot, "html": html, "resources": combined_resources}
244-
245191
def get_serialized_dom(driver, cookies, percy_dom_script=None, **kwargs):
246192
# 1. Serialize the main page first (this adds the data-percy-element-ids)
247193
dom_snapshot = driver.execute_script(f'return PercyDOM.serialize({json.dumps(kwargs)})')
@@ -270,15 +216,14 @@ def get_serialized_dom(driver, cookies, percy_dom_script=None, **kwargs):
270216
processed_frames.append(result)
271217

272218
if processed_frames:
273-
dom_snapshot = stitch_cors_iframes(dom_snapshot, processed_frames)
219+
dom_snapshot['corsIframes'] = processed_frames
274220
except Exception as e:
275221
log(f"Failed to process cross-origin iframes: {e}", "debug")
276222

277223
dom_snapshot['cookies'] = cookies
278224
return dom_snapshot
279225

280226
def get_responsive_widths(widths=None):
281-
"""Gets computed responsive widths from the Percy server for responsive snapshot capture."""
282227
if widths is None:
283228
widths = []
284229
try:
@@ -300,7 +245,6 @@ def get_responsive_widths(widths=None):
300245
msg = "Update Percy CLI to the latest version to use responsiveSnapshotCapture"
301246
raise Exception(msg) from e
302247

303-
304248
def _setup_resize_listener(driver):
305249
"""Initializes the resize counter and attaches a named listener to avoid duplicates."""
306250
driver.execute_script("""

tests/test_snapshot.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -992,21 +992,16 @@ def test_process_frame_returns_none_on_script_injection_failure(self):
992992

993993
self.assertIsNone(result)
994994

995-
def test_get_serialized_dom_stitches_cross_origin_iframes(self):
996-
"""get_serialized_dom stitches cross-origin iframe HTML into srcdoc."""
995+
def test_get_serialized_dom_populates_cors_iframes(self):
997996
driver = Mock()
998-
# process_frame calls execute_script twice inside the iframe:
999-
# 1. inject percy_dom_script 2. serialize the frame DOM
1000997
driver.execute_script.side_effect = [
1001998
{
1002999
"html": '<html><body><iframe data-percy-element-id="cid-1"></iframe></body></html>',
1003-
"resources": [{"url": "https://cdn/main.css", "content": "m"}]
1004-
}, # main page serialize (get_serialized_dom)
1005-
None, # inject percy_dom_script into frame
1000+
"resources": [{"url": "https://cdn/main.css", "content": "m"}]},
1001+
None,
10061002
{
10071003
"html": "<iframe-html/>",
1008-
"resources": [{"url": "https://cdn/frame.css", "content": "f"}]
1009-
}, # frame DOM serialize
1004+
"resources": [{"url": "https://cdn/frame.css", "content": "f"}]},
10101005
]
10111006
driver.current_url = "http://main.example.com/"
10121007

@@ -1022,11 +1017,14 @@ def test_get_serialized_dom_stitches_cross_origin_iframes(self):
10221017

10231018
dom = local.get_serialized_dom(driver, [], percy_dom_script="some_script")
10241019

1025-
self.assertNotIn("corsIframes", dom)
1026-
self.assertIn('data-percy-element-id="cid-1"', dom["html"])
1027-
self.assertIn('srcdoc="<iframe-html/>"', dom["html"])
1028-
urls = [resource["url"] for resource in dom["resources"]]
1029-
self.assertEqual(urls, ["https://cdn/main.css", "https://cdn/frame.css"])
1020+
self.assertIn("corsIframes", dom)
1021+
self.assertEqual(len(dom["corsIframes"]), 1)
1022+
entry = dom["corsIframes"][0]
1023+
self.assertEqual(entry["iframeData"]["percyElementId"], "cid-1")
1024+
self.assertEqual(entry["iframeSnapshot"]["html"], "<iframe-html/>")
1025+
self.assertEqual(entry["frameUrl"], "https://cross.example.com/page")
1026+
# HTML is left unchanged (no srcdoc injection here — core handles that)
1027+
self.assertNotIn("srcdoc", dom["html"])
10301028

10311029
def test_get_serialized_dom_skips_blank_src_frames(self):
10321030
"""Frames with no src or src='about:blank' are not processed."""
@@ -1044,7 +1042,7 @@ def test_get_serialized_dom_skips_blank_src_frames(self):
10441042

10451043
dom = local.get_serialized_dom(driver, [], percy_dom_script="some_script")
10461044

1047-
self.assertNotIn("srcdoc=", dom["html"])
1045+
self.assertNotIn("corsIframes", dom)
10481046

10491047
def test_get_serialized_dom_no_cors_iframes_without_script(self):
10501048
"""Without a percy_dom_script, cross-origin iframes are not processed."""
@@ -1062,7 +1060,7 @@ def test_get_serialized_dom_no_cors_iframes_without_script(self):
10621060

10631061
dom = local.get_serialized_dom(driver, [], percy_dom_script=None)
10641062

1065-
self.assertNotIn("srcdoc=", dom["html"])
1063+
self.assertNotIn("corsIframes", dom)
10661064

10671065
def test_get_serialized_dom_cookies_always_attached(self):
10681066
"""Cookies are always added to the dom_snapshot regardless of iframes."""
@@ -1096,7 +1094,8 @@ def test_get_serialized_dom_same_host_different_scheme_is_cross_origin(self):
10961094

10971095
dom = local.get_serialized_dom(driver, [], percy_dom_script="script")
10981096

1099-
self.assertIn('srcdoc="<frame/>"', dom["html"])
1097+
self.assertIn("corsIframes", dom)
1098+
self.assertEqual(dom["corsIframes"][0]["iframeSnapshot"]["html"], "<frame/>")
11001099

11011100
def test_get_serialized_dom_same_host_different_port_is_cross_origin(self):
11021101
"""http://example.com:3000 and http://example.com:4000 differ in port → cross-origin."""
@@ -1116,7 +1115,8 @@ def test_get_serialized_dom_same_host_different_port_is_cross_origin(self):
11161115

11171116
dom = local.get_serialized_dom(driver, [], percy_dom_script="script")
11181117

1119-
self.assertIn('srcdoc="<frame/>"', dom["html"])
1118+
self.assertIn("corsIframes", dom)
1119+
self.assertEqual(dom["corsIframes"][0]["iframeSnapshot"]["html"], "<frame/>")
11201120

11211121
def test_get_serialized_dom_same_origin_is_not_cross_origin(self):
11221122
"""http://main.example.com/page1 and http://main.example.com/page2 share origin."""
@@ -1132,7 +1132,7 @@ def test_get_serialized_dom_same_origin_is_not_cross_origin(self):
11321132

11331133
dom = local.get_serialized_dom(driver, [], percy_dom_script="script")
11341134

1135-
self.assertNotIn("srcdoc=", dom["html"])
1135+
self.assertNotIn("corsIframes", dom)
11361136

11371137
def test_process_frame_passes_enable_javascript_option(self):
11381138
"""process_frame serializes the frame with enableJavaScript=True, mirroring
@@ -1169,31 +1169,30 @@ def test_process_frame_uses_unknown_src_fallback(self):
11691169
self.assertIsNotNone(result)
11701170
self.assertEqual(result["frameUrl"], "unknown-src")
11711171

1172-
def test_stitch_cors_iframes_injects_srcdoc_and_merges_resources(self):
1173-
"""stitch_cors_iframes injects iframe HTML into srcdoc and deduplicates resources by URL."""
1174-
dom_snapshot = {
1175-
"html": '<html><body><iframe data-percy-element-id="cid-1"></iframe></body></html>',
1176-
"resources": [{"url": "https://cdn/main.css", "content": "m"}]
1177-
}
1178-
processed_frames = [{
1179-
"iframeData": {"percyElementId": "cid-1"},
1180-
"iframeSnapshot": {
1181-
"html": '<html><body><h1>F & "Q"</h1></body></html>',
1182-
"resources": [
1183-
{"url": "https://cdn/main.css", "content": "dup"},
1184-
{"url": "https://cdn/frame.css", "content": "f"}
1185-
]
1186-
}
1187-
}]
1172+
def test_get_serialized_dom_corsIframes_entry_has_correct_structure(self):
1173+
dom_html = '<html><body><iframe data-percy-element-id="cid-1"></iframe></body></html>'
1174+
frame_resource = {"url": "https://cdn/frame.css", "content": "f"}
1175+
driver = Mock()
1176+
driver.execute_script.side_effect = [
1177+
{"html": dom_html, "resources": []},
1178+
None,
1179+
{"html": '<html><body><h1>Frame</h1></body></html>', "resources": [frame_resource]},
1180+
]
1181+
driver.current_url = "http://main.example.com/"
1182+
frame = Mock()
1183+
frame.get_attribute = lambda attr: (
1184+
"https://cross.example.com/page" if attr == 'src' else "cid-1"
1185+
)
1186+
driver.find_elements.return_value = [frame]
11881187

1189-
stitched = local.stitch_cors_iframes(dom_snapshot, processed_frames)
1188+
dom = local.get_serialized_dom(driver, [], percy_dom_script="some_script")
11901189

1191-
self.assertIn(
1192-
'srcdoc="<html><body><h1>F &amp; &quot;Q&quot;</h1></body></html>"',
1193-
stitched["html"]
1194-
)
1195-
urls = [resource["url"] for resource in stitched["resources"]]
1196-
self.assertEqual(urls, ["https://cdn/main.css", "https://cdn/frame.css"])
1190+
self.assertIn("corsIframes", dom)
1191+
entry = dom["corsIframes"][0]
1192+
self.assertEqual(entry["frameUrl"], "https://cross.example.com/page")
1193+
self.assertEqual(entry["iframeData"], {"percyElementId": "cid-1"})
1194+
self.assertIn("Frame", entry["iframeSnapshot"]["html"])
1195+
self.assertIn(frame_resource, entry["iframeSnapshot"]["resources"])
11971196

11981197
def test_get_serialized_dom_multiple_cross_origin_frames(self):
11991198
"""All cross-origin frames are collected; same-origin frames are skipped."""
@@ -1225,9 +1224,12 @@ def test_get_serialized_dom_multiple_cross_origin_frames(self):
12251224

12261225
dom = local.get_serialized_dom(driver, [], percy_dom_script="script")
12271226

1228-
self.assertIn('data-percy-element-id="pid-1" srcdoc="<frame1/>"', dom["html"])
1229-
self.assertIn('data-percy-element-id="pid-2" srcdoc="<frame2/>"', dom["html"])
1230-
self.assertNotIn('data-percy-element-id="pid-same" srcdoc=', dom["html"])
1227+
self.assertIn("corsIframes", dom)
1228+
self.assertEqual(len(dom["corsIframes"]), 2)
1229+
pids = [e["iframeData"]["percyElementId"] for e in dom["corsIframes"]]
1230+
self.assertIn("pid-1", pids)
1231+
self.assertIn("pid-2", pids)
1232+
self.assertNotIn("pid-same", pids)
12311233

12321234
def test_get_serialized_dom_handles_find_elements_exception(self):
12331235
"""If find_elements raises, the error is swallowed, cookies are still attached,
@@ -1240,7 +1242,7 @@ def test_get_serialized_dom_handles_find_elements_exception(self):
12401242
dom = local.get_serialized_dom(driver, [{"name": "k", "value": "v"}],
12411243
percy_dom_script="script")
12421244

1243-
self.assertNotIn("srcdoc=", dom["html"])
1245+
self.assertNotIn("corsIframes", dom)
12441246
self.assertEqual(dom["cookies"], [{"name": "k", "value": "v"}])
12451247

12461248
def test_get_serialized_dom_process_frame_failure_is_skipped(self):
@@ -1270,8 +1272,10 @@ def test_get_serialized_dom_process_frame_failure_is_skipped(self):
12701272

12711273
dom = local.get_serialized_dom(driver, [], percy_dom_script="script")
12721274

1273-
self.assertIn('data-percy-element-id="pid-ok" srcdoc="<ok/>"', dom["html"])
1274-
self.assertNotIn('data-percy-element-id="pid-fail" srcdoc=', dom["html"])
1275+
self.assertIn("corsIframes", dom)
1276+
self.assertEqual(len(dom["corsIframes"]), 1)
1277+
self.assertEqual(dom["corsIframes"][0]["iframeData"]["percyElementId"], "pid-ok")
1278+
self.assertEqual(dom["corsIframes"][0]["iframeSnapshot"]["html"], "<ok/>")
12751279

12761280

12771281
class TestCreateRegion(unittest.TestCase):

0 commit comments

Comments
 (0)