Skip to content

Commit 4c705f3

Browse files
authored
test: add test_frames (microsoft#27)
1 parent c0868d5 commit 4c705f3

File tree

5 files changed

+240
-22
lines changed

5 files changed

+240
-22
lines changed

playwright/helper.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,15 @@ def set_default_timeout(self, timeout):
8080
self.timeout = timeout
8181

8282
class Error(BaseException):
83-
def __init__(self, message: str) -> None:
83+
def __init__(self, message: str, stack: str = None) -> None:
8484
self.message = message
85+
self.stack = stack
8586

8687
def serialize_error(ex: BaseException) -> ErrorPayload:
8788
return dict(message=str(ex))
8889

8990
def parse_error(error: ErrorPayload):
90-
return Error('%s\n%s' % (error['message'], error['stack']))
91+
return Error(error['message'], error['stack'])
9192

9293
def is_function_body(expression: str) -> bool:
9394
expression = expression.strip()

playwright/page.py

-4
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,11 @@ def _on_request_failed(self, request: Request, failure_text: str = None) -> None
106106
def _on_frame_attached(self, frame: Frame) -> None:
107107
frame._page = self
108108
self._frames.append(frame)
109-
if frame._parent_frame:
110-
frame._parent_frame._child_frames.append(frame)
111109
self.emit(Page.Events.FrameAttached, frame)
112110

113111
def _on_frame_detached(self, frame: Frame) -> None:
114112
self._frames.remove(frame)
115113
frame._detached = True
116-
if frame._parent_frame:
117-
frame._parent_frame._child_frames.remove(frame)
118114
self.emit(Page.Events.FrameDetached, frame)
119115

120116
def _on_frame_navigated(self, frame: Frame, url: str, name: str) -> None:

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def start_http_server():
7979
resource = File(static_path)
8080
site = web_server.Site(resource)
8181
reactor.listenTCP(server_object.PORT, site)
82-
t = threading.Thread(target=reactor.run)
82+
t = threading.Thread(target=lambda: reactor.run(installSignalHandlers=0))
8383
t.start()
8484
yield
8585
reactor.stop()

tests/test_frames.py

+212-11
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,215 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
async def test_frames_respect_name(page):
16-
await page.setContent('<iframe name=target></iframe>')
17-
assert page.frame(name='bogus') is None
18-
frame = page.frame(name='target')
19-
assert frame
20-
assert frame == (page.mainFrame.childFrames[0])
21-
22-
async def test_frames_respect_url(page, server):
23-
await page.setContent(f'<iframe src="{server.EMPTY_PAGE}"></iframe>')
24-
assert page.frame(url='bogus') is None
25-
assert page.frame(url=f'**/empty.html').url == (f'http://localhost:{server.PORT}/empty.html')
15+
import asyncio
16+
from playwright.helper import Error
17+
18+
async def test_evaluate_handle(page, server):
19+
await page.goto(server.EMPTY_PAGE)
20+
main_frame = page.mainFrame
21+
window_handle = await main_frame.evaluateHandle('window')
22+
assert window_handle
23+
24+
async def test_frame_element(page, server, utils):
25+
await page.goto(server.EMPTY_PAGE)
26+
frame1 = await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
27+
await utils.attach_frame(page, 'frame2', server.EMPTY_PAGE)
28+
frame3 = await utils.attach_frame(page, 'frame3', server.EMPTY_PAGE)
29+
frame1handle1 = await page.querySelector('#frame1')
30+
frame1handle2 = await frame1.frameElement()
31+
frame3handle1 = await page.querySelector('#frame3')
32+
frame3handle2 = await frame3.frameElement()
33+
assert await frame1handle1.evaluate('(a, b) => a === b', frame1handle2)
34+
assert await frame3handle1.evaluate('(a, b) => a === b', frame3handle2)
35+
assert await frame1handle1.evaluate('(a, b) => a === b', frame3handle1) == False
36+
37+
async def test_frame_element_with_content_frame(page, server, utils):
38+
await page.goto(server.EMPTY_PAGE)
39+
frame = await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
40+
handle = await frame.frameElement()
41+
contentFrame = await handle.contentFrame()
42+
assert contentFrame == frame
43+
44+
async def test_frame_element_throw_when_detached(page, server, utils):
45+
await page.goto(server.EMPTY_PAGE)
46+
frame1 = await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
47+
await page.evalOnSelector('#frame1', 'e => e.remove()')
48+
error = None
49+
try:
50+
await frame1.frameElement()
51+
except Error as e:
52+
error = e
53+
assert error.message == 'Frame has been detached.'
54+
55+
async def test_evaluate_throw_for_detached_frames(page, server, utils):
56+
frame1 = await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
57+
await utils.detach_frame(page, 'frame1')
58+
error = None
59+
try:
60+
await frame1.evaluate('7 * 8')
61+
except Error as e:
62+
error = e
63+
assert 'Execution Context is not available in detached frame' in error.message
64+
65+
async def test_evaluate_isolated_between_frames(page, server, utils):
66+
await page.goto(server.EMPTY_PAGE)
67+
await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
68+
assert len(page.frames) == 2
69+
[frame1, frame2] = page.frames
70+
assert frame1 != frame2
71+
72+
await asyncio.gather(
73+
frame1.evaluate('window.a = 1'),
74+
frame2.evaluate('window.a = 2')
75+
)
76+
[a1, a2] = await asyncio.gather(
77+
frame1.evaluate('window.a'),
78+
frame2.evaluate('window.a')
79+
)
80+
assert a1 == 1
81+
assert a2 == 2
82+
83+
async def test_should_handle_nested_frames(page, server, utils):
84+
await page.goto(server.PREFIX + '/frames/nested-frames.html')
85+
assert utils.dump_frames(page.mainFrame) == [
86+
'http://localhost:<PORT>/frames/nested-frames.html',
87+
' http://localhost:<PORT>/frames/frame.html (aframe)',
88+
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
89+
' http://localhost:<PORT>/frames/frame.html (dos)',
90+
' http://localhost:<PORT>/frames/frame.html (uno)',
91+
]
92+
93+
async def test_should_send_events_when_frames_are_manipulated_dynamically(page, server, utils):
94+
await page.goto(server.EMPTY_PAGE)
95+
# validate frameattached events
96+
attached_frames = []
97+
page.on('frameattached', lambda frame: attached_frames.append(frame))
98+
await utils.attach_frame(page, 'frame1', './assets/frame.html')
99+
assert len(attached_frames) == 1
100+
assert '/assets/frame.html' in attached_frames[0].url
101+
102+
# validate framenavigated events
103+
navigated_frames = []
104+
page.on('framenavigated', lambda frame: navigated_frames.append(frame))
105+
await page.evaluate('''() => {
106+
frame = document.getElementById('frame1')
107+
frame.src = './empty.html'
108+
return new Promise(x => frame.onload = x)
109+
}''')
110+
111+
assert len(navigated_frames) == 1
112+
assert navigated_frames[0].url == server.EMPTY_PAGE
113+
114+
# validate framedetached events
115+
detached_frames = list()
116+
page.on('framedetached', lambda frame: detached_frames.append(frame))
117+
await utils.detach_frame(page, 'frame1')
118+
assert len(detached_frames) == 1
119+
assert detached_frames[0].isDetached()
120+
121+
async def test_framenavigated_when_navigating_on_anchor_urls(page, server):
122+
await page.goto(server.EMPTY_PAGE)
123+
await asyncio.gather(
124+
page.goto(server.EMPTY_PAGE + '#foo'),
125+
page.waitForEvent('framenavigated')
126+
)
127+
assert page.url == server.EMPTY_PAGE + '#foo'
128+
129+
async def test_persist_main_frame_on_cross_process_navigation(page, server):
130+
await page.goto(server.EMPTY_PAGE)
131+
main_frame = page.mainFrame
132+
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html')
133+
assert page.mainFrame == main_frame
134+
135+
async def test_should_not_send_attach_detach_events_for_main_frame(page, server):
136+
has_events = list()
137+
page.on('frameattached', lambda frame: has_events.append(True))
138+
page.on('framedetached', lambda frame: has_events.append(True))
139+
await page.goto(server.EMPTY_PAGE)
140+
assert has_events == []
141+
142+
async def test_detach_child_frames_on_navigation(page, server):
143+
attached_frames = []
144+
detached_frames = []
145+
navigated_frames = []
146+
page.on('frameattached', lambda frame: attached_frames.append(frame))
147+
page.on('framedetached', lambda frame: detached_frames.append(frame))
148+
page.on('framenavigated', lambda frame: navigated_frames.append(frame))
149+
await page.goto(server.PREFIX + '/frames/nested-frames.html')
150+
assert len(attached_frames) == 4
151+
assert len(detached_frames) == 0
152+
assert len(navigated_frames) == 5
153+
154+
attached_frames = []
155+
detached_frames = []
156+
navigated_frames = []
157+
await page.goto(server.EMPTY_PAGE)
158+
assert len(attached_frames) == 0
159+
assert len(detached_frames) == 4
160+
assert len(navigated_frames) == 1
161+
162+
async def test_framesets(page, server):
163+
attached_frames = []
164+
detached_frames = []
165+
navigated_frames = []
166+
page.on('frameattached', lambda frame: attached_frames.append(frame))
167+
page.on('framedetached', lambda frame: detached_frames.append(frame))
168+
page.on('framenavigated', lambda frame: navigated_frames.append(frame))
169+
await page.goto(server.PREFIX + '/frames/frameset.html')
170+
assert len(attached_frames) == 4
171+
assert len(detached_frames) == 0
172+
assert len(navigated_frames) == 5
173+
174+
attached_frames = []
175+
detached_frames = []
176+
navigated_frames = []
177+
await page.goto(server.EMPTY_PAGE)
178+
assert len(attached_frames) == 0
179+
assert len(detached_frames) == 4
180+
assert len(navigated_frames) == 1
181+
182+
async def test_frame_from_inside_shadow_dom(page, server):
183+
await page.goto(server.PREFIX + '/shadow.html')
184+
await page.evaluate('''async url => {
185+
frame = document.createElement('iframe');
186+
frame.src = url;
187+
document.body.shadowRoot.appendChild(frame);
188+
await new Promise(x => frame.onload = x);
189+
}''', server.EMPTY_PAGE)
190+
assert len(page.frames) == 2
191+
assert page.frames[1].url == server.EMPTY_PAGE
192+
193+
async def test_frame_name(page, server, utils):
194+
await utils.attach_frame(page, 'theFrameId', server.EMPTY_PAGE)
195+
await page.evaluate('''url => {
196+
frame = document.createElement('iframe');
197+
frame.name = 'theFrameName';
198+
frame.src = url;
199+
document.body.appendChild(frame);
200+
return new Promise(x => frame.onload = x);
201+
}''', server.EMPTY_PAGE)
202+
assert page.frames[0].name == ''
203+
assert page.frames[1].name == 'theFrameId'
204+
assert page.frames[2].name == 'theFrameName'
205+
206+
async def test_frame_parent(page, server, utils):
207+
await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
208+
await utils.attach_frame(page, 'frame2', server.EMPTY_PAGE)
209+
assert page.frames[0].parentFrame == None
210+
assert page.frames[1].parentFrame == page.mainFrame
211+
assert page.frames[2].parentFrame == page.mainFrame
212+
213+
async def test_should_report_different_frame_instance_when_frame_re_attaches(page, server, utils):
214+
frame1 = await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE)
215+
await page.evaluate('''() => {
216+
window.frame = document.querySelector('#frame1')
217+
window.frame.remove()
218+
}''')
219+
220+
assert frame1.isDetached()
221+
[frame2, _] = await asyncio.gather(
222+
page.waitForEvent('frameattached'),
223+
page.evaluate('() => document.body.appendChild(window.frame)'),
224+
)
225+
assert frame2.isDetached() == False
226+
assert frame1 != frame2

