Skip to content

Commit

Permalink
add RSpec: frame_spec
Browse files Browse the repository at this point in the history
  • Loading branch information
YusukeIwaki committed Sep 21, 2020
1 parent c8613da commit 0ef7951
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 3 deletions.
4 changes: 1 addition & 3 deletions lib/puppeteer/frame.rb
Expand Up @@ -266,9 +266,7 @@ def title
# @param frame_payload [Hash]
def navigated(frame_payload)
@name = frame_payload['name']
# TODO(lushnikov): remove this once requestInterception has loaderId exposed.
@navigation_url = frame_payload['url']
@url = frame_payload['url']
@url = "#{frame_payload['url']}#{frame_payload['urlFragment']}"

# Ensure loaderId updated.
# The order of [Page.lifecycleEvent name="init"] and [Page.frameNavigated] is random... for some reason...
Expand Down
334 changes: 334 additions & 0 deletions spec/integration/frame_spec.rb
@@ -0,0 +1,334 @@
require 'spec_helper'

RSpec.describe Puppeteer::Frame do
context 'with empty page' do
sinatra do
get('/') do
'<html><body>Hello puppeteer!</body></html>'
end
end

describe '#execution_context' do
include Utils::AttachFrame

it 'should work' do
page.goto('http://127.0.0.1:4567/')
attach_frame(page, 'frame1', '/')
expect(page.frames.size).to eq(2)

frames = page.frames
contexts = frames.map(&:execution_context)
expect(contexts).to all(be_truthy)
expect(contexts.first).not_to eq(contexts.last)
expect(contexts.map(&:frame)).to eq(frames)

contexts.each_with_index do |context, i|
context.evaluate("() => (globalThis.a = #{i + 1})")
end
values = contexts.map { |context| context.evaluate('() => globalThis.a') }
expect(values).to eq([1, 2])
end
end

describe '#evaluate_handle' do
it 'should work' do
page.goto('http://127.0.0.1:4567/')
main_frame = page.main_frame
window_handle = main_frame.evaluate_handle('() => window')
expect(window_handle).to be_truthy
end
end

describe '#evaluate' do
include Utils::AttachFrame
include Utils::DetachFrame

it 'should throw for detached frames' do
page.goto('http://127.0.0.1:4567/')
frame1 = attach_frame(page, 'frame1', '/')
detach_frame(page, 'frame1')
expect {
frame1.evaluate('() => 7 * 8')
}.to raise_error(/Execution Context is not available in detached frame/)
end
end
end

context 'with nested frames page' do
sinatra do
get('/nested-frames.html') do
<<~HTML
<style>
body {
display: flex;
}
body iframe {
flex-grow: 1;
flex-shrink: 1;
}
::-webkit-scrollbar{
display: none;
}
html { /* for Firefox */
scrollbar-width: none;
}
</style>
<script>
async function attachFrame(frameId, url) {
var frame = document.createElement('iframe');
frame.src = url;
frame.id = frameId;
document.body.appendChild(frame);
await new Promise(x => frame.onload = x);
return 'kazakh';
}
</script>
<iframe src='./two-frames.html' name='2frames'></iframe>
<iframe src='./frame.html' name='aframe'></iframe>
HTML
end
get('/two-frames.html') do
<<~HTML
<style>
body {
display: flex;
flex-direction: column;
}
body iframe {
flex-grow: 1;
flex-shrink: 1;
}
html { /* for Firefox */
scrollbar-width: none;
}
</style>
<iframe src='./frame.html' name='uno'></iframe>
<iframe src='./frame.html' name='dos'></iframe>
HTML
end
get('/frame.html') do
<<~HTML
<script src='./script.js' type='text/javascript'></script>
<style>
div {
color: blue;
line-height: 18px;
}
</style>
<div>Hi, I'm frame</div>
HTML
end
end

include Utils::DumpFrames

