Merge remote-tracking branch 'origin/develop' into 7.0-release

This commit is contained in:
Zach Bloomquist
2021-03-24 16:27:56 -07:00
215 changed files with 7654 additions and 5214 deletions

View File

@@ -478,8 +478,6 @@ Please refer to each packages' `README.md` which documents how to run tests. It
If you're curious how we manage all of these tests in CI check out our [`circle.yml`](circle.yml) file found in the root `cypress` directory.
Each of the independent packages (in the [`/npm`](./npm) folder) have a `ciJobs` field in their `package.json`. This field corresponds to the CI jobs for that package and is used when determining what tests must pass before the package can be released.
#### Docker
Sometimes tests pass locally, but fail in CI. Our CI environment is dockerized. In order to run the image used in CI locally:
@@ -546,49 +544,7 @@ All updates to `master` are automatically merged into `develop`, so `develop` al
### Independent Packages CI Workflow
Independent packages are automatically released when code is merged into `master`. In order to make these automatic releases work smoothly, independent packages have a couple of special configuration options in their `package.json`.
##### `ciJobs`
List of Circle CI jobs that directly test the current package. These tests must pass before the package can be released.
In addition, these tests will run when a PR is created that changes this package. All tests will run on `develop` and `master`, regardless of what packages were changed.
Note: CI jobs should be unique to a package. Any jobs that are not listed within a `ciJobs` field are considered to be part of the binary and will only run when the binary is changed.
This option takes an array of CI job names.
Example
```json
{
"ciJobs": [
"npm-react",
"npm-react-axe",
"npm-react-next"
]
}
```
##### `ciDependents`
List of local independent (npm) packages that are dependent on the current package. The tests specified in these packages' `ciJobs` must pass before the current package will be released.
When the current package is changed in a PR, it will consider these packages to be changed as well and run CI accordingly.
This option takes an array of package names.
Example
```json
{
"ciDependents": [
"@cypress/react",
"@cypress/vue",
"@cypress/webpack-preprocessor"
]
}
```
You can read more about our CI design decisions in [#8730](https://github.com/cypress-io/cypress/pull/8730#issue-496593325)
Independent packages are automatically released when code is merged into `master` and the entire build passes.
### Pull Requests

View File

@@ -128,7 +128,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
environment:
CYPRESS_KONFIG_ENV: production
@@ -170,7 +169,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: |
cmd=$([[ <<parameters.percy>> == 'true' ]] && echo 'yarn percy exec --') || true
@@ -198,7 +196,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: |
cmd=$([[ <<parameters.percy>> == 'true' ]] && echo 'yarn percy exec --') || true
@@ -228,7 +225,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: yarn workspace @packages/server test ./test/e2e/$(( $CIRCLE_NODE_INDEX ))_*spec* --browser <<parameters.browser>>
- verify-mocha-results
@@ -279,7 +275,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: "Cloning test project: <<parameters.repo>>"
command: |
@@ -328,7 +323,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure the binary and NPM package files are present
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -464,7 +458,6 @@ commands:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure the binary and NPM package files are present
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -576,14 +569,6 @@ commands:
- run:
name: "Waiting on Circle CI jobs: <<parameters.job-names>>"
command: node ./scripts/wait-on-circle-jobs.js --job-names="<<parameters.job-names>>"
check-conditional-ci:
description: Halt CI if the package that this job corresponds to is unchanged
steps:
- run:
name: Check if job should run
command: node scripts/check-conditional-ci.js
build-binary:
steps:
- run:
@@ -797,15 +782,6 @@ jobs:
command: node cli/bin/cypress info --dev
- store-npm-logs
list-changed-packages:
<<: *defaults
steps:
- attach_workspace:
at: ~/
- run:
name: List changed packages
command: node scripts/changed-packages.js
# a special job that keeps polling Circle and when all
# individual jobs are finished, it closes the Percy build
percy-finalize:
@@ -845,7 +821,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run: mkdir -p cli/visual-snapshots
- run:
command: node cli/bin/cypress info --dev | yarn --silent term-to-html | node scripts/sanitize --type cli-info > cli/visual-snapshots/cypress-info.html
@@ -870,11 +845,12 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure mocha runs
- run: yarn test-mocha
# test binary build code
- run: yarn test-scripts
# check for compile errors with the releaserc scripts
- run: yarn test-npm-package-release-script
# make sure our snapshots are compared correctly
- run: yarn test-mocha-snapshot
# make sure packages with TypeScript can be transpiled to JS
@@ -916,7 +892,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run: yarn test-unit --scope @packages/server
- verify-mocha-results:
expectedResultCount: 1
@@ -930,7 +905,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run: yarn test-unit --scope @packages/server-ct
- verify-mocha-results:
expectedResultCount: 1
@@ -944,7 +918,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run: yarn test-integration --scope @packages/server
- verify-mocha-results:
expectedResultCount: 1
@@ -957,7 +930,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: yarn workspace @packages/server test-performance
- verify-mocha-results:
@@ -1046,7 +1018,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: yarn build-prod
working_directory: packages/desktop-gui
@@ -1072,7 +1043,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
# builds JS and CSS, and we need the app CSS
# to correctly apply component styles
@@ -1106,7 +1076,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: yarn build-for-tests
working_directory: packages/reporter
@@ -1131,7 +1100,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: yarn build-for-tests
working_directory: packages/ui-components
@@ -1153,7 +1121,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
command: node index.js
working_directory: packages/launcher
@@ -1163,7 +1130,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Build
command: yarn workspace @cypress/webpack-preprocessor build
@@ -1202,7 +1168,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/webpack-dev-server test
@@ -1211,7 +1176,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/vite-dev-server test
@@ -1221,7 +1185,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/rollup-dev-server test
@@ -1231,7 +1194,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/webpack-batteries-included-preprocessor test
@@ -1241,14 +1203,17 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Build
command: yarn workspace @cypress/vue build
- run:
name: Run tests
command: yarn test
command: yarn test --reporter cypress-circleci-reporter --reporter-options resultsDir=./test_results
working_directory: npm/vue
- store_test_results:
path: npm/vue/test_results
- store_artifacts:
path: npm/vue/test_results
- store-npm-logs
npm-design-system:
@@ -1256,7 +1221,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Build
command: yarn workspace @cypress/design-system build
@@ -1273,7 +1237,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- restore_cache:
name: Restore yarn cache
key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-npm-react-babel-cache
@@ -1306,7 +1269,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/eslint-plugin-dev test
@@ -1317,7 +1279,7 @@ jobs:
- attach_workspace:
at: ~/
- run:
name: Release packages
name: Release packages after all jobs pass
command: yarn npm-release
create-build-artifacts:
@@ -1326,7 +1288,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- build-binary
- build-npm-package
- run:
@@ -1410,7 +1371,6 @@ jobs:
# needs uploaded NPM and test binary
- attach_workspace:
at: ~/
- check-conditional-ci
- run: ls -la
# make sure JSON files with uploaded urls are present
- run: ls -la binary-url.json npm-package-url.json
@@ -1447,7 +1407,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure we have cypress.zip received
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -1487,7 +1446,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure we have cypress.zip received
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -1529,7 +1487,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure we have cypress.zip received
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -1573,7 +1530,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure we have cypress.zip received
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -1617,7 +1573,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# make sure we have cypress.zip received
- run: ls -l
- run: ls -l cypress.zip cypress.tgz
@@ -1650,7 +1605,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run: ls -l
# make sure we have the binary and NPM package
- run: ls -l cypress.zip cypress.tgz
@@ -1781,7 +1735,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
# the user should be "node"
- run: whoami
- run: pwd
@@ -1832,9 +1785,6 @@ linux-workflow: &linux-workflow
name: Linux lint
requires:
- build
- list-changed-packages:
requires:
- build
- percy-finalize:
context: test-runner:poll-circle-workflow
required_env_var: PERCY_TOKEN # skips job if not defined (external PR)
@@ -1938,9 +1888,42 @@ linux-workflow: &linux-workflow
- npm-eslint-plugin-dev:
requires:
- build
# This release definition must be updated with any new jobs
# Any attempts to automate this are welcome
# If CircleCI provided an "after all" hook, then this wouldn't be necessary
- npm-release:
requires:
- build
- npm-eslint-plugin-dev
- npm-create-cypress-tests
- npm-react
- npm-vue
- npm-design-system
- npm-webpack-batteries-included-preprocessor
- npm-webpack-preprocessor
- npm-rollup-dev-server
- npm-vite-dev-server
- npm-webpack-dev-server
- run-launcher
- ui-components-integration-tests
- reporter-integration-tests
- Linux lint
- desktop-gui-component-tests
- desktop-gui-integration-tests-2x
- runner-ct-integration-tests-chrome
- runner-integration-tests-firefox
- runner-integration-tests-chrome
- driver-integration-tests-firefox
- driver-integration-tests-chrome
- server-e2e-tests-non-root
- server-e2e-tests-firefox
- server-e2e-tests-electron
- server-e2e-tests-chrome
- server-performance-tests
- server-integration-tests
- server-unit-tests
- unit-tests
- cli-visual-tests
# various testing scenarios, like building full binary
# and testing it on a real project

View File

@@ -281,6 +281,16 @@
"type": "boolean",
"default": false,
"description": "Enables including elements within the shadow DOM when using querying commands (e.g. cy.get(), cy.find()). Can be set globally in cypress.json, per-suite or per-test in the test configuration object, or programmatically with Cypress.config()"
},
"component": {
"type": "object",
"default": {},
"description": "Any component runner specific overrides"
},
"e2e": {
"type": "object",
"default": {},
"description": "Any e2e runner specific overrides"
}
}
}

View File

@@ -1860,7 +1860,7 @@ declare namespace Cypress {
* // or use this shortcut
* cy.tick(5000).invoke('restore')
*/
tick(milliseconds: number): Chainable<Clock>
tick(milliseconds: number, options?: Partial<Loggable>): Chainable<Clock>
/**
* Get the `document.title` property of the page that is currently active.
@@ -2126,6 +2126,21 @@ declare namespace Cypress {
```
*/
writeFile<C extends FileContents>(filePath: string, contents: C, options?: Partial<WriteFileOptions>): Chainable<C>
/**
* Write to a file with the specified encoding and contents.
*
* An `encoding` option in `options` will override the `encoding` argument.
*
* @see https://on.cypress.io/writefile
```
cy.writeFile('path/to/ascii.txt', 'Hello World', 'utf8', {
flag: 'a+',
}).then((text) => {
expect(text).to.equal('Hello World') // true
})
```
*/
writeFile<C extends FileContents>(filePath: string, contents: C, encoding: Encodings, options?: Partial<WriteFileOptions>): Chainable<C>
/**
* jQuery library bound to the AUT
@@ -2601,6 +2616,18 @@ declare namespace Cypress {
* @default false
*/
includeShadowDom: boolean
/**
* Override default config options for Component Testing runner.
* @default {}
*/
component: ResolvedConfigOptions
/**
* Override default config options for E2E Testing runner.
* @default {}
*/
e2e: ResolvedConfigOptions
}
/**
@@ -2725,6 +2752,10 @@ declare namespace Cypress {
* Absolute path to the root of the project
*/
projectRoot: string
/**
* Type of test and associated runner that was launched.
*/
testingType: 'e2e' | 'component'
/**
* Cypress version.
*/

