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 it('allows readonly array to be an argument', async () => { 84 const { page, server } = getTestState(); 85 await page.goto(server.EMPTY_PAGE); 86 const mainFrame = page.mainFrame(); 87 88 // This test checks if Frame.evaluate allows a readonly array to be an argument. 89 // See https://github.com/puppeteer/puppeteer/issues/6953. 90 const readonlyArray: readonly string[] = ['a', 'b', 'c']; 91 await mainFrame.evaluate((arr) => arr, readonlyArray); 92 }); 93 }); 94 95 describe('Frame Management', function () { 96 it('should handle nested frames', async () => { 97 const { page, server } = getTestState(); 98 99 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 100 expect(utils.dumpFrames(page.mainFrame())).toEqual([ 101 'http://localhost:<PORT>/frames/nested-frames.html', 102 ' http://localhost:<PORT>/frames/two-frames.html (2frames)', 103 ' http://localhost:<PORT>/frames/frame.html (uno)', 104 ' http://localhost:<PORT>/frames/frame.html (dos)', 105 ' http://localhost:<PORT>/frames/frame.html (aframe)', 106 ]); 107 }); 108 it( 109 'should send events when frames are manipulated dynamically', 110 async () => { 111 const { page, server } = getTestState(); 112 113 await page.goto(server.EMPTY_PAGE); 114 // validate frameattached events 115 const attachedFrames = []; 116 page.on('frameattached', (frame) => attachedFrames.push(frame)); 117 await utils.attachFrame(page, 'frame1', './assets/frame.html'); 118 expect(attachedFrames.length).toBe(1); 119 expect(attachedFrames[0].url()).toContain('/assets/frame.html'); 120 121 // validate framenavigated events 122 const navigatedFrames = []; 123 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 124 await utils.navigateFrame(page, 'frame1', './empty.html'); 125 expect(navigatedFrames.length).toBe(1); 126 expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); 127 128 // validate framedetached events 129 const detachedFrames = []; 130 page.on('framedetached', (frame) => detachedFrames.push(frame)); 131 await utils.detachFrame(page, 'frame1'); 132 expect(detachedFrames.length).toBe(1); 133 expect(detachedFrames[0].isDetached()).toBe(true); 134 } 135 ); 136 it( 137 'should send "framenavigated" when navigating on anchor URLs', 138 async () => { 139 const { page, server } = getTestState(); 140 141 await page.goto(server.EMPTY_PAGE); 142 await Promise.all([ 143 page.goto(server.EMPTY_PAGE + '#foo'), 144 utils.waitEvent(page, 'framenavigated'), 145 ]); 146 expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); 147 } 148 ); 149 it('should persist mainFrame on cross-process navigation', async () => { 150 const { page, server } = getTestState(); 151 152 await page.goto(server.EMPTY_PAGE); 153 const mainFrame = page.mainFrame(); 154 await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); 155 expect(page.mainFrame() === mainFrame).toBeTruthy(); 156 }); 157 it('should not send attach/detach events for main frame', async () => { 158 const { page, server } = getTestState(); 159 160 let hasEvents = false; 161 page.on('frameattached', () => (hasEvents = true)); 162 page.on('framedetached', () => (hasEvents = true)); 163 await page.goto(server.EMPTY_PAGE); 164 expect(hasEvents).toBe(false); 165 }); 166 it('should detach child frames on navigation', async () => { 167 const { page, server } = getTestState(); 168 169 let attachedFrames = []; 170 let detachedFrames = []; 171 let navigatedFrames = []; 172 page.on('frameattached', (frame) => attachedFrames.push(frame)); 173 page.on('framedetached', (frame) => detachedFrames.push(frame)); 174 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 175 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 176 expect(attachedFrames.length).toBe(4); 177 expect(detachedFrames.length).toBe(0); 178 expect(navigatedFrames.length).toBe(5); 179 180 attachedFrames = []; 181 detachedFrames = []; 182 navigatedFrames = []; 183 await page.goto(server.EMPTY_PAGE); 184 expect(attachedFrames.length).toBe(0); 185 expect(detachedFrames.length).toBe(4); 186 expect(navigatedFrames.length).toBe(1); 187 }); 188 it('should support framesets', async () => { 189 const { page, server } = getTestState(); 190 191 let attachedFrames = []; 192 let detachedFrames = []; 193 let navigatedFrames = []; 194 page.on('frameattached', (frame) => attachedFrames.push(frame)); 195 page.on('framedetached', (frame) => detachedFrames.push(frame)); 196 page.on('framenavigated', (frame) => navigatedFrames.push(frame)); 197 await page.goto(server.PREFIX + '/frames/frameset.html'); 198 expect(attachedFrames.length).toBe(4); 199 expect(detachedFrames.length).toBe(0); 200 expect(navigatedFrames.length).toBe(5); 201 202 attachedFrames = []; 203 detachedFrames = []; 204 navigatedFrames = []; 205 await page.goto(server.EMPTY_PAGE); 206 expect(attachedFrames.length).toBe(0); 207 expect(detachedFrames.length).toBe(4); 208 expect(navigatedFrames.length).toBe(1); 209 }); 210 it('should report frame from-inside shadow DOM', async () => { 211 const { page, server } = getTestState(); 212 213 await page.goto(server.PREFIX + '/shadow.html'); 214 await page.evaluate(async (url: string) => { 215 const frame = document.createElement('iframe'); 216 frame.src = url; 217 document.body.shadowRoot.appendChild(frame); 218 await new Promise((x) => (frame.onload = x)); 219 }, server.EMPTY_PAGE); 220 expect(page.frames().length).toBe(2); 221 expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE); 222 }); 223 it('should report frame.name()', async () => { 224 const { page, server } = getTestState(); 225 226 await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); 227 await page.evaluate((url: string) => { 228 const frame = document.createElement('iframe'); 229 frame.name = 'theFrameName'; 230 frame.src = url; 231 document.body.appendChild(frame); 232 return new Promise((x) => (frame.onload = x)); 233 }, server.EMPTY_PAGE); 234 expect(page.frames()[0].name()).toBe(''); 235 expect(page.frames()[1].name()).toBe('theFrameId'); 236 expect(page.frames()[2].name()).toBe('theFrameName'); 237 }); 238 it('should report frame.parent()', async () => { 239 const { page, server } = getTestState(); 240 241 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 242 await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); 243 expect(page.frames()[0].parentFrame()).toBe(null); 244 expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); 245 expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); 246 }); 247 it( 248 'should report different frame instance when frame re-attaches', 249 async () => { 250 const { page, server } = getTestState(); 251 252 const frame1 = await utils.attachFrame( 253 page, 254 'frame1', 255 server.EMPTY_PAGE 256 ); 257 await page.evaluate(() => { 258 globalThis.frame = document.querySelector('#frame1'); 259 globalThis.frame.remove(); 260 }); 261 expect(frame1.isDetached()).toBe(true); 262 const [frame2] = await Promise.all([ 263 utils.waitEvent(page, 'frameattached'), 264 page.evaluate(() => document.body.appendChild(globalThis.frame)), 265 ]); 266 expect(frame2.isDetached()).toBe(false); 267 expect(frame1).not.toBe(frame2); 268 } 269 ); 270 it('should support url fragment', async () => { 271 const { page, server } = getTestState(); 272 273 await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html'); 274 275 expect(page.frames().length).toBe(2); 276 expect(page.frames()[1].url()).toBe( 277 server.PREFIX + '/frames/frame.html?param=value#fragment' 278 ); 279 }); 280 }); 281}); 282