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

STCLI-255 add stripes test cypress command #362

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { babelOptions } = require('@folio/stripes-webpack');
const { getBaseCypressConfig } = require('./lib/test/cypress-service');

module.exports = {
babelOptions,
getBaseCypressConfig
};

97 changes: 97 additions & 0 deletions lib/commands/test/cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const importLazy = require('import-lazy')(require);
// const fs = require('fs');
// const child = require('child_process');

const { contextMiddleware } = importLazy('../../cli/context-middleware');
const { stripesConfigMiddleware } = importLazy('../../cli/stripes-config-middleware');
const { CypressService } = importLazy('../../test/cypress-service');
const StripesPlatform = importLazy('../../platform/stripes-platform');
const { serverOptions, okapiOptions, stripesConfigFile, stripesConfigStdin, stripesConfigOptions } = importLazy('../common-options');
const StripesCore = importLazy('../../cli/stripes-core');

function cypressCommand(argv) {
const context = argv.context;

// pass moduleName up to globals so that it can be accessed for standalone webpack config.
process.env.stripesCLIContextModuleName = context.moduleName;

// Default test command to test env
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = 'test';
}

if (!(context.isUiModule || context.isStripesModule)) {
console.log('Tests are only supported within an app context.');
return;
}

const platform = new StripesPlatform(argv.stripesConfig, context, argv);
const webpackOverrides = platform.getWebpackOverrides(context);

if (context.plugin && context.plugin.beforeBuild) {
webpackOverrides.push(context.plugin.beforeBuild(argv));
}

console.log('Starting Cypress tests...');
const stripes = new StripesCore(context, platform.aliases);
const webpackConfigOptions = {
coverage: argv.coverage,
omitPlatform: context.type === 'component',
bundle: argv.bundle,
webpackOverrides,
};
const webpackConfig = stripes.getStripesWebpackConfig(platform.getStripesConfig(), webpackConfigOptions, context);

// This fixes the warnings similar to:
// WARNING in ./node_modules/mocha/mocha-es2018.js 18541:26-55
// Critical dependency: the request of a dependency is an expression
// https://github.com/mochajs/mocha/issues/2448#issuecomment-355222358
webpackConfig.module.exprContextCritical = false;

// This fixes warning:
// WARNING in DefinePlugin
// Conflicting values for 'process.env.NODE_ENV'
// https://webpack.js.org/configuration/mode/#usage
webpackConfig.mode = 'none';

const cypressService = new CypressService(context.cwd);
if (argv.cypress?.install) {
cypressService.installAndRun(webpackConfig, Object.assign({}, argv.cypress, { watch: argv.watch, cache: argv.cache }), context);
} else {
cypressService.runCypressTests(webpackConfig, Object.assign({}, argv.cypress, { watch: argv.watch, cache: argv.cache }), context);
}
}

module.exports = {
command: 'cypress [configFile]',
describe: 'Run the current app module\'s Cypress tests',
builder: (yargs) => {
yargs
.middleware([
contextMiddleware(),
stripesConfigMiddleware(),
])
.positional('configFile', stripesConfigFile.configFile)
.option('coverage', {
describe: 'Enable Cypress coverage reports',
type: 'boolean',
alias: 'cypress.coverage', // this allows --coverage to be passed to Cypress
})
.option('bundle', {
describe: 'Create and use a production bundle retaining test hooks',
type: 'boolean'
})
.option('cypress', {
describe: 'Options passed to Cypress using dot-notation and camelCase: --cypress.browsers=Chrome --cypress.singleRun',
})
.option('cypress.install', { type: 'boolean', describe: 'install cypress manually to account for ignore-scripts' })
.option('cypress.open', { type: 'boolean', describe: 'run cypress in ui mode' })
.option('cypress.browsers', { type: 'array', hidden: true }) // defined but hidden so yargs will parse as an array
.option('cypress.reporters', { type: 'array', hidden: true })
.option('watch', { type: 'boolean', describe: 'Watch test files for changes and run tests automatically when changes are saved.' })
.option('cache', { type: 'boolean', describe: 'Enable caching of test bundle. Defaults to false.' })
.options(Object.assign({}, serverOptions, okapiOptions, stripesConfigStdin, stripesConfigOptions))
.example('$0 test cypress', 'Run tests with Cypress for the current app module');
},
handler: cypressCommand,
};
18 changes: 18 additions & 0 deletions lib/test/cypress-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference types="cypress" />
const webpack = require('@cypress/webpack-preprocessor')

