mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-22 06:59:30 -06:00
chore(deprecate): Remove unused create-cypress-tests package (#28472)
This commit is contained in:
@@ -2134,13 +2134,6 @@ jobs:
|
||||
path: npm/grep/test_results
|
||||
- store-npm-logs
|
||||
|
||||
npm-create-cypress-tests:
|
||||
<<: *defaults
|
||||
resource_class: small
|
||||
steps:
|
||||
- restore_cached_workspace
|
||||
- run: yarn lerna run build --scope create-cypress-tests
|
||||
|
||||
npm-eslint-plugin-dev:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@@ -2798,9 +2791,6 @@ linux-x64-workflow: &linux-x64-workflow
|
||||
- npm-mount-utils:
|
||||
requires:
|
||||
- build
|
||||
- npm-create-cypress-tests:
|
||||
requires:
|
||||
- build
|
||||
- npm-eslint-plugin-dev:
|
||||
requires:
|
||||
- build
|
||||
@@ -2820,7 +2810,6 @@ linux-x64-workflow: &linux-x64-workflow
|
||||
- check-ts
|
||||
- npm-angular
|
||||
- npm-eslint-plugin-dev
|
||||
- npm-create-cypress-tests
|
||||
- npm-react
|
||||
- npm-mount-utils
|
||||
- npm-vue
|
||||
@@ -2876,7 +2865,6 @@ linux-x64-workflow: &linux-x64-workflow
|
||||
- check-ts
|
||||
- npm-angular
|
||||
- npm-eslint-plugin-dev
|
||||
- npm-create-cypress-tests
|
||||
- npm-react
|
||||
- npm-mount-utils
|
||||
- npm-vue
|
||||
@@ -3194,9 +3182,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow
|
||||
- npm-mount-utils:
|
||||
requires:
|
||||
- build
|
||||
- npm-create-cypress-tests:
|
||||
requires:
|
||||
- build
|
||||
- npm-eslint-plugin-dev:
|
||||
requires:
|
||||
- build
|
||||
@@ -3216,7 +3201,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow
|
||||
- check-ts
|
||||
- npm-angular
|
||||
- npm-eslint-plugin-dev
|
||||
- npm-create-cypress-tests
|
||||
- npm-puppeteer-unit-tests
|
||||
- npm-puppeteer-cypress-tests
|
||||
- npm-react
|
||||
@@ -3272,7 +3256,6 @@ linux-x64-contributor-workflow: &linux-x64-contributor-workflow
|
||||
- check-ts
|
||||
- npm-angular
|
||||
- npm-eslint-plugin-dev
|
||||
- npm-create-cypress-tests
|
||||
- npm-react
|
||||
- npm-mount-utils
|
||||
- npm-vue
|
||||
|
||||
@@ -53,8 +53,6 @@ module.exports = {
|
||||
'tooling/**',
|
||||
'packages/{app,driver,frontend-shared,launchpad}/cypress/**',
|
||||
'*.test.ts',
|
||||
// ignore in packages that don't run in the Cypress process
|
||||
'npm/create-cypress-tests/**',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-properties': 'off',
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -92,7 +92,7 @@ system-tests/lib/fixtureDirs.ts
|
||||
/packages/frontend-shared/src/generated
|
||||
/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts
|
||||
|
||||
# from npm/create-cypress-tests
|
||||
# from old npm/create-cypress-tests
|
||||
/npm/create-cypress-tests/initial-template
|
||||
/npm/create-cypress-tests/src/test-output
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
- [Cypress App](https://on.cypress.io/changelog)
|
||||
- [`@cypress/angular`](https://github.com/cypress-io/cypress/blob/develop/npm/angular/CHANGELOG.md)
|
||||
- [`@cypress/create-cypress-tests`](https://github.com/cypress-io/cypress/blob/develop/npm/create-cypress-tests/CHANGELOG.md)
|
||||
- [`@cypress/eslint-plugin-dev`](https://github.com/cypress-io/cypress/blob/develop/npm/eslint-plugin-dev/CHANGELOG.md)
|
||||
- [`@cypress/mount-utils`](https://github.com/cypress-io/cypress/blob/develop/npm/mount-utils/CHANGELOG.md)
|
||||
- [`@cypress/react`](https://github.com/cypress-io/cypress/blob/develop/npm/react/CHANGELOG.md)
|
||||
|
||||
@@ -183,7 +183,6 @@ Here is a list of the npm packages in this repository:
|
||||
| Folder Name | Package Name | Purpose |
|
||||
| :----------------------------------------------------- | :--------------------------------- | :--------------------------------------------------------------------------- |
|
||||
| [angular](./npm/angular) | `@cypress/angular` | Cypress component testing for Angular. |
|
||||
| [create-cypress-tests](./npm/create-cypress-tests) | `@cypress/create-cypress-tests` | Tooling to scaffold Cypress configuration and demo test files. |
|
||||
| [eslint-plugin-dev](./npm/eslint-plugin-dev) | `@cypress/eslint-plugin-dev` | Eslint plugin for internal development. |
|
||||
| [grep](./npm/grep) | `@cypress/grep` | Filter tests using substring |
|
||||
| [mount-utils](./npm/mount-utils) | `@cypress/mount-utils` | Common functionality for Vue/React/Angular adapters. |
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
**/dist
|
||||
**/*.d.ts
|
||||
**/package-lock.json
|
||||
**/tsconfig.json
|
||||
**/cypress/fixtures
|
||||
**/test/fixtures
|
||||
**/__snapshots__
|
||||
/initial-template
|
||||
/**/*.template.*
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"cypress",
|
||||
"@cypress/dev"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@cypress/dev/general",
|
||||
"plugin:@cypress/dev/tests"
|
||||
],
|
||||
"env": {
|
||||
"cypress/globals": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"watch-ignore": [
|
||||
"node_modules"
|
||||
],
|
||||
"require": "ts-node/register",
|
||||
"exit": true
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
./src/
|
||||
./initial-template/
|
||||
scripts/
|
||||
__snapshots__/
|
||||
@@ -1,118 +0,0 @@
|
||||
# [create-cypress-tests-v2.0.4](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.3...create-cypress-tests-v2.0.4) (2023-10-16)
|
||||
|
||||
# [create-cypress-tests-v2.0.3](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.2...create-cypress-tests-v2.0.3) (2023-09-07)
|
||||
|
||||
# [create-cypress-tests-v2.0.2](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.1...create-cypress-tests-v2.0.2) (2023-04-07)
|
||||
|
||||
# [create-cypress-tests-v2.0.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.0...create-cypress-tests-v2.0.1) (2023-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change wording for spec creation ([#25271](https://github.com/cypress-io/cypress/issues/25271)) ([c12a7e3](https://github.com/cypress-io/cypress/commit/c12a7e37c73d972eb0514e4b602940df210d86c7))
|
||||
|
||||
# [create-cypress-tests-v2.0.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.3.0...create-cypress-tests-v2.0.0) (2022-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* scope config to current testing type ([#20677](https://github.com/cypress-io/cypress/issues/20677)) ([61f7cfc](https://github.com/cypress-io/cypress/commit/61f7cfc59284a2938e0a1c15d74ee75215ba5f8b))
|
||||
* support using create-cypress-tests as part of build process ([#18714](https://github.com/cypress-io/cypress/issues/18714)) ([0501452](https://github.com/cypress-io/cypress/commit/0501452fb9e2df954ee871171052ab9f01367b25))
|
||||
* **unified-desktop-gui branch:** initial installation on windows ([#18247](https://github.com/cypress-io/cypress/issues/18247)) ([8614e97](https://github.com/cypress-io/cypress/commit/8614e978029bcbf7155b7ae98ac54feb11f2e7f3))
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* prep npm packages for use with Cypress v10 ([b924d08](https://github.com/cypress-io/cypress/commit/b924d086ee2e2ccc93303731e001b2c9e9d0af17))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add vue2 package from npm/vue/v2 branch ([#21026](https://github.com/cypress-io/cypress/issues/21026)) ([3aa69e2](https://github.com/cypress-io/cypress/commit/3aa69e2538aae5702bfc48789c54f37263ce08fc))
|
||||
* Deprecate run-ct / open-ct, and update all examples to use --ct instead ([#18422](https://github.com/cypress-io/cypress/issues/18422)) ([196e8f6](https://github.com/cypress-io/cypress/commit/196e8f62cc6d27974f235945cb5700624b3dae41))
|
||||
* remove testFiles reference ([#20565](https://github.com/cypress-io/cypress/issues/20565)) ([5670344](https://github.com/cypress-io/cypress/commit/567034459089d9d53dfab5556cb9369fb335c3db))
|
||||
* update on-links ([#19235](https://github.com/cypress-io/cypress/issues/19235)) ([cc2d734](https://github.com/cypress-io/cypress/commit/cc2d7348185e2a090c60d92d9319ab460d8c7827))
|
||||
* Use .config files ([#18578](https://github.com/cypress-io/cypress/issues/18578)) ([081dd19](https://github.com/cypress-io/cypress/commit/081dd19cc6da3da229a7af9c84f62730c85a5cd6))
|
||||
* use supportFile by testingType ([#19364](https://github.com/cypress-io/cypress/issues/19364)) ([0366d4f](https://github.com/cypress-io/cypress/commit/0366d4fa8971e5e5189c6fd6450cc3c8d72dcfe1))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* new version of packages for Cypress v10
|
||||
|
||||
# [create-cypress-tests-v1.3.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.2.0...create-cypress-tests-v1.3.0) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Restore broken gif ([#18987](https://github.com/cypress-io/cypress/issues/18987)) ([f251681](https://github.com/cypress-io/cypress/commit/f251681b814b102ca374abdef148b777c4e72c67))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [create-cypress-tests-v1.2.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.1.3...create-cypress-tests-v1.2.0) (2021-11-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** update dependency electron to v15 🌟 ([#18317](https://github.com/cypress-io/cypress/issues/18317)) ([3095d73](https://github.com/cypress-io/cypress/commit/3095d733e92527ffd67344c6899211e058ceefa3))
|
||||
|
||||
# [create-cypress-tests-v1.1.3](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.1.2...create-cypress-tests-v1.1.3) (2021-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* revive type checker ([#18172](https://github.com/cypress-io/cypress/issues/18172)) ([af472b6](https://github.com/cypress-io/cypress/commit/af472b6419ecb2aec1abdb09df99b2fa5f56e033))
|
||||
|
||||
# [create-cypress-tests-v1.1.2](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.1.1...create-cypress-tests-v1.1.2) (2021-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* case issue create cypress tests with `react/plugins/load-webpack` ([#16961](https://github.com/cypress-io/cypress/issues/16961)) ([c37ecea](https://github.com/cypress-io/cypress/commit/c37ecea3ca462015637515b331d1c9828ac1ed29)), closes [#16960](https://github.com/cypress-io/cypress/issues/16960)
|
||||
|
||||
# [create-cypress-tests-v1.1.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.1.0...create-cypress-tests-v1.1.1) (2021-05-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add return config for vitejs templates ([69d9de5](https://github.com/cypress-io/cypress/commit/69d9de581a03dce8e3535917a4cdcea8fa4eb6e9))
|
||||
* add return config for vueCli and vueWebpack ([9c12ee6](https://github.com/cypress-io/cypress/commit/9c12ee6d8467c65414ab2d413a9c45b2bbec64e9))
|
||||
* remove all of rollup, not supported anymore ([f8a71e7](https://github.com/cypress-io/cypress/commit/f8a71e75ae8208dc628d342cb1054c12f98338e9))
|
||||
* typo in the final message (run vs run-ct) ([294db04](https://github.com/cypress-io/cypress/commit/294db04f042dba86b69bb15d847c80a2c4202e80))
|
||||
* vueCli and webpack key vue@2 fix when guessing ([89f1bb9](https://github.com/cypress-io/cypress/commit/89f1bb9bc6bd987fbf6679a9d955c3587e69aa61))
|
||||
|
||||
# [create-cypress-tests-v1.1.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.0.1...create-cypress-tests-v1.1.0) (2021-04-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **component-testing:** Fix webpack-dev-server deps validation crash ([#15708](https://github.com/cypress-io/cypress/issues/15708)) ([254eb47](https://github.com/cypress-io/cypress/commit/254eb47d91c75a9f56162e7493ab83e5be169935))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support ct/e2e specific overrides in cypress.json ([#15526](https://github.com/cypress-io/cypress/issues/15526)) ([43c8ae2](https://github.com/cypress-io/cypress/commit/43c8ae2a7c20ba70a0bb0b45b8f6a086e2782f29))
|
||||
|
||||
# [create-cypress-tests-v1.0.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.0.0...create-cypress-tests-v1.0.1) (2021-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing script for building wizard ([#15502](https://github.com/cypress-io/cypress/issues/15502)) ([393a8ca](https://github.com/cypress-io/cypress/commit/393a8ca9cac905e0f6d8623bff889b041dd076b6))
|
||||
|
||||
# create-cypress-tests-v1.0.0 (2021-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runner-ct:** open link in external browser ([#15420](https://github.com/cypress-io/cypress/issues/15420)) ([d291157](https://github.com/cypress-io/cypress/commit/d291157f07ffebe961527fdd85c7ec51056801e7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **@cypress/react:** Make correct plugins for different adapters/bundlers ([#15337](https://github.com/cypress-io/cypress/issues/15337)) ([fc30118](https://github.com/cypress-io/cypress/commit/fc301182523f0a645bfb17ea3b541644b9732dd0)), closes [#9116](https://github.com/cypress-io/cypress/issues/9116)
|
||||
* create-cypress-tests installation wizard ([#9563](https://github.com/cypress-io/cypress/issues/9563)) ([c405ee8](https://github.com/cypress-io/cypress/commit/c405ee89ef5321df6151fdeec1e917ac952c0d38)), closes [#9116](https://github.com/cypress-io/cypress/issues/9116)
|
||||
* create-cypress-tests wizard ([#8857](https://github.com/cypress-io/cypress/issues/8857)) ([21ee591](https://github.com/cypress-io/cypress/commit/21ee591d1e9c4083a0c67f2062ced92708c0cedd))
|
||||
@@ -1,56 +0,0 @@
|
||||
# Create Cypress Tests
|
||||
|
||||
Installs and injects all the required configuration to run cypress tests.
|
||||
|
||||
## Quick overview
|
||||
|
||||
```
|
||||
cd my-app
|
||||
npx create-cypress-tests
|
||||
npx cypress open
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Package manager
|
||||
|
||||
This wizard will automatically determine which package do you use. If `yarn` available as global dependency it will use yarn to install dependencies and create lock file.
|
||||
|
||||
If you need to use `npm` over `yarn` you can do the following
|
||||
|
||||
```
|
||||
npx create-cypress-tests --use-npm
|
||||
```
|
||||
|
||||
By the way you can use yarn to run the installation wizard 😉
|
||||
|
||||
```
|
||||
yarn create cypress-tests
|
||||
```
|
||||
|
||||
## Typescript
|
||||
|
||||
This package will also automatically determine if typescript if available in this project and inject the required typescript configuration for cypress. If you are starting a new project and want to create typescript configuration, please do the following:
|
||||
|
||||
```
|
||||
npm init
|
||||
npm install typescript
|
||||
npx create-cypress-tests
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Here is a list of available configuration options:
|
||||
|
||||
`--use-npm` – use npm if yarn available
|
||||
`--ignore-typescript` – will not create typescript configuration if available
|
||||
`--ignore-examples` – will create a 1 template spec file (`cypress/integration/spec.js`) to start with
|
||||
`--component-tests` – will not ask should setup component testing or not
|
||||
|
||||
## License
|
||||
|
||||
The project is licensed under the terms of [MIT license](../../LICENSE)
|
||||
|
||||
## Changelog
|
||||
|
||||
[Changelog](./CHANGELOG.md)
|
||||
@@ -1,13 +0,0 @@
|
||||
exports['babel installation template correctly generates plugins config 1'] = `
|
||||
const injectDevServer = require('@cypress/react/plugins/babel');
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config);
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,43 +0,0 @@
|
||||
exports['injects guessed next.js template cypress.config.ts'] = `
|
||||
export default {
|
||||
specPattern: "src/**/*.spec.{js,ts,jsx,tsx}"
|
||||
};
|
||||
|
||||
`
|
||||
|
||||
exports['injects guessed next.js template plugins/index.js'] = `
|
||||
const injectDevServer = require("@cypress/react/plugins/next");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config);
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
|
||||
`
|
||||
|
||||
exports['Injected overridden webpack template cypress.config.ts'] = `
|
||||
export default {
|
||||
specPattern: "cypress/component/**/*.spec.{js,ts,jsx,tsx}"
|
||||
};
|
||||
|
||||
`
|
||||
|
||||
exports['Injected overridden webpack template plugins/index.js'] = `
|
||||
const injectDevServer = require("@cypress/react/plugins/react-scripts");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config);
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
|
||||
`
|
||||
|
||||
exports['Injected overridden webpack template support/component.js'] = `
|
||||
import "./commands.js";
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
exports['next.js install template correctly generates plugins config 1'] = `
|
||||
const injectDevServer = require('@cypress/react/plugins/next');
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config);
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
exports['create-react-app install template correctly generates plugins config 1'] = `
|
||||
const injectDevServer = require('@cypress/react/plugins/react-scripts');
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config);
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,32 +0,0 @@
|
||||
exports['webpack-file install template correctly generates plugins config when webpack config path is missing 1'] = `
|
||||
const injectDevServer = require("@cypress/react/plugins/load-webpack");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config, {
|
||||
// TODO replace with valid webpack config path
|
||||
webpackFilename: './webpack.config.js'
|
||||
});
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
|
||||
exports['webpack-file install template correctly generates plugins config when webpack config path is provided 1'] = `
|
||||
const injectDevServer = require("@cypress/react/plugins/load-webpack");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
injectDevServer(on, config, {
|
||||
webpackFilename: 'config/webpack.config.js'
|
||||
});
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,17 +0,0 @@
|
||||
exports['vue: vite template correctly generates plugins config 1'] = `
|
||||
const {
|
||||
startDevServer
|
||||
} = require("@cypress/vite-dev-server");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
on("dev-server:start", async options => startDevServer({
|
||||
options
|
||||
}));
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,20 +0,0 @@
|
||||
exports['vue webpack-file install template correctly generates plugins for vue-cli-service 1'] = `
|
||||
const {
|
||||
startDevServer
|
||||
} = require("@cypress/webpack-dev-server");
|
||||
|
||||
const webpackConfig = require("@vue/cli-service/webpack.config.js");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
on('dev-server:start', options => startDevServer({
|
||||
options,
|
||||
webpackConfig
|
||||
}));
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,42 +0,0 @@
|
||||
exports['vue webpack-file install template correctly generates plugins config when webpack config path is missing 1'] = `
|
||||
const {
|
||||
startDevServer
|
||||
} = require("@cypress/webpack-dev-server");
|
||||
|
||||
const webpackConfig = require("./webpack.config.js"); // TODO replace with valid webpack config path
|
||||
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
on('dev-server:start', options => startDevServer({
|
||||
options,
|
||||
webpackConfig
|
||||
}));
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
|
||||
exports['vue webpack-file install template correctly generates plugins config when webpack config path is provided 1'] = `
|
||||
const {
|
||||
startDevServer
|
||||
} = require("@cypress/webpack-dev-server");
|
||||
|
||||
const webpackConfig = require("build/webpack.config.js");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
on('dev-server:start', options => startDevServer({
|
||||
options,
|
||||
webpackConfig
|
||||
}));
|
||||
}
|
||||
|
||||
return config; // IMPORTANT to return a config
|
||||
};
|
||||
`
|
||||
@@ -1,40 +0,0 @@
|
||||
exports['webpack-options template correctly generates plugins config 1'] = `
|
||||
const path = require("path");
|
||||
|
||||
const {
|
||||
startDevServer
|
||||
} = require("@cypress/webpack-dev-Server");
|
||||
|
||||
const something = require("something");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === "component") {
|
||||
/** @type import("webpack").Configuration */
|
||||
const webpackConfig = {
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.jsx', '.tsx']
|
||||
},
|
||||
mode: 'development',
|
||||
devtool: false,
|
||||
output: {
|
||||
publicPath: '/',
|
||||
chunkFilename: '[name].bundle.js'
|
||||
},
|
||||
// TODO: update with valid configuration for your components
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\\.(js|jsx|mjs|ts|tsx)$/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: path.resolve(__dirname, '.babel-cache')
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
on('dev-server:start', options => startDevServer({
|
||||
options,
|
||||
webpackConfig
|
||||
}));
|
||||
}
|
||||
};
|
||||
`
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = {}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 30 MiB |
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"name": "create-cypress-tests",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Cypress smart installation wizard",
|
||||
"main": "dist/src/main.js",
|
||||
"scripts": {
|
||||
"build": "yarn prepare-example && tsc -p ./tsconfig.json && node scripts/example copy-to ./dist/initial-template && yarn prepare-copy-templates",
|
||||
"prepare-example": "node scripts/example copy-to ./initial-template",
|
||||
"prepare-copy-templates": "node scripts/copy-templates copy-to ./dist/src",
|
||||
"test": "cross-env TS_NODE_PROJECT=./tsconfig.test.json mocha --config .mocharc.json './src/**/*.test.ts'",
|
||||
"test:watch": "yarn test -w",
|
||||
"lint": "eslint --ext .js,.ts,.json, ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.5.4",
|
||||
"@babel/plugin-transform-typescript": "^7.2.0",
|
||||
"@babel/template": "^7.5.4",
|
||||
"@babel/types": "^7.5.0",
|
||||
"bluebird": "3.7.2",
|
||||
"chalk": "4.1.0",
|
||||
"cli-highlight": "2.1.10",
|
||||
"commander": "6.2.1",
|
||||
"find-up": "5.0.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"inquirer": "8.2.4",
|
||||
"ora": "^5.1.0",
|
||||
"recast": "0.20.4",
|
||||
"semver": "7.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel__core": "^7.1.2",
|
||||
"@types/inquirer": "8.2.4",
|
||||
"@types/mock-fs": "4.10.0",
|
||||
"@types/node": "18.17.5",
|
||||
"@types/ora": "^3.2.0",
|
||||
"@types/semver": "7.5.0",
|
||||
"copy": "0.3.2",
|
||||
"mocha": "7.1.1",
|
||||
"mock-fs": "5.2.0",
|
||||
"snap-shot-it": "7.9.3",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"bin"
|
||||
],
|
||||
"bin": {
|
||||
"create-cypress-tests": "dist/src/index.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/cypress-io/cypress.git",
|
||||
"homepage": "https://github.com/cypress-io/cypress/blob/develop/npm/create-cypress-tests/#readme",
|
||||
"nx": {
|
||||
"implicitDependencies": [
|
||||
"@packages/example"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
const globby = require('globby')
|
||||
const fs = require('fs-extra')
|
||||
const chalk = require('chalk')
|
||||
const path = require('path')
|
||||
const program = require('commander')
|
||||
|
||||
program
|
||||
.command('copy-to [destination]')
|
||||
.description('copy ./src/**/*.template.js into destination')
|
||||
.action(async (destination) => {
|
||||
const srcPath = path.resolve(__dirname, '..', 'src')
|
||||
const destinationPath = path.resolve(process.cwd(), destination)
|
||||
|
||||
const templates = await globby('**/*.template.js', {
|
||||
cwd: srcPath,
|
||||
onlyFiles: true,
|
||||
unique: true,
|
||||
})
|
||||
|
||||
const srcOutput = './src/'
|
||||
let destinationOutput = destination.replace('/\\/g', '/')
|
||||
|
||||
if (!destinationOutput.endsWith('/')) {
|
||||
destinationOutput += '/'
|
||||
}
|
||||
|
||||
const relOutput = (template, forSource) => {
|
||||
return `${forSource ? srcOutput : destinationOutput}${template}`
|
||||
}
|
||||
|
||||
const result = await Promise.all(templates.map(async (template) => {
|
||||
await fs.copy(path.join(srcPath, template), path.join(destinationPath, template))
|
||||
|
||||
return () => console.log(`✅ ${relOutput(template, true)} successfully copied to ${chalk.cyan(relOutput(template, false))}`)
|
||||
}))
|
||||
|
||||
result.forEach((r) => r())
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
@@ -1,27 +0,0 @@
|
||||
const fs = require('fs-extra')
|
||||
const chalk = require('chalk')
|
||||
const path = require('path')
|
||||
const program = require('commander')
|
||||
|
||||
program
|
||||
.command('copy-to [destination]')
|
||||
.description('copy cypress/packages/example into destination')
|
||||
.action(async (destination) => {
|
||||
const exampleFolder = path.resolve(__dirname, '..', '..', '..', 'packages', 'example', 'cypress')
|
||||
const destinationPath = path.resolve(process.cwd(), destination)
|
||||
|
||||
await fs.remove(destinationPath)
|
||||
await fs.copy(exampleFolder, destinationPath, { recursive: true })
|
||||
|
||||
console.log(`✅ E2E Examples were successfully created at ${chalk.cyan(destination)}`)
|
||||
|
||||
await fs.copy(path.join(__dirname, 'examples', 'cypress'), path.join(destination))
|
||||
|
||||
console.log(`✅ Cypress Setup was successfully created at ${chalk.cyan(destination)}`)
|
||||
|
||||
await fs.copy(path.join(__dirname, 'examples', 'tsconfig.json'), path.join(destination, 'tsconfig.json'))
|
||||
|
||||
console.log(`✅ tsconfig.json was created for ${chalk.cyan(destination)}`)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// 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 commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.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 commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom"
|
||||
],
|
||||
"types": [
|
||||
"cypress"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts*"
|
||||
]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/// <reference path="../../../../../cli/types/mocha/index.d.ts" />
|
||||
|
||||
import * as babel from '@babel/core'
|
||||
import { expect } from 'chai'
|
||||
import { createTransformPluginsFileBabelPlugin } from './babelTransform'
|
||||
|
||||
describe('babel transform utils', () => {
|
||||
context('Plugins config babel plugin', () => {
|
||||
it('injects code into the plugins file based on ast', () => {
|
||||
const plugin = createTransformPluginsFileBabelPlugin({
|
||||
RequireAst: babel.template.ast('require("something")'),
|
||||
IfComponentTestingPluginsAst: babel.template.ast('yey()'),
|
||||
})
|
||||
|
||||
const output = babel.transformSync([
|
||||
'module.exports = (on, config) => {',
|
||||
'on("do")',
|
||||
'}',
|
||||
].join('\n'), {
|
||||
plugins: [plugin],
|
||||
})?.code
|
||||
|
||||
expect(output).to.equal([
|
||||
'require("something");',
|
||||
'',
|
||||
'module.exports = (on, config) => {',
|
||||
' on("do");',
|
||||
'',
|
||||
' if (config.testingType === "component") {',
|
||||
' yey();',
|
||||
' }',
|
||||
'};',
|
||||
].join(`\n`))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,138 +0,0 @@
|
||||
import path from 'path'
|
||||
import * as fs from 'fs-extra'
|
||||
import * as babel from '@babel/core'
|
||||
import * as babelTypes from '@babel/types'
|
||||
import { prettifyCode } from '../../utils'
|
||||
|
||||
type AST = ReturnType<typeof babel.template.ast>
|
||||
|
||||
export type PluginsConfigAst = {
|
||||
RequireAst: AST
|
||||
IfComponentTestingPluginsAst: AST
|
||||
requiresReturnConfig?: true
|
||||
}
|
||||
|
||||
const sharedBabelOptions = {
|
||||
// disable user config
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [],
|
||||
root: process.env.BABEL_TEST_ROOT, // for testing
|
||||
}
|
||||
|
||||
async function transformFileViaPlugin (filePath: string, babelPlugin: babel.PluginObj) {
|
||||
try {
|
||||
const initialCode = await fs.readFile(filePath, { encoding: 'utf-8' })
|
||||
|
||||
const updatedResult = await babel.transformAsync(initialCode, {
|
||||
filename: path.basename(filePath),
|
||||
filenameRelative: path.relative(process.cwd(), filePath),
|
||||
plugins: [babelPlugin],
|
||||
...sharedBabelOptions,
|
||||
})
|
||||
|
||||
if (!updatedResult) {
|
||||
return false
|
||||
}
|
||||
|
||||
let finalCode = updatedResult.code
|
||||
|
||||
if (finalCode === initialCode) {
|
||||
return false
|
||||
}
|
||||
|
||||
finalCode = await prettifyCode(finalCode)
|
||||
|
||||
await fs.writeFile(filePath, finalCode)
|
||||
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const returnConfigAst = babel.template.ast('return config; // IMPORTANT to return a config', { preserveComments: true })
|
||||
|
||||
export function createTransformPluginsFileBabelPlugin (ast: PluginsConfigAst): babel.PluginObj {
|
||||
return {
|
||||
visitor: {
|
||||
Program: (path) => {
|
||||
path.unshiftContainer('body', ast.RequireAst)
|
||||
},
|
||||
Function: (path) => {
|
||||
if (!babelTypes.isAssignmentExpression(path.parent)) {
|
||||
return
|
||||
}
|
||||
|
||||
const assignment = path.parent.left
|
||||
|
||||
const isModuleExports =
|
||||
babelTypes.isMemberExpression(assignment)
|
||||
&& babelTypes.isIdentifier(assignment.object)
|
||||
&& assignment.object.name === 'module'
|
||||
&& babelTypes.isIdentifier(assignment.property)
|
||||
&& assignment.property.name === 'exports'
|
||||
|
||||
if (isModuleExports && babelTypes.isFunction(path.parent.right)) {
|
||||
const paramsLength = path.parent.right.params.length
|
||||
|
||||
if (paramsLength === 0) {
|
||||
path.parent.right.params.push(babelTypes.identifier('on'))
|
||||
path.parent.right.params.push(babelTypes.identifier('config'))
|
||||
}
|
||||
|
||||
if (paramsLength === 1) {
|
||||
path.parent.right.params.push(babelTypes.identifier('config'))
|
||||
}
|
||||
|
||||
const statementToInject = Array.isArray(ast.IfComponentTestingPluginsAst)
|
||||
? ast.IfComponentTestingPluginsAst
|
||||
: [ast.IfComponentTestingPluginsAst]
|
||||
|
||||
const ifComponentMode = babelTypes.ifStatement(
|
||||
babelTypes.binaryExpression(
|
||||
'===',
|
||||
babelTypes.identifier('config.testingType'),
|
||||
babelTypes.stringLiteral('component'),
|
||||
),
|
||||
babelTypes.blockStatement(statementToInject as babelTypes.Statement[] | babelTypes.Statement[]),
|
||||
)
|
||||
|
||||
path.get('body').pushContainer('body' as never, ifComponentMode as never)
|
||||
|
||||
if (ast.requiresReturnConfig) {
|
||||
path.get('body').pushContainer('body' as never, returnConfigAst as never)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function injectPluginsCode (pluginsFilePath: string, ast: PluginsConfigAst) {
|
||||
return transformFileViaPlugin(pluginsFilePath, createTransformPluginsFileBabelPlugin(ast))
|
||||
}
|
||||
|
||||
export async function getPluginsSourceExample (ast: PluginsConfigAst) {
|
||||
const exampleCode = [
|
||||
'module.exports = (on, config) => {',
|
||||
'',
|
||||
'}',
|
||||
].join('\n')
|
||||
|
||||
try {
|
||||
const babelResult = await babel.transformAsync(exampleCode, {
|
||||
filename: 'nothing.js',
|
||||
plugins: [createTransformPluginsFileBabelPlugin(ast)],
|
||||
...sharedBabelOptions,
|
||||
})
|
||||
|
||||
if (!babelResult?.code) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
return babelResult.code
|
||||
} catch (e) {
|
||||
throw new Error('Can not generate code example for plugins file because of unhandled error. Please update the plugins file manually.')
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
/// <reference path="../../../../../cli/types/mocha/index.d.ts" />
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { insertValueInJSString } from './configFileUpdater'
|
||||
|
||||
// Test util - if needed outside the tests we can move it to utils
|
||||
const stripIndent = (strings: any, ...args: any) => {
|
||||
const parts = []
|
||||
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
parts.push(strings[i])
|
||||
|
||||
if (i < strings.length - 1) {
|
||||
parts.push(`<<${i}>>`)
|
||||
}
|
||||
}
|
||||
|
||||
const lines = parts.join('').split('\n')
|
||||
const firstLine = lines[0].length === 0 ? lines[1] : lines[0]
|
||||
let indentSize = 0
|
||||
|
||||
for (let i = 0; i < firstLine.length; i++) {
|
||||
if (firstLine[i] === ' ') {
|
||||
indentSize++
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
const strippedLines = lines.map((line) => line.substring(indentSize))
|
||||
|
||||
let result = strippedLines.join('\n').trimLeft()
|
||||
|
||||
args.forEach((arg: any, i: any) => {
|
||||
result = result.replace(`<<${i}>>`, `${arg}`)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
describe('lib/util/config-file-updater', () => {
|
||||
context('with js files', () => {
|
||||
describe('#insertValueInJSString', () => {
|
||||
describe('es6 vs es5', () => {
|
||||
it('finds the object literal and adds the values to it es6', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
foo: 42,
|
||||
}
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
foo: 42,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('finds the object literal and adds the values to it es5', async () => {
|
||||
const src = stripIndent`\
|
||||
module.exports = {
|
||||
foo: 42,
|
||||
}
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
module.exports = {
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
foo: 42,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('works with and without the quotes around keys', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
"foo": 42,
|
||||
}
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
"foo": 42,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
describe('defineConfig', () => {
|
||||
it('skips defineConfig and add to the object inside', async () => {
|
||||
const src = stripIndent`\
|
||||
import { defineConfig } from "cypress"
|
||||
export default defineConfig({
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
import { defineConfig } from "cypress"
|
||||
export default defineConfig({
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('skips defineConfig even if it renamed in an import (es6)', async () => {
|
||||
const src = stripIndent`\
|
||||
import { defineConfig as cy_defineConfig } from "cypress"
|
||||
export default cy_defineConfig({
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
import { defineConfig as cy_defineConfig } from "cypress"
|
||||
export default cy_defineConfig({
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('skips defineConfig even if it renamed in a require (es5)', async () => {
|
||||
const src = stripIndent`\
|
||||
const { defineConfig: cy_defineConfig } = require("cypress")
|
||||
module.exports = cy_defineConfig({
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
const { defineConfig: cy_defineConfig } = require("cypress")
|
||||
module.exports = cy_defineConfig({
|
||||
projectId: "id1234",
|
||||
viewportWidth: 400,
|
||||
foo: 42,
|
||||
})
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { projectId: 'id1234', viewportWidth: 400 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updates', () => {
|
||||
it('updates a value if the same value is found in resolved config', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
foo: 42,
|
||||
}
|
||||
`
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
foo: 1000,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('accepts inline comments', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
foo: 12, // will do this later
|
||||
viewportWidth: 800,
|
||||
}
|
||||
`
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
foo: 1000, // will do this later
|
||||
viewportWidth: 800,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates a value even when this value is explicitely undefined', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
foo: undefined, // will do this later
|
||||
viewportWidth: 800,
|
||||
}
|
||||
`
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
foo: 1000, // will do this later
|
||||
viewportWidth: 800,
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000 })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates values and inserts config', async () => {
|
||||
const src = stripIndent`\
|
||||
export default {
|
||||
foo: 42,
|
||||
bar: 84,
|
||||
component: {
|
||||
devServer() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
export default {
|
||||
projectId: "id1234",
|
||||
foo: 1000,
|
||||
bar: 3000,
|
||||
component: {
|
||||
devServer() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { foo: 1000, bar: 3000, projectId: 'id1234' })
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
describe('subkeys', () => {
|
||||
it('inserts nested values', async () => {
|
||||
const src = stripIndent`\
|
||||
module.exports = {
|
||||
foo: 42
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { component: { specPattern: 'src/**/*.spec.cy.js' } })
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
module.exports = {
|
||||
component: {
|
||||
specPattern: "src/**/*.spec.cy.js",
|
||||
},
|
||||
foo: 42
|
||||
}
|
||||
`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('inserts nested values into existing keys', async () => {
|
||||
const src = stripIndent`\
|
||||
module.exports = {
|
||||
component: {
|
||||
viewportWidth: 800
|
||||
},
|
||||
foo: 42
|
||||
}
|
||||
`
|
||||
|
||||
const output = await insertValueInJSString(src, { component: { specPattern: 'src/**/*.spec.cy.js' } })
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
module.exports = {
|
||||
component: {
|
||||
specPattern: "src/**/*.spec.cy.js",
|
||||
viewportWidth: 800
|
||||
},
|
||||
foo: 42
|
||||
}
|
||||
`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
|
||||
it('updates nested values', async () => {
|
||||
const src = stripIndent`\
|
||||
module.exports = {
|
||||
foo: 42,
|
||||
component: {
|
||||
specPattern: 'components/**/*.spec.cy.js',
|
||||
foo: 82
|
||||
}
|
||||
}`
|
||||
|
||||
const output = await insertValueInJSString(src, { component: { specPattern: 'src/**/*.spec.cy.js' } })
|
||||
|
||||
const expectedOutput = stripIndent`\
|
||||
module.exports = {
|
||||
foo: 42,
|
||||
component: {
|
||||
specPattern: "src/**/*.spec.cy.js",
|
||||
foo: 82
|
||||
}
|
||||
}`
|
||||
|
||||
expect(output).to.equal(expectedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
describe('failures', () => {
|
||||
it('fails if not an object literal', () => {
|
||||
const src = [
|
||||
'const foo = {}',
|
||||
'export default foo',
|
||||
].join('\n')
|
||||
|
||||
return insertValueInJSString(src, { bar: 10 })
|
||||
.then(() => {
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err.message).to.equal('Cypress was unable to add/update values in your configuration file.')
|
||||
})
|
||||
})
|
||||
|
||||
it('fails if one of the values to update is not a literal', () => {
|
||||
const src = [
|
||||
'const bar = 12',
|
||||
'export default {',
|
||||
' foo: bar',
|
||||
'}',
|
||||
].join('\n')
|
||||
|
||||
return insertValueInJSString(src, { foo: 10 })
|
||||
.then(() => {
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err.message).to.equal('Cypress was unable to add/update values in your configuration file.')
|
||||
})
|
||||
})
|
||||
|
||||
it('fails with inlined values', () => {
|
||||
const src = stripIndent`\
|
||||
const foo = 12
|
||||
export default {
|
||||
foo
|
||||
}
|
||||
`
|
||||
|
||||
return insertValueInJSString(src, { foo: 10 })
|
||||
.then(() => {
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err.message).to.equal('Cypress was unable to add/update values in your configuration file.')
|
||||
})
|
||||
})
|
||||
|
||||
it('fails if there is a spread', () => {
|
||||
const src = stripIndent`\
|
||||
const foo = { bar: 12 }
|
||||
export default {
|
||||
bar: 8,
|
||||
...foo
|
||||
}
|
||||
`
|
||||
|
||||
return insertValueInJSString(src, { bar: 10 })
|
||||
.then(() => {
|
||||
throw Error('this should not succeed')
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err.message).to.equal('Cypress was unable to add/update values in your configuration file.')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,294 +0,0 @@
|
||||
import { parse } from '@babel/parser'
|
||||
import type { File } from '@babel/types'
|
||||
import type { NodePath } from 'ast-types/lib/node-path'
|
||||
import { visit } from 'recast'
|
||||
import type { namedTypes } from 'ast-types'
|
||||
import * as fs from 'fs-extra'
|
||||
import { prettifyCode } from '../../utils'
|
||||
|
||||
export async function insertValuesInConfigFile (filePath: string, obj: Record<string, any> = {}) {
|
||||
await insertValuesInJavaScript(filePath, obj)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function insertValuesInJavaScript (filePath: string, obj: Record<string, any>) {
|
||||
const fileContents = await fs.readFile(filePath, { encoding: 'utf8' })
|
||||
|
||||
let finalCode = await insertValueInJSString(fileContents, obj)
|
||||
|
||||
const prettifiedCode = await prettifyCode(finalCode)
|
||||
|
||||
if (prettifiedCode) {
|
||||
finalCode = prettifiedCode
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, finalCode)
|
||||
}
|
||||
|
||||
export async function insertValueInJSString (fileContents: string, obj: Record<string, any>): Promise<string> {
|
||||
const ast = parse(fileContents, { plugins: ['typescript'], sourceType: 'module' })
|
||||
|
||||
let objectLiteralNode: namedTypes.ObjectExpression | undefined
|
||||
|
||||
function handleExport (nodePath: NodePath<namedTypes.CallExpression, any> | NodePath<namedTypes.ObjectExpression, any>): void {
|
||||
if (nodePath.node.type === 'CallExpression'
|
||||
&& nodePath.node.callee.type === 'Identifier') {
|
||||
const functionName = nodePath.node.callee.name
|
||||
|
||||
if (isDefineConfigFunction(ast, functionName)) {
|
||||
return handleExport(nodePath.get('arguments', 0))
|
||||
}
|
||||
}
|
||||
|
||||
if (nodePath.node.type === 'ObjectExpression' && !nodePath.node.properties.find((prop) => prop.type !== 'ObjectProperty')) {
|
||||
objectLiteralNode = nodePath.node
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Cypress was unable to add/update values in your configuration file.')
|
||||
}
|
||||
|
||||
visit(ast, {
|
||||
visitAssignmentExpression (nodePath) {
|
||||
if (nodePath.node.left.type === 'MemberExpression') {
|
||||
if (nodePath.node.left.object.type === 'Identifier' && nodePath.node.left.object.name === 'module'
|
||||
&& nodePath.node.left.property.type === 'Identifier' && nodePath.node.left.property.name === 'exports') {
|
||||
handleExport(nodePath.get('right'))
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
visitExportDefaultDeclaration (nodePath) {
|
||||
handleExport(nodePath.get('declaration'))
|
||||
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
const splicers: Splicer[] = []
|
||||
|
||||
if (!objectLiteralNode) {
|
||||
// if the export is no object literal
|
||||
throw new Error('Cypress was unable to add/update values in your configuration file.')
|
||||
}
|
||||
|
||||
setRootKeysSplicers(splicers, obj, objectLiteralNode!, ' ')
|
||||
setSubKeysSplicers(splicers, obj, objectLiteralNode!, ' ', ' ')
|
||||
|
||||
// sort splicers to keep the order of the original file
|
||||
const sortedSplicers = splicers.sort((a, b) => a.start === b.start ? 0 : a.start > b.start ? 1 : -1)
|
||||
|
||||
if (!sortedSplicers.length) return fileContents
|
||||
|
||||
let nextStartingIndex = 0
|
||||
let resultCode = ''
|
||||
|
||||
sortedSplicers.forEach((splicer) => {
|
||||
resultCode += fileContents.slice(nextStartingIndex, splicer.start) + splicer.replaceString
|
||||
nextStartingIndex = splicer.end
|
||||
})
|
||||
|
||||
return resultCode + fileContents.slice(nextStartingIndex)
|
||||
}
|
||||
|
||||
export function isDefineConfigFunction (ast: File, functionName: string): boolean {
|
||||
let value = false
|
||||
|
||||
visit(ast, {
|
||||
visitVariableDeclarator (nodePath) {
|
||||
// if this is a require of cypress
|
||||
if (nodePath.node.init?.type === 'CallExpression'
|
||||
&& nodePath.node.init.callee.type === 'Identifier'
|
||||
&& nodePath.node.init.callee.name === 'require'
|
||||
&& nodePath.node.init.arguments[0].type === 'StringLiteral'
|
||||
&& nodePath.node.init.arguments[0].value === 'cypress') {
|
||||
if (nodePath.node.id?.type === 'ObjectPattern') {
|
||||
const defineConfigFunctionNode = nodePath.node.id.properties.find((prop) => {
|
||||
return prop.type === 'ObjectProperty'
|
||||
&& prop.key.type === 'Identifier'
|
||||
&& prop.key.name === 'defineConfig'
|
||||
})
|
||||
|
||||
if (defineConfigFunctionNode) {
|
||||
value = (defineConfigFunctionNode as any).value?.name === functionName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
visitImportDeclaration (nodePath) {
|
||||
if (nodePath.node.source.type === 'StringLiteral'
|
||||
&& nodePath.node.source.value === 'cypress') {
|
||||
const defineConfigFunctionNode = nodePath.node.specifiers?.find((specifier) => {
|
||||
return specifier.type === 'ImportSpecifier'
|
||||
&& specifier.imported.type === 'Identifier'
|
||||
&& specifier.imported.name === 'defineConfig'
|
||||
})
|
||||
|
||||
if (defineConfigFunctionNode) {
|
||||
value = (defineConfigFunctionNode as any).local?.name === functionName
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function setRootKeysSplicers (
|
||||
splicers: Splicer[],
|
||||
obj: Record<string, any>,
|
||||
objectLiteralNode: namedTypes.ObjectExpression,
|
||||
lineStartSpacer: string,
|
||||
) {
|
||||
const objectLiteralStartIndex = (objectLiteralNode as any).start + 1
|
||||
// add values
|
||||
const objKeys = Object.keys(obj).filter((key) => ['boolean', 'number', 'string'].includes(typeof obj[key]))
|
||||
|
||||
// update values
|
||||
const keysToUpdate = objKeys.filter((key) => {
|
||||
return objectLiteralNode.properties.find((prop) => {
|
||||
return prop.type === 'ObjectProperty'
|
||||
&& prop.key.type === 'Identifier'
|
||||
&& prop.key.name === key
|
||||
})
|
||||
})
|
||||
|
||||
keysToUpdate.forEach(
|
||||
(key) => {
|
||||
const propertyToUpdate = propertyFromKey(objectLiteralNode, key)
|
||||
|
||||
if (propertyToUpdate) {
|
||||
setSplicerToUpdateProperty(splicers, propertyToUpdate, obj[key], key, obj)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const keysToInsert = objKeys.filter((key) => !keysToUpdate.includes(key))
|
||||
|
||||
if (keysToInsert.length) {
|
||||
const valuesInserted = `\n${lineStartSpacer}${ keysToInsert.map((key) => `${key}: ${JSON.stringify(obj[key])},`).join(`\n${lineStartSpacer}`)}`
|
||||
|
||||
splicers.push({
|
||||
start: objectLiteralStartIndex,
|
||||
end: objectLiteralStartIndex,
|
||||
replaceString: valuesInserted,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function setSubKeysSplicers (
|
||||
splicers: Splicer[],
|
||||
obj: Record<string, any>,
|
||||
objectLiteralNode: namedTypes.ObjectExpression,
|
||||
lineStartSpacer: string,
|
||||
parentLineStartSpacer: string,
|
||||
) {
|
||||
const objectLiteralStartIndex = (objectLiteralNode as any).start + 1
|
||||
|
||||
const keysToUpdateWithObjects: string[] = []
|
||||
|
||||
const objSubkeys = Object.keys(obj).filter((key) => typeof obj[key] === 'object').reduce((acc: Array<{parent: string, subkey: string}>, key) => {
|
||||
keysToUpdateWithObjects.push(key)
|
||||
Object.entries(obj[key]).forEach(([subkey, value]) => {
|
||||
if (['boolean', 'number', 'string'].includes(typeof value)) {
|
||||
acc.push({ parent: key, subkey })
|
||||
}
|
||||
})
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
// add values where the parent key needs to be created
|
||||
const subkeysToInsertWithoutKey = objSubkeys.filter(({ parent }) => {
|
||||
return !objectLiteralNode.properties.find((prop) => {
|
||||
return prop.type === 'ObjectProperty'
|
||||
&& prop.key.type === 'Identifier'
|
||||
&& prop.key.name === parent
|
||||
})
|
||||
})
|
||||
const keysToInsertForSubKeys: Record<string, string[]> = {}
|
||||
|
||||
subkeysToInsertWithoutKey.forEach((keyTuple) => {
|
||||
const subkeyList = keysToInsertForSubKeys[keyTuple.parent] || []
|
||||
|
||||
subkeyList.push(keyTuple.subkey)
|
||||
keysToInsertForSubKeys[keyTuple.parent] = subkeyList
|
||||
})
|
||||
|
||||
let subvaluesInserted = ''
|
||||
|
||||
for (const key in keysToInsertForSubKeys) {
|
||||
subvaluesInserted += `\n${parentLineStartSpacer}${key}: {`
|
||||
keysToInsertForSubKeys[key].forEach((subkey) => {
|
||||
subvaluesInserted += `\n${parentLineStartSpacer}${lineStartSpacer}${subkey}: ${JSON.stringify(obj[key][subkey])},`
|
||||
})
|
||||
|
||||
subvaluesInserted += `\n${parentLineStartSpacer}},`
|
||||
}
|
||||
|
||||
if (subkeysToInsertWithoutKey.length) {
|
||||
splicers.push({
|
||||
start: objectLiteralStartIndex,
|
||||
end: objectLiteralStartIndex,
|
||||
replaceString: subvaluesInserted,
|
||||
})
|
||||
}
|
||||
|
||||
// add/update values where parent key already exists
|
||||
keysToUpdateWithObjects.filter((parent) => {
|
||||
return objectLiteralNode.properties.find((prop) => {
|
||||
return prop.type === 'ObjectProperty'
|
||||
&& prop.key.type === 'Identifier'
|
||||
&& prop.key.name === parent
|
||||
})
|
||||
}).forEach((key) => {
|
||||
const propertyToUpdate = propertyFromKey(objectLiteralNode, key)
|
||||
|
||||
if (propertyToUpdate?.value.type === 'ObjectExpression') {
|
||||
setRootKeysSplicers(splicers, obj[key], propertyToUpdate.value, parentLineStartSpacer + lineStartSpacer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setSplicerToUpdateProperty (splicers: Splicer[],
|
||||
propertyToUpdate: namedTypes.ObjectProperty,
|
||||
updatedValue: any,
|
||||
key: string,
|
||||
obj: Record<string, any>) {
|
||||
if (propertyToUpdate && (isPrimitive(propertyToUpdate.value) || isUndefinedOrNull(propertyToUpdate.value))) {
|
||||
splicers.push({
|
||||
start: (propertyToUpdate.value as any).start,
|
||||
end: (propertyToUpdate.value as any).end,
|
||||
replaceString: JSON.stringify(updatedValue),
|
||||
})
|
||||
} else {
|
||||
throw new Error('Cypress was unable to add/update values in your configuration file.')
|
||||
}
|
||||
}
|
||||
|
||||
function propertyFromKey (objectLiteralNode: namedTypes.ObjectExpression | undefined, key: string): namedTypes.ObjectProperty | undefined {
|
||||
return objectLiteralNode?.properties.find((prop) => {
|
||||
return prop.type === 'ObjectProperty' && prop.key.type === 'Identifier' && prop.key.name === key
|
||||
}) as namedTypes.ObjectProperty
|
||||
}
|
||||
|
||||
function isPrimitive (value: NodePath['node']): value is namedTypes.NumericLiteral | namedTypes.StringLiteral | namedTypes.BooleanLiteral {
|
||||
return value.type === 'NumericLiteral' || value.type === 'StringLiteral' || value.type === 'BooleanLiteral'
|
||||
}
|
||||
|
||||
function isUndefinedOrNull (value: NodePath['node']): value is namedTypes.Identifier {
|
||||
return value.type === 'Identifier' && ['undefined', 'null'].includes(value.name)
|
||||
}
|
||||
|
||||
interface Splicer{
|
||||
start: number
|
||||
end: number
|
||||
replaceString: string
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import snapshot from 'snap-shot-it'
|
||||
import { expect, use } from 'chai'
|
||||
import sinon, { SinonStub, SinonSpy } from 'sinon'
|
||||
import chalk from 'chalk'
|
||||
import mockFs from 'mock-fs'
|
||||
import { initComponentTesting } from './init-component-testing'
|
||||
import inquirer from 'inquirer'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import childProcess from 'child_process'
|
||||
import { someOfSpyCallsIncludes } from '../test-utils'
|
||||
|
||||
use(sinonChai)
|
||||
|
||||
describe('init component tests script', () => {
|
||||
let promptSpy: SinonStub<any> | null = null
|
||||
let logSpy: SinonSpy | null = null
|
||||
let processExitStub: SinonStub<any> | null = null
|
||||
let execStub: SinonStub | null = null
|
||||
|
||||
const e2eTestOutputPath = path.resolve(__dirname, '..', 'test-output')
|
||||
const cypressConfigPath = path.join(e2eTestOutputPath, 'cypress.config.ts')
|
||||
|
||||
beforeEach(async () => {
|
||||
logSpy = sinon.spy(global.console, 'log')
|
||||
// @ts-ignores
|
||||
execStub = sinon.stub(childProcess, 'exec').callsFake((command, callback) => callback())
|
||||
processExitStub = sinon.stub(process, 'exit').callsFake(() => {
|
||||
throw new Error(`${chalk.red('process.exit')} should not be called`)
|
||||
})
|
||||
|
||||
await fs.remove(e2eTestOutputPath)
|
||||
await fs.mkdir(e2eTestOutputPath)
|
||||
|
||||
process.env.BABEL_TEST_ROOT = e2eTestOutputPath
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
logSpy?.restore()
|
||||
promptSpy?.restore()
|
||||
processExitStub?.restore()
|
||||
execStub?.restore()
|
||||
})
|
||||
|
||||
function createTempFiles (tempFiles: Record<string, string>) {
|
||||
Object.entries(tempFiles).forEach(([fileName, content]) => {
|
||||
fs.outputFileSync(
|
||||
path.join(e2eTestOutputPath, fileName),
|
||||
content,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function snapshotGeneratedFiles (name: string) {
|
||||
snapshot(
|
||||
`${name} cypress.config.ts`,
|
||||
fs.readFileSync(
|
||||
path.join(e2eTestOutputPath, 'cypress.config.ts'),
|
||||
{ encoding: 'utf-8' },
|
||||
),
|
||||
)
|
||||
|
||||
snapshot(
|
||||
`${name} plugins/index.js`,
|
||||
fs.readFileSync(
|
||||
path.join(e2eTestOutputPath, 'cypress', 'plugins', 'index.js'),
|
||||
{ encoding: 'utf-8' },
|
||||
),
|
||||
)
|
||||
|
||||
const supportFile = fs.readFileSync(
|
||||
path.join(e2eTestOutputPath, 'cypress', 'support', 'component.js'),
|
||||
{ encoding: 'utf-8' },
|
||||
)
|
||||
|
||||
// Comparing empty snapshot errors.
|
||||
if (supportFile.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
snapshot(
|
||||
`${name} support/component.js`,
|
||||
fs.readFileSync(
|
||||
path.join(e2eTestOutputPath, 'cypress', 'support', 'component.js'),
|
||||
{ encoding: 'utf-8' },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
it('determines more presumable configuration to suggest', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/cypress/support/component.js': '',
|
||||
'/cypress/plugins/index.js': 'module.exports = (on, config) => {}',
|
||||
// For next.js user will have babel config, but we want to suggest to use the closest config for the application code
|
||||
'/babel.config.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({ dependencies: { react: '^17.x', next: '^9.2.0' } }),
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'next.js',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
|
||||
const [{ choices }] = (inquirer.prompt as any).args[0][0]
|
||||
|
||||
expect(choices[0]).to.equal('next.js')
|
||||
snapshotGeneratedFiles('injects guessed next.js template')
|
||||
})
|
||||
|
||||
it('automatically suggests to the user which config to use', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/cypress/support/component.js': 'import "./commands.js";',
|
||||
'/cypress/plugins/index.js': 'module.exports = () => {}',
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
react: '^16.10.0',
|
||||
},
|
||||
}),
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'create-react-app',
|
||||
componentFolder: 'cypress/component',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
const [{ choices, message }] = (inquirer.prompt as any).args[0][0]
|
||||
|
||||
expect(choices[0]).to.equal('webpack')
|
||||
expect(message).to.contain(
|
||||
`Press ${chalk.inverse(' Enter ')} to continue with ${chalk.green(
|
||||
'webpack',
|
||||
)} configuration`,
|
||||
)
|
||||
|
||||
snapshotGeneratedFiles('Injected overridden webpack template')
|
||||
})
|
||||
|
||||
it('Asks for preferred bundling tool if can not determine the right one', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({ dependencies: { } }),
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve({
|
||||
framework: 'vue@2',
|
||||
}) as any)
|
||||
.onCall(1)
|
||||
.returns(Promise.resolve({
|
||||
chosenTemplateName: 'webpack',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
|
||||
expect(
|
||||
someOfSpyCallsIncludes(global.console.log, 'We were unable to automatically determine your framework 😿'),
|
||||
).to.be.true
|
||||
})
|
||||
|
||||
it('Asks for framework if more than 1 option was auto detected', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({ dependencies: { react: '*', vue: '^2.4.5' } }),
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve({
|
||||
framework: 'vue@3',
|
||||
}) as any)
|
||||
.onCall(1)
|
||||
.returns(Promise.resolve({
|
||||
chosenTemplateName: 'webpack',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
|
||||
expect(
|
||||
someOfSpyCallsIncludes(global.console.log, `It looks like all these frameworks: ${chalk.yellow('react, vue@2')} are available from this directory.`),
|
||||
).to.be.true
|
||||
})
|
||||
|
||||
it('installs the right adapter', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({ dependencies: { react: '16.4.5' } }),
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve({
|
||||
chosenTemplateName: 'vite',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
expect(execStub).to.be.calledWith('yarn add @cypress/react --dev')
|
||||
})
|
||||
|
||||
it('installs the right adapter for vue 3', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/vite.config.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({ dependencies: { vue: '^3.0.0' } }),
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve({
|
||||
chosenTemplateName: 'vite',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
expect(execStub).to.be.calledWith('yarn add @cypress/vue --dev')
|
||||
})
|
||||
|
||||
it('suggest the right instruction based on user template choice', async () => {
|
||||
createTempFiles({
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
react: '^16.0.0',
|
||||
},
|
||||
}),
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
})
|
||||
|
||||
promptSpy = sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'create-react-app',
|
||||
componentFolder: 'src',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
expect(
|
||||
someOfSpyCallsIncludes(
|
||||
global.console.log,
|
||||
'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/react-scripts',
|
||||
),
|
||||
).to.be.true
|
||||
})
|
||||
|
||||
it('suggests right docs example and cypress.config.ts config based on the `componentFolder` answer', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
react: '^16.0.0',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'create-react-app',
|
||||
componentFolder: 'cypress/component',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
|
||||
const injectedCode = require(path.join(e2eTestOutputPath, 'cypress.config.ts'))
|
||||
|
||||
expect(JSON.stringify(injectedCode.default, null, 2)).to.equal(JSON.stringify(
|
||||
{
|
||||
specPattern: 'cypress/component/**/*.spec.{js,ts,jsx,tsx}',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
))
|
||||
})
|
||||
|
||||
it('Shows help message if cypress files are not created', async () => {
|
||||
createTempFiles({
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
react: '^16.0.0',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'create-react-app',
|
||||
componentFolder: 'cypress/component',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
|
||||
expect(
|
||||
someOfSpyCallsIncludes(
|
||||
global.console.log,
|
||||
'was not updated automatically. Please add the following config manually:',
|
||||
),
|
||||
).to.be.true
|
||||
})
|
||||
|
||||
it(`Doesn't affect injected code if user has custom babel.config.js`, async () => {
|
||||
createTempFiles({
|
||||
'/cypress/plugins/index.js': 'module.exports = (on, config) => {}',
|
||||
'/cypress.config.ts': 'export default {}',
|
||||
'babel.config.js': `module.exports = ${JSON.stringify({
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
],
|
||||
})}`,
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
babel: '*',
|
||||
react: '^16.0.0',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
sinon.stub(inquirer, 'prompt').returns(Promise.resolve({
|
||||
chosenTemplateName: 'create-react-app',
|
||||
componentFolder: 'cypress/component',
|
||||
}) as any)
|
||||
|
||||
await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true })
|
||||
const babelPluginsOutput = await fs.readFile(
|
||||
path.join(e2eTestOutputPath, 'cypress', 'plugins', 'index.js'),
|
||||
'utf-8',
|
||||
)
|
||||
|
||||
expect(babelPluginsOutput).not.to.contain('use strict')
|
||||
expect(babelPluginsOutput).to.contain('module.exports = (on, config) => {')
|
||||
})
|
||||
})
|
||||
@@ -1,194 +0,0 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import highlight from 'cli-highlight'
|
||||
import { Template } from './templates/Template'
|
||||
import { guessTemplate } from './templates/guessTemplate'
|
||||
import { installFrameworkAdapter } from './installFrameworkAdapter'
|
||||
import { injectPluginsCode, getPluginsSourceExample } from './babel/babelTransform'
|
||||
import { installDependency } from '../utils'
|
||||
import { insertValuesInConfigFile } from './config-file-updater/configFileUpdater'
|
||||
|
||||
async function injectOrShowConfigCode (injectFn: () => Promise<boolean>, {
|
||||
code,
|
||||
filePath,
|
||||
fallbackFileMessage,
|
||||
language,
|
||||
}: {
|
||||
code: string
|
||||
filePath: string
|
||||
language: string
|
||||
fallbackFileMessage: string
|
||||
}) {
|
||||
const fileExists = fs.existsSync(filePath)
|
||||
const readableFilePath = fileExists ? path.relative(process.cwd(), filePath) : fallbackFileMessage
|
||||
|
||||
const printCode = () => {
|
||||
console.log()
|
||||
console.log(highlight(code, { language }))
|
||||
console.log()
|
||||
}
|
||||
|
||||
const printSuccess = () => {
|
||||
console.log(`✅ ${chalk.bold.green(readableFilePath)} was updated with the following config:`)
|
||||
printCode()
|
||||
}
|
||||
|
||||
const printFailure = () => {
|
||||
console.log(`❌ ${chalk.bold.red(readableFilePath)} was not updated automatically. Please add the following config manually: `)
|
||||
printCode()
|
||||
}
|
||||
|
||||
if (!fileExists) {
|
||||
printFailure()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// something get completely wrong when using babel or something. Print error message.
|
||||
const injected = await injectFn().catch(() => false)
|
||||
|
||||
injected ? printSuccess() : printFailure()
|
||||
}
|
||||
|
||||
async function injectAndShowCypressConfig (
|
||||
cypressJsonPath: string,
|
||||
componentFolder: string,
|
||||
) {
|
||||
const configToInject = {
|
||||
specPattern: `${componentFolder}/**/*.spec.{js,ts,jsx,tsx}`,
|
||||
}
|
||||
|
||||
await injectOrShowConfigCode(() => insertValuesInConfigFile(cypressJsonPath, configToInject), {
|
||||
code: JSON.stringify(configToInject, null, 2),
|
||||
language: 'js',
|
||||
filePath: cypressJsonPath,
|
||||
fallbackFileMessage: 'cypress.json config file',
|
||||
})
|
||||
}
|
||||
|
||||
async function injectAndShowPluginConfig<T> (template: Template<T>, {
|
||||
templatePayload,
|
||||
pluginsFilePath,
|
||||
cypressProjectRoot,
|
||||
}: {
|
||||
templatePayload: T | null
|
||||
pluginsFilePath: string
|
||||
cypressProjectRoot: string
|
||||
}) {
|
||||
const ast = template.getPluginsCodeAst(templatePayload, { cypressProjectRoot })
|
||||
|
||||
await injectOrShowConfigCode(() => injectPluginsCode(pluginsFilePath, ast), {
|
||||
code: await getPluginsSourceExample(ast),
|
||||
language: 'js',
|
||||
filePath: pluginsFilePath,
|
||||
fallbackFileMessage: 'plugins file (https://on.cypress.io/plugins-file)',
|
||||
})
|
||||
}
|
||||
|
||||
type InitComponentTestingOptions = {
|
||||
config: Record<string, string>
|
||||
cypressConfigPath: string
|
||||
useYarn: boolean
|
||||
}
|
||||
|
||||
export async function initComponentTesting<T> ({ config, useYarn, cypressConfigPath }: InitComponentTestingOptions) {
|
||||
const cypressProjectRoot = path.resolve(cypressConfigPath, '..')
|
||||
|
||||
const framework = await installFrameworkAdapter(cypressProjectRoot, { useYarn })
|
||||
const {
|
||||
possibleTemplates,
|
||||
defaultTemplate,
|
||||
defaultTemplateName,
|
||||
templatePayload,
|
||||
} = await guessTemplate<T>(framework, cypressProjectRoot)
|
||||
|
||||
const pluginsFilePath = path.resolve(
|
||||
cypressProjectRoot,
|
||||
config.pluginsFile ?? './cypress/plugins/index.js',
|
||||
)
|
||||
|
||||
const templateChoices = Object.keys(possibleTemplates).sort((key) => {
|
||||
return key === defaultTemplateName ? -1 : 0
|
||||
})
|
||||
|
||||
const {
|
||||
chosenTemplateName,
|
||||
componentFolder,
|
||||
}: Record<string, string> = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'chosenTemplateName',
|
||||
choices: templateChoices,
|
||||
default: defaultTemplate ? 0 : undefined,
|
||||
message: defaultTemplate?.message
|
||||
? `${defaultTemplate?.message}\n\n Press ${chalk.inverse(
|
||||
' Enter ',
|
||||
)} to continue with ${chalk.green(
|
||||
defaultTemplateName,
|
||||
)} configuration or select another template from the list:`
|
||||
: 'We were not able to automatically determine which framework or bundling tool you are using. Please choose one from the list:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'componentFolder',
|
||||
filter: (input) => input.trim(),
|
||||
validate: (input) => {
|
||||
return input === '' || !/^[a-zA-Z].*/.test(input)
|
||||
? `Directory "${input}" is invalid`
|
||||
: true
|
||||
},
|
||||
message: 'Which folder would you like to use for your component tests?',
|
||||
default: (answers: { chosenTemplateName: keyof typeof possibleTemplates }) => {
|
||||
return possibleTemplates[answers.chosenTemplateName].recommendedComponentFolder
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const chosenTemplate = possibleTemplates[chosenTemplateName] as Template<T>
|
||||
|
||||
console.log()
|
||||
console.log(`Installing required dependencies`)
|
||||
console.log()
|
||||
|
||||
for (const dependency of chosenTemplate.dependencies) {
|
||||
await installDependency(dependency, { useYarn })
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log(`Let's setup everything for component testing with ${chalk.cyan(chosenTemplateName)}:`)
|
||||
console.log()
|
||||
|
||||
await injectAndShowCypressConfig(cypressConfigPath, componentFolder)
|
||||
await injectAndShowPluginConfig(chosenTemplate, {
|
||||
templatePayload,
|
||||
pluginsFilePath,
|
||||
cypressProjectRoot,
|
||||
})
|
||||
|
||||
if (chosenTemplate.printHelper) {
|
||||
chosenTemplate.printHelper()
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Find examples of component tests for ${chalk.green(
|
||||
chosenTemplateName,
|
||||
)} in ${chalk.underline(chosenTemplate.getExampleUrl({ componentFolder }))}.`,
|
||||
)
|
||||
|
||||
if (framework === 'react') {
|
||||
console.log()
|
||||
|
||||
console.log(
|
||||
`Docs for different recipes of bundling tools: ${chalk.bold.underline(
|
||||
'https://github.com/cypress-io/cypress/tree/develop/npm/react/docs/recipes.md',
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
|
||||
// render delimiter
|
||||
console.log()
|
||||
console.log(new Array(process.stdout.columns).fill('═').join(''))
|
||||
console.log()
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import { scanFSForAvailableDependency } from '../findPackageJson'
|
||||
import { installDependency } from '../utils'
|
||||
|
||||
async function guessOrAskForFramework (cwd: string): Promise<'react' | 'vue@2' | 'vue@3'> {
|
||||
// please sort this alphabetically
|
||||
const frameworks = {
|
||||
react: () => scanFSForAvailableDependency(cwd, { react: '*', 'react-dom': '*' }),
|
||||
'vue@2': () => scanFSForAvailableDependency(cwd, { vue: '2.x' }),
|
||||
'vue@3': () => scanFSForAvailableDependency(cwd, { vue: '3.x' }),
|
||||
}
|
||||
|
||||
const guesses = Object.keys(frameworks).filter((framework) => {
|
||||
return frameworks[framework as keyof typeof frameworks]()
|
||||
}) as Array<'react' | 'vue@2' | 'vue@3'>
|
||||
|
||||
// found 1 precise guess. Continue
|
||||
if (guesses.length === 1) {
|
||||
const framework = guesses[0]
|
||||
|
||||
console.log(`\nThis project is using ${chalk.bold.cyan(framework)}. Let's install the right adapter:`)
|
||||
|
||||
return framework
|
||||
}
|
||||
|
||||
if (guesses.length === 0) {
|
||||
console.log(`We were unable to automatically determine your framework 😿. ${chalk.grey('Make sure to run this command from the directory where your components located in order to make smart detection works. Or continue with manual setup:')}`)
|
||||
}
|
||||
|
||||
if (guesses.length > 0) {
|
||||
console.log(`It looks like all these frameworks: ${chalk.yellow(guesses.join(', '))} are available from this directory. ${chalk.grey('Make sure to run this command from the directory where your components located in order to make smart detection works. Or continue with manual setup:')}`)
|
||||
}
|
||||
|
||||
const { framework } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'framework',
|
||||
choices: Object.keys(frameworks),
|
||||
message: `Which framework do you use?`,
|
||||
},
|
||||
])
|
||||
|
||||
return framework
|
||||
}
|
||||
|
||||
type InstallAdapterOptions = {
|
||||
useYarn: boolean
|
||||
}
|
||||
|
||||
const frameworkDependencies = {
|
||||
react: '@cypress/react',
|
||||
'vue@2': '@cypress/vue2',
|
||||
'vue@3': '@cypress/vue',
|
||||
}
|
||||
|
||||
export async function installFrameworkAdapter (cwd: string, options: InstallAdapterOptions) {
|
||||
const framework = await guessOrAskForFramework(cwd)
|
||||
|
||||
await installDependency(frameworkDependencies[framework], options)
|
||||
|
||||
return framework
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { PluginsConfigAst } from '../babel/babelTransform'
|
||||
|
||||
export interface Template<T = unknown> {
|
||||
message: string
|
||||
getExampleUrl: ({ componentFolder }: { componentFolder: string }) => string
|
||||
recommendedComponentFolder: string
|
||||
test(rootPath: string): { success: boolean, payload?: T }
|
||||
getPluginsCodeAst: (
|
||||
payload: T | null,
|
||||
options: { cypressProjectRoot: string },
|
||||
) => PluginsConfigAst
|
||||
dependencies: string[]
|
||||
printHelper?: () => void
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Template } from '../Template'
|
||||
import { ViteTemplate } from './vite'
|
||||
|
||||
export const frameworkAgnosticTemplates: Record<string, Template<any>> = {
|
||||
vite: ViteTemplate,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { ViteTemplate } from './vite'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
|
||||
describe('vue: vite template', () => {
|
||||
it('correctly generates plugins config', () => snapshotPluginsAstCode(ViteTemplate))
|
||||
})
|
||||
@@ -1,27 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import { scanFSForAvailableDependency } from '../../../findPackageJson'
|
||||
import { Template } from '../Template'
|
||||
|
||||
export const ViteTemplate: Template = {
|
||||
message:
|
||||
'It looks like you are using vitejs to run and build an application.',
|
||||
getExampleUrl: () => 'https://github.com/cypress-io/cypress/tree/develop/npm/vue/examples/vite',
|
||||
recommendedComponentFolder: 'src',
|
||||
dependencies: ['@cypress/vite-dev-server'],
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast(
|
||||
'const { startDevServer } = require("@cypress/vite-dev-server");',
|
||||
),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
'on("dev-server:start", async (options) => startDevServer({ options }))',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: (root) => {
|
||||
return {
|
||||
success: scanFSForAvailableDependency(root, { vite: '*' }),
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Template } from './Template'
|
||||
import { reactTemplates } from './react'
|
||||
import { vueTemplates } from './vue'
|
||||
import { frameworkAgnosticTemplates } from './_shared'
|
||||
|
||||
const frameworkSpecificTemplates = {
|
||||
react: reactTemplates,
|
||||
'vue@2': vueTemplates,
|
||||
'vue@3': vueTemplates,
|
||||
}
|
||||
|
||||
export async function guessTemplate<T> (framework: keyof typeof frameworkSpecificTemplates, cwd: string) {
|
||||
const templates = { ...frameworkAgnosticTemplates, ...frameworkSpecificTemplates[framework] }
|
||||
|
||||
for (const [name, template] of Object.entries(templates)) {
|
||||
const typedTemplate = template as Template<T>
|
||||
const { success, payload } = typedTemplate.test(cwd)
|
||||
|
||||
if (success) {
|
||||
return {
|
||||
defaultTemplate: typedTemplate,
|
||||
defaultTemplateName: name,
|
||||
templatePayload: payload ?? null,
|
||||
possibleTemplates: templates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
templatePayload: null,
|
||||
defaultTemplate: null,
|
||||
defaultTemplateName: null,
|
||||
possibleTemplates: templates,
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { BabelTemplate } from './babel'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
|
||||
describe('babel installation template', () => {
|
||||
beforeEach(mockFs.restore)
|
||||
|
||||
it('resolves babel.config.json', () => {
|
||||
mockFs({
|
||||
'/babel.config.json': JSON.stringify({
|
||||
presets: [],
|
||||
plugins: [],
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = BabelTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('resolves babel.config.js', () => {
|
||||
mockFs({
|
||||
'/project/babel.config.js':
|
||||
'module.exports = { presets: [], plugins: [] };',
|
||||
'/project/index/package.json': 'dev/null',
|
||||
})
|
||||
|
||||
const { success } = BabelTemplate.test('/project/index')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('resolves babel config from the deep folder', () => {
|
||||
mockFs({
|
||||
'/some/.babelrc': JSON.stringify({
|
||||
presets: [],
|
||||
plugins: [],
|
||||
}),
|
||||
'/some/deep/folder/text.txt': '1',
|
||||
})
|
||||
|
||||
const { success } = BabelTemplate.test('/some/deep/folder')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('fails if no babel config found', () => {
|
||||
mockFs({
|
||||
'/some.txt': '1',
|
||||
})
|
||||
|
||||
const { success } = BabelTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(false)
|
||||
})
|
||||
|
||||
it('resolves babel.config from package.json', () => {
|
||||
mockFs({
|
||||
'/package.json': JSON.stringify({
|
||||
babel: {
|
||||
presets: [],
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = BabelTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('correctly generates plugins config', () => snapshotPluginsAstCode(BabelTemplate))
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import chalk from 'chalk'
|
||||
import findUp from 'find-up'
|
||||
import * as babel from '@babel/core'
|
||||
import { Template } from '../Template'
|
||||
import { createFindPackageJsonIterator } from '../../../findPackageJson'
|
||||
|
||||
export const BabelTemplate: Template = {
|
||||
message: `It looks like you have babel config defined. We can use it to transpile your components for testing.\n ${chalk.red(
|
||||
'>>',
|
||||
)} This is not a replacement for bundling tool. We will use ${chalk.red(
|
||||
'webpack',
|
||||
)} to bundle the components for testing.`,
|
||||
recommendedComponentFolder: 'cypress/component',
|
||||
dependencies: ['webpack', '@cypress/webpack-dev-server'],
|
||||
getExampleUrl: () => 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/babel',
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/babel\')'),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
'injectDevServer(on, config)',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: (cwd) => {
|
||||
const babelConfig = findUp.sync(
|
||||
['babel.config.js', 'babel.config.json', '.babelrc', '.babelrc.json'],
|
||||
{ type: 'file', cwd },
|
||||
)
|
||||
|
||||
if (babelConfig) {
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
// babel config can also be declared in package.json with `babel` key https://babeljs.io/docs/en/configuration#packagejson
|
||||
const packageJsonIterator = createFindPackageJsonIterator(cwd)
|
||||
|
||||
return packageJsonIterator.map(({ babel }) => {
|
||||
return {
|
||||
success: Boolean(babel),
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Template } from '../Template'
|
||||
import { NextTemplate } from './next'
|
||||
import { WebpackTemplate } from './reactWebpackFile'
|
||||
import { ReactScriptsTemplate } from './react-scripts'
|
||||
import { BabelTemplate } from './babel'
|
||||
import { WebpackOptions } from './webpack-options'
|
||||
|
||||
export const reactTemplates: Record<string, Template<any>> = {
|
||||
'create-react-app': ReactScriptsTemplate,
|
||||
'next.js': NextTemplate,
|
||||
webpack: WebpackTemplate,
|
||||
babel: BabelTemplate,
|
||||
'default (webpack options)': WebpackOptions,
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import sinon, { SinonSpy } from 'sinon'
|
||||
import { expect, use } from 'chai'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { NextTemplate } from './next'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
|
||||
use(sinonChai)
|
||||
|
||||
describe('next.js install template', () => {
|
||||
let warnSpy: SinonSpy | null = null
|
||||
|
||||
beforeEach(() => {
|
||||
warnSpy = sinon.spy(global.console, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
warnSpy?.restore()
|
||||
})
|
||||
|
||||
it('finds the closest package.json and checks that next is declared as dependency', () => {
|
||||
mockFs({
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '^9.2.3',
|
||||
},
|
||||
scripts: {
|
||||
build: 'next',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = NextTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('works if next is declared in the devDependencies as well', () => {
|
||||
mockFs({
|
||||
'./package.json': JSON.stringify({
|
||||
devDependencies: {
|
||||
next: '^9.2.3',
|
||||
},
|
||||
scripts: {
|
||||
build: 'next',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = NextTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('warns and fails if version is not supported', () => {
|
||||
mockFs({
|
||||
'./package.json': JSON.stringify({
|
||||
devDependencies: {
|
||||
next: '^8.2.3',
|
||||
},
|
||||
scripts: {
|
||||
build: 'next',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = NextTemplate.test('i/am/in/some/deep/folder')
|
||||
|
||||
console.log(global.console.warn)
|
||||
expect(success).to.equal(false)
|
||||
|
||||
expect(global.console.warn).to.be.called
|
||||
})
|
||||
|
||||
it('correctly generates plugins config', () => snapshotPluginsAstCode(NextTemplate))
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import { createFindPackageJsonIterator } from '../../../findPackageJson'
|
||||
import { Template } from '../Template'
|
||||
import { validateSemverVersion } from '../../../utils'
|
||||
import { MIN_SUPPORTED_VERSION } from '../../versions'
|
||||
|
||||
export const NextTemplate: Template = {
|
||||
message: 'It looks like you are using next.js.',
|
||||
getExampleUrl: () => {
|
||||
return 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/nextjs'
|
||||
},
|
||||
recommendedComponentFolder: 'cypress/component',
|
||||
dependencies: ['@cypress/webpack-dev-server'],
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/next\')'),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
'injectDevServer(on, config)',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: (cwd) => {
|
||||
const packageJsonIterator = createFindPackageJsonIterator(cwd)
|
||||
|
||||
return packageJsonIterator.map(({ dependencies, devDependencies }, path) => {
|
||||
if (!dependencies && !devDependencies) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const allDeps = {
|
||||
...(devDependencies || {}),
|
||||
...(dependencies || {}),
|
||||
} as Record<string, string>
|
||||
|
||||
const nextVersion = allDeps['next']
|
||||
|
||||
if (!nextVersion) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
if (
|
||||
!validateSemverVersion(
|
||||
nextVersion,
|
||||
MIN_SUPPORTED_VERSION['next'],
|
||||
'next.js',
|
||||
)
|
||||
) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import sinon, { SinonSpy } from 'sinon'
|
||||
import { expect, use } from 'chai'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { ReactScriptsTemplate } from './react-scripts'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
|
||||
use(sinonChai)
|
||||
|
||||
describe('create-react-app install template', () => {
|
||||
let warnSpy: SinonSpy | null = null
|
||||
|
||||
beforeEach(() => {
|
||||
warnSpy = sinon.spy(global.console, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
warnSpy?.restore()
|
||||
})
|
||||
|
||||
it('finds the closest package.json and checks that react-scripts is declared as dependency', () => {
|
||||
mockFs({
|
||||
'/package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'react-scripts': '^3.2.3',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = ReactScriptsTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('works if react-scripts is declared in the devDependencies as well', () => {
|
||||
mockFs({
|
||||
'./package.json': JSON.stringify({
|
||||
devDependencies: {
|
||||
'react-scripts': '^3.2.3',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = ReactScriptsTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('warns and fails if version is not supported', () => {
|
||||
mockFs({
|
||||
'./package.json': JSON.stringify({
|
||||
devDependencies: {
|
||||
'react-scripts': '^2.2.3',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = ReactScriptsTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(false)
|
||||
expect(global.console.warn).to.be.called
|
||||
})
|
||||
|
||||
it('correctly generates plugins config', () => snapshotPluginsAstCode(ReactScriptsTemplate))
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
import chalk from 'chalk'
|
||||
import { createFindPackageJsonIterator } from '../../../findPackageJson'
|
||||
import { Template } from '../Template'
|
||||
import { validateSemverVersion } from '../../../utils'
|
||||
import { MIN_SUPPORTED_VERSION } from '../../versions'
|
||||
import * as babel from '@babel/core'
|
||||
|
||||
export const ReactScriptsTemplate: Template = {
|
||||
recommendedComponentFolder: 'src',
|
||||
message: 'It looks like you are using create-react-app.',
|
||||
dependencies: ['@cypress/webpack-dev-server'],
|
||||
getExampleUrl: ({ componentFolder }) => {
|
||||
return componentFolder === 'src'
|
||||
? 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/react-scripts'
|
||||
: 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/react-scripts-folder'
|
||||
},
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/react-scripts\')'),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
'injectDevServer(on, config)',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: () => {
|
||||
// TODO also determine ejected create react app
|
||||
const packageJsonIterator = createFindPackageJsonIterator(process.cwd())
|
||||
|
||||
return packageJsonIterator.map(({ dependencies, devDependencies }) => {
|
||||
if (dependencies || devDependencies) {
|
||||
const allDeps = { ...devDependencies, ...dependencies } || {}
|
||||
|
||||
if (!allDeps['react-scripts']) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
if (
|
||||
!validateSemverVersion(
|
||||
allDeps['react-scripts'],
|
||||
MIN_SUPPORTED_VERSION['react-scripts'],
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`It looks like you are using ${chalk.green(
|
||||
'create-react-app',
|
||||
)}, but we support only projects with version ${chalk.bold(
|
||||
MIN_SUPPORTED_VERSION['react-scripts'],
|
||||
)} of react-scripts.`,
|
||||
)
|
||||
|
||||
// yey found the template
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
return { success: false }
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
import { WebpackTemplate } from './reactWebpackFile'
|
||||
|
||||
describe('webpack-file install template', () => {
|
||||
afterEach(mockFs.restore)
|
||||
|
||||
it('resolves webpack.config.js', () => {
|
||||
mockFs({
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
})
|
||||
|
||||
const { success, payload } = WebpackTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/webpack.config.js')
|
||||
})
|
||||
|
||||
it('finds the closest package.json and tries to fetch webpack config path from scrips', () => {
|
||||
mockFs({
|
||||
'/configs/webpack.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({
|
||||
scripts: {
|
||||
build: 'webpack --config configs/webpack.js',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success, payload } = WebpackTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/configs/webpack.js')
|
||||
})
|
||||
|
||||
it('looks for package.json in the upper folder', () => {
|
||||
mockFs({
|
||||
'/i/am/in/some/deep/folder/withFile': 'test',
|
||||
'/somewhere/configs/webpack.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({
|
||||
scripts: {
|
||||
build: 'webpack --config somewhere/configs/webpack.js',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success, payload } = WebpackTemplate.test(
|
||||
'i/am/in/some/deep/folder',
|
||||
)
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/somewhere/configs/webpack.js')
|
||||
})
|
||||
|
||||
it('returns success:false if cannot find webpack config', () => {
|
||||
mockFs({
|
||||
'/a.js': '1',
|
||||
'/b.js': '2',
|
||||
})
|
||||
|
||||
const { success, payload } = WebpackTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(false)
|
||||
expect(payload).to.equal(undefined)
|
||||
})
|
||||
|
||||
it('correctly generates plugins config when webpack config path is missing', () => {
|
||||
snapshotPluginsAstCode(WebpackTemplate)
|
||||
})
|
||||
|
||||
it('correctly generates plugins config when webpack config path is provided', () => {
|
||||
snapshotPluginsAstCode(WebpackTemplate, { webpackConfigPath: '/config/webpack.config.js' })
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import path from 'path'
|
||||
import { Template } from '../Template'
|
||||
import { findWebpackConfig } from '../templateUtils'
|
||||
|
||||
export const WebpackTemplate: Template<{ webpackConfigPath: string }> = {
|
||||
message:
|
||||
'It looks like you have custom `webpack.config.js`. We can use it to bundle the components for testing.',
|
||||
getExampleUrl: () => {
|
||||
return 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/webpack-file'
|
||||
},
|
||||
recommendedComponentFolder: 'cypress/component',
|
||||
dependencies: ['@cypress/webpack-dev-server'],
|
||||
getPluginsCodeAst: (payload, { cypressProjectRoot }) => {
|
||||
const includeWarnComment = !payload
|
||||
const webpackConfigPath = payload
|
||||
? path.relative(cypressProjectRoot, payload.webpackConfigPath)
|
||||
: './webpack.config.js'
|
||||
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast('const injectDevServer = require("@cypress/react/plugins/load-webpack")'),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
'injectDevServer(on, config, {',
|
||||
includeWarnComment
|
||||
? ' // TODO replace with valid webpack config path'
|
||||
: '',
|
||||
` webpackFilename: '${webpackConfigPath}'`,
|
||||
'})',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: (root) => {
|
||||
const webpackConfigPath = findWebpackConfig(root)
|
||||
|
||||
return webpackConfigPath ? {
|
||||
success: true,
|
||||
payload: { webpackConfigPath },
|
||||
} : {
|
||||
success: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/** @type import("webpack").Configuration */
|
||||
const webpackConfig = {
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
||||
},
|
||||
mode: 'development',
|
||||
devtool: false,
|
||||
output: {
|
||||
publicPath: '/',
|
||||
chunkFilename: '[name].bundle.js',
|
||||
},
|
||||
// TODO: update with valid configuration for your components
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
loader: 'babel-loader',
|
||||
options: { cacheDirectory: path.resolve(__dirname, '.babel-cache') },
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))
|
||||
@@ -1,35 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as babel from '@babel/core'
|
||||
import chalk from 'chalk'
|
||||
import { Template } from '../Template'
|
||||
|
||||
export const WebpackOptions: Template = {
|
||||
// this should never show ideally
|
||||
message: `Unable to detect where webpack options are.`,
|
||||
getExampleUrl: () => {
|
||||
return 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/webpack-options'
|
||||
},
|
||||
test: () => ({ success: false }),
|
||||
recommendedComponentFolder: 'src',
|
||||
dependencies: ['webpack', '@cypress/webpack-dev-server'],
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
RequireAst: babel.template.ast([
|
||||
'const path = require("path")',
|
||||
'const { startDevServer } = require("@cypress/webpack-dev-Server")',
|
||||
].join('\n')),
|
||||
IfComponentTestingPluginsAst: babel.template.ast(
|
||||
fs.readFileSync(path.resolve(__dirname, 'webpack-options-module-exports.template.js'), { encoding: 'utf-8' }),
|
||||
{ preserveComments: true },
|
||||
),
|
||||
}
|
||||
},
|
||||
printHelper: () => {
|
||||
console.log(
|
||||
`${chalk.inverse('Important:')} this configuration is using ${chalk.blue(
|
||||
'new webpack configuration',
|
||||
)} to bundle components. If you are using some framework (e.g. next) or bundling tool (e.g. rollup/vite) consider using them to bundle component specs for cypress. \n`,
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { WebpackOptions } from './webpack-options'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
|
||||
describe('webpack-options template', () => {
|
||||
it('correctly generates plugins config', () => snapshotPluginsAstCode(WebpackOptions))
|
||||
})
|
||||
@@ -1,53 +0,0 @@
|
||||
import findUp from 'find-up'
|
||||
import path from 'path'
|
||||
import { createFindPackageJsonIterator } from '../../findPackageJson'
|
||||
|
||||
export function extractWebpackConfigPathFromScript (script: string) {
|
||||
if (script.includes('webpack ') || script.includes('webpack-dev-server ')) {
|
||||
const webpackCliArgs = script.split(' ').map((part) => part.trim())
|
||||
const configArgIndex = webpackCliArgs.findIndex((arg) => arg === '--config')
|
||||
|
||||
return configArgIndex === -1 ? null : webpackCliArgs[configArgIndex + 1]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function findWebpackConfig (root: string) {
|
||||
const webpackConfigPath = findUp.sync('webpack.config.js', { cwd: root })
|
||||
|
||||
if (webpackConfigPath) {
|
||||
return webpackConfigPath
|
||||
}
|
||||
|
||||
const packageJsonIterator = createFindPackageJsonIterator(root)
|
||||
|
||||
const { success, payload } = packageJsonIterator.map(({ scripts }, packageJsonPath) => {
|
||||
if (!scripts) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
for (const script of Object.values(scripts)) {
|
||||
const webpackConfigRelativePath = extractWebpackConfigPathFromScript(
|
||||
script,
|
||||
)
|
||||
|
||||
if (webpackConfigRelativePath) {
|
||||
const directoryRoot = path.resolve(packageJsonPath, '..')
|
||||
const webpackConfigPath = path.resolve(
|
||||
directoryRoot,
|
||||
webpackConfigRelativePath,
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
payload: { webpackConfigPath },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false }
|
||||
})
|
||||
|
||||
return success ? payload?.webpackConfigPath : null
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Template } from '../Template'
|
||||
import { VueCliTemplate } from './vueCli'
|
||||
import { VueWebpackTemplate } from './vueWebpackFile'
|
||||
|
||||
export const vueTemplates: Record<string, Template<any>> = {
|
||||
webpack: VueWebpackTemplate,
|
||||
'vue-cli': VueCliTemplate,
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
import { VueCliTemplate } from './vueCli'
|
||||
|
||||
describe('vue webpack-file install template', () => {
|
||||
beforeEach(mockFs.restore)
|
||||
|
||||
it('resolves webpack.config.js', () => {
|
||||
mockFs({
|
||||
'/package.json': JSON.stringify({
|
||||
'devDependencies': {
|
||||
'@vue/cli-plugin-babel': '~4.5.0',
|
||||
'@vue/cli-plugin-eslint': '~4.5.0',
|
||||
'@vue/cli-plugin-router': '~4.5.0',
|
||||
'@vue/cli-service': '~4.5.0',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = VueCliTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(true)
|
||||
})
|
||||
|
||||
it('returns success:false if vue-cli-service is not installed', () => {
|
||||
mockFs({
|
||||
'/package.json': JSON.stringify({
|
||||
'devDependencies': {
|
||||
'webpack': '*',
|
||||
'vue': '2.x',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success } = VueCliTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(false)
|
||||
})
|
||||
|
||||
it('correctly generates plugins for vue-cli-service', () => {
|
||||
snapshotPluginsAstCode(VueCliTemplate)
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import { scanFSForAvailableDependency } from '../../../findPackageJson'
|
||||
import { Template } from '../Template'
|
||||
|
||||
export const VueCliTemplate: Template = {
|
||||
message:
|
||||
'It looks like you are using vue-cli-service to run and build an application.',
|
||||
getExampleUrl: () => 'https://github.com/cypress-io/cypress/tree/develop/npm/vue/examples/cli',
|
||||
recommendedComponentFolder: 'src',
|
||||
dependencies: ['@cypress/webpack-dev-server'],
|
||||
getPluginsCodeAst: () => {
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast([
|
||||
'const { startDevServer } = require("@cypress/webpack-dev-server")',
|
||||
`const webpackConfig = require("@vue/cli-service/webpack.config.js")`,
|
||||
].join('\n')),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
`on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))`,
|
||||
].join('\n'), { preserveComments: true }),
|
||||
}
|
||||
},
|
||||
test: (root) => {
|
||||
const hasVueCliService = scanFSForAvailableDependency(root, { '@vue/cli-service': '>=4' })
|
||||
|
||||
return {
|
||||
success: hasVueCliService,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import mockFs from 'mock-fs'
|
||||
import { snapshotPluginsAstCode } from '../../../test-utils'
|
||||
import { VueWebpackTemplate } from './vueWebpackFile'
|
||||
|
||||
describe('vue webpack-file install template', () => {
|
||||
beforeEach(mockFs.restore)
|
||||
|
||||
it('resolves webpack.config.js', () => {
|
||||
mockFs({
|
||||
'/webpack.config.js': 'module.exports = { }',
|
||||
})
|
||||
|
||||
const { success, payload } = VueWebpackTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/webpack.config.js')
|
||||
})
|
||||
|
||||
it('finds the closest package.json and tries to fetch webpack config path from scrips', () => {
|
||||
mockFs({
|
||||
'/configs/webpack.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({
|
||||
scripts: {
|
||||
build: 'webpack --config configs/webpack.js',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success, payload } = VueWebpackTemplate.test(process.cwd())
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/configs/webpack.js')
|
||||
})
|
||||
|
||||
it('looks for package.json in the upper folder', () => {
|
||||
mockFs({
|
||||
'/some/deep/folder/withFile': 'test',
|
||||
'/somewhere/configs/webpack.js': 'module.exports = { }',
|
||||
'/package.json': JSON.stringify({
|
||||
scripts: {
|
||||
build: 'webpack --config somewhere/configs/webpack.js',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const { success, payload } = VueWebpackTemplate.test(
|
||||
'/some/deep/folder',
|
||||
)
|
||||
|
||||
expect(success).to.equal(true)
|
||||
expect(payload?.webpackConfigPath).to.equal('/somewhere/configs/webpack.js')
|
||||
})
|
||||
|
||||
it('returns success:false if cannot find webpack config', () => {
|
||||
mockFs({
|
||||
'/a.js': '1',
|
||||
'/b.js': '2',
|
||||
})
|
||||
|
||||
const { success, payload } = VueWebpackTemplate.test('/')
|
||||
|
||||
expect(success).to.equal(false)
|
||||
expect(payload).to.equal(undefined)
|
||||
})
|
||||
|
||||
it('correctly generates plugins config when webpack config path is missing', () => {
|
||||
snapshotPluginsAstCode(VueWebpackTemplate)
|
||||
})
|
||||
|
||||
it('correctly generates plugins config when webpack config path is provided', () => {
|
||||
snapshotPluginsAstCode(VueWebpackTemplate, { webpackConfigPath: '/build/webpack.config.js' })
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import path from 'path'
|
||||
import { Template } from '../Template'
|
||||
import { findWebpackConfig } from '../templateUtils'
|
||||
|
||||
export const VueWebpackTemplate: Template<{ webpackConfigPath: string }> = {
|
||||
message:
|
||||
'It looks like you have custom `webpack.config.js`. We can use it to bundle the components for testing.',
|
||||
getExampleUrl: () => 'https://github.com/cypress-io/cypress/tree/develop/npm/vue/examples/cli',
|
||||
recommendedComponentFolder: 'cypress/component',
|
||||
dependencies: ['@cypress/webpack-dev-server'],
|
||||
getPluginsCodeAst: (payload, { cypressProjectRoot }) => {
|
||||
const includeWarnComment = !payload
|
||||
const webpackConfigPath = payload
|
||||
? path.relative(cypressProjectRoot, payload.webpackConfigPath)
|
||||
: './webpack.config.js'
|
||||
|
||||
return {
|
||||
requiresReturnConfig: true,
|
||||
RequireAst: babel.template.ast([
|
||||
'const { startDevServer } = require("@cypress/webpack-dev-server")',
|
||||
|
||||
`const webpackConfig = require("${webpackConfigPath}")`,
|
||||
includeWarnComment
|
||||
? '// TODO replace with valid webpack config path'
|
||||
: '',
|
||||
].join('\n'), { preserveComments: true }),
|
||||
IfComponentTestingPluginsAst: babel.template.ast([
|
||||
`on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))`,
|
||||
].join('\n')),
|
||||
}
|
||||
},
|
||||
test: (root) => {
|
||||
const webpackConfigPath = findWebpackConfig(root)
|
||||
|
||||
return webpackConfigPath ? {
|
||||
success: true,
|
||||
payload: { webpackConfigPath },
|
||||
} : {
|
||||
success: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export const MIN_SUPPORTED_VERSION = {
|
||||
'react-scripts': '^=3.x || ^=4.x',
|
||||
next: '^=9.x || ^=10.x',
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import findUp from 'find-up'
|
||||
import { validateSemverVersion } from './utils'
|
||||
|
||||
type PackageJsonLike = {
|
||||
name?: string
|
||||
scripts?: Record<string, string>
|
||||
dependencies?: Record<string, string>
|
||||
devDependencies?: Record<string, string>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type FindPackageJsonResult =
|
||||
| {
|
||||
packageData: PackageJsonLike
|
||||
filename: string
|
||||
done: false
|
||||
}
|
||||
| {
|
||||
packageData: undefined
|
||||
filename: undefined
|
||||
done: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parsed package.json that we find in a parent folder.
|
||||
*
|
||||
* @returns {Object} Value, filename and indication if the iteration is done.
|
||||
*/
|
||||
export function createFindPackageJsonIterator (rootPath = process.cwd()) {
|
||||
function scanForPackageJson (cwd: string): FindPackageJsonResult {
|
||||
const packageJsonPath = findUp.sync('package.json', { cwd })
|
||||
|
||||
if (!packageJsonPath) {
|
||||
return {
|
||||
packageData: undefined,
|
||||
filename: undefined,
|
||||
done: true,
|
||||
}
|
||||
}
|
||||
|
||||
const packageData = JSON.parse(
|
||||
fs.readFileSync(packageJsonPath, {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
packageData,
|
||||
filename: packageJsonPath,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
map: <TPayload>(
|
||||
cb: (
|
||||
data: PackageJsonLike,
|
||||
packageJsonPath: string,
|
||||
) => { success: boolean, payload?: TPayload },
|
||||
) => {
|
||||
let stepPathToScan = rootPath
|
||||
|
||||
// eslint-disable-next-line
|
||||
while (true) {
|
||||
const result = scanForPackageJson(stepPathToScan)
|
||||
|
||||
if (result.done) {
|
||||
// didn't find the package.json
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
if (result.packageData) {
|
||||
const cbResult = cb(result.packageData, result.filename)
|
||||
|
||||
if (cbResult.success) {
|
||||
return { success: true, payload: cbResult.payload }
|
||||
}
|
||||
}
|
||||
|
||||
const nextStepPathToScan = path.resolve(stepPathToScan, '..')
|
||||
|
||||
if (nextStepPathToScan === stepPathToScan) {
|
||||
// we are at the root. Give up
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
stepPathToScan = nextStepPathToScan
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function scanFSForAvailableDependency (cwd: string, lookingForDeps: Record<string, string>) {
|
||||
const { success } = createFindPackageJsonIterator(cwd)
|
||||
.map(({ dependencies, devDependencies }, path) => {
|
||||
if (!dependencies && !devDependencies) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
return {
|
||||
success: Object.entries({ ...dependencies, ...devDependencies })
|
||||
.some(([dependency, version]) => {
|
||||
return (
|
||||
Boolean(lookingForDeps[dependency])
|
||||
&& validateSemverVersion(version, lookingForDeps[dependency] as string, dependency)
|
||||
)
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
export type PackageJsonIterator = ReturnType<typeof createFindPackageJsonIterator>
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { program } from 'commander'
|
||||
import { main } from './main'
|
||||
import { version } from '../package.json'
|
||||
|
||||
program
|
||||
.option('--ignore-examples', 'Ignore generating example tests and fixtures by creating one ready-to-fill spec file')
|
||||
.option('--use-npm', 'Use npm even if yarn is available')
|
||||
.option('--ignore-ts', 'Ignore typescript if available')
|
||||
.option('--component-tests', 'Run component testing installation without asking')
|
||||
|
||||
program.version(version, '-v --version')
|
||||
program.parse(process.argv)
|
||||
|
||||
main({
|
||||
useNpm: program.useNpm,
|
||||
ignoreTs: program.ignoreTs,
|
||||
ignoreExamples: Boolean(program.ignoreExamples),
|
||||
setupComponentTesting: program.componentTests,
|
||||
}).catch(console.error)
|
||||
@@ -1,18 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
const INITIAL_TEMPLATE_PATH = path.resolve(__dirname, '..', 'initial-template')
|
||||
|
||||
export async function getInitialSupportFilesPaths () {
|
||||
return (
|
||||
await fs.readdir(path.join(INITIAL_TEMPLATE_PATH, 'support'))
|
||||
).map((filename) => path.join(INITIAL_TEMPLATE_PATH, 'support', filename))
|
||||
}
|
||||
|
||||
export function getInitialPluginsFilePath () {
|
||||
return path.join(INITIAL_TEMPLATE_PATH, 'plugins', 'index.js')
|
||||
}
|
||||
|
||||
export function getInitialTsConfigPath () {
|
||||
return path.join(INITIAL_TEMPLATE_PATH, 'tsconfig.json')
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import fs from 'fs-extra'
|
||||
import findUp from 'find-up'
|
||||
import path from 'path'
|
||||
import { installDependency } from './utils'
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import * as initialTemplate from './initialTemplate'
|
||||
|
||||
type InstallCypressOpts = {
|
||||
useYarn: boolean
|
||||
useTypescript: boolean
|
||||
ignoreExamples: boolean
|
||||
}
|
||||
|
||||
async function copyFiles ({ ignoreExamples, useTypescript }: InstallCypressOpts) {
|
||||
let fileSpinner = ora('Creating config files').start()
|
||||
|
||||
await fs.outputFile(path.resolve(process.cwd(), useTypescript ? 'cypress.config.ts' : 'cypress.config.js'), useTypescript ? `export default {}` : `module.exports = {}\n`)
|
||||
await fs.copy(
|
||||
initialTemplate.getInitialPluginsFilePath(),
|
||||
path.resolve('cypress', 'plugins/index.js'),
|
||||
)
|
||||
|
||||
const supportFiles: string[] = await initialTemplate.getInitialSupportFilesPaths()
|
||||
|
||||
await Promise.all(
|
||||
supportFiles.map((supportFilePath) => {
|
||||
const newSupportFilePath = path.resolve('cypress', 'support', path.basename(supportFilePath))
|
||||
|
||||
return fs.copy(supportFilePath, newSupportFilePath)
|
||||
}),
|
||||
)
|
||||
|
||||
if (useTypescript) {
|
||||
await fs.copy(initialTemplate.getInitialTsConfigPath(), path.resolve('cypress', 'tsconfig.json'))
|
||||
}
|
||||
|
||||
// TODO think about better approach
|
||||
if (ignoreExamples) {
|
||||
const dummySpec = [
|
||||
'describe("Spec", () => {',
|
||||
'',
|
||||
'})',
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
const specFileName = useTypescript ? 'spec.cy.ts' : 'spec.cy.js'
|
||||
const specFileToCreate = path.resolve('cypress', 'e2e', specFileName)
|
||||
|
||||
await fs.outputFile(specFileToCreate, dummySpec)
|
||||
console.log(`In order to ignore examples a spec file ${chalk.green(path.relative(process.cwd(), specFileToCreate))}.`)
|
||||
}
|
||||
|
||||
fileSpinner.succeed()
|
||||
}
|
||||
|
||||
export async function findInstalledOrInstallCypress (options: InstallCypressOpts) {
|
||||
const configFile = options.useTypescript ? 'cypress.config.ts' : 'cypress.config.js'
|
||||
let cypressConfigPath = await findUp(configFile)
|
||||
|
||||
if (!cypressConfigPath) {
|
||||
await installDependency('cypress', options)
|
||||
await copyFiles(options)
|
||||
|
||||
cypressConfigPath = await findUp(configFile)
|
||||
}
|
||||
|
||||
if (!cypressConfigPath) {
|
||||
throw new Error('Unexpected error during cypress installation.')
|
||||
}
|
||||
|
||||
const config = await import(cypressConfigPath)
|
||||
|
||||
return {
|
||||
cypressConfigPath,
|
||||
config: config.default,
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
import { expect, use } from 'chai'
|
||||
import path from 'path'
|
||||
import sinon, { SinonStub, SinonSpy, SinonSpyCallApi } from 'sinon'
|
||||
import mockFs from 'mock-fs'
|
||||
import fsExtra from 'fs-extra'
|
||||
import { main } from './main'
|
||||
import sinonChai from 'sinon-chai'
|
||||
import childProcess from 'child_process'
|
||||
|
||||
use(sinonChai)
|
||||
|
||||
function mockFsWithInitialTemplate (...args: Parameters<typeof mockFs>) {
|
||||
const [fsConfig, options] = args
|
||||
|
||||
mockFs({
|
||||
...fsConfig,
|
||||
// @ts-expect-error Load required template files
|
||||
[path.resolve(__dirname, '..', 'initial-template')]: mockFs.load(path.resolve(__dirname, '..', 'initial-template')),
|
||||
}, options)
|
||||
}
|
||||
|
||||
function someOfSpyCallsIncludes (spy: any, logPart: string) {
|
||||
return spy.getCalls().some(
|
||||
(spy: SinonSpyCallApi<unknown[]>) => {
|
||||
return spy.args.some((callArg) => typeof callArg === 'string' && callArg.includes(logPart))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
describe('create-cypress-tests', () => {
|
||||
let promptSpy: SinonStub<any> | null = null
|
||||
let logSpy: SinonSpy | null = null
|
||||
let errorSpy: SinonSpy | null = null
|
||||
let execStub: SinonStub | null = null
|
||||
let fsCopyStub: SinonStub | null = null
|
||||
let processExitStub: SinonStub | null = null
|
||||
|
||||
beforeEach(() => {
|
||||
logSpy = sinon.spy(global.console, 'log')
|
||||
errorSpy = sinon.spy(global.console, 'error')
|
||||
// @ts-ignore
|
||||
execStub = sinon.stub(childProcess, 'exec').callsFake((command, callback) => callback())
|
||||
// @ts-ignore
|
||||
fsCopyStub = sinon.stub(fsExtra, 'copy').returns(Promise.resolve())
|
||||
processExitStub = sinon.stub(process, 'exit').callsFake(() => {
|
||||
throw new Error('process.exit should not be called')
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
logSpy?.restore()
|
||||
promptSpy?.restore()
|
||||
execStub?.restore()
|
||||
fsCopyStub?.restore()
|
||||
processExitStub?.restore()
|
||||
execStub?.restore()
|
||||
errorSpy?.restore()
|
||||
})
|
||||
|
||||
it('Install cypress if no config found', async () => {
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
await main({ useNpm: false, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
|
||||
expect(execStub).calledWith('yarn add cypress --dev')
|
||||
})
|
||||
|
||||
it('Uses npm if yarn is not available', async () => {
|
||||
execStub
|
||||
?.onFirstCall().callsFake((command, callback) => callback('yarn is not available'))
|
||||
?.onSecondCall().callsFake((command, callback) => callback())
|
||||
?.onThirdCall().callsFake((command, callback) => callback())
|
||||
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
await main({ useNpm: false, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
expect(execStub).calledWith('npm install -D cypress')
|
||||
})
|
||||
|
||||
it('Uses npm if --use-npm was provided', async () => {
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
await main({ useNpm: true, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
|
||||
expect(execStub).calledWith('npm install -D cypress')
|
||||
})
|
||||
|
||||
it('Prints correct commands helper for npm', async () => {
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
await main({ useNpm: true, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
expect(someOfSpyCallsIncludes(logSpy, 'npx cypress open')).to.be.true
|
||||
})
|
||||
|
||||
it('Prints correct commands helper for yarn', async () => {
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
await main({ useNpm: false, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
expect(someOfSpyCallsIncludes(logSpy, 'yarn cypress open')).to.be.true
|
||||
})
|
||||
|
||||
it('Fails if git repository have untracked or uncommitted files', async () => {
|
||||
mockFsWithInitialTemplate({
|
||||
'/package.json': JSON.stringify({ }),
|
||||
})
|
||||
|
||||
execStub?.callsFake((_, callback) => callback(null, { stdout: 'test' }))
|
||||
processExitStub?.callsFake(() => {})
|
||||
|
||||
await main({ useNpm: true, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
|
||||
expect(
|
||||
someOfSpyCallsIncludes(errorSpy, 'This repository has untracked files or uncommitted changes.'),
|
||||
).to.equal(true)
|
||||
|
||||
expect(processExitStub).to.be.called
|
||||
})
|
||||
|
||||
context('e2e fs tests', () => {
|
||||
const e2eTestOutputPath = path.resolve(__dirname, 'test-output')
|
||||
|
||||
beforeEach(async () => {
|
||||
fsCopyStub?.restore()
|
||||
mockFs.restore()
|
||||
sinon.stub(process, 'cwd').returns(e2eTestOutputPath)
|
||||
|
||||
await fsExtra.remove(e2eTestOutputPath)
|
||||
await fsExtra.mkdir(e2eTestOutputPath)
|
||||
})
|
||||
|
||||
it('Copies plugins and support files', async () => {
|
||||
await fsExtra.outputFile(
|
||||
path.join(e2eTestOutputPath, 'package.json'),
|
||||
JSON.stringify({ name: 'test' }, null, 2),
|
||||
)
|
||||
|
||||
await main({ useNpm: true, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
|
||||
expect(await fsExtra.pathExists(path.resolve(e2eTestOutputPath, 'cypress', 'plugins', 'index.js'))).to.equal(true)
|
||||
expect(await fsExtra.pathExists(path.resolve(e2eTestOutputPath, 'cypress', 'support', 'e2e.js'))).to.equal(true)
|
||||
expect(await fsExtra.pathExists(path.resolve(e2eTestOutputPath, 'cypress', 'support', 'commands.js'))).to.equal(true)
|
||||
expect(await fsExtra.pathExists(path.resolve(e2eTestOutputPath, 'cypress.config.ts'))).to.equal(true)
|
||||
})
|
||||
|
||||
it('Copies tsconfig if typescript is installed', async () => {
|
||||
await fsExtra.outputFile(
|
||||
path.join(e2eTestOutputPath, 'package.json'),
|
||||
JSON.stringify({
|
||||
name: 'test-typescript',
|
||||
dependencies: { typescript: '^4.0.0' },
|
||||
}, null, 2),
|
||||
)
|
||||
|
||||
await main({ useNpm: false, ignoreTs: false, ignoreExamples: false, setupComponentTesting: false })
|
||||
await fsExtra.pathExists(path.resolve(e2eTestOutputPath, 'cypress', 'tsconfig.json'))
|
||||
console.log(path.resolve(e2eTestOutputPath, 'cypress', 'tsconfig.json'))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,106 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import findUp from 'find-up'
|
||||
import chalk from 'chalk'
|
||||
import util from 'util'
|
||||
import inquirer from 'inquirer'
|
||||
import { initComponentTesting } from './component-testing/init-component-testing'
|
||||
import { exec } from 'child_process'
|
||||
import { scanFSForAvailableDependency } from './findPackageJson'
|
||||
import { findInstalledOrInstallCypress } from './installCypress'
|
||||
|
||||
type MainArgv = {
|
||||
useNpm: boolean
|
||||
ignoreTs: boolean
|
||||
ignoreExamples: boolean
|
||||
setupComponentTesting: boolean
|
||||
}
|
||||
|
||||
async function getGitStatus () {
|
||||
const execAsync = util.promisify(exec)
|
||||
|
||||
try {
|
||||
let { stdout } = await execAsync(`git status --porcelain`)
|
||||
|
||||
console.log(stdout)
|
||||
|
||||
return stdout.trim()
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function shouldUseYarn () {
|
||||
const execAsync = util.promisify(exec)
|
||||
|
||||
return execAsync('yarn --version')
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
||||
|
||||
function shouldUseTypescript () {
|
||||
return scanFSForAvailableDependency(process.cwd(), { typescript: '*' })
|
||||
}
|
||||
|
||||
async function askForComponentTesting () {
|
||||
const { shouldSetupComponentTesting } = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'shouldSetupComponentTesting',
|
||||
message: `Do you want to setup ${chalk.cyan('component testing')}? ${chalk.grey('You can do this later by rerunning this command')}.`,
|
||||
})
|
||||
|
||||
return shouldSetupComponentTesting
|
||||
}
|
||||
|
||||
function printCypressCommandsHelper (options: { shouldSetupComponentTesting: boolean, useYarn: boolean }) {
|
||||
const printCommand = (command: string, description: string) => {
|
||||
const displayedRunner = options.useYarn ? 'yarn' : 'npx'
|
||||
|
||||
console.log()
|
||||
console.log(chalk.cyan(` ${displayedRunner} ${command}`))
|
||||
console.log(` ${description}`)
|
||||
}
|
||||
|
||||
printCommand('cypress open', 'Opens cypress local development app.')
|
||||
printCommand('cypress run', 'Runs tests in headless mode.')
|
||||
|
||||
if (options.shouldSetupComponentTesting) {
|
||||
printCommand('cypress open --component', 'Opens Cypress component testing interactive mode.')
|
||||
printCommand('cypress run-ct', 'Runs all Cypress component testing suites.')
|
||||
}
|
||||
}
|
||||
|
||||
export async function main ({ useNpm, ignoreTs, setupComponentTesting, ignoreExamples }: MainArgv) {
|
||||
const rootPackageJsonPath = await findUp('package.json')
|
||||
const useYarn = useNpm === true ? false : await shouldUseYarn()
|
||||
const useTypescript = ignoreTs ? false : shouldUseTypescript()
|
||||
|
||||
if (!rootPackageJsonPath) {
|
||||
console.log(`${chalk.bold.red(`It looks like you are running cypress installation wizard outside of npm module.`)}\nIf you would like to setup a new project for cypress tests please run the ${chalk.inverse(useNpm ? ' npm init ' : ' yarn init ')} first.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const { name = 'unknown', version = '0.0.0' } = JSON.parse(fs.readFileSync(rootPackageJsonPath).toString())
|
||||
|
||||
console.log(`Running ${chalk.green('cypress 🌲')} installation wizard for ${chalk.cyan(`${name}@${version}`)}`)
|
||||
|
||||
const gitStatus = await getGitStatus()
|
||||
|
||||
if (gitStatus) {
|
||||
console.error(`\n${chalk.bold.red('This repository has untracked files or uncommitted changes.')}\nThis command will ${chalk.cyan('make changes in the codebase')}, so please remove untracked files, stash or commit any changes, and try again.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const { config, cypressConfigPath } = await findInstalledOrInstallCypress({ useYarn, useTypescript, ignoreExamples })
|
||||
const shouldSetupComponentTesting = setupComponentTesting ?? await askForComponentTesting()
|
||||
|
||||
if (shouldSetupComponentTesting) {
|
||||
await initComponentTesting({ config, cypressConfigPath, useYarn })
|
||||
}
|
||||
|
||||
console.log(`\n👍 Success! Cypress is installed and ready to run tests.`)
|
||||
printCypressCommandsHelper({ useYarn, shouldSetupComponentTesting })
|
||||
|
||||
console.log(`\nHappy testing with ${chalk.green('cypress.io')} 🌲\n`)
|
||||
}
|
||||
|
||||
export { scanFSForAvailableDependency }
|
||||
@@ -1,34 +0,0 @@
|
||||
import * as babel from '@babel/core'
|
||||
import snapshot from 'snap-shot-it'
|
||||
import mockFs from 'mock-fs'
|
||||
import { SinonSpyCallApi } from 'sinon'
|
||||
import { createTransformPluginsFileBabelPlugin } from './component-testing/babel/babelTransform'
|
||||
import { Template } from './component-testing/templates/Template'
|
||||
|
||||
export function someOfSpyCallsIncludes (spy: any, logPart: string) {
|
||||
return spy.getCalls().some(
|
||||
(spy: SinonSpyCallApi<unknown[]>) => {
|
||||
return spy.args.some((callArg) => typeof callArg === 'string' && callArg.includes(logPart))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export function snapshotPluginsAstCode<T> (template: Template<T>, payload?: T) {
|
||||
mockFs.restore()
|
||||
const code = [
|
||||
'const something = require("something")',
|
||||
'module.exports = (on) => {',
|
||||
'};',
|
||||
].join('\n')
|
||||
|
||||
const babelPlugin = createTransformPluginsFileBabelPlugin(template.getPluginsCodeAst(payload ?? null, { cypressProjectRoot: '/' }))
|
||||
const output = babel.transformSync(code, {
|
||||
plugins: [babelPlugin],
|
||||
})
|
||||
|
||||
if (!output || !output.code) {
|
||||
throw new Error('Babel transform output is empty.')
|
||||
}
|
||||
|
||||
snapshot(output.code)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import semver from 'semver'
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import util from 'util'
|
||||
import { exec } from 'child_process'
|
||||
|
||||
/**
|
||||
* Compare available version range with the provided version from package.json
|
||||
* @param packageName Package name used to display a helper message to user.
|
||||
*/
|
||||
export function validateSemverVersion (
|
||||
version: string,
|
||||
allowedVersionRange: string,
|
||||
packageName?: string,
|
||||
) {
|
||||
let isValid: boolean
|
||||
|
||||
try {
|
||||
const minAvailableVersion = semver.minVersion(version)?.raw
|
||||
|
||||
isValid = Boolean(
|
||||
minAvailableVersion &&
|
||||
semver.satisfies(minAvailableVersion, allowedVersionRange),
|
||||
)
|
||||
} catch (e) {
|
||||
// handle not semver versions like "latest", "git:" or "file:"
|
||||
isValid = false
|
||||
}
|
||||
|
||||
if (!isValid && packageName) {
|
||||
const packageNameSymbol = chalk.green(packageName)
|
||||
|
||||
console.warn(
|
||||
`It seems like you are using ${packageNameSymbol} with version ${chalk.bold(
|
||||
version,
|
||||
)}, however we support only ${packageNameSymbol} projects with version ${chalk.bold(
|
||||
allowedVersionRange,
|
||||
)}. \n`,
|
||||
)
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
export async function installDependency (name: string, options: { useYarn: boolean}) {
|
||||
const commandToRun = options.useYarn ? `yarn add ${name} --dev` : `npm install -D ${name}`
|
||||
let cliSpinner = ora(`Installing ${name} ${chalk.gray(`(${commandToRun})`)}`).start()
|
||||
|
||||
try {
|
||||
// do this inside function for test stubbing
|
||||
const execAsync = util.promisify(exec)
|
||||
|
||||
await execAsync(commandToRun)
|
||||
} catch (e) {
|
||||
cliSpinner.fail(`Can not install ${name} using ${chalk.inverse(commandToRun)})}`)
|
||||
console.log(e)
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
cliSpinner.succeed()
|
||||
}
|
||||
|
||||
export async function prettifyCode (finalCode?: string | null) {
|
||||
try {
|
||||
const maybePrettier = require('prettier')
|
||||
|
||||
if (maybePrettier && maybePrettier.format) {
|
||||
finalCode = maybePrettier.format(finalCode, { parser: 'babel' })
|
||||
}
|
||||
} catch (e) {
|
||||
return null
|
||||
} finally {
|
||||
return finalCode
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"resolveJsonModule": true,
|
||||
"module": "CommonJS",
|
||||
"target": "ES2018",
|
||||
"types": [
|
||||
"node",
|
||||
],
|
||||
"lib": [
|
||||
"ES2018"
|
||||
],
|
||||
"noImplicitAny": true
|
||||
},
|
||||
"exclude": [
|
||||
"./src/**/*.test.ts",
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": [
|
||||
"mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"binary-package": "cross-env NODE_OPTIONS=--max_old_space_size=8192 node ./scripts/binary.js package",
|
||||
"check-binary-on-cdn": "node ./scripts/binary.js checkIfBinaryExistsOnCdn",
|
||||
"build": "lerna run build --stream && lerna run build-cli --stream",
|
||||
"build-prod": "lerna run build-prod --stream --ignore create-cypress-tests && node ./cli/scripts/post-build.js && lerna run build-prod --stream --scope create-cypress-tests --scope",
|
||||
"build-prod": "lerna run build-prod --stream && node ./cli/scripts/post-build.js && lerna run build-prod --stream --scope",
|
||||
"build-v8-snapshot-dev": "node --max-old-space-size=8192 tooling/v8-snapshot/scripts/setup-v8-snapshot-in-cypress.js --env=dev",
|
||||
"build-v8-snapshot-prod": "node --max-old-space-size=8192 tooling/v8-snapshot/scripts/setup-v8-snapshot-in-cypress.js",
|
||||
"check-node-version": "node scripts/check-node-version.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This package is responsible for copying the `cypress/e2e` and `app` files from [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink) into the cypress repository.
|
||||
|
||||
The `cypress/e2e` tests, pulled into this package from the [kitchen sink app](https://github.com/cypress-io/cypress-example-kitchensink), are used for scaffolding user's e2e tests in `packages/data-context` and in `npm/create-cypress-tests`.
|
||||
The `cypress/e2e` tests, pulled into this package from the [kitchen sink app](https://github.com/cypress-io/cypress-example-kitchensink), are used for scaffolding user's e2e tests in `packages/data-context`.
|
||||
|
||||
The `app` content, pulled into this package from the [kitchen sink app](https://github.com/cypress-io/cypress-example-kitchensink), is published to `cypress-io/cypress` repository's Github page, [https://example.cypress.io](https://example.cypress.io).
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ export const replaceLocalNpmVersions = async function (basePath: string) {
|
||||
let shouldWriteFile = false
|
||||
|
||||
for (const [depName, version] of Object.entries(dependencies)) {
|
||||
const matchedPkg = Boolean(depName.startsWith('@cypress/') || depName === 'create-cypress-tests')
|
||||
const matchedPkg = Boolean(depName.startsWith('@cypress/'))
|
||||
|
||||
if (!matchedPkg || version !== '0.0.0-development') {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user