Add sample app layout snapshot test
Summary: This is fairly experimental. The test passes, but I don't know how stable it will be when litho changes for example. However, it's easy to exclude specific attributes from the hierarchy, so maybe we can iterate if it breaks at first. It just takes a snapshot of the layout hierarchy from headless flipper, and compares it to it's known one. To run it: `cd headless-tests && DEVICE=emulator-5554 yarn test` while you have the sample app running on an emulator. To update the snapshots, just add `-u` to the end of that command. Reviewed By: danielbuechele Differential Revision: D15715674 fbshipit-source-id: 4fe6f83b60f8003d48aceb6468d93c075e6c38b8
This commit is contained in:
committed by
Facebook Github Bot
parent
c4f395dda2
commit
173ce43192
File diff suppressed because one or more lines are too long
@@ -7,9 +7,25 @@
|
|||||||
|
|
||||||
import {spawn} from 'child_process';
|
import {spawn} from 'child_process';
|
||||||
import memoize from 'lodash.memoize';
|
import memoize from 'lodash.memoize';
|
||||||
|
// $FlowFixMe
|
||||||
|
import stringify from 'canonical-json';
|
||||||
|
|
||||||
const TEST_TIMEOUT_MS = 30 * 1000;
|
const TEST_TIMEOUT_MS = 30 * 1000;
|
||||||
|
|
||||||
|
const layoutPathsToExcludeFromSnapshots = [
|
||||||
|
'id',
|
||||||
|
'children.*',
|
||||||
|
'extraInfo.linkedAXNode',
|
||||||
|
'data.View.*.value',
|
||||||
|
'data.View.*.*.value',
|
||||||
|
'data.View.*.*.*.value',
|
||||||
|
'data.Drawable.*.value',
|
||||||
|
'data.LithoView.mountbounds',
|
||||||
|
'data.Layout.height',
|
||||||
|
'data.Layout.width',
|
||||||
|
'data.*.typeface',
|
||||||
|
];
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
bin: process.env.FLIPPER_PATH || '/tmp/flipper-macos',
|
bin: process.env.FLIPPER_PATH || '/tmp/flipper-macos',
|
||||||
securePort: process.env.SECURE_PORT || '8088',
|
securePort: process.env.SECURE_PORT || '8088',
|
||||||
@@ -63,6 +79,25 @@ const runHeadless = memoize((args: Array<string>) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getPluginState(app: string, plugin: string): Promise<Object> {
|
||||||
|
return runHeadless(basicArgs).then(result => {
|
||||||
|
const pluginStates = result.store.pluginStates;
|
||||||
|
for (const pluginId of Object.keys(pluginStates)) {
|
||||||
|
const matches = /([^#]+)#([^#]+)#([^#]+)#([^#]+)#([^#]+)/.exec(pluginId);
|
||||||
|
if (
|
||||||
|
matches &&
|
||||||
|
matches.length === 6 &&
|
||||||
|
matches[1] === app &&
|
||||||
|
matches[5] === plugin
|
||||||
|
) {
|
||||||
|
const id = matches[0];
|
||||||
|
return pluginStates[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`No matching plugin state for ${app}, ${plugin}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Flipper app appears in exported clients',
|
'Flipper app appears in exported clients',
|
||||||
() => {
|
() => {
|
||||||
@@ -112,3 +147,85 @@ test(
|
|||||||
},
|
},
|
||||||
TEST_TIMEOUT_MS,
|
TEST_TIMEOUT_MS,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function stripUnstableLayoutAttributes(node: Object): Object {
|
||||||
|
let newNode = node;
|
||||||
|
for (const path of layoutPathsToExcludeFromSnapshots) {
|
||||||
|
const parts = path.split('.');
|
||||||
|
newNode = stripNode(newNode, parts);
|
||||||
|
}
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripNode(node: any, path: Array<string>) {
|
||||||
|
if (path.length === 0) {
|
||||||
|
return 'PLACEHOLDER';
|
||||||
|
}
|
||||||
|
if (path[0] === '*') {
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
return node.map(e => stripNode(e, path.slice(1)));
|
||||||
|
}
|
||||||
|
return Object.entries(node).reduce((acc, [key, val]) => {
|
||||||
|
acc[key] = stripNode(val, path.slice(1));
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
if (!node[path[0]]) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return {...node, [path[0]]: stripNode(node[path[0]], path.slice(1))};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('test layout snapshot stripping', () => {
|
||||||
|
const beforeStripping = {
|
||||||
|
my: {
|
||||||
|
test: {
|
||||||
|
id: 4,
|
||||||
|
node: 7,
|
||||||
|
something: 9,
|
||||||
|
list: [1, 2, 3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
id: 2,
|
||||||
|
children: [1, 2, 3],
|
||||||
|
extraInfo: {
|
||||||
|
linkedAXNode: 55,
|
||||||
|
somethingElse: 44,
|
||||||
|
},
|
||||||
|
data: {View: {bounds: {something: {value: 4}}}},
|
||||||
|
other: 8,
|
||||||
|
};
|
||||||
|
const afterStripping = stripUnstableLayoutAttributes(beforeStripping);
|
||||||
|
expect(afterStripping).toEqual({
|
||||||
|
my: {
|
||||||
|
test: {
|
||||||
|
id: 4,
|
||||||
|
node: 7,
|
||||||
|
something: 9,
|
||||||
|
list: [1, 2, 3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
id: 'PLACEHOLDER',
|
||||||
|
children: ['PLACEHOLDER', 'PLACEHOLDER', 'PLACEHOLDER'],
|
||||||
|
extraInfo: {
|
||||||
|
linkedAXNode: 'PLACEHOLDER',
|
||||||
|
somethingElse: 44,
|
||||||
|
},
|
||||||
|
data: {View: {bounds: {something: {value: 'PLACEHOLDER'}}}},
|
||||||
|
other: 8,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Sample app layout hierarchy matches snapshot', () => {
|
||||||
|
return getPluginState('Flipper', 'Inspector').then(state => {
|
||||||
|
expect(state.rootAXElement).toBe('com.facebook.flipper.sample');
|
||||||
|
expect(state.rootElement).toBe('com.facebook.flipper.sample');
|
||||||
|
const canonicalizedElements = Object.values(state.elements)
|
||||||
|
.map(e => {
|
||||||
|
const stableizedElements = stripUnstableLayoutAttributes(e);
|
||||||
|
return stringify(stableizedElements);
|
||||||
|
})
|
||||||
|
.sort();
|
||||||
|
expect(canonicalizedElements).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@babel/preset-env": "^7.4.5",
|
"@babel/preset-env": "^7.4.5",
|
||||||
"@babel/preset-flow": "^7.0.0",
|
"@babel/preset-flow": "^7.0.0",
|
||||||
"babel-jest": "^24.8.0",
|
"babel-jest": "^24.8.0",
|
||||||
|
"canonical-json": "^0.0.4",
|
||||||
"lodash.memoize": "^4.1.2"
|
"lodash.memoize": "^4.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1170,6 +1170,11 @@ caniuse-lite@^1.0.30000971:
|
|||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz#d1000e4546486a6977756547352bc96a4cfd2b13"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz#d1000e4546486a6977756547352bc96a4cfd2b13"
|
||||||
integrity sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==
|
integrity sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==
|
||||||
|
|
||||||
|
canonical-json@^0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/canonical-json/-/canonical-json-0.0.4.tgz#6579c072c3db5c477ec41dc978fbf2b8f41074a3"
|
||||||
|
integrity sha1-ZXnAcsPbXEd+xB3JePvyuPQQdKM=
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
|
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
|
||||||
|
|||||||
Reference in New Issue
Block a user