[ftr] default window size (#14834)

* [ftr/mocha] revert Mocha UI assigments after loading test files

* [ftr] add beforeTopLevelSuite and afterTopLevelSuite lifecycle hooks

* [ftr] add defaultWindowWidth and defaultWindowHeight options

Adds two configuration parameters to the functional test runner which
will set the windowSize before running the tests in any file and
restores the original window size when the tests complete.

Individual test files can set the windowSize within its own before() or
beforeEach() handlers using the same `command.setWindowSize()` command.

* [ftr/assignmentProxy] use better naming

* [ftr] restore initial window size after each suite

* [ftr] improve error message for unexpected suite definitions

* [ftr/remote] remove configuration option, rely on suite-level changes

* [ftr] fix stack manipulation order

* [ftr/remote] write tests for window size management
This commit is contained in:
Spencer 2017-11-17 11:15:02 -06:00 committed by GitHub
parent 8f1aa33604
commit 60298ec8b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 249 additions and 2 deletions

View file

@ -2,7 +2,9 @@ export function createLifecycle() {
const listeners = {
beforeLoadTests: [],
beforeTests: [],
beforeTestSuite: [],
beforeEachTest: [],
afterTestSuite: [],
testFailure: [],
testHookFailure: [],
cleanup: [],

View file

@ -1,7 +1,25 @@
export function createAssignmentProxy(object, interceptor) {
const originalValues = new Map();
return new Proxy(object, {
set(target, property, value) {
if (!originalValues.has(property)) {
originalValues.set(property, object[property]);
}
return Reflect.set(target, property, interceptor(property, value));
},
get(target, property) {
if (property === 'revertProxiedAssignments') {
return function () {
for (const [property, value] of originalValues) {
object[property] = value;
}
};
}
return Reflect.get(target, property);
}
});
}

View file

@ -24,7 +24,7 @@ export function decorateMochaUi(lifecycle, context) {
*/
function wrapSuiteFunction(name, fn) {
return wrapFunction(fn, {
before() {
before(target, thisArg, argumentsList) {
if (suiteCount > 0 && suiteLevel === 0) {
throw new Error(`
Test files must only define a single top-level suite. Please ensure that
@ -32,6 +32,23 @@ export function decorateMochaUi(lifecycle, context) {
`);
}
const [name, provider] = argumentsList;
if (typeof name !== 'string' || typeof provider !== 'function') {
throw new Error(`Unexpected arguments to ${name}(${argumentsList.join(', ')})`);
}
argumentsList[1] = function () {
before(async () => {
await lifecycle.trigger('beforeTestSuite', this);
});
provider.call(this);
after(async () => {
await lifecycle.trigger('afterTestSuite', this);
});
};
suiteCount += 1;
suiteLevel += 1;
},

View file

@ -38,7 +38,8 @@ export const loadTestFiles = (mocha, log, lifecycle, providers, paths) => {
loadTracer(provider, `testProvider[${path}]`, () => {
// mocha.suite hocus-pocus comes from: https://git.io/vDnXO
mocha.suite.emit('pre-require', decorateMochaUi(lifecycle, global), path, mocha);
const context = decorateMochaUi(lifecycle, global);
mocha.suite.emit('pre-require', context, path, mocha);
const returnVal = provider({
loadTestFile: innerLoadTestFile,
@ -53,6 +54,7 @@ export const loadTestFiles = (mocha, log, lifecycle, providers, paths) => {
mocha.suite.emit('require', returnVal, path, mocha);
mocha.suite.emit('post-require', global, path, mocha);
context.revertProxiedAssignments();
});
};

View file

@ -0,0 +1,12 @@
import { RemoteProvider } from '../../../remote';
export default function () {
return {
testFiles: [
require.resolve('./test'),
],
services: {
remote: RemoteProvider
}
};
}

View file

@ -0,0 +1,31 @@
export default function ({ getService, loadTestFile }) {
const remote = getService('remote');
describe('suite1', () => {
before(async () => {
process.send({
name: 'before suite1',
size: await remote.getWindowSize()
});
await remote.setWindowSize(1000, 1000);
});
it('has the right window size', async () => {
process.send({
name: 'in suite1',
size: await remote.getWindowSize()
});
});
loadTestFile(require.resolve('./test2'));
loadTestFile(require.resolve('./test3'));
after(async () => {
process.send({
name: 'after suite1',
size: await remote.getWindowSize()
});
});
});
}

View file

@ -0,0 +1,28 @@
export default function ({ getService }) {
const remote = getService('remote');
describe('suite2', () => {
before(async () => {
process.send({
name: 'before suite2',
size: await remote.getWindowSize()
});
await remote.setWindowSize(900, 900);
});
it('has the right window size', async () => {
process.send({
name: 'in suite2',
size: await remote.getWindowSize()
});
});
after(async () => {
process.send({
name: 'after suite2',
size: await remote.getWindowSize()
});
});
});
}

View file

@ -0,0 +1,28 @@
export default function ({ getService }) {
const remote = getService('remote');
describe('suite3.1', () => {
before(async () => {
process.send({
name: 'before suite3.1',
size: await remote.getWindowSize()
});
await remote.setWindowSize(700, 700);
});
it('has the right window size', async () => {
process.send({
name: 'in suite3.1',
size: await remote.getWindowSize()
});
});
after(async () => {
process.send({
name: 'after suite3.1',
size: await remote.getWindowSize()
});
});
});
}

View file

@ -0,0 +1,30 @@
export default function ({ getService, loadTestFile }) {
const remote = getService('remote');
describe('suite3', () => {
before(async () => {
process.send({
name: 'before suite3',
size: await remote.getWindowSize()
});
await remote.setWindowSize(800, 800);
});
it('has the right window size', async () => {
process.send({
name: 'in suite3',
size: await remote.getWindowSize()
});
});
loadTestFile(require.resolve('./test3.1'));
after(async () => {
process.send({
name: 'after suite3',
size: await remote.getWindowSize()
});
});
});
}

View file

@ -0,0 +1,63 @@
import { fork } from 'child_process';
import expect from 'expect.js';
const FTR_SCRIPT = require.resolve('../../../../../scripts/functional_test_runner');
const CONFIG_PATH = require.resolve('./fixtures/several_nested_window_size_changes/config.js');
const SECOND = 1000;
const DEFAULT_SIZE = { width: 1600, height: 1000 };
const SUITE1_SIZE = { width: 1000, height: 1000 };
const SUITE2_SIZE = { width: 900, height: 900 };
const SUITE3_SIZE = { width: 800, height: 800 };
const SUITE31_SIZE = { width: 700, height: 700 };
describe('remote default window size', function () {
// give the tests some time to initialize the FTR and Chrome
this.timeout(30 * SECOND);
it('restores the window size after a suite completes', async () => {
const proc = fork(FTR_SCRIPT, [
'--config', CONFIG_PATH
], {
silent: true
});
const messages = [];
proc.on('message', (msg) => {
messages.push(msg);
});
await new Promise((resolve, reject) => {
proc.once('exit', resolve);
proc.once('error', reject);
});
expect(messages).to.eql([
// default width/height
{ name: 'before suite1', size: DEFAULT_SIZE },
// suite1 uses 1000X1000
{ name: 'in suite1', size: SUITE1_SIZE },
// suite2 should start with suite1's size
{ name: 'before suite2', size: SUITE1_SIZE },
// but it changes it to 900X900
{ name: 'in suite2', size: SUITE2_SIZE },
// and it should still be 900X900 when it ends
{ name: 'after suite2', size: SUITE2_SIZE },
// suite3 should then start with suite1's size, because suite2's was rolled back
{ name: 'before suite3', size: SUITE1_SIZE },
// it then runs in 800x800
{ name: 'in suite3', size: SUITE3_SIZE },
// suite3.1 runs within 3, so it should start with suite3 size
{ name: 'before suite3.1', size: SUITE3_SIZE },
// then switch to its 700x700 size
{ name: 'in suite3.1', size: SUITE31_SIZE },
// and still has 800x800 in it's last after hook
{ name: 'after suite3.1', size: SUITE31_SIZE },
// but suite3 size should be restored before running suite3's after hook
{ name: 'after suite3', size: SUITE3_SIZE },
// then finally, suite1 should complete with 1000X1000 because suite3's size was rolled back
{ name: 'after suite1', size: SUITE1_SIZE }
]);
});
});

View file

@ -17,6 +17,22 @@ export async function RemoteProvider({ getService }) {
log.info('Remote initialized');
lifecycle.on('beforeTests', async () => {
// hard coded default, can be overriden per suite using `remote.setWindowSize()`
// and will be automatically reverted after each suite
await command.setWindowSize(1600, 1000);
});
const windowSizeStack = [];
lifecycle.on('beforeTestSuite', async () => {
windowSizeStack.unshift(await command.getWindowSize());
});
lifecycle.on('afterTestSuite', async () => {
const { width, height } = windowSizeStack.shift();
await command.setWindowSize(width, height);
});
return new Proxy({}, {
get(obj, prop) {
if (prop === 'then' || prop === 'catch' || prop === 'finally') {