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'; 18const { waitEvent } = utils; 19import expect from 'expect'; 20import { 21 getTestState, 22 setupTestBrowserHooks, 23 setupTestPageAndContextHooks, 24 itFailsFirefox, 25} from './mocha-utils'; // eslint-disable-line import/extensions 26import { Target } from '../lib/cjs/puppeteer/common/Target.js'; 27 28describe('Target', function () { 29 setupTestBrowserHooks(); 30 setupTestPageAndContextHooks(); 31 32 it('Browser.targets should return all of the targets', async () => { 33 const { browser } = getTestState(); 34 35 // The pages will be the testing page and the original newtab page 36 const targets = browser.targets(); 37 expect( 38 targets.some( 39 (target) => target.type() === 'page' && target.url() === 'about:blank' 40 ) 41 ).toBeTruthy(); 42 expect(targets.some((target) => target.type() === 'browser')).toBeTruthy(); 43 }); 44 it('Browser.pages should return all of the pages', async () => { 45 const { page, context } = getTestState(); 46 47 // The pages will be the testing page 48 const allPages = await context.pages(); 49 expect(allPages.length).toBe(1); 50 expect(allPages).toContain(page); 51 }); 52 it('should contain browser target', async () => { 53 const { browser } = getTestState(); 54 55 const targets = browser.targets(); 56 const browserTarget = targets.find((target) => target.type() === 'browser'); 57 expect(browserTarget).toBeTruthy(); 58 }); 59 it('should be able to use the default page in the browser', async () => { 60 const { page, browser } = getTestState(); 61 62 // The pages will be the testing page and the original newtab page 63 const allPages = await browser.pages(); 64 const originalPage = allPages.find((p) => p !== page); 65 expect( 66 await originalPage.evaluate(() => ['Hello', 'world'].join(' ')) 67 ).toBe('Hello world'); 68 expect(await originalPage.$('body')).toBeTruthy(); 69 }); 70 it( 71 'should report when a new page is created and closed', 72 async () => { 73 const { page, server, context } = getTestState(); 74 75 const [otherPage] = await Promise.all([ 76 context 77 .waitForTarget( 78 (target) => 79 target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html' 80 ) 81 .then((target) => target.page()), 82 page.evaluate( 83 (url: string) => window.open(url), 84 server.CROSS_PROCESS_PREFIX + '/empty.html' 85 ), 86 ]); 87 expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); 88 expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe( 89 'Hello world' 90 ); 91 expect(await otherPage.$('body')).toBeTruthy(); 92 93 let allPages = await context.pages(); 94 expect(allPages).toContain(page); 95 expect(allPages).toContain(otherPage); 96 97 const closePagePromise = new Promise((fulfill) => 98 context.once('targetdestroyed', (target) => fulfill(target.page())) 99 ); 100 await otherPage.close(); 101 expect(await closePagePromise).toBe(otherPage); 102 103 allPages = await Promise.all( 104 context.targets().map((target) => target.page()) 105 ); 106 expect(allPages).toContain(page); 107 expect(allPages).not.toContain(otherPage); 108 } 109 ); 110 it( 111 'should report when a service worker is created and destroyed', 112 async () => { 113 const { page, server, context } = getTestState(); 114 115 await page.goto(server.EMPTY_PAGE); 116 const createdTarget = new Promise<Target>((fulfill) => 117 context.once('targetcreated', (target) => fulfill(target)) 118 ); 119 120 await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); 121 122 expect((await createdTarget).type()).toBe('service_worker'); 123 expect((await createdTarget).url()).toBe( 124 server.PREFIX + '/serviceworkers/empty/sw.js' 125 ); 126 127 const destroyedTarget = new Promise((fulfill) => 128 context.once('targetdestroyed', (target) => fulfill(target)) 129 ); 130 await page.evaluate(() => 131 globalThis.registrationPromise.then((registration) => 132 registration.unregister() 133 ) 134 ); 135 expect(await destroyedTarget).toBe(await createdTarget); 136 } 137 ); 138 it('should create a worker from a service worker', async () => { 139 const { page, server, context } = getTestState(); 140 141 await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); 142 143 const target = await context.waitForTarget( 144 (target) => target.type() === 'service_worker' 145 ); 146 const worker = await target.worker(); 147 expect(await worker.evaluate(() => self.toString())).toBe( 148 '[object ServiceWorkerGlobalScope]' 149 ); 150 }); 151 it('should create a worker from a shared worker', async () => { 152 const { page, server, context } = getTestState(); 153 154 await page.goto(server.EMPTY_PAGE); 155 await page.evaluate(() => { 156 new SharedWorker('data:text/javascript,console.log("hi")'); 157 }); 158 const target = await context.waitForTarget( 159 (target) => target.type() === 'shared_worker' 160 ); 161 const worker = await target.worker(); 162 expect(await worker.evaluate(() => self.toString())).toBe( 163 '[object SharedWorkerGlobalScope]' 164 ); 165 }); 166 it('should report when a target url changes', async () => { 167 const { page, server, context } = getTestState(); 168 169 await page.goto(server.EMPTY_PAGE); 170 let changedTarget = new Promise<Target>((fulfill) => 171 context.once('targetchanged', (target) => fulfill(target)) 172 ); 173 await page.goto(server.CROSS_PROCESS_PREFIX + '/'); 174 expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/'); 175 176 changedTarget = new Promise((fulfill) => 177 context.once('targetchanged', (target) => fulfill(target)) 178 ); 179 await page.goto(server.EMPTY_PAGE); 180 expect((await changedTarget).url()).toBe(server.EMPTY_PAGE); 181 }); 182 it('should not report uninitialized pages', async () => { 183 const { context } = getTestState(); 184 185 let targetChanged = false; 186 const listener = () => (targetChanged = true); 187 context.on('targetchanged', listener); 188 const targetPromise = new Promise<Target>((fulfill) => 189 context.once('targetcreated', (target) => fulfill(target)) 190 ); 191 const newPagePromise = context.newPage(); 192 const target = await targetPromise; 193 expect(target.url()).toBe('about:blank'); 194 195 const newPage = await newPagePromise; 196 const targetPromise2 = new Promise<Target>((fulfill) => 197 context.once('targetcreated', (target) => fulfill(target)) 198 ); 199 const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); 200 const target2 = await targetPromise2; 201 expect(target2.url()).toBe('about:blank'); 202 await evaluatePromise; 203 await newPage.close(); 204 expect(targetChanged).toBe(false); 205 context.removeListener('targetchanged', listener); 206 }); 207 it( 208 'should not crash while redirecting if original request was missed', 209 async () => { 210 const { page, server, context } = getTestState(); 211 212 let serverResponse = null; 213 server.setRoute('/one-style.css', (req, res) => (serverResponse = res)); 214 // Open a new page. Use window.open to connect to the page later. 215 await Promise.all([ 216 page.evaluate( 217 (url: string) => window.open(url), 218 server.PREFIX + '/one-style.html' 219 ), 220 server.waitForRequest('/one-style.css'), 221 ]); 222 // Connect to the opened page. 223 const target = await context.waitForTarget((target) => 224 target.url().includes('one-style.html') 225 ); 226 const newPage = await target.page(); 227 // Issue a redirect. 228 serverResponse.writeHead(302, { location: '/injectedstyle.css' }); 229 serverResponse.end(); 230 // Wait for the new page to load. 231 await waitEvent(newPage, 'load'); 232 // Cleanup. 233 await newPage.close(); 234 } 235 ); 236 it('should have an opener', async () => { 237 const { page, server, context } = getTestState(); 238 239 await page.goto(server.EMPTY_PAGE); 240 const [createdTarget] = await Promise.all([ 241 new Promise<Target>((fulfill) => 242 context.once('targetcreated', (target) => fulfill(target)) 243 ), 244 page.goto(server.PREFIX + '/popup/window-open.html'), 245 ]); 246 expect((await createdTarget.page()).url()).toBe( 247 server.PREFIX + '/popup/popup.html' 248 ); 249 expect(createdTarget.opener()).toBe(page.target()); 250 expect(page.target().opener()).toBe(null); 251 }); 252 253 describe('Browser.waitForTarget', () => { 254 it('should wait for a target', async () => { 255 const { browser, puppeteer, server } = getTestState(); 256 257 let resolved = false; 258 const targetPromise = browser.waitForTarget( 259 (target) => target.url() === server.EMPTY_PAGE 260 ); 261 targetPromise 262 .then(() => (resolved = true)) 263 .catch((error) => { 264 resolved = true; 265 if (error instanceof puppeteer.errors.TimeoutError) { 266 console.error(error); 267 } else throw error; 268 }); 269 const page = await browser.newPage(); 270 expect(resolved).toBe(false); 271 await page.goto(server.EMPTY_PAGE); 272 try { 273 const target = await targetPromise; 274 expect(await target.page()).toBe(page); 275 } catch (error) { 276 if (error instanceof puppeteer.errors.TimeoutError) { 277 console.error(error); 278 } else throw error; 279 } 280 await page.close(); 281 }); 282 it('should timeout waiting for a non-existent target', async () => { 283 const { browser, server, puppeteer } = getTestState(); 284 285 let error = null; 286 await browser 287 .waitForTarget((target) => target.url() === server.EMPTY_PAGE, { 288 timeout: 1, 289 }) 290 .catch((error_) => (error = error_)); 291 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 292 }); 293 }); 294}); 295