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:
John Knox
2019-06-12 03:25:20 -07:00
committed by Facebook Github Bot
parent c4f395dda2
commit 173ce43192
4 changed files with 155 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -7,9 +7,25 @@
import {spawn} from 'child_process';
import memoize from 'lodash.memoize';
// $FlowFixMe
import stringify from 'canonical-json';
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 = {
bin: process.env.FLIPPER_PATH || '/tmp/flipper-macos',
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(
'Flipper app appears in exported clients',
() => {
@@ -112,3 +147,85 @@ test(
},
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();
});
});

View File

@@ -19,6 +19,7 @@
"@babel/preset-env": "^7.4.5",
"@babel/preset-flow": "^7.0.0",
"babel-jest": "^24.8.0",
"canonical-json": "^0.0.4",
"lodash.memoize": "^4.1.2"
}
}

View File

@@ -1170,6 +1170,11 @@ caniuse-lite@^1.0.30000971:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz#d1000e4546486a6977756547352bc96a4cfd2b13"
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:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"