Skip to content

Commit

Permalink
Use ReactDOM Test Selector API in DevTools e2e tests (facebook#22978)
Browse files Browse the repository at this point in the history
Builds on top of the existing Playwright tests to plug in the test selector API: https://gist.github.com/bvaughn/d3c8b8842faf2ac2439bb11773a19cec

My goals in doing this are to...
1. Experiment with the new API to see what works and what doesn't.
2. Add some test selector attributes (and remove DOM-structure based selectors).
3. Focus the tests on DevTools itself (rather than the test app).

I also took this opportunity to add a few new test cases– like named hooks, editable props, component search, and profiling- just to play around more with the Playwright API.

Relates to issue facebook#22646
  • Loading branch information
Brian Vaughn authored and zhengjitf committed Apr 15, 2022
1 parent 238a4f0 commit 5204f56
Show file tree
Hide file tree
Showing 24 changed files with 549 additions and 74 deletions.
206 changes: 206 additions & 0 deletions packages/react-devtools-inline/__tests__/__e2e__/components.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/** @flow */

'use strict';

const listAppUtils = require('./list-app-utils');
const devToolsUtils = require('./devtools-utils');
const {test, expect} = require('@playwright/test');
const config = require('../../playwright.config');
test.use(config);
test.describe('Components', () => {
let page;

test.beforeEach(async ({browser}) => {
page = await browser.newPage();

await page.goto('http://localhost:8080/e2e.html', {
waitUntil: 'domcontentloaded',
});

await page.waitForSelector('#iframe');

await devToolsUtils.clickButton(page, 'TabBarButton-components');
});

test('Should display initial React components', async () => {
const appRowCount = await page.evaluate(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_APP;
const container = document.getElementById('iframe').contentDocument;
const rows = findAllNodes(container, [
createTestNameSelector('ListItem'),
]);
return rows.length;
});
expect(appRowCount).toBe(3);

const devToolsRowCount = await devToolsUtils.getElementCount(
page,
'ListItem'
);
expect(devToolsRowCount).toBe(3);
});

test('Should display newly added React components', async () => {
await listAppUtils.addItem(page, 'four');

const count = await devToolsUtils.getElementCount(page, 'ListItem');
expect(count).toBe(4);
});

test('Should allow elements to be inspected', async () => {
// Select the first list item in DevTools.
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');

// Then read the inspected values.
const [propName, propValue, sourceText] = await page.evaluate(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const editableName = findAllNodes(container, [
createTestNameSelector('InspectedElementPropsTree'),
createTestNameSelector('EditableName'),
])[0];
const editableValue = findAllNodes(container, [
createTestNameSelector('InspectedElementPropsTree'),
createTestNameSelector('EditableValue'),
])[0];
const source = findAllNodes(container, [
createTestNameSelector('InspectedElementView-Source'),
])[0];

return [editableName.value, editableValue.value, source.innerText];
});

expect(propName).toBe('label');
expect(propValue).toBe('"one"');
expect(sourceText).toContain('ListApp.js');
});

test('should allow props to be edited', async () => {
// Select the first list item in DevTools.
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');

// Then edit the label prop.
await page.evaluate(() => {
const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

focusWithin(container, [
createTestNameSelector('InspectedElementPropsTree'),
createTestNameSelector('EditableValue'),
]);
});

page.keyboard.press('Backspace'); // "
page.keyboard.press('Backspace'); // e
page.keyboard.press('Backspace'); // n
page.keyboard.press('Backspace'); // o
page.keyboard.insertText('new"');
page.keyboard.press('Enter');

await page.waitForFunction(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_APP;
const container = document.getElementById('iframe').contentDocument;
const rows = findAllNodes(container, [
createTestNameSelector('ListItem'),
])[0];
return rows.innerText === 'new';
});
});