Check failure on line 2 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 2 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
const options = {
// use the same Webpack options to bundle spec files as your app does "normally"
// which should instrument the spec files in this project
webpackOptions: require('../../webpack.config'),

Check failure on line 11 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected require()

Check warning on line 11 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected use of file extension "config" for "../../webpack.config"

Check failure on line 11 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected require()

Check warning on line 11 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected use of file extension "config" for "../../webpack.config"
watchOptions: {}
}

Check failure on line 13 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 13 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon
on('file:preprocessor', webpack(options))

Check failure on line 14 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 14 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

require('@cypress/code-coverage/task')(on, config)

Check failure on line 16 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected require()

Check failure on line 16 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 16 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Unexpected require()

Check failure on line 16 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon
return config

Check failure on line 17 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 17 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon
}

Check failure on line 18 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Newline required at end of file but not found

Check failure on line 18 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon

Check failure on line 18 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Newline required at end of file but not found

Check failure on line 18 in lib/test/cypress-plugin.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

Missing semicolon
228 changes: 228 additions & 0 deletions lib/test/cypress-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const fs = require('fs');
const path = require('path');
const os = require('os');

Check warning on line 3 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'os' is assigned a value but never used. Allowed unused vars must match /React/u

Check warning on line 3 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'os' is assigned a value but never used. Allowed unused vars must match /React/u
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const _pickBy = require('lodash/pickBy');

const { defineConfig, run, open } = require('cypress');
const webpack = require('@cypress/webpack-preprocessor');

Check warning on line 10 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'webpack' is assigned a value but never used. Allowed unused vars must match /React/u

Check warning on line 10 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'webpack' is assigned a value but never used. Allowed unused vars must match /React/u
const logger = require('../cli/logger')('cypress');
const getStripesWebpackConfigStandalone = require('./webpack-config-standalone');

function getTestIndex(cwd, dirs) {
let file = path.join(cwd, dirs[0], 'index.js');
let i = 0;

while (!fs.existsSync(file) && dirs[++i]) {
file = path.join(cwd, dirs[i], 'index.js');
}

return file;
}

function getBaseCypressConfig(fn = (cfg) => cfg, context) {

Check warning on line 25 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'context' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'context' is defined but never used. Allowed unused args must match /^_/u
const baseConfig = defineConfig({
browser: 'chrome',
viewportWidth: 800,
viewportHeight: 600,
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
webpackConfig : getStripesWebpackConfigStandalone()
},
specPattern: '**/*[.-]test.js',
},
});
return fn(baseConfig, defineConfig);
}

