2020-08-05 01:09:24 +03:00
|
|
|
/**
|
|
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
|
|
* Modifications copyright (c) Microsoft Corporation.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-08-20 07:32:12 +03:00
|
|
|
|
2021-05-06 17:08:22 +03:00
|
|
|
import { test as it, expect } from './pageTest';
|
2021-05-06 05:10:28 +03:00
|
|
|
import { attachFrame } from '../config/utils';
|
|
|
|
import type { ElementHandle } from '../../index';
|
2020-08-05 01:09:24 +03:00
|
|
|
|
2020-12-16 18:28:44 +03:00
|
|
|
it('exposeBinding should work', async ({page}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
let bindingSource;
|
|
|
|
await page.exposeBinding('add', (source, a, b) => {
|
|
|
|
bindingSource = source;
|
|
|
|
return a + b;
|
|
|
|
});
|
|
|
|
const result = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return window['add'](5, 6);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
2020-12-16 18:28:44 +03:00
|
|
|
expect(bindingSource.context).toBe(page.context());
|
2020-08-05 01:09:24 +03:00
|
|
|
expect(bindingSource.page).toBe(page);
|
|
|
|
expect(bindingSource.frame).toBe(page.mainFrame());
|
|
|
|
expect(result).toEqual(11);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return a * b;
|
|
|
|
});
|
|
|
|
const result = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](9, 4);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(36);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work with handles and complex objects', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
const fooHandle = await page.evaluateHandle(() => {
|
2020-08-12 01:50:53 +03:00
|
|
|
window['fooValue'] = { bar: 2 };
|
|
|
|
return window['fooValue'];
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
await page.exposeFunction('handle', () => {
|
|
|
|
return [{ foo: fooHandle }];
|
|
|
|
});
|
|
|
|
const equals = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
const value = await window['handle']();
|
2020-08-05 01:09:24 +03:00
|
|
|
const [{ foo }] = value;
|
2020-08-12 01:50:53 +03:00
|
|
|
return foo === window['fooValue'];
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(equals).toBe(true);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should throw exception in page context', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('woof', function() {
|
|
|
|
throw new Error('WOOF WOOF');
|
|
|
|
});
|
2020-08-28 14:20:29 +03:00
|
|
|
const {message, stack} = await page.evaluate(async () => {
|
2020-08-05 01:09:24 +03:00
|
|
|
try {
|
2020-08-28 14:20:29 +03:00
|
|
|
await window['woof']();
|
2020-08-05 01:09:24 +03:00
|
|
|
} catch (e) {
|
|
|
|
return {message: e.message, stack: e.stack};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
expect(message).toBe('WOOF WOOF');
|
|
|
|
expect(stack).toContain(__filename);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should support throwing "null"', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('woof', function() {
|
|
|
|
throw null;
|
|
|
|
});
|
2020-08-28 14:20:29 +03:00
|
|
|
const thrown = await page.evaluate(async () => {
|
2020-08-05 01:09:24 +03:00
|
|
|
try {
|
2020-08-28 14:20:29 +03:00
|
|
|
await window['woof']();
|
2020-08-05 01:09:24 +03:00
|
|
|
} catch (e) {
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
expect(thrown).toBe(null);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should be callable from-inside addInitScript', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
let called = false;
|
|
|
|
await page.exposeFunction('woof', function() {
|
|
|
|
called = true;
|
|
|
|
});
|
2020-08-28 14:20:29 +03:00
|
|
|
await page.addInitScript(() => window['woof']());
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.reload();
|
|
|
|
expect(called).toBe(true);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should survive navigation', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return a * b;
|
|
|
|
});
|
|
|
|
|
|
|
|
await page.goto(server.EMPTY_PAGE);
|
|
|
|
const result = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](9, 4);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(36);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should await returned promise', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return Promise.resolve(a * b);
|
|
|
|
});
|
|
|
|
|
|
|
|
const result = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](3, 5);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(15);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work on frames', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return Promise.resolve(a * b);
|
|
|
|
});
|
|
|
|
|
|
|
|
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
|
|
|
const frame = page.frames()[1];
|
|
|
|
const result = await frame.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](3, 5);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(15);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work on frames before navigation', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return Promise.resolve(a * b);
|
|
|
|
});
|
|
|
|
|
|
|
|
const frame = page.frames()[1];
|
|
|
|
const result = await frame.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](3, 5);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(15);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work after cross origin navigation', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.goto(server.EMPTY_PAGE);
|
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return a * b;
|
|
|
|
});
|
|
|
|
|
|
|
|
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
|
|
|
|
const result = await page.evaluate(async function() {
|
2020-08-12 01:50:53 +03:00
|
|
|
return await window['compute'](9, 4);
|
2020-08-05 01:09:24 +03:00
|
|
|
});
|
|
|
|
expect(result).toBe(36);
|
|
|
|
});
|
|
|
|
|
2020-08-28 14:20:29 +03:00
|
|
|
it('should work with complex objects', async ({page, server}) => {
|
2020-08-05 01:09:24 +03:00
|
|
|
await page.exposeFunction('complexObject', function(a, b) {
|
|
|
|
return {x: a.x + b.x};
|
|
|
|
});
|
2020-08-28 14:20:29 +03:00
|
|
|
const result = await page.evaluate(async () => window['complexObject']({x: 5}, {x: 2}));
|
2020-08-05 01:09:24 +03:00
|
|
|
expect(result.x).toBe(7);
|
|
|
|
});
|
2020-10-02 08:47:31 +03:00
|
|
|
|
|
|
|
it('exposeBindingHandle should work', async ({page}) => {
|
|
|
|
let target;
|
|
|
|
await page.exposeBinding('logme', (source, t) => {
|
|
|
|
target = t;
|
|
|
|
return 17;
|
|
|
|
}, { handle: true });
|
|
|
|
const result = await page.evaluate(async function() {
|
|
|
|
return window['logme']({ foo: 42 });
|
|
|
|
});
|
|
|
|
expect(await target.evaluate(x => x.foo)).toBe(42);
|
|
|
|
expect(result).toEqual(17);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('exposeBindingHandle should not throw during navigation', async ({page, server}) => {
|
|
|
|
await page.exposeBinding('logme', (source, t) => {
|
|
|
|
return 17;
|
|
|
|
}, { handle: true });
|
|
|
|
await page.goto(server.EMPTY_PAGE);
|
|
|
|
await Promise.all([
|
|
|
|
page.evaluate(async url => {
|
|
|
|
window['logme']({ foo: 42 });
|
|
|
|
window.location.href = url;
|
|
|
|
}, server.PREFIX + '/one-style.html'),
|
|
|
|
page.waitForNavigation({ waitUntil: 'load' }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw for duplicate registrations', async ({page}) => {
|
|
|
|
await page.exposeFunction('foo', () => {});
|
|
|
|
const error = await page.exposeFunction('foo', () => {}).catch(e => e);
|
|
|
|
expect(error.message).toContain('page.exposeFunction: Function "foo" has been already registered');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('exposeBindingHandle should throw for multiple arguments', async ({page}) => {
|
|
|
|
await page.exposeBinding('logme', (source, t) => {
|
|
|
|
return 17;
|
|
|
|
}, { handle: true });
|
|
|
|
expect(await page.evaluate(async function() {
|
|
|
|
return window['logme']({ foo: 42 });
|
|
|
|
})).toBe(17);
|
|
|
|
expect(await page.evaluate(async function() {
|
|
|
|
return window['logme']({ foo: 42 }, undefined, undefined);
|
|
|
|
})).toBe(17);
|
|
|
|
expect(await page.evaluate(async function() {
|
|
|
|
return window['logme'](undefined, undefined, undefined);
|
|
|
|
})).toBe(17);
|
|
|
|
|
|
|
|
const error = await page.evaluate(async function() {
|
|
|
|
return window['logme'](1, 2);
|
|
|
|
}).catch(e => e);
|
|
|
|
expect(error.message).toContain('exposeBindingHandle supports a single argument, 2 received');
|
|
|
|
});
|
2020-12-03 21:51:59 +03:00
|
|
|
|
2021-04-09 17:59:09 +03:00
|
|
|
it('should not result in unhandled rejection', async ({page, isAndroid}) => {
|
|
|
|
it.fixme(isAndroid);
|
2021-04-05 05:32:14 +03:00
|
|
|
|
2020-12-05 01:48:20 +03:00
|
|
|
const closedPromise = page.waitForEvent('close');
|
2020-12-03 21:51:59 +03:00
|
|
|
await page.exposeFunction('foo', async () => {
|
|
|
|
await page.close();
|
|
|
|
});
|
|
|
|
await page.evaluate(() => {
|
2020-12-05 01:48:20 +03:00
|
|
|
setTimeout(() => (window as any).foo(), 0);
|
|
|
|
return undefined;
|
2020-12-03 21:51:59 +03:00
|
|
|
});
|
2020-12-05 01:48:20 +03:00
|
|
|
await closedPromise;
|
|
|
|
// Make a round-trip to be sure we did not throw immediately after closing.
|
|
|
|
expect(await page.evaluate('1 + 1').catch(e => e)).toBeInstanceOf(Error);
|
2020-12-03 21:51:59 +03:00
|
|
|
});
|
2020-12-08 19:38:29 +03:00
|
|
|
|
2021-04-09 17:59:09 +03:00
|
|
|
it('should work with internal bindings', async ({page, toImpl, server, mode, browserName, isElectron, isAndroid}) => {
|
2021-04-05 05:32:14 +03:00
|
|
|
it.skip(mode !== 'default');
|
|
|
|
it.skip(browserName !== 'chromium');
|
2021-04-09 17:59:09 +03:00
|
|
|
it.skip(isAndroid);
|
|
|
|
it.skip(isElectron);
|
2021-04-05 05:32:14 +03:00
|
|
|
|
2021-05-06 05:10:28 +03:00
|
|
|
const implPage: import('../../src/server/page').Page = toImpl(page);
|
2020-12-08 19:38:29 +03:00
|
|
|
let foo;
|
|
|
|
await implPage.exposeBinding('foo', false, ({}, arg) => {
|
|
|
|
foo = arg;
|
|
|
|
}, 'utility');
|
|
|
|
expect(await page.evaluate('!!window.foo')).toBe(false);
|
2021-03-17 20:47:07 +03:00
|
|
|
expect(await implPage.mainFrame().evaluateExpression('!!window.foo', false, {}, 'utility')).toBe(true);
|
2020-12-08 19:38:29 +03:00
|
|
|
expect(foo).toBe(undefined);
|
2021-03-17 20:47:07 +03:00
|
|
|
await implPage.mainFrame().evaluateExpression('window.foo(123)', false, {}, 'utility');
|
2020-12-08 19:38:29 +03:00
|
|
|
expect(foo).toBe(123);
|
|
|
|
|
|
|
|
// should work after reload
|
|
|
|
await page.goto(server.EMPTY_PAGE);
|
|
|
|
expect(await page.evaluate('!!window.foo')).toBe(false);
|
2021-03-17 20:47:07 +03:00
|
|
|
await implPage.mainFrame().evaluateExpression('window.foo(456)', false, {}, 'utility');
|
2020-12-08 19:38:29 +03:00
|
|
|
expect(foo).toBe(456);
|
|
|
|
|
|
|
|
// should work inside frames
|
|
|
|
const frame = await attachFrame(page, 'myframe', server.CROSS_PROCESS_PREFIX + '/empty.html');
|
|
|
|
expect(await frame.evaluate('!!window.foo')).toBe(false);
|
2021-05-06 05:10:28 +03:00
|
|
|
const implFrame: import('../../src/server/frames').Frame = toImpl(frame);
|
2021-03-17 20:47:07 +03:00
|
|
|
await implFrame.evaluateExpression('window.foo(789)', false, {}, 'utility');
|
2020-12-08 19:38:29 +03:00
|
|
|
expect(foo).toBe(789);
|
|
|
|
});
|
|
|
|
|
2021-01-05 00:54:55 +03:00
|
|
|
it('exposeBinding(handle) should work with element handles', async ({ page}) => {
|
|
|
|
let cb;
|
|
|
|
const promise = new Promise(f => cb = f);
|
|
|
|
await page.exposeBinding('clicked', async (source, element: ElementHandle) => {
|
|
|
|
cb(await element.innerText().catch(e => e));
|
|
|
|
}, { handle: true });
|
|
|
|
await page.goto('about:blank');
|
|
|
|
await page.setContent(`
|
|
|
|
<script>
|
|
|
|
document.addEventListener('click', event => window.clicked(event.target));
|
|
|
|
</script>
|
|
|
|
<div id="a1">Click me</div>
|
|
|
|
`);
|
|
|
|
await page.click('#a1');
|
|
|
|
expect(await promise).toBe('Click me');
|
|
|
|
});
|
2021-02-27 01:16:32 +03:00
|
|
|
|
|
|
|
it('should work with setContent', async ({page, server}) => {
|
|
|
|
await page.exposeFunction('compute', function(a, b) {
|
|
|
|
return Promise.resolve(a * b);
|
|
|
|
});
|
|
|
|
await page.setContent('<script>window.result = compute(3, 2)</script>');
|
|
|
|
expect(await page.evaluate('window.result')).toBe(6);
|
|
|
|
});
|