From 0ef79516276ff83b2cb6421d242d586259a82ed0 Mon Sep 17 00:00:00 2001 From: YusukeIwaki Date: Mon, 21 Sep 2020 23:38:09 +0900 Subject: [PATCH] add RSpec: frame_spec --- lib/puppeteer/frame.rb | 4 +- spec/integration/frame_spec.rb | 334 +++++++++++++++++++++++++++++++++ spec/utils.rb | 24 +++ 3 files changed, 359 insertions(+), 3 deletions(-) create mode 100644 spec/integration/frame_spec.rb diff --git a/lib/puppeteer/frame.rb b/lib/puppeteer/frame.rb index c51b777a..419b31e5 100644 --- a/lib/puppeteer/frame.rb +++ b/lib/puppeteer/frame.rb @@ -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... diff --git a/spec/integration/frame_spec.rb b/spec/integration/frame_spec.rb new file mode 100644 index 00000000..9d686a84 --- /dev/null +++ b/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 + 'Hello puppeteer!' + 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 + + + + + HTML + end + get('/two-frames.html') do + <<~HTML + + + + HTML + end + get('/frame.html') do + <<~HTML + + +
Hi, I'm frame
+ 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:/nested-frames.html', + ' http://127.0.0.1:/two-frames.html (2frames)', + ' http://127.0.0.1:/frame.html (uno)', + ' http://127.0.0.1:/frame.html (dos)', + ' http://127.0.0.1:/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 + 'Hello puppeteer!' + 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 + "" + end + get('/frame.html') do + <<~HTML + + + +
Hi, I'm frame
+ 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 diff --git a/spec/utils.rb b/spec/utils.rb index 9357cb7f..e961a11a 100644 --- a/spec/utils.rb +++ b/spec/utils.rb @@ -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}\//, ':/') + 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