Skip to content

Commit 1328ff7

Browse files
authored
[DevTools] Regression-proof e2e Tests (#24620)
This PR: * Increases test retry count to 2 so that flaky tests have more of a chance to pass * Ideally most e2e tests will run for all React versions (and ensure DevTools elegantly fails if React doesn't support its features). However, some features aren't supported in older React versions at all (ex. Profiling) Add runOnlyForReactRange function in these cases to skip tests that don't satisfy the correct React semver range * Fix should allow searching for component by name test, which was flaky because sometimes the Searchbox would be unfocused the second time we try to type in it * Edited test Should allow elements to be inspected to check that element inspect gracefully fails in older React versions * Updated config to add a config.use.url field and a config.use.react_version field, which change depending on the React Version (and whether it's specified)
1 parent 05c34de commit 1328ff7

File tree

5 files changed

+99
-29
lines changed

5 files changed

+99
-29
lines changed

packages/react-devtools-inline/__tests__/__e2e__/components.test.js

+61-25
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
'use strict';
44

5+
const {runOnlyForReactRange} = require('./utils');
56
const listAppUtils = require('./list-app-utils');
67
const devToolsUtils = require('./devtools-utils');
78
const {test, expect} = require('@playwright/test');
89
const config = require('../../playwright.config');
10+
const semver = require('semver');
911
test.use(config);
1012
test.describe('Components', () => {
1113
let page;
1214

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

16-
await page.goto('http://localhost:8080/e2e.html', {
18+
await page.goto(config.use.url, {
1719
waitUntil: 'domcontentloaded',
1820
});
1921

@@ -51,32 +53,60 @@ test.describe('Components', () => {
5153
// Select the first list item in DevTools.
5254
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
5355

56+
// Prop names/values may not be editable based on the React version.
57+
// If they're not editable, make sure they degrade gracefully
58+
const isEditableName = semver.gte(config.use.react_version, '17.0.0');
59+
const isEditableValue = semver.gte(config.use.react_version, '16.8.0');
60+
5461
// Then read the inspected values.
55-
const [propName, propValue, sourceText] = await page.evaluate(() => {
56-
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
57-
const container = document.getElementById('devtools');
62+
const [propName, propValue, sourceText] = await page.evaluate(
63+
isEditable => {
64+
const {
65+
createTestNameSelector,
66+
findAllNodes,
67+
} = window.REACT_DOM_DEVTOOLS;
68+
const container = document.getElementById('devtools');
5869

59-
const editableName = findAllNodes(container, [
60-
createTestNameSelector('InspectedElementPropsTree'),
61-
createTestNameSelector('EditableName'),
62-
])[0];
63-
const editableValue = findAllNodes(container, [
64-
createTestNameSelector('InspectedElementPropsTree'),
65-
createTestNameSelector('EditableValue'),
66-
])[0];
67-
const source = findAllNodes(container, [
68-
createTestNameSelector('InspectedElementView-Source'),
69-
])[0];
70+
// Get name of first prop
71+
const selectorName = isEditable.name
72+
? 'EditableName'
73+
: 'NonEditableName';
74+
const nameElement = findAllNodes(container, [
75+
createTestNameSelector('InspectedElementPropsTree'),
76+
createTestNameSelector(selectorName),
77+
])[0];
78+
const name = isEditable.name
79+
? nameElement.value
80+
: nameElement.innerText;
81+
82+
// Get value of first prop
83+
const selectorValue = isEditable.value
84+
? 'EditableValue'
85+
: 'NonEditableValue';
86+
const valueElement = findAllNodes(container, [
87+
createTestNameSelector('InspectedElementPropsTree'),
88+
createTestNameSelector(selectorValue),
89+
])[0];
90+
const source = findAllNodes(container, [
91+
createTestNameSelector('InspectedElementView-Source'),
92+
])[0];
93+
const value = isEditable.value
94+
? valueElement.value
95+
: valueElement.innerText;
7096

71-
return [editableName.value, editableValue.value, source.innerText];
72-
});
97+
return [name, value, source.innerText];
98+
},
99+
{name: isEditableName, value: isEditableValue}
100+
);
73101

74102
expect(propName).toBe('label');
75103
expect(propValue).toBe('"one"');
76-
expect(sourceText).toContain('ListApp.js');
104+
expect(sourceText).toMatch(/ListApp[a-zA-Z]*\.js/);
77105
});
78106

79107
test('should allow props to be edited', async () => {
108+
runOnlyForReactRange('>=16.8');
109+
80110
// Select the first list item in DevTools.
81111
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
82112

@@ -109,6 +139,8 @@ test.describe('Components', () => {
109139
});
110140

111141
test('should load and parse hook names for the inspected element', async () => {
142+
runOnlyForReactRange('>=16.8');
143+
112144
// Select the List component DevTools.
113145
await devToolsUtils.selectElement(page, 'List', 'App');
114146

@@ -162,19 +194,23 @@ test.describe('Components', () => {
162194
});
163195
}
164196

165-
await page.evaluate(() => {
166-
const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
167-
const container = document.getElementById('devtools');
197+
async function focusComponentSearch() {
198+
await page.evaluate(() => {
199+
const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
200+
const container = document.getElementById('devtools');
168201

169-
focusWithin(container, [
170-
createTestNameSelector('ComponentSearchInput-Input'),
171-
]);
172-
});
202+
focusWithin(container, [
203+
createTestNameSelector('ComponentSearchInput-Input'),
204+
]);
205+
});
206+
}
173207

208+
await focusComponentSearch();
174209
page.keyboard.insertText('List');
175210
let count = await getComponentSearchResultsCount();
176211
expect(count).toBe('1 | 4');
177212

213+
await focusComponentSearch();
178214
page.keyboard.insertText('Item');
179215
count = await getComponentSearchResultsCount();
180216
expect(count).toBe('1 | 3');

packages/react-devtools-inline/__tests__/__e2e__/profiler.test.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
'use strict';
44

5+
const {runOnlyForReactRange} = require('./utils');
56
const listAppUtils = require('./list-app-utils');
67
const devToolsUtils = require('./devtools-utils');
78
const {test, expect} = require('@playwright/test');
@@ -12,8 +13,7 @@ test.describe('Profiler', () => {
1213

1314
test.beforeEach(async ({browser}) => {
1415
page = await browser.newPage();
15-
16-
await page.goto('http://localhost:8080/e2e.html', {
16+
await page.goto(config.use.url, {
1717
waitUntil: 'domcontentloaded',
1818
});
1919

@@ -23,6 +23,8 @@ test.describe('Profiler', () => {
2323
});
2424

2525
test('should record renders and commits when active', async () => {
26+
// Profiling is only available in 16.5 and over
27+
runOnlyForReactRange('>=16.5');
2628
async function getSnapshotSelectorText() {
2729
return await page.evaluate(() => {
2830
const {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
/** @flow */
4+
5+
const semver = require('semver');
6+
const config = require('../../playwright.config');
7+
const {test} = require('@playwright/test');
8+
9+
function runOnlyForReactRange(range) {
10+
test.skip(
11+
!semver.satisfies(config.use.react_version, range),
12+
`This test requires a React version of ${range} to run. ` +
13+
`The React version you're using is ${config.use.react_version}`
14+
);
15+
}
16+
17+
module.exports = {runOnlyForReactRange};

packages/react-devtools-inline/playwright.config.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
const semver = require('semver');
2+
const fs = require('fs');
3+
const ReactVersionSrc = fs.readFileSync(require.resolve('shared/ReactVersion'));
4+
const reactVersion = /export default '([^']+)';/.exec(ReactVersionSrc)[1];
5+
16
const config = {
27
use: {
38
headless: true,
@@ -7,7 +12,15 @@ const config = {
712
// and DevTools operations to be sent across the bridge.
813
slowMo: 100,
914
},
15+
url: process.env.REACT_VERSION
16+
? 'http://localhost:8080/e2e-regression.html'
17+
: 'http://localhost:8080/e2e.html',
18+
react_version: process.env.REACT_VERSION
19+
? semver.coerce(process.env.REACT_VERSION).version
20+
: reactVersion,
1021
},
22+
// Some of our e2e tests can be flaky. Retry tests to make sure the error isn't transient
23+
retries: 2,
1124
};
1225

1326
module.exports = config;

packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export default function KeyValue({
231231
);
232232
} else {
233233
renderedName = (
234-
<span className={styles.Name}>
234+
<span className={styles.Name} data-testname="NonEditableName">
235235
{name}
236236
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
237237
</span>
@@ -286,7 +286,9 @@ export default function KeyValue({
286286
{displayValue}
287287
</a>
288288
) : (
289-
<span className={styles.Value}>{displayValue}</span>
289+
<span className={styles.Value} data-testname="NonEditableValue">
290+
{displayValue}
291+
</span>
290292
)}
291293
</div>
292294
);

0 commit comments

Comments
 (0)