it 'should handle nested frames' do
page.goto('http://127.0.0.1:4567/nested-frames.html')
expect(dump_frames(page.main_frame)).to eq([
'http://127.0.0.1:<PORT>/nested-frames.html',
' http://127.0.0.1:<PORT>/two-frames.html (2frames)',
' http://127.0.0.1:<PORT>/frame.html (uno)',
' http://127.0.0.1:<PORT>/frame.html (dos)',
' http://127.0.0.1:<PORT>/frame.html (aframe)',
])
end
end

# itFailsFirefox(
# 'should send events when frames are manipulated dynamically',
# async () => {
# const { page, server } = getTestState();

# await page.goto(server.EMPTY_PAGE);
# // validate frameattached events
# const attachedFrames = [];
# page.on('frameattached', (frame) => attachedFrames.push(frame));
# await utils.attachFrame(page, 'frame1', './assets/frame.html');
# expect(attachedFrames.length).toBe(1);
# expect(attachedFrames[0].url()).toContain('/assets/frame.html');

# // validate framenavigated events
# const navigatedFrames = [];
# page.on('framenavigated', (frame) => navigatedFrames.push(frame));
# await utils.navigateFrame(page, 'frame1', './empty.html');
# expect(navigatedFrames.length).toBe(1);
# expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE);

# // validate framedetached events
# const detachedFrames = [];
# page.on('framedetached', (frame) => detachedFrames.push(frame));
# await utils.detachFrame(page, 'frame1');
# expect(detachedFrames.length).toBe(1);
# expect(detachedFrames[0].isDetached()).toBe(true);
# }
# );
# itFailsFirefox(
# 'should send "framenavigated" when navigating on anchor URLs',
# async () => {
# const { page, server } = getTestState();

# await page.goto(server.EMPTY_PAGE);
# await Promise.all([
# page.goto(server.EMPTY_PAGE + '#foo'),
# utils.waitEvent(page, 'framenavigated'),
# ]);
# expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
# }
# );
# it('should persist mainFrame on cross-process navigation', async () => {
# const { page, server } = getTestState();

# await page.goto(server.EMPTY_PAGE);
# const mainFrame = page.mainFrame();
# await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
# expect(page.mainFrame() === mainFrame).toBeTruthy();
# });
# it('should not send attach/detach events for main frame', async () => {
# const { page, server } = getTestState();

# let hasEvents = false;
# page.on('frameattached', () => (hasEvents = true));
# page.on('framedetached', () => (hasEvents = true));
# await page.goto(server.EMPTY_PAGE);
# expect(hasEvents).toBe(false);
# });
# itFailsFirefox('should detach child frames on navigation', async () => {
# const { page, server } = getTestState();

# let attachedFrames = [];
# let detachedFrames = [];
# let navigatedFrames = [];
# page.on('frameattached', (frame) => attachedFrames.push(frame));
# page.on('framedetached', (frame) => detachedFrames.push(frame));
# page.on('framenavigated', (frame) => navigatedFrames.push(frame));
# await page.goto(server.PREFIX + '/frames/nested-frames.html');
# expect(attachedFrames.length).toBe(4);
# expect(detachedFrames.length).toBe(0);
# expect(navigatedFrames.length).toBe(5);

# attachedFrames = [];
# detachedFrames = [];
# navigatedFrames = [];
# await page.goto(server.EMPTY_PAGE);
# expect(attachedFrames.length).toBe(0);
# expect(detachedFrames.length).toBe(4);
# expect(navigatedFrames.length).toBe(1);
# });
# itFailsFirefox('should support framesets', async () => {
# const { page, server } = getTestState();

# let attachedFrames = [];
# let detachedFrames = [];
# let navigatedFrames = [];
# page.on('frameattached', (frame) => attachedFrames.push(frame));
# page.on('framedetached', (frame) => detachedFrames.push(frame));
# page.on('framenavigated', (frame) => navigatedFrames.push(frame));
# await page.goto(server.PREFIX + '/frames/frameset.html');
# expect(attachedFrames.length).toBe(4);
# expect(detachedFrames.length).toBe(0);
# expect(navigatedFrames.length).toBe(5);