tests/utils.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,36 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import re
16+
from typing import List
17+
18+
from playwright.frame import Frame
19+
from playwright.page import Page
20+
1521
class Utils:
16-
async def attach_frame(self, page, frameId, url):
17-
handle = await page.evaluateHandle('''async ({ frameId, url }) => {
22+
async def attach_frame(self, page: Page, frame_id: str, url: str):
23+
handle = await page.evaluateHandle('''async ({ frame_id, url }) => {
1824
const frame = document.createElement('iframe');
1925
frame.src = url;
20-
frame.id = frameId;
26+
frame.id = frame_id;
2127
document.body.appendChild(frame);
2228
await new Promise(x => frame.onload = x);
2329
return frame;
24-
}''', { 'frameId': frameId, 'url': url })
30+
}''', { 'frame_id': frame_id, 'url': url })
2531
return await handle.asElement().contentFrame()
2632

33+
async def detach_frame(self, page: Page, frame_id: str):
34+
await page.evaluate('frame_id => document.getElementById(frame_id).remove()', frame_id)
35+
36+
def dump_frames(self, frame: Frame, indentation: str = '') -> List[str]:
37+
indentation = indentation or ''
38+
description = re.sub(r':\d+/', ':<PORT>/', frame.url)
39+
if frame.name:
40+
description += ' (' + frame.name + ')'
41+
result = [indentation + description]
42+
sorted_frames = sorted(frame.childFrames, key=lambda frame: frame.url + frame.name)
43+
for child in sorted_frames:
44+
result = result + utils.dump_frames(child, ' ' + indentation)
45+
return result
46+
2747
utils = Utils()

0 commit comments

Comments
 (0)