Merge branch develop into 9.0-release

This commit is contained in:
Jennifer Shehane
2021-09-23 14:01:52 -05:00
546 changed files with 14898 additions and 30147 deletions
+1 -1
View File
@@ -1 +1 @@
14.16.0
14.17.0
-8
View File
@@ -23,14 +23,6 @@
"execute": false,
"command": "yarn cypress:run --project ../project"
},
{
"name": "cypress open (CT)",
"focus": true,
"onlySingle": true,
"execute": true,
"cwd": "[cwd]/packages/server-ct",
"command": "yarn cypress:open"
},
{
"name": "packages/server test-e2e",
"focus": true,
+13 -5
View File
@@ -278,6 +278,9 @@ Here is a list of the core packages in this repository with a short description,
| [example](./packages/example) | `@packages/example` | Our example kitchen-sink application. |
| [extension](./packages/extension) | `@packages/extension` | The Cypress Chrome browser extension |
| [https-proxy](./packages/https-proxy) | `@packages/https-proxy` | This does https proxy for handling http certs and traffic. |
| [net-stubbing](./packages/net-stubbing) | `@packages/net-stubbing` | Contains server side code for Cypress' network stubbing features. |
| [network](./packages/networ ) | `@packages/network` | Various utilities related to networking. |
| [proxy](./packages/proxy) | `@packages/proxy` | Code for Cypress' network proxy layer. |
| [launcher](./packages/launcher) | `@packages/launcher` | Finds and launches browsers installed on your system. |
| [reporter](./packages/reporter) | `@packages/reporter` | The reporter shows the running results of the tests (The Command Log UI). |
| [root](./packages/root) | `@packages/root` | Dummy package pointing at the root of the repository. |
@@ -285,8 +288,8 @@ Here is a list of the core packages in this repository with a short description,
| [runner-ct](./packages/runner-ct) | `@packages/runner-ct` | The runner for component testing |
| [runner-shared](./packages/runner-shared) | `@packages/runner-shared` | The shared components between the `runner` and the `runner-ct` packages |
| [server](./packages/server) | `@packages/server` | The <3 of Cypress. This orchestrates everything. The backend node process. |
| [server-ct](./packages/server-ct) | `@packages/server-ct` | Some Component Testing specific overrides. Mostly extends functionality from `@packages/server` |
| [socket](./packages/socket) | `@packages/socket` | A wrapper around socket.io to provide common libraries. |
| [static](./packages/static) | `@packages/static` | Serves static assets used in the Cypress GUI. |
| [ts](./packages/ts) | `@packages/ts` | A centralized version of typescript. |
Public packages live within the [`npm`](./npm) folder and are standalone modules that get independently published to npm under the `@cypress/` namespace. These packages generally contain extensions, plugins, or other packages that are complementary to, yet independent of, the main Cypress app.
@@ -295,10 +298,15 @@ Here is a list of the npm packages in this repository:
| Folder Name | Package Name | Purpose |
| :----------------------------------------------------- | :--------------------------------- | :--------------------------------------------------------------------------- |
| [eslint-plugin-dev](./npm/eslint-plugin-dev) | `@cypress/eslint-plugin-dev` | Eslint plugin for internal development. |
| [react](./npm/react) | `@cypress/react` | Cypress component testing for React. |
| [vue](./npm/vue) | `@cypress/vue` | Cypress component testing for Vue. |
| [webpack-preprocessor](./npm/webpack-preprocessor) | `@cypress/webpack-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack. |
| [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. |
| [mount-utils](./npm/mount-utils) | `@cypress/mount-utils` | Common functionality for Vue/React/Angular adapters. |
| [react](./npm/react) | `@cypress/react` | Cypress component testing for React. |
| [vite-dev-server](./npm/vite-dev-server) | `@cypress/vite-dev-server` | Vite powered dev server for Component Testing. |
| [webpack-preprocessor](./npm/webpack-preprocessor) | `@cypress/webpack-preprocessor` | Cypress preprocessor for bundling JavaScript via webpack. |
| [webpack-dev-server](./npm/webpack-dev-server) | `@cypress/webpack-dev-server` | Webpack powered dev server for Component Testing. |
| [vue](./npm/vue) | `@cypress/vue` | Cypress component testing for Vue. |
We try to tag all issues with a `pkg/` or `npm/` tag describing the appropriate package the work is required in. For public packages, we use their qualified package name: For example, issues relating to the webpack preprocessor are tagged under [`npm: @cypress/webpack-preprocessor`](https://github.com/cypress-io/cypress/labels/npm%3A%20%40cypress%2Fwebpack-preprocessor) label and issues related to the `driver` package are tagged with the [`pkg/driver`](https://github.com/cypress-io/cypress/labels/pkg%2Fdriver) label.
+2 -2
View File
@@ -2,13 +2,13 @@ branches:
only:
- master
- develop
- windows-code-signing
- fix-test-other-projects
- /win*/
# https://www.appveyor.com/docs/lang/nodejs-iojs/
environment:
# use matching version of Node.js
nodejs_version: "14.16.0"
nodejs_version: "14.17.0"
# encode secure variables which will NOT be used
# in pull requests
# https://www.appveyor.com/docs/build-configuration/#secure-variables
+2 -2
View File
@@ -1,4 +1,4 @@
{
"chrome:beta": "93.0.4577.18",
"chrome:stable": "92.0.4515.107"
"chrome:beta": "94.0.4606.54",
"chrome:stable": "94.0.4606.54"
}
+19 -16
View File
@@ -8,7 +8,7 @@ macBuildFilters: &macBuildFilters
branches:
only:
- develop
- tgriesser/fix/mac-build
- tgriesser/chore/fix-release
defaults: &defaults
parallelism: 1
@@ -42,6 +42,7 @@ onlyMainBranches: &onlyMainBranches
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- create-build-artifacts
@@ -49,7 +50,7 @@ executors:
# the Docker image with Cypress dependencies and Chrome browser
cy-doc:
docker:
- image: cypress/browsers:node14.16.0-chrome90-ff88
- image: cypress/browsers:node14.17.0-chrome91-ff89
# by default, we use "small" to save on CI costs. bump on a per-job basis if needed.
resource_class: small
environment:
@@ -58,7 +59,7 @@ executors:
# Docker image with non-root "node" user
non-root-docker-user:
docker:
- image: cypress/browsers:node14.16.0-chrome90-ff88
- image: cypress/browsers:node14.17.0-chrome91-ff89
user: node
environment:
PLATFORM: linux
@@ -303,6 +304,7 @@ commands:
dpkg -i /usr/src/google-chrome-<<parameters.channel>>_<<parameters.version>>_amd64.deb ; \
apt-get install -f -y && \
rm -f /usr/src/google-chrome-<<parameters.channel>>_<<parameters.version>>_amd64.deb
which google-chrome-<<parameters.channel>> || (printf "\n\033[0;31mChrome was not successfully downloaded - bailing\033[0m\n\n" && exit 1)
echo "Location of Google Chrome Installation: `which google-chrome-<<parameters.channel>>`"
echo "Google Chrome Version: `google-chrome-<<parameters.channel>> --version`"
@@ -373,6 +375,7 @@ commands:
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
$cmd yarn workspace @packages/runner cypress:run --record --parallel --group runner-integration-<<parameters.browser>> --browser <<parameters.browser>>
- verify-mocha-results
- store_test_results:
path: /tmp/cypress
- store_artifacts:
@@ -407,6 +410,7 @@ commands:
else
echo "skipping percy screenshots uploading"
fi
- verify-mocha-results
- store_test_results:
path: /tmp/cypress
- store_artifacts:
@@ -1030,18 +1034,6 @@ jobs:
path: /tmp/cypress
- store-npm-logs
server-ct-unit-tests:
<<: *defaults
parallelism: 1
steps:
- restore_cached_workspace
- run: yarn test-unit --scope @packages/server-ct
- verify-mocha-results:
expectedResultCount: 1
- store_test_results:
path: /tmp/cypress
- store-npm-logs
server-integration-tests:
<<: *defaults
resource_class: medium
@@ -1126,6 +1118,7 @@ jobs:
runner-integration-tests-electron:
<<: *defaults
resource_class: medium
parallelism: 2
steps:
- run-runner-integration-tests:
@@ -1150,6 +1143,7 @@ jobs:
driver-integration-tests-chrome-beta:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
@@ -1488,7 +1482,7 @@ jobs:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "tgriesser/chore/fix-release" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -2080,6 +2074,7 @@ linux-workflow: &linux-workflow
# Any attempts to automate this are welcome
# If CircleCI provided an "after all" hook, then this wouldn't be necessary
- npm-release:
context: test-runner:npm-release
requires:
- build
- npm-eslint-plugin-dev
@@ -2123,6 +2118,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- build
- test-kitchensink:
@@ -2134,6 +2130,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- build
- create-build-artifacts:
@@ -2183,6 +2180,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- create-build-artifacts
- test-npm-module-and-verify-binary:
@@ -2190,6 +2188,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- create-build-artifacts
- test-binary-against-staging:
@@ -2198,6 +2197,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- create-build-artifacts
@@ -2222,6 +2222,7 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- create-build-artifacts
@@ -2293,6 +2294,7 @@ mac-workflow: &mac-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- darwin-create-build-artifacts
@@ -2304,6 +2306,7 @@ mac-workflow: &mac-workflow
branches:
only:
- develop
- tgriesser/chore/fix-release
requires:
- darwin-create-build-artifacts
+5 -7
View File
@@ -9,6 +9,7 @@ const request = require('@cypress/request')
const Promise = require('bluebird')
const requestProgress = require('request-progress')
const { stripIndent } = require('common-tags')
const getProxyForUrl = require('proxy-from-env').getProxyForUrl
const { throwFormErrorText, errors } = require('../errors')
const fs = require('../fs')
@@ -16,12 +17,9 @@ const util = require('../util')
const defaultBaseUrl = 'https://download.cypress.io/'
const getProxyUrl = () => {
return process.env.HTTPS_PROXY ||
process.env.https_proxy ||
const getProxyForUrlWithNpmConfig = (url) => {
return getProxyForUrl(url) ||
process.env.npm_config_https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.npm_config_proxy ||
null
}
@@ -205,7 +203,7 @@ const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => {
// {filename: ..., downloaded: true}
const downloadFromUrl = ({ url, downloadDestination, progress, ca }) => {
return new Promise((resolve, reject) => {
const proxy = getProxyUrl()
const proxy = getProxyForUrlWithNpmConfig(url)
debug('Downloading package', {
url,
@@ -357,6 +355,6 @@ const start = (opts) => {
module.exports = {
start,
getUrl,
getProxyUrl,
getProxyForUrlWithNpmConfig,
getCA,
}
+2 -1
View File
@@ -20,7 +20,7 @@
"unit": "cross-env BLUEBIRD_DEBUG=1 NODE_ENV=test mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json"
},
"dependencies": {
"@cypress/request": "^2.88.5",
"@cypress/request": "^2.88.6",
"@cypress/xvfb": "^1.2.4",
"@types/node": "^14.14.31",
"@types/sinonjs__fake-timers": "^6.0.2",
@@ -54,6 +54,7 @@
"minimist": "^1.2.5",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
"proxy-from-env": "1.0.0",
"ramda": "~0.27.1",
"request-progress": "^3.0.0",
"supports-color": "^8.1.1",
+43 -11
View File
@@ -313,28 +313,60 @@ describe('lib/tasks/download', function () {
})
context('with proxy env vars', () => {
const testUriHttp = 'http://anything.com'
const testUriHttps = 'https://anything.com'
beforeEach(function () {
this.env = _.clone(process.env)
// add a default no_proxy which does not match the testUri
process.env.NO_PROXY = 'localhost,.org'
})
afterEach(function () {
process.env = this.env
})
it('prefers https_proxy over http_proxy', () => {
process.env.HTTP_PROXY = 'foo'
expect(download.getProxyUrl()).to.eq('foo')
process.env.https_proxy = 'bar'
expect(download.getProxyUrl()).to.eq('bar')
it('uses http_proxy on http request', () => {
process.env.http_proxy = 'http://foo'
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('http://foo')
})
it('ignores http_proxy on https request', () => {
process.env.http_proxy = 'http://foo'
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq(null)
process.env.https_proxy = 'https://bar'
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
})
it('falls back to npm_config_proxy', () => {
process.env.npm_config_proxy = 'foo'
expect(download.getProxyUrl()).to.eq('foo')
process.env.npm_config_https_proxy = 'bar'
expect(download.getProxyUrl()).to.eq('bar')
process.env.https_proxy = 'baz'
expect(download.getProxyUrl()).to.eq('baz')
process.env.npm_config_proxy = 'http://foo'
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('http://foo')
process.env.npm_config_https_proxy = 'https://bar'
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
process.env.https_proxy = 'https://baz'
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://baz')
})
it('respects no_proxy on http and https requests', () => {
process.env.NO_PROXY = 'localhost,.com'
process.env.http_proxy = 'http://foo'
process.env.https_proxy = 'https://bar'
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq(null)
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq(null)
})
it('ignores no_proxy for npm proxy configs, prefers https over http', () => {
process.env.NO_PROXY = 'localhost,.com'
process.env.npm_config_proxy = 'http://foo'
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('http://foo')
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('http://foo')
process.env.npm_config_https_proxy = 'https://bar'
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('https://bar')
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
})
})
+79 -12
View File
@@ -22,6 +22,19 @@ declare namespace Cypress {
password: string
}
interface RemoteState {
auth?: {
username: string
password: string
}
domainName: string
strategy: 'file' | 'http'
origin: string
fileServer: string
props: Record<string, any>
visiting: string
}
interface Backend {
/**
* Firefox only: Force Cypress to run garbage collection routines.
@@ -982,7 +995,7 @@ declare namespace Cypress {
* .its('contentType')
* .should('eq', 'text/html')
*/
document(options?: Partial<Loggable>): Chainable<Document>
document(options?: Partial<Loggable & Timeoutable>): Chainable<Document>
/**
* Iterate through an array like structure (arrays or objects with a length property).
@@ -1958,7 +1971,7 @@ declare namespace Cypress {
*
* @see https://on.cypress.io/title
*/
title(options?: Partial<Loggable>): Chainable<string>
title(options?: Partial<Loggable & Timeoutable>): Chainable<string>
/**
* Trigger an event on a DOM element.
@@ -2060,7 +2073,7 @@ declare namespace Cypress {
* @alias cy.location('href')
* @see https://on.cypress.io/url
*/
url(options?: Partial<Loggable & Timeoutable>): Chainable<string>
url(options?: Partial<UrlOptions>): Chainable<string>
/**
* Control the size and orientation of the screen for your application.
@@ -2488,6 +2501,47 @@ declare namespace Cypress {
cmdKey: boolean
}
interface PEMCert {
/**
* Path to the certificate file, relative to project root.
*/
cert: string
/**
* Path to the private key file, relative to project root.
*/
key: string
/**
* Path to a text file containing the passphrase, relative to project root.
*/
passphrase?: string
}
interface PFXCert {
/**
* Path to the certificate container, relative to project root.
*/
pfx: string
/**
* Path to a text file containing the passphrase, relative to project root.
*/
passphrase?: string
}
interface ClientCertificate {
/**
* URL to match requests against. Wildcards following [minimatch](https://github.com/isaacs/minimatch) rules are supported.
*/
url: string
/**
* Paths to one or more CA files to validate certs against, relative to project root.
*/
ca?: string[]
/**
* A PEM format certificate/private key pair or PFX certificate container
*/
certs: PEMCert[] | PFXCert[]
}
interface ResolvedConfigOptions {
/**
* Url used as prefix for [cy.visit()](https://on.cypress.io/visit) or [cy.request()](https://on.cypress.io/request) commands url
@@ -2750,6 +2804,11 @@ declare namespace Cypress {
* @default {}
*/
e2e: Omit<ResolvedConfigOptions, TestingType>
/**
* An array of objects defining the certificates
*/
clientCertificates: ClientCertificate[]
}
/**
@@ -2810,19 +2869,15 @@ declare namespace Cypress {
projectName: string
projectRoot: string
proxyUrl: string
remote: RemoteState
report: boolean
reporterRoute: string
reporterUrl: string
socketId: null | string
socketIoCookie: string
socketIoRoute: string
spec: {
absolute: string
name: string
relative: string
specFilter: null | string
specType: 'integration' | 'component'
}
spec: Cypress['spec'] | null
specs: Array<Cypress['spec']>
xhrRoute: string
xhrUrl: string
}
@@ -3173,6 +3228,18 @@ declare namespace Cypress {
eventConstructor: string
}
/**
* Options to change the default behavior of .url()
*/
interface UrlOptions extends Loggable, Timeoutable {
/**
* Whether the url is decoded
*
* @default false
*/
decode: boolean
}
/** Options to change the default behavior of .writeFile */
interface WriteFileOptions extends Loggable {
flag: string
@@ -5263,7 +5330,7 @@ declare namespace Cypress {
tag?: string
}
interface DevServerOptions {
interface DevServerConfig {
specs: Spec[]
config: ResolvedConfigOptions & RuntimeConfigOptions
devServerEvents: NodeJS.EventEmitter
@@ -5282,7 +5349,7 @@ declare namespace Cypress {
(action: 'before:spec', fn: (spec: Spec) => void | Promise<void>): void
(action: 'before:browser:launch', fn: (browser: Browser, browserLaunchOptions: BrowserLaunchOptions) => void | BrowserLaunchOptions | Promise<BrowserLaunchOptions>): void
(action: 'file:preprocessor', fn: (file: FileObject) => string | Promise<string>): void
(action: 'dev-server:start', fn: (file: DevServerOptions) => Promise<ResolvedDevServerConfig>): void
(action: 'dev-server:start', fn: (file: DevServerConfig) => Promise<ResolvedDevServerConfig>): void
(action: 'task', tasks: Tasks): void
}
+5
View File
@@ -497,6 +497,11 @@ namespace CypressLocationTests {
cy.location('pathname') // $ExpectType Chainable<string>
}
// https://github.com/cypress-io/cypress/issues/17399
namespace CypressUrlTests {
cy.url({decode: true}).should('contain', '사랑')
}
namespace CypressBrowserTests {
Cypress.isBrowser('chrome')// $ExpectType boolean
Cypress.isBrowser('firefox')// $ExpectType boolean
+11 -3
View File
@@ -1,6 +1,7 @@
{
"name": "@cypress/angular",
"version": "0.0.0-development",
"description": "Test Angular components using Cypress",
"private": true,
"main": "dist/index.js",
"scripts": {
@@ -62,7 +63,7 @@
"tslib": "^2.2.0",
"tslint": "5.20.1",
"typescript": "4.2.4",
"webpack-dev-server": "3.11.2",
"webpack-dev-server": "4.0.0",
"zone.js": "0.11.4"
},
"peerDependencies": {
@@ -84,5 +85,12 @@
"type": "git",
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://on.cypress.io/component-testing"
}
"homepage": "https://on.cypress.io/component-testing",
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
"contributors": [
{
"name": "Jean-Michel Leclercq",
"social": "@LeJeanbono"
}
]
}
+7
View File
@@ -1,3 +1,10 @@
# [@cypress/schematic-v1.5.1](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v1.5.0...@cypress/schematic-v1.5.1) (2021-09-10)
### Bug Fixes
* **cypress/schematic:** add edge to list of allowed browsers ([#17637](https://github.com/cypress-io/cypress/issues/17637)) ([49de24f](https://github.com/cypress-io/cypress/commit/49de24f6178e0ef7bfd654c3efc0bfa8008e962e))
# [@cypress/schematic-v1.5.0](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v1.4.2...@cypress/schematic-v1.5.0) (2021-07-20)
@@ -3,7 +3,7 @@ import { JsonObject } from '@angular-devkit/core'
export interface CypressBuilderOptions extends JsonObject {
baseUrl: string
configFile: string | false
browser: 'electron' | 'chrome' | 'chromium' | 'canary' | 'firefox' | string
browser: 'electron' | 'chrome' | 'chromium' | 'canary' | 'firefox' | 'edge' | string
devServerTarget: string
env: Record<string, string>
quiet: boolean
@@ -27,7 +27,8 @@
"chrome",
"chromium",
"canary",
"firefox"
"firefox",
"edge"
]
},
"devServerTarget": {
+14
View File
@@ -1,3 +1,17 @@
# [@cypress/react-v5.10.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.9.4...@cypress/react-v5.10.0) (2021-09-10)
### Features
* allow usage of @react/plugins with cypress.config.js ([#17738](https://github.com/cypress-io/cypress/issues/17738)) ([da4b1e0](https://github.com/cypress-io/cypress/commit/da4b1e06ce33945aabddda0e6e175dc0e1b488a5))
# [@cypress/react-v5.9.4](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.9.3...@cypress/react-v5.9.4) (2021-08-10)
### Bug Fixes
* do not throw when alt path is found in next ([#17658](https://github.com/cypress-io/cypress/issues/17658)) ([ee7e203](https://github.com/cypress-io/cypress/commit/ee7e203fa059efeac45c7a18526e563643e76d77)), closes [#17476](https://github.com/cypress-io/cypress/issues/17476)
# [@cypress/react-v5.9.3](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.9.2...@cypress/react-v5.9.3) (2021-08-03)
@@ -1,9 +1,14 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
+2 -2
View File
@@ -7,10 +7,10 @@ Use the plugin:
// import your craco.config.js
const cracoConfig = require('../../craco.config.js')
const injectDevServer = require('@cypress/react/plugins/craco')
const devServer = require('@cypress/react/plugins/craco')
module.exports = (on, config) => {
injectDevServer(on, config, cracoConfig)
devServer(on, config, cracoConfig)
return config
}
@@ -1,8 +1,13 @@
const cracoConfig = require('../../craco.config.js')
const injectDevServer = require('@cypress/react/plugins/craco')
// @ts-check
const cracoConfig = require('../../craco.config.js')
const devServer = require('@cypress/react/plugins/craco')
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
injectDevServer(on, config, cracoConfig)
devServer(on, config, cracoConfig)
return config
}
+2 -2
View File
@@ -38,10 +38,10 @@ Here we are adding some Component Testing specific options, hence the `"componen
The last thing we need to is tell Cypress to use `@cypress/webpack-dev-server` for component tests. Plugins are explained in detail in the [Cypress documentation](https://docs.cypress.io/guides/tooling/plugins-guide#Installing-plugins). By default plugins are loaded from `cypress/plugins/index.js`. Create that file and add:
```js
const injectDevServer = require("@cypress/react/plugins/react-scripts")
const devServer = require("@cypress/react/plugins/react-scripts")
module.exports = (on, config) => {
injectDevServer(on, config)
devServer(on, config)
return config
}
```
@@ -1,7 +1,12 @@
// @ts-check
const findReactScriptsWebpackConfig = require('@cypress/react/plugins/react-scripts/findReactScriptsWebpackConfig')
const { startDevServer } = require('@cypress/webpack-dev-server')
const _ = require('lodash')
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
const map = _.map([4, 8], (n) => n * 2)
@@ -10,9 +15,9 @@ module.exports = (on, config) => {
const webpackConfig = findReactScriptsWebpackConfig(config)
const rules = webpackConfig.module.rules.find((rule) => !!rule.oneOf).oneOf
const babelRule = rules.find((rule) => /babel-loader/.test(rule.loader))
const babelRule = rules.find((rule) => typeof rule.loader === 'string' && /babel-loader/.test(rule.loader))
babelRule.options.plugins.push(require.resolve('babel-plugin-istanbul'))
typeof babelRule.options !== 'string' && babelRule.options.plugins.push(require.resolve('babel-plugin-istanbul'))
on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig })
@@ -1,10 +1,10 @@
const injectNextDevServer = require('@cypress/react/plugins/next')
const devServer = require('@cypress/react/plugins/next')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectNextDevServer(on, config)
devServer(on, config)
return config
}
+2 -2
View File
@@ -9,10 +9,10 @@
In order to reuse next's webpack configuration and all the custom configuration defined in `next.config.js` connect special plugin in [plugin file](./cypress/plugins/index.js)
```js
const preprocessor = require('@cypress/react/plugins/next')
const devServer = require('@cypress/react/plugins/next')
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
return config
}
@@ -1,10 +1,10 @@
const injectNextDevServer = require('@cypress/react/plugins/next')
const devServer = require('@cypress/react/plugins/next')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectNextDevServer(on, config)
devServer(on, config)
return config
}
@@ -1,9 +1,14 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -1,7 +1,10 @@
const injectDevServer = require('@cypress/react/plugins/react-scripts')
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectDevServer(on, config)
devServer(on, config)
return config
}
+9 -4
View File
@@ -1,9 +1,14 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -1,9 +1,14 @@
// load Webpack file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const injectWebpackDevServer = require('@cypress/react/plugins/load-webpack')
// @ts-check
// load Webpack file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/load-webpack')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectWebpackDevServer(on, config, {
devServer(on, config, {
webpackFilename: 'webpack.config.js',
})
@@ -1,10 +1,15 @@
// load file preprocessor that comes with this plugin
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
const devServer = require('@cypress/react/plugins/react-scripts')
const { initPlugin: initSnapshots } = require('cypress-plugin-snapshots/plugin')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// initialize the snapshots plugin following
// https://github.com/meinaart/cypress-plugin-snapshots
initSnapshots(on, config)
@@ -1,9 +1,14 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const injectReactScriptsDevServer = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectReactScriptsDevServer(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -31,14 +31,14 @@ npm test
## Specs
See spec files [src/\*.spec.js](src). The specs are bundled using [babel.config.js](babel.config.js) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file preprocessor.
See spec files [src/\*.spec.js](src). The specs are bundled using [babel.config.js](babel.config.js) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file devServer.
```js
// let's bundle spec files and the components they include using
// the same bundling settings as the project by loading .babelrc
const preprocessor = require('@cypress/react/plugins/babelrc')
const devServer = require('@cypress/react/plugins/babel')
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
return config
@@ -1,10 +1,15 @@
// @ts-check
// let's bundle spec files and the components they include using
// the same bundling settings as the project by loading .babelrc
// https://github.com/bahmutov/cypress-react-unit-test#install
const injectWebpackWithBabelDevServer = require('@cypress/react/plugins/babel')
const devServer = require('@cypress/react/plugins/babel')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
injectWebpackWithBabelDevServer(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
+3 -3
View File
@@ -31,14 +31,14 @@ npm test
## Specs
See spec files [src/\*.spec.js](src). The specs are bundled using [.babelrc](.babelrc) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file preprocessor
See spec files [src/\*.spec.js](src). The specs are bundled using [.babelrc](.babelrc) settings via [cypress/plugins/index.js](cypress/plugins/index.js) file that includes file devServer
```js
// let's bundle spec files and the components they include using
// the same bundling settings as the project by loading .babelrc
const preprocessor = require('@cypress/react/plugins/babelrc')
const devServer = require('@cypress/react/plugins/babel')
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
return config
@@ -1,10 +1,15 @@
// @ts-check
// let's bundle spec files and the components they include using
// the same bundling settings as the project by loading .babelrc
// https://github.com/bahmutov/cypress-react-unit-test#install
const injectWebpackWithBabelDevServer = require('@cypress/react/plugins/babel')
const devServer = require('@cypress/react/plugins/babel')
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
injectWebpackWithBabelDevServer(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -1,11 +1,16 @@
// load file preprocessor that comes with this plugin
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
const devServer = require('@cypress/react/plugins/react-scripts')
const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
addMatchImageSnapshotPlugin(on, config)
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
return config
@@ -1,13 +1,19 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
return config
}
// @ts-ignore
require('@applitools/eyes-cypress')(module)
@@ -1,11 +1,17 @@
// load file preprocessor that comes with this plugin
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/cypress-react-unit-test#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
const devServer = require('@cypress/react/plugins/react-scripts')
// @ts-ignore
const happoTask = require('happo-cypress/task')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
on('task', happoTask)
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -1,9 +1,14 @@
// load file preprocessor that comes with this plugin
// https://github.com/bahmutov/@cypress/react#install
const preprocessor = require('@cypress/react/plugins/react-scripts')
// @ts-check
// load file devServer that comes with this plugin
// https://github.com/bahmutov/@cypress/react#install
const devServer = require('@cypress/react/plugins/react-scripts')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)
devServer(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
@@ -1,3 +1,8 @@
// @ts-check
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
require('@cypress/react/plugins/load-webpack')(on, config, {
// from the root of the project (folder with cypress.json file)
+1 -1
View File
@@ -1,6 +1,6 @@
# example: webpack-options
> The Webpack preprocessor in [cypress/plugins/index.js](cypress/plugins/index.js) adds the Babel React preset to the list of default Webpack plugins. This allows Cypress to transpile JSX code in [cypress/component/Test.cy-spec.js](cypress/component/Test.cy-spec.js) file.
> The Webpack devServer in [cypress/plugins/index.js](cypress/plugins/index.js) adds the Babel React preset to the list of default Webpack plugins. This allows Cypress to transpile JSX code in [cypress/component/Test.cy-spec.js](cypress/component/Test.cy-spec.js) file.
## Usage
@@ -3,8 +3,11 @@ const path = require('path')
const { startDevServer } = require('@cypress/webpack-dev-server')
const babelConfig = require('../../babel.config')
// Cypress Webpack preprocessor includes Babel env preset,
// but to transpile JSX code we need to add Babel React preset
/**
* Cypress Webpack devServer includes Babel env preset,
* but to transpile JSX code we need to add Babel React preset
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
/** @type import("webpack").Configuration */
const webpackConfig = {
+29 -3
View File
@@ -1,19 +1,22 @@
{
"name": "@cypress/react",
"version": "0.0.0-development",
"description": "Unit test React components using Cypress",
"description": "Test React components using Cypress",
"main": "dist/cypress-react.cjs.js",
"scripts": {
"build": "rimraf dist && yarn rollup -c rollup.config.js",
"build": "rimraf dist && yarn transpile-plugins && yarn rollup -c rollup.config.js",
"build-prod": "yarn build",
"cy:open": "node ../../scripts/cypress.js open-ct",
"cy:open:debug": "node --inspect-brk ../../scripts/start.js --component-testing --run-project ${PWD}",
"cy:run": "node ../../scripts/cypress.js run-ct",
"cy:run:debug": "node --inspect-brk ../../scripts/start.js --component-testing --run-project ${PWD}",
"pretest": "yarn transpile",
"test": "yarn cy:run",
"test": "yarn test-unit && yarn test-component",
"test-unit": "mocha -r @packages/ts/register test/**/*.spec.*",
"test-component": "yarn cy:run",
"test-ci": "node ../../scripts/run-ct-examples.js --examplesList=./examples.env",
"transpile": "tsc",
"transpile-plugins": "yarn transpile --project ./plugins",
"watch": "yarn build --watch --watch.exclude ./dist/**/*"
},
"dependencies": {
@@ -118,6 +121,7 @@
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/blob/master/npm/react/#readme",
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
"bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Freact&template=1-bug-report.md&title=",
"keywords": [
"react",
@@ -126,6 +130,28 @@
"test",
"testing"
],
"contributors": [
{
"name": "Dmitriy Kovalenko",
"social": "@dmtrKovalenko"
},
{
"name": "Brian Mann",
"social": "@brian-mann"
},
{
"name": "Barthélémy Ledoux",
"social": "@elevatebart"
},
{
"name": "Lachlan Miller",
"social": "@lmiller1990"
},
{
"name": "Jessica Sachs",
"social": "@JessicaSachs"
}
],
"unpkg": "dist/cypress-react.browser.js",
"module": "dist/cypress-react.esm-bundler.js",
"peerDependenciesMeta": {
@@ -43,7 +43,7 @@ const webpackConfigLoadsBabel = {
* return config
* }
*/
module.exports = (on, config, { setWebpackConfig } = { setWebpackConfig: null }) => {
module.exports = (config, { setWebpackConfig } = { setWebpackConfig: null }) => {
debug('env object %o', config.env)
debug('initial environments %o', {
+35
View File
@@ -0,0 +1,35 @@
import { Configuration } from "webpack";
declare namespace legacyDevServer {
interface CypressBabelDevServerConfig {
/**
* Allows to adjust the webpackConfig that our dev-server will use
* @param config configuration generated by the plugin
* @returns modified final configuration
*/
setWebpackConfig?(config:Configuration): Configuration
}
/**
* Type helper to make writing `CypressBabelDevServerConfig` easier
*/
function defineDevServerConfig(devServerConfig: CypressBabelDevServerConfig): CypressBabelDevServerConfig
/**
* Sets up a webpack dev server with the proper configuration for babel transpilation
* @param cypressDevServerConfig comes from the `devServer()` function first argument
* @param devServerConfig additional config object (create an empty object to see how to use it)
* @returns the resolved dev server object that cypress can use to start testing
*/
function devServer(cypressDevServerConfig: Cypress.DevServerConfig, devServerConfig?: CypressBabelDevServerConfig): Cypress.ResolvedDevServerConfig
}
/**
* Setup a webpack dev server with the proper configuration for babel transpilation
* @param on comes from the argument of the `pluginsFile` function
* @param config comes from the argument of the `pluginsFile` function
* @param devServerConfig additional config object (create an empty object it to see how to use it)
*/
declare function legacyDevServer(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, devServerConfig?: legacyDevServer.CypressBabelDevServerConfig): void
export = legacyDevServer;
+16 -4
View File
@@ -1,12 +1,24 @@
const getBabelWebpackConfig = require('./getBabelWebpackConfig')
const { startDevServer } = require('@cypress/webpack-dev-server')
const getBabelWebpackConfig = require('./getBabelWebpackConfig')
const { getLegacyDevServer } = require('../utils/legacy-setup-dev-server')
module.exports = (on, config, moduleOptions) => {
on('dev-server:start', async (options) => {
return startDevServer({ options, webpackConfig: getBabelWebpackConfig(on, config, moduleOptions) })
function devServer (cypressDevServerConfig, devServerConfig) {
return startDevServer({
options: cypressDevServerConfig,
webpackConfig: getBabelWebpackConfig(cypressDevServerConfig.config, devServerConfig),
})
}
// Legacy signature
module.exports = getLegacyDevServer(devServer, (config) => {
config.env.reactDevtools = true
return config
})
// New signature
module.exports.devServer = devServer
module.exports.defineDevServerConfig = function (devServerConfig) {
return devServerConfig
}
+31
View File
@@ -0,0 +1,31 @@
declare namespace legacyDevServer {
interface CypressCracoDevServerConfig {
/**
* The object exported of your craco.config.js file
*/
cracoConfig: any
}
/**
* Type helper to make writing `CypressCracoDevServerConfig` easier
*/
function defineDevServerConfig(devServerConfig: CypressCracoDevServerConfig): CypressCracoDevServerConfig
/**
* Sets up a dev server for using Cypress compoennt testing with CRACO (https://github.com/gsoft-inc/craco)
* @param cypressDevServerConfig comes from the `devServer()` function first argument
* @param devServerConfig additional config object (create an empty object to see how to use it)
* @returns the resolved dev server object that cypress can use to start testing
*/
function devServer(cypressDevServerConfig: Cypress.DevServerConfig, devServerConfig: CypressCracoDevServerConfig): Cypress.ResolvedDevServerConfig
}
/**
* Setup a dev server for using Cypress compoennt testing with CRACO (https://github.com/gsoft-inc/craco)
* @param on comes from the argument of the `pluginsFile` function
* @param config comes from the argument of the `pluginsFile` function
* @param cracoConfig the object exported of your craco.config.js file
*/
declare function legacyDevServer(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, cracoConfig: any): void
export = legacyDevServer;
+20 -10
View File
@@ -1,17 +1,27 @@
const { startDevServer } = require('@cypress/webpack-dev-server')
const { createWebpackDevConfig } = require('@craco/craco')
const { getLegacyDevServer } = require('../utils/legacy-setup-dev-server')
module.exports = (on, config, cracoConfig) => {
if (!cracoConfig) {
throw Error('craco config is required.')
}
on('dev-server:start', (options) => {
return startDevServer({
options,
webpackConfig: createWebpackDevConfig(cracoConfig),
})
function devServer (cypressDevServerConfig, cracoConfig) {
return startDevServer({
options: cypressDevServerConfig,
webpackConfig: createWebpackDevConfig(cracoConfig),
})
}
// Legacy signature
module.exports = getLegacyDevServer(devServer, (config) => {
config.env.reactDevtools = true
return config
})
// New signature
// - Note that this also includes a change to the second argument!
module.exports.devServer = (cypressDevServerConfig, { cracoConfig }) => {
return devServer(cypressDevServerConfig, cracoConfig)
}
module.exports.defineDevServerConfig = function (devServerConfig) {
return devServerConfig
}
+31
View File
@@ -0,0 +1,31 @@
declare namespace legacyDevServer {
interface CypressWebpackDevServerConfig {
/**
* Location of the weppack.config Cypress should use
*/
webpackFilename?: string
}
/**
* Type helper to make writing `CypressWebpackDevServerConfig` easier
*/
function defineDevServerConfig(devServerConfig: CypressWebpackDevServerConfig): CypressWebpackDevServerConfig
/**
* Sets up a webpack dev server with the proper configuration for babel transpilation
* @param cypressDevServerConfig comes from the `devServer()` function first argument
* @param devServerConfig additional config object (create an empty object to see how to use it)
* @returns the resolved dev server object that cypress can use to start testing
*/
function devServer(cypressDevServerConfig: Cypress.DevServerConfig, devServerConfig?: CypressWebpackDevServerConfig): Cypress.ResolvedDevServerConfig
}
/**
* Setup a webpack dev server with the proper configuration for babel transpilation
* @param on comes from the argument of the `pluginsFile` function
* @param config comes from the argument of the `pluginsFile` function
* @param devServerConfig additional config object (create an empty object to see how to use it)
*/
declare function legacyDevServer(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, devServerConfig?: legacyDevServer.CypressWebpackDevServerConfig): void
export = legacyDevServer;
+16 -8
View File
@@ -1,7 +1,7 @@
// @ts-check
const path = require('path')
const { startDevServer } = require('@cypress/webpack-dev-server')
const tryLoadWebpackConfig = require('../utils/tryLoadWebpackConfig')
const { getLegacyDevServer } = require('../utils/legacy-setup-dev-server')
/** @type {(config: Cypress.PluginConfigOptions, path: string) => string} */
function normalizeWebpackPath (config, webpackConfigPath) {
@@ -9,27 +9,35 @@ function normalizeWebpackPath (config, webpackConfigPath) {
? webpackConfigPath
: path.resolve(config.projectRoot, webpackConfigPath)
}
/**
* Injects dev-server based on the webpack config file.
*
* **Important:** `webpackFilename` path is relative to the project root (cypress.json location)
* @type {(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, options: { webpackFilename: string }) => Cypress.PluginConfigOptions}
*/
function injectWebpackDevServer (on, config, { webpackFilename }) {
const webpackConfig = tryLoadWebpackConfig(normalizeWebpackPath(config, webpackFilename))
function devServer (cypressDevServerConfig, { webpackFilename }) {
const webpackConfig = tryLoadWebpackConfig(normalizeWebpackPath(cypressDevServerConfig.config, webpackFilename))
if (!webpackConfig) {
throw new Error(`Can not load webpack config from path ${webpackFilename}.`)
}
on('dev-server:start', async (options) => {
return startDevServer({ options, webpackConfig })
return startDevServer({
options: cypressDevServerConfig,
webpackConfig,
})
}
// Legacy signature
module.exports = getLegacyDevServer(devServer, (config) => {
config.env.reactDevtools = true
return config
}
})
module.exports = injectWebpackDevServer
// New signature
module.exports.devServer = devServer
module.exports.defineDevServerConfig = function (devServerConfig) {
return devServerConfig
}
@@ -2,6 +2,7 @@
/// <reference types="next" />
const debug = require('debug')('@cypress/react')
const getNextJsBaseWebpackConfig = require('next/dist/build/webpack-config').default
const { findPagesDir } = require('../../dist/next/findPagesDir')
async function getNextWebpackConfig (config) {
let loadConfig
@@ -14,9 +15,9 @@ async function getNextWebpackConfig (config) {
// is not in the next-server folder anymore.
// @ts-ignore
loadConfig = require('next/dist/server/config').default
} else {
throw e
}
throw e
}
const nextConfig = await loadConfig('development', config.projectRoot)
const nextWebpackConfig = await getNextJsBaseWebpackConfig(
@@ -26,7 +27,7 @@ async function getNextWebpackConfig (config) {
config: nextConfig,
dev: true,
isServer: false,
pagesDir: config.projectRoot,
pagesDir: findPagesDir(config.projectRoot),
entrypoints: {},
rewrites: { fallback: [], afterFiles: [], beforeFiles: [] },
},
+33
View File
@@ -0,0 +1,33 @@
import * as fs from 'fs'
import * as path from 'path'
const existsSync = (file: string) => {
try {
fs.accessSync(file, fs.constants.F_OK)
return true
} catch (_) {
return false
}
}
/**
* Next allows the `pages` directory to be located at either
* `${projectRoot}/pages` or `${projectRoot}/src/pages`.
* If neither is found, return projectRoot
*/
export function findPagesDir (projectRoot: string) {
// prioritize ./pages over ./src/pages
let pagesDir = path.join(projectRoot, 'pages')
if (existsSync(pagesDir)) {
return pagesDir
}
pagesDir = path.join(projectRoot, 'src/pages')
if (existsSync(pagesDir)) {
return pagesDir
}
return projectRoot
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Sets up a Cypress component testing environment for your NextJs application
* @param on comes from the argument of the `pluginsFile` function
* @param config comes from the argument of the `pluginsFile` function
*/
declare function legacyDevServer(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): void
declare namespace legacyDevServer {
/**
* Sets up a Cypress component testing environment for your NextJs application
* @param cypressDevServerConfig comes from the `devServer()` function first argument
* @returns the resolved dev server object that cypress can use to start testing
*/
function devServer(cypressDevServerConfig: Cypress.DevServerConfig): Cypress.ResolvedDevServerConfig
}
export = legacyDevServer;
+17 -12
View File
@@ -1,21 +1,26 @@
const findNextWebpackConfig = require('./findNextWebpackConfig')
const path = require('path')
const findNextWebpackConfig = require('./findNextWebpackConfig')
const { getLegacyDevServer } = require('../utils/legacy-setup-dev-server')
module.exports = (on, config) => {
on('dev-server:start', async (options) => {
const webpackConfig = await findNextWebpackConfig(config)
async function devServer (cypressDevServerConfig) {
const webpackConfig = await findNextWebpackConfig(cypressDevServerConfig.config)
// require('webpack') now points to nextjs bundled version
const { startDevServer } = require('@cypress/webpack-dev-server')
// require('webpack') now points to nextjs bundled version
const { startDevServer } = require('@cypress/webpack-dev-server')
return startDevServer({
options,
webpackConfig,
template: path.resolve(__dirname, 'index-template.html'),
})
return startDevServer({
options: cypressDevServerConfig,
webpackConfig,
template: path.resolve(__dirname, 'index-template.html'),
})
}
// Legacy signature
module.exports = getLegacyDevServer(devServer, (config) => {
config.env.reactDevtools = true
return config
}
})
// New signature
module.exports.devServer = devServer
@@ -20,7 +20,7 @@ module.exports = function findReactScriptsWebpackConfig (config, {
}
// because for react-scripts user doesn't have direct access to webpack webpackConfig
// we must implicitly inject everything required to run tests
// we must implicitly setup everything required to run tests
addCypressToWebpackEslintRulesInPlace(webpackConfig)
getTranspileFolders(config).forEach((cypressFolder) => {
+31
View File
@@ -0,0 +1,31 @@
declare namespace legacyDevServer {
interface CypressCRADevServerConfig {
/**
* Location of the weppack.config Cypress should use
*/
webpackConfigPath?: string
}
/**
* Type helper to make writing `CypressCRADevServerConfig` easier
*/
function defineDevServerConfig(devServerConfig: CypressCRADevServerConfig): CypressCRADevServerConfig
/**
* Sets up a Cypress component testing environment for your Create React App environment
* @param cypressDevServerConfig comes from the `devServer()` function first argument
* @param devServerConfig additional config object (create an empty object to see how to use it)
* @returns the resolved dev server object that cypress can use to start testing
*/
function devServer(cypressDevServerConfig: Cypress.DevServerConfig, devServerConfig?: CypressCRADevServerConfig): Cypress.ResolvedDevServerConfig
}
/**
* Sets up a Cypress component testing environment for your Create React App environment
* @param on comes from the argument of the `pluginsFile` function
* @param config comes from the argument of the `pluginsFile` function
* @param devServerConfig additional config object (create an empty object to see how to use it)
*/
declare function legacyDevServer(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, devServerConfig?: legacyDevServer.CypressCRADevServerConfig): void
export = legacyDevServer;
+21 -17
View File
@@ -1,26 +1,30 @@
const { startDevServer } = require('@cypress/webpack-dev-server')
const findReactScriptsWebpackConfig = require('./findReactScriptsWebpackConfig')
const { getLegacyDevServer } = require('../utils/legacy-setup-dev-server')
module.exports = (
on,
config, {
webpackConfigPath,
} = {
webpackConfigPath: 'react-scripts/config/webpack.config',
},
) => {
on('dev-server:start', async (options) => {
return startDevServer({
options,
webpackConfig: findReactScriptsWebpackConfig(config, {
webpackConfigPath,
}),
})
function devServer (cypressDevServerConfig, {
webpackConfigPath,
} = {
webpackConfigPath: 'react-scripts/config/webpack.config',
}) {
return startDevServer({
options: cypressDevServerConfig,
webpackConfig: findReactScriptsWebpackConfig(cypressDevServerConfig.config, {
webpackConfigPath,
}),
})
}
// Legacy signature
module.exports = getLegacyDevServer(devServer, (config) => {
config.env.reactDevtools = true
// IMPORTANT to return the config object
// with the any changed environment variables
return config
})
// New signature
module.exports.devServer = devServer
module.exports.defineDevServerConfig = function (devServerConfig) {
return devServerConfig
}
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "./",
"types": [
"cypress",
"node"
]
},
"include": ["./**/*.ts"]
}
@@ -0,0 +1,11 @@
function getLegacyDevServer (devServer, postProcessConfig = (config) => config) {
return (on, config, ...args) => {
on('dev-server:start', (cypressDevServerConfig) => {
return devServer(cypressDevServerConfig, ...args)
})
return postProcessConfig(config)
}
}
module.exports = { getLegacyDevServer }
+19
View File
@@ -0,0 +1,19 @@
import { expect } from 'chai'
import * as path from 'path'
import { findPagesDir } from '../../plugins/next/findPagesDir'
describe('Next.js findPagesDir', () => {
it('should find the correct pagesDir', () => {
const nextPluginFixturePath = path.join(__dirname, '../fixtures/next-plugin')
let projectRoot = nextPluginFixturePath
expect(findPagesDir(projectRoot)).to.equal(projectRoot)
projectRoot = path.join(nextPluginFixturePath, 'next-project-one')
expect(findPagesDir(projectRoot)).to.equal(path.join(projectRoot, 'pages'))
projectRoot = path.join(nextPluginFixturePath, 'next-project-two')
expect(findPagesDir(projectRoot)).to.equal(path.join(projectRoot, 'src/pages'))
})
})
+30
View File
@@ -1,3 +1,33 @@
# [@cypress/vite-dev-server-v2.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.0.8...@cypress/vite-dev-server-v2.1.0) (2021-09-10)
### Features
* allow usage of @react/plugins with cypress.config.js ([#17738](https://github.com/cypress-io/cypress/issues/17738)) ([da4b1e0](https://github.com/cypress-io/cypress/commit/da4b1e06ce33945aabddda0e6e175dc0e1b488a5))
# [@cypress/vite-dev-server-v2.0.8](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.0.7...@cypress/vite-dev-server-v2.0.8) (2021-08-30)
### Bug Fixes
* prevent vite from crashing where there are no support files or specs found ([#17624](https://github.com/cypress-io/cypress/issues/17624)) ([ae0ea87](https://github.com/cypress-io/cypress/commit/ae0ea87802168c524ee5cfe04d0aa59a46195a7d)), closes [#17373](https://github.com/cypress-io/cypress/issues/17373)
* publish the types for vite-dev-server ([#17786](https://github.com/cypress-io/cypress/issues/17786)) ([a94ff69](https://github.com/cypress-io/cypress/commit/a94ff69d09564140ad0cc890771175396eb351cc)), closes [#17648](https://github.com/cypress-io/cypress/issues/17648)
* repair re-run of vite-dev-server issues ([4139631](https://github.com/cypress-io/cypress/commit/4139631b159bac159bd6d2d4c020b5d8b3aa0fa7))
# [@cypress/vite-dev-server-v2.0.7](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.0.6...@cypress/vite-dev-server-v2.0.7) (2021-08-12)
### Bug Fixes
* **vite-dev-server:** chain update all specs when changing child ([#17693](https://github.com/cypress-io/cypress/issues/17693)) ([66e8896](https://github.com/cypress-io/cypress/commit/66e8896b66207e9ce2d1a5dd9f66f73fe58a1e7e)), closes [#17691](https://github.com/cypress-io/cypress/issues/17691)
# [@cypress/vite-dev-server-v2.0.6](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.0.5...@cypress/vite-dev-server-v2.0.6) (2021-08-10)
### Bug Fixes
* prevent vite from crashing where there are no support files or s… ([#17641](https://github.com/cypress-io/cypress/issues/17641)) ([1d2b053](https://github.com/cypress-io/cypress/commit/1d2b053322eb36935928122e4552563a7f98f35d)), closes [#17624](https://github.com/cypress-io/cypress/issues/17624) [#17373](https://github.com/cypress-io/cypress/issues/17373)
# [@cypress/vite-dev-server-v2.0.5](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.0.4...@cypress/vite-dev-server-v2.0.5) (2021-08-04)
@@ -4,7 +4,7 @@ import Hello from './Hello.vue'
describe('Hello', () => {
it('shows error for short text', () => {
cy.viewport(300, 200)
cy.viewport(500, 800)
mount(Hello)
// use the component like a real user
cy.findByRole('textbox').type('abc')
@@ -3,12 +3,16 @@
<input v-model="username" />
<div v-if="error" class="error">{{ error }}</div>
<img src="./logo.png" />
<Card/>
</div>
</template>
<script>
import Card from "../Card/Card.vue"
export default {
name: "Hello",
components: { Card },
data() {
return {
username: "",
+2 -1
View File
@@ -34,7 +34,8 @@
"files": [
"dist",
"client",
"index.html"
"index.html",
"index.d.ts"
],
"license": "MIT",
"repository": {
+1 -8
View File
@@ -4,14 +4,7 @@ const debug = debugFn('cypress:vite-dev-server:vite')
export { StartDevServerOptions }
type DoneCallback = () => unknown
export interface ResolvedDevServerConfig {
port: number
close: (done?: DoneCallback) => void
}
export async function startDevServer (startDevServerArgs: StartDevServerOptions): Promise<ResolvedDevServerConfig> {
export async function startDevServer (startDevServerArgs: StartDevServerOptions): Promise<Cypress.ResolvedDevServerConfig> {
const viteDevServer = await createDevServer(startDevServerArgs)
const app = await viteDevServer.listen()
+24 -8
View File
@@ -34,8 +34,8 @@ interface Spec{
export const makeCypressPlugin = (
projectRoot: string,
supportFilePath: string,
devServerEvents: EventEmitter,
supportFilePath: string | false,
devServerEvents: NodeJS.EventEmitter,
specs: Spec[],
): Plugin => {
let base = '/'
@@ -93,8 +93,10 @@ export const makeCypressPlugin = (
let moduleImporters = server.moduleGraph.fileToModulesMap.get(file)
let iterationNumber = 0
const exploredFiles = new Set<string>()
// until we reached a point where the current module is imported by no other
while (moduleImporters && moduleImporters.size) {
while (moduleImporters?.size) {
if (iterationNumber > HMR_DEPENDENCY_LOOKUP_MAX_ITERATION) {
debug(`max hmr iteration reached: ${HMR_DEPENDENCY_LOOKUP_MAX_ITERATION}; Rerun will not happen on this file change.`)
@@ -108,19 +110,22 @@ export const makeCypressPlugin = (
debug('handleHotUpdate - support compile success')
devServerEvents.emit('dev-server:compile:success')
// if we update support we know we have to re-run it all
// no need to ckeck further
return []
}
if (mod.file && specsPathsSet.has(mod.file)) {
debug('handleHotUpdate - spec compile success', mod.file)
devServerEvents.emit('dev-server:compile:success', { specFile: mod.file })
return []
// if we find one spec, does not mean we are done yet,
// there could be other spec files to re-run
// see https://github.com/cypress-io/cypress/issues/17691
}
}
// get all the modules that import the current one
moduleImporters = getImporters(moduleImporters)
moduleImporters = getImporters(moduleImporters, exploredFiles)
iterationNumber += 1
}
@@ -129,11 +134,22 @@ export const makeCypressPlugin = (
}
}
function getImporters (modules: Set<ModuleNode>): Set<ModuleNode> {
/**
* Gets all the modules that import the set of modules passed in parameters
* @param modules the set of module whose dependents to return
* @param alreadyExploredFiles set of files that have already been looked at and should be avoided in case of circular dependency
* @returns a set of ModuleMode that import directly the current modules
*/
function getImporters (modules: Set<ModuleNode>, alreadyExploredFiles: Set<string>): Set<ModuleNode> {
const allImporters = new Set<ModuleNode>()
modules.forEach((m) => {
m.importers.forEach((imp) => allImporters.add(imp))
if (m.file && !alreadyExploredFiles.has(m.file)) {
alreadyExploredFiles.add(m.file)
m.importers.forEach((imp) => {
allImporters.add(imp)
})
}
})
return allImporters
+11 -11
View File
@@ -6,25 +6,18 @@ import { makeCypressPlugin } from './makeCypressPlugin'
const debug = Debug('cypress:vite-dev-server:start')
interface Options {
specs: Cypress.Cypress['spec'][]
config: Record<string, string>
devServerEvents: EventEmitter
[key: string]: unknown
}
export interface StartDevServerOptions {
/**
* the Cypress options object
* the Cypress dev server configuration object
*/
options: Options
options: Cypress.DevServerConfig
/**
* By default, vite will use your vite.config file to
* Start the server. If you need additional plugins or
* to override some options, you can do so using this.
* @optional
*/
viteConfig?: UserConfig
viteConfig?: Omit<UserConfig, 'base' | 'root'>
}
const resolveServerConfig = async ({ viteConfig, options }: StartDevServerOptions): Promise<InlineConfig> => {
@@ -59,7 +52,14 @@ const resolveServerConfig = async ({ viteConfig, options }: StartDevServerOption
// Ask vite to pre-optimize all dependencies of the specs
finalConfig.optimizeDeps = finalConfig.optimizeDeps || {}
finalConfig.optimizeDeps.entries = [...options.specs.map((spec) => spec.relative), supportFile]
// pre-optimizea all the specs
if ((options.specs && options.specs.length)) {
finalConfig.optimizeDeps.entries = [...options.specs.map((spec) => spec.relative)]
// only optimize a supportFile is it is not false or undefined
if (supportFile) {
finalConfig.optimizeDeps.entries.push(supportFile)
}
}
debug(`the resolved server config is ${JSON.stringify(finalConfig, null, 2)}`)
+11
View File
@@ -76,11 +76,22 @@
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/blob/master/npm/vue/#readme",
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
"bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Fvue&template=1-bug-report.md&title=",
"keywords": [
"cypress",
"vue"
],
"contributors": [
{
"name": "Jessica Sachs",
"social": "@JessicaSachs"
},
{
"name": "Amir Rustamzadeh",
"social": "@amirrustam"
}
],
"module": "dist/cypress-vue.esm-bundler.js",
"peerDependenciesMeta": {
"@cypress/webpack-dev-server": {
+14
View File
@@ -1,3 +1,17 @@
# [@cypress/webpack-dev-server-v1.6.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v1.5.0...@cypress/webpack-dev-server-v1.6.0) (2021-09-10)
### Features
* allow usage of @react/plugins with cypress.config.js ([#17738](https://github.com/cypress-io/cypress/issues/17738)) ([da4b1e0](https://github.com/cypress-io/cypress/commit/da4b1e06ce33945aabddda0e6e175dc0e1b488a5))
# [@cypress/webpack-dev-server-v1.5.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v1.4.0...@cypress/webpack-dev-server-v1.5.0) (2021-08-30)
### Features
* support webpack-dev-server v4 ([#17918](https://github.com/cypress-io/cypress/issues/17918)) ([16e4759](https://github.com/cypress-io/cypress/commit/16e4759e0196f68c5f0525efb020211337748f94))
# [@cypress/webpack-dev-server-v1.4.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v1.3.1...@cypress/webpack-dev-server-v1.4.0) (2021-06-17)
+4 -3
View File
@@ -6,7 +6,8 @@
"scripts": {
"build": "tsc",
"build-prod": "tsc",
"test": "tsc && mocha -r @packages/ts/register test/**/*.spec.ts test/*.spec.ts --exit",
"test": "node ./test-wds-3.js",
"test-all": "tsc && mocha -r @packages/ts/register test/**/*.spec.ts test/*.spec.ts --exit",
"watch": "tsc -w"
},
"dependencies": {
@@ -16,7 +17,7 @@
},
"devDependencies": {
"@types/webpack": ">=4",
"@types/webpack-dev-server": "^3.11.1",
"@types/webpack-dev-server": "^4.0.0",
"chai": "^4.2.0",
"html-webpack-plugin": "4.x",
"mocha": "^8.1.3",
@@ -24,7 +25,7 @@
"speed-measure-webpack-plugin": "1.4.2",
"typescript": "^4.2.3",
"webpack": "^4.44.2",
"webpack-dev-server": "^3.11.0"
"webpack-dev-server": "^4.0.0"
},
"peerDependencies": {
"html-webpack-plugin": ">=4",
+26 -10
View File
@@ -2,6 +2,7 @@ import { debug as debugFn } from 'debug'
import { AddressInfo } from 'net'
import { Server } from 'http'
import { start as createDevServer, StartDevServer } from './startServer'
import { webpackDevServerFacts } from './webpackDevServerFacts'
const debug = debugFn('cypress:webpack-dev-server:webpack')
@@ -17,26 +18,41 @@ export { StartDevServer }
export async function startDevServer (startDevServerArgs: StartDevServer, exitProcess = process.exit) {
const webpackDevServer = await createDevServer(startDevServerArgs, exitProcess)
return new Promise<ResolvedDevServerConfig>((resolve) => {
const httpSvr = webpackDevServer.listen(0, '127.0.0.1', () => {
// webpack-dev-server v3 returns `http.Server`.
// v4 returns a Promise that resolves `http.Server`.
// use Promise.resolve to make sure we get the `http.Server`,
// regardless of webpack-dev-server version.
Promise.resolve(httpSvr).then((server: Server) => {
return new Promise<ResolvedDevServerConfig>(async (resolve, reject) => {
if (webpackDevServerFacts.isV3()) {
const server: Server = webpackDevServer.listen(0, '127.0.0.1', () => {
// FIXME: handle address returning a string
const port = (server.address() as AddressInfo).port
debug('Component testing webpack server started on port', port)
return resolve({
resolve({
port,
close: (done?: DoneCallback) => {
httpSvr.close()
server.close()
done?.()
},
})
})
})
return
}
if (webpackDevServerFacts.isV4()) {
await webpackDevServer.start()
resolve({
// @ts-expect-error @types do not yet support v4
port: webpackDevServer.options.port,
close: (done?: DoneCallback) => {
webpackDevServer.stop()
done?.()
},
})
return
}
reject(webpackDevServerFacts.unsupported())
})
}
+19 -9
View File
@@ -1,12 +1,12 @@
import Debug from 'debug'
import webpack from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import webpackDevServerPkg from 'webpack-dev-server/package.json'
import { makeWebpackConfig, UserWebpackDevServerOptions } from './makeWebpackConfig'
import { webpackDevServerFacts } from './webpackDevServerFacts'
export interface StartDevServer extends UserWebpackDevServerOptions {
/* this is the Cypress options object */
options: Cypress.DevServerOptions
/* this is the Cypress dev server configuration object */
options: Cypress.DevServerConfig
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
/* base html template to render in AUT */
@@ -53,25 +53,35 @@ export async function start ({ webpackConfig: userWebpackConfig, template, optio
hot: false,
}
if (webpackDevServerPkg.version.match(/3\./)) {
if (webpackDevServerFacts.isV3()) {
debug('using webpack-dev-server v3')
webpackDevServerConfig = {
...webpackDevServerConfig,
// @ts-ignore ignore webpack-dev-server v3 type errors
inline: false,
publicPath: devServerPublicPathRoute,
noInfo: false,
}
} else if (webpackDevServerPkg.version.match(/4\./)) {
// @ts-ignore ignore webpack-dev-server v3 type errors
return new WebpackDevServer(compiler, webpackDevServerConfig)
}
if (webpackDevServerFacts.isV4()) {
debug('using webpack-dev-server v4')
webpackDevServerConfig = {
host: 'localhost',
port: 'auto',
...userWebpackConfig?.devServer,
devMiddleware: {
publicPath: devServerPublicPathRoute,
},
hot: false,
}
} else {
throw Error(`@cypress/webpack-dev-server only supports webpack-dev-server v3 and v4. Found: ${webpackDevServerPkg.version}.`)
// @ts-expect-error Webpack types are clashing between Webpack and WebpackDevServer
return new WebpackDevServer(webpackDevServerConfig, compiler)
}
// @ts-ignore types for webpack v5 are incorrect?
return new WebpackDevServer(compiler, webpackDevServerConfig)
throw webpackDevServerFacts.unsupported()
}
@@ -0,0 +1,14 @@
import webpackDevServerPkg from 'webpack-dev-server/package.json'
export const webpackDevServerFacts = {
version: webpackDevServerPkg.version,
isV3 (version = webpackDevServerPkg.version) {
return /^3\./.test(version)
},
isV4 (version = webpackDevServerPkg.version) {
return /^4\./.test(version)
},
unsupported () {
return Error(`@cypress/webpack-dev-server only supports webpack-dev-server v3 and v4. Found: ${webpackDevServerFacts.version}.`)
},
}
+49
View File
@@ -0,0 +1,49 @@
const execa = require('execa')
const pkg = require('./package.json')
const fs = require('fs')
/**
* This file installs WebpackDevServer 3 and runs the tests for the dev-server.
* We read package.json, update the webpack version, then re-run yarn install.
* After it finishes, pass or fail,
* we revert the package.json back to the original state.
*
* The tests for the example projects (inside of examples) run with WebpackDevServer 3.
* This ensures we have some coverage for both versions.
*/
const main = async () => {
const originalPkg = JSON.stringify(pkg, null, 2)
const resetPkg = async () => {
fs.writeFileSync('package.json', originalPkg, 'utf8')
await execa('yarn', ['install'], { stdio: 'inherit' })
}
const checkExit = async ({ exitCode }) => {
if (typeof exitCode !== 'number') {
// eslint-disable-next-line no-console
console.error(`Finished with missing exit code from execa (received ${exitCode})`)
}
await resetPkg()
process.exit(exitCode)
}
pkg.devDependencies['webpack-dev-server'] = '3.11.0'
// eslint-disable-next-line no-console
console.log('[@cypress/webpack-dev-server]: updating package.json...')
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2))
// eslint-disable-next-line no-console
console.log('[@cypress/webpack-dev-server]: install dependencies...')
await execa('yarn', ['install'], { stdio: 'inherit' })
const { exitCode } = await execa('yarn', ['test-all'], { stdio: 'inherit' })
await checkExit({ exitCode })
}
// execute main function if called from command line
if (require.main === module) {
main()
}
+6 -6
View File
@@ -1,10 +1,10 @@
import webpack from 'webpack'
import path from 'path'
import sinon from 'sinon'
import { expect } from 'chai'
import { EventEmitter } from 'events'
import http from 'http'
import fs from 'fs'
import { webpackDevServerFacts } from '../src/webpackDevServerFacts'
import { startDevServer } from '../'
@@ -34,11 +34,11 @@ const requestSpecFile = (port: number) => {
const root = path.join(__dirname, '..')
const webpackConfig: webpack.Configuration = {
output: {
path: root,
publicPath: root,
},
const webpackConfig = {
devServer: webpackDevServerFacts.isV3()
? { contentBase: root }
: { static: { directory: root } },
}
const specs: Cypress.Cypress['spec'][] = [
@@ -0,0 +1,19 @@
import { expect } from 'chai'
import { webpackDevServerFacts } from '../../src/webpackDevServerFacts'
describe('webpackDevServerFacts', () => {
it('should detect v3', () => {
expect(webpackDevServerFacts.isV3('3.0.0')).equals(true)
expect(webpackDevServerFacts.isV3('3.1.4')).equals(true)
expect(webpackDevServerFacts.isV3('4.0.0')).equals(false)
expect(webpackDevServerFacts.isV3('4.3.0')).equals(false)
})
it('should detect v4', () => {
expect(webpackDevServerFacts.isV4('3.0.0')).equals(false)
expect(webpackDevServerFacts.isV4('3.1.4')).equals(false)
expect(webpackDevServerFacts.isV4('3.4.4')).equals(false)
expect(webpackDevServerFacts.isV4('4.0.0')).equals(true)
expect(webpackDevServerFacts.isV4('4.3.0')).equals(true)
})
})
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "cypress",
"version": "8.2.0",
"version": "8.4.1",
"description": "Cypress.io end to end testing tool",
"private": true,
"scripts": {
@@ -77,7 +77,7 @@
"@cypress/env-or-json-file": "2.0.0",
"@cypress/github-commit-status-check": "1.5.0",
"@cypress/questions-remain": "1.0.1",
"@cypress/request": "2.88.5",
"@cypress/request": "2.88.6",
"@cypress/request-promise": "4.2.6",
"@fellow/eslint-plugin-coffee": "0.4.13",
"@percy/cli": "1.0.0-beta.48",
@@ -160,7 +160,7 @@
"lint-staged": "11.0.0",
"listr2": "3.8.3",
"lodash": "4.17.21",
"make-empty-github-commit": "1.2.0",
"make-empty-github-commit": "cypress-io/make-empty-github-commit#4a592aedb776ba2f4cc88979055315a53eec42ee",
"minimist": "1.2.5",
"mocha": "3.5.3",
"mocha-banner": "1.1.2",
@@ -196,7 +196,7 @@
"yarn-deduplicate": "3.1.0"
},
"engines": {
"node": ">=14.16.0",
"node": ">=14.17.0",
"yarn": ">=1.17.3"
},
"productName": "Cypress",
@@ -132,7 +132,6 @@
"integrationFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/integration",
"isHeadless": false,
"isNewProject": false,
"javascripts": [],
"morgan": true,
"namespace": "__cypress",
"numTestsKeptInMemory": 50,
@@ -852,34 +852,101 @@ describe('Runs List', function () {
})
})
it('displays empty message', () => {
cy.contains('To record your first')
})
context('a/b control group', function () {
beforeEach(function () {
this.getProjectStatus.resolve({
orgId: '0',
})
})
it('opens project id guide on clicking "Why?"', () => {
cy.contains('Why?').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/what-is-a-project-id')
it('displays empty message', () => {
cy.contains('To record your first run')
cy.percySnapshot()
})
it('opens project id guide on clicking "Why?"', () => {
cy.contains('Why?').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/what-is-a-project-id' })
})
})
it('opens dashboard on clicking "Cypress Dashboard"', () => {
cy.contains('Cypress Dashboard').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.config.projectId}/runs`)
})
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
cy.get('#code-record-command').find('.action-copy').trigger('mouseout')
})
it('copies record key command to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').click()
.then(function () {
expect(this.ipc.setClipboardText).to.be.calledWith(`cypress run --record --key <record-key>`)
})
})
})
it('opens dashboard on clicking "Cypress Dashboard"', () => {
cy.contains('Cypress Dashboard').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.config.projectId}/runs`)
context('a/b test group', function () {
beforeEach(function () {
this.getProjectStatus.resolve({
orgId: '1',
})
})
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
cy.get('#code-record-command').find('.action-copy').trigger('mouseout')
})
it('displays empty message', () => {
cy.contains('How to record your first run')
cy.percySnapshot()
})
it('copies record key command to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').click()
.then(function () {
expect(this.ipc.setClipboardText).to.be.calledWith(`cypress run --record --key <record-key>`)
it('displays tooltip with project id info', () => {
cy.get('.help-text').eq(0).find('a').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'This helps Cypress uniquely identify your project')
.contains('Learn more').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/what-is-a-project-id' })
})
})
it('displays tooltip with record run command info', () => {
cy.get('.help-text').eq(1).find('a').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Close this application and run this command')
.contains('Learn more').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/recording-project-runs' })
})
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
cy.get('#code-record-command').find('.action-copy').trigger('mouseout')
})
it('copies record key command to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').click()
.then(function () {
expect(this.ipc.setClipboardText).to.be.calledWith(`cypress run --record --key <record-key>`)
})
})
it('displays run in ci panel with link', () => {
cy.contains('Run in CI').parents('.panel').contains('Show me how').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/ci' })
})
})
it('displays sample project panel with link', () => {
cy.contains('Sample Project').parents('.panel').contains('See the sample').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/rwa-dashboard' })
})
})
})
})
@@ -75,7 +75,7 @@ const onSubmitNewProject = function (orgId) {
it('displays empty runs page', function () {
cy.get('.setup-project').should('not.exist')
cy.contains('To record your first')
cy.contains('How to record your first')
cy.contains('cypress run --record --key record-key-123')
})
@@ -282,4 +282,10 @@ export default class Project {
serialize () {
return _.pick(this, cacheProps)
}
getTestGroup (numGroups) {
const numKey = this.orgId && this.orgId.length ? this.orgId.charCodeAt(0) : 0
return numKey % numGroups
}
}
@@ -137,6 +137,17 @@ const closeProject = (project) => {
])
}
const updateProjectStatus = (project) => {
return ipc.getProjectStatus(project.clientDetails())
.then((projectDetails) => {
project.update(projectDetails)
})
.catch(ipc.isUnauthed, ipc.handleUnauthed)
.catch((err) => {
project.setApiError(err)
})
}
const openProject = (project) => {
specsStore.loading(true)
@@ -145,17 +156,6 @@ const openProject = (project) => {
project.setError(err)
}
const updateProjectStatus = () => {
return ipc.getProjectStatus(project.clientDetails())
.then((projectDetails) => {
project.update(projectDetails)
})
.catch(ipc.isUnauthed, ipc.handleUnauthed)
.catch((err) => {
project.setApiError(err)
})
}
const updateConfig = (config) => {
project.update({
id: config.projectId,
@@ -199,9 +199,9 @@ const openProject = (project) => {
project.setLoading(false)
getSpecs(setProjectError)
projectPollingId = setInterval(updateProjectStatus, 10000)
projectPollingId = setInterval(() => updateProjectStatus(project), 10000)
return updateProjectStatus()
return updateProjectStatus(project)
})
.catch(setProjectError)
}
@@ -242,6 +242,7 @@ const getRecordKeys = () => {
export default {
loadProjects,
updateProjectStatus,
openProject,
reopenProject,
closeProject,
@@ -0,0 +1,228 @@
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import Tooltip from '@cypress/react-tooltip'
import Loader from 'react-loader'
import ipc from '../lib/ipc'
import projectsApi from '../projects/projects-api'
import { configFileFormatted } from '../lib/config-file-formatted'
@observer
class RunsListEmpty extends Component {
componentDidMount () {
this._updateProjectStatus()
}
componentDidUpdate () {
this._updateProjectStatus()
}
_updateProjectStatus = () => {
const { project } = this.props
if (!project.orgId) {
projectsApi.updateProjectStatus(project)
}
}
_openProjectIdGuide = (e, utm_medium = 'Empty Runs Tab') => {
e.preventDefault()
ipc.externalOpen({
url: 'https://on.cypress.io/what-is-a-project-id',
params: {
utm_medium,
utm_campaign: 'ProjectId',
},
})
}
_openRuns = (e) => {
e.preventDefault()
ipc.externalOpen(`https://on.cypress.io/dashboard/projects/${this.props.project.id}/runs`)
}
_openCiGuide = (e, utm_medium = 'Empty Runs Tab') => {
e.preventDefault()
ipc.externalOpen({
url: 'https://on.cypress.io/ci',
params: {
utm_medium,
utm_campaign: 'CI',
},
})
}
_openRunGuide = (e, utm_medium = 'Empty Runs Tab') => {
e.preventDefault()
ipc.externalOpen({
url: 'https://on.cypress.io/recording-project-runs',
params: {
utm_medium,
utm_campaign: 'Runs Guide',
},
})
}
_openSampleProject = (e) => {
e.preventDefault()
ipc.externalOpen({
url: 'https://on.cypress.io/rwa-dashboard',
params: {
utm_medium: 'Empty Runs Tab',
utm_camptain: 'Sample Project',
},
})
}
_recordCommand = () => {
return `cypress run --record --key ${this.props.recordKey || '<record-key>'}`
}
_control = () => {
return (
<div>
<div className='first-run-instructions'>
<h4 className='center'>
To record your first run...
</h4>
<h5>
<span>
1. <code>projectId: {this.props.project.id}</code> has been saved to your {configFileFormatted(this.props.project.configFile)}.{' '}
Make sure to check this file into source control.
</span>
<a onClick={(e) => this._openProjectIdGuide(e, 'Control Empty Runs Tab')}>
<i className='fas fa-question-circle' />{' '}
Why?
</a>
</h5>
<h5>
<span>
2. Run this command now, or in CI.
</span>
<a onClick={(e) => this._openCiGuide(e, 'Control Empty Runs Tab')}>
<i className='fas fa-question-circle' />{' '}
Need help?
</a>
</h5>
<pre id="code-record-command" className="copy-to-clipboard">
<a className="action-copy" onClick={() => ipc.setClipboardText(this._recordCommand())}>
<Tooltip
title='Copy to clipboard'
placement='top'
className='cy-tooltip'
>
<i className='fas fa-clipboard' />
</Tooltip>
</a>
<code>{this._recordCommand()}</code>
</pre>
<hr />
<p className='alert alert-default'>
<i className='fas fa-info-circle' />{' '}
Recorded runs will show up{' '}
<a href='#' onClick={(e) => this._openRunGuide(e, 'Control Empty Runs Tab')}>here</a>{' '}
and on your{' '}
<a href='#' onClick={this._openRuns}>Cypress Dashboard Service</a>.
</p>
</div>
</div>
)
}
_new = () => {
return (
<div>
<div className='first-run-instructions new-first-run-instructions'>
<h4>
How to record your first run
</h4>
<p className='subtitle'>
Recording test runs to the Dashboard enables you to run tests faster with parallelization and load balancing, debug failed tests in CI with screenshots and videos, and identify flaky tests.
</p>
<div className='step'>
<span>
1. <code>projectId: {this.props.project.id}</code> has been saved to your {configFileFormatted(this.props.project.configFile)}.{' '}
Make sure to check this file into source control.
</span>
<span className='help-text'>
<Tooltip
className='tooltip-text-left cy-tooltip'
title={<span>This helps Cypress uniquely identify your project. If altered or deleted, analytics and load balancing will not function properly. <a onClick={this._openProjectIdGuide}>Learn more</a></span>}
>
<a><i className='fas fa-question-circle' /></a>
</Tooltip>
</span>
</div>
<div className='step'>
<span>
2. Run this command now, or in CI.
</span>
<span className='help-text'>
<Tooltip
className='tooltip-text-left cy-tooltip'
title={<span>Close this application and run this command with <code className='tooltip-code'>npx</code> or <code className='tooltip-code'>yarn</code> in your terminal. <a onClick={this._openRunGuide}>Learn more</a></span>}
>
<a><i className='fas fa-question-circle' /></a>
</Tooltip>
</span>
</div>
<pre id="code-record-command" className="copy-to-clipboard">
<a className="action-copy" onClick={() => ipc.setClipboardText(this._recordCommand())}>
<Tooltip
title='Copy to clipboard'
placement='top'
className='cy-tooltip'
>
<i className='fas fa-clipboard' />
</Tooltip>
</a>
<code>{this._recordCommand()}</code>
</pre>
<hr />
<div className='panel-wrapper'>
<div className='panel'>
<div className='panel-icon panel-icon-small'>
<i className='fas fa-infinity fa-fw' />
</div>
<div>
<p>
<strong>Run in CI</strong>
<br />
Cypress was designed to be run in your CI, enabling parallel test runs and rich test analytics. <a onClick={this._openCiGuide}>Show me how</a>
</p>
</div>
</div>
<div className='panel'>
<div className='panel-icon'>
<i className='fas fa-tasks fa-fw' />
</div>
<div>
<p>
<strong>Sample Project</strong>
<br />
Want to see what a recorded run looks like? See an example project in the Dashboard. <a onClick={this._openSampleProject}>See the sample</a>
</p>
</div>
</div>
</div>
</div>
</div>
)
}
render () {
const { project } = this.props
if (!project.orgId) {
return <Loader color='#888' scale={0.5}/>
}
if (project.getTestGroup(2)) {
return this._new()
}
return this._control()
}
}
export default RunsListEmpty
+8 -77
View File
@@ -2,10 +2,8 @@ import _ from 'lodash'
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import Loader from 'react-loader'
import Tooltip from '@cypress/react-tooltip'
import ipc from '../lib/ipc'
import { configFileFormatted } from '../lib/config-file-formatted'
import authStore from '../auth/auth-store'
import RunsStore from './runs-store'
import errors from '../lib/errors'
@@ -21,6 +19,7 @@ import PermissionMessage from './permission-message'
import ProjectNotSetup from './project-not-setup'
import DashboardBanner from './dashboard-banner'
import WhatIsDashboard from './what-is-dashboard'
import RunsListEmpty from './runs-list-empty'
@observer
class RunsList extends Component {
@@ -45,8 +44,8 @@ class RunsList extends Component {
}
componentDidUpdate () {
this._getRecordKeys()
this._handlePolling()
this._getRecordKeys()
}
componentWillUnmount () {
@@ -204,7 +203,7 @@ class RunsList extends Component {
// OR they have setup CI
}
return this._empty()
return <RunsListEmpty project={this.props.project} recordKey={this.state.recordKey} />
}
//--------End Run States----------//
@@ -320,79 +319,6 @@ class RunsList extends Component {
})
}
_empty () {
const recordCommand = `cypress run --record --key ${this.state.recordKey || '<record-key>'}`
return (
<div>
<div className='first-run-instructions'>
<h4>
To record your first run...
</h4>
<h5>
<span>
1. <code>projectId: {this.props.project.id}</code> has been saved to your {configFileFormatted(this.props.project.configFile)}.{' '}
Make sure to check this file into source control.
</span>
<a onClick={this._openProjectIdGuide}>
<i className='fas fa-question-circle' />{' '}
Why?
</a>
</h5>
<h5>
<span>
2. Run this command now, or in CI.
</span>
<a onClick={this._openCiGuide}>
<i className='fas fa-question-circle' />{' '}
Need help?
</a>
</h5>
<pre id="code-record-command" className="copy-to-clipboard">
<a className="action-copy" onClick={() => ipc.setClipboardText(recordCommand)}>
<Tooltip
title='Copy to clipboard'
placement='top'
className='cy-tooltip'
>
<i className='fas fa-clipboard' />
</Tooltip>
</a>
<code>{recordCommand}</code>
</pre>
<hr />
<p className='alert alert-default'>
<i className='fas fa-info-circle' />{' '}
Recorded runs will show up{' '}
<a href='#' onClick={this._openRunGuide}>here</a>{' '}
and on your{' '}
<a href='#' onClick={this._openRuns}>Cypress Dashboard Service</a>.
</p>
</div>
</div>
)
}
_openRunGuide = (e) => {
e.preventDefault()
ipc.externalOpen('https://on.cypress.io/recording-project-runs')
}
_openRuns = (e) => {
e.preventDefault()
ipc.externalOpen(`https://on.cypress.io/dashboard/projects/${this.props.project.id}/runs`)
}
_openCiGuide = (e) => {
e.preventDefault()
ipc.externalOpen('https://on.cypress.io/guides/continuous-integration')
}
_openProjectIdGuide = (e) => {
e.preventDefault()
ipc.externalOpen('https://on.cypress.io/what-is-a-project-id')
}
_openDashboard = (e) => {
e.preventDefault()
ipc.externalOpen({
@@ -404,6 +330,11 @@ class RunsList extends Component {
})
}
_openRuns = (e) => {
e.preventDefault()
ipc.externalOpen(`https://on.cypress.io/dashboard/projects/${this.props.project.id}/runs`)
}
_openRun = (buildNumber) => {
ipc.externalOpen(`https://on.cypress.io/dashboard/projects/${this.props.project.id}/runs/${buildNumber}`)
}
+53 -1
View File
@@ -22,6 +22,10 @@
.first-run-instructions {
padding: 40px 130px;
&.new-first-run-instructions {
padding: 40px 100px;
}
a {
cursor: pointer;
}
@@ -49,6 +53,18 @@
}
}
.step {
display: flex;
font-weight: 400;
font-size: 14px;
margin: 20px 0 10px;
line-height: 18px;
.help-text {
margin-left: auto;
}
}
.alert {
border-radius: 3px;
border: 1px solid #eee;
@@ -74,7 +90,8 @@
margin-top: 25px;
margin-bottom: 25px;
}
h4, .center {
.center {
text-align: center;
}
@@ -85,6 +102,41 @@
border-radius: 3px;
color: #eee;
}
.subtitle {
color: #666;
font-size: 13px;
text-align: left;
}
.panel-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
.panel {
border: 1px solid #E5E5E5;
border-radius: 6px;
display: flex;
font-size: 13px;
padding: 12px;
p {
margin-bottom: 0;
text-align: left;
}
.panel-icon {
color: $brand-primary;
font-size: 18px;
padding-right: 12px;
&.panel-icon-small {
font-size: 16px;
}
}
}
}
}
.runs {
@@ -150,4 +150,16 @@ pre.copy-to-clipboard {
color: #E2E8F0;
}
}
}
}
.cy-tooltip {
&.tooltip-text-left {
text-align: left;
}
.tooltip-code {
color: #fff;
background-color: #252831;
border: none;
}
}
+1
View File
@@ -1,6 +1,7 @@
{
"projectId": "ypt4pf",
"baseUrl": "http://localhost:3500",
"testFiles": "**/*",
"hosts": {
"*.foobar.com": "127.0.0.1"
},
@@ -213,6 +213,10 @@
<input type="checkbox" name="colors" value="blue" />
<input type="checkbox" name="colors" value="green" />
<input type="checkbox" name="colors" value="red" />
<input type="checkbox" name="dogs" value="husky" />
<input type="checkbox" name="dogs" value="poodle" />
<input type="checkbox" name="dogs" value="on" />
<input type="checkbox" name="dogs" data-no-value="true" />
<input type="tel">
</form>
@@ -523,6 +523,18 @@ describe('src/cy/commands/actions/check', () => {
cy.get(':checkbox:first').check().should('have.class', 'checked')
})
it('throws when cmd recieves values but subject has no value attribute', function (done) {
cy.get('[name=dogs]').check(['husky', 'poodle', 'on']).then(($chk) => {
expect($chk.length).to.eq(4)
})
cy.on('fail', (err) => {
expect(err.message).to.include(' cannot be checked/unchecked because it has no \`value\` attribute')
done()
})
})
})
describe('.log', () => {
@@ -1074,6 +1086,18 @@ describe('src/cy/commands/actions/check', () => {
cy.get(':checkbox:first').uncheck().should('have.class', 'unchecked')
})
it('throws when cmd recieves values but subject has no value attribute', function (done) {
cy.get('[name=dogs]').uncheck(['husky', 'poodle', 'on']).then(($chk) => {
expect($chk.length).to.eq(4)
})
cy.on('fail', (err) => {
expect(err.message).to.include(' cannot be checked/unchecked because it has no \`value\` attribute')
done()
})
})
})
describe('.log', () => {
@@ -1,5 +1,5 @@
const { $ } = Cypress.$Cypress
const { _ } = Cypress
const { $ } = window.Cypress.$Cypress
const { _ } = window.Cypress
describe('src/cy/commands/actions/scroll', () => {
before(() => {
@@ -65,7 +65,6 @@ describe('src/cy/commands/actions/select', () => {
expect($select.val()).to.equal('same')
expect($select.find('option:selected')).to.have.text('Uhura')
expect($select[0].selectedIndex).to.equal(2)
expect($select[0].selectedOptions[0]).to.eql($select.find('option:selected')[0])
})
})
@@ -74,7 +73,6 @@ describe('src/cy/commands/actions/select', () => {
expect($select.val()).to.equal('same')
expect($select.find('option:selected')).to.have.text('Uhura')
expect($select[0].selectedIndex).to.equal(2)
expect($select[0].selectedOptions[0]).to.eql($select.find('option:selected')[0])
})
})
@@ -24,8 +24,6 @@ const expectTextEndsWith = (expected) => {
}
}
const isChromium = Cypress.isBrowser({ family: 'chromium' })
describe('src/cy/commands/actions/type - #type', () => {
beforeEach(() => {
cy.visit('/fixtures/dom.html')
@@ -1659,7 +1657,7 @@ describe('src/cy/commands/actions/type - #type', () => {
href="#"
target="_blank" alt="area" />
</map>
<img usemap="#map" src="/__cypress/static/favicon.ico" alt="image" />
<img usemap="#map" src="/fixtures/media/cypress.png" alt="image" />
`).prependTo(cy.$$('body'))
let keydown = cy.stub()
@@ -2028,12 +2026,8 @@ describe('src/cy/commands/actions/type - #type', () => {
.type(' f\n{backspace}')
.type('{moveToStart}{del}')
.then(($el) => {
if (isChromium) {
expect(stub).callCount(5)
expect($el[0].value).eq('oo bar baz ')
} else {
expect(stub, 'should NOT send beforeinput unless in chromium based browser').not.called
}
expect(stub).callCount(5)
expect($el[0].value).eq('oo bar baz ')
})
})
@@ -2076,12 +2070,8 @@ describe('src/cy/commands/actions/type - #type', () => {
.type(' f\n{backspace}')
.type('{moveToStart}{del}')
.then(($el) => {
if (isChromium) {
expect(stub).callCount(5)
expect($el[0].value).eq('oo bar baz f')
} else {
expect(stub, 'should NOT send beforeinput unless in chromium based browser').not.called
}
expect(stub).callCount(5)
expect($el[0].value).eq('oo bar baz f')
})
})
@@ -2124,12 +2114,8 @@ describe('src/cy/commands/actions/type - #type', () => {
.type(' f\n{backspace}')
.type('{moveToStart}{del}')
.then(($el) => {
if (isChromium) {
expect(stub).callCount(5)
expect($el[0].textContent).eq('oo bar baz f')
} else {
expect(stub, 'should NOT send beforeinput unless in chromium based browser').not.called
}
expect(stub).callCount(5)
expect($el[0].textContent).eq('oo bar baz f')
})
})
@@ -2169,11 +2155,7 @@ describe('src/cy/commands/actions/type - #type', () => {
.type('{ctrl}{backspace}')
.type('{ctrl}{shift}{backspace}')
.then(($el) => {
if (isChromium) {
expect(stub).callCount(4)
} else {
expect(stub, 'should NOT send beforeinput unless in chromium based browser').not.called
}
expect(stub).callCount(4)
})
})
@@ -2190,12 +2172,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
.type('foo')
.then(($el) => {
if (isChromium) {
expect(callCount).eq(3)
expect($el[0].value).eq('foo bar baz')
} else {
expect(callCount, 'should NOT send beforeinput unless in chromium based browser').eq(0)
}
expect(callCount).eq(3)
expect($el[0].value).eq('foo bar baz')
})
})
})
@@ -2573,17 +2551,15 @@ describe('src/cy/commands/actions/type - #type', () => {
// eslint-disable-next-line
console.table(table.data, table.columns)
const beforeinput = Cypress.isBrowser('firefox') ? '' : ' beforeinput,'
expect(table.name).to.eq('Keyboard Events')
const expectedTable = {
1: { 'Details': '{ code: KeyH, which: 72 }', Typed: 'h', 'Events Fired': `keydown, keypress,${beforeinput} textInput, input, keyup`, 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
1: { 'Details': '{ code: KeyH, which: 72 }', Typed: 'h', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
2: { 'Details': '{ code: ControlLeft, which: 17 }', Typed: '{ctrl}', 'Events Fired': 'keydown', 'Active Modifiers': 'ctrl', 'Prevented Default': null, 'Target Element': $input[0] },
3: { 'Details': '{ code: AltLeft, which: 18 }', Typed: '{alt}', 'Events Fired': 'keydown', 'Active Modifiers': 'alt, ctrl', 'Prevented Default': null, 'Target Element': $input[0] },
4: { 'Details': '{ code: Equal, which: 187 }', Typed: '+', 'Events Fired': 'keydown, keyup', 'Active Modifiers': 'alt, ctrl', 'Prevented Default': null, 'Target Element': $input[0] },
5: { 'Details': '{ code: AltLeft, which: 18 }', Typed: '{alt}', 'Events Fired': 'keyup', 'Active Modifiers': 'ctrl', 'Prevented Default': null, 'Target Element': $input[0] },
6: { 'Details': '{ code: ControlLeft, which: 17 }', Typed: '{ctrl}', 'Events Fired': 'keyup', 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
7: { 'Details': '{ code: KeyI, which: 73 }', Typed: 'i', 'Events Fired': `keydown, keypress,${beforeinput} textInput, input, keyup`, 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
7: { 'Details': '{ code: KeyI, which: 73 }', Typed: 'i', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
}
// uncomment for debugging
@@ -3484,20 +3460,18 @@ describe('src/cy/commands/actions/type - #type', () => {
// eslint-disable-next-line
console.table(table.data, table.columns)
const beforeInput = isChromium ? 'beforeinput, ' : ''
expect(table.name).to.eq('Keyboard Events')
const expectedTable = {
1: { 'Details': '{ code: MetaLeft, which: 91 }', Typed: '{cmd}', 'Events Fired': 'keydown', 'Active Modifiers': 'meta', 'Prevented Default': null, 'Target Element': $input[0] },
2: { 'Details': '{ code: AltLeft, which: 18 }', Typed: '{option}', 'Events Fired': 'keydown', 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
3: { 'Details': '{ code: KeyF, which: 70 }', Typed: 'f', 'Events Fired': `keydown, keypress, ${beforeInput}textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
4: { 'Details': '{ code: KeyO, which: 79 }', Typed: 'o', 'Events Fired': `keydown, keypress, ${beforeInput}textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
5: { 'Details': '{ code: KeyO, which: 79 }', Typed: 'o', 'Events Fired': `keydown, keypress, ${beforeInput}textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
6: { 'Details': '{ code: Enter, which: 13 }', Typed: '{enter}', 'Events Fired': `keydown, keypress, ${beforeInput}keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
7: { 'Details': '{ code: KeyB, which: 66 }', Typed: 'b', 'Events Fired': `keydown, keypress, ${beforeInput}textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
3: { 'Details': '{ code: KeyF, which: 70 }', Typed: 'f', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
4: { 'Details': '{ code: KeyO, which: 79 }', Typed: 'o', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
5: { 'Details': '{ code: KeyO, which: 79 }', Typed: 'o', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
6: { 'Details': '{ code: Enter, which: 13 }', Typed: '{enter}', 'Events Fired': `keydown, keypress, beforeinput, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
7: { 'Details': '{ code: KeyB, which: 66 }', Typed: 'b', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
8: { 'Details': '{ code: ArrowLeft, which: 37 }', Typed: '{leftarrow}', 'Events Fired': 'keydown, keyup', 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
9: { 'Details': '{ code: Delete, which: 46 }', Typed: '{del}', 'Events Fired': `keydown, ${beforeInput}input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
10: { 'Details': '{ code: Enter, which: 13 }', Typed: '{enter}', 'Events Fired': `keydown, keypress, ${beforeInput}keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
9: { 'Details': '{ code: Delete, which: 46 }', Typed: '{del}', 'Events Fired': `keydown, beforeinput, input, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
10: { 'Details': '{ code: Enter, which: 13 }', Typed: '{enter}', 'Events Fired': `keydown, keypress, beforeinput, keyup`, 'Active Modifiers': 'alt, meta', 'Prevented Default': null, 'Target Element': $input[0] },
11: { 'Details': '{ code: MetaLeft, which: 91 }', Typed: '{cmd}', 'Events Fired': 'keyup', 'Active Modifiers': 'alt', 'Prevented Default': null, 'Target Element': $input[0] },
12: { 'Details': '{ code: AltLeft, which: 18 }', Typed: '{option}', 'Events Fired': 'keyup', 'Active Modifiers': null, 'Prevented Default': null, 'Target Element': $input[0] },
}
@@ -3513,10 +3487,8 @@ describe('src/cy/commands/actions/type - #type', () => {
cy.get(':text:first').type('f').then(function ($el) {
const table = this.lastLog.invoke('consoleProps').table[2]()
const beforeInput = isChromium ? 'beforeinput, ' : ''
expect(table.data).to.deep.eq({
1: { Typed: 'f', 'Events Fired': `keydown, keypress, ${beforeInput}textInput, input, keyup`, 'Active Modifiers': null, Details: '{ code: KeyF, which: 70 }', 'Prevented Default': null, 'Target Element': $el[0] },
1: { Typed: 'f', 'Events Fired': `keydown, keypress, beforeinput, textInput, input, keyup`, 'Active Modifiers': null, Details: '{ code: KeyF, which: 70 }', 'Prevented Default': null, 'Target Element': $el[0] },
})
})
})
@@ -1197,6 +1197,20 @@ describe('src/cy/commands/assertions', () => {
})
})
})
describe('escape markdown', () => {
// https://github.com/cypress-io/cypress/issues/17357
it('images', (done) => {
const text = 'hello world ![JSDoc example](/slides/img/jsdoc.png)'
const result = 'hello world ``![JSDoc example](/slides/img/jsdoc.png)``'
expectMarkdown(
() => expect(text).to.equal(text),
`expected **${result}** to equal **${result}**`,
done,
)
})
})
})
context('chai overrides', () => {
@@ -30,6 +30,15 @@ describe('src/cy/commands/location', () => {
cy.url().should('include', '/baz.html')
})
// https://github.com/cypress-io/cypress/issues/17399
it('url decode option', () => {
// encodeURI() is used below because we cannot visit the site without it.
// For the curious, 사랑 means "love" in Korean.
cy.visit(encodeURI('/custom-headers?x=사랑'))
cy.url({ decode: true }).should('contain', '사랑')
})
describe('assertion verification', () => {
beforeEach(function () {
cy.on('log:added', (attrs, log) => {
@@ -1,5 +1,5 @@
import { getDisplayUrlMatcher } from '@packages/driver/src/cy/net-stubbing/route-matcher-log'
import { RouteMatcherOptions } from '@packages/net-stubbing/lib/external-types'
import type { RouteMatcherOptions } from '@packages/net-stubbing/lib/external-types'
const testFail = (cb, expectedDocsUrl = 'https://on.cypress.io/intercept') => {
cy.on('fail', (err) => {
@@ -1638,12 +1638,10 @@ describe('network stubbing', { retries: 2 }, function () {
delay: 5000,
}).as('create')
cy.window().then((win) => {
win.eval(
`fetch("/post-only", {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
});`,
)
cy.then(() => {
fetch('/post-only', {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
})
})
cy.wait('@create', { timeout: 500 })
@@ -2127,6 +2125,61 @@ describe('network stubbing', { retries: 2 }, function () {
cy.get('#request').click()
cy.get('#result').should('contain', 'client')
})
it('works with reply', () => {
cy.intercept({
method: 'POST',
times: 1,
url: '/post-only',
},
(req) => {
req.reply('stubbed data')
}).as('interceptor')
cy.visit('fixtures/request.html')
cy.get('#request').click()
cy.get('#result').should('contain', 'stubbed data')
cy.get('#request').click()
cy.get('#result').should('contain', 'client')
})
it('works with reply and fallthrough', () => {
let times = 0
cy.intercept({
method: 'POST',
times: 3,
url: '/post-only',
},
(req) => {
req.reply(`${req.body === 'foo' ? 'foo' : 'nothing'} stubbed data ${times++}`)
})
cy.intercept({
method: 'POST',
times: 2,
url: '/post-only',
},
(req) => {
req.body = 'foo'
})
cy.visit('fixtures/request.html')
cy.get('#request').click()
cy.get('#result').should('contain', 'foo stubbed data 0')
cy.get('#request').click()
cy.get('#result').should('contain', 'foo stubbed data 1')
cy.get('#request').click()
cy.get('#result').should('contain', 'nothing stubbed data 2')
cy.get('#request').click()
cy.get('#result').should('contain', 'client')
})
})
})
})
@@ -2628,6 +2681,31 @@ describe('network stubbing', { retries: 2 }, function () {
.wait('@get')
})
// https://github.com/cypress-io/cypress/issues/17084
it('does not overwrite the json-related content-type header', () => {
cy.intercept('/json-content-type', (req) => {
req.on('response', (res) => {
res.send({
statusCode: 500,
headers: {
'content-type': 'application/problem+json',
'access-control-allow-origin': '*',
},
body: {
status: 500,
title: 'Internal Server Error',
},
})
})
})
.then(() => {
return fetch('/json-content-type')
.then((res) => {
expect(res.headers.get('content-type')).to.eq('application/problem+json')
})
})
})
context('body parsing', function () {
[
'application/json',
@@ -3049,7 +3127,7 @@ describe('network stubbing', { retries: 2 }, function () {
})
})
context('waiting and aliasing', function () {
context('waiting and aliasing', { defaultCommandTimeout: 10000 }, function () {
const testFailWaiting = (cb) => testFail(cb, 'https://on.cypress.io/wait')
it('can wait on a single response using "alias"', function () {
@@ -735,7 +735,7 @@ describe('src/cy/commands/waiting', () => {
cy.wait('@foo', '@bar')
})
it('throws when passed caallback function', (done) => {
it('throws when passed callback function', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.wait()` was passed invalid arguments. You cannot pass a function. If you would like to wait on the result of a `cy.wait()`, use `cy.then()`.')
expect(err.docsUrl).to.eq('https://on.cypress.io/wait')
@@ -1026,14 +1026,25 @@ describe('src/cy/commands/xhr', () => {
})
it('sets err on log when caused by code errors', function (done) {
cy.on('fail', (err) => {
done = _.once(done)
cy.once('fail', (err) => {
// suppress failure
})
cy.on('log:changed', () => {
const { lastLog } = this
expect(this.logs.length).to.eq(1)
expect(lastLog.get('name')).to.eq('request')
expect(lastLog.get('error').message).contain('foo is not defined')
if (!lastLog || lastLog.get('name') !== 'request') return
done()
try {
expect(this.logs.length).to.eq(1)
expect(lastLog.get('error').message).contain('foo is not defined')
done()
} catch (err) {
// eslint-disable-next-line no-console
console.log('assertion failure', err)
}
})
cy.window().then((win) => {
@@ -1047,17 +1058,27 @@ describe('src/cy/commands/xhr', () => {
})
it('causes errors caused by onreadystatechange callback function', function (done) {
done = _.once(done)
const e = new Error('onreadystatechange caused this error')
cy.on('fail', (err) => {
cy.once('fail', (err) => {
// suppress failure
})
cy.on('log:changed', () => {
const { lastLog } = this
expect(this.logs.length).to.eq(1)
expect(lastLog.get('name')).to.eq('request')
expect(err.message).to.include(lastLog.get('error').message)
expect(err.message).to.include(e.message)
if (!lastLog || lastLog.get('name') !== 'request') return
done()
try {
expect(this.logs.length).to.eq(1)
expect(lastLog.get('name')).to.eq('request')
expect(e.message).to.include(lastLog.get('error').message)
done()
} catch (err) {
// eslint-disable-next-line no-console
console.log('assertion failure', err)
}
})
cy
@@ -2563,7 +2584,9 @@ describe('src/cy/commands/xhr', () => {
xhr.open('GET', '/timeout?ms=999')
xhr.send()
xhr.abort()
// allow the request time to make it out of the browser so proxy logging has a chance to see it
requestAnimationFrame(() => xhr.abort())
cy.wrap(null).should(() => {
expect(log.get('state')).to.eq('failed')
@@ -1,5 +1,5 @@
const { $ } = Cypress
const $SnapshotsCss = require('../../../src/cy/snapshots_css')
const $SnapshotsCss = require('../../../src/cy/snapshots_css').default
const normalizeStyles = (styles) => {
return styles
@@ -1,4 +1,4 @@
const browserProps = require('@packages/driver/src/cypress/browser')
import browserProps from '@packages/driver/src/cypress/browser'
describe('src/cypress/browser', () => {
beforeEach(function () {

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