# attachedFrames = [];
# detachedFrames = [];
# navigatedFrames = [];
# await page.goto(server.EMPTY_PAGE);
# expect(attachedFrames.length).toBe(0);
# expect(detachedFrames.length).toBe(4);
# expect(navigatedFrames.length).toBe(1);
# });
# itFailsFirefox('should report frame from-inside shadow DOM', async () => {
# const { page, server } = getTestState();

# await page.goto(server.PREFIX + '/shadow.html');
# await page.evaluate(async (url: string) => {
# const frame = document.createElement('iframe');
# frame.src = url;
# document.body.shadowRoot.appendChild(frame);
# await new Promise((x) => (frame.onload = x));
# }, server.EMPTY_PAGE);
# expect(page.frames().length).toBe(2);
# expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
# });

context 'with empty page' do
include Utils::AttachFrame

sinatra do
get('/') do
'<html><body>Hello puppeteer!</body></html>'
end
end

before { page.goto('http://127.0.0.1:4567/') }

it 'should report frame.name()' do
attach_frame(page, 'theFrameId', '/')
js = <<~JAVASCRIPT
function (url) {
const frame = document.createElement('iframe');
frame.name = 'theFrameName';
frame.src = url;
document.body.appendChild(frame);
return new Promise((x) => (frame.onload = x));
}
JAVASCRIPT
page.evaluate(js, '/')
expect(page.frames.map(&:name)).to eq(['', 'theFrameId', 'theFrameName'])
end

it 'should report frame.parent()' do
attach_frame(page, 'frame1', '/')
attach_frame(page, 'frame2', '/')
expect(page.frames.map(&:parent_frame)).to eq([nil, page.main_frame, page.main_frame])
end

it 'should report different frame instance when frame re-attaches' do
frame1 = attach_frame(page, 'frame1', '/')
js = <<~JAVASCRIPT
() => {
globalThis.frame = document.querySelector('#frame1');
globalThis.frame.remove();
}
JAVASCRIPT
page.evaluate(js)
expect(frame1).to be_detached

frame2 = await_all(
resolvable_future { |f| page.once('frameattached') { |frame| f.fulfill(frame) }},
page.async_evaluate('() => document.body.appendChild(globalThis.frame)'),
).first
expect(frame2).not_to be_detached
expect(frame1).not_to eq(frame2)
end
end

context 'with one-frame-url-fragment page' do
sinatra do
get('/one-frame-url-fragment.html') do
"<iframe src='./frame.html?param=value#fragment'></iframe>"
end
get('/frame.html') do
<<~HTML
<link rel='stylesheet' href='./style.css'>
<script src='./script.js' type='text/javascript'></script>
<style>
div {
line-height: 18px;
}
</style>
<div>Hi, I'm frame</div>
HTML
end
end

it 'should support url fragment' do
page.goto('http://127.0.0.1:4567/one-frame-url-fragment.html')

expect(page.frames.size).to eq(2)
expect(page.frames.last.url).to eq('http://127.0.0.1:4567/frame.html?param=value#fragment')
end
end
end
24 changes: 24 additions & 0 deletions spec/utils.rb
Expand Up @@ -15,3 +15,27 @@ def attach_frame(page, frame_id, url)
page.evaluate_handle(js, frame_id, url).as_element.content_frame
end
end

module Utils::DetachFrame
def detach_frame(page, frame_id)
js = <<~JAVASCRIPT
function detachFrame(frameId) {
const frame = document.getElementById(frameId);
frame.remove();
}
JAVASCRIPT
page.evaluate(js, frame_id)
end
end

module Utils::DumpFrames
def dump_frames(frame, indentation = '')
description = frame.url.gsub(/:\d{4}\//, ':<PORT>/')
if frame.name && frame.name.length > 0
description = "#{description} (#{frame.name})"
end
["#{indentation}#{description}"] + frame.child_frames.flat_map do |child|
dump_frames(child, " #{indentation}")
end
end
end

0 comments on commit 0ef7951

Please sign in to comment.