1/** 2 * Copyright 2018 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import utils from './utils.js'; 18import expect from 'expect'; 19import { 20 getTestState, 21 setupTestBrowserHooks, 22 setupTestPageAndContextHooks, 23 itFailsFirefox, 24} from './mocha-utils'; // eslint-disable-line import/extensions 25 26describe('Frame specs', function () { 27 setupTestBrowserHooks(); 28 setupTestPageAndContextHooks(); 29 30 describe('Frame.executionContext', function () { 31 it('should work', async () => { 32 const { page, server } = getTestState(); 33 34 await page.goto(server.EMPTY_PAGE); 35 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 36 expect(page.frames().length).toBe(2); 37 const [frame1, frame2] = page.frames(); 38 const context1 = await frame1.executionContext(); 39 const context2 = await frame2.executionContext(); 40 expect(context1).toBeTruthy(); 41 expect(context2).toBeTruthy(); 42 expect(context1 !== context2).toBeTruthy(); 43 expect(context1.frame()).toBe(frame1); 44 expect(context2.frame()).toBe(frame2); 45 46 await Promise.all([ 47 context1.evaluate(() => (globalThis.a = 1)), 48 context2.evaluate(() => (globalThis.a = 2)), 49 ]); 50 const [a1, a2] = await Promise.all([ 51 context1.evaluate(() => globalThis.a), 52 context2.evaluate(() => globalThis.a), 53 ]); 54 expect(a1).toBe(1); 55 expect(a2).toBe(2); 56 }); 57 }); 58 59 describe('Frame.evaluateHandle', function () { 60 it('should work', async () => { 61 const { page, server } = getTestState(); 62 63 await page.goto(server.EMPTY_PAGE); 64 const mainFrame = page.mainFrame(); 65 const windowHandle = await mainFrame.evaluateHandle(() => window); 66 expect(windowHandle).toBeTruthy(); 67 }); 68 }); 69 70 describe('Frame.evaluate', function () { 71 it('should throw for detached frames', async () => { 72 const { page, server } = getTestState(); 73 74 const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 75 await utils.detachFrame(page, 'frame1'); 76 let error = null; 77 await frame1.evaluate(() => 7 * 8).catch((error_) => (error = error_)); 78 expect(error.message).toContain( 79 'Execution context is not available in detached frame' 80 ); 81 }); 82 }); 83 84 describe('Frame Management', function () { 85 it('should handle nested frames', async () => { 86 const { page, server } = getTestState(); 87 88 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 89 expect(utils.dumpFrames(page.mainFrame())).toEqual([ 90 'http://localhost:<PORT>/frames/nested-frames.html', 91 ' http://localhost:<PORT>/frames/two-frames.html (2frames)', 92 ' http://localhost:<PORT>/frames/frame.html (uno)', 93 ' http://localhost:<PORT>/frames/frame.html (dos)', 94 ' http://localhost:<PORT>/frames/frame.html (aframe)', 95 ]); 96 }); 97 it( 98 'should send events when frames are manipulated dynamically', 99 async () => { 100 const { page, server } = getTestState(); 101 102 await page.goto(server.EMPTY_PAGE); 103 // validate frameattached events 104 const attachedFrames = []; 105 page.on('frameattached', (frame) => attachedFrames.push(frame)); 106 await utils.attachFrame(page, 'frame1', './assets/frame.html'); 107 expect(attachedFrames.length).toBe(1); 108 expect(attachedFrames[0].url()).toContain('/assets/frame.html'); 109 110 // validate framenavigated events 111 const navigatedFrames = []; 112 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 113 await utils.navigateFrame(page, 'frame1', './empty.html'); 114 expect(navigatedFrames.length).toBe(1); 115 expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); 116 117 // validate framedetached events 118 const detachedFrames = []; 119 page.on('framedetached', (frame) => detachedFrames.push(frame)); 120 await utils.detachFrame(page, 'frame1'); 121 expect(detachedFrames.length).toBe(1); 122 expect(detachedFrames[0].isDetached()).toBe(true); 123 } 124 ); 125 it( 126 'should send "framenavigated" when navigating on anchor URLs', 127 async () => { 128 const { page, server } = getTestState(); 129 130 await page.goto(server.EMPTY_PAGE); 131 await Promise.all([ 132 page.goto(server.EMPTY_PAGE + '#foo'), 133 utils.waitEvent(page, 'framenavigated'), 134 ]); 135 expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); 136 } 137 ); 138 it('should persist mainFrame on cross-process navigation', async () => { 139 const { page, server } = getTestState(); 140 141 await page.goto(server.EMPTY_PAGE); 142 const mainFrame = page.mainFrame(); 143 await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); 144 expect(page.mainFrame() === mainFrame).toBeTruthy(); 145 }); 146 it('should not send attach/detach events for main frame', async () => { 147 const { page, server } = getTestState(); 148 149 let hasEvents = false; 150 page.on('frameattached', () => (hasEvents = true)); 151 page.on('framedetached', () => (hasEvents = true)); 152 await page.goto(server.EMPTY_PAGE); 153 expect(hasEvents).toBe(false); 154 }); 155 it('should detach child frames on navigation', async () => { 156 const { page, server } = getTestState(); 157 158 let attachedFrames = []; 159 let detachedFrames = []; 160 let navigatedFrames = []; 161 page.on('frameattached', (frame) => attachedFrames.push(frame)); 162 page.on('framedetached', (frame) => detachedFrames.push(frame)); 163 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 164 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 165 expect(attachedFrames.length).toBe(4); 166 expect(detachedFrames.length).toBe(0); 167 expect(navigatedFrames.length).toBe(5); 168 169 attachedFrames = []; 170 detachedFrames = []; 171 navigatedFrames = []; 172 await page.goto(server.EMPTY_PAGE); 173 expect(attachedFrames.length).toBe(0); 174 expect(detachedFrames.length).toBe(4); 175 expect(navigatedFrames.length).toBe(1); 176 }); 177 it('should support framesets', async () => { 178 const { page, server } = getTestState(); 179 180 let attachedFrames = []; 181 let detachedFrames = []; 182 let navigatedFrames = []; 183 page.on('frameattached', (frame) => attachedFrames.push(frame)); 184 page.on('framedetached', (frame) => detachedFrames.push(frame)); 185 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 186 await page.goto(server.PREFIX + '/frames/frameset.html'); 187 expect(attachedFrames.length).toBe(4); 188 expect(detachedFrames.length).toBe(0); 189 expect(navigatedFrames.length).toBe(5); 190 191 attachedFrames = []; 192 detachedFrames = []; 193 navigatedFrames = []; 194 await page.goto(server.EMPTY_PAGE); 195 expect(attachedFrames.length).toBe(0); 196 expect(detachedFrames.length).toBe(4); 197 expect(navigatedFrames.length).toBe(1); 198 }); 199 it('should report frame from-inside shadow DOM', async () => { 200 const { page, server } = getTestState(); 201 202 await page.goto(server.PREFIX + '/shadow.html'); 203 await page.evaluate(async (url: string) => { 204 const frame = document.createElement('iframe'); 205 frame.src = url; 206 document.body.shadowRoot.appendChild(frame); 207 await new Promise((x) => (frame.onload = x)); 208 }, server.EMPTY_PAGE); 209 expect(page.frames().length).toBe(2); 210 expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE); 211 }); 212 it('should report frame.name()', async () => { 213 const { page, server } = getTestState(); 214 215 await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); 216 await page.evaluate((url: string) => { 217 const frame = document.createElement('iframe'); 218 frame.name = 'theFrameName'; 219 frame.src = url; 220 document.body.appendChild(frame); 221 return new Promise((x) => (frame.onload = x)); 222 }, server.EMPTY_PAGE); 223 expect(page.frames()[0].name()).toBe(''); 224 expect(page.frames()[1].name()).toBe('theFrameId'); 225 expect(page.frames()[2].name()).toBe('theFrameName'); 226 }); 227 it('should report frame.parent()', async () => { 228 const { page, server } = getTestState(); 229 230 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 231 await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); 232 expect(page.frames()[0].parentFrame()).toBe(null); 233 expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); 234 expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); 235 }); 236 it( 237 'should report different frame instance when frame re-attaches', 238 async () => { 239 const { page, server } = getTestState(); 240 241 const frame1 = await utils.attachFrame( 242 page, 243 'frame1', 244 server.EMPTY_PAGE 245 ); 246 await page.evaluate(() => { 247 globalThis.frame = document.querySelector('#frame1'); 248 globalThis.frame.remove(); 249 }); 250 expect(frame1.isDetached()).toBe(true); 251 const [frame2] = await Promise.all([ 252 utils.waitEvent(page, 'frameattached'), 253 page.evaluate(() => document.body.appendChild(globalThis.frame)), 254 ]); 255 expect(frame2.isDetached()).toBe(false); 256 expect(frame1).not.toBe(frame2); 257 } 258 ); 259 it('should support url fragment', async () => { 260 const { page, server } = getTestState(); 261 262 await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html'); 263 264 expect(page.frames().length).toBe(2); 265 expect(page.frames()[1].url()).toBe( 266 server.PREFIX + '/frames/frame.html?param=value#fragment' 267 ); 268 }); 269 }); 270}); 271