summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/test/src/evaluation.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/test/src/evaluation.spec.ts')
-rw-r--r--remote/test/puppeteer/test/src/evaluation.spec.ts600
1 files changed, 600 insertions, 0 deletions
diff --git a/remote/test/puppeteer/test/src/evaluation.spec.ts b/remote/test/puppeteer/test/src/evaluation.spec.ts
new file mode 100644
index 0000000000..f8992b051e
--- /dev/null
+++ b/remote/test/puppeteer/test/src/evaluation.spec.ts
@@ -0,0 +1,600 @@
+/**
+ * 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.
+ */
+
+import expect from 'expect';
+
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+} from './mocha-utils.js';
+import {attachFrame} from './utils.js';
+
+describe('Evaluation specs', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ describe('Page.evaluate', function () {
+ it('should work', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return 7 * 3;
+ });
+ expect(result).toBe(21);
+ });
+ it('should transfer BigInt', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate((a: bigint) => {
+ return a;
+ }, BigInt(42));
+ expect(result).toBe(BigInt(42));
+ });
+ it('should transfer NaN', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(a => {
+ return a;
+ }, NaN);
+ expect(Object.is(result, NaN)).toBe(true);
+ });
+ it('should transfer -0', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(a => {
+ return a;
+ }, -0);
+ expect(Object.is(result, -0)).toBe(true);
+ });
+ it('should transfer Infinity', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(a => {
+ return a;
+ }, Infinity);
+ expect(Object.is(result, Infinity)).toBe(true);
+ });
+ it('should transfer -Infinity', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(a => {
+ return a;
+ }, -Infinity);
+ expect(Object.is(result, -Infinity)).toBe(true);
+ });
+ it('should transfer arrays', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(
+ a => {
+ return a;
+ },
+ [1, 2, 3]
+ );
+ expect(result).toEqual([1, 2, 3]);
+ });
+ it('should transfer arrays as arrays, not objects', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(
+ a => {
+ return Array.isArray(a);
+ },
+ [1, 2, 3]
+ );
+ expect(result).toBe(true);
+ });
+ it('should modify global environment', async () => {
+ const {page} = getTestState();
+
+ await page.evaluate(() => {
+ return ((globalThis as any).globalVar = 123);
+ });
+ expect(await page.evaluate('globalVar')).toBe(123);
+ });
+ it('should evaluate in the page context', async () => {
+ const {page, server} = getTestState();
+
+ await page.goto(server.PREFIX + '/global-var.html');
+ expect(await page.evaluate('globalVar')).toBe(123);
+ });
+ it('should return undefined for objects with symbols', async () => {
+ const {page} = getTestState();
+
+ expect(
+ await page.evaluate(() => {
+ return [Symbol('foo4')];
+ })
+ ).toBe(undefined);
+ });
+ it('should work with function shorthands', async () => {
+ const {page} = getTestState();
+
+ const a = {
+ sum(a: number, b: number) {
+ return a + b;
+ },
+
+ async mult(a: number, b: number) {
+ 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 work with unicode chars', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(
+ a => {
+ return a['中文字符'];
+ },
+ {
+ 中文字符: 42,
+ }
+ );
+ expect(result).toBe(42);
+ });
+ it('should throw when evaluation triggers reload', async () => {
+ const {page} = getTestState();
+
+ let error!: Error;
+ await page
+ .evaluate(() => {
+ location.reload();
+ return new Promise(() => {});
+ })
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error.message).toContain('Protocol error');
+ });
+ it('should await promise', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return Promise.resolve(8 * 7);
+ });
+ expect(result).toBe(56);
+ });
+ it('should work right after framenavigated', async () => {
+ const {page, server} = getTestState();
+
+ let frameEvaluation = null;
+ page.on('framenavigated', async frame => {
+ frameEvaluation = frame.evaluate(() => {
+ return 6 * 7;
+ });
+ });
+ await page.goto(server.EMPTY_PAGE);
+ expect(await frameEvaluation).toBe(42);
+ });
+ it('should work from-inside an exposed function', async () => {
+ const {page} = getTestState();
+
+ // Setup inpage callback, which calls Page.evaluate
+ await page.exposeFunction(
+ 'callController',
+ async function (a: number, b: number) {
+ return await page.evaluate(
+ (a: number, b: number): number => {
+ return a * b;
+ },
+ a,
+ b
+ );
+ }
+ );
+ const result = await page.evaluate(async function () {
+ return await (globalThis as any).callController(9, 3);
+ });
+ expect(result).toBe(27);
+ });
+ it('should reject promise with exception', async () => {
+ const {page} = getTestState();
+
+ let error!: Error;
+ await page
+ .evaluate(() => {
+ // @ts-expect-error we know the object doesn't exist
+ return notExistingObject.property;
+ })
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('notExistingObject');
+ });
+ it('should support thrown strings as error messages', async () => {
+ const {page} = getTestState();
+
+ let error!: Error;
+ await page
+ .evaluate(() => {
+ throw 'qwerty';
+ })
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('qwerty');
+ });
+ it('should support thrown numbers as error messages', async () => {
+ const {page} = getTestState();
+
+ let error!: Error;
+ await page
+ .evaluate(() => {
+ throw 100500;
+ })
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('100500');
+ });
+ it('should return complex objects', async () => {
+ const {page} = getTestState();
+
+ const object = {foo: 'bar!'};
+ const result = await page.evaluate(a => {
+ return a;
+ }, object);
+ expect(result).not.toBe(object);
+ expect(result).toEqual(object);
+ });
+ it('should return BigInt', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return BigInt(42);
+ });
+ expect(result).toBe(BigInt(42));
+ });
+ it('should return NaN', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return NaN;
+ });
+ expect(Object.is(result, NaN)).toBe(true);
+ });
+ it('should return -0', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return -0;
+ });
+ expect(Object.is(result, -0)).toBe(true);
+ });
+ it('should return Infinity', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return Infinity;
+ });
+ expect(Object.is(result, Infinity)).toBe(true);
+ });
+ it('should return -Infinity', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return -Infinity;
+ });
+ expect(Object.is(result, -Infinity)).toBe(true);
+ });
+ it('should accept "null" as one of multiple parameters', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(
+ (a, b) => {
+ return Object.is(a, null) && Object.is(b, 'foo');
+ },
+ null,
+ 'foo'
+ );
+ expect(result).toBe(true);
+ });
+ it('should properly serialize null fields', async () => {
+ const {page} = getTestState();
+
+ expect(
+ await page.evaluate(() => {
+ return {a: undefined};
+ })
+ ).toEqual({});
+ });
+ it('should return undefined for non-serializable objects', async () => {
+ const {page} = getTestState();
+
+ expect(
+ await page.evaluate(() => {
+ return window;
+ })
+ ).toBe(undefined);
+ });
+ it('should return promise as empty object', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ return {
+ promise: new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ }),
+ };
+ });
+ expect(result).toEqual({
+ promise: {},
+ });
+ });
+ it('should fail for circular object', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ const a: {[x: string]: any} = {};
+ const b = {a};
+ a['b'] = b;
+ return a;
+ });
+ expect(result).toBe(undefined);
+ });
+ it('should be able to throw a tricky error', async () => {
+ const {page} = getTestState();
+
+ const windowHandle = await page.evaluateHandle(() => {
+ return window;
+ });
+ const errorText = await windowHandle.jsonValue().catch(error_ => {
+ return error_.message;
+ });
+ const error = await page
+ .evaluate(errorText => {
+ throw new Error(errorText);
+ }, errorText)
+ .catch(error_ => {
+ return error_;
+ });
+ expect(error.message).toContain(errorText);
+ });
+ it('should accept a string', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate('1 + 2');
+ expect(result).toBe(3);
+ });
+ it('should accept a string with semi colons', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate('1 + 5;');
+ expect(result).toBe(6);
+ });
+ it('should accept a string with comments', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate('2 + 5;\n// do some math!');
+ expect(result).toBe(7);
+ });
+ it('should accept element handle as an argument', async () => {
+ const {page} = getTestState();
+
+ await page.setContent('<section>42</section>');
+ const element = (await page.$('section'))!;
+ const text = await page.evaluate(e => {
+ return e.textContent;
+ }, element);
+ expect(text).toBe('42');
+ });
+ it('should throw if underlying element was disposed', async () => {
+ const {page} = getTestState();
+
+ await page.setContent('<section>39</section>');
+ const element = (await page.$('section'))!;
+ expect(element).toBeTruthy();
+ await element.dispose();
+ let error!: Error;
+ await page
+ .evaluate((e: HTMLElement) => {
+ return e.textContent;
+ }, element)
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error.message).toContain('JSHandle is disposed');
+ });
+ it('should throw if elementHandles are from other frames', async () => {
+ const {page, server} = getTestState();
+
+ await attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const bodyHandle = await page.frames()[1]!.$('body');
+ let error!: Error;
+ await page
+ .evaluate(body => {
+ return body?.innerHTML;
+ }, bodyHandle)
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error).toBeTruthy();
+ expect(error.message).toContain(
+ 'JSHandles can be evaluated only in the context they were created'
+ );
+ });
+ it('should simulate a user gesture', async () => {
+ const {page} = getTestState();
+
+ const result = await page.evaluate(() => {
+ document.body.appendChild(document.createTextNode('test'));
+ document.execCommand('selectAll');
+ return document.execCommand('copy');
+ });
+ expect(result).toBe(true);
+ });
+ it('should throw a nice error after a navigation', async () => {
+ const {page} = getTestState();
+
+ const executionContext = await page.mainFrame().executionContext();
+
+ await Promise.all([
+ page.waitForNavigation(),
+ executionContext.evaluate(() => {
+ return window.location.reload();
+ }),
+ ]);
+ const error = await executionContext
+ .evaluate(() => {
+ return null;
+ })
+ .catch(error_ => {
+ return error_;
+ });
+ expect((error as Error).message).toContain('navigation');
+ });
+ it('should not throw an error when evaluation does a navigation', async () => {
+ const {page, server} = getTestState();
+
+ await page.goto(server.PREFIX + '/one-style.html');
+ const result = await page.evaluate(() => {
+ (window as any).location = '/empty.html';
+ return [42];
+ });
+ expect(result).toEqual([42]);
+ });
+ it('should transfer 100Mb of data from page to node.js', async function () {
+ this.timeout(25_000);
+ const {page} = getTestState();
+
+ const a = await page.evaluate(() => {
+ return Array(100 * 1024 * 1024 + 1).join('a');
+ });
+ expect(a.length).toBe(100 * 1024 * 1024);
+ });
+ it('should throw error with detailed information on exception inside promise ', async () => {
+ const {page} = getTestState();
+
+ let error!: Error;
+ await page
+ .evaluate(() => {
+ return new Promise(() => {
+ throw new Error('Error in promise');
+ });
+ })
+ .catch(error_ => {
+ return (error = error_);
+ });
+ expect(error.message).toContain('Error in promise');
+ });
+ });
+
+ describe('Page.evaluateOnNewDocument', function () {
+ it('should evaluate before anything else on the page', async () => {
+ const {page, server} = getTestState();
+
+ await page.evaluateOnNewDocument(function () {
+ (globalThis as any).injected = 123;
+ });
+ await page.goto(server.PREFIX + '/tamperable.html');
+ expect(
+ await page.evaluate(() => {
+ return (globalThis as any).result;
+ })
+ ).toBe(123);
+ });
+ it('should work with CSP', async () => {
+ const {page, server} = getTestState();
+
+ server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
+ await page.evaluateOnNewDocument(function () {
+ (globalThis as any).injected = 123;
+ });
+ await page.goto(server.PREFIX + '/empty.html');
+ expect(
+ await page.evaluate(() => {
+ return (globalThis as any).injected;
+ })
+ ).toBe(123);
+
+ // Make sure CSP works.
+ await page.addScriptTag({content: 'window.e = 10;'}).catch(error => {
+ return void error;
+ });
+ expect(
+ await page.evaluate(() => {
+ return (window as any).e;
+ })
+ ).toBe(undefined);
+ });
+ });
+
+ describe('Frame.evaluate', function () {
+ it('should have different execution contexts', async () => {
+ const {page, server} = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ await attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(page.frames()).toHaveLength(2);
+ await page.frames()[0]!.evaluate(() => {
+ return ((globalThis as any).FOO = 'foo');
+ });
+ await page.frames()[1]!.evaluate(() => {
+ return ((globalThis as any).FOO = 'bar');
+ });
+ expect(
+ await page.frames()[0]!.evaluate(() => {
+ return (globalThis as any).FOO;
+ })
+ ).toBe('foo');
+ expect(
+ await page.frames()[1]!.evaluate(() => {
+ return (globalThis as any).FOO;
+ })
+ ).toBe('bar');
+ });
+ it('should have correct execution contexts', async () => {
+ const {page, server} = getTestState();
+
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames()).toHaveLength(2);
+ expect(
+ await page.frames()[0]!.evaluate(() => {
+ return document.body.textContent!.trim();
+ })
+ ).toBe('');
+ expect(
+ await page.frames()[1]!.evaluate(() => {
+ return document.body.textContent!.trim();
+ })
+ ).toBe(`Hi, I'm frame`);
+ });
+ it('should execute after cross-site navigation', async () => {
+ const {page, server} = getTestState();
+
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ expect(
+ await mainFrame.evaluate(() => {
+ return window.location.href;
+ })
+ ).toContain('localhost');
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(
+ await mainFrame.evaluate(() => {
+ return window.location.href;
+ })
+ ).toContain('127');
+ });
+ });
+});