Skip to content

Commit 65efa9f

Browse files
authored
[code-infra] Optimize regression tests (#43889)
1 parent 590f4ff commit 65efa9f

File tree

3 files changed

+99
-72
lines changed

3 files changed

+99
-72
lines changed

test/regressions/TestViewer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import JoyBox from '@mui/joy/Box';
77
import { CssVarsProvider } from '@mui/joy/styles';
88

99
function TestViewer(props) {
10-
const { children } = props;
10+
const { children, path } = props;
1111

1212
// We're simulating `act(() => ReactDOM.render(children))`
1313
// In the end children passive effects should've been flushed.
@@ -82,6 +82,7 @@ function TestViewer(props) {
8282
<JoyBox
8383
aria-busy={!ready}
8484
data-testid="testcase"
85+
data-testpath={path}
8586
sx={{ bgcolor: 'background.body', ...viewerBoxSx }}
8687
>
8788
{children}
@@ -91,6 +92,7 @@ function TestViewer(props) {
9192
<Box
9293
aria-busy={!ready}
9394
data-testid="testcase"
95+
data-testpath={path}
9496
sx={{ bgcolor: 'background.default', ...viewerBoxSx }}
9597
>
9698
{children}
@@ -103,6 +105,7 @@ function TestViewer(props) {
103105

104106
TestViewer.propTypes = {
105107
children: PropTypes.node.isRequired,
108+
path: PropTypes.string.isRequired,
106109
};
107110

108111
export default TestViewer;

test/regressions/index.js

Lines changed: 78 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import PropTypes from 'prop-types';
33
import * as ReactDOMClient from 'react-dom/client';
4-
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
4+
import { BrowserRouter as Router, Routes, Route, Link, useNavigate } from 'react-router-dom';
55
import webfontloader from 'webfontloader';
66
import { Globals } from '@react-spring/web';
77
import TestViewer from './TestViewer';
@@ -11,6 +11,12 @@ Globals.assign({
1111
skipAnimation: true,
1212
});
1313

14+
window.muiFixture = {
15+
navigate: () => {
16+
throw new Error(`muiFixture.navigate is not ready`);
17+
},
18+
};
19+
1420
// Get all the fixtures specifically written for preventing visual regressions.
1521
const importRegressionFixtures = require.context('./fixtures', true, /\.(js|ts|tsx)$/, 'lazy');
1622
const regressionFixtures = [];
@@ -295,13 +301,13 @@ if (unusedBlacklistPatterns.size > 0) {
295301

296302
const viewerRoot = document.getElementById('test-viewer');
297303

298-
function FixtureRenderer({ component: FixtureComponent }) {
304+
function FixtureRenderer({ component: FixtureComponent, path }) {
299305
const viewerReactRoot = React.useRef(null);
300306

301307
React.useLayoutEffect(() => {
302308
const renderTimeout = setTimeout(() => {
303309
const children = (
304-
<TestViewer>
310+
<TestViewer path={path}>
305311
<FixtureComponent />
306312
</TestViewer>
307313
);
@@ -320,38 +326,43 @@ function FixtureRenderer({ component: FixtureComponent }) {
320326
viewerReactRoot.current = null;
321327
});
322328
};
323-
}, [FixtureComponent]);
329+
}, [FixtureComponent, path]);
324330

325331
return null;
326332
}
327333

328334
FixtureRenderer.propTypes = {
329335
component: PropTypes.elementType,
336+
path: PropTypes.string.isRequired,
330337
};
331338

332-
function App(props) {
333-
const { fixtures } = props;
334-
335-
function computeIsDev() {
336-
if (window.location.hash === '#dev') {
337-
return true;
338-
}
339-
if (window.location.hash === '#no-dev') {
340-
return false;
341-
}
342-
return process.env.NODE_ENV === 'development';
343-
}
344-
const [isDev, setDev] = React.useState(computeIsDev);
345-
React.useEffect(() => {
346-
function handleHashChange() {
347-
setDev(computeIsDev());
348-
}
349-
window.addEventListener('hashchange', handleHashChange);
350-
339+
function useHash() {
340+
const subscribe = React.useCallback((callback) => {
341+
window.addEventListener('hashchange', callback);
351342
return () => {
352-
window.removeEventListener('hashchange', handleHashChange);
343+
window.removeEventListener('hashchange', callback);
353344
};
354345
}, []);
346+
const getSnapshot = React.useCallback(() => window.location.hash, []);
347+
const getServerSnapshot = React.useCallback(() => '', []);
348+
return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
349+
}
350+
351+
function computeIsDev(hash) {
352+
if (hash === '#dev') {
353+
return true;
354+
}
355+
if (hash === '#no-dev') {
356+
return false;
357+
}
358+
return process.env.NODE_ENV === 'development';
359+
}
360+
361+
function App(props) {
362+
const { fixtures } = props;
363+
364+
const hash = useHash();
365+
const isDev = computeIsDev(hash);
355366

356367
// Using <link rel="stylesheet" /> does not apply the google Roboto font in chromium headless/headfull.
357368
const [fontState, setFontState] = React.useState('pending');
@@ -380,8 +391,13 @@ function App(props) {
380391
return `/${fixture.suite}/${fixture.name}`;
381392
}
382393

394+
const navigate = useNavigate();
395+
React.useEffect(() => {
396+
window.muiFixture.navigate = navigate;
397+
}, [navigate]);
398+
383399
return (
384-
<Router>
400+
<React.Fragment>
385401
<Routes>
386402
{fixtures.map((fixture) => {
387403
const path = computePath(fixture);
@@ -396,36 +412,43 @@ function App(props) {
396412
key={path}
397413
exact
398414
path={path}
399-
element={fixturePrepared ? <FixtureRenderer component={FixtureComponent} /> : null}
415+
element={
416+
fixturePrepared ? (
417+
<FixtureRenderer component={FixtureComponent} path={path} />
418+
) : null
419+
}
400420
/>
401421
);
402422
})}
403423
</Routes>
404424

405-
<div hidden={!isDev}>
406-
<div data-webfontloader={fontState}>webfontloader: {fontState}</div>
407-
<p>
408-
Devtools can be enabled by appending <code>#dev</code> in the addressbar or disabled by
409-
appending <code>#no-dev</code>.
410-
</p>
411-
<a href="#no-dev">Hide devtools</a>
412-
<details>
413-
<summary id="my-test-summary">nav for all tests</summary>
414-
<nav id="tests">
415-
<ol>
416-
{fixtures.map((fixture) => {
417-
const path = computePath(fixture);
418-
return (
419-
<li key={path}>
420-
<Link to={path}>{path}</Link>
421-
</li>
422-
);
423-
})}
424-
</ol>
425-
</nav>
426-
</details>
427-
</div>
428-
</Router>
425+
{isDev ? (
426+
<div>
427+
<div data-webfontloader={fontState}>webfontloader: {fontState}</div>
428+
<p>
429+
Devtools can be enabled by appending <code>#dev</code> in the addressbar or disabled by
430+
appending <code>#no-dev</code>.
431+
</p>
432+
<a href="#no-dev">Hide devtools</a>
433+
<details>
434+
<summary id="my-test-summary">nav for all tests</summary>
435+
436+
<nav id="tests">
437+
<ol>
438+
{fixtures.map((fixture) => {
439+
const path = computePath(fixture);
440+
return (
441+
<li key={path}>
442+
<Link to={path}>{path}</Link>
443+
</li>
444+
);
445+
})}
446+
</ol>
447+
</nav>
448+
</details>
449+
</div>
450+
) : null}
451+
</React.Fragment>
429452
);
430453
}
431454

@@ -434,6 +457,10 @@ App.propTypes = {
434457
};
435458

436459
const container = document.getElementById('react-root');
437-
const children = <App fixtures={regressionFixtures.concat(demoFixtures)} />;
460+
const children = (
461+
<Router>
462+
<App fixtures={regressionFixtures.concat(demoFixtures)} />{' '}
463+
</Router>
464+
);
438465
const reactRoot = ReactDOMClient.createRoot(container);
439466
reactRoot.render(children);

test/regressions/index.test.js

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async function main() {
3131

3232
// Wait for all requests to finish.
3333
// This should load shared resources such as fonts.
34-
await page.goto(`${baseUrl}#no-dev`, { waitUntil: 'networkidle0' });
34+
await page.goto(`${baseUrl}#dev`, { waitUntil: 'networkidle0' });
3535
// If we still get flaky fonts after awaiting this try `document.fonts.ready`
3636
await page.waitForSelector('[data-webfontloader="active"]', { state: 'attached' });
3737

@@ -50,18 +50,21 @@ async function main() {
5050
});
5151
routes = routes.map((route) => route.replace(baseUrl, ''));
5252

53-
async function renderFixture(index) {
53+
/**
54+
* @param {string} route
55+
*/
56+
async function renderFixture(route) {
5457
// Use client-side routing which is much faster than full page navigation via page.goto().
55-
// Could become an issue with test isolation.
56-
// If tests are flaky due to global pollution switch to page.goto(route);
57-
// puppeteers built-in click() times out
58-
await page.$eval(`#tests li:nth-of-type(${index + 1}) a`, (link) => {
59-
link.click();
60-
});
58+
await page.evaluate((_route) => {
59+
window.muiFixture.navigate(`${_route}#no-dev`);
60+
}, route);
61+
6162
// Move cursor offscreen to not trigger unwanted hover effects.
62-
page.mouse.move(0, 0);
63+
await page.mouse.move(0, 0);
6364

64-
const testcase = await page.waitForSelector('[data-testid="testcase"]:not([aria-busy="true"])');
65+
const testcase = await page.waitForSelector(
66+
`[data-testid="testcase"][data-testpath="${route}"]:not([aria-busy="true"])`,
67+
);
6568

6669
return testcase;
6770
}
@@ -94,35 +97,29 @@ async function main() {
9497
await browser.close();
9598
});
9699

97-
routes.forEach((route, index) => {
100+
routes.forEach((route) => {
98101
it(`creates screenshots of ${route}`, async function test() {
99102
// With the playwright inspector we might want to call `page.pause` which would lead to a timeout.
100103
if (process.env.PWDEBUG) {
101104
this.timeout(0);
102105
}
103106

104-
const testcase = await renderFixture(index);
107+
const testcase = await renderFixture(route);
105108
await takeScreenshot({ testcase, route });
106109
});
107110
});
108111

109112
describe('Rating', () => {
110113
it('should handle focus-visible correctly', async () => {
111-
const index = routes.findIndex(
112-
(route) => route === '/regression-Rating/FocusVisibleRating',
113-
);
114-
const testcase = await renderFixture(index);
114+
const testcase = await renderFixture('/regression-Rating/FocusVisibleRating');
115115
await page.keyboard.press('Tab');
116116
await takeScreenshot({ testcase, route: '/regression-Rating/FocusVisibleRating2' });
117117
await page.keyboard.press('ArrowLeft');
118118
await takeScreenshot({ testcase, route: '/regression-Rating/FocusVisibleRating3' });
119119
});
120120

121121
it('should handle focus-visible with precise ratings correctly', async () => {
122-
const index = routes.findIndex(
123-
(route) => route === '/regression-Rating/PreciseFocusVisibleRating',
124-
);
125-
const testcase = await renderFixture(index);
122+
const testcase = await renderFixture('/regression-Rating/PreciseFocusVisibleRating');
126123
await page.keyboard.press('Tab');
127124
await takeScreenshot({ testcase, route: '/regression-Rating/PreciseFocusVisibleRating2' });
128125
await page.keyboard.press('ArrowRight');

0 commit comments

Comments
 (0)