Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip][poc] Add Pigment CSS screenshot test #43280

Merged
merged 33 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
84ad37e
init
mnajdova Aug 13, 2024
6402f6c
add run command
mnajdova Aug 13, 2024
b003e48
change run command
mnajdova Aug 13, 2024
271be6c
revert run cmd change
mnajdova Aug 13, 2024
55c3227
add build step before
mnajdova Aug 13, 2024
9878e5e
use common js mocharc file
mnajdova Aug 13, 2024
d4f9624
update command
mnajdova Aug 13, 2024
3766fa1
define __dirname
mnajdova Aug 14, 2024
4387379
fix
mnajdova Aug 14, 2024
a4bce28
break into two runs
mnajdova Aug 14, 2024
73bdebb
add polyfils
mnajdova Aug 14, 2024
3edabb7
pnpm dedupe
mnajdova Aug 14, 2024
e414dd8
some fixes & logs
mnajdova Aug 14, 2024
2a2901e
prettier
mnajdova Aug 14, 2024
35c41d6
fix filter
mnajdova Aug 14, 2024
a3d6e3e
omit the index.test path from the links
mnajdova Aug 14, 2024
4de9c06
fix selector
mnajdova Aug 14, 2024
927db19
lint issues
mnajdova Aug 14, 2024
fc5d041
revert changes in regressions
mnajdova Aug 15, 2024
174a500
fixes
mnajdova Aug 15, 2024
50c5922
fix path
mnajdova Aug 15, 2024
0200896
remove console.logs
mnajdova Aug 15, 2024
ab06674
improve index
mnajdova Aug 15, 2024
a117799
remove unused props
mnajdova Aug 15, 2024
86c3a41
Merge branch 'master' into pigment-css/regression-tests
mnajdova Sep 4, 2024
9ccaed6
pnpm i & dedupe
mnajdova Sep 4, 2024
370f21f
add demos
mnajdova Sep 4, 2024
5278807
filter broken page
mnajdova Sep 4, 2024
e6e90e3
add select screenshots
mnajdova Sep 5, 2024
75b91bc
Add instructions on how to run the suite
mnajdova Sep 5, 2024
462f2fc
pnpm dedupe
mnajdova Sep 5, 2024
d45061e
Merge branch 'master' into pigment-css/regression-tests
mnajdova Sep 6, 2024
8702cf0
pnpm-lock & dependency version update
mnajdova Sep 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ jobs:
- run:
name: Run visual regression tests
command: xvfb-run pnpm test:regressions
- run:
name: Build packages for fixtures
command: xvfb-run pnpm release:build
- run:
name: Run visual regression tests using Pigment CSS
command: xvfb-run pnpm test:regressions-pigment-css
- run:
name: Upload screenshots to Argos CI
command: pnpm test:argos
Expand Down
121 changes: 121 additions & 0 deletions apps/pigment-css-next-app/src/app/material-ui/react-select/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client';
import * as React from 'react';
import BasicSelect from '../../../../../../docs/data/material/components/selects/BasicSelect';
import ControlledOpenSelect from '../../../../../../docs/data/material/components/selects/ControlledOpenSelect';
import CustomizedSelects from '../../../../../../docs/data/material/components/selects/CustomizedSelects';
import DialogSelect from '../../../../../../docs/data/material/components/selects/DialogSelect';
import GroupedSelect from '../../../../../../docs/data/material/components/selects/GroupedSelect';
import MultipleSelect from '../../../../../../docs/data/material/components/selects/MultipleSelect';
import MultipleSelectCheckmarks from '../../../../../../docs/data/material/components/selects/MultipleSelectCheckmarks';
import MultipleSelectChip from '../../../../../../docs/data/material/components/selects/MultipleSelectChip';
import MultipleSelectNative from '../../../../../../docs/data/material/components/selects/MultipleSelectNative';
import MultipleSelectPlaceholder from '../../../../../../docs/data/material/components/selects/MultipleSelectPlaceholder';
import NativeSelectDemo from '../../../../../../docs/data/material/components/selects/NativeSelectDemo';
import SelectAutoWidth from '../../../../../../docs/data/material/components/selects/SelectAutoWidth';
import SelectLabels from '../../../../../../docs/data/material/components/selects/SelectLabels';
import SelectOtherProps from '../../../../../../docs/data/material/components/selects/SelectOtherProps';
import SelectSmall from '../../../../../../docs/data/material/components/selects/SelectSmall';
import SelectVariants from '../../../../../../docs/data/material/components/selects/SelectVariants';

export default function Selects() {
return (
<React.Fragment>
<section>
<h2> Basic Select</h2>
<div className="demo-container">
<BasicSelect />
</div>
</section>
<section>
<h2> Controlled Open Select</h2>
<div className="demo-container">
<ControlledOpenSelect />
</div>
</section>
<section>
<h2> Customized Selects</h2>
<div className="demo-container">
<CustomizedSelects />
</div>
</section>
<section>
<h2> Dialog Select</h2>
<div className="demo-container">
<DialogSelect />
</div>
</section>
<section>
<h2> Grouped Select</h2>
<div className="demo-container">
<GroupedSelect />
</div>
</section>
<section>
<h2> Multiple Select</h2>
<div className="demo-container">
<MultipleSelect />
</div>
</section>
<section>
<h2> Multiple Select Checkmarks</h2>
<div className="demo-container">
<MultipleSelectCheckmarks />
</div>
</section>
<section>
<h2> Multiple Select Chip</h2>
<div className="demo-container">
<MultipleSelectChip />
</div>
</section>
<section>
<h2> Multiple Select Native</h2>
<div className="demo-container">
<MultipleSelectNative />
</div>
</section>
<section>
<h2> Multiple Select Placeholder</h2>
<div className="demo-container">
<MultipleSelectPlaceholder />
</div>
</section>
<section>
<h2> Native Select Demo</h2>
<div className="demo-container">
<NativeSelectDemo />
</div>
</section>
<section>
<h2> Select Auto Width</h2>
<div className="demo-container">
<SelectAutoWidth />
</div>
</section>
<section>
<h2> Select Labels</h2>
<div className="demo-container">
<SelectLabels />
</div>
</section>
<section>
<h2> Select Other Props</h2>
<div className="demo-container">
<SelectOtherProps />
</div>
</section>
<section>
<h2> Select Small</h2>
<div className="demo-container">
<SelectSmall />
</div>
</section>
<section>
<h2> Select Variants</h2>
<div className="demo-container">
<SelectVariants />
</div>
</section>
</React.Fragment>
);
}
7 changes: 7 additions & 0 deletions apps/pigment-css-vite-app/.mocharc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
recursive: true,
slow: 500,
timeout: (process.env.CIRCLECI === 'true' ? 4 : 2) * 1000, // Circle CI has low-performance CPUs.
reporter: 'dot',
require: ['@mui/internal-test-utils/setupBabelPlaywright'],
};
4 changes: 3 additions & 1 deletion apps/pigment-css-vite-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@mui/material": "workspace:^",
"@mui/system": "workspace:^",
"clsx": "^2.1.1",
"playwright": "^1.46.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
Expand All @@ -32,7 +33,8 @@
"postcss": "^8.4.44",
"postcss-combine-media-query": "^1.0.1",
"vite": "5.4.2",
"vite-plugin-pages": "^0.32.3"
"vite-plugin-pages": "^0.32.3",
"vite-plugin-node-polyfills": "0.22.0"
},
"nx": {
"targets": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';

export default function BasicSelect() {
const [age, setAge] = React.useState(10);

const handleChange = (event) => {
setAge(event.target.value);
};

return (
<Box sx={{ minWidth: 120, minHeight: 250 }}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Age</InputLabel>
<Select
defaultOpen
labelId="demo-simple-select-label"
id="demo-simple-select"
value={age}
label="Age"
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</Box>
);
}
117 changes: 117 additions & 0 deletions apps/pigment-css-vite-app/src/pages/fixtures/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as path from 'path';
import * as fse from 'fs-extra';
import * as playwright from 'playwright';

async function main() {
const baseUrl = 'http://localhost:5001/fixtures';
const screenshotDir = path.resolve('screenshots/chrome');
const browser = await playwright.chromium.launch({
args: ['--font-render-hinting=none'],
// otherwise the loaded google Roboto font isn't applied
headless: false,
});
// reuse viewport from `vrtest`
// https://github.com/nathanmarks/vrtest/blob/1185b852a6c1813cedf5d81f6d6843d9a241c1ce/src/server/runner.js#L44
const page = await browser.newPage({
viewport: { width: 1000, height: 700 },
reducedMotion: 'reduce',
});

// Block images since they slow down tests (need download).
// They're also most likely decorative for documentation demos
await page.route(/./, async (route, request) => {
const type = await request.resourceType();
if (type === 'image') {
route.abort();
} else {
route.continue();
}
});

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

// Simulate portrait mode for date pickers.
// See `useIsLandscape`.
await page.evaluate(() => {
Object.defineProperty(window.screen.orientation, 'angle', {
get() {
return 0;
},
});
});

let routes = await page.$$eval('#tests a', (links) => {
return links.map((link) => link.href);
});
routes = routes.map((route) => route.replace(baseUrl, ''));

async function renderFixture(index) {
// Use client-side routing which is much faster than full page navigation via page.goto().
// Could become an issue with test isolation.
// If tests are flaky due to global pollution switch to page.goto(route);
// puppeteers built-in click() times out
await page.$eval(`#tests li:nth-of-type(${index + 1}) a`, (link) => {
link.click();
});
// Move cursor offscreen to not trigger unwanted hover effects.
page.mouse.move(0, 0);

const testcase = await page.waitForSelector('#root-demo');

return testcase;
}

async function takeScreenshot({ testcase, route }) {
const screenshotPath = path.resolve(screenshotDir, `.${route}.png`);
await fse.ensureDir(path.dirname(screenshotPath));

const explicitScreenshotTarget = await page.$('[data-testid="screenshot-target"]');
const screenshotTarget = explicitScreenshotTarget || testcase;

await screenshotTarget.screenshot({
path: screenshotPath,
type: 'png',
animations: 'disabled',
});
}

// prepare screenshots
await fse.emptyDir(screenshotDir);

describe('visual regressions', () => {
beforeEach(async () => {
await page.evaluate(() => {
localStorage.clear();
});
});

after(async () => {
await browser.close();
});

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

const testcase = await renderFixture(index);
await takeScreenshot({ testcase, route });
});
});
});

run();
}

main().catch((error) => {
// error during setup.
// Throwing lets mocha hang.
console.error(error);
process.exit(1);
});
80 changes: 80 additions & 0 deletions apps/pigment-css-vite-app/src/pages/fixtures/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as React from 'react';
import { useLocation, matchRoutes, Link } from 'react-router-dom';
import routes from '~react-pages';
import IndexLayout from '../../Layout';

export default function Layout() {
const location = useLocation();
const matchedRoute = React.useMemo(
() => matchRoutes(routes, location.pathname)?.[0],
[location.pathname],
);

const materialUIRoute = React.useMemo(
() => matchRoutes(routes, location.pathname.replace('fixtures', 'material-ui'))?.[0],
[location.pathname],
);

const demo = new URLSearchParams(location.search).get('demo');
const fixturesRoutes = (matchedRoute?.route.children ?? []).filter(
(item) => !!item.path && item.path !== 'index.test',
);

const demosRoutes = (materialUIRoute?.route.children ?? []).filter(
(item) => !!item.path && item.path.indexOf('react-pagination') < 0,
);

return (
<IndexLayout>
{demo && (
<div id="root-demo">
{fixturesRoutes.find((item) => item.path === demo)?.element}
{demosRoutes.find((item) => item.path === demo)?.element}
</div>
)}
<div>
<h1>Fixtures Material UI + Pigment CSS</h1>
<nav id="tests">
<ul
sx={{
margin: 0,
marginBlock: '1rem',
padding: 0,
paddingLeft: '1.5rem',
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
}}
>
{fixturesRoutes.map((item) => (
<li key={item.path}>
<Link
to={`/fixtures/?demo=${item.path}`}
sx={{
textDecoration: 'underline',
fontSize: '17px',
}}
>
{item.path}
</Link>
</li>
))}
{demosRoutes.map((item) => (
<li key={item.path}>
<Link
to={`/fixtures/?demo=${item.path}`}
sx={{
textDecoration: 'underline',
fontSize: '17px',
}}
>
{item.path}
</Link>
</li>
))}
</ul>
</nav>
</div>
</IndexLayout>
);
}
Loading