View File

@@ -465,6 +465,10 @@ cy.writeFile('../file.path', '', {
flag: 'a+',
encoding: 'utf-8'
})
cy.writeFile('../file.path', '', 'ascii', {
flag: 'a+',
encoding: 'utf-8'
})
cy.get('foo').click()
cy.get('foo').click({

View File

@@ -1,3 +1,10 @@
# [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)

View File

@@ -4,7 +4,10 @@ const injectDevServer = require('@cypress/react/plugins/babel');
const something = require("something");
module.exports = (on, config) => {
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config);
}
return config; // IMPORTANT to return a config
};
`

View File

@@ -10,8 +10,11 @@ exports['Injected overridden webpack template plugins/index.js'] = `
const injectDevServer = require("@cypress/react/plugins/react-scripts");
module.exports = (on, config) => {
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config);
}
return config; // IMPORTANT to return a config
};
`
@@ -32,8 +35,11 @@ exports['injects guessed next.js template plugins/index.js'] = `
const injectDevServer = require("@cypress/react/plugins/next");
module.exports = (on, config) => {
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config);
}
return config; // IMPORTANT to return a config
};
`

View File

@@ -4,7 +4,10 @@ const injectDevServer = require('@cypress/react/plugins/next');
const something = require("something");
module.exports = (on, config) => {
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config);
}
return config; // IMPORTANT to return a config
};
`

View File

