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 describeFailsFirefox, 25} from './mocha-utils'; // eslint-disable-line import/extensions 26 27const bigint = typeof BigInt !== 'undefined'; 28 29describe('Evaluation specs', function () { 30 setupTestBrowserHooks(); 31 setupTestPageAndContextHooks(); 32 33 describe('Page.evaluate', function () { 34 it('should work', async () => { 35 const { page } = getTestState(); 36 37 const result = await page.evaluate(() => 7 * 3); 38 expect(result).toBe(21); 39 }); 40 (bigint ? it : xit)('should transfer BigInt', async () => { 41 const { page } = getTestState(); 42 43 const result = await page.evaluate((a: BigInt) => a, BigInt(42)); 44 expect(result).toBe(BigInt(42)); 45 }); 46 it('should transfer NaN', async () => { 47 const { page } = getTestState(); 48 49 const result = await page.evaluate((a) => a, NaN); 50 expect(Object.is(result, NaN)).toBe(true); 51 }); 52 it('should transfer -0', async () => { 53 const { page } = getTestState(); 54 55 const result = await page.evaluate((a) => a, -0); 56 expect(Object.is(result, -0)).toBe(true); 57 }); 58 it('should transfer Infinity', async () => { 59 const { page } = getTestState(); 60 61 const result = await page.evaluate((a) => a, Infinity); 62 expect(Object.is(result, Infinity)).toBe(true); 63 }); 64 it('should transfer -Infinity', async () => { 65 const { page } = getTestState(); 66 67 const result = await page.evaluate((a) => a, -Infinity); 68 expect(Object.is(result, -Infinity)).toBe(true); 69 }); 70 it('should transfer arrays', async () => { 71 const { page } = getTestState(); 72 73 const result = await page.evaluate((a) => a, [1, 2, 3]); 74 expect(result).toEqual([1, 2, 3]); 75 }); 76 it('should transfer arrays as arrays, not objects', async () => { 77 const { page } = getTestState(); 78 79 const result = await page.evaluate((a) => Array.isArray(a), [1, 2, 3]); 80 expect(result).toBe(true); 81 }); 82 it('should modify global environment', async () => { 83 const { page } = getTestState(); 84 85 await page.evaluate(() => (globalThis.globalVar = 123)); 86 expect(await page.evaluate('globalVar')).toBe(123); 87 }); 88 it('should evaluate in the page context', async () => { 89 const { page, server } = getTestState(); 90 91 await page.goto(server.PREFIX + '/global-var.html'); 92 expect(await page.evaluate('globalVar')).toBe(123); 93 }); 94 it( 95 'should return undefined for objects with symbols', 96 async () => { 97 const { page } = getTestState(); 98 99 expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); 100 } 101 ); 102 it('should work with function shorthands', async () => { 103 const { page } = getTestState(); 104 105 const a = { 106 sum(a, b) { 107 return a + b; 108 }, 109 110 async mult(a, b) { 111 return a * b; 112 }, 113 }; 114 expect(await page.evaluate(a.sum, 1, 2)).toBe(3); 115 expect(await page.evaluate(a.mult, 2, 4)).toBe(8); 116 }); 117 it('should work with unicode chars', async () => { 118 const { page } = getTestState(); 119 120 const result = await page.evaluate((a) => a['中文字符'], { 121 中文字符: 42, 122 }); 123 expect(result).toBe(42); 124 }); 125 it('should throw when evaluation triggers reload', async () => { 126 const { page } = getTestState(); 127 128 let error = null; 129 await page 130 .evaluate(() => { 131 location.reload(); 132 return new Promise(() => {}); 133 }) 134 .catch((error_) => (error = error_)); 135 expect(error.message).toContain('Protocol error'); 136 }); 137 it('should await promise', async () => { 138 const { page } = getTestState(); 139 140 const result = await page.evaluate(() => Promise.resolve(8 * 7)); 141 expect(result).toBe(56); 142 }); 143 it('should work right after framenavigated', async () => { 144 const { page, server } = getTestState(); 145 146 let frameEvaluation = null; 147 page.on('framenavigated', async (frame) => { 148 frameEvaluation = frame.evaluate(() => 6 * 7); 149 }); 150 await page.goto(server.EMPTY_PAGE); 151 expect(await frameEvaluation).toBe(42); 152 }); 153 it('should work from-inside an exposed function', async () => { 154 const { page } = getTestState(); 155 156 // Setup inpage callback, which calls Page.evaluate 157 await page.exposeFunction('callController', async function (a, b) { 158 return await page.evaluate<(a: number, b: number) => number>( 159 (a, b) => a * b, 160 a, 161 b 162 ); 163 }); 164 const result = await page.evaluate(async function () { 165 return await globalThis.callController(9, 3); 166 }); 167 expect(result).toBe(27); 168 }); 169 it('should reject promise with exception', async () => { 170 const { page } = getTestState(); 171 172 let error = null; 173 await page 174 // @ts-expect-error we know the object doesn't exist 175 .evaluate(() => notExistingObject.property) 176 .catch((error_) => (error = error_)); 177 expect(error).toBeTruthy(); 178 expect(error.message).toContain('notExistingObject'); 179 }); 180 it('should support thrown strings as error messages', async () => { 181 const { page } = getTestState(); 182 183 let error = null; 184 await page 185 .evaluate(() => { 186 throw 'qwerty'; 187 }) 188 .catch((error_) => (error = error_)); 189 expect(error).toBeTruthy(); 190 expect(error.message).toContain('qwerty'); 191 }); 192 it('should support thrown numbers as error messages', async () => { 193 const { page } = getTestState(); 194 195 let error = null; 196 await page 197 .evaluate(() => { 198 throw 100500; 199 }) 200 .catch((error_) => (error = error_)); 201 expect(error).toBeTruthy(); 202 expect(error.message).toContain('100500'); 203 }); 204 it('should return complex objects', async () => { 205 const { page } = getTestState(); 206 207 const object = { foo: 'bar!' }; 208 const result = await page.evaluate((a) => a, object); 209 expect(result).not.toBe(object); 210 expect(result).toEqual(object); 211 }); 212 (bigint ? it : xit)('should return BigInt', async () => { 213 const { page } = getTestState(); 214 215 const result = await page.evaluate(() => BigInt(42)); 216 expect(result).toBe(BigInt(42)); 217 }); 218 it('should return NaN', async () => { 219 const { page } = getTestState(); 220 221 const result = await page.evaluate(() => NaN); 222 expect(Object.is(result, NaN)).toBe(true); 223 }); 224 it('should return -0', async () => { 225 const { page } = getTestState(); 226 227 const result = await page.evaluate(() => -0); 228 expect(Object.is(result, -0)).toBe(true); 229 }); 230 it('should return Infinity', async () => { 231 const { page } = getTestState(); 232 233 const result = await page.evaluate(() => Infinity); 234 expect(Object.is(result, Infinity)).toBe(true); 235 }); 236 it('should return -Infinity', async () => { 237 const { page } = getTestState(); 238 239 const result = await page.evaluate(() => -Infinity); 240 expect(Object.is(result, -Infinity)).toBe(true); 241 }); 242 it('should accept "undefined" as one of multiple parameters', async () => { 243 const { page } = getTestState(); 244 245 const result = await page.evaluate( 246 (a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), 247 undefined, 248 'foo' 249 ); 250 expect(result).toBe(true); 251 }); 252 it('should properly serialize null fields', async () => { 253 const { page } = getTestState(); 254 255 expect(await page.evaluate(() => ({ a: undefined }))).toEqual({}); 256 }); 257 it( 258 'should return undefined for non-serializable objects', 259 async () => { 260 const { page } = getTestState(); 261 262 expect(await page.evaluate(() => window)).toBe(undefined); 263 } 264 ); 265 it('should fail for circular object', async () => { 266 const { page } = getTestState(); 267 268 const result = await page.evaluate(() => { 269 const a: { [x: string]: any } = {}; 270 const b = { a }; 271 a.b = b; 272 return a; 273 }); 274 expect(result).toBe(undefined); 275 }); 276 it('should be able to throw a tricky error', async () => { 277 const { page } = getTestState(); 278 279 const windowHandle = await page.evaluateHandle(() => window); 280 const errorText = await windowHandle 281 .jsonValue<string>() 282 .catch((error_) => error_.message); 283 const error = await page 284 .evaluate<(errorText: string) => Error>((errorText) => { 285 throw new Error(errorText); 286 }, errorText) 287 .catch((error_) => error_); 288 expect(error.message).toContain(errorText); 289 }); 290 it('should accept a string', async () => { 291 const { page } = getTestState(); 292 293 const result = await page.evaluate('1 + 2'); 294 expect(result).toBe(3); 295 }); 296 it('should accept a string with semi colons', async () => { 297 const { page } = getTestState(); 298 299 const result = await page.evaluate('1 + 5;'); 300 expect(result).toBe(6); 301 }); 302 it('should accept a string with comments', async () => { 303 const { page } = getTestState(); 304 305 const result = await page.evaluate('2 + 5;\n// do some math!'); 306 expect(result).toBe(7); 307 }); 308 it('should accept element handle as an argument', async () => { 309 const { page } = getTestState(); 310 311 await page.setContent('<section>42</section>'); 312 const element = await page.$('section'); 313 const text = await page.evaluate<(e: HTMLElement) => string>( 314 (e) => e.textContent, 315 element 316 ); 317 expect(text).toBe('42'); 318 }); 319 it('should throw if underlying element was disposed', async () => { 320 const { page } = getTestState(); 321 322 await page.setContent('<section>39</section>'); 323 const element = await page.$('section'); 324 expect(element).toBeTruthy(); 325 await element.dispose(); 326 let error = null; 327 await page 328 .evaluate((e: HTMLElement) => e.textContent, element) 329 .catch((error_) => (error = error_)); 330 expect(error.message).toContain('JSHandle is disposed'); 331 }); 332 it( 333 'should throw if elementHandles are from other frames', 334 async () => { 335 const { page, server } = getTestState(); 336 337 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 338 const bodyHandle = await page.frames()[1].$('body'); 339 let error = null; 340 await page 341 .evaluate((body: HTMLElement) => body.innerHTML, bodyHandle) 342 .catch((error_) => (error = error_)); 343 expect(error).toBeTruthy(); 344 expect(error.message).toContain( 345 'JSHandles can be evaluated only in the context they were created' 346 ); 347 } 348 ); 349 it('should simulate a user gesture', async () => { 350 const { page } = getTestState(); 351 352 const result = await page.evaluate(() => { 353 document.body.appendChild(document.createTextNode('test')); 354 document.execCommand('selectAll'); 355 return document.execCommand('copy'); 356 }); 357 expect(result).toBe(true); 358 }); 359 it('should throw a nice error after a navigation', async () => { 360 const { page } = getTestState(); 361 362 const executionContext = await page.mainFrame().executionContext(); 363 364 await Promise.all([ 365 page.waitForNavigation(), 366 executionContext.evaluate(() => window.location.reload()), 367 ]); 368 const error = await executionContext 369 .evaluate(() => null) 370 .catch((error_) => error_); 371 expect((error as Error).message).toContain('navigation'); 372 }); 373 it( 374 'should not throw an error when evaluation does a navigation', 375 async () => { 376 const { page, server } = getTestState(); 377 378 await page.goto(server.PREFIX + '/one-style.html'); 379 const result = await page.evaluate(() => { 380 (window as any).location = '/empty.html'; 381 return [42]; 382 }); 383 expect(result).toEqual([42]); 384 } 385 ); 386 it('should transfer 100Mb of data from page to node.js', async function () { 387 const { page } = getTestState(); 388 389 const a = await page.evaluate<() => string>(() => 390 Array(100 * 1024 * 1024 + 1).join('a') 391 ); 392 expect(a.length).toBe(100 * 1024 * 1024); 393 }); 394 it('should throw error with detailed information on exception inside promise ', async () => { 395 const { page } = getTestState(); 396 397 let error = null; 398 await page 399 .evaluate( 400 () => 401 new Promise(() => { 402 throw new Error('Error in promise'); 403 }) 404 ) 405 .catch((error_) => (error = error_)); 406 expect(error.message).toContain('Error in promise'); 407 }); 408 }); 409 410 describe('Page.evaluateOnNewDocument', function () { 411 it('should evaluate before anything else on the page', async () => { 412 const { page, server } = getTestState(); 413 414 await page.evaluateOnNewDocument(function () { 415 globalThis.injected = 123; 416 }); 417 await page.goto(server.PREFIX + '/tamperable.html'); 418 expect(await page.evaluate(() => globalThis.result)).toBe(123); 419 }); 420 it('should work with CSP', async () => { 421 const { page, server } = getTestState(); 422 423 server.setCSP('/empty.html', 'script-src ' + server.PREFIX); 424 await page.evaluateOnNewDocument(function () { 425 globalThis.injected = 123; 426 }); 427 await page.goto(server.PREFIX + '/empty.html'); 428 expect(await page.evaluate(() => globalThis.injected)).toBe(123); 429 430 // Make sure CSP works. 431 await page 432 .addScriptTag({ content: 'window.e = 10;' }) 433 .catch((error) => void error); 434 expect(await page.evaluate(() => (window as any).e)).toBe(undefined); 435 }); 436 }); 437 438 describe('Frame.evaluate', function () { 439 it('should have different execution contexts', async () => { 440 const { page, server } = getTestState(); 441 442 await page.goto(server.EMPTY_PAGE); 443 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 444 expect(page.frames().length).toBe(2); 445 await page.frames()[0].evaluate(() => (globalThis.FOO = 'foo')); 446 await page.frames()[1].evaluate(() => (globalThis.FOO = 'bar')); 447 expect(await page.frames()[0].evaluate(() => globalThis.FOO)).toBe('foo'); 448 expect(await page.frames()[1].evaluate(() => globalThis.FOO)).toBe('bar'); 449 }); 450 it('should have correct execution contexts', async () => { 451 const { page, server } = getTestState(); 452 453 await page.goto(server.PREFIX + '/frames/one-frame.html'); 454 expect(page.frames().length).toBe(2); 455 expect( 456 await page.frames()[0].evaluate(() => document.body.textContent.trim()) 457 ).toBe(''); 458 expect( 459 await page.frames()[1].evaluate(() => document.body.textContent.trim()) 460 ).toBe(`Hi, I'm frame`); 461 }); 462 it('should execute after cross-site navigation', async () => { 463 const { page, server } = getTestState(); 464 465 await page.goto(server.EMPTY_PAGE); 466 const mainFrame = page.mainFrame(); 467 expect(await mainFrame.evaluate(() => window.location.href)).toContain( 468 'localhost' 469 ); 470 await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); 471 expect(await mainFrame.evaluate(() => window.location.href)).toContain( 472 '127' 473 ); 474 }); 475 }); 476}); 477