test: split out evaluation tests (#3584)
This commit is contained in:
parent
e83918987a
commit
d61916b53e
248
test/evaluation.spec.js
Normal file
248
test/evaluation.spec.js
Normal file
@ -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('<section>42</section>');
|
||||
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('<section>39</section>');
|
||||
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');
|
||||
});
|
||||
});
|
||||
};
|
@ -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');
|
||||
|
@ -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('<section>42</section>');
|
||||
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('<section>39</section>');
|
||||
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();
|
||||
|
@ -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});
|
||||
|
Loading…
Reference in New Issue
Block a user