test('should load and parse hook names for the inspected element', async () => {
// Select the List component DevTools.
await devToolsUtils.selectElement(page, 'List', 'App');

// Then click to load and parse hook names.
await devToolsUtils.clickButton(page, 'LoadHookNamesButton');

// Make sure the expected hook names are parsed and displayed eventually.
await page.waitForFunction(
hookNames => {
const {
createTestNameSelector,
findAllNodes,
} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const hooksTree = findAllNodes(container, [
createTestNameSelector('InspectedElementHooksTree'),
])[0];

if (!hooksTree) {
return false;
}

const hooksTreeText = hooksTree.innerText;

for (let i = 0; i < hookNames.length; i++) {
if (!hooksTreeText.includes(hookNames[i])) {
return false;
}
}

return true;
},
['State(items)', 'Ref(inputRef)']
);
});

test('should allow searching for component by name', async () => {
async function getComponentSearchResultsCount() {
return await page.evaluate(() => {
const {
createTestNameSelector,
findAllNodes,
} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const element = findAllNodes(container, [
createTestNameSelector('ComponentSearchInput-ResultsCount'),
])[0];
return element.innerText;
});
}

await page.evaluate(() => {
const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

focusWithin(container, [
createTestNameSelector('ComponentSearchInput-Input'),
]);
});

page.keyboard.insertText('List');
let count = await getComponentSearchResultsCount();
expect(count).toBe('1 | 4');

page.keyboard.insertText('Item');
count = await getComponentSearchResultsCount();
expect(count).toBe('1 | 3');

page.keyboard.press('Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('2 | 3');

page.keyboard.press('Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('3 | 3');

page.keyboard.press('Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('1 | 3');

page.keyboard.press('Shift+Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('3 | 3');

page.keyboard.press('Shift+Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('2 | 3');

page.keyboard.press('Shift+Enter');
count = await getComponentSearchResultsCount();
expect(count).toBe('1 | 3');
});
});
83 changes: 83 additions & 0 deletions packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

/** @flow */

async function clickButton(page, buttonTestName) {
await page.evaluate(testName => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const button = findAllNodes(container, [
createTestNameSelector(testName),
])[0];
button.click();
}, buttonTestName);
}

async function getElementCount(page, displayName) {
return await page.evaluate(listItemText => {
const {
createTestNameSelector,
createTextSelector,
findAllNodes,
} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');
const rows = findAllNodes(container, [
createTestNameSelector('ComponentTreeListItem'),
createTextSelector(listItemText),
]);
return rows.length;
}, displayName);
}

async function selectElement(page, displayName, waitForOwnersText) {
await page.evaluate(listItemText => {
const {
createTestNameSelector,
createTextSelector,
findAllNodes,
} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const listItem = findAllNodes(container, [
createTestNameSelector('ComponentTreeListItem'),
createTextSelector(listItemText),
])[0];
listItem.click();
}, displayName);

if (waitForOwnersText) {
// Wait for selected element's props to load.
await page.waitForFunction(
({titleText, ownersListText}) => {
const {
createTestNameSelector,
findAllNodes,
} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const title = findAllNodes(container, [
createTestNameSelector('InspectedElement-Title'),
])[0];

const ownersList = findAllNodes(container, [
createTestNameSelector('InspectedElementView-Owners'),
])[0];

return (
title &&
title.innerText.includes(titleText) &&
ownersList &&
ownersList.innerText.includes(ownersListText)
);
},
{titleText: displayName, ownersListText: waitForOwnersText}
);
}
}

module.exports = {
clickButton,
getElementCount,
selectElement,
};

This file was deleted.

25 changes: 25 additions & 0 deletions packages/react-devtools-inline/__tests__/__e2e__/list-app-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

/** @flow */

async function addItem(page, newItemText) {
await page.evaluate(text => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_APP;
const container = document.getElementById('iframe').contentDocument;

const input = findAllNodes(container, [
createTestNameSelector('AddItemInput'),
])[0];
input.value = text;

const button = findAllNodes(container, [
createTestNameSelector('AddItemButton'),
])[0];

button.click();
}, newItemText);
}

module.exports = {
addItem,
};
Loading

0 comments on commit 5204f56

Please sign in to comment.