Merge branch 'develop' into agg23/RerenderRecipe

This commit is contained in:
Adam Gastineau
2021-04-02 13:13:11 -07:00
committed by GitHub
76 changed files with 1406 additions and 3294 deletions
@@ -1,4 +1,4 @@
export const MIN_SUPPORTED_VERSION = {
'react-scripts': '^=3.x || ^=4.x',
next: '^=9.x',
next: '^=9.x || ^=10.x',
}
+16 -10
View File
@@ -51,16 +51,22 @@ async function askForComponentTesting () {
return shouldSetupComponentTesting
}
function printCypressCommandsHelper ({ useYarn }: { useYarn: boolean }) {
const displayedCommand = useYarn ? 'yarn' : 'npx'
function printCypressCommandsHelper (options: { shouldSetupComponentTesting: boolean, useYarn: boolean }) {
const printCommand = (command: string, description: string) => {
const displayedRunner = options.useYarn ? 'yarn' : 'npx'
console.log('Inside this directory, you can run several commands:')
console.log()
console.log(chalk.cyan(` ${displayedCommand} cypress open`))
console.log(' Opens cypress local development app.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} cypress run`))
console.log(' Runs tests in headless mode.')
console.log()
console.log(chalk.cyan(` ${displayedRunner} ${command}`))
console.log(` ${description}`)
}
printCommand('cypress open', 'Opens cypress local development app.')
printCommand('cypress run', 'Runs tests in headless mode.')
if (options.shouldSetupComponentTesting) {
printCommand('cypress open-ct', 'Opens cypress component-testing web app.')
printCommand('cypress run', 'Runs component testing in headless mode.')
}
}
export async function main ({ useNpm, ignoreTs, setupComponentTesting, ignoreExamples }: MainArgv) {
@@ -92,7 +98,7 @@ export async function main ({ useNpm, ignoreTs, setupComponentTesting, ignoreExa
}
console.log(`\n👍 Success! Cypress is installed and ready to run tests.`)
printCypressCommandsHelper({ useYarn })
printCypressCommandsHelper({ useYarn, shouldSetupComponentTesting })
console.log(`\nHappy testing with ${chalk.green('cypress.io')} 🌲\n`)
}
+7 -41
View File
@@ -1,4 +1,4 @@
> A little helper to unit test React components in the open source [Cypress.io](https://www.cypress.io/) E2E test runner **v4.5.0+**
> A little helper to unit test React components in the open source [Cypress.io](https://www.cypress.io/) test runner **v7.0.0+**
**Jump to:** [Comparison](#comparison), [Blog posts](#blog-posts), [Install](#install), Examples: [basic](#basic-examples), [advanced](#advanced-examples), [full](#full-examples), [external](#external-examples), [Style options](#options), [Code coverage](#code-coverage), [Visual testing](#visual-testing), [Common problems](#common-problems), [Chat](#chat)
@@ -8,9 +8,9 @@
![Example component test](images/dynamic.gif)
- How is this different from [Enzyme](https://github.com/airbnb/enzyme) or [RTL](https://testing-library.com/docs/react-testing-library/intro)? It is similar in functionality BUT runs the component in the real browser with full power of Cypress E2E test runner: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/), and [visual testing](https://on.cypress.io/visual-testing). Ohh, and the code coverage is built-in!
- How is this different from [Enzyme](https://github.com/airbnb/enzyme) or [RTL](https://testing-library.com/docs/react-testing-library/intro)? It is similar in functionality BUT runs the component in the real browser with full power of Cypress E2E test runner: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/), and [visual testing](https://on.cypress.io/visual-testing).
- If you like using `@testing-library/react`, you can use `@testing-library/cypress` for the same `findBy`, `queryBy` commands, see one of the examples in the list below
- Read [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/)
- Read [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/) by Gleb Bahmutov
## Comparison
@@ -54,10 +54,10 @@ If you are coming from Enzyme world, check out the [enzyme](cypress/component/ba
## Install
Requires [Node](https://nodejs.org/en/) version 8 or above.
Requires [Node](https://nodejs.org/en/) version 12 or above.
```sh
npm install --save-dev cypress @cypress/react
npm install --save-dev cypress @cypress/react @cypress/webpack-dev-server
```
## Init
@@ -371,44 +371,13 @@ Finally, when running tests on the continuous integration service, the true test
</details>
<details id="speed">
<summary>Slow bundling</summary>
When you bundle spec file, you are now bundling React, Read DOM and other libraries, which is might be slow. For now, you can disable inline source maps by adding to your Webpack config settings (if available) the following:
```js
const webpackOptions = {
devtool: false,
}
```
Keep your eye on issue [#9663](https://github.com/cypress-io/cypress/issues/9663) for more information.
</details>
<details id="missing-code-coverage">
<summary>Missing code coverage</summary>
If you are using your custom Webpack, this plugin might be missing code coverage information because the code was not instrumented. We try to insert the `babel-plugin-istanbul` plugin automatically, but your bundling might not use Babel, or configure it differently, preventing plugin insertion. Please let us know by opening an issue with full reproducible details.
See related issue [#141](https://github.com/bahmutov/cypress-react-unit-test/react/issues/141). You can also debug the plugin's behavior by running it with `DEBUG` environment variable, see [#debugging](#debugging) section.
</details>
<details id="gatsby-not-supported">
<summary>Gatsby.js projects not supported</summary>
Currently, this project cannot find Webpack settings used by Gatsby.js, thus it cannot bundle specs and application code correctly. Keep an eye on [#307](https://github.com/cypress-io/cypress/issues/9671)
</details>
## Context Provider usage
React context provider usage and API described in [./docs/providers-and-composition.md](./docs/providers-and-composition.md)
## Chat
We have a chat workspace at [https://component-testing.slack.com/](https://component-testing.slack.com/), you are welcome to [join us](https://join.slack.com/t/component-testing/shared_invite/zt-h93lrgsl-8WzE8yNQlcZuZji_gA_mtg).
Come chat with us [on discord](https://discord.gg/7ZHYhZSW) in the #component-testing channel.
## Development
@@ -432,12 +401,9 @@ DEBUG=@cypress/react,find-webpack
Same feature for unit testing components from other frameworks using Cypress
- [cypress-vue-unit-test](https://github.com/bahmutov/cypress-vue-unit-test)
- [@cypress/vue](https://github.com/cypress-io/cypress/tree/develop/npm/vue)
- [cypress-cycle-unit-test](https://github.com/bahmutov/cypress-cycle-unit-test)
- [cypress-svelte-unit-test](https://github.com/bahmutov/cypress-svelte-unit-test)
- [cypress-angular-unit-test](https://github.com/bahmutov/cypress-angular-unit-test)
- [cypress-hyperapp-unit-test](https://github.com/bahmutov/cypress-hyperapp-unit-test)
- [cypress-angularjs-unit-test](https://github.com/bahmutov/cypress-angularjs-unit-test)
[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
[renovate-app]: https://renovateapp.com/
@@ -2,7 +2,6 @@
/// <reference types="cypress" />
import * as React from 'react'
import RouterPage from '../../pages/router'
import { createRouter } from 'next/router'
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { mount } from '@cypress/react'
@@ -22,6 +21,9 @@ describe('Component with router usage', () => {
reload: cy.spy(),
back: cy.spy(),
prefetch: cy.spy(),
isReady: true,
isPreview: false,
isLocaleDomain: false,
beforePopState: cy.spy(),
}
@@ -34,33 +36,6 @@ describe('Component with router usage', () => {
cy.contains('Next.js route /testPath')
})
it('renders the component that uses next.js with parsed query', () => {
// alternatively you can use next's internal `createRouter` function to create a real instance of NextRouter
const router = createRouter(
'/testPath',
{ param1: 'param1' },
'/asTestPath',
{
subscription: cy.spy(),
initialProps: {},
App: cy.spy(),
Component: cy.spy(),
pageLoader: cy.spy(),
initialStyleSheets: [],
wrapApp: cy.spy(),
isFallback: false,
},
)
mount(
<RouterContext.Provider value={router}>
<RouterPage />
</RouterContext.Provider>,
)
cy.contains('My query: {"param1":"param1"}')
})
it('pushes the new route', () => {
const router = {
pathname: '/router',
@@ -76,6 +51,9 @@ describe('Component with router usage', () => {
reload: cy.spy(),
back: cy.spy(),
prefetch: cy.spy(),
isReady: true,
isPreview: false,
isLocaleDomain: false,
beforePopState: cy.spy(),
}
+2 -3
View File
@@ -15,12 +15,11 @@
"devDependencies": {
"@cypress/react": "file:../../dist",
"@mdx-js/loader": "^1.6.16",
"@next/mdx": "10.0.7",
"@next/mdx": "10.1.2",
"@zeit/next-sass": "^1.0.1",
"check-code-coverage": "1.9.2",
"cypress-circleci-reporter": "0.2.0",
"next": "10.0.7",
"webpack": "^4.44.2"
"next": "10.1.2"
},
"license": "MIT"
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 3.2 MiB

+1 -1
View File
@@ -69,7 +69,7 @@
"lodash": "4.17.15",
"mobx": "6.0.0",
"mobx-react-lite": "3.0.0",
"next": "^9.5.3",
"next": "^10.1.2",
"pretty": "2.0.0",
"prop-types": "15.7.2",
"radioactive-state": "1.3.4",
@@ -1,4 +1,5 @@
// @ts-check
/// <reference types="next" />
const debug = require('debug')('@cypress/react')
const loadConfig = require('next/dist/next-server/server/config').default
const getNextJsBaseWebpackConfig = require('next/dist/build/webpack-config').default
@@ -14,7 +15,7 @@ async function getNextWebpackConfig (config) {
isServer: false,
pagesDir: config.projectRoot,
entrypoints: {},
rewrites: [],
rewrites: { fallback: [], afterFiles: [], beforeFiles: [] },
},
)
+46 -1
View File
@@ -1,4 +1,6 @@
# ⚡️ + 🌲 Cypress Component Testing w/ Vite
# @cypress/vite-dev-server
> ⚡️ + 🌲 Cypress Component Testing w/ Vite
To install vite in you component testing environment,
1. Install it `yarn add @cypress/vite-dev-server`
@@ -13,3 +15,46 @@ module.exports = (on, config) => {
return config
}
```
# @cypress/webpack-dev-server
> **Note** this package is not meant to be used outside of cypress component testing.
Install `@cypress/vue` or `@cypress/react` to get this package working properly
## Architecture
### Cypress server
- Every HTTP request goes to the cypress server which returns an html page. We call "TOP" because of its name in the dev tools
This page
- renders the list of spec files
- And the timetraveling command log
- Finally, it renders an AUT Iframe. this iframe calls a url that has 2 parts concatenated.
- a prefix: `__cypress/iframes/`
- the path to the current. For example: `src/components/button.spec.tsx`
- In the cypress server, calls prefixed with `__cypress/iframes/...` will be passed to the dev-server as `__cypress/src/index.html`
- Every call with the prefix `__cypress/src/` will be passed to the dev-server to deal as is, without changes.
### Dev-server
- Responds to every query with the prefix `__cypress/src/` (base path should be this prefix).
- Responds to `__cypress/src/index.html` with an html page.
This page
- will contain an element `<div id="__cy_root"></div>`. Tis will be used by mount function to mount the app containing the components we want.
- will load support files
- will load the current spec from the url
- will start the test when both files are done loading
- The server re-runs the tests as soon as the current spec or any dependency is updated by calling an event `devServerEvents.emit('dev-server:compile:success')`
## Vite dev server
- Takes the users `vite.config` and adds base of `__cypress/src/` and a cypress vite plugin.
- The cypress plugin takes care of
- responding to the index.html query with an html page
- restarting the tests when files are changed
- The HTML page calls a script that loads support file and the specs using a native `import()` function
- Then triggers the loaded tests
Vite is reponsible for compiling and bundling all the files. We use its error overlay to display any transpiling error.
Omly runtime errors have to be handled through cypress
+1
View File
@@ -1,5 +1,6 @@
{
"pluginsFile": "cypress/plugins.js",
"video": false,
"testFiles": "**/*.spec.*",
"componentFolder": "cypress/components",
"supportFile": "cypress/support/support.js"
+3 -2
View File
@@ -12,7 +12,8 @@
"watch": "tsc -w"
},
"dependencies": {
"debug": "4.3.2"
"debug": "^4.3.2",
"get-port": "^5.1.1"
},
"devDependencies": {
"@cypress/react": "0.0.0-development",
@@ -27,7 +28,7 @@
"vue": "3.0.9"
},
"peerDependencies": {
"vite": ">= 2"
"vite": ">= 2.1.3"
},
"files": [
"dist",
+2 -16
View File
@@ -1,23 +1,9 @@
import { EventEmitter } from 'events'
import { debug as debugFn } from 'debug'
import { start as createDevServer } from './startServer'
import { UserConfig } from 'vite'
import { Server } from 'http'
import { start as createDevServer, StartDevServer } from './startServer'
const debug = debugFn('cypress:vite-dev-server:vite')
interface Options {
specs: any[] // Cypress.Cypress['spec'][] // Why isn't this working? It works for webpack-dev-server
config: Record<string, string>
devServerEvents: EventEmitter
[key: string]: unknown
}
export interface StartDevServer {
/* this is the Cypress options object */
options: Options
/* support passing a path to the user's webpack config */
viteConfig?: UserConfig // TODO: implement taking in the user's vite configuration. Right now we don't
}
export { StartDevServer }
export interface ResolvedDevServerConfig {
port: number
+1 -2
View File
@@ -1,4 +1,3 @@
import { EventEmitter } from 'events'
import { resolve } from 'path'
import { readFile } from 'fs'
import { promisify } from 'util'
@@ -24,7 +23,7 @@ export const makeCypressPlugin = (
if (env) {
return {
define: {
'import.meta.env.__cypress_supportPath': JSON.stringify(resolve(projectRoot, supportFilePath)),
'import.meta.env.__cypress_supportPath': JSON.stringify(supportFilePath ? resolve(projectRoot, supportFilePath) : undefined),
'import.meta.env.__cypress_originAutUrl': JSON.stringify('__cypress/iframes/'),
},
}
+35 -33
View File
@@ -1,47 +1,49 @@
import Debug from 'debug'
import { StartDevServer } from '.'
import { createServer, ViteDevServer, InlineConfig } from 'vite'
import { createServer, ViteDevServer, InlineConfig, UserConfig } from 'vite'
import { dirname, resolve } from 'path'
import getPort from 'get-port'
import { makeCypressPlugin } from './makeCypressPlugin'
import { EventEmitter } from 'events'
const debug = Debug('cypress:vite-dev-server:start')
// TODO: Pull in types for Options so we can infer these
const serverConfig = (projectRoot: string, supportFilePath: string, devServerEvents: EventEmitter): InlineConfig => {
return {
root: resolve(__dirname, projectRoot),
base: '/__cypress/src/',
plugins: [makeCypressPlugin(projectRoot, supportFilePath, devServerEvents)],
server: {
port: 0,
},
resolve: {
alias: {
// Necessary to avoid a "prefixIdentifiers" issue from slots mounting
// Could be resolved in test-utils
'@vue/compiler-core': resolve(dirname(require.resolve('@vue/compiler-core')), 'dist', 'compiler-core.cjs.js'),
},
},
}
interface Options {
specs: Cypress.Cypress['spec'][]
config: Record<string, string>
devServerEvents: EventEmitter
[key: string]: unknown
}
const resolveServerConfig = ({ viteConfig, options }: StartDevServer) => {
const defaultServerConfig = serverConfig(
options.config.projectRoot,
options.config.supportFile,
options.devServerEvents,
)
export interface StartDevServer {
/* this is the Cypress options object */
options: Options
/* support passing a path to the user's webpack config */
viteConfig?: UserConfig // TODO: implement taking in the user's vite configuration. Right now we don't
}
const requiredOptions = {
base: defaultServerConfig.base,
root: defaultServerConfig.root,
const resolveServerConfig = async ({ viteConfig, options }: StartDevServer): Promise<InlineConfig> => {
const { projectRoot, supportFile } = options.config
const requiredOptions: InlineConfig = {
base: '/__cypress/src/',
root: projectRoot,
}
const finalConfig = { ...defaultServerConfig, ...viteConfig, ...requiredOptions }
const finalConfig: InlineConfig = { ...viteConfig, ...requiredOptions }
finalConfig.plugins = [...(viteConfig.plugins || []), defaultServerConfig.plugins[0]]
finalConfig.server.port = defaultServerConfig.server.port
finalConfig.plugins = [...(viteConfig.plugins || []), makeCypressPlugin(projectRoot, supportFile, options.devServerEvents)]
// This alias is necessary to avoid a "prefixIdentifiers" issue from slots mounting
// only cjs compiler-core accepts using prefixIdentifiers in slots which vue test utils use.
// Could we resolve this usage in test-utils?
finalConfig.resolve = finalConfig.resolve || {}
finalConfig.resolve.alias = {
...finalConfig.resolve.alias,
'@vue/compiler-core': resolve(dirname(require.resolve('@vue/compiler-core')), 'dist', 'compiler-core.cjs.js'),
},
finalConfig.server = finalConfig.server || {}
finalConfig.server.port = await getPort({ port: 3000, host: 'localhost' }),
debug(`the resolved server config is ${JSON.stringify(finalConfig, null, 2)}`)
@@ -55,7 +57,7 @@ export async function start (devServerOptions: StartDevServer): Promise<ViteDevS
}
debug('starting vite dev server')
const resolvedConfig = resolveServerConfig(devServerOptions)
const resolvedConfig = await resolveServerConfig(devServerOptions)
return createServer(resolvedConfig)
}
+58 -144
View File
@@ -3,7 +3,6 @@
[![NPM][npm-icon] ][npm-url]
[![semantic-release][semantic-image] ][semantic-url]
[![renovate-app badge][renovate-badge]][renovate-app]
> Browser-based Component Testing for Vue.js with the Open-Source [Cypress.io](https://www.cypress.io/) Test Runner ✌️🌲
>
@@ -11,8 +10,6 @@
**Jump to:** [Comparison](#comparison), [Blog posts](#blog-posts), Examples: [basic](#basic-examples), [advanced](#advanced-examples), [full](#full-examples), [external](#external-examples), [Code coverage](#code-coverage), [Development](#development)
**🚧 Notice** We are in the middle of moving into the Cypress NPM org, so any references to `cypress-vue-unit-test` should be switched to `@cypress/vue`. Once complete, this repository will be archived.
### What is @cypress/vue?
This package allows you to use the [Cypress](https://www.cypress.io/) test runner to mount and test your components within Cypress. It is built on top of the [Vue Test Utils](https://github.com/vuejs/vue-test-utils) package.
@@ -25,26 +22,63 @@ It uses [Vue Test Utils](https://github.com/vuejs/vue-test-utils) under the hood
## Installation
<p align="center">
<img src="./docs/commands.gif" width="400px" alt="Terminal typing vue add cypress-experimental"/>
</p>
- Requires Cypress v7.0.0 or later
- Requires [Node](https://nodejs.org/en/) version 12 or above
- Supports webpack-based projects, vite in alpha, if you would like us to support another, please [create an issue](https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm:%20@cypress/vue&template=3-feature.md) or, if an issue already exists subscribe to it.
- Requires Cypress v4.5.0 or later
- Requires [Node](https://nodejs.org/en/) version 8 or above
- Only supporting webpack-based projects
- Installation via Vue CLI recommended, read [Write Your First Vue Component Test](https://glebbahmutov.com/blog/first-vue-component-test/)
Now you are ready to install.
### Vue CLI Installation
### Manual Installation
> Vue CLI v3+
Using [@cypress/webpack-dev-server](https://github.com/cypress-io/cypress-webpack-preprocessor#readme) and [vue-loader](https://github.com/vuejs/vue-loader).
_Recommended_: One step install to existing projects with Vue CLI via [experimental plugin](https://github.com/jessicasachs/vue-cli-plugin-cypress-experimental), read [Write Your First Vue Component Test](https://glebbahmutov.com/blog/first-vue-component-test/)
```js
// cypress/plugins/index.js
const webpack = require('@cypress/webpack-dev-server')
const webpackOptions = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
}
```sh
vue add cypress-experimental
const options = {
// send in the options from your webpack.config.js, so it works the same
// as your app's code
webpackOptions,
watchOptions: {},
}
module.exports = (on) => {
on('dev-server:start', webpack(options))
}
```
If you want to install this package manually, follow [manual install](./docs/manual-install.md)
Install dev dependencies
```shell
npm i -D @cypress/webpack-dev-server \
vue-loader vue-template-compiler css-loader
```
And write a test
```js
import Hello from '../../components/Hello.vue'
import { mountCallback } from '@cypress/vue'
describe('Hello.vue', () => {
beforeEach(mountCallback(Hello))
it('shows hello', () => {
cy.contains('Hello World!')
})
})
```
## Usage and Examples
@@ -82,8 +116,6 @@ mount(Todo, {
See examples below for details.
<a name="global-vue-extensions"/>
### Global Vue Options
You can pass extensions (global components, mixins, modules to use)
@@ -95,9 +127,7 @@ the `options`.
- `mixin` (alias `mixins`) - list of global mixins, see [Mixins](cypress/component/basic/mixins) example
- `filters` - hash of global filters, see [Filters](cypress/component/basic/filters) example
<a name="intro-example"/>
### The intro example
### intro example
Take a look at the first Vue v2 example:
[Declarative Rendering](https://vuejs.org/v2/guide/#Declarative-Rendering).
@@ -164,9 +194,7 @@ the reference `Cypress.vue.$data` and via GUI. The full power of the
![Hello world tested](images/spec.png)
<a name="list-example"/>
### The list example
### list example
There is a list example next in the Vue docs.
@@ -233,8 +261,6 @@ describe('Declarative rendering', () => {
![List tested](images/list-spec.png)
<a name="handling-user-input"/>
### Handling User Input
The next section in the Vue docs starts with [reverse message example](https://vuejs.org/v2/guide/#Handling-User-Input).
@@ -358,8 +384,6 @@ describe('Several components', () => {
})
```
<a name="spying-example"/>
### Spying example
Button counter component is used in several Vue doc examples
@@ -553,91 +577,7 @@ Repo | Description
[vue-component-test-example](https://github.com/bahmutov/vue-component-test-example) | Scaffolded Vue CLI v3 project with added component tests, read [Write Your First Vue Component Test](https://glebbahmutov.com/blog/first-vue-component-test/).
<!-- prettier-ignore-end -->
## Known problems
<a name="bundling"/>
## Bundling
How do we load this Vue file into the testing code? Using webpack preprocessor. Note that this module ships with [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress-webpack-preprocessor#compatibility) 2.x that requires Webpack 4.x. If you have Webpack 3.x please add `@cypress/webpack-preprocessor v1.x`.
<a name="short-way"/>
### Short way
#### For Webpack Users
Your project probably already has `webpack.config.js` setup to transpile
`.vue` files. To load these files in the Cypress tests, grab the webpack
processor included in this module, and load it from the `cypress/plugins/index.js`
file.
```js
const {
onFilePreprocessor,
} = require('@cypress/vue/preprocessor/webpack')
module.exports = (on) => {
on('file:preprocessor', onFilePreprocessor('../path/to/webpack.config'))
}
```
Cypress should be able to import `.vue` files in the tests
<a name="manual"/>
### Manual
Using [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress-webpack-preprocessor#readme) and [vue-loader](https://github.com/vuejs/vue-loader).
You can use [cypress/plugins/index.js](cypress/plugins/index.js) to load `.vue` files
using `vue-loader`.
```js
// cypress/plugins/index.js
const webpack = require('@cypress/webpack-preprocessor')
const webpackOptions = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
}
const options = {
// send in the options from your webpack.config.js, so it works the same
// as your app's code
webpackOptions,
watchOptions: {},
}
module.exports = (on) => {
on('file:preprocessor', webpack(options))
}
```
Install dev dependencies
```shell
npm i -D @cypress/webpack-preprocessor \
vue-loader vue-template-compiler css-loader
```
And write a test
```js
import Hello from '../../components/Hello.vue'
import { mountCallback } from '@cypress/vue'
describe('Hello.vue', () => {
beforeEach(mountCallback(Hello))
it('shows hello', () => {
cy.contains('Hello World!')
})
})
```
## Code coverage
@@ -659,27 +599,22 @@ If you want to disable code coverage instrumentation and reporting, use `--env c
We were in the middle of moving into the Cypress NPM org, so any references to `cypress-vue-unit-test` should be switched to `@cypress/vue`. Once complete, the old repository will be archived.
<a name="#development"/>
## Development
To see all local tests, install dependencies, build the code and open Cypress in GUI mode
To see all local tests, install dependencies, build the code and open Cypress using the open-ct command
```sh
npm install
npm run build
yarn install
yarn workspace @cypress/vue build
```
The build is done using `tsc` that transpiles all files from [src](src) to the `dist` folder. You can then run component tests by opening Cypress
The build is done using `rollup`. It bundles all files from [src](src) to the `dist` folder. You can then run component tests by opening Cypress
```sh
npm run cy:open
# cypress open-ct
yarn workspace @cypress/vue cy:open
```
and clicking on any component spec
![Component specs](images/component-specs.png)
Larger tests that use full application and run on CI (see [circle.yml](circle.yml)) are located in the folder [examples](examples).
### Debugging
@@ -696,8 +631,6 @@ If some deeply nested objects are abbreviated and do not print fully, set the ma
DEBUG=@cypress/vue DEBUG_DEPTH=10
```
<a name="#related"/>
## Related info
- [Testing Vue web applications with Vuex data store & REST backend](https://www.cypress.io/blog/2017/11/28/testing-vue-web-application-with-vuex-data-store-and-rest-backend/)
@@ -706,27 +639,10 @@ DEBUG=@cypress/vue DEBUG_DEPTH=10
- [Learn TDD in Vue](https://learntdd.in/vue)
- [@cypress/vue vs vue-test-utils](https://codingitwrong.com/2018/03/04/comparing-vue-component-testing-tools.html)
```js
const {
onFileDefaultPreprocessor,
} = require('@cypress/vue/preprocessor/webpack')
module.exports = (on, config) => {
require('@cypress/code-coverage/task')(on, config)
on('file:preprocessor', onFileDefaultPreprocessor(config))
// IMPORTANT to return the config object
// with the any changed environment variables
return config
}
```
## Blog posts
- [Write Your First Vue Component Test](https://glebbahmutov.com/blog/first-vue-component-test/)
<a name="#other"/>
## Test adapters for other frameworks
- [@cypress/react](https://github.com/cypress-io/@cypress/react)
@@ -736,8 +652,6 @@ module.exports = (on, config) => {
- [cypress-hyperapp-unit-test](https://github.com/bahmutov/cypress-hyperapp-unit-test)
- [cypress-angularjs-unit-test](https://github.com/bahmutov/cypress-angularjs-unit-test)
<a name="#maintainers"/>
## Maintainers
The Cypress.io Component Testing Team
+53 -8
View File
@@ -1,17 +1,62 @@
# Webpack-ct
# @cypress/webpack-dev-server
> **Note** this package is not meant to be used outside of cypress component testing.
Install `@cypress/vue` or `@cypress/react` to get this package working properly
## Responsibilities
To install `@cypress/webpack-dev-server` in you component testing environment,
1. Install it `yarn add @cypress/webpack-dev-server`
2. Add it to `cypress/plugins/index.js`
- Make a `webpack.config` from the users setup
- add current project rules and aliases
- remove eslint?
- Launch webpack dev server
- Update entry point (in `src/browser.ts`)
- The entry point (`browser.ts`) has to delegate the loading of spec files to the loader + plugin
```js
import { startDevServer } from '@cypress/webpack-dev-server'
module.exports = (on, config) => {
on('dev-server:start', async (options) => startDevServer({ options }))
return config
}
```
## Architecture
### Cypress server
- Every HTTP request goes to the cypress server which returns an html page. We call "TOP" because of its name in the dev tools
This page
- renders the list of spec files
- And the timetraveling command log
- Finally, it renders an AUT Iframe. this iframe calls a url that has 2 parts concatenated.
- a prefix: `__cypress/iframes/`
- the path to the current. For example: `src/components/button.spec.tsx`
- In the cypress server, calls prefixed with `__cypress/iframes/...` will be passed to the dev-server as `__cypress/src/index.html`
- Every call with the prefix `__cypress/src/` will be passed to the dev-server to deal as is, without changes.
### Dev-server
- Responds to every query with the prefix `__cypress/src/` (base path should be this prefix).
- Responds to `__cypress/src/index.html` with an html page.
This page
- will contain an element `<div id="__cy_root"></div>` to attach the mounted components to.
- will load support files
- will load the current spec from the url
- will start the test when both files are done loading
- The server re-runs the tests as soon as the current spec or any dependency is updated
by calling an event `devServerEvents.emit('dev-server:compile:success')`
## Webpack
Webpack-dev-server fulfills his reponsibilities by
- Making a `webpack.config` from the users setup by changing the entrypoint to `browser.ts`
- Launches the webpack dev server with `devServer.publicPath = "__cypress/src/"` and `devServer.hot = false`
- The entry point (`browser.ts`) delegates the loading of spec files to the loader.
- By using a require on itself, it enables using a custom loader and Hot Module Replacements if hot is false.
- The plugin is responsible for passing down the path to the support files and the list of specs discovered by the cypress server to the loader
- The loader:
- compares the url with each spec path given by the plugin
- picks the spec that should be run
- runs the `AUT-Runner.ts` to load and launch the support file and the current spec
## Performance tests
-5
View File
@@ -1,5 +0,0 @@
const { validatePeerDependencies } = require('./dist/errors')
validatePeerDependencies()
module.exports = require('./dist')
+4 -4
View File
@@ -2,7 +2,7 @@
"name": "@cypress/webpack-dev-server",
"version": "0.0.0-development",
"description": "Launches Webpack Dev Server for Component Testing",
"main": "index.js",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"build-prod": "tsc",
@@ -25,9 +25,9 @@
"webpack-dev-server": "^3.11.0"
},
"peerDependencies": {
"html-webpack-plugin": "> 3",
"webpack": "> 4",
"webpack-dev-server": "> 3"
"html-webpack-plugin": ">=4",
"webpack": ">=4",
"webpack-dev-server": ">=4"
},
"files": [
"dist",
-80
View File
@@ -1,80 +0,0 @@
export interface MissingDependency {
prettyName: string
packageName: string
}
const peerDeps: MissingDependency[] = [
{
prettyName: 'Webpack Dev Server',
packageName: 'webpack-dev-server',
},
{
prettyName: 'Html Webpack Plugin',
packageName: 'html-webpack-plugin',
},
{
prettyName: 'Webpack',
packageName: 'webpack',
},
]
const makePrettyLibs = (libs) => {
return libs.reduce((acc, curr, idx) => {
acc.prettyNames.push(curr.prettyName)
acc.packageNames.push(curr.packageName)
if (idx >= libs.length) return acc
return {
prettyNames: acc.prettyNames.join(', '),
packageNames: acc.packageNames.join(' '),
}
}, { prettyNames: [], packageNames: [] })
}
export class MissingPeerDependency extends Error {
private libs: { prettyNames: string[], packageNames: string[] }
constructor (message,
libs: MissingDependency[]) {
const prettyLibs = makePrettyLibs(libs)
super(`${message} ${prettyLibs.prettyNames}`)
this.name = 'PeerDependencyMissing'
this.libs = prettyLibs
Object.setPrototypeOf(this, MissingPeerDependency.prototype)
}
get prettyMessage () {
return `❌ Missing ${this.libs.prettyNames}. Please install them with npm or yarn.
npm i ${this.libs.packageNames} -D
yarn add ${this.libs.packageNames} --dev
Updating webpack config is unnecessary
`
}
}
export function validatePeerDependencies () {
const missingPeerDeps = peerDeps.filter((peerDep) => {
try {
require(peerDep.packageName)
} catch (err) {
return true
}
return false
})
if (missingPeerDeps.length) {
const error = new MissingPeerDependency(`@cypress/webpack-dev-server is missing peer dependencies`, missingPeerDeps)
console.error(error.prettyMessage) // eslint-disable-line
throw error
}
return true
}
+2 -8
View File
@@ -1,7 +1,6 @@
import { debug as debugFn } from 'debug'
import { AddressInfo } from 'net'
import { start as createDevServer } from './startServer'
import { UserWebpackDevServerOptions } from './makeWebpackConfig'
import { start as createDevServer, StartDevServer } from './startServer'
const debug = debugFn('cypress:webpack-dev-server:webpack')
@@ -12,12 +11,7 @@ export interface ResolvedDevServerConfig {
close: (done?: DoneCallback) => void
}
export interface StartDevServer extends UserWebpackDevServerOptions {
/* this is the Cypress options object */
options: Cypress.DevServerOptions
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
}
export { StartDevServer }
export async function startDevServer (startDevServerArgs: StartDevServer, exitProcess = process.exit) {
const webpackDevServer = await createDevServer(startDevServerArgs, exitProcess)
@@ -22,7 +22,7 @@ interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions, UserWe
isOpenMode: boolean
}
const mergePublicPath = (baseValue: string, userValue = '/') => {
const mergePublicPath = (baseValue = '/', userValue = '/') => {
return path.join(baseValue, userValue, '/')
}
+8 -15
View File
@@ -1,8 +1,14 @@
import Debug from 'debug'
import webpack from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import { StartDevServer } from '.'
import { makeWebpackConfig } from './makeWebpackConfig'
import { makeWebpackConfig, UserWebpackDevServerOptions } from './makeWebpackConfig'
export interface StartDevServer extends UserWebpackDevServerOptions {
/* this is the Cypress options object */
options: Cypress.DevServerOptions
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
}
const debug = Debug('cypress:webpack-dev-server:start')
@@ -40,19 +46,6 @@ export async function start ({ webpackConfig: userWebpackConfig, options, ...use
debug('starting webpack dev server')
// TODO: write a test for how we are NOT modifying publicPath
// here, and instead stripping it out of the cypress proxy layer
//
// ...this prevents a problem if users have a 'before' or 'after'
// function defined in their webpack config, it does NOT
// interfere with their routes... otherwise the public
// path we are prefixing like /__cypress/src/ would be
// prepended to req.url and cause their routing handlers to fail
//
// NOTE: we are merging in webpackConfig.devServer here so
// that user values for the devServer get passed on correctly
// since we are passing in the compiler directly, and these
// devServer options would otherwise get ignored
const webpackDevServerConfig = {
...userWebpackConfig.devServer,
hot: false,
+1 -1
View File
@@ -14,6 +14,6 @@ module.exports = {
path: path.resolve(__dirname, 'dist'),
},
plugins: [new HtmlWebpackPlugin({
template: path.join(__dirname, '../index-template.html'),
template: path.resolve(__dirname, '..', 'index-template.html'),
})],
}
@@ -67,6 +67,11 @@ describe('Footer', () => {
it('shows update indicator', () => {
cy.get('.update-indicator').should('be.visible')
cy.wrap(ipc.updaterCheck).should('be.calledOnceWith', {
initialLaunch: true,
testingType: 'e2e',
})
cy.percySnapshot()
})
+8 -1
View File
@@ -5,16 +5,23 @@ import updateStore from '../update/update-store'
import ipc from '../lib/ipc'
import { useLifecycle } from '../lib/use-lifecycle'
let initialLaunch = true
const checkForUpdate = () => {
ipc.offUpdaterCheck()
ipc.updaterCheck()
ipc.updaterCheck({
initialLaunch,
testingType: 'e2e',
})
.then((version) => {
if (version) updateStore.setNewVersion(version)
})
.catch((error) => {
console.warn('Error checking for updates:', error) // eslint-disable-line no-console
})
initialLaunch = false
}
export const useUpdateChecker = () => {
@@ -0,0 +1,29 @@
<html>
<body>
<div id="parent" style="display:flex; height: 90vh;">
<div id="Preselect" style="flex:1; background-color:aquamarine; height: 100%;">
Preselect
</div>
<div id="Details" style="flex:1; background-color:blue; height: 100%; overflow:scroll;">
Details
<div id="modal" style="background-color: chartreuse; height: 80vh; width: 90%; position: fixed; display: block; left:5%; top:5%;">
<div id="sticky" style="background-color: darkgray; position:sticky; bottom: 10%; width:9%; height:10%;">
<button id="button" style="background-color: darkred; color:white;">
click me
</button>
<div id="message"></div>
</div>
</div>
</div>
</div>
</body>
<script>
const btn = document.getElementById('button')
const msg = document.getElementById('message')
btn.onclick = (e) => {
msg.innerHTML = 'Success!'
}
</script>
</html>
@@ -780,6 +780,11 @@ describe('src/cypress/dom/visibility', () => {
expect(this.$childPointerEventsNone.find('span')).to.be.visible
expect(this.$childPointerEventsNone.find('span')).to.not.be.hidden
})
it('is visible when position: sticky', () => {
cy.visit('fixtures/sticky.html')
cy.get('#button').should('be.visible')
})
})
describe('css display', function () {
+4 -8
View File
@@ -87,7 +87,7 @@ const isHidden = (el, methodName = 'isHidden()', options = { checkOpacity: true
return true // is hidden
}
if (elOrAncestorIsFixed($el)) {
if (elOrAncestorIsFixedOrSticky($el)) {
return elIsNotElementFromPoint($el)
}
@@ -218,12 +218,8 @@ const canClipContent = function ($el, $ancestor) {
return true
}
const elOrAncestorIsFixed = function ($el) {
const $stickyOrFixedEl = $elements.getFirstFixedOrStickyPositionParent($el)
if ($stickyOrFixedEl) {
return $stickyOrFixedEl.css('position') === 'fixed'
}
const elOrAncestorIsFixedOrSticky = function ($el) {
return !!$elements.getFirstFixedOrStickyPositionParent($el)
}
const elAtCenterPoint = function ($el) {
@@ -509,7 +505,7 @@ const getReasonIsHidden = function ($el, options = { checkOpacity: true }) {
}
// nested else --___________--
if (elOrAncestorIsFixed($el)) {
if (elOrAncestorIsFixedOrSticky($el)) {
if (elIsNotElementFromPoint($el)) {
// show the long element here
const covered = $elements.stringify(elAtCenterPoint($el))
+6 -1
View File
@@ -97,6 +97,7 @@ export function launch (
browser: FoundBrowser,
url: string,
args: string[] = [],
defaultBrowserEnv = {},
) {
log('launching browser %o', { browser, url })
@@ -110,7 +111,11 @@ export function launch (
log('spawning browser with args %o', { args })
const proc = cp.spawn(browser.path, args, { stdio: ['ignore', 'pipe', 'pipe'] })
// allow setting default env vars such as MOZ_HEADLESS_WIDTH
// but only if it's not already set by the environment
const env = Object.assign({}, defaultBrowserEnv, process.env)
const proc = cp.spawn(browser.path, args, { stdio: ['ignore', 'pipe', 'pipe'], env })
proc.stdout.on('data', (buf) => {
log('%s stdout: %s', browser.name, String(buf).trim())
+4
View File
@@ -6,5 +6,9 @@
"reporter": "../../node_modules/cypress-multi-reporters/index.js",
"reporterOptions": {
"configFile": "../../mocha-reporter-config.json"
},
"retries": {
"runMode": 2,
"openMode": 0
}
}
+5 -1
View File
@@ -1,4 +1,8 @@
{
"projectId": "ypt4pf",
"baseUrl": "http://localhost:3500"
"baseUrl": "http://localhost:3500",
"retries": {
"runMode": 2,
"openMode": 0
}
}
@@ -18,7 +18,7 @@ const threeTestsWithRetry = {
},
}
describe('src/cypress/runner retries mochaEvents', () => {
describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => {
// NOTE: for test-retries
it('can set retry config', () => {
runIsolatedCypress({}, { config: { retries: 1 } })
@@ -13,7 +13,7 @@ const threeTestsWithHooks = {
suites: { 'suite 1': { hooks: ['before', 'beforeEach', 'afterEach', 'after'], tests: ['test 1', 'test 2', 'test 3'] } },
}
describe('src/cypress/runner', () => {
describe('src/cypress/runner', { retries: 0 }, () => {
describe('tests finish with correct state', () => {
describe('hook failures', () => {
it('fail in [before]', () => {
@@ -1,9 +1,9 @@
const helpers = require('../support/helpers')
const { createCypress } = helpers
const { runIsolatedCypress, snapshotMochaEvents } = createCypress({ config: { experimentalStudio: true, isTextTerminal: true } })
const { runIsolatedCypress, snapshotMochaEvents } = createCypress({ config: { experimentalStudio: true, isTextTerminal: true, retries: 0 } })
describe('studio mocha events', () => {
describe('studio mocha events', { retries: 0 }, () => {
it('only runs a single test by id', () => {
runIsolatedCypress('cypress/fixtures/studio/basic_spec.js', {
state: {
+27 -5
View File
@@ -1,18 +1,40 @@
/* eslint-disable no-console */
import chalk from 'chalk'
import browsers from '@packages/server/lib/browsers'
import openProject from '@packages/server/lib/open_project'
export * from './src/socket-ct'
export * from './src/server-ct'
import chalk from 'chalk'
import human from 'human-interval'
import _ from 'lodash'
export * from './src/project-ct'
export * from './src/server-ct'
export * from './src/socket-ct'
export * from './src/specs-store'
const Updater = require('@packages/server/lib/updater')
const registerCheckForUpdates = () => {
const checkForUpdates = (initialLaunch) => {
Updater.check({
initialLaunch,
testingType: 'ct',
onNewVersion: _.noop,
onNoNewVersion: _.noop,
})
}
setInterval(() => checkForUpdates(false), human('60 minutes'))
checkForUpdates(true)
}
// Partial because there are probably other options that are not included in this type.
export const start = async (projectRoot: string, args: Record<string, any>) => {
if (process.env['CYPRESS_INTERNAL_ENV'] === 'production') {
registerCheckForUpdates()
}
// add chrome as a default browser if none has been specified
return browsers.ensureAndGetByNameOrPath(args.browser)
.then((browser: Cypress.Browser) => {
@@ -0,0 +1,42 @@
const Updater = require('@packages/server/lib/updater')
import openProject from '@packages/server/lib/open_project'
import browsers from '@packages/server/lib/browsers'
import sinon from 'sinon'
import { expect } from 'chai'
import * as Index from '../../index'
describe('index.spec', () => {
let backupEnv
let stub_setInterval
beforeEach(() => {
backupEnv = process.env
process.env.CYPRESS_INTERNAL_ENV = 'production'
stub_setInterval = sinon.spy(global, 'setInterval')
sinon.stub(Updater, 'check').resolves()
sinon.stub(openProject, 'create').resolves()
sinon.stub(openProject, 'launch').resolves()
sinon.stub(browsers, 'ensureAndGetByNameOrPath').resolves()
})
afterEach(() => {
if (backupEnv) {
process.env = backupEnv
backupEnv = null
}
})
it('registers update check', async () => {
await Index.start('/path/to/project', {
browser: 'chrome',
})
expect(stub_setInterval.callCount).eq(1)
expect(stub_setInterval.firstCall.args[1]).eq(1000 * 60 * 60)
expect(Updater.check.callCount).eq(1)
expect(Updater.check.firstCall.args[0]).includes({
testingType: 'ct',
initialLaunch: true,
})
})
})
@@ -115,7 +115,7 @@ exports['e2e config applies defaultCommandTimeout globally 1'] = `
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/dom_times_out_spec.js/short defaultCommandTimeo (1280x720)
- /XXX/XXX/XXX/cypress/screenshots/dom_times_out_spec.js/short defaultCommandTimeo (1920x1080)
ut -- times out looking for a missing element (failed).png
@@ -363,123 +363,3 @@ export const externalTest = () => {
}
`
exports['e2e studio / can create tests in empty spec files'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (empty.spec.js) │
│ Searched: cypress/integration/empty.spec.js │
│ Experiments: experimentalStudio=true │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: empty.spec.js (1 of 1)
✓ New Test
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: empty.spec.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/empty.spec.js.mp4 (X second)
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ empty.spec.js XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
`
exports['empty.spec.js'] = `
import { saveStudio, verifyCommandLog, verifyVisit } from '../support'
Cypress.config('isTextTerminal', false)
// this whole thing has to run outside of a test to get the proper 0 state
// so we bind to the start event rather than using an \`it\` block
Cypress.on('run:start', () => {
const $document = Cypress.$(window.top.document.body)
// can't use Cypress commands outside of a test so we're limited to jquery
if ($document.find('.no-tests')[0]) {
$document.find('.open-studio')[0].click()
} else {
Cypress.config('isTextTerminal', true)
Cypress.emit('run:end')
// use setTimeout to push to the end of the stack after render
setTimeout(() => {
$document.find('.runner').find('.input-active')[0].click()
// react is super funky when it comes to event handling
// and this is the 'simplest' way to mock an change event
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
nativeInputValueSetter.call($document.find('.runner').find('.input-active')[0], 'new.html')
const changeEvent = new Event('input', { bubbles: true })
$document.find('.runner').find('.input-active')[0].dispatchEvent(changeEvent)
setTimeout(() => {
$document.find('.runner').find('.btn-submit')[0].click()
// we can finally use Cypress commands which makes it super easy from here on out
cy.get('.btn', { log: false }).click({ log: false })
verifyVisit('new.html')
verifyCommandLog(2, {
selector: '.btn',
name: 'click',
})
saveStudio('My New Test')
})
})
}
})
/* === Test Created with Cypress Studio === */
it('My New Test', function() {
/* ==== Generated with Cypress Studio ==== */
cy.visit('new.html');
cy.get('.btn').click();
/* ==== End Cypress Studio ==== */
});
`
+1 -1
View File
@@ -34,7 +34,7 @@ process.noDeprecation = process.env.CYPRESS_INTERNAL_ENV === 'production'
// always show stack traces for Electron deprecation warnings
process.traceDeprecation = true
require('./lib/util/suppress_unauthorized_warning').suppress()
require('./lib/util/suppress_warnings').suppress()
function launchOrFork () {
const nodeOptions = require('./lib/util/node_options')
+1
View File
@@ -261,6 +261,7 @@ module.exports = {
'recordKey',
'specPattern',
'tags',
'testingType',
]),
runnerCapabilities,
}
+2 -2
View File
@@ -428,9 +428,9 @@ export = {
if (isHeadless) {
args.push('--headless')
// set the window size for headless to a better default
// set default headless size to 1920x1080
// https://github.com/cypress-io/cypress/issues/6210
args.push('--window-size=1280,720')
args.push('--window-size=1920,1080')
}
// force ipv4
+27 -5
View File
@@ -153,6 +153,12 @@ module.exports = {
},
}
if (options.browser.isHeadless) {
// prevents a tiny 1px padding around the window
// causing screenshots/videos to be off by 1px
options.resizable = false
}
return _.defaultsDeep({}, options, defaults)
},
@@ -161,6 +167,15 @@ module.exports = {
_render (url, projectRoot, automation, options = {}) {
const win = Windows.create(projectRoot, options)
if (options.browser.isHeadless) {
// seemingly the only way to force headless to a certain screen size
// electron BrowserWindow constructor is not respecting width/height options
win.setSize(options.width, options.height)
} else {
// we maximize in headed mode, this is consistent with chrome+firefox headed
win.maximize()
}
automation.use(_getAutomation(win, options))
return this._launch(win, url, automation, options)
@@ -235,7 +250,7 @@ module.exports = {
return this._enableDebugger(win.webContents)
})
.then(() => {
return this._handleDownloads(win.webContents, options.downloadsFolder, automation)
return this._handleDownloads(win, options.downloadsFolder, automation)
})
.return(win)
},
@@ -291,8 +306,8 @@ module.exports = {
return webContents.debugger.sendCommand('Console.enable')
},
_handleDownloads (webContents, dir, automation) {
webContents.session.on('will-download', (event, downloadItem) => {
_handleDownloads (win, dir, automation) {
const onWillDownload = (event, downloadItem) => {
const savePath = path.join(dir, downloadItem.getFilename())
automation.push('create:download', {
@@ -307,9 +322,16 @@ module.exports = {
id: downloadItem.getETag(),
})
})
})
}
return webContents.debugger.sendCommand('Page.setDownloadBehavior', {
const { session } = win.webContents
session.on('will-download', onWillDownload)
// avoid adding redundant `will-download` handlers if session is reused for next spec
win.on('closed', () => session.removeListener('will-download', onWillDownload))
return win.webContents.debugger.sendCommand('Page.setDownloadBehavior', {
behavior: 'allow',
downloadPath: dir,
})
+53 -8
View File
@@ -308,6 +308,37 @@ const defaultPreferences = {
'browser.helperApps.neverAsk.saveToDisk': downloadMimeTypes,
}
const FIREFOX_HEADED_USERCSS = `\
#urlbar:not(.megabar), #urlbar.megabar > #urlbar-background, #searchbar {
background: -moz-Field !important;
color: -moz-FieldText !important;
}`
const FIREFOX_HEADLESS_USERCSS = `\
#urlbar {
height: 0px !important;
min-height: 0px !important;
overflow: hidden !important;
}
#toolbar {
height: 0px !important;
min-height: 0px !important;
overflow: hidden !important;
}
toolbar {
height: 0px !important;
min-height: 0px !important;
overflow: hidden !important;
}
#titlebar {
height: 0px !important;
min-height: 0px !important;
overflow: hidden !important;
display: none;
}
`
export function _createDetachedInstance (browserInstance: BrowserInstance): BrowserInstance {
const detachedInstance: BrowserInstance = new EventEmitter() as BrowserInstance
@@ -339,6 +370,10 @@ export async function open (browser: Browser, url, options: any = {}): Promise<B
if (browser.isHeadless) {
defaultLaunchOptions.args.push('-headless')
// we don't need to specify width/height since MOZ_HEADLESS_ env vars will be set
// and the browser will spawn maximized. The user may still supply these args to override
// defaultLaunchOptions.args.push('--width=1920')
// defaultLaunchOptions.args.push('--height=1081')
}
debug('firefox open %o', options)
@@ -428,18 +463,23 @@ export async function open (browser: Browser, url, options: any = {}): Promise<B
const userCSSPath = path.join(profileDir, 'chrome')
if (!await fs.pathExists(path.join(userCSSPath, 'userChrome.css'))) {
const userCss = `
#urlbar:not(.megabar), #urlbar.megabar > #urlbar-background, #searchbar {
background: -moz-Field !important;
color: -moz-FieldText !important;
}
`
try {
await fs.mkdir(userCSSPath)
} catch {
// probably the folder already exists, this is fine
}
// if we're headed we change the yellow automation mode url bar back to a normal color
//
// if we're headless we use userCss to 'trick' the browser
// into having a consistent browser window size that's near-fullscreen
// however it unfortunately still leaves 1px of padding at the top)
// without this trick there would be ~74px of padding at the top instead of 1px.
//
// TODO: allow configuring userCss through launchOptions
const userCss = options.browser.isHeadless ? FIREFOX_HEADLESS_USERCSS : FIREFOX_HEADED_USERCSS
await fs.writeFile(path.join(profileDir, 'chrome', 'userChrome.css'), userCss)
}
@@ -450,7 +490,12 @@ export async function open (browser: Browser, url, options: any = {}): Promise<B
debug('launch in firefox', { url, args: launchOptions.args })
const browserInstance = await utils.launch(browser, 'about:blank', launchOptions.args)
const browserInstance = await utils.launch(browser, 'about:blank', launchOptions.args, {
// sets headless resolution to 1920x1080 by default
// user can overwrite this default with these env vars or --height, --width arguments
MOZ_HEADLESS_WIDTH: '1920',
MOZ_HEADLESS_HEIGHT: '1081',
})
await firefoxUtil.setup({ extensions: launchOptions.extensions, url, foxdriverPort, marionettePort })
.catch((err) => {
+1
View File
@@ -202,6 +202,7 @@ const handleEvent = function (options, bus, event, id, type, arg) {
case 'updater:check':
return Updater.check({
...arg,
onNewVersion ({ version }) {
return send(version)
},
+4 -2
View File
@@ -273,7 +273,7 @@ const createRun = Promise.method((options = {}) => {
ciBuildId: null,
})
let { projectId, recordKey, platform, git, specPattern, specs, parallel, ciBuildId, group, tags } = options
let { projectId, recordKey, platform, git, specPattern, specs, parallel, ciBuildId, group, tags, testingType } = options
if (recordKey == null) {
recordKey = env.get('CYPRESS_RECORD_KEY')
@@ -319,6 +319,7 @@ const createRun = Promise.method((options = {}) => {
projectId,
recordKey,
specPattern,
testingType,
ci: {
params: ciProvider.ciParams(),
provider: ciProvider.provider(),
@@ -562,7 +563,7 @@ const _postInstanceTests = ({
}
const createRunAndRecordSpecs = (options = {}) => {
const { specPattern, specs, sys, browser, projectId, config, projectRoot, runAllSpecs, parallel, ciBuildId, group, project, onError } = options
const { specPattern, specs, sys, browser, projectId, config, projectRoot, runAllSpecs, parallel, ciBuildId, group, project, onError, testingType } = options
const recordKey = options.key
// we want to normalize this to an array to send to API
@@ -585,6 +586,7 @@ const createRunAndRecordSpecs = (options = {}) => {
return createRun({
git,
specs,
testingType,
group,
tags,
parallel,
+4 -3
View File
@@ -544,8 +544,8 @@ const getChromeProps = (writeVideoFrame) => {
const getElectronProps = (isHeaded, writeVideoFrame, onError) => {
return _
.chain({
width: 1280,
height: 720,
width: 1920,
height: 1080,
show: isHeaded,
onCrashed () {
const err = errors.get('RENDERER_CRASHED')
@@ -1513,7 +1513,7 @@ module.exports = {
const socketId = random.id()
const { projectRoot, record, key, ciBuildId, parallel, group, browser: browserName, tag } = options
const { projectRoot, record, key, ciBuildId, parallel, group, browser: browserName, tag, testingType } = options
// this needs to be a closure over `this.exitEarly` and not a reference
// because `this.exitEarly` gets overwritten in `this.listenForProjectEnd`
@@ -1630,6 +1630,7 @@ module.exports = {
browser,
parallel,
ciBuildId,
testingType,
project,
projectId,
projectRoot,
+1 -1
View File
@@ -1,6 +1,6 @@
require('graceful-fs').gracefulify(require('fs'))
require('../../util/suppress_unauthorized_warning').suppress()
require('../../util/suppress_warnings').suppress()
const ipc = require('../util').wrapIpc(process)
const { file: pluginsFile, projectRoot } = require('minimist')(process.argv.slice(2))
+4
View File
@@ -19,6 +19,10 @@ module.exports = {
return emitter.emit(message.event, ...message.args)
})
// prevent max listeners warning on ipc
// @see https://github.com/cypress-io/cypress/issues/1305#issuecomment-780895569
emitter.setMaxListeners(Infinity)
return {
send (event, ...args) {
if (aProcess.killed) {
+2 -2
View File
@@ -23,7 +23,7 @@ import statusCode from './util/status_code'
type WarningErr = Record<string, any>
const fullyQualifiedRe = /^https?:\/\//
const textHtmlContentTypeRe = /^text\/html/i
const htmlContentTypesRe = /^(text\/html|application\/xhtml)/i
const debug = Debug('cypress:server:server-e2e')
@@ -32,7 +32,7 @@ const isResponseHtml = function (contentType, responseBuffer) {
// want to match anything starting with 'text/html'
// including 'text/html;charset=utf-8' and 'Text/HTML'
// https://github.com/cypress-io/cypress/issues/8506
return textHtmlContentTypeRe.test(contentType)
return htmlContentTypesRe.test(contentType)
}
const body = _.invoke(responseBuffer, 'toString')
+11 -4
View File
@@ -1,3 +1,4 @@
const os = require('os')
const debug = require('debug')('cypress:server:updater')
const semver = require('semver')
const rp = require('@cypress/request-promise')
@@ -5,15 +6,17 @@ const pkg = require('@packages/root')
const { agent } = require('@packages/network')
const konfig = require('./konfig')
const { machineId } = require('./util/machine_id')
const _getManifest = (id) => {
const _getManifest = ({ id, initialLaunch, testingType }) => {
const url = konfig('desktop_manifest_url')
return rp.get({
url,
headers: {
'x-cypress-version': pkg.version,
'x-os-name': os.platform(),
'x-machine-id': id,
'x-initial-launch:': String(initialLaunch),
'x-testing-type': testingType,
},
agent,
proxy: null,
@@ -21,10 +24,14 @@ const _getManifest = (id) => {
})
}
const check = async ({ onNewVersion, onNoNewVersion } = {}) => {
const check = async ({ testingType, initialLaunch, onNewVersion, onNoNewVersion } = {}) => {
try {
const id = await machineId()
const manifest = await _getManifest(id)
const manifest = await _getManifest({
id,
testingType,
initialLaunch,
})
if (!manifest || !manifest.version) {
throw new Error('manifest is empty or does not have a version')
@@ -1,6 +1,7 @@
const _ = require('lodash')
const Debug = require('debug')
const originalEmitWarning = process.emitWarning
const debug = Debug('cypress:server:lib:util:suppress_warnings')
let suppressed = false
@@ -10,7 +11,8 @@ let suppressed = false
* https://github.com/cypress-io/cypress/issues/5248
*/
const suppress = () => {
if (suppressed) {
if (suppressed || process.env.CYPRESS_INTERNAL_ENV === 'production') {
// in development, still log warnings since they are helpful
return
}
@@ -18,7 +20,7 @@ const suppress = () => {
process.emitWarning = (warning, type, code, ...args) => {
if (_.isString(warning) && _.includes(warning, 'NODE_TLS_REJECT_UNAUTHORIZED')) {
// https://github.com/nodejs/node/blob/82f89ec8c1554964f5029fab1cf0f4fad1fa55a8/lib/_tls_wrap.js#L1378-L1384
// https://github.com/nodejs/node/blob/85e6089c4db4da23dd88358fe0a12edefcd411f2/lib/internal/options.js#L17
return
}
@@ -31,7 +33,7 @@ const suppress = () => {
return
}
return originalEmitWarning.call(process, warning, type, code, ...args)
debug('suppressed emitWarning from node: %o', { process, warning, type, code, args })
}
}
+1 -1
View File
@@ -127,7 +127,7 @@
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.0",
"@cypress/debugging-proxy": "2.0.1",
"@cypress/json-schemas": "5.37.3",
"@cypress/json-schemas": "5.38.0",
"@cypress/sinon-chai": "2.9.1",
"@ffprobe-installer/ffprobe": "1.1.0",
"@packages/desktop-gui": "0.0.0-development",
@@ -0,0 +1,33 @@
import _ from 'lodash'
import path from 'path'
import fs from 'fs-extra'
import e2e from '../support/helpers/e2e'
import Fixtures from '../support/helpers/fixtures'
const projectPath = Fixtures.projectPath('max-listeners')
describe('max listeners warning spec', () => {
e2e.setup()
// @see https://github.com/cypress-io/cypress/issues/1305
e2e.it('does not log MaxEventListeners error', {
browser: 'electron',
project: projectPath,
spec: '*',
onRun: async (exec) => {
const integrationPath = path.join(projectPath, 'cypress/integration')
// create a bunch of dummy test files to reproduce #1305
await fs.mkdirp(integrationPath)
await Promise.all(
_.times(15, (i) => fs.writeFile(path.join(integrationPath, `${i}.spec.js`), `it('test', () => {})`)),
)
const { stderr } = await exec()
expect(stderr).to.not.include('setMaxListeners')
expect(stderr).to.not.include('preprocessor:close')
},
})
})
+3 -9
View File
@@ -27,6 +27,7 @@ describe('e2e studio', function () {
project,
spec: 'extend.spec.js',
snapshot: true,
browser: 'electron',
onRun (exec) {
return exec().then(() => snapshotFile('extend.spec.js'))
},
@@ -37,6 +38,7 @@ describe('e2e studio', function () {
e2e.it('creates new test', {
project,
spec: 'new.spec.js',
browser: 'electron',
snapshot: true,
onRun (exec) {
return exec().then(() => snapshotFile('new.spec.js'))
@@ -47,6 +49,7 @@ describe('e2e studio', function () {
project,
spec: 'external.spec.js',
snapshot: true,
browser: 'electron',
onRun (exec) {
return exec()
// we snapshot the original spec to make sure it does NOT get written there
@@ -54,13 +57,4 @@ describe('e2e studio', function () {
.then(() => snapshotFile('external.js', 'support'))
},
})
e2e.it('can create tests in empty spec files', {
project,
spec: 'empty.spec.js',
snapshot: true,
onRun (exec) {
return exec().then(() => snapshotFile('empty.spec.js'))
},
})
})
+2 -3
View File
@@ -40,10 +40,9 @@ describe('e2e headless', function () {
})
})
e2e.it('launches maximized by default in headless mode (1280x720)', {
browser: 'chrome',
e2e.it('launches maximized by default in headless mode (1920x1080)', {
headed: false,
project: Fixtures.projectPath('screen-size'),
spec: '720p.spec.js',
spec: 'default_size.spec.js',
})
})
@@ -100,6 +100,7 @@ describe('e2e record', () => {
expect(postRun.body.projectId).to.eq('pid123')
expect(postRun.body.recordKey).to.eq('f858a2bc-b469-4e48-be67-0876339ee7e1')
expect(postRun.body.specPattern).to.eq('cypress/integration/record*')
expect(postRun.body.testingType).to.eq('e2e')
const firstInstance = requests[1]
@@ -1143,9 +1143,13 @@ describe('lib/cypress', () => {
setProxy: sinon.stub().resolves(),
setUserAgent: sinon.stub(),
on: sinon.stub(),
removeListener: sinon.stub(),
},
}
ee.maximize = sinon.stub
ee.setSize = sinon.stub
sinon.stub(browserUtils, 'launch').resolves(ee)
sinon.stub(Windows, 'create').returns(ee)
})
@@ -474,12 +474,13 @@ describe('Server', () => {
.get('/c').reply(200, 'notHtml', { 'content-type': 'text/html;charset=utf-8' })
// invalid, but let's be tolerant
.get('/d').reply(200, 'notHtml', { 'content-type': 'text/html;' })
.get('/e').reply(200, 'notHtml', { 'content-type': 'application/xhtml+xml' })
const bad = await this.server._onResolveUrl('http://example.com/a', {}, this.automationRequest)
expect(bad.isHtml).to.be.false
for (const path of ['/b', '/c', '/d']) {
for (const path of ['/b', '/c', '/d', '/e']) {
const details = await this.server._onResolveUrl(`http://example.com${path}`, {}, this.automationRequest)
expect(details.isHtml).to.be.true
@@ -6,7 +6,7 @@ const http = require('http')
const Jimp = require('jimp')
const path = require('path')
const Promise = require('bluebird')
const { useFixedFirefoxResolution } = require('../../../utils')
const { useFixedBrowserLaunchSize } = require('../../../utils')
/**
* @type {Cypress.PluginConfig}
@@ -53,7 +53,7 @@ module.exports = (on, config) => {
})
on('before:browser:launch', (browser, options) => {
useFixedFirefoxResolution(browser, options, config)
useFixedBrowserLaunchSize(browser, options, config)
if (browser.family === 'firefox' && process.env.FIREFOX_FORCE_STRICT_SAMESITE) {
// @see https://www.jardinesoftware.net/2019/10/28/samesite-by-default-in-2020/
@@ -1,6 +1,6 @@
/// <reference types="cypress" />
const { useFixedFirefoxResolution } = require('../../../utils')
const { useFixedBrowserLaunchSize } = require('../../../utils')
/**
* @type {Cypress.PluginConfig}
@@ -11,7 +11,7 @@ module.exports = (on, config) => {
}
on('before:browser:launch', (browser, options) => {
useFixedFirefoxResolution(browser, options, config)
useFixedBrowserLaunchSize(browser, options, config)
return options
})
@@ -1,21 +0,0 @@
describe('windowSize', () => {
it('spawns 720p', () => {
// assert the browser was spawned at 1280x720 and is full size
// normally e2e tests spawn at fixed size, but this spec should be spawned without passing any width/height arguments in plugins file.
expect({
innerWidth: top.window.innerWidth,
innerHeight: top.window.innerHeight,
availWidth: top.screen.availWidth,
availHeight: top.screen.availHeight,
screenWidth: top.screen.width,
screenHeight: top.screen.height,
}).deep.eq({
innerWidth: 1280,
innerHeight: 720,
availWidth: 1280,
availHeight: 720,
screenWidth: 1280,
screenHeight: 720,
})
})
})
@@ -0,0 +1,22 @@
describe('windowSize', () => {
it('spawns with correct default size', () => {
// assert the browser was spawned at 1920x1080 and is full size
// normally e2e tests spawn at fixed size, but this spec should be spawned without passing any width/height arguments in plugins file.
// TODO: look into fixing screen/available height and width
expect({
innerWidth: top.window.innerWidth,
innerHeight: top.window.innerHeight,
// screenWidth: top.screen.width,
// screenHeight: top.screen.height,
// availWidth: top.screen.availWidth,
// availHeight: top.screen.availHeight,
}).deep.eq({
innerWidth: 1920,
innerHeight: 1080,
// screenWidth: 1920,
// screenHeight: 1080,
// availWidth: 1920,
// availHeight: 1080,
})
})
})
@@ -1,48 +0,0 @@
import { saveStudio, verifyCommandLog, verifyVisit } from '../support'
Cypress.config('isTextTerminal', false)
// this whole thing has to run outside of a test to get the proper 0 state
// so we bind to the start event rather than using an `it` block
Cypress.on('run:start', () => {
const $document = Cypress.$(window.top.document.body)
// can't use Cypress commands outside of a test so we're limited to jquery
if ($document.find('.no-tests')[0]) {
$document.find('.open-studio')[0].click()
} else {
Cypress.config('isTextTerminal', true)
Cypress.emit('run:end')
// use setTimeout to push to the end of the stack after render
setTimeout(() => {
$document.find('.runner').find('.input-active')[0].click()
// react is super funky when it comes to event handling
// and this is the 'simplest' way to mock an change event
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
nativeInputValueSetter.call($document.find('.runner').find('.input-active')[0], 'new.html')
const changeEvent = new Event('input', { bubbles: true })
$document.find('.runner').find('.input-active')[0].dispatchEvent(changeEvent)
setTimeout(() => {
$document.find('.runner').find('.btn-submit')[0].click()
// we can finally use Cypress commands which makes it super easy from here on out
cy.get('.btn', { log: false }).click({ log: false })
verifyVisit('new.html')
verifyCommandLog(2, {
selector: '.btn',
name: 'click',
})
saveStudio('My New Test')
})
})
}
})
@@ -1,11 +1,19 @@
module.exports = {
useFixedFirefoxResolution (browser, options, config) {
if (browser.family === 'firefox' && !config.env['NO_RESIZE']) {
useFixedBrowserLaunchSize (browser, options, config) {
if (config.env['NO_RESIZE']) return
if (browser.family === 'firefox') {
// this is needed to ensure correct error screenshot / video recording
// resolution of exactly 1280x720 (height must account for firefox url bar)
options.args = options.args.concat(
['-width', '1280', '-height', '794'],
// resolution of exactly 1280x720
// (height must account for firefox url bar, which we can only shrink to 1px)
options.args.push(
'-width', '1280', '-height', '721',
)
} else if (browser.name === 'electron') {
options.preferences.width = 1280
options.preferences.height = 720
} else if (browser.family === 'chromium') {
options.args.push('--window-size=1280,720')
}
},
}
@@ -416,7 +416,6 @@ const e2e = {
if (process.env.NO_EXIT) {
Fixtures.scaffoldWatch()
process.env.CYPRESS_INTERNAL_E2E_TESTS
}
sinon.stub(process, 'exit')
@@ -54,7 +54,7 @@ const routeHandlers = {
postRun: {
method: 'post',
url: '/runs',
req: 'postRunRequest@2.3.0',
req: 'postRunRequest@2.4.0',
resSchema: 'postRunResponse@2.2.0',
res: (req, res) => {
if (!req.body.specs) {
@@ -106,7 +106,7 @@ describe('lib/browsers/chrome', () => {
expect(args).to.include.members([
'--headless',
'--window-size=1280,720',
'--window-size=1920,1080',
])
})
})
@@ -22,6 +22,9 @@ describe('lib/browsers/electron', () => {
some: 'var',
projectRoot: '/foo/',
onWarning: sinon.stub().returns(),
browser: {
isHeadless: false,
},
}
this.automation = new Automation('foo', 'bar', 'baz')
@@ -278,14 +281,16 @@ describe('lib/browsers/electron', () => {
context('._render', () => {
beforeEach(function () {
this.newWin = {}
this.newWin = {
maximize: sinon.stub(),
setSize: sinon.stub(),
}
sinon.stub(menu, 'set')
sinon.stub(electron, '_setProxy').resolves()
sinon.stub(electron, '_launch').resolves()
return sinon.stub(Windows, 'create')
.withArgs(this.options.projectRoot, this.options)
.returns(this.newWin)
})
@@ -293,18 +298,29 @@ describe('lib/browsers/electron', () => {
return electron._render(this.url, this.options.projectRoot, this.automation, this.options)
.then(() => {
expect(Windows.create).to.be.calledWith(this.options.projectRoot, this.options)
expect(this.newWin.maximize).called
expect(this.newWin.setSize).not.called
expect(electron._launch).to.be.calledWith(this.newWin, this.url, this.automation, this.options)
})
})
it('calls setSize on electron window if headless', function () {
const options = { ...this.options, browser: { isHeadless: true }, width: 100, height: 200 }
return electron._render(this.url, this.options.projectRoot, this.automation, options)
.then(() => {
expect(this.newWin.maximize).not.called
expect(this.newWin.setSize).calledWith(100, 200)
})
})
it('registers onRequest automation middleware', function () {
sinon.spy(this.automation, 'use')
return electron._render(this.url, this.options.projectRoot, this.automation, this.options)
.then(() => {
expect(Windows.create).to.be.calledWith(this.options.projectRoot, this.options)
expect(this.automation.use).to.be.called
expect(this.automation.use.lastCall.args[0].onRequest).to.be.a('function')
})
})
@@ -366,14 +382,14 @@ describe('lib/browsers/electron', () => {
})
it('.onFocus', function () {
let opts = electron._defaultOptions('/foo', this.state, { show: true })
let opts = electron._defaultOptions('/foo', this.state, { show: true, browser: {} })
opts.onFocus()
expect(menu.set).to.be.calledWith({ withDevTools: true })
menu.set.reset()
opts = electron._defaultOptions('/foo', this.state, { show: false })
opts = electron._defaultOptions('/foo', this.state, { show: false, browser: {} })
opts.onFocus()
expect(menu.set).not.to.be.called
@@ -273,6 +273,7 @@ describe('lib/modes/record', () => {
version: '59',
}
const tag = 'nightly,develop'
const testingType = 'e2e'
return recordMode.createRunAndRecordSpecs({
key,
@@ -287,6 +288,7 @@ describe('lib/modes/record', () => {
specPattern,
runAllSpecs,
tag,
testingType,
})
.then(() => {
expect(commitInfo.commitInfo).to.be.calledWith(projectRoot)
@@ -297,6 +299,7 @@ describe('lib/modes/record', () => {
projectId,
ciBuildId,
recordKey: key,
testingType,
specPattern: 'spec/pattern1,spec/pattern2',
specs: ['path/to/spec/a', 'path/to/spec/b'],
platform: {
+2 -2
View File
@@ -101,9 +101,9 @@ describe('lib/modes/run', () => {
it('sets width and height', () => {
const props = runMode.getElectronProps()
expect(props.width).to.eq(1280)
expect(props.width).to.eq(1920)
expect(props.height).to.eq(720)
expect(props.height).to.eq(1080)
})
it('sets show to boolean', () => {
@@ -6,9 +6,9 @@ import proxyquire from 'proxyquire'
const ERROR_MESSAGE = 'Setting the NODE_TLS_REJECT_UNAUTHORIZED'
const TLS_CONNECT = `require('tls').connect().on('error', ()=>{});`
const SUPPRESS_WARNING = `require('${__dirname}/../../lib/util/suppress_unauthorized_warning').suppress();`
const SUPPRESS_WARNING = `require('${__dirname}/../../lib/util/suppress_warnings').suppress();`
describe('lib/util/suppress_unauthorized_warning', function () {
describe('lib/util/suppress_warnings', function () {
it('tls.connect emits warning if NODE_TLS_REJECT_UNAUTHORIZED=0 and not suppressed', function () {
return execa.shell(`node -e "${TLS_CONNECT}"`, {
env: {
@@ -36,7 +36,7 @@ describe('lib/util/suppress_unauthorized_warning', function () {
const emitWarning = sinon.spy(process, 'emitWarning')
// force typescript to always be non-requireable
const { suppress } = proxyquire('../../lib/util/suppress_unauthorized_warning', {})
const { suppress } = proxyquire('../../lib/util/suppress_warnings', {})
suppress()
+4 -1
View File
@@ -10,12 +10,15 @@ describe('lib/updater', () => {
it('sends the right headers', () => {
sinon.stub(rp, 'get').resolves({})
Updater._getManifest('machine-id')
Updater._getManifest({ testingType: 'type', initialLaunch: true, id: 'machine-id' })
expect(rp.get).to.be.calledWithMatch({
headers: {
'x-cypress-version': pkg.version,
'x-os-name': 'linux',
'x-machine-id': 'machine-id',
'x-initial-launch:': 'true',
'x-testing-type': 'type',
},
})
})
+4
View File
@@ -4,5 +4,9 @@
"reporter": "../../node_modules/cypress-multi-reporters/index.js",
"reporterOptions": {
"configFile": "../../mocha-reporter-config.json"
},
"retries": {
"runMode": 2,
"openMode": 0
}
}
+367 -777
View File
File diff suppressed because it is too large Load Diff