class CypressService {
constructor(cwd) {
this.cwd = cwd;
}

generateCypressConfig(webpackConfig, cypressOptions) {
// TODO: Standardize on test folder, `test/bigtest` vs 'test' vs 'tests'
const testIndex = getTestIndex(this.cwd, ['test/bigtest', 'test', 'tests']);

Check warning on line 49 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'testIndex' is assigned a value but never used. Allowed unused vars must match /React/u

Check warning on line 49 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'testIndex' is assigned a value but never used. Allowed unused vars must match /React/u

// cypress webpack, ignores 'entry' so to keep it from griping, just exclude it.
const {
entry, // eslint-disable-line no-unused-vars
...webpackConfigRest

Check warning on line 54 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'webpackConfigRest' is assigned a value but never used. Allowed unused vars must match /React/u

Check warning on line 54 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'webpackConfigRest' is assigned a value but never used. Allowed unused vars must match /React/u
} = webpackConfig;

const output = {

Check warning on line 57 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'output' is assigned a value but never used. Allowed unused vars must match /React/u

Check warning on line 57 in lib/test/cypress-service.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'output' is assigned a value but never used. Allowed unused vars must match /React/u
// The path defined here is the same as what cypress-webpack is using by default.
// https://github.com/ryanclark/cypress-webpack/blob/master/lib/webpack/defaults.js#L10

// We are redefining it here so we can work around a current limitation
// related to static files (translations) not loading correctly.
// Please see more comments under:
// https://github.com/ryanclark/cypress-webpack/issues/498
// path: path.join(os.tmpdir(), '_cypress_webpack_') + Math.floor(Math.random() * 1000000),
};

// set webpack's watch/cache features via cypress config.
// these features are unnecessary in the CI environment, so we turn them off by default.
// They can be enabled individually via command-line options '--watch' and '--cache'.
let webpackTestConfig = {};
webpackTestConfig.watch = !!cypressOptions?.watch;
webpackTestConfig.cache = !!cypressOptions?.cache;
// only apply 'false' options as overrides to what cypress-webpack wants to set.
webpackTestConfig = _pickBy(webpackTestConfig, (opt) => !opt);

// let cypressConfig = {
// frameworks: ['mocha', 'webpack'],
// reporters: ['mocha'],
// port: 9876,

// browsers: ['Chrome'],

// customLaunchers: {
// // Custom launcher for CI
// ChromeHeadlessDocker: {
// base: 'ChromeHeadless',
// flags: [
// '--no-sandbox',
// '--disable-web-security'
// ]
// },
// ChromeDocker: {
// base: 'Chrome',
// flags: [
// '--no-sandbox',
// '--disable-web-security'
// ]
// }
// },

// junitReporter: {
// outputDir: 'artifacts/runTest',
// useBrowserName: true,
// },

// files: [
// { pattern: testIndex, watched: false },
// // use output.path to work around the issue with loading
// // static files
// // https://github.com/ryanclark/cypress-webpack/issues/498
// {
// pattern: `${output.path}/**/*`,
// watched: false,
// included: false,
// served: true,
// },
// ],

// preprocessors: {
// [testIndex]: ['webpack']
// },

// webpack: {
// ...webpackConfigRest,
// ...webpackTestConfig,
// output,
// },
// webpackMiddleware: {
// stats: 'errors-only',
// },

// mochaReporter: {
// showDiff: true,
// },

// plugins: [
// 'cypress-chrome-launcher',
// 'cypress-firefox-launcher',
// 'cypress-mocha',
// 'cypress-webpack',
// 'cypress-mocha-reporter',
// ],

// coverageIstanbulReporter: {
// dir: 'artifacts/coverage',
// reports: ['text-summary', 'lcov'],
// thresholds: {
// // Thresholds under which cypress will return failure
// // Modules are expected to define their own values in cypress.conf.js
// global: {},
// each: {},
// }
// },
// };

// Apply user supplied --cypress options to configuration
// Added now so they will be available within app-supplied config function
// if (cypressOptions) {
// logger.log('Applying command-line Cypress options', cypressOptions);
// Object.assign(cypressConfig, cypressOptions);
// }

// if (cypressOptions && cypressOptions.coverage) {
// logger.log('Enabling coverage');
// cypressConfig.reporters.push('coverage-istanbul');
// cypressConfig.plugins.push('cypress-coverage-istanbul-reporter');
// }

// if (cypressConfig.reporters.includes('junit')) {
// logger.log('Enabling junit reporter');
// cypressConfig.plugins.push('cypress-junit-reporter');
// }

// Use cypress's parser to prep the base config
let cypressConfig = getBaseCypressConfig();

// Check for an app-supplied cypress config and apply it
const localConfig = path.join(this.cwd, 'cypress.config.js');
if (fs.existsSync(localConfig)) {
const appCypressConfig = require(localConfig); // eslint-disable-line
logger.log('Applying local cypress config', localConfig);
cypressConfig = appCypressConfig;

// Reapply user options so they take precedence
if (cypressOptions) {
Object.assign(cypressConfig, cypressOptions);
}
}
// console.log(JSON.stringify(cypressConfig, null, 2));
return cypressConfig;
}

// Runs the specified integration tests
runCypressTests(webpackConfig, cypressOptions) {
const cypressConfig = this.generateCypressConfig(webpackConfig, cypressOptions);

try {
const cyCommand = cypressOptions.open ? open : run;
cyCommand(cypressConfig)
.then(res => {
logger.log('Cypress results:', res);
})
.catch(err => {
logger.log(err.message);
console.log(err.message);
process.exit(1);
});
} catch (e) {
logger.log('Error running cypress tests:', e);
}
}

async installAndRun() {
console.log('Attempting cypress install...');
try {
await exec('npx cypress install');
this.runCypressTests.apply(this, arguments); // eslint-disable-line
} catch (e) {
console.log(e);
}
}
}

module.exports = {
getBaseCypressConfig,
CypressService
};
28 changes: 28 additions & 0 deletions lib/test/cypress-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import '@cypress/code-coverage/support';

// Import commands.js using ES2015 syntax:
// import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')

import { mount } from 'cypress/react18';

Cypress.Commands.add('mount', mount);

Check failure on line 25 in lib/test/cypress-support.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'Cypress' is not defined

Check failure on line 25 in lib/test/cypress-support.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'Cypress' is not defined

// Example use:
// cy.mount(<MyComponent />)
Loading
Loading