@samitouri / QOS-React-2 / commits / 1328ff70cd

[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)

Luna Ruan committed May 25, 2022 at 17:53 UTC 1328ff70cd69f02e48ec5505a1f93501b2bcaef4
5 files changed +99 -29
packages/react-devtools-inline/__tests__/__e2e__/components.test.js
+61 -25
@@ -2,10 +2,12 @@
2
3 'use strict';
4
5 +const {runOnlyForReactRange} = require('./utils');
6 const listAppUtils = require('./list-app-utils');
7 const devToolsUtils = require('./devtools-utils');
8 const {test, expect} = require('@playwright/test');
9 const config = require('../../playwright.config');
10 +const semver = require('semver');
11 test.use(config);
12 test.describe('Components', () => {
13 let page;
@@ -13,7 +15,7 @@ test.describe('Components', () => {
15 test.beforeEach(async ({browser}) => {
16 page = await browser.newPage();
17
16 - await page.goto('http://localhost:8080/e2e.html', {
18 + await page.goto(config.use.url, {
19 waitUntil: 'domcontentloaded',
20 });
21
@@ -51,32 +53,60 @@ test.describe('Components', () => {
53 // Select the first list item in DevTools.
54 await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
55
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 +
61 // 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');
69
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;
96
71 - return [editableName.value, editableValue.value, source.innerText];
72 - });
97 + return [name, value, source.innerText];
98 + },
99 + {name: isEditableName, value: isEditableValue}
100 + );
101
102 expect(propName).toBe('label');
103 expect(propValue).toBe('"one"');
76 - expect(sourceText).toContain('ListApp.js');
104 + expect(sourceText).toMatch(/ListApp[a-zA-Z]*\.js/);
105 });
106
107 test('should allow props to be edited', async () => {
108 + runOnlyForReactRange('>=16.8');
109 +
110 // Select the first list item in DevTools.
111 await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
112
@@ -109,6 +139,8 @@ test.describe('Components', () => {
139 });
140
141 test('should load and parse hook names for the inspected element', async () => {
142 + runOnlyForReactRange('>=16.8');
143 +
144 // Select the List component DevTools.
145 await devToolsUtils.selectElement(page, 'List', 'App');
146
@@ -162,19 +194,23 @@ test.describe('Components', () => {
194 });
195 }
196
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');
201
169 - focusWithin(container, [
170 - createTestNameSelector('ComponentSearchInput-Input'),
171 - ]);
172 - });
202 + focusWithin(container, [
203 + createTestNameSelector('ComponentSearchInput-Input'),
204 + ]);
205 + });
206 + }
207
208 + await focusComponentSearch();
209 page.keyboard.insertText('List');
210 let count = await getComponentSearchResultsCount();
211 expect(count).toBe('1 | 4');
212
213 + await focusComponentSearch();
214 page.keyboard.insertText('Item');
215 count = await getComponentSearchResultsCount();
216 expect(count).toBe('1 | 3');
packages/react-devtools-inline/__tests__/__e2e__/profiler.test.js
+4 -2
@@ -2,6 +2,7 @@
2
3 'use strict';
4
5 +const {runOnlyForReactRange} = require('./utils');
6 const listAppUtils = require('./list-app-utils');
7 const devToolsUtils = require('./devtools-utils');
8 const {test, expect} = require('@playwright/test');
@@ -12,8 +13,7 @@ test.describe('Profiler', () => {
13
14 test.beforeEach(async ({browser}) => {
15 page = await browser.newPage();
15 -
16 - await page.goto('http://localhost:8080/e2e.html', {
16 + await page.goto(config.use.url, {
17 waitUntil: 'domcontentloaded',
18 });
19
@@ -23,6 +23,8 @@ test.describe('Profiler', () => {
23 });
24
25 test('should record renders and commits when active', async () => {
26 + // Profiling is only available in 16.5 and over
27 + runOnlyForReactRange('>=16.5');
28 async function getSnapshotSelectorText() {
29 return await page.evaluate(() => {
30 const {
packages/react-devtools-inline/__tests__/__e2e__/utils.js new
+17
@@ -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
@@ -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 +
6 const config = {
7 use: {
8 headless: true,
@@ -7,7 +12,15 @@ const config = {
12 // and DevTools operations to be sent across the bridge.
13 slowMo: 100,
14 },
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,
21 },
22 + // Some of our e2e tests can be flaky. Retry tests to make sure the error isn't transient
23 + retries: 2,
24 };
25
26 module.exports = config;
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
+4 -2
@@ -231,7 +231,7 @@ export default function KeyValue({
231 );
232 } else {
233 renderedName = (
234 - <span className={styles.Name}>
234 + <span className={styles.Name} data-testname="NonEditableName">
235 {name}
236 {!!hookName && <span className={styles.HookName}>({hookName})</span>}
237 </span>
@@ -286,7 +286,9 @@ export default function KeyValue({
286 {displayValue}
287 </a>
288 ) : (
289 - <span className={styles.Value}>{displayValue}</span>
289 + <span className={styles.Value} data-testname="NonEditableValue">
290 + {displayValue}
291 + </span>
292 )}
293 </div>
294 );