@@ -4,7 +4,10 @@ const injectDevServer = require('@cypress/react/plugins/react-scripts');
const something = require("something");
module.exports = (on, config) => {
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config);
}
return config; // IMPORTANT to return a config
};
`

View File

@@ -4,10 +4,14 @@ const injectDevServer = require("@cypress/react/plugins/load-webpack");
const something = require("something");
module.exports = (on, config) => {
// TODO replace with valid webpack config path
config.env.webpackFilename = './webpack.config.js';
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
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
};
`
@@ -17,8 +21,12 @@ const injectDevServer = require("@cypress/react/plugins/load-webpack");
const something = require("something");
module.exports = (on, config) => {
config.env.webpackFilename = 'config/webpack.config.js';
injectDevServer(on, config);
return config; // IMPORTANT to return the config object
if (config.testingType === "component") {
injectDevServer(on, config, {
webpackFileName: 'config/webpack.config.js'
});
}
return config; // IMPORTANT to return a config
};
`

View File

@@ -8,14 +8,16 @@ const {
const something = require("something");
module.exports = (on, config) => {
on("dev-server:start", async options => {
return startDevServer({
options,
// TODO replace with valid rollup config path
rollupConfig: path.resolve(__dirname, 'rollup.config.js')
if (config.testingType === "component") {
on("dev-server:start", async options => {
return startDevServer({
options,
// TODO replace with valid rollup config path
rollupConfig: path.resolve(__dirname, 'rollup.config.js')
});
});
});
return config; // IMPORTANT to return the config object
return config; // IMPORTANT to return the config object
}
};
`
@@ -29,12 +31,14 @@ const {
const something = require("something");
module.exports = (on, config) => {
on("dev-server:start", async options => {
return startDevServer({
options,
rollupConfig: path.resolve(__dirname, 'config/rollup.config.js')
if (config.testingType === "component") {
on("dev-server:start", async options => {
return startDevServer({
options,
rollupConfig: path.resolve(__dirname, 'config/rollup.config.js')
});
});
});
return config; // IMPORTANT to return the config object
return config; // IMPORTANT to return the config object
}
};
`

View File

@@ -6,8 +6,10 @@ const {
const something = require("something");
module.exports = (on, config) => {
on("dev-server:start", async options => startDevServer({
options
}));
if (config.testingType === "component") {
on("dev-server:start", async options => startDevServer({
options
}));
}
};
`

View File

@@ -8,9 +8,11 @@ const webpackConfig = require("@vue/cli-service/webpack.config.js");
const something = require("something");
module.exports = (on, config) => {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
if (config.testingType === "component") {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
}
};
`

View File

@@ -9,10 +9,12 @@ const webpackConfig = require("./webpack.config.js"); // TODO replace with valid
const something = require("something");
module.exports = (on, config) => {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
if (config.testingType === "component") {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
}
};
`
@@ -26,9 +28,11 @@ const webpackConfig = require("build/webpack.config.js");
const something = require("something");
module.exports = (on, config) => {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
if (config.testingType === "component") {
on('dev-server:start', options => startDevServer({
options,
webpackConfig
}));
}
};
`

View File

@@ -8,31 +8,33 @@ const {
const something = require("something");
module.exports = (on, config) => {
/** @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
}));
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
}));
}
};
`

View File

@@ -5,7 +5,8 @@
"private": false,
"main": "index.js",
"scripts": {
"build": "yarn prepare-example && tsc -p ./tsconfig.json && shx chmod +x dist/src/index.js && node scripts/example copy-to ./dist/initial-template && shx cp src/**/*.template.js dist/src",
"build": "yarn prepare-example && tsc -p ./tsconfig.json && node scripts/example copy-to ./dist/initial-template && yarn copy \"./src/**/*.template.js\" \"./dist/src\"",
"build-prod": "yarn build",
"prepare-example": "node scripts/example copy-to ./initial-template",
"test": "cross-env TS_NODE_PROJECT=./tsconfig.test.json mocha --config .mocharc.json './src/**/*.test.ts'",
"test:watch": "yarn test -w"

View File

@@ -6,8 +6,8 @@ describe('babel transform utils', () => {
context('Plugins config babel plugin', () => {
it('injects code into the plugins file based on ast', () => {
const plugin = createTransformPluginsFileBabelPlugin({
Require: babel.template.ast('require("something")'),
ModuleExportsBody: babel.template.ast('yey()'),
RequireAst: babel.template.ast('require("something")'),
IfComponentTestingPluginsAst: babel.template.ast('yey()'),
})
const output = babel.transformSync([
@@ -23,7 +23,10 @@ describe('babel transform utils', () => {
'',
'module.exports = (on, config) => {',
' on("do");',
' yey();',
'',
' if (config.testingType === "component") {',
' yey();',
' }',
'};',
].join(`\n`))
})

View File

@@ -3,7 +3,13 @@ import * as fs from 'fs-extra'
import * as babel from '@babel/core'
import * as babelTypes from '@babel/types'
export type PluginsConfigAst = Record<'Require' | 'ModuleExportsBody', ReturnType<typeof babel.template.ast>>
type AST = ReturnType<typeof babel.template.ast>
export type PluginsConfigAst = {
RequireAst: AST
IfComponentTestingPluginsAst: AST
requiresReturnConfig?: true
}
function tryRequirePrettier () {
try {
@@ -48,11 +54,13 @@ async function transformFileViaPlugin (filePath: string, babelPlugin: babel.Plug
}
}
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.Require)
path.unshiftContainer('body', ast.RequireAst)
},
Function: (path) => {
if (!babelTypes.isAssignmentExpression(path.parent)) {
@@ -80,7 +88,24 @@ export function createTransformPluginsFileBabelPlugin (ast: PluginsConfigAst): b
path.parent.right.params.push(babelTypes.identifier('config'))
}
path.get('body').pushContainer('body' as never, ast.ModuleExportsBody)
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 babel.Node)
if (ast.requiresReturnConfig) {
path.get('body').pushContainer('body' as never, returnConfigAst)
}
}
},
},

View File

@@ -34,11 +34,11 @@ export const RollupTemplate: Template<{ rollupConfigPath: string }> = {
: 'rollup.config.js'
return {
Require: babel.template.ast([
RequireAst: babel.template.ast([
'const path = require("path")',
'const { startDevServer } = require("@cypress/rollup-dev-server")',
].join('\n')),
ModuleExportsBody: babel.template.ast([
IfComponentTestingPluginsAst: babel.template.ast([
`on("dev-server:start", async (options) => {`,
` return startDevServer({`,
` options,`,

View File

@@ -10,10 +10,10 @@ export const ViteTemplate: Template = {
dependencies: ['@cypress/vite-dev-server'],
getPluginsCodeAst: () => {
return {
Require: babel.template.ast(
RequireAst: babel.template.ast(
'const { startDevServer } = require("@cypress/vite-dev-server");',
),
ModuleExportsBody: babel.template.ast([
IfComponentTestingPluginsAst: babel.template.ast([
'on("dev-server:start", async (options) => startDevServer({ options }))',
].join('\n'), { preserveComments: true }),
}

View File

@@ -15,10 +15,10 @@ export const BabelTemplate: Template = {
getExampleUrl: () => 'https://github.com/cypress-io/cypress/tree/develop/npm/react/examples/babel',
getPluginsCodeAst: () => {
return {
Require: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/babel\')'),
ModuleExportsBody: babel.template.ast([
requiresReturnConfig: true,
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/babel\')'),
IfComponentTestingPluginsAst: babel.template.ast([
'injectDevServer(on, config)',
'return config // IMPORTANT to return the config object',
].join('\n'), { preserveComments: true }),
}
},

View File

@@ -13,10 +13,10 @@ export const NextTemplate: Template = {
dependencies: ['@cypress/webpack-dev-server'],
getPluginsCodeAst: () => {
return {
Require: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/next\')'),
ModuleExportsBody: babel.template.ast([
requiresReturnConfig: true,
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/next\')'),
IfComponentTestingPluginsAst: babel.template.ast([
'injectDevServer(on, config)',
'return config // IMPORTANT to return the config object',
].join('\n'), { preserveComments: true }),
}
},

View File

@@ -16,10 +16,10 @@ export const ReactScriptsTemplate: Template = {
},
getPluginsCodeAst: () => {
return {
Require: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/react-scripts\')'),
ModuleExportsBody: babel.template.ast([
requiresReturnConfig: true,
RequireAst: babel.template.ast('const injectDevServer = require(\'@cypress/react/plugins/react-scripts\')'),
IfComponentTestingPluginsAst: babel.template.ast([
'injectDevServer(on, config)',
'return config // IMPORTANT to return the config object',
].join('\n'), { preserveComments: true }),
}
},

View File

@@ -18,14 +18,15 @@ export const WebpackTemplate: Template<{ webpackConfigPath: string }> = {
: './webpack.config.js'
return {
Require: babel.template.ast('const injectDevServer = require("@cypress/react/plugins/load-webpack")'),
ModuleExportsBody: babel.template.ast([
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'
? ' // TODO replace with valid webpack config path'
: '',
`config.env.webpackFilename = '${webpackConfigPath}'`,
'injectDevServer(on, config)',
'return config // IMPORTANT to return the config object',
` webpackFileName: '${webpackConfigPath}'`,
'})',
].join('\n'), { preserveComments: true }),
}
},

View File

@@ -15,11 +15,11 @@ export const WebpackOptions: Template = {
dependencies: ['webpack', '@cypress/webpack-dev-server'],
getPluginsCodeAst: () => {
return {
Require: babel.template.ast([
RequireAst: babel.template.ast([
'const path = require("path")',
'const { startDevServer } = require("@cypress/webpack-dev-Server")',
].join('\n')),
ModuleExportsBody: babel.template.ast(
IfComponentTestingPluginsAst: babel.template.ast(
fs.readFileSync(path.resolve(__dirname, 'webpack-options-module-exports.template.js'), { encoding: 'utf-8' }),
{ preserveComments: true },
),

View File

@@ -10,11 +10,11 @@ export const VueCliTemplate: Template = {
dependencies: ['@cypress/webpack-dev-server'],
getPluginsCodeAst: () => {
return {
Require: babel.template.ast([
RequireAst: babel.template.ast([
'const { startDevServer } = require("@cypress/webpack-dev-server")',
`const webpackConfig = require("@vue/cli-service/webpack.config.js")`,
].join('\n')),
ModuleExportsBody: babel.template.ast([
IfComponentTestingPluginsAst: babel.template.ast([
`on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))`,
].join('\n'), { preserveComments: true }),
}

View File

@@ -16,7 +16,7 @@ export const VueWebpackTemplate: Template<{ webpackConfigPath: string }> = {
: './webpack.config.js'
return {
Require: babel.template.ast([
RequireAst: babel.template.ast([
'const { startDevServer } = require("@cypress/webpack-dev-server")',
`const webpackConfig = require("${webpackConfigPath}")`,
@@ -24,7 +24,7 @@ export const VueWebpackTemplate: Template<{ webpackConfigPath: string }> = {
? '// TODO replace with valid webpack config path'
: '',
].join('\n'), { preserveComments: true }),
ModuleExportsBody: babel.template.ast([
IfComponentTestingPluginsAst: babel.template.ast([
`on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))`,
].join('\n')),
}

View File

@@ -1,47 +0,0 @@
{
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"plugin:@cypress/dev/react",
"../../packages/reporter/src/.eslintrc.json"
],
"parser": "@typescript-eslint/parser",
"env": {
"cypress/globals": true
},
"rules": {
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".js",
".jsx",
".tsx"
]
}
]
},
"overrides": [
{
"files": [
"lib/*"
],
"rules": {
"no-console": 1
}
},
{
"files": [
"**/*.json"
],
"rules": {
"quotes": "off",
"comma-dangle": "off"
}
}
]
}

View File

@@ -0,0 +1,137 @@
{
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"plugin:@cypress/dev/react",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"../../packages/reporter/src/.eslintrc.json"
],
"parser": "@typescript-eslint/parser",
"env": {
"cypress/globals": true
},
"rules": {
"react/display-name": "off",
"react/function-component-definition": [
"error",
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
"react/jsx-boolean-value": [
"error",
"always"
],
"react/jsx-closing-bracket-location": [
"error",
"line-aligned"
],
"react/jsx-closing-tag-location": "error",
"react/jsx-curly-brace-presence": [
"error",
{
"props": "never",
"children": "never"
}
],
"react/jsx-curly-newline": "error",
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".js",
".jsx",
".tsx"
]
}
],
"react/jsx-first-prop-new-line": "error",
"react/jsx-max-props-per-line": [
"error",
{
"maximum": 1,
"when": "multiline"
}
],
"react/jsx-no-bind": [
"error",
{
"ignoreDOMComponents": true
}
],
"react/jsx-no-useless-fragment": "error",
"react/jsx-one-expression-per-line": [
"error",
{
"allow": "literal"
}
],
"react/jsx-sort-props": [
"error",
{
"callbacksLast": true,
"ignoreCase": true,
"noSortAlphabetically": true,
"reservedFirst": true
}
],
"react/jsx-tag-spacing": [
"error",
{
"closingSlash": "never",
"beforeSelfClosing": "always"
}
],
"react/jsx-wrap-multilines": [
"error",
{
"declaration": "parens-new-line",
"assignment": "parens-new-line",
"return": "parens-new-line",
"arrow": "parens-new-line",
"condition": "parens-new-line",
"logical": "parens-new-line",
"prop": "parens-new-line"
}
],
"react/no-array-index-key": "error",
"react/no-unescaped-entities": "off",
"react/prop-types": "off",
"quote-props": [
"error",
"as-needed"
]
},
"overrides": [
{
"files": [
"lib/*"
],
"rules": {
"no-console": 1
}
},
{
"files": [
"**/*.json"
],
"rules": {
"quotes": "off",
"comma-dangle": "off"
}
},
{
"files": "*.spec.tsx",
"rules": {
"no-unused-vars": "off",
"react/jsx-no-bind": "off"
}
}
]
}

View File

@@ -0,0 +1,8 @@
{
"watch-ignore": [
"./test/_test-output",
"node_modules"
],
"require": "ts-node/register",
"exit": true
}

View File

@@ -1,4 +1,5 @@
module.exports = {
plugins: ['@babel/plugin-proposal-optional-chaining'],
presets: [
'@babel/preset-env',
'@babel/preset-react',

View File

@@ -14,6 +14,5 @@
"componentFolder": "src",
"experimentalComponentTesting": true,
"experimentalFetchPolyfill": true,
"fixturesFolder": false,
"supportFile": false
"fixturesFolder": false
}

View File

@@ -0,0 +1,2 @@
import 'regenerator-runtime/runtime'
import 'cypress-real-events/support'

View File

@@ -26,6 +26,7 @@
},
"devDependencies": {
"@babel/core": "7.4.5",
"@babel/plugin-proposal-optional-chaining": "^7.13.8",
"@babel/preset-env": "7.4.5",
"@babel/preset-react": "7.0.0",
"@babel/preset-typescript": "7.10.4",
@@ -41,9 +42,13 @@
"babel-loader": "8.0.6",
"css-loader": "2.1.1",
"cypress": "0.0.0-development",
"cypress-real-events": "1.1.0",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "16.8.6",
"react-dom": "16.8.6",
"rollup": "^2.38.5",
"rollup-plugin-copy-assets": "2.0.3",
"rollup-plugin-peer-deps-external": "2.2.4",
"rollup-plugin-postcss-modules": "2.0.2",
"rollup-plugin-typescript2": "^0.29.0",
@@ -79,9 +84,6 @@
"publishConfig": {
"access": "restricted"
},
"ciJobs": [
"npm-design-system"
],
"standard": {
"globals": [
"Cypress",
@@ -89,4 +91,4 @@
"expect"
]
}
}
}

View File

@@ -6,6 +6,7 @@ import peerDepsExternal from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss-modules'
import pkg from './package.json'
import image from '@rollup/plugin-image'
import copy from 'rollup-plugin-copy-assets'
const banner = `
/**
@@ -29,7 +30,17 @@ function createEntry (options) {
'react-dom',
],
plugins: [
peerDepsExternal(), resolve(), json(), commonjs(), postcss({ writeDefinitions: false }), image(),
peerDepsExternal(),
resolve(),
json(),
commonjs(),
postcss({ writeDefinitions: false }),
image(),
copy({
assets: [
'./index.scss',
],
}),
],
output: {
banner,

View File

@@ -1,4 +1,6 @@
import React from 'react'
import { button } from './Button.module.scss'
export const Button = () => (<button className={button}>Hello World</button>)
export const Button = () => (
<button className={button}>Hello World</button>
)

View File

@@ -16,8 +16,10 @@ interface LogoProps {
export const CypressLogo: React.FC<LogoProps> = (props) => {
return (
<img className={styles.logo}
<img
className={styles.logo}
style={{ width: sizes[props.size] }}
src={LogoPng} />
src={LogoPng}
/>
)
}

View File

@@ -59,7 +59,8 @@ describe('LeftNav', () => {
href: '#foo',
},
},
]} />)
]}
/>)
cy.get('a').first().eq(0).click().url().should('include', '#foo')
})
@@ -71,7 +72,8 @@ describe('LeftNav', () => {
<div style={{
height: 1000,
width: 1000,
}}>
}}
>
This is the main page content
</div>
</div>

View File

@@ -1,6 +1,10 @@
import * as React from 'react'
import cs from 'classnames'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
library.add(fas)
import styles from './LeftNav.module.scss'
import { LeftNavProps, NavButtonProps, NavLocation, NavItem } from './types'
@@ -50,8 +54,8 @@ export const LeftNav: React.FC<LeftNavProps> = ({ items, activeIndex, leftNavCla
const topNav = (
<nav
className={styles.top}
key='nav-section-top'
className={styles.top}
>
{mappedItems.top.map((item) => navItem(item))}
</nav>
@@ -59,8 +63,8 @@ export const LeftNav: React.FC<LeftNavProps> = ({ items, activeIndex, leftNavCla
const bottomNav = (
<nav
className={styles.bottom}
key='nav-section-bottom'
className={styles.bottom}
>
{mappedItems.bottom.map((item) => navItem(item))}
</nav>

View File

@@ -12,13 +12,15 @@ library.add(fab)
describe('Playground', () => {
it('cypress logo', () => {
mount(<>
<CypressLogo size="small" />
<br/>
<CypressLogo size="medium" />
<br/>
<CypressLogo size="large" />
</>)
mount(
<>
<CypressLogo size="small" />
<br />
<CypressLogo size="medium" />
<br />
<CypressLogo size="large" />
</>,
)
})
it('search input', () => {
@@ -26,26 +28,31 @@ describe('Playground', () => {
const [value, setValue] = React.useState(props.value || '')
const inputRef = React.useRef<HTMLInputElement>(null)
return (<SearchInput
prefixIcon={props.prefixIcon}
onSuffixClicked={() => {
setValue('')
inputRef.current.focus()
}}
placeholder={props.placeholder}
inputRef={inputRef}
value={value}
onChange={(event) => setValue(event.target.value)}>
</SearchInput>)
return (
<SearchInput
prefixIcon={props.prefixIcon}
placeholder={props.placeholder}
inputRef={inputRef}
value={value}
onSuffixClicked={() => {
setValue('')
inputRef.current.focus()
}}
onChange={(event) => setValue(event.target.value)}
>
</SearchInput>
)
}
mount(<>
<Wrapper placeholder="Find components..." prefixIcon="search" />
<br/>
{/* <Wrapper placeholder="Find components..." prefixIcon="coffee"/> */}
<br/>
{/* <Wrapper placeholder="Find components..." prefixIcon="search" suffixIcon="times"/> */}
</>)
mount(
<>
<Wrapper placeholder="Find components..." prefixIcon="search" />
<br />
{/* <Wrapper placeholder="Find components..." prefixIcon="coffee"/> */}
<br />
{/* <Wrapper placeholder="Find components..." prefixIcon="search" suffixIcon="times"/> */}
</>,
)
cy.get('input').should('exist')
cy.get('input').should('exist').first()

View File

@@ -2,9 +2,7 @@
$active-color: $metal-50;
$inactive-color: $metal-20;
$input-color: $metal-50;
$input-width: 120px;
@mixin quick-transition($param) {
transition: $param 0.15s ease-in-out;
@@ -16,13 +14,12 @@ $input-width: 120px;
color: $input-color;
padding: 0 $text-m 0 $text-xxs;
width: 100%;
max-width: $input-width;
border: unset;
outline: unset;
border-bottom: 1px solid $inactive-color;
&.hasPrefix {
padding: 0 $text-m;
padding: 0.2rem $text-m;
}
}
@@ -45,11 +42,14 @@ $icon-margin: calc(#{$icon-size} / 3);
.suffix {
margin: $icon-margin $icon-negative-margin;
right: 20px;
}
.inputButton {
position: relative;
display: flex;
margin: 1rem;
align-items: center;
&:active, &:focus-within {
.searchInput {

View File

@@ -12,6 +12,8 @@ interface SearchInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
}
export const SearchInput: React.FC<SearchInputProps> = (props) => {
const { onSuffixClicked } = props
const prefixIcon = props.prefixIcon && (
<FontAwesomeIcon
className={styles.prefix}
@@ -19,27 +21,34 @@ export const SearchInput: React.FC<SearchInputProps> = (props) => {
/>
)
const onKeyPress = React.useCallback((e: React.KeyboardEvent<SVGSVGElement>) => {
if (e.key === 'Enter') {
onSuffixClicked?.()
}
}, [onSuffixClicked])
return (
<span className={styles.inputButton}>
{prefixIcon}
<input
type="text"
ref={props.inputRef}
type="text"
className={cs([styles.searchInput, props.prefixIcon ? styles.hasPrefix : ''])}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
/>
{
props.value &&
<FontAwesomeIcon
data-testid="close"
className={styles.suffix}
tabIndex={0}
icon="times"
onClick={props.onSuffixClicked}
onKeyPress={(e) => e.key === 'Enter' && props.onSuffixClicked && props.onSuffixClicked()}
/>
props.value && (
<FontAwesomeIcon
data-testid="close"
className={styles.suffix}
tabIndex={0}
icon="times"
onClick={props.onSuffixClicked}
onKeyPress={onKeyPress}
/>
)
}
</span>
)

View File

@@ -5,7 +5,7 @@
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"skipLibCheck": true,
"lib": [
"es2015",
"es2016",
"dom"
] /* Specify library files to be included in the compilation: */,
"declaration": true, /* Generates corresponding '.d.ts' file. */

View File

@@ -214,6 +214,9 @@ module.exports = {
json: {
'sort-package-json': 'pro',
},
react: {
version: 'detect',
},
},
env: {
node: true,
@@ -252,12 +255,16 @@ module.exports = {
},
},
{
files: '*.ts',
files: [
'*.ts',
'*.tsx',
],
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
rules: {
'no-undef': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',

View File

@@ -17,9 +17,9 @@
"shelljs": "^0.8.3"
},
"devDependencies": {
"eslint": "^6.1.0",
"eslint": "^7.22.0",
"eslint-plugin-json-format": "^2.0.0",
"eslint-plugin-mocha": "^5.3.0",
"eslint-plugin-mocha": "^8.1.0",
"eslint-plugin-promise": "^4.2.1",
"jest": "^24.8.0",
"jest-cli": "^24.8.0",
@@ -33,7 +33,7 @@
"eslint": ">= 3.2.1",
"eslint-plugin-json-format": ">= 2.0.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.2.1"
"eslint-plugin-react": "^7.22.0"
},
"bin": {
"lint-changed": "./lib/scripts/lint-changed.js",
@@ -53,8 +53,5 @@
"cypress",
"eslint",
"eslintplugin"
],
"ciJobs": [
"npm-eslint-plugin-dev"
]
}
}

View File

@@ -12,6 +12,7 @@
"env": {
"cypress/globals": true
},
"root": true,
"globals": {
"jest": "readonly"
},

View File

@@ -1,7 +1,12 @@
/**
* NOTICE
* Disable the release for React until further notice.
* You may re-enable it by toggling publishConfig: restricted/public
* and uncommenting the following lines
*/
module.exports = {
...require('../../.releaserc.base'),
branches: [
{ name: '' }, // avoid releasing lastest channel until further notice
{ name: 'master', channel: 'next', prerelease: 'alpha' },
{ name: 'master', channel: 'next' },
],
}

View File

@@ -1,8 +1,16 @@
# [@cypress/react-v5.1.0-alpha.1](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.0.1...@cypress/react-v5.1.0-alpha.1) (2021-03-10)
# [@cypress/react-v5.1.2](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.1.1...@cypress/react-v5.1.2) (2021-03-16)
### Bug Fixes
* make react@next match vue@next ([5cbecb8](https://github.com/cypress-io/cypress/commit/5cbecb8a5e93484be6552f5a0303b70b8d4f1783))
* add missing script for building wizard ([#15502](https://github.com/cypress-io/cypress/issues/15502)) ([393a8ca](https://github.com/cypress-io/cypress/commit/393a8ca9cac905e0f6d8623bff889b041dd076b6))
# [@cypress/react-v5.1.1](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.1.0...@cypress/react-v5.1.1) (2021-03-16)
### Bug Fixes
* Revert cypress.json changes ([#15499](https://github.com/cypress-io/cypress/issues/15499)) ([237c426](https://github.com/cypress-io/cypress/commit/237c426707714a287ff20ef2bdabff5f0c39e93a))
# [@cypress/react-v5.1.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.0.1...@cypress/react-v5.1.0) (2021-03-15)
@@ -24,14 +32,6 @@
# [@cypress/react-v5.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.0.0...@cypress/react-v5.0.1) (2021-02-17)
### Bug Fixes
* trigger semantic release ([#15128](https://github.com/cypress-io/cypress/issues/15128)) ([3a6f3b1](https://github.com/cypress-io/cypress/commit/3a6f3b1928277f7086062b1107f424e5a0247e00))
# [@cypress/react-v5.0.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v4.16.4...@cypress/react-v5.0.0) (2021-02-17)
### Bug Fixes
* update dependencies of npm/react-vue ([#15095](https://github.com/cypress-io/cypress/issues/15095)) ([e028262](https://github.com/cypress-io/cypress/commit/e028262aed485865c4f40162c1f8102970ef91f8))
@@ -45,31 +45,11 @@
### BREAKING CHANGES
* change of architecture for
component testing
Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
# [@cypress/react-v5.0.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v4.16.4...@cypress/react-v5.0.0) (2021-02-16)
### Bug Fixes
* update dependencies of npm/react-vue ([#15095](https://github.com/cypress-io/cypress/issues/15095)) ([e028262](https://github.com/cypress-io/cypress/commit/e028262aed485865c4f40162c1f8102970ef91f8))
* **component-testing:** make content adjust to size of window ([#14876](https://github.com/cypress-io/cypress/issues/14876)) ([4cf3896](https://github.com/cypress-io/cypress/commit/4cf3896ecbb074831709f73f22768457fdaf5779))
### Features
* component testing ([#14479](https://github.com/cypress-io/cypress/issues/14479)) ([af26fbe](https://github.com/cypress-io/cypress/commit/af26fbebe6bc609132013a0493a116cc78bb1bd4))
### BREAKING CHANGES
* change of architecture for
component testing
Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
* Added the need to install a preprocessor or a dev-server plugin
* Removed the pre-instalation of test coverage
* Install it manually by following [the documentation](https://docs.cypress.io/guides/tooling/code-coverage.html#Introduction)
* removed the pre-installation of `cypress-react-selector`
* If you use `cy.react()` in your tests, the command will not work anymore. [Install it back in your support file](https://www.npmjs.com/package/cypress-react-selector)
# [@cypress/react-v4.16.4](https://github.com/cypress-io/cypress/compare/@cypress/react-v4.16.3...@cypress/react-v4.16.4) (2021-01-27)

View File

@@ -65,18 +65,12 @@ npm install --save-dev cypress @cypress/react
You can use our command line wizard to give you instructions on configuring this plugin. It will try to determine which framework or bundling tool you are using and give you instructions on right configuration.
```sh
@cypress/react init
npx create-cypress-tests --component-testing
```
Or continue with manual installation:
Or continue with manual installation in the plugin file
1. Include this plugin from your project's `cypress/support/index.js`
```js
require('@cypress/react/support')
```
2. Tell Cypress how your React application is transpiled or bundled (using Webpack), so Cypress can load your components. For example, if you use `react-scripts` (even after ejecting) do:
1. Tell Cypress how your React application is transpiled or bundled (using Webpack), so Cypress can load your components. For example, if you use `react-scripts` (even after ejecting) do:
```js
// cypress/plugins/index.js
@@ -90,11 +84,10 @@ module.exports = (on, config) => {
See [Recipes](./docs/recipes.md) for more examples.
3. ⚠️ Turn the experimental component support on in your `cypress.json`. You can also specify where component spec files are located. For example, to have them located in `src` folder use:
2. You can specify where component spec files are located. For example, to have them located in `src` folder use:
```json
{
"experimentalComponentTesting": true,
"componentFolder": "src"
}
```

View File

@@ -1,6 +1,9 @@
import * as React from 'react'
const LazyDog = React.lazy(() => import(/* webpackChunkName: "Dog" */ './Dog'))
const LazyDog = React.lazy(() => {
return import(/* webpackChunkName: "Dog" */ './Dog')
.then((comp) => new Promise((resolve) => setTimeout(() => resolve(comp), 10)))
})
interface LazyComponentProps {}

View File

@@ -61,6 +61,10 @@ const webpackConfig = {
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
if (config.testingType !== 'component') {
throw Error(`This is a component testing project. testingType should be 'component'. Received ${config.testingType}`)
}
on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig, disableLazyCompilation: false })
})

View File

@@ -13,7 +13,9 @@
"@cypress/react": "file:../../dist",
"check-code-coverage": "1.9.2",
"cypress-circleci-reporter": "0.2.0",
"react-scripts": "3.4.3"
"react": "17.0.1",
"react-dom": "17.0.1",
"react-scripts": "4.0.2"
},
"browserslist": {
"production": [

View File

@@ -1,4 +1,5 @@
import React from 'react'
import ReactDom from 'react-dom'
import RemotePizza from './RemotePizza'
import { mount } from '@cypress/react'
@@ -10,7 +11,7 @@ describe('RemotePizza', () => {
'pizza',
)
mount(<RemotePizza />)
mount(<RemotePizza />, { ReactDom })
cy.contains('button', /cook/i).click()
cy.wait('@pizza') // make sure the network stub was used
@@ -22,7 +23,7 @@ describe('RemotePizza', () => {
it('stubs via prop (di)', () => {
const fetchIngredients = cy.stub().resolves({ args: { ingredients } })
mount(<RemotePizza fetchIngredients={fetchIngredients} />)
mount(<RemotePizza fetchIngredients={fetchIngredients} />, { ReactDom })
cy.contains('button', /cook/i).click()
for (const ingredient of ingredients) {
@@ -35,7 +36,7 @@ describe('RemotePizza', () => {
args: { ingredients },
})
mount(<RemotePizza />)
mount(<RemotePizza />, { ReactDom })
cy.contains('button', /cook/i).click()
for (const ingredient of ingredients) {

File diff suppressed because it is too large Load Diff

View File

@@ -313,7 +313,6 @@ sudoku._search = function(candidates, reverse) {
// If only one candidate for every square, we've a solved puzzle!
// Return the candidates map.
var max_nr_candidates = 0
// eslint-disable-next-line
var max_candidates_square = null
for (var si in SQUARES) {
var square = SQUARES[si]
@@ -322,6 +321,7 @@ sudoku._search = function(candidates, reverse) {
if (nr_candidates > max_nr_candidates) {
max_nr_candidates = nr_candidates
// eslint-disable-next-line no-unused-vars
max_candidates_square = square
}
}

View File

@@ -154,11 +154,8 @@
}
},
"publishConfig": {
"access": "public"
"access": "restricted"
},
"ciJobs": [
"npm-react"
],
"standard": {
"globals": [
"Cypress",

View File

@@ -83,6 +83,10 @@ const addCypressToWebpackEslintRulesInPlace = (webpackOptions) => {
}
module.exports = function findReactScriptsWebpackConfig (config) {
// this is required because
// 1) we use our own HMR and we don't need react-refresh transpiling overhead
// 2) it doesn't work with process.env=test @see https://github.com/cypress-io/cypress-realworld-app/pull/832
process.env.FAST_REFRESH = 'false'
const webpackConfig = tryLoadWebpackConfig('react-scripts/config/webpack.config')
if (!webpackConfig) {

View File

@@ -9,6 +9,7 @@ interface Options {
specs: Cypress.Cypress['spec'][] // Why isn't this working? It works for webpack-dev-server
config: Record<string, string>
devServerEvents: EventEmitter
devServerPublicPathRoute: string
[key: string]: unknown
}

View File

@@ -15,16 +15,16 @@ const readIndexHtml = () => readFileSync(indexHtmlPath).toString()
* Example usage:
* formatSpecName('/cypress/component/foo.spec.tsx') //=> 'foo.spec.js'
*/
function formatSpecName (filename: string) {
function formatSpecName (publicPath: string, filename: string) {
const split = filename.split('/')
const name = split[split.length - 1]
const pos = name.lastIndexOf('.')
const newName = `${name.substr(0, pos < 0 ? name.length : pos)}.js`
return `/${newName}`
return `${publicPath}/${newName}`
}
function handleIndex (indexHtml: string, projectRoot: string, supportFilePath: string, cypressSpecPath: string) {
function handleIndex (indexHtml: string, publicPath: string, supportFilePath: string, cypressSpecPath: string) {
const specPath = `/${cypressSpecPath}`
console.log(supportFilePath)
@@ -32,7 +32,7 @@ function handleIndex (indexHtml: string, projectRoot: string, supportFilePath: s
return render(indexHtml, {
supportFile,
specPath: formatSpecName(specPath),
specPath: formatSpecName(publicPath, specPath),
})
}
@@ -40,13 +40,14 @@ export const makeHtmlPlugin = (
projectRoot: string,
supportFilePath: string,
server: Express,
publicPath: string,
) => {
const indexHtml = readIndexHtml()
server.use('/index.html', (req, res) => {
server.use(`${publicPath}/index.html`, (req, res) => {
const html = handleIndex(
indexHtml,
projectRoot,
publicPath,
supportFilePath,
req.headers.__cypress_spec_path as string,
)

View File

@@ -41,6 +41,7 @@ interface NollupDevServer {
}
export async function start (devServerOptions: StartDevServer): Promise<NollupDevServer> {
console.log('OBject', Object.keys(devServerOptions.options))
const rollupConfigObj = typeof devServerOptions.rollupConfig === 'string'
? await loadConfigFile(devServerOptions.rollupConfig).then((configResult) => configResult.options)
: devServerOptions.rollupConfig
@@ -58,25 +59,28 @@ export async function start (devServerOptions: StartDevServer): Promise<NollupDe
}
})
const { devServerPublicPathRoute, projectRoot, supportFile } = devServerOptions.options.config
const app = express()
const server = http.createServer(app)
const contentBase = resolve(__dirname, devServerOptions.options.config.projectRoot)
const contentBase = resolve(__dirname, projectRoot)
/* random port between 3000 and 23000 */
const port = parseInt(((Math.random() * 20000) + 3000).toFixed(0), 10)
const nollup = NollupDevMiddleware(app, config, {
contentBase,
port,
publicPath: '/',
publicPath: devServerPublicPathRoute,
hot: true,
}, server)
app.use(nollup)
makeHtmlPlugin(
devServerOptions.options.config.projectRoot,
devServerOptions.options.config.supportFile,
projectRoot,
supportFile,
app,
devServerPublicPathRoute,
)
return {

View File

@@ -35,8 +35,5 @@
"bugs": "https://github.com/cypress-io/cypress/issues/new?template=1-bug-report.md",
"publishConfig": {
"access": "public"
},
"ciJobs": [
"npm-vite-dev-server"
]
}
}

View File

@@ -8,8 +8,8 @@ const pluginName = 'cypress-transform-html'
const indexHtmlPath = resolve(__dirname, '../index-template.html')
const readIndexHtml = () => readFileSync(indexHtmlPath).toString()
function handleIndex (indexHtml, projectRoot, supportFilePath, req, res) {
const specPath = `/${req.headers.__cypress_spec_path}`
function handleIndex (indexHtml, projectRoot, publicPath, supportFilePath, req, res) {
const specPath = `${publicPath}/${req.headers.__cypress_spec_path}`
const supportPath = supportFilePath ? `/${relative(projectRoot, supportFilePath)}` : null
res.end(render(indexHtml, { supportPath, specPath }))
@@ -18,6 +18,7 @@ function handleIndex (indexHtml, projectRoot, supportFilePath, req, res) {
export const makeCypressPlugin = (
projectRoot: string,
supportFilePath: string,
publicPath: string,
devServerEvents: EventEmitter,
): Plugin => {
return {
@@ -26,7 +27,7 @@ export const makeCypressPlugin = (
configureServer: (server: ViteDevServer) => {
const indexHtml = readIndexHtml()
server.middlewares.use('/index.html', (req, res) => handleIndex(indexHtml, projectRoot, supportFilePath, req, res))
server.middlewares.use(`${publicPath}/index.html`, (req, res) => handleIndex(indexHtml, projectRoot, publicPath, supportFilePath, req, res))
},
handleHotUpdate: () => {
devServerEvents.emit('dev-server:compile:success')

View File

@@ -1,27 +0,0 @@
import { relative, resolve } from 'path'
import { readFileSync } from 'fs'
import { Plugin, ViteDevServer } from 'vite'
import { render } from 'mustache'
const pluginName = 'cypress-transform-html'
const indexHtmlPath = resolve(__dirname, '../index-template.html')
const readIndexHtml = () => readFileSync(indexHtmlPath).toString()
function handleIndex (indexHtml, projectRoot, supportFilePath, req, res) {
const specPath = `/${req.headers.__cypress_spec_path}`
const supportPath = supportFilePath ? `/${relative(projectRoot, supportFilePath)}` : null
res.end(render(indexHtml, { supportPath, specPath }))
}
export const makeHtmlPlugin = (projectRoot: string, supportFilePath: string): Plugin => {
return {
name: pluginName,
enforce: 'pre',
configureServer: (server: ViteDevServer) => {
const indexHtml = readIndexHtml()
server.middlewares.use('/index.html', (req, res) => handleIndex(indexHtml, projectRoot, supportFilePath, req, res))
},
}
}

View File

@@ -8,11 +8,11 @@ import { EventEmitter } from 'events'
const debug = Debug('cypress:vite-dev-server:start')
// TODO: Pull in types for Options so we can infer these
const serverConfig = (projectRoot: string, supportFilePath: string, devServerEvents: EventEmitter): InlineConfig => {
const serverConfig = (projectRoot: string, supportFilePath: string, publicPath: string, devServerEvents: EventEmitter): InlineConfig => {
return {
root: resolve(__dirname, projectRoot),
base: '/__cypress/src/',
plugins: [makeCypressPlugin(projectRoot, supportFilePath, devServerEvents)],
plugins: [makeCypressPlugin(projectRoot, supportFilePath, publicPath, devServerEvents)],
server: {
port: 0,
},
@@ -23,6 +23,7 @@ const resolveServerConfig = ({ viteConfig, options }: StartDevServer) => {
const defaultServerConfig = serverConfig(
options.config.projectRoot,
options.config.supportFile,
options.config.devServerPublicPathRoute,
options.devServerEvents,
)

View File

@@ -1,3 +1,10 @@
# [@cypress/vue-v2.1.1](https://github.com/cypress-io/cypress/compare/@cypress/vue-v2.1.0...@cypress/vue-v2.1.1) (2021-03-16)
### Bug Fixes
* Revert cypress.json changes ([#15499](https://github.com/cypress-io/cypress/issues/15499)) ([237c426](https://github.com/cypress-io/cypress/commit/237c426707714a287ff20ef2bdabff5f0c39e93a))
# [@cypress/vue-v2.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue-v2.0.2...@cypress/vue-v2.1.0) (2021-03-15)

View File

@@ -6,6 +6,10 @@ const webpackConfig = require('../../webpack.config')
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
if (config.testingType !== 'component') {
throw Error(`This is a component testing project. testingType should be 'component'. Received ${config.testingType}`)
}
require('@cypress/code-coverage/task')(on, config)
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))

View File

@@ -1 +0,0 @@
require('../../dist/support')

View File

@@ -35,8 +35,8 @@
"@vue/cli-plugin-eslint": "~4.4.0",
"@vue/cli-service": "~4.4.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint": "^7.22.0",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
}
}
}

View File

@@ -12,8 +12,7 @@
"watch": "yarn build --watch --watch.exclude ./dist/**/*"
},
"dependencies": {
"@vue/test-utils": "1.0.3",
"unfetch": "4.1.0"
"@vue/test-utils": "^1.1.3"
},
"devDependencies": {
"@babel/core": "7.9.0",
@@ -31,6 +30,7 @@
"babel-plugin-istanbul": "6.0.0",
"css-loader": "3.4.2",
"cypress": "0.0.0-development",
"cypress-circleci-reporter": "0.2.0",
"debug": "4.3.2",
"eslint-plugin-vue": "^6.2.2",
"find-webpack": "2.1.0",
@@ -40,6 +40,7 @@
"rollup-plugin-typescript2": "^0.29.0",
"tailwindcss": "1.1.4",
"typescript": "3.9.6",
"unfetch": "4.1.0",
"vue": "2.6.11",
"vue-i18n": "8.9.0",
"vue-loader": "15.9.3",
@@ -85,8 +86,5 @@
"publishConfig": {
"access": "public",
"registry": "http://registry.npmjs.org/"
},
"ciJobs": [
"npm-vue"
]
}
}

View File

@@ -5,8 +5,10 @@ import {
mount as testUtilsMount,
VueTestUtilsConfigOptions,
Wrapper,
enableAutoDestroy,
} from '@vue/test-utils'
import { renderTestingPlatform, ROOT_ID } from './renderTestingPlatform'
const ROOT_ID = '__cy_root'
const defaultOptions: (keyof MountOptions)[] = [
'vue',
@@ -63,7 +65,7 @@ const installMixins = (Vue, options) => {
}
}
const hasStore = ({ store }: { store: any }) => store && store._vm // @ts-ignore
const hasStore = ({ store }: { store: any }) => Boolean(store && store._vm)
const forEachValue = <T>(obj: Record<string, T>, fn: (value: T, key: string) => void) => {
return Object.keys(obj).forEach((key) => fn(obj[key], key))
@@ -305,6 +307,12 @@ function failTestOnVueError (err, vm, info) {
window.top.onerror(err)
}
function cyBeforeEach (cb: () => void) {
Cypress.on('test:before:run', cb)
}
enableAutoDestroy(cyBeforeEach)
/**
* Mounts a Vue component inside Cypress browser.
* @param {object} component imported from Vue file
@@ -357,14 +365,8 @@ export const mount = (
// @ts-ignore
const document: Document = cy.state('document')
document.body.innerHTML = ''
let el = document.getElementById(ROOT_ID)
// If the target div doesn't exist, create it
if (!el) {
el = renderTestingPlatform(document.head.innerHTML)
}
if (typeof options.stylesheets === 'string') {
options.stylesheets = [options.stylesheets]
}
@@ -394,11 +396,9 @@ export const mount = (
// setup Vue instance
installFilters(localVue, options)
installMixins(localVue, options)
// @ts-ignore
installPlugins(localVue, options, props)
registerGlobalComponents(localVue, options)
// @ts-ignore
props.attachTo = componentNode
const wrapper = localVue.extend(component as any)

View File

@@ -1,20 +0,0 @@
export const ROOT_ID = '__cy_root'
/** Initialize an empty document with root element
* This only needs for experimentalComponentTesting
*/
export function renderTestingPlatform (headInnerHTML: string) {
// @ts-expect-error no idea
const document = cy.state('document')
if (document.body) document.body.innerHTML = ''
if (document.head) document.head.innerHTML = headInnerHTML
const rootNode = document.createElement('div')
rootNode.setAttribute('id', ROOT_ID)
document.getElementsByTagName('body')[0].prepend(rootNode)
return rootNode
}

View File

@@ -1,16 +1,5 @@
/* eslint-env mocha */
// empty to keep backwards compat
const { renderTestingPlatform } = require('./renderTestingPlatform')
// FIXME: delete in next major
let headInnerHTML = document.head.innerHTML
beforeEach(() => {
renderTestingPlatform(headInnerHTML)
})
before(() => {
// after the root imports are done
const document = cy.state('document')
headInnerHTML = document.head && document.head.innerHTML
})
console.error('Avoid using this file, it will be deleted in the next major')

View File

@@ -28,14 +28,14 @@
"@cypress/webpack-preprocessor": "0.0.0-development",
"@types/mocha": "^8.0.2",
"@types/webpack": "^4.41.21",
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"babel-eslint": "^10.1.0",
"chai": "^4.2.0",
"eslint": "^7.6.0",
"eslint": "^7.22.0",
"eslint-plugin-json-format": "^2.0.1",
"eslint-plugin-mocha": "^8.0.0",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-mocha": "^8.1.0",
"eslint-plugin-react": "^7.22.0",
"fs-extra": "^9.0.1",
"graphql": "14.0.0",
"mocha": "^8.1.1",
@@ -65,8 +65,5 @@
],
"publishConfig": {
"access": "public"
},
"ciJobs": [
"npm-webpack-batteries-included-preprocessor"
]
}
}
}

View File

@@ -42,12 +42,5 @@
"bugs": "https://github.com/cypress-io/cypress/issues/new?template=1-bug-report.md",
"publishConfig": {
"access": "public"
},
"ciDependents": [
"@cypress/react",
"@cypress/vue"
],
"ciJobs": [
"npm-webpack-dev-server"
]
}
}

View File

@@ -1,14 +1,9 @@
/*eslint-env browser,mocha*/
/* eslint-disable no-console */
/*eslint-env browser */
function appendTargetIfNotExists (id: string, tag = 'div', parent = document.body) {
let node = document.getElementById(id)
if (node) {
// it is required to completely remove node from the document
// cause framework can store the information between renders inside the root node (like react-dom is doing)
node.parentElement.removeChild(node)
}
node = document.createElement(tag)
node.setAttribute('id', id)
parent.appendChild(node)
@@ -26,11 +21,22 @@ export function init (importPromises, parent = (window.opener || window.parent))
Cypress.onSpecWindow(window, importPromises)
Cypress.action('app:window:before:load', window)
// In this variable, we save head
// innerHTML to account for loader installed styles
let headInnerHTML = ''
// before the run starts save
Cypress.on('run:start', () => {
headInnerHTML = document.head.innerHTML
})
// Before all tests we are mounting the root element, **not beforeEach**
// Cleaning up platform between tests is the responsibility of the specific adapter
// because unmounting react/vue component should be done using specific framework API
// (for devtools and to get rid of global event listeners from previous tests.)
before(() => {
Cypress.on('test:before:run', () => {
document.body.innerHTML = ''
document.head.innerHTML = headInnerHTML
appendTargetIfNotExists('__cy_root')
})

View File

@@ -18,7 +18,7 @@ export interface UserWebpackDevServerOptions {
}
interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions, UserWebpackDevServerOptions {
webpackDevServerPublicPathRoute: string
devServerPublicPathRoute: string
isOpenMode: boolean
}
@@ -42,7 +42,7 @@ function getLazyCompilationWebpackConfig (options: MakeWebpackConfigOptions): we
}
export async function makeWebpackConfig (userWebpackConfig: webpack.Configuration, options: MakeWebpackConfigOptions): Promise<webpack.Configuration> {
const { projectRoot, webpackDevServerPublicPathRoute, files, supportFile, devServerEvents } = options
const { projectRoot, devServerPublicPathRoute, files, supportFile, devServerEvents } = options
debug(`User passed in webpack config with values %o`, userWebpackConfig)
@@ -52,7 +52,7 @@ export async function makeWebpackConfig (userWebpackConfig: webpack.Configuratio
const entry = path.resolve(__dirname, './browser.js')
const publicPath = mergePublicPath(webpackDevServerPublicPathRoute, userWebpackConfig?.output?.publicPath)
const publicPath = mergePublicPath(devServerPublicPathRoute, userWebpackConfig?.output?.publicPath)
const dynamicWebpackConfig = {
output: {

View File

@@ -11,13 +11,13 @@ export async function start ({ webpackConfig: userWebpackConfig, options, ...use
debug('User did not pass in any webpack configuration')
}
// @ts-expect-error ?? webpackDevServerPublicPathRoute is not a valid option of Cypress.Config
const { projectRoot, webpackDevServerPublicPathRoute, isTextTerminal } = options.config
// @ts-expect-error ?? devServerPublicPathRoute is not a valid option of Cypress.Config
const { projectRoot, devServerPublicPathRoute, isTextTerminal } = options.config
const webpackConfig = await makeWebpackConfig(userWebpackConfig || {}, {
files: options.specs,
projectRoot,
webpackDevServerPublicPathRoute,
devServerPublicPathRoute,
devServerEvents: options.devServerEvents,
supportFile: options.config.supportFile as string,
isOpenMode: !isTextTerminal,
@@ -57,6 +57,7 @@ export async function start ({ webpackConfig: userWebpackConfig, options, ...use
...userWebpackConfig.devServer,
hot: false,
inline: false,
publicPath: devServerPublicPathRoute,
}
return new WebpackDevServer(compiler, webpackDevServerConfig)

View File

@@ -53,7 +53,7 @@ const config = {
projectRoot: root,
supportFile: '',
isTextTerminal: true,
webpackDevServerPublicPathRoute: root,
devServerPublicPathRoute: root,
} as any as Cypress.ResolvedConfigOptions & Cypress.RuntimeConfigOptions
describe('#startDevServer', () => {

View File

@@ -6,7 +6,7 @@
"main": "dist",
"scripts": {
"ban": "ban",
"build": "rm -rf dist && tsc",
"build": "shx rm -rf dist && tsc",
"build-prod": "yarn build",
"deps": "deps-ok && dependency-check --no-dev .",
"license": "license-checker --production --onlyunknown --csv",
@@ -32,8 +32,8 @@
"@babel/preset-env": "^7.0.0",
"@fellow/eslint-plugin-coffee": "0.4.13",
"@types/webpack": "4.41.12",
"@typescript-eslint/eslint-plugin": "2.31.0",
"@typescript-eslint/parser": "2.31.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"babel-loader": "^8.0.2",
"ban-sensitive-files": "1.9.0",
"chai": "4.1.2",
@@ -42,10 +42,10 @@
"cypress": "0.0.0-development",
"dependency-check": "2.9.1",
"deps-ok": "1.2.1",
"eslint": "6.8.0",
"eslint": "7.22.0",
"eslint-plugin-cypress": "2.11.2",
"eslint-plugin-json-format": "2.0.1",
"eslint-plugin-mocha": "4.11.0",
"eslint-plugin-mocha": "8.1.0",
"fast-glob": "3.1.1",
"find-webpack": "1.5.0",
"fs-extra": "8.1.0",
@@ -58,6 +58,7 @@
"react-dom": "16.13.1",
"react-scripts": "3.2",
"semantic-release": "17.0.4",
"shx": "0.3.3",
"sinon": "^9.0.0",
"sinon-chai": "^3.5.0",
"snap-shot-it": "7.9.2",
@@ -88,11 +89,5 @@
"cypress-plugin",
"cypress-preprocessor",
"webpack"
],
"ciDependents": [
"@cypress/react"
],
"ciJobs": [
"npm-webpack-preprocessor"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "cypress",
"version": "6.7.1",
"version": "6.8.0",
"description": "Cypress.io end to end testing tool",
"private": true,
"scripts": {
@@ -52,6 +52,7 @@
"test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"test-mocha": "mocha --reporter spec scripts/spec.js",
"test-mocha-snapshot": "mocha scripts/mocha-snapshot-spec.js",
"test-npm-package-release-script": "npx lerna exec --scope \"@cypress/*\" -- npx --no-install semantic-release --dry-run",
"test-s3-api": "node -r ./packages/ts/register scripts/binary/s3-api-demo.ts",
"test-scripts": "mocha -r packages/ts/register --reporter spec 'scripts/unit/**/*spec.js'",
"test-scripts-watch": "yarn test-scripts --watch --watch-extensions 'ts,js'",
@@ -100,8 +101,8 @@
"@types/react-dom": "16.9.8",
"@types/request-promise": "4.1.45",
"@types/sinon-chai": "3.2.3",
"@typescript-eslint/eslint-plugin": "3",
"@typescript-eslint/parser": "3",
"@typescript-eslint/eslint-plugin": "4.18.0",
"@typescript-eslint/parser": "4.18.0",
"ansi-styles": "3.2.1",
"arg": "4.1.2",
"ascii-table": "0.0.9",
@@ -122,11 +123,12 @@
"electron-builder": "22.9.1",
"electron-notarize": "1.0.0",
"enzyme-adapter-react-16": "1.12.1",
"eslint": "6.8.0",
"eslint": "7.22.0",
"eslint-plugin-cypress": "2.11.2",
"eslint-plugin-json-format": "2.0.0",
"eslint-plugin-mocha": "6.1.0",
"eslint-plugin-react": "7.18.3",
"eslint-plugin-json-format": "2.0.1",
"eslint-plugin-mocha": "8.1.0",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"execa": "4.0.0",
"execa-wrap": "1.4.0",
"filesize": "4.1.2",
@@ -141,7 +143,7 @@
"gulp-debug": "4.0.0",
"gulp-rename": "1.4.0",
"hasha": "5.0.0",
"http-server": "0.12.1",
"http-server": "0.12.3",
"human-interval": "1.0.0",
"husky": "2.4.1",
"inquirer": "3.3.0",
@@ -253,4 +255,4 @@
"**/pretty-format": "26.4.0",
"**/socket.io-parser": "4.0.2"
}
}
}

View File

@@ -121,13 +121,7 @@ describe('Settings', () => {
cy.get('.config-vars').invoke('text')
.should('contain', '0:Chrome')
// make sure the main collapsible content
// has finished animating and that it has
// an empty inline style attribute
cy.get('.rc-collapse-content')
.should('not.have.class', 'rc-collapse-anim')
.should('have.attr', 'style', '')
cy.ensureAnimationsFinished()
cy.percySnapshot()
})
@@ -482,12 +476,9 @@ describe('Settings', () => {
})
it('shows message that user must be logged in to view record keys', () => {
// turn off animation to ensure panel is fully expanded in time for percy snapshot
cy.get('body').then(($body) => {
$body.append('<style>.rc-collapse-anim-active { transition: none !important; }<style>')
})
cy.get('.empty-well').should('contain', 'must be logged in')
cy.ensureAnimationsFinished()
cy.percySnapshot()
})
@@ -501,11 +492,6 @@ describe('Settings', () => {
this.ipc.getRecordKeys.onCall(1).resolves(this.keys)
// turn off animation to ensure panel is fully expanded in time for percy snapshot
cy.get('body').then(($body) => {
$body.append('<style>.rc-collapse-anim-active { transition: none !important; }<style>')
})
cy.get('.empty-well button').click()
cy.contains('Log In to Dashboard').click().should(() => {
expect(this.ipc.getRecordKeys).to.be.calledTwice
@@ -517,6 +503,7 @@ describe('Settings', () => {
// extra insurance that panel in background is fully expanded
cy.contains('You can change this key')
cy.ensureAnimationsFinished()
cy.percySnapshot()
})
})

View File

@@ -27,6 +27,7 @@ describe('Specs List', function () {
cy.stub(this.ipc, 'onboardingClosed')
cy.stub(this.ipc, 'onSpecChanged')
cy.stub(this.ipc, 'setUserEditor')
cy.stub(this.ipc, 'showNewSpecDialog').resolves({ specs: null, path: null })
this.openProject = this.util.deferred()
cy.stub(this.ipc, 'openProject').returns(this.openProject.promise)
@@ -209,6 +210,20 @@ describe('Specs List', function () {
cy.get('.file .file-name-wrapper').last().should('contain', 'last_list_spec.coffee')
cy.get('.file .file-name-wrapper').last().should('not.contain', 'admin_users')
})
it('sets focus on search files filters if user presses Cmd + F', () => {
if (Cypress.platform === 'darwin') {
cy.get('.filter').type('{cmd}F')
cy.get('.filter').should('have.focus')
}
})
it('sets focus on search files filter if user presses Ctrl + F', () => {
if (Cypress.platform !== 'darwin') {
cy.get('.filter').type('{ctrl}F')
cy.get('.filter').should('have.focus')
}
})
})
})
@@ -975,4 +990,144 @@ describe('Specs List', function () {
})
})
})
describe('new spec file', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
})
it('launches system save dialog', function () {
cy.contains('New Spec File').click().then(function () {
expect(this.ipc.showNewSpecDialog).to.be.called
})
})
context('POSIX paths', function () {
context('when file is created within project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: '/user/project/cypress/integration/new_spec.js',
relative: 'cypress/integration/new_spec.js',
}
this.ipc.showNewSpecDialog.resolves({
specs: { ...this.specs, integration: this.specs.integration.concat(this.newSpec) },
path: this.newSpec.absolute,
})
})
it('adds and highlights new spec item', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').should('have.class', 'new-spec')
})
it('scrolls the new spec item into view', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').then(function ($el) {
cy.stub($el[0], 'scrollIntoView')
cy.contains('New Spec File').click()
cy.wrap($el[0].scrollIntoView).should('be.called')
})
})
it('does not display warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
context('when file is created outside of project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: '/user/desktop/my_folder/new_spec.js',
}
this.ipc.showNewSpecDialog.resolves({
specs: this.specs,
path: this.newSpec.absolute,
})
})
it('displays a dismissable warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created')
.should('be.visible')
.closest('.notification-wrap')
.find('.notification-close')
.click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
})
context('Windows paths', function () {
beforeEach(function () {
this.ipc.getSpecs.yields(null, this.specsWindows)
})
context('when file is created within project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: 'C:\\Users\\user\\project\\cypress\\integration\\new_spec.js',
relative: 'cypress\\integration\\new_spec.js',
}
this.ipc.showNewSpecDialog.resolves({
specs: { ...this.specsWindows, integration: this.specs.integration.concat(this.newSpec) },
path: this.newSpec.absolute,
})
})
it('adds and highlights new spec item', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').should('have.class', 'new-spec')
})
it('scrolls the new spec item into view', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').then(function ($el) {
cy.stub($el[0], 'scrollIntoView')
cy.contains('New Spec File').click()
cy.wrap($el[0].scrollIntoView).should('be.called')
})
})
it('does not display warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
context('when file is created outside of project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: 'C:\\Users\\user\\Desktop\\my_folder\\new_spec.js',
}
this.ipc.showNewSpecDialog.resolves({
specs: this.specsWindows,
path: this.newSpec.absolute,
})
})
it('displays a dismissable warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created')
.should('be.visible')
.closest('.notification-wrap')
.find('.notification-close')
.click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
})
})
})

View File

@@ -67,3 +67,9 @@ Cypress.Commands.add('setAppStore', (options = {}) => {
win.AppStore.set(options)
})
})
Cypress.Commands.add('ensureAnimationsFinished', () => {
cy.get('.rc-collapse-content')
.should('not.have.class', 'rc-collapse-anim')
.should('have.attr', 'style', '')
})

View File

@@ -66,6 +66,7 @@ register('request:access')
register('setup:dashboard:project')
register('set:project:id')
register('show:directory:dialog')
register('show:new:spec:dialog')
register('updater:check', false)
register('updater:run', false)
register('window:open')

View File

@@ -1,5 +1,6 @@
.notification {
bottom: 4.8rem;
padding-left: 1.2rem;
padding-right: 1.2rem;
position: fixed;
right: 0;
@@ -97,3 +98,9 @@
right: 1rem;
z-index: 9999;
}
.new-spec-warning {
.content i {
color: #F5A327;
}
}

View File

@@ -8,6 +8,7 @@ import Loader from 'react-loader'
import Tooltip from '@cypress/react-tooltip'
import FileOpener from './file-opener'
import Notification from '../notifications/notification'
import ipc from '../lib/ipc'
import projectsApi from '../projects/projects-api'
import specsStore, { allIntegrationSpecsSpec, allComponentSpecsSpec } from './specs-store'
@@ -57,7 +58,12 @@ const sortedSpecList = (specs) => {
class SpecsList extends Component {
constructor (props) {
super(props)
this.state = {
isFocused: false,
}
this.filterRef = React.createRef()
this.newSpecRef = React.createRef()
// when the specs are running and the user changes the search filter
// we still want to show the previous button label to reflect what
// is currently running
@@ -75,6 +81,22 @@ class SpecsList extends Component {
}
}
componentDidUpdate () {
if (this.newSpecRef.current) {
this.newSpecRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' })
// unset new spec after animation to prevent further scrolling
this.removeNewSpecTimeout = setTimeout(() => specsStore.setNewSpecPath(null), 3000)
}
}
componentWillUnmount () {
if (this.removeNewSpecTimeout) {
clearTimeout(this.removeNewSpecTimeout)
}
specsStore.setNewSpecPath(null)
}
render () {
if (specsStore.isLoading) return <Loader color='#888' scale={0.5}/>
@@ -110,12 +132,17 @@ class SpecsList extends Component {
<input
id='filter'
className='filter'
placeholder='Search...'
placeholder={this._togglePlaceholderSearchTips()}
value={specsStore.filter || ''}
ref={this.filterRef}
onBlur={this._toggleFocus}
onChange={this._updateFilter}
onFocus={this._toggleFocus}
onKeyUp={this._executeFilterAction}
/>
{ window.addEventListener('keydown', this._focusWhenSearchKeys) }
<Tooltip
title='Clear search'
className='browser-info-tooltip cy-tooltip'
@@ -123,8 +150,12 @@ class SpecsList extends Component {
<a className='clear-filter fas fa-times' onClick={this._clearFilter} />
</Tooltip>
</div>
<div className='new-file-button'>
<button className='btn btn-primary' onClick={this._createNewFile.bind(this)}>New Spec File</button>
</div>
</header>
{this._specsList()}
{this._newSpecNotification()}
</div>
)
}
@@ -156,6 +187,22 @@ class SpecsList extends Component {
return spec.hasChildren ? this._folderContent(spec, nestingLevel) : this._specContent(spec, nestingLevel)
}
_toggleFocus = () => {
this.setState({
isFocused: !this.state.isFocused,
})
}
_searchPlaceholderText () {
const osKey = window.clientInformation['platform'] === 'MacIntel' ? 'Cmd' : 'Ctrl'
return `Press ${osKey} + F to search...`
}
_togglePlaceholderSearchTips = () => {
return (this.state.isFocused) ? 'Search' : this._searchPlaceholderText()
}
_allSpecsIcon () {
return this._areTestsRunning() ? 'far fa-dot-circle green' : 'fas fa-play'
}
@@ -191,6 +238,16 @@ class SpecsList extends Component {
}
}
_focusWhenSearchKeys = (e) => {
const keysForMacOs = (e.metaKey && e.keyCode === 70 && window.clientInformation['platform'] === 'MacIntel')
const keysForOtherOs = (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70 && window.clientInformation['platform'] !== 'MacIntel'))
return (keysForOtherOs || keysForMacOs)
// @ts-ignore
? document.querySelector('#filter').focus()
: ''
}
_selectSpec (spec, e) {
e.preventDefault()
e.stopPropagation()
@@ -232,6 +289,18 @@ class SpecsList extends Component {
specsStore.toggleExpandSpecFolder(specFolderPath)
}
_createNewFile (e) {
e.preventDefault()
e.stopPropagation()
ipc.showNewSpecDialog().then(({ specs, path }) => {
if (path) {
specsStore.setNewSpecPath(path)
specsStore.setSpecs(specs)
}
})
}
_folderContent (spec, nestingLevel) {
const isExpanded = spec.isExpanded
const specType = spec.specType || 'integration'
@@ -314,10 +383,11 @@ class SpecsList extends Component {
}
const isActive = specsStore.isChosen(spec)
const className = cs(`file level-${nestingLevel}`, { active: isActive })
const isNew = specsStore.isNew(spec)
const className = cs(`file level-${nestingLevel}`, { active: isActive, 'new-spec': isNew })
return (
<li key={spec.path} className={className}>
<li key={spec.path} className={className} ref={isNew ? this.newSpecRef : null}>
<a href='#' onClick={this._selectSpec.bind(this, spec)} className="file-name-wrapper">
<div className="file-name">
<i className={`fa-fw ${this._specIcon(isActive)}`} />
@@ -360,6 +430,16 @@ class SpecsList extends Component {
)
}
_newSpecNotification () {
return (
<Notification className='new-spec-warning' show={specsStore.showNewSpecWarning} onClose={specsStore.dismissNewSpecWarning}>
<i className='fas fa-exclamation-triangle' />
Your file has been successfully created.
However, since it was created outside of your integration folder or is not recognized as a spec file, it won't be visible in this list.
</Notification>
)
}
_openIntegrationFolder () {
ipc.openFinder(this.props.project.integrationFolder)
}

View File

@@ -61,6 +61,8 @@ export class SpecsStore {
@observable isLoading = false
@observable filter
@observable selectedSpec
@observable newSpecAbsolutePath
@observable showNewSpecWarning = false
@computed get specs () {
return this._tree(this._files)
@@ -77,6 +79,10 @@ export class SpecsStore {
})
}))
if (this.newSpecAbsolutePath && !_.find(this._files, this.isNew)) {
this.showNewSpecWarning = true
}
this.isLoading = false
}
@@ -104,6 +110,15 @@ export class SpecsStore {
}
}
@action setNewSpecPath (absolutePath) {
this.newSpecAbsolutePath = absolutePath
this.dismissNewSpecWarning()
}
@action dismissNewSpecWarning = () => {
this.showNewSpecWarning = false
}
@action setExpandSpecFolder (spec, isExpanded) {
spec.setExpanded(isExpanded)
}
@@ -144,6 +159,10 @@ export class SpecsStore {
return pathsEqual(this.chosenSpecPath, formRelativePath(spec))
}
isNew = (spec) => {
return pathsEqual(this.newSpecAbsolutePath, spec.absolute)
}
getSpecsFilterId ({ id, path = '' }) {
const shortenedPath = path.replace(/.*cypress/, 'cypress')

View File

@@ -19,6 +19,7 @@ $max-nesting-level: 14;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.search {
@@ -72,6 +73,15 @@ $max-nesting-level: 14;
}
}
.new-file-button {
padding-right: 15px;
button {
font-size: 13px;
padding: 6px 10px;
}
}
.all-tests {
margin-left: auto;
font-size: 13px;
@@ -197,6 +207,21 @@ $max-nesting-level: 14;
}
}
&.new-spec {
animation: 3s ease-in-out 1 new-spec-highlight;
@keyframes new-spec-highlight {
0%, 100% {
background-color: inherit;
color: inherit;
}
40%, 60% {
background-color: #cdedff;
}
}
}
&:hover, &:focus {
background-color: #f8f8f8;
cursor: pointer;

View File

@@ -752,6 +752,20 @@ describe('src/cy/commands/actions/click', () => {
})
})
it('each click gets a full command timeout', () => {
cy.spy(cy, 'retry')
cy.get('#three-buttons button').click({ multiple: true }).then(() => {
const [firstCall, secondCall] = cy.retry.getCalls()
const firstCallOptions = firstCall.args[1]
const secondCallOptions = secondCall.args[1]
// ensure we clone the options object passed to `retry()` so that
// each click in `{ multiple: true }` gets its own full timeout
expect(firstCallOptions !== secondCallOptions, 'Expected click retry options to be different object references between clicks').to.be.true
})
})
// this test needs to increase the height + width of the div
// when we implement scrollBy the delta of the left/top
it('can click elements which are huge and the center is naturally below the fold', () => {

View File

@@ -461,6 +461,15 @@ describe('src/cy/commands/clock', () => {
expect(log.get('snapshots')[1].name).to.equal('after')
})
})
it('does not emit when {log: false}', () => {
cy
.clock()
.tick(10, { log: false })
.then(function () {
expect(this.logs[0]).to.be.undefined
})
})
})
})
})

View File

@@ -261,16 +261,29 @@ const getCoordinatesForEl = function (cy, $el, options) {
}
const ensureNotAnimating = function (cy, $el, coordsHistory, animationDistanceThreshold) {
// if we dont have at least 2 points
// then automatically retry
// if we dont have at least 2 points, we throw this error to force a
// retry, which will get us another point.
// this error is purposefully generic because if the actionability
//check times out, this error is the one displayed to the user and
// saying something like "coordsHistory must be at least 2 sets
// of coords" is not very useful.
// that would only happen if the actionability check times out, which
// shouldn't happen with default timeouts, but could theoretically
// on a very, very slow system
// https://github.com/cypress-io/cypress/issues/3738
if (coordsHistory.length < 2) {
$errUtils.throwErrByPath('dom.animation_coords_history_invalid')
$errUtils.throwErrByPath('dom.actionability_failed', {
args: {
node: $dom.stringify($el),
cmd: cy.state('current').get('name'),
},
})
}
// verify that our element is not currently animating
// by verifying it is still at the same coordinates within
// 5 pixels of x/y
return cy.ensureElementIsNotAnimating($el, coordsHistory, animationDistanceThreshold)
cy.ensureElementIsNotAnimating($el, coordsHistory, animationDistanceThreshold)
}
const verify = function (cy, $el, options, callbacks) {
@@ -336,7 +349,6 @@ const verify = function (cy, $el, options, callbacks) {
}
return Promise.try(() => {
let retryActionability
const coordsHistory = []
const runAllChecks = function () {
@@ -426,7 +438,7 @@ const verify = function (cy, $el, options, callbacks) {
// element passes every single check, we MUST fire the event
// synchronously else we risk the state changing between
// the checks and firing the event!
return (retryActionability = function () {
const retryActionability = () => {
try {
return runAllChecks()
} catch (err) {
@@ -434,7 +446,9 @@ const verify = function (cy, $el, options, callbacks) {
return cy.retry(retryActionability, options)
}
})()
}
return retryActionability()
})
}

View File

@@ -116,9 +116,8 @@ module.exports = (Commands, Cypress, cy, state, config) => {
})
}
// we want to add this delay delta to our
// runnables timeout so we prevent it from
// timing out from multiple clicks
// add this delay delta to the runnables timeout because we delay
// by it below before performing each click
cy.timeout($actionability.delay, true, eventName)
const createLog = (domEvents, fromElWindow, fromAutWindow) => {
@@ -169,11 +168,17 @@ module.exports = (Commands, Cypress, cy, state, config) => {
.return(null)
}
// if { multiple: true }, make a shallow copy of options, since
// properties like `total` and `_retries` are mutated by
// $actionability.verify and retrying, but each click should
// have its own full timeout
const individualOptions = { ... options }
// must use callbacks here instead of .then()
// because we're issuing the clicks synchronously
// once we establish the coordinates and the element
// passes all of the internal checks
return $actionability.verify(cy, $el, options, {
return $actionability.verify(cy, $el, individualOptions, {
onScroll ($el, type) {
return Cypress.action('cy:scrolled', $el, type)
},

View File

@@ -97,7 +97,7 @@ module.exports = function (Commands, Cypress, cy, state) {
const { tick } = clock
clock.tick = function (ms) {
clock.tick = function (ms, options = {}) {
if ((ms != null) && !_.isNumber(ms)) {
$errUtils.throwErrByPath('tick.invalid_argument', { args: { arg: JSON.stringify(ms) } })
}
@@ -106,10 +106,14 @@ module.exports = function (Commands, Cypress, cy, state) {
ms = 0
}
const theLog = log('tick', `${ms}ms`, false, {
'Now': clock.details().now + ms,
'Ticked': `${ms} milliseconds`,
})
let theLog
if (options.log !== false) {
theLog = log('tick', `${ms}ms`, false, {
'Now': clock.details().now + ms,
'Ticked': `${ms} milliseconds`,
})
}
if (theLog) {
theLog.snapshot('before', { next: 'after' })
@@ -151,12 +155,12 @@ module.exports = function (Commands, Cypress, cy, state) {
return clock
},
tick (subject, ms) {
tick (subject, ms, options = {}) {
if (!clock) {
$errUtils.throwErrByPath('tick.no_clock')
}
clock.tick(ms)
clock.tick(ms, options)
return clock
},

View File

@@ -8,7 +8,7 @@ import * as $dom from '../dom'
import * as $document from '../dom/document'
import * as $elements from '../dom/elements'
// eslint-disable-next-line no-duplicate-imports
import { HTMLTextLikeElement, HTMLTextLikeInputElement } from '../dom/elements'
import { HTMLTextLikeElement } from '../dom/elements'
import * as $selection from '../dom/selection'
import $window from '../dom/window'

View File

@@ -104,6 +104,7 @@ class $Cypress {
this.version = config.version
this.browser = config.browser
this.platform = config.platform
this.testingType = config.testingType
// normalize this into boolean
config.isTextTerminal = !!config.isTextTerminal
@@ -128,7 +129,7 @@ class $Cypress {
// change this in the NEXT_BREAKING
const { env } = config
config = _.omit(config, 'env', 'remote', 'resolved', 'scaffoldedFiles', 'javascripts', 'state')
config = _.omit(config, 'env', 'remote', 'resolved', 'scaffoldedFiles', 'javascripts', 'state', 'testingType')
_.extend(this, browserInfo(config))

View File

@@ -293,6 +293,13 @@ module.exports = {
},
dom: {
actionability_failed: stripIndent`
${cmd('{{cmd}}')} could not be issued because we could not determine the actionability of this element:
\`{{node}}\`
You can prevent this by passing \`{force: true}\` to disable all error checking.
`,
animating: {
message: stripIndent`\
${cmd('{{cmd}}')} could not be issued because this element is currently animating:
@@ -305,7 +312,6 @@ module.exports = {
- Passing \`{animationDistanceThreshold: 20}\` which decreases the sensitivity`,
docsUrl: 'https://on.cypress.io/element-is-animating',
},
animation_coords_history_invalid: 'coordsHistory must be at least 2 sets of coords',
animation_check_failed: 'Not enough coord points provided to calculate distance.',
center_hidden: {
message: stripIndent`\

View File

@@ -1,3 +1,4 @@
{
"projectId": "2pz86o"
}

View File

@@ -392,6 +392,8 @@ interface WaitOptions {
declare global {
namespace Cypress {
// TODO: Why is Subject unused?
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject = any> {
/**
* Use `cy.intercept()` to stub and intercept HTTP requests and responses.

View File

@@ -39,10 +39,6 @@ describe('commands', () => {
})
cy.contains('http://localhost:3000') // ensure test content has loaded
// ensure the page is loaded before proceeding
// this makes visual snapshots stable
cy.get('.focus-tests-text').should('be.visible')
})
it('displays all the commands', () => {

Some files were not shown because too many files have changed in this diff Show More