From d61916b53e5b5f491b14288ea00f23ee7e31f473 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 20 Nov 2018 19:59:59 -0800 Subject: [PATCH] test: split out evaluation tests (#3584) --- test/evaluation.spec.js | 248 ++++++++++++++++++++++++++++++++++++++++ test/frame.spec.js | 21 ---- test/page.spec.js | 199 -------------------------------- test/test.js | 1 + 4 files changed, 249 insertions(+), 220 deletions(-) create mode 100644 test/evaluation.spec.js diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js new file mode 100644 index 0000000000000..d626fb6f60796 --- /dev/null +++ b/test/evaluation.spec.js @@ -0,0 +1,248 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const utils = require('./utils'); + +let asyncawait = true; +try { + new Function('async function foo() {await 1}'); +} catch (e) { + asyncawait = false; +} + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('Page.evaluate', function() { + it('should work', async({page, server}) => { + const result = await page.evaluate(() => 7 * 3); + expect(result).toBe(21); + }); + (asyncawait ? it : xit)('should work with function shorthands', async({page, server}) => { + // trick node6 transpiler to not touch our object. + // TODO(lushnikov): remove eval once Node6 is dropped. + const a = eval(`({ + sum(a, b) { return a + b; }, + + async mult(a, b) { return a * b; } + })`); + expect(await page.evaluate(a.sum, 1, 2)).toBe(3); + expect(await page.evaluate(a.mult, 2, 4)).toBe(8); + }); + it('should throw when evaluation triggers reload', async({page, server}) => { + let error = null; + await page.evaluate(() => { + location.reload(); + return new Promise(resolve => { + setTimeout(() => resolve(1), 0); + }); + }).catch(e => error = e); + expect(error.message).toContain('Protocol error'); + }); + it('should await promise', async({page, server}) => { + const result = await page.evaluate(() => Promise.resolve(8 * 7)); + expect(result).toBe(56); + }); + it('should work right after framenavigated', async({page, server}) => { + let frameEvaluation = null; + page.on('framenavigated', async frame => { + frameEvaluation = frame.evaluate(() => 6 * 7); + }); + await page.goto(server.EMPTY_PAGE); + expect(await frameEvaluation).toBe(42); + }); + it('should work from-inside an exposed function', async({page, server}) => { + // Setup inpage callback, which calls Page.evaluate + await page.exposeFunction('callController', async function(a, b) { + return await page.evaluate((a, b) => a * b, a, b); + }); + const result = await page.evaluate(async function() { + return await callController(9, 3); + }); + expect(result).toBe(27); + }); + it('should reject promise with exception', async({page, server}) => { + let error = null; + await page.evaluate(() => not.existing.object.property).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('not is not defined'); + }); + it('should support thrown strings as error messages', async({page, server}) => { + let error = null; + await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('qwerty'); + }); + it('should support thrown numbers as error messages', async({page, server}) => { + let error = null; + await page.evaluate(() => { throw 100500; }).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('100500'); + }); + it('should return complex objects', async({page, server}) => { + const object = {foo: 'bar!'}; + const result = await page.evaluate(a => a, object); + expect(result).not.toBe(object); + expect(result).toEqual(object); + }); + it('should return NaN', async({page, server}) => { + const result = await page.evaluate(() => NaN); + expect(Object.is(result, NaN)).toBe(true); + }); + it('should return -0', async({page, server}) => { + const result = await page.evaluate(() => -0); + expect(Object.is(result, -0)).toBe(true); + }); + it('should return Infinity', async({page, server}) => { + const result = await page.evaluate(() => Infinity); + expect(Object.is(result, Infinity)).toBe(true); + }); + it('should return -Infinity', async({page, server}) => { + const result = await page.evaluate(() => -Infinity); + expect(Object.is(result, -Infinity)).toBe(true); + }); + it('should accept "undefined" as one of multiple parameters', async({page, server}) => { + const result = await page.evaluate((a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), undefined, 'foo'); + expect(result).toBe(true); + }); + it('should properly serialize null fields', async({page}) => { + expect(await page.evaluate(() => ({a: undefined}))).toEqual({}); + }); + it('should return undefined for non-serializable objects', async({page, server}) => { + expect(await page.evaluate(() => window)).toBe(undefined); + expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); + }); + it('should fail for circular object', async({page, server}) => { + const result = await page.evaluate(() => { + const a = {}; + const b = {a}; + a.b = b; + return a; + }); + expect(result).toBe(undefined); + }); + it('should accept a string', async({page, server}) => { + const result = await page.evaluate('1 + 2'); + expect(result).toBe(3); + }); + it('should accept a string with semi colons', async({page, server}) => { + const result = await page.evaluate('1 + 5;'); + expect(result).toBe(6); + }); + it('should accept a string with comments', async({page, server}) => { + const result = await page.evaluate('2 + 5;\n// do some math!'); + expect(result).toBe(7); + }); + it('should accept element handle as an argument', async({page, server}) => { + await page.setContent('
42
'); + const element = await page.$('section'); + const text = await page.evaluate(e => e.textContent, element); + expect(text).toBe('42'); + }); + it('should throw if underlying element was disposed', async({page, server}) => { + await page.setContent('
39
'); + const element = await page.$('section'); + expect(element).toBeTruthy(); + await element.dispose(); + let error = null; + await page.evaluate(e => e.textContent, element).catch(e => error = e); + expect(error.message).toContain('JSHandle is disposed'); + }); + it('should throw if elementHandles are from other frames', async({page, server}) => { + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const bodyHandle = await page.frames()[1].$('body'); + let error = null; + await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('JSHandles can be evaluated only in the context they were created'); + }); + it('should accept object handle as an argument', async({page, server}) => { + const navigatorHandle = await page.evaluateHandle(() => navigator); + const text = await page.evaluate(e => e.userAgent, navigatorHandle); + expect(text).toContain('Mozilla'); + }); + it('should accept object handle to primitive types', async({page, server}) => { + const aHandle = await page.evaluateHandle(() => 5); + const isFive = await page.evaluate(e => Object.is(e, 5), aHandle); + expect(isFive).toBeTruthy(); + }); + it('should simulate a user gesture', async({page, server}) => { + await page.evaluate(playAudio); + // also test evaluating strings + await page.evaluate(`(${playAudio})()`); + + function playAudio() { + const audio = document.createElement('audio'); + audio.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA='; + // This returns a promise which throws if it was not triggered by a user gesture. + return audio.play(); + } + }); + it('should throw a nice error after a navigation', async({page, server}) => { + const executionContext = await page.mainFrame().executionContext(); + + await Promise.all([ + page.waitForNavigation(), + executionContext.evaluate(() => window.location.reload()) + ]); + const error = await executionContext.evaluate(() => null).catch(e => e); + expect(error.message).toContain('navigation'); + }); + }); + + describe('Page.evaluateOnNewDocument', function() { + it('should evaluate before anything else on the page', async({page, server}) => { + await page.evaluateOnNewDocument(function(){ + window.injected = 123; + }); + await page.goto(server.PREFIX + '/tamperable.html'); + expect(await page.evaluate(() => window.result)).toBe(123); + }); + it('should work with CSP', async({page, server}) => { + server.setCSP('/empty.html', 'script-src ' + server.PREFIX); + await page.evaluateOnNewDocument(function(){ + window.injected = 123; + }); + await page.goto(server.PREFIX + '/empty.html'); + expect(await page.evaluate(() => window.injected)).toBe(123); + + // Make sure CSP works. + await page.addScriptTag({content: 'window.e = 10;'}).catch(e => void e); + expect(await page.evaluate(() => window.e)).toBe(undefined); + }); + }); + + describe('Frame.evaluate', function() { + it('should have different execution contexts', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + expect(page.frames().length).toBe(2); + await page.frames()[0].evaluate(() => window.FOO = 'foo'); + await page.frames()[1].evaluate(() => window.FOO = 'bar'); + expect(await page.frames()[0].evaluate(() => window.FOO)).toBe('foo'); + expect(await page.frames()[1].evaluate(() => window.FOO)).toBe('bar'); + }); + it('should execute after cross-site navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const mainFrame = page.mainFrame(); + expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + expect(await mainFrame.evaluate(() => window.location.href)).toContain('127'); + }); + }); +}; diff --git a/test/frame.spec.js b/test/frame.spec.js index e4b9ee9d67640..d4417e3a72842 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -135,27 +135,6 @@ module.exports.addTests = function({testRunner, expect}) { }); }); - describe('Frame.evaluate', function() { - it('should have different execution contexts', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - expect(page.frames().length).toBe(2); - const frame1 = page.frames()[0]; - const frame2 = page.frames()[1]; - await frame1.evaluate(() => window.FOO = 'foo'); - await frame2.evaluate(() => window.FOO = 'bar'); - expect(await frame1.evaluate(() => window.FOO)).toBe('foo'); - expect(await frame2.evaluate(() => window.FOO)).toBe('bar'); - }); - it('should execute after cross-site navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const mainFrame = page.mainFrame(); - expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(await mainFrame.evaluate(() => window.location.href)).toContain('127'); - }); - }); - describe('Frame Management', function() { it('should handle nested frames', async({page, server}) => { await page.goto(server.PREFIX + '/frames/nested-frames.html'); diff --git a/test/page.spec.js b/test/page.spec.js index 2029192912e32..39bf8fc470416 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -171,183 +171,6 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); }); - describe('Page.evaluate', function() { - it('should work', async({page, server}) => { - const result = await page.evaluate(() => 7 * 3); - expect(result).toBe(21); - }); - (asyncawait ? it : xit)('should work with function shorthands', async({page, server}) => { - // trick node6 transpiler to not touch our object. - // TODO(lushnikov): remove eval once Node6 is dropped. - const a = eval(`({ - sum(a, b) { return a + b; }, - - async mult(a, b) { return a * b; } - })`); - expect(await page.evaluate(a.sum, 1, 2)).toBe(3); - expect(await page.evaluate(a.mult, 2, 4)).toBe(8); - }); - it('should throw when evaluation triggers reload', async({page, server}) => { - let error = null; - await page.evaluate(() => { - location.reload(); - return new Promise(resolve => { - setTimeout(() => resolve(1), 0); - }); - }).catch(e => error = e); - expect(error.message).toContain('Protocol error'); - }); - it('should await promise', async({page, server}) => { - const result = await page.evaluate(() => Promise.resolve(8 * 7)); - expect(result).toBe(56); - }); - it('should work right after framenavigated', async({page, server}) => { - let frameEvaluation = null; - page.on('framenavigated', async frame => { - frameEvaluation = frame.evaluate(() => 6 * 7); - }); - await page.goto(server.EMPTY_PAGE); - expect(await frameEvaluation).toBe(42); - }); - it('should work from-inside an exposed function', async({page, server}) => { - // Setup inpage callback, which calls Page.evaluate - await page.exposeFunction('callController', async function(a, b) { - return await page.evaluate((a, b) => a * b, a, b); - }); - const result = await page.evaluate(async function() { - return await callController(9, 3); - }); - expect(result).toBe(27); - }); - it('should reject promise with exception', async({page, server}) => { - let error = null; - await page.evaluate(() => not.existing.object.property).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('not is not defined'); - }); - it('should support thrown strings as error messages', async({page, server}) => { - let error = null; - await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('qwerty'); - }); - it('should support thrown numbers as error messages', async({page, server}) => { - let error = null; - await page.evaluate(() => { throw 100500; }).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('100500'); - }); - it('should return complex objects', async({page, server}) => { - const object = {foo: 'bar!'}; - const result = await page.evaluate(a => a, object); - expect(result).not.toBe(object); - expect(result).toEqual(object); - }); - it('should return NaN', async({page, server}) => { - const result = await page.evaluate(() => NaN); - expect(Object.is(result, NaN)).toBe(true); - }); - it('should return -0', async({page, server}) => { - const result = await page.evaluate(() => -0); - expect(Object.is(result, -0)).toBe(true); - }); - it('should return Infinity', async({page, server}) => { - const result = await page.evaluate(() => Infinity); - expect(Object.is(result, Infinity)).toBe(true); - }); - it('should return -Infinity', async({page, server}) => { - const result = await page.evaluate(() => -Infinity); - expect(Object.is(result, -Infinity)).toBe(true); - }); - it('should accept "undefined" as one of multiple parameters', async({page, server}) => { - const result = await page.evaluate((a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), undefined, 'foo'); - expect(result).toBe(true); - }); - it('should properly serialize null fields', async({page}) => { - expect(await page.evaluate(() => ({a: undefined}))).toEqual({}); - }); - it('should return undefined for non-serializable objects', async({page, server}) => { - expect(await page.evaluate(() => window)).toBe(undefined); - expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); - }); - it('should fail for circular object', async({page, server}) => { - const result = await page.evaluate(() => { - const a = {}; - const b = {a}; - a.b = b; - return a; - }); - expect(result).toBe(undefined); - }); - it('should accept a string', async({page, server}) => { - const result = await page.evaluate('1 + 2'); - expect(result).toBe(3); - }); - it('should accept a string with semi colons', async({page, server}) => { - const result = await page.evaluate('1 + 5;'); - expect(result).toBe(6); - }); - it('should accept a string with comments', async({page, server}) => { - const result = await page.evaluate('2 + 5;\n// do some math!'); - expect(result).toBe(7); - }); - it('should accept element handle as an argument', async({page, server}) => { - await page.setContent('
42
'); - const element = await page.$('section'); - const text = await page.evaluate(e => e.textContent, element); - expect(text).toBe('42'); - }); - it('should throw if underlying element was disposed', async({page, server}) => { - await page.setContent('
39
'); - const element = await page.$('section'); - expect(element).toBeTruthy(); - await element.dispose(); - let error = null; - await page.evaluate(e => e.textContent, element).catch(e => error = e); - expect(error.message).toContain('JSHandle is disposed'); - }); - it('should throw if elementHandles are from other frames', async({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const bodyHandle = await page.frames()[1].$('body'); - let error = null; - await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('JSHandles can be evaluated only in the context they were created'); - }); - it('should accept object handle as an argument', async({page, server}) => { - const navigatorHandle = await page.evaluateHandle(() => navigator); - const text = await page.evaluate(e => e.userAgent, navigatorHandle); - expect(text).toContain('Mozilla'); - }); - it('should accept object handle to primitive types', async({page, server}) => { - const aHandle = await page.evaluateHandle(() => 5); - const isFive = await page.evaluate(e => Object.is(e, 5), aHandle); - expect(isFive).toBeTruthy(); - }); - it('should simulate a user gesture', async({page, server}) => { - await page.evaluate(playAudio); - // also test evaluating strings - await page.evaluate(`(${playAudio})()`); - - function playAudio() { - const audio = document.createElement('audio'); - audio.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA='; - // This returns a promise which throws if it was not triggered by a user gesture. - return audio.play(); - } - }); - it('should throw a nice error after a navigation', async({page, server}) => { - const executionContext = await page.mainFrame().executionContext(); - - await Promise.all([ - page.waitForNavigation(), - executionContext.evaluate(() => window.location.reload()) - ]); - const error = await executionContext.evaluate(() => null).catch(e => e); - expect(error.message).toContain('navigation'); - }); - }); - describe('Page.setOfflineMode', function() { it('should work', async({page, server}) => { await page.setOfflineMode(true); @@ -1386,28 +1209,6 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); }); - describe('Page.evaluateOnNewDocument', function() { - it('should evaluate before anything else on the page', async({page, server}) => { - await page.evaluateOnNewDocument(function(){ - window.injected = 123; - }); - await page.goto(server.PREFIX + '/tamperable.html'); - expect(await page.evaluate(() => window.result)).toBe(123); - }); - it('should work with CSP', async({page, server}) => { - server.setCSP('/empty.html', 'script-src ' + server.PREFIX); - await page.evaluateOnNewDocument(function(){ - window.injected = 123; - }); - await page.goto(server.PREFIX + '/empty.html'); - expect(await page.evaluate(() => window.injected)).toBe(123); - - // Make sure CSP works. - await page.addScriptTag({content: 'window.e = 10;'}).catch(e => void e); - expect(await page.evaluate(() => window.e)).toBe(undefined); - }); - }); - describe('Page.setCacheEnabled', function() { it('should enable or disable the cache based on the state passed', async({page, server}) => { const responses = new Map(); diff --git a/test/test.js b/test/test.js index 834d962de3b57..1dfcbc4ae4274 100644 --- a/test/test.js +++ b/test/test.js @@ -161,6 +161,7 @@ describe('Browser', function() { require('./jshandle.spec.js').addTests({testRunner, expect}); require('./network.spec.js').addTests({testRunner, expect}); require('./page.spec.js').addTests({testRunner, expect, headless}); + require('./evaluation.spec.js').addTests({testRunner, expect, headless}); require('./emulation.spec.js').addTests({testRunner, expect, headless}); require('./screenshot.spec.js').addTests({testRunner, expect}); require('./target.spec.js').addTests({testRunner, expect});