Skip to content

Commit

Permalink
build: Cypress React 18 support and React version switcher (#6933)
Browse files Browse the repository at this point in the history
* refactor: convert Cypress mount and realMount commands to TypeScript

* build: Conditionally use @cypress/react or @cypress/react18 depending on REACT_VERSION environment variable

* build: add Cypress React version switcher

* docs: update cypress-testing.md

* build: move react version console.log
  • Loading branch information
tkajtoch committed Jul 24, 2023
1 parent ad4c63a commit 0e143aa
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 101 deletions.
12 changes: 4 additions & 8 deletions cypress/support/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReactNode } from 'react';
import { mount } from 'cypress/react';
import { ContextObject, Result, RunOptions } from 'axe-core';
import { realPress } from 'cypress-real-events/commands/realPress';
import type { EuiProviderProps } from '../../src/components/provider';
import type { mountCommand } from './setup/mount';
import type { realMountCommand } from './setup/realMount';

type KeyOrShortcut = Parameters<typeof realPress>[0];
type RealPressOptions = Parameters<typeof realPress>[1];
Expand Down Expand Up @@ -32,16 +31,13 @@ declare global {
/**
* Mounts components with a basic `EuiProvider` wrapper
*/
mount: <T = {}>(
children: ReactNode,
options?: { providerProps?: Partial<EuiProviderProps<T>> }
) => ReturnType<typeof mount>;
mount: mountCommand;

/**
* This ensures the correct testing window has focus when using Cypress Real Events.
* @see https://github.com/dmtrKovalenko/cypress-real-events/issues/196
*/
realMount: typeof mount;
realMount: realMountCommand;

/**
* Repeat the Real Events `realPress()` method 2 or more times
Expand Down
16 changes: 0 additions & 16 deletions cypress/support/setup/mount.js
Original file line number Diff line number Diff line change
@@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { mount as cypressMount } from 'cypress/react';
import { EuiProvider } from '../../../src';

Cypress.Commands.add('mount', (children, options = {}) => {
const { providerProps } = options;
return cypressMount(<EuiProvider {...providerProps}>{children}</EuiProvider>);
});
30 changes: 30 additions & 0 deletions cypress/support/setup/mount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { ReactNode } from 'react';
import { EuiProvider } from '../../../src';
import type { mount } from '@cypress/react18';

// Pick cypress mount function based on which React version is currently being
// tested. It has to be directly compared against process.env.REACT_VERSION
// for tree-shaking to work and not throw an error because of a missing import.
let cypressMount: typeof mount;
if (process.env.REACT_VERSION === '18') {
cypressMount = require('@cypress/react18').mount;
} else {
cypressMount = require('@cypress/react').mount;
}

const mountCommand = (children: ReactNode): ReturnType<typeof mount> => {
return cypressMount(<EuiProvider>{children}</EuiProvider>);
};

// Export only the type to not confuse code-completion tools
export type mountCommand = typeof mountCommand;

Cypress.Commands.add('mount', mountCommand);
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@
* Side Public License, v 1.
*/

import { React, Fragment } from 'react';
import React, { ReactNode } from 'react';
import './mount';

Cypress.Commands.add('realMount', (children) => {
const realMountCommand = (children: ReactNode) => {
cy.mount(
<Fragment>
<>
<div
data-test-subj="cypress-real-event-target"
style={{ height: '1px', width: '1px' }}
/>
{children}
</Fragment>
</>
).then(() => {
cy.get('[data-test-subj="cypress-real-event-target"]').realClick({
position: 'topLeft',
});
});
});
};

// Export only the type to not confuse code-completion tools
export type realMountCommand = typeof realMountCommand;

Cypress.Commands.add('realMount', realMountCommand);
17 changes: 14 additions & 3 deletions cypress/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ const { ProvidePlugin, DefinePlugin } = require('webpack');

const THEME_IMPORT = `'../../dist/eui_theme_${process.env.THEME}.css'`;

const alias = {};
const reactVersion = process.env.REACT_VERSION || '18';

// Setup module aliasing when we're testing an older React version
if (['16', '17'].includes(reactVersion)) {
alias.react = `react-${reactVersion}`;
alias['react-dom'] = `react-dom-${reactVersion}`;
}

module.exports = {
mode: 'development',

Expand All @@ -26,6 +35,7 @@ module.exports = {
os: false,
process: require.resolve('process/browser'),
},
alias,
},

module: {
Expand All @@ -46,9 +56,9 @@ module.exports = {
loader: 'style-loader',
options: {
insert: 'meta[name="css-styles"]',
}
},
},
'css-loader'
'css-loader',
],
exclude: /node_modules/,
},
Expand All @@ -62,7 +72,8 @@ module.exports = {
}),

new DefinePlugin({
THEME_IMPORT, // allow cypress/suport/index.js to require the correct css file
THEME_IMPORT, // allow cypress/support/component.tsx to require the correct css file
'process.env.REACT_VERSION': JSON.stringify(reactVersion),
}),
],
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
"@babel/preset-typescript": "^7.21.5",
"@babel/template": "^7.21.9",
"@cypress/code-coverage": "^3.10.0",
"@cypress/react": "^5.10.3",
"@cypress/react": "^7.0.3",
"@cypress/react18": "^2.0.0",
"@cypress/webpack-dev-server": "^1.7.0",
"@elastic/charts": "^53.1.1",
"@elastic/datemath": "^5.0.3",
Expand Down Expand Up @@ -226,8 +227,12 @@
"puppeteer": "^5.5.0",
"raw-loader": "^4.0.1",
"react": "^18.2.0",
"react-16": "npm:react@^16.14.0",
"react-17": "npm:react@^17.0.2",
"react-docgen-typescript": "^2.2.2",
"react-dom": "^18.2.0",
"react-dom-16": "npm:react-dom@^16.14.0",
"react-dom-17": "npm:react-dom@^17.0.2",
"react-helmet": "^6.1.0",
"react-redux": "^7.2.1",
"react-refresh": "^0.11.0",
Expand Down
8 changes: 8 additions & 0 deletions scripts/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ const argv = yargs(hideBin(process.argv))
dev: { type: 'boolean' },
theme: { type: 'string', default: 'light', choices: ['light', 'dark'] },
a11y: { type: 'boolean' },
'react-version': {
type: 'number',
default: 18,
choices: [16, 17, 18],
},
}).argv;

const isDev = argv.hasOwnProperty('dev');
const isA11y = argv.hasOwnProperty('a11y');
const skipScss = argv.hasOwnProperty('skip-css');
const theme = argv.theme;
const reactVersion = argv['react-version'];

const info = chalk.white;
const log = chalk.grey;
Expand Down Expand Up @@ -54,11 +60,13 @@ const cypressCommandParts = [
`THEME=${theme}`, // pass the theme
'BABEL_MODULES=false', // let webpack receive ES Module code
'NODE_ENV=cypress_test', // enable code coverage checks
`REACT_VERSION=${reactVersion}`, // set react version to test
`cypress ${testParams}`,
...argv._, // pass any extra options given to this script
];
const cypressCommand = cypressCommandParts.join(' ');

console.log(info(`Running tests on React v${reactVersion}`));
console.log(info(`${isDev ? 'Opening' : 'Running'} cypress`));
console.log(log(cypressCommand));
execSync(cypressCommand, {
Expand Down
5 changes: 5 additions & 0 deletions wiki/contributing-to-eui/testing/cypress-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ By default tests are run using the light theme. Dark mode can be enabled by pass

To ensure tests use up-to-date styles, the test runner compiles our SCSS to CSS before executing Cypress. This adds some processing time before the tests can run, and often the existing locally-built styles are still accurate. The CSS compilation step can be skipped by passing the `--skip-css` flag to `yarn test-cypress`, `yarn test-cypress-dev` and `yarn test-cypress-a11y`.

### Testing specific version of React

By default, EUI Cypress tests are run using the latest supported version of React.
You can change that behavior and run e2e tests using a different React version by passing the `--react-version` option set to `16`, `17` or `18`.

### Cypress arguments

You can also pass [Cypress CLI arguments](https://docs.cypress.io/guides/guides/command-line). For example:
Expand Down
Loading

0 comments on commit 0e143aa

Please sign in to comment.