Merge branch 'develop' into 7.0-release

# Conflicts:
#	packages/driver/package.json
#	yarn.lock
This commit is contained in:
Jennifer Shehane
2021-02-05 09:33:00 +06:30
406 changed files with 49837 additions and 10078 deletions
+4 -1
View File
@@ -7,8 +7,11 @@
"plugin:@cypress/dev/tests"
],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-useless-constructor": "off",
"prefer-spread": "off",
"prefer-rest-params": "off"
"prefer-rest-params": "off",
"no-useless-constructor": "off"
},
"settings": {
"react": {
+6
View File
@@ -10,9 +10,14 @@ jobs:
create-jira-issue-from-labeled-issue:
runs-on: ubuntu-latest
steps:
- name: Log label
if: github.event.label.name != 'internal-priority'
run: echo Label is ${{ github.event.label.name }} - skipping next steps
# https://github.com/atlassian/gajira-login
- name: Jira login
id: login
if: github.event.label.name == 'internal-priority'
uses: atlassian/gajira-login@master
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
@@ -36,6 +41,7 @@ jobs:
# https://github.com/actions/github-script
- name: Add comment to GitHub issue
if: github.event.label.name == 'internal-priority'
uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
+6
View File
@@ -10,9 +10,14 @@ jobs:
create-jira-issue-from-labeled-pr:
runs-on: ubuntu-latest
steps:
- name: Log label
if: github.event.label.name != 'internal-priority'
run: echo Label is ${{ github.event.label.name }} - skipping next steps
# https://github.com/atlassian/gajira-login
- name: Jira login
id: login
if: github.event.label.name == 'internal-priority'
uses: atlassian/gajira-login@master
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
@@ -37,6 +42,7 @@ jobs:
# https://github.com/actions/github-script
- name: Add comment to GitHub PR
if: github.event.label.name == 'internal-priority'
uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
+7 -1
View File
@@ -1,6 +1,12 @@
{
"eslint.alwaysShowStatus": true,
"eslint.validate": ["json"],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"json"
],
"eslint.enable": true,
// this project does not use Prettier
// thus set all settings to disable accidentally running Prettier
+39
View File
@@ -0,0 +1,39 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "rename yarn workspace packages",
"type": "shell",
"command": "node ./scripts/rename-workspace-packages --file ${file}",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "decaffeinate-bulk file",
"type": "shell",
"command": "yarn decaffeinate-bulk convert --file ${file}",
"problemMatcher": []
},
{
"label": "decaffeinate-bulk multiple files",
"type": "shell",
"command": "yarn decaffeinate-bulk convert --file ${file} ${file}",
"problemMatcher": []
},
{
"label": "decaffeinate-bulk dir",
"type": "shell",
"command": "yarn decaffeinate-bulk --dir ${fileDirname} convert",
"problemMatcher": []
}
]
}
+33 -2
View File
@@ -2,19 +2,43 @@
"autorun": false,
"terminals": [
{
"name": "cypress open",
"name": "yarn",
"focus": true,
"onlySingle": true,
"execute": true,
"cwd": "[cwd]",
"command": "yarn"
},
{
"name": "cypress open (E2E)",
"focus": true,
"onlySingle": true,
"execute": true,
"command": "yarn cypress:open"
},
{
"name": "cypress run",
"name": "cypress run (E2E)",
"focus": true,
"onlySingle": true,
"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,
"onlySingle": true,
"execute": false,
"cwd": "[cwd]/packages/server",
"command": "yarn test-e2e [fileBasename]"
},
{
"name": "packages/server test-watch",
"focus": true,
@@ -38,6 +62,13 @@
"cwd": "[cwd]/packages/runner",
"command": "yarn watch"
},
{
"name": "packages/runner-ct watch",
"focus": true,
"onlySingle": true,
"cwd": "[cwd]/packages/runner-ct",
"command": "yarn watch"
},
{
"name": "packages/driver cypress open",
"focus": true,
+3 -7
View File
@@ -43,7 +43,6 @@ You can build the Cypress binary locally by running `yarn binary-build`. You can
- Ensure you have the following permissions set up:
- An AWS account with permission to create AWS access keys for the Cypress CDN.
- Permissions for your npm account to publish the `cypress` package.
- Permissions to modify environment variables for `cypress` on CircleCI and AppVeyor.
- Permissions to update releases in ZenHub.
- Set up the following environment variables:
@@ -142,11 +141,10 @@ In the following instructions, "X.Y.Z" is used to denote the version of Cypress
npm dist-tag add cypress@X.Y.Z
```
9. Run `binary-release` to update the [download server's manifest](https://download.cypress.io/desktop.json) and set the next CI version:
9. Run `binary-release` to update the [download server's manifest](https://download.cypress.io/desktop.json):
```shell
yarn run binary-release --version X.Y.Z
```
> Note: Currently, there is an [issue setting the next CI version](https://github.com/cypress-io/cypress/issues/7176) that will cause this command to fail after setting the download manifest. You will need to manually update NEXT_DEV_VERSION by logging in to CircleCI and AppVeyor. This is noted in Step 16 below.
10. If needed, push out any updated changes to the links manifest to [`on.cypress.io`](https://github.com/cypress-io/cypress-services/tree/develop/packages/on).
@@ -185,9 +183,7 @@ In the following instructions, "X.Y.Z" is used to denote the version of Cypress
16. Publish a new docker image in [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images) under `included` for the new cypress version.
17. Decide on the next version that we will work on. For example, if we have just released `3.7.0` we probably will work on `3.7.1` next. Set it on [CI machines](#set-next-version-on-cis).
18. Update example projects to the new version. For most projects, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
17. Update example projects to the new version. For most projects, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
- [cypress-example-todomvc](https://github.com/cypress-io/cypress-example-todomvc/issues/99)
- [cypress-example-todomvc-redux](https://github.com/cypress-io/cypress-example-todomvc-redux/issues/1)
- [cypress-example-realworld](https://github.com/cypress-io/cypress-example-realworld/issues/2)
@@ -199,7 +195,7 @@ In the following instructions, "X.Y.Z" is used to denote the version of Cypress
- [cypress-documentation](https://github.com/cypress-io/cypress-documentation/issues/1313)
- [cypress-example-docker-compose](https://github.com/cypress-io/cypress-example-docker-compose) - Doesn't have a Renovate issue, but will auto-create and auto-merge non-major Cypress updates as long as the tests pass.
19. Check if any test or example repositories have a branch for testing the features or fixes from the newly published version `x.y.z`. The branch should also be named `x.y.z`. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z`, merge it into `master`.
18. Check if any test or example repositories have a branch for testing the features or fixes from the newly published version `x.y.z`. The branch should also be named `x.y.z`. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z`, merge it into `master`.
**Test Repos**
+3 -4
View File
@@ -2,7 +2,7 @@ branches:
only:
- master
- develop
- revert-create-cypress-tests
- sem-next-ver
- /win*/
# https://www.appveyor.com/docs/lang/nodejs-iojs/
@@ -54,7 +54,6 @@ install:
- node --version
- node --print process.arch
- yarn --version
- yarn check-next-dev-version
# prints all public variables relevant to the build
- print-env Platform
- yarn check-node-version
@@ -89,8 +88,8 @@ test_script:
# make sure our snapshots are compared correctly
# - npm run test-mocha-snapshot
# the other larger tests
- echo *** Building Windows NPM package %NEXT_DEV_VERSION% ***
- npm --no-git-tag-version version %NEXT_DEV_VERSION%
- echo *** Building Windows NPM package ***
- yarn get-next-version --npm
- cd cli
- yarn
- yarn build
+65 -17
View File
@@ -9,7 +9,9 @@ macBuildFilters: &macBuildFilters
only:
- develop
- v6.0-release
- feat/component-testing
- include-electron-node-version
- sem-next-ver
defaults: &defaults
parallelism: 1
@@ -37,6 +39,8 @@ testBinaryFirefox: &testBinaryFirefox
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
- build-binary
@@ -231,7 +235,7 @@ commands:
clone-repo-and-checkout-release-branch:
description: |
Clones an external repo and then checks out the branch that matches NEXT_DEV_VERSION otherwise uses 'master' branch.
Clones an external repo and then checks out the branch that matches the next version otherwise uses 'master' branch.
parameters:
repo:
description: "Name of the github repo to clone like: cypress-example-kitchensink"
@@ -244,7 +248,7 @@ commands:
name: "Cloning test project: <<parameters.repo>>"
command: |
git clone --depth 1 --no-single-branch https://github.com/cypress-io/<<parameters.repo>>.git /tmp/<<parameters.repo>>
cd /tmp/<<parameters.repo>> && (git checkout $NEXT_DEV_VERSION || true)
cd /tmp/<<parameters.repo>> && (git checkout $(node ./scripts/get-next-version) || true)
test-binary-against-repo:
description: |
@@ -643,6 +647,20 @@ jobs:
path: /tmp/cypress
- store-npm-logs
server-ct-unit-tests:
<<: *defaults
parallelism: 1
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- 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
parallelism: 1
@@ -894,6 +912,16 @@ jobs:
working_directory: npm/webpack-preprocessor/examples/react-app
- store-npm-logs
npm-webpack-dev-server:
<<: *defaults
steps:
- attach_workspace:
at: ~/
- check-conditional-ci
- run:
name: Run tests
command: yarn workspace @cypress/webpack-dev-server test
npm-webpack-batteries-included-preprocessor:
<<: *defaults
steps:
@@ -915,7 +943,8 @@ jobs:
command: yarn workspace @cypress/vue build
- run:
name: Run tests
command: yarn workspace @cypress/vue test
command: yarn test
working_directory: npm/vue
- store-npm-logs
npm-react:
@@ -1028,13 +1057,10 @@ jobs:
DEBUG: electron-builder,electron-osx-sign*
# notarization on Mac can take a while
no_output_timeout: "45m"
# if this is a forked pull request, the NEXT_DEV_VERSION environment variable
# won't be set and we will use default version, since we are not going to
# upload the dev binary build anywhere
command: |
. ./scripts/load-nvm.sh
node --version
yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development}
yarn binary-build --platform $PLATFORM --version $(node ./scripts/get-next-version)
- run:
name: Zip the binary
command: |
@@ -1060,7 +1086,7 @@ jobs:
command: |
node scripts/binary.js upload-unique-binary \
--file cypress.zip \
--version $NEXT_DEV_VERSION
--version $(node ./scripts/get-next-version)
- run: cat binary-url.json
- store-npm-logs
- persist_to_workspace:
@@ -1139,14 +1165,9 @@ jobs:
at: ~/
- check-conditional-ci
- install-required-node
- run:
name: Check next dev version
command: |
. ./scripts/load-nvm.sh
yarn check-next-dev-version
- run:
name: bump NPM version
command: yarn version --no-git-tag-version --new-version ${NEXT_DEV_VERSION:-0.0.0-development}
command: yarn get-next-version --npm
- run:
name: build NPM package
command: |
@@ -1168,8 +1189,8 @@ jobs:
command: ls -l
# created file should have filename cypress-<version>.tgz
- run: mkdir /tmp/urls
- run: cp cli/build/cypress-v${NEXT_DEV_VERSION:-0.0.0-development}.tgz cypress.tgz
- run: cp cli/build/cypress-v${NEXT_DEV_VERSION:-0.0.0-development}.tgz /tmp/urls/cypress.tgz
- run: cp cli/build/cypress-v*.tgz cypress.tgz
- run: cp cli/build/cypress-v*.tgz /tmp/urls/cypress.tgz
- run: ls -l /tmp/urls
- store-npm-logs
- run: pwd
@@ -1192,7 +1213,7 @@ jobs:
command: |
node scripts/binary.js upload-npm-package \
--file cypress.tgz \
--version $NEXT_DEV_VERSION
--version $(node ./scripts/get-next-version)
- store-npm-logs
- run: ls -l
- run: cat npm-package-url.json
@@ -1720,6 +1741,9 @@ linux-workflow: &linux-workflow
requires:
- build
- npm-webpack-dev-server:
requires:
- build
- npm-webpack-preprocessor:
requires:
- build
@@ -1818,6 +1842,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build
- test-kitchensink:
@@ -1836,6 +1862,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build
- build-npm-package:
@@ -1848,6 +1876,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
- build-binary:
@@ -1860,6 +1890,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-binary
- test-npm-module-on-minimum-node-version:
@@ -1917,6 +1949,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- upload-npm-package
- upload-binary
@@ -1927,6 +1961,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- upload-npm-package
- upload-binary
@@ -1936,6 +1972,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
- build-binary
@@ -1946,6 +1984,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
- build-binary
@@ -1972,6 +2012,8 @@ linux-workflow: &linux-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
- build-binary
@@ -2066,6 +2108,8 @@ mac-workflow: &mac-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package
- Mac binary
@@ -2079,6 +2123,8 @@ mac-workflow: &mac-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package upload
- Mac binary upload
@@ -2092,6 +2138,8 @@ mac-workflow: &mac-workflow
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package upload
- Mac binary upload
+1
View File
@@ -35,6 +35,7 @@ exports['shows help for open --foo 1'] = `
-P, --project <project-path> path to the project
--dev runs cypress in development and bypasses
binary check
-ct, --component-testing Cypress Component Testing mode (alpha)
-h, --help display help for command
-------
stderr:
+22
View File
@@ -139,6 +139,7 @@ const knownCommands = [
'open',
'open-ct',
'run',
'run-ct',
'verify',
'-v',
'--version',
@@ -409,6 +410,26 @@ module.exports = {
.catch(util.logErrorExit1)
})
program
// TODO make this command public once CT will be merged completely
.command('run-ct', { hidden: true })
.usage('[options]')
.description('Runs all Cypress Component Testing suites')
.option('-b, --browser <browser-path>', text('browserOpenMode'))
.option('-c, --config <config>', text('config'))
.option('-C, --config-file <config-file>', text('configFile'))
.option('-d, --detached [bool]', text('detached'), coerceFalse)
.option('-e, --env <env>', text('env'))
.option('--global', text('global'))
.option('-p, --port <port>', text('port'))
.option('-P, --project <project-path>', text('project'))
.option('--dev', text('dev'), coerceFalse)
.action((opts) => {
debug('running Cypress run-ct')
require('./exec/run')
.start(util.parseOpts(opts), { isComponentTesting: true })
})
program
.command('open')
.usage('[options]')
@@ -422,6 +443,7 @@ module.exports = {
.option('-p, --port <port>', text('port'))
.option('-P, --project <project-path>', text('project'))
.option('--dev', text('dev'), coerceFalse)
.option('-ct, --component-testing', 'Cypress Component Testing mode (alpha)')
.action((opts) => {
debug('opening Cypress')
require('./exec/open')
+5 -1
View File
@@ -161,7 +161,7 @@ module.exports = {
processRunOptions,
isValidProject,
// resolves with the number of failed tests
start (options = {}) {
start (options = {}, { isComponentTesting } = { isComponentTesting: false }) {
_.defaults(options, {
key: null,
spec: null,
@@ -183,6 +183,10 @@ module.exports = {
throw err
}
if (isComponentTesting) {
args.push('--componentTesting')
}
debug('run to spawn.start args %j', args)
return spawn.start(args, {
+6 -2
View File
@@ -2,11 +2,15 @@ const os = require('os')
const Promise = require('bluebird')
const Xvfb = require('@cypress/xvfb')
const { stripIndent } = require('common-tags')
const debug = require('debug')('cypress:cli')
const debugXvfb = require('debug')('cypress:xvfb')
const Debug = require('debug')
const { throwFormErrorText, errors } = require('../errors')
const util = require('../util')
const debug = Debug('cypress:cli')
const debugXvfb = Debug('cypress:xvfb')
debug.Debug = debugXvfb.Debug = Debug
const xvfbOptions = {
timeout: 30000, // milliseconds
// need to explicitly define screen otherwise electron will crash
+1
View File
@@ -220,6 +220,7 @@ const parseOpts = (opts) => {
'reporter',
'reporterOptions',
'record',
'runProject',
'spec',
'tag')
+2 -1
View File
@@ -23,6 +23,7 @@
"@cypress/listr-verbose-renderer": "^0.4.1",
"@cypress/request": "^2.88.5",
"@cypress/xvfb": "^1.2.4",
"@types/node": "12.12.50",
"@types/sinonjs__fake-timers": "^6.0.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.1.2",
@@ -35,7 +36,7 @@
"commander": "^5.1.0",
"common-tags": "^1.8.0",
"dayjs": "^1.9.3",
"debug": "^4.1.1",
"debug": "4.3.2",
"eventemitter2": "^6.4.2",
"execa": "^4.0.2",
"executable": "^4.1.1",
+11
View File
@@ -633,10 +633,21 @@ describe('cli', () => {
expect(spawn.start.firstCall.args[0]).to.include('--componentTesting')
})
it('runs server with correct args for component-testing', () => {
this.exec('run-ct --dev')
expect(spawn.start.firstCall.args[0]).to.include('--componentTesting')
})
it('does not display open-ct command in the help', () => {
return execa('bin/cypress', ['help']).then((result) => {
expect(result).to.not.include('open-ct')
})
})
it('does not display run-ct command in the help', () => {
return execa('bin/cypress', ['help']).then((result) => {
expect(result).to.not.include('run-ct')
})
})
})
})
+13 -2
View File
@@ -9,9 +9,20 @@ describe('lib/exec/xvfb', function () {
})
context('debugXvfb', function () {
let { Debug } = xvfb._debugXvfb
let { namespaces } = Debug
beforeEach(() => {
Debug.enable(namespaces)
})
afterEach(() => {
Debug.enable(namespaces)
})
it('outputs when enabled', function () {
sinon.stub(process.stderr, 'write').returns(undefined)
sinon.stub(xvfb._debugXvfb, 'enabled').value(true)
Debug.enable(xvfb._debugXvfb.namespace)
xvfb._xvfb._onStderrData('asdf')
@@ -21,7 +32,7 @@ describe('lib/exec/xvfb', function () {
it('does not output when disabled', function () {
sinon.stub(process.stderr, 'write')
sinon.stub(xvfb._debugXvfb, 'enabled').value(false)
Debug.disable()
xvfb._xvfb._onStderrData('asdf')
+13
View File
@@ -0,0 +1,13 @@
/**
* This file should be deleted as soon as the serever
* TODO: delete this file when ResolvedDevServerConfig.server is converted to closeServer
*/
/// <reference types="node" />
import * as cyUtilsHttp from 'http'
export = cyUtilsHttp
/**
* namespace created to bridge nodeJs.http typings so that
* we can type http Server in CT
*/
export as namespace cyUtilsHttp
+19
View File
@@ -1,3 +1,4 @@
/// <reference path="./cy-http.d.ts" />
/// <reference path="./cypress-npm-api.d.ts" />
declare namespace Cypress {
@@ -5119,6 +5120,23 @@ declare namespace Cypress {
tag?: string
}
interface DevServerOptions {
specs: Spec[]
config: {
supportFile?: string
projectRoot: string
webpackDevServerPublicPathRoute: string
},
devServerEvents: NodeJS.EventEmitter,
}
interface ResolvedDevServerConfig {
port: number
// TODO: when removing server and replacing it by close Function,
// delete the cy-http.d.ts file. It's a hack.
server: cyUtilsHttp.Server
}
interface PluginEvents {
(action: 'after:run', fn: (results: CypressCommandLine.CypressRunResult | CypressCommandLine.CypressFailedRunResult) => void | Promise<void>): void
(action: 'after:screenshot', fn: (details: ScreenshotDetails) => void | AfterScreenshotReturnObject | Promise<AfterScreenshotReturnObject>): void
@@ -5127,6 +5145,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: 'task', tasks: Tasks): void
}
+37 -1
View File
@@ -299,6 +299,42 @@ module.exports = {
'mocha/no-global-tests': 'error',
'@cypress/dev/skip-comment': 'error',
},
overrides: [{
files: '*.tsx',
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {
jsx: true, // Allows for the parsing of JSX
},
},
settings: {
react: {
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
},
},
plugins: [
'@typescript-eslint',
'react',
],
rules: {
'no-unused-vars': 'off', // avoid interface imports to be warned against
'@typescript-eslint/no-unused-vars': 'error',
'react/jsx-curly-spacing': 'error',
'react/jsx-equals-spacing': 'error',
'react/jsx-no-duplicate-props': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-pascal-case': 'error',
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'react/jsx-wrap-multilines': 'error',
'react/no-unknown-property': 'error',
'react/prefer-es6-class': 'error',
'react/react-in-jsx-scope': 'error',
'react/require-render-return': 'error',
},
}],
},
react: {
@@ -318,7 +354,6 @@ module.exports = {
rules: {
'react/jsx-curly-spacing': 'error',
'react/jsx-equals-spacing': 'error',
'react/jsx-filename-extension': 'error',
'react/jsx-no-duplicate-props': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-pascal-case': 'error',
@@ -329,6 +364,7 @@ module.exports = {
'react/prefer-es6-class': 'error',
'react/react-in-jsx-scope': 'error',
'react/require-render-return': 'error',
'react/jsx-filename-extension': 'error',
},
},
},
+1
View File
@@ -5,6 +5,7 @@
"projectId": "z9dxah",
"testFiles": "**/*spec.{js,jsx,ts,tsx}",
"env": {
"reactDevtools": true,
"cypress-react-selector": {
"root": "#cypress-root"
}
@@ -3,7 +3,7 @@ import { mount } from '@cypress/react'
import { Timer } from './Timer'
import { TimerView } from './timer-view'
describe('MobX v6', { viewportWidth: 200, viewportHeight: 100 }, () => {
describe('MobX v6', () => {
context('TimerView', () => {
it('increments every second', () => {
const myTimer = new Timer()
@@ -1,3 +0,0 @@
button {
height: 50px;
}
@@ -1,101 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { createMount, mount } from '@cypress/react'
describe('cssFile', () => {
it('is loaded', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />, {
cssFiles: 'cypress/component/basic/styles/css-file/index.css',
})
cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('cssFile is for loading a single file', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />, {
cssFile: 'cypress/component/basic/styles/css-file/index.css',
})
cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('allows loading several CSS files', () => {
const Component = () => {
return (
<button className="green">Large green button</button>
)
}
mount(<Component />, {
cssFiles: [
'cypress/component/basic/styles/css-file/base.css',
'cypress/component/basic/styles/css-file/index.css',
],
log: false,
})
// check the style from the first css file
cy.get('button')
.should('have.class', 'green')
.invoke('css', 'height')
.should((value) => {
// round the height, since in real browser it is never exactly 50
expect(parseFloat(value), 'height is 50px').to.be.closeTo(50, 1)
})
// and should have style from the second css file
cy.get('button').and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('resets the style', () => {
const Component = () => {
return (
<button className="green">Large green button</button>
)
}
mount(<Component />)
// the component should NOT have CSS styles
cy.get('button')
.should('have.class', 'green')
.invoke('css', 'height')
.should((value) => {
expect(parseFloat(value), 'height is < 30px').to.be.lessThan(30)
})
})
context('Using createMount to simplify global css experience', () => {
const mount = createMount({
cssFiles: 'cypress/component/basic/styles/css-file/index.css',
})
it('createMount green button', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />)
cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('createMount blue button', () => {
const Component = () => <button className="blue">blue button</button>
mount(<Component />)
cy.get('button')
.should('have.class', 'blue')
.and('have.css', 'background-color', 'rgb(0, 0, 255)')
})
})
})
@@ -1,7 +0,0 @@
button.green {
background-color: #00ff00;
}
button.blue {
background-color: #0000ff;
}
@@ -1,79 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
describe('stylesheets', () => {
const baseUrl = '/__root/cypress/component/basic/styles/css-file/base.css'
const indexUrl = '/__root/cypress/component/basic/styles/css-file/index.css'
context('options.stylesheet', () => {
it('options.stylesheet string', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />, {
stylesheet: indexUrl,
})
cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('options.stylesheet []', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />, {
stylesheet: [indexUrl],
})
cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
})
context('options.stylesheets', () => {
it('allows loading several CSS files', () => {
const Component = () => {
return (
<button className="green">Large green button</button>
)
}
mount(<Component />, {
stylesheets: [baseUrl, indexUrl],
})
// check the style from the first css file
cy.get('button')
.should('have.class', 'green')
.invoke('css', 'height')
.should((value) => {
// round the height, since in real browser it is never exactly 50
expect(parseFloat(value), 'height is 50px').to.be.closeTo(50, 1)
})
// and should have style from the second css file
cy.get('button').and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
it('resets the style', () => {
const Component = () => {
return (
<button className="green">Large green button</button>
)
}
mount(<Component />)
// the component should NOT have CSS styles
cy.get('button')
.should('have.class', 'green')
.invoke('css', 'height')
.should((value) => {
// meaning: the style has been reset
expect(parseFloat(value), 'height is < 30px').to.be.lessThan(30)
})
})
})
})
@@ -0,0 +1,5 @@
describe('Smoke Test', () => {
it('does not use the mount command', () => {
expect(true).to.eq(true)
})
})
@@ -0,0 +1,39 @@
import React from 'react'
import { mount } from '@cypress/react'
const viewportWidth = 200
const viewportHeight = 100
describe('cypress.json viewport',
{ viewportWidth, viewportHeight },
() => {
it('should have the correct dimensions', () => {
// cy.should cannot be the first cy command we run
cy.window().should((w) => {
expect(w.innerWidth).to.eq(viewportWidth)
expect(w.innerHeight).to.eq(viewportHeight)
})
})
})
describe('cy.viewport', () => {
it('should resize the viewport', () => {
cy.viewport(viewportWidth, viewportHeight).should(() => {
expect(window.innerWidth).to.eq(viewportWidth)
expect(window.innerHeight).to.eq(viewportHeight)
})
})
it('should make it scale down when overflowing', () => {
mount(<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Incidunt necessitatibus quia quo obcaecati tempora numquam nobis
minima libero vel? Nam sequi iusto quod fugit vel rerum eligendi beatae voluptatibus numquam.
</p>)
expect(getComputedStyle(window.parent.document.querySelector('iframe').parentElement).transform).to.contain('matrix(1')
cy.viewport(2000, 200).should(() => {
expect(getComputedStyle(window.parent.document.querySelector('iframe').parentElement).transform).not.to.contain('matrix(1')
})
})
})
+1 -1
View File
@@ -2,4 +2,4 @@
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
}
+6 -10
View File
@@ -1,8 +1,8 @@
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const { startDevServer } = require('@cypress/webpack-dev-server')
const babelConfig = require('../../babel.config.js')
/** @type import("webpack").Configuration */
const webpackOptions = {
const webpackConfig = {
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx'],
},
@@ -55,15 +55,11 @@ const webpackOptions = {
},
}
const options = {
// send in the options from your webpack.config.js, so it works the same
// as your app's code
webpackOptions,
watchOptions: {},
}
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
on('file:preprocessor', webpackPreprocessor(options))
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))
return config
}
-2
View File
@@ -15,5 +15,3 @@
// custom commands provided by this package, built from TypeScript code in "lib"
// using "npm run transpile"
import '@cypress/react/hooks'
import 'cypress-react-selector'
File diff suppressed because it is too large Load Diff
+8 -4
View File
@@ -6,18 +6,22 @@
"scripts": {
"build": "rimraf dist && yarn transpile",
"build-prod": "yarn build",
"cy:open": "node ../../scripts/cypress open",
"cy:open": "node ../../scripts/cypress.js open-ct --project ${PWD}",
"cy:open:debug": "NODE_OPTIONS=--max-http-header-size=1048576 node --inspect-brk ../../scripts/start.js --component-testing --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js run-ct --project ${PWD}",
"cy:run:debug": "NODE_OPTIONS=--max-http-header-size=1048576 node --inspect-brk ../../scripts/start.js --component-testing --run-project ${PWD}",
"pretest": "yarn transpile",
"test": "node ../../scripts/cypress run",
"test": "yarn cy:run",
"transpile": "tsc",
"watch": "tsc -w"
},
"dependencies": {
"@babel/plugin-transform-modules-commonjs": "7.12.1",
"@cypress/code-coverage": "3.8.6",
"@cypress/webpack-dev-server": "0.0.0-development",
"@cypress/webpack-preprocessor": "0.0.0-development",
"babel-plugin-istanbul": "6.0.0",
"debug": "4.3.1",
"debug": "4.3.2",
"find-up": "5.0.0",
"find-webpack": "2.2.1",
"mime-types": "2.1.26",
@@ -45,7 +49,7 @@
"@types/chalk": "2.2.0",
"@types/inquirer": "7.3.1",
"@types/mock-fs": "4.10.0",
"@types/node": "9.6.49",
"@types/node": "12.12.50",
"@types/semver": "7.3.4",
"arg": "4.1.3",
"autoprefixer": "9.7.6",
+1 -1
View File
@@ -1,7 +1,7 @@
// @ts-ignore
const unfetch = require('unfetch/dist/unfetch.js')
// @ts-ignore
const isComponentSpec = () => Cypress.spec.specType === 'component'
const isComponentSpec = () => true
// When running component specs, we cannot allow "cy.visit"
// because it will wipe out our preparation work, and does not make much sense
-3
View File
@@ -1,6 +1,3 @@
export * from './mount'
export * from './mountHook'
/** @deprecated */
export { default } from './mount'
+6 -3
View File
@@ -2,10 +2,12 @@ import * as React from 'react'
import ReactDOM, { unmountComponentAtNode } from 'react-dom'
import getDisplayName from './getDisplayName'
import { injectStylesBeforeElement } from './utils'
import './hooks'
import 'cypress-react-selector'
const rootId = 'cypress-root'
const isComponentSpec = () => Cypress.spec.specType === 'component'
const isComponentSpec = () => true
function checkMountModeEnabled () {
if (!isComponentSpec()) {
@@ -46,7 +48,7 @@ const injectStyles = (options: MountOptions) => {
})
```
**/
export const mount = (jsx: React.ReactElement, options: MountOptions = {}) => {
export const mount = (jsx: React.ReactNode, options: MountOptions = {}) => {
checkMountModeEnabled()
// Get the display name property via the component constructor
@@ -105,9 +107,10 @@ export const mount = (jsx: React.ReactElement, options: MountOptions = {}) => {
if (logInstance) {
const logConsoleProps = {
// @ts-ignore protect the use of jsx functional components use ReactNode
props: jsx.props,
description: 'Mounts React component',
home: 'https://github.com/bahmutov/cypress-react-unit-test',
home: 'https://github.com/cypress-io/cypress',
}
const componentElement = el.children[0]
+1
View File
@@ -2,4 +2,5 @@
// "supportFile": "node_modules/@cypress/react/support"
// adds commands from @cypress/react
require('../dist/hooks')
require('cypress-react-selector')
require('@cypress/code-coverage/support')
+6
View File
@@ -0,0 +1,6 @@
{
"plugins": [
"@babel/plugin-transform-modules-commonjs",
"babel-plugin-istanbul"
]
}
+1
View File
@@ -2,6 +2,7 @@
"viewportWidth": 500,
"viewportHeight": 500,
"video": false,
"responseTimeout": 2500,
"projectId": "134ej7",
"testFiles": "**/*spec.js",
"experimentalComponentTesting": true,
@@ -0,0 +1,5 @@
describe('long title', () => {
it('tests', () => {
expect(true).to.equal(true)
})
})
@@ -7,7 +7,8 @@ import { mount } from '@cypress/vue'
import messages from './translations.json'
function expectHelloWorldGreeting () {
cy.viewport(400, 200)
// TODO: Support this API!
// cy.viewport(400, 200)
const allLocales = Cypress.vue.$i18n.availableLocales
// ensure we don't strip locales
@@ -4,7 +4,8 @@ import Users from './3-Users.vue'
// test file can import the entire AxiosApi module
import * as AxiosApi from './AxiosApi'
describe('Mocking imports from Axios Wrapper', () => {
// TODO: esmodule mocking is broken
xdescribe('Mocking imports from Axios Wrapper', () => {
it('renders mocked data', () => {
// stub export "get" that Users component imports and uses
cy.stub(AxiosApi, 'get')
@@ -5,7 +5,7 @@ import * as GreetingModule from './greeting'
describe('Mocking ES6 imports', () => {
beforeEach(() => {
cy.viewport(300, 200)
// cy.viewport(300, 200)
})
it('shows real greeting without mocking', () => {
@@ -4,7 +4,8 @@ import { mount } from '@cypress/vue'
describe('Calculator', () => {
it('adds two numbers', () => {
cy.viewport(400, 200)
// TODO: Uncomment with cy.viewport fixes are merged.
// cy.viewport(400, 200)
mount(Calculator)
cy.get('[data-cy=a]').clear().type(23)
cy.get('[data-cy=b]').clear().type(19)
@@ -1,8 +1,8 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import PizzaShop from './index'
import Home from './Home'
import Order from './Order'
import PizzaShop from './index.vue'
import Home from './Home.vue'
import Order from './Order.vue'
Vue.use(VueRouter)
+97 -40
View File
@@ -6,59 +6,116 @@ import { mount } from '@cypress/vue'
/* eslint-env mocha */
describe('AjaxList', () => {
// because this component loads data right away
// we need to setup XHR intercepts BEFORE mounting it
// thus each test will first do its "cy.route"
// then will mount the component
context('using cy.intercept()', () => {
// because this component loads data right away
// we need to setup XHR intercepts BEFORE mounting it
// thus each test will first do its "cy.route"
// then will mount the component
it('loads list of posts', () => {
mount(AjaxList)
cy.get('li').should('have.length', 3)
it('loads list of posts', () => {
mount(AjaxList)
cy.get('li').should('have.length', 3)
})
it('can inspect real data in XHR', () => {
cy.intercept('/users?_limit=3').as('users')
mount(AjaxList)
cy.wait('@users').its('response.body').should('have.length', 3)
})
it('can display mock XHR response', () => {
const users = [{ id: 1, name: 'foo' }]
cy.intercept('GET', '/users?_limit=3', { body: users }).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 1).first().contains('foo')
})
it('can inspect mocked XHR', () => {
const users = [{ id: 1, name: 'foo' }]
cy.intercept('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.wait('@users').its('response.body').should('deep.equal', users)
})
it('can delay and wait on XHR', () => {
const users = [{ id: 1, name: 'foo' }]
cy.intercept({
method: 'GET',
url: '/users?_limit=3',
}, {
delayMs: 1000,
body: users,
}).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
})
})
it('can inspect real data in XHR', () => {
cy.server()
cy.route('/users?_limit=3').as('users')
mount(AjaxList)
context('using cy.route()', () => {
// because this component loads data right away
// we need to setup XHR intercepts BEFORE mounting it
// thus each test will first do its "cy.route"
// then will mount the component
cy.wait('@users').its('response.body').should('have.length', 3)
})
it('loads list of posts', () => {
mount(AjaxList)
cy.get('li').should('have.length', 3)
})
it('can display mock XHR response', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
it('can inspect real data in XHR', () => {
cy.server()
cy.route('/users?_limit=3').as('users')
mount(AjaxList)
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.wait('@users').its('response.body').should('have.length', 3)
})
cy.get('li').should('have.length', 1).first().contains('foo')
})
it('can display mock XHR response', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
it('can inspect mocked XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 1).first().contains('foo')
})
cy.wait('@users').its('response.body').should('deep.equal', users)
})
it('can inspect mocked XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
it('can delay and wait on XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
delay: 1000,
}).as('users')
cy.wait('@users').its('response.body').should('deep.equal', users)
})
mount(AjaxList)
it('can delay and wait on XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
delay: 1000,
}).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
})
})
})
+7 -2
View File
@@ -1,8 +1,13 @@
/// <reference types="cypress" />
const preprocessor = require('../../dist/plugins/webpack')
const { startDevServer } = require('@cypress/webpack-dev-server')
const webpackConfig = require('../../webpack.config')
/**
* @type Cypress.PluginConfig
*/
module.exports = (on, config) => {
preprocessor(on, config, require('../../webpack.config.js'))
require('@cypress/code-coverage/task')(on, config)
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))
return config
}
@@ -1,7 +1,8 @@
const preprocessor = require('@cypress/vue/dist/plugins/webpack')
const webpackConfig = require('@vue/cli-service/webpack.config')
module.exports = (on, config) => {
preprocessor(on, config)
preprocessor(on, config, webpackConfig)
// IMPORTANT return the config object
return config
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+24
View File
@@ -0,0 +1,24 @@
# example
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
}
@@ -0,0 +1 @@
// setup code goes here
+7
View File
@@ -0,0 +1,7 @@
{
"experimentalComponentTesting": true,
"testFiles": "**/*.spec.*",
"pluginsFile": "./plugins.js",
"componentFolder": "./src",
"componentSupportFile": "./component-helpers.js"
}
+23
View File
@@ -0,0 +1,23 @@
{
"name": "vue-cli-2-example",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "vue-cli-service build",
"serve": "vue-cli-service serve"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
+8
View File
@@ -0,0 +1,8 @@
/// <reference types="cypress" />
const preprocessor = require('../../dist/plugins/webpack')
module.exports = (on, config) => {
preprocessor(on, config, require('@vue/cli-service/webpack.config'))
return config
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -0,0 +1,26 @@
/* eslint-env mocha,chai,jest */
import HelloWorld from './HelloWorld'
import { mount } from '@cypress/vue'
describe('hello', () => {
it('works!', () => {
mount(HelloWorld, {
propsData: {
msg: 'Hello World!',
},
})
cy.get('h1').contains('Hello World!')
})
it('works again', () => {
mount(HelloWorld, {
propsData: {
msg: 'Hello World Again!',
},
})
cy.get('h1').contains('Hello World Again!')
})
})
@@ -0,0 +1,62 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
* {
color: red;
}
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
@@ -0,0 +1,26 @@
/* eslint-env mocha,chai,jest */
import HelloWorld from './HelloWorld'
import { mount } from '@cypress/vue'
describe('Prettiest', () => {
it('spec works!', () => {
mount(HelloWorld, {
propsData: {
msg: 'Hello World!',
},
})
cy.get('h1').contains('Hello World!')
})
it('spec works again', () => {
mount(HelloWorld, {
propsData: {
msg: 'Hello World!',
},
})
cy.get('h1').contains('Hello World!')
})
})
@@ -0,0 +1,16 @@
/* eslint-env mocha,chai,jest */
import HelloWorld from '../HelloWorld'
import { mount } from '@cypress/vue'
describe('Pretty', () => {
it('spec works', () => {
mount(HelloWorld, {
propsData: {
msg: 'Hello World!',
},
})
cy.get('h1').contains('Hello World!')
})
})
+8
View File
@@ -0,0 +1,8 @@
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: (h) => h(App),
}).$mount('#app')
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -6,11 +6,10 @@
"scripts": {
"build": "tsc",
"build-prod": "yarn build",
"build:watch": "tsc --watch",
"cy:open": "node ../../scripts/cypress open",
"cy:run": "node ../../scripts/cypress run",
"pretest": "yarn build",
"test": "yarn cy:run"
"cy:open": "node ../../scripts/cypress.js open-ct --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js run-ct --project ${PWD}",
"test": "yarn cy:run",
"watch": "tsc -w"
},
"dependencies": {
"@babel/plugin-transform-modules-commonjs": "7.10.4",
@@ -19,17 +18,19 @@
"@intlify/vue-i18n-loader": "1.0.0",
"@vue/test-utils": "1.0.3",
"babel-plugin-istanbul": "6.0.0",
"debug": "4.3.1",
"debug": "4.3.2",
"find-webpack": "2.1.0",
"unfetch": "4.1.0"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.5",
"@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-service": "~4.4.0",
"axios": "0.19.2",
"babel-loader": "8.1.0",
"css-loader": "3.4.2",
"cypress": "*",
"cypress": "0.0.0-development",
"eslint-plugin-vue": "^6.2.2",
"mocha": "7.1.1",
"tailwindcss": "1.1.4",
+8 -9
View File
@@ -1,4 +1,5 @@
/// <reference types="cypress" />
import Vue from 'vue'
import {
createLocalVue,
mount as testUtilsMount,
@@ -14,8 +15,7 @@ const defaultOptions: (keyof MountOptions)[] = [
]
function checkMountModeEnabled () {
// @ts-ignore
if (Cypress.spec.specType !== 'component') {
if (!Cypress.spec.relative.includes('cypress/component')) {
throw new Error(
`In order to use mount or unmount functions please place the spec in component folder`,
)
@@ -70,17 +70,16 @@ const installMixins = (Vue, options) => {
}
}
// @ts-ignore
const hasStore = ({ store }: { store: object }) => store && store._vm
const hasStore = ({ store }: { store: any }) => store && store._vm // @ts-ignore
const forEachValue = (obj: object, fn: Function) => {
const forEachValue = <T>(obj: Record<string, T>, fn: (value: T, key: string) => void) => {
return Object.keys(obj).forEach((key) => fn(obj[key], key))
}
const resetStoreVM = (Vue, { store }) => {
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const wrappedGetters = store._wrappedGetters as Record<string, (store: any) => void>
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
@@ -120,13 +119,13 @@ type VueComponent = Vue.ComponentOptions<any> | Vue.VueConstructor
*
* @interface ComponentOptions
*/
interface ComponentOptions {}
type ComponentOptions = Record<string, unknown>
// local placeholder types
type VueLocalComponents = object
type VueLocalComponents = Record<string, VueComponent>
type VueFilters = {
[key: string]: Function
[key: string]: (value: string) => string
}
type VueMixin = unknown
+2 -2
View File
@@ -1,5 +1,5 @@
/// <reference types="cypress" />
const { onFileDefaultPreprocessor } = require('../../preprocessor/webpack')
const { startDevServer } = require('@cypress/webpack-dev-server')
/**
* Registers Cypress preprocessor for Vue component testing.
@@ -18,7 +18,7 @@ const { onFileDefaultPreprocessor } = require('../../preprocessor/webpack')
*/
const cypressPluginsFn = (on, config, webpackConfig) => {
require('@cypress/code-coverage/task')(on, config)
on('file:preprocessor', onFileDefaultPreprocessor(config, webpackConfig))
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }))
return config
}
-3
View File
@@ -1,3 +0,0 @@
const onFilePreprocessor = require('./webpack').onFilePreprocessor
module.exports = onFilePreprocessor
-197
View File
@@ -1,197 +0,0 @@
import webpack from 'webpack'
import util from 'util'
// Cypress webpack bundler adaptor
// https://github.com/cypress-io/cypress-webpack-preprocessor
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const debug = require('debug')('@cypress/vue')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
// const { VueLoaderPlugin } = require('vue-loader')
const fw = require('find-webpack')
// Preventing chunks because we don't serve static assets
function preventChunking (options = {}) {
if (options && options.optimization && options.optimization.splitChunks) {
delete options.optimization.splitChunks
}
options.plugins = options.plugins || []
options.plugins.push(
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1, // no chunks from dynamic imports -- includes the entry file
}),
)
return options
}
// Base 64 all the things because we don't serve static assets
function inlineUrlLoadedAssets (options = {}) {
const isUrlLoader = (use) => {
return use && use.loader && use.loader.indexOf('url-loader') > -1
}
const mergeUrlLoaderOptions = (use) => {
if (isUrlLoader(use)) {
use.options = use.options || {}
use.options.limit = Number.MAX_SAFE_INTEGER
}
return use
}
if (options.module && options.module.rules) {
options.module.rules = options.module.rules.map((rule) => {
if (Array.isArray(rule.use)) {
rule.use = rule.use.map(mergeUrlLoaderOptions)
}
return rule
})
}
return options
}
function compileTemplate (options = {}) {
options.resolve = options.resolve || {}
options.resolve.alias = options.resolve.alias || {}
options.resolve.alias['vue$'] = 'vue/dist/vue.esm.js'
}
/**
* Warning: modifies the input object
<<<<<<< HEAD
* @param {WebpackOptions} options
*/
function removeForkTsCheckerWebpackPlugin (options) {
if (!Array.isArray(options.plugins)) {
return
}
options.plugins = options.plugins.filter((plugin) => {
return plugin.typescript === undefined
})
}
/**
* Warning: modifies the input object
=======
>>>>>>> origin
* @param {Cypress.ConfigOptions} config
* @param {WebpackOptions} options
*/
function insertBabelLoader (config, options) {
const skipCodeCoverage = config && config.env && config.env.coverage === false
if (!options.devtool) {
options.devtool = '#eval-source-map'
}
const babelRule = {
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: [
// this plugin allows ES6 imports mocking
[
'@babel/plugin-transform-modules-commonjs',
{
loose: true,
},
],
],
},
}
if (skipCodeCoverage) {
debug('not adding code instrument plugin')
} else {
debug('adding code coverage plugin')
// this plugin instruments the loaded code
// which allows us to collect code coverage
const instrumentPlugin = [
'babel-plugin-istanbul',
{
// specify some options for NYC instrumentation here
// like tell it to instrument both JavaScript and Vue files
extension: ['.js', '.vue'],
},
]
babelRule.options.plugins.push(instrumentPlugin)
}
options.module = options.module || {}
options.module.rules = options.module.rules || []
options.module.rules.push(babelRule)
options.plugins = options.plugins || []
const pluginFound = options.plugins.find((plugin) => {
return (
plugin.constructor && plugin.constructor.name === VueLoaderPlugin.name
)
})
if (!pluginFound) {
debug('inserting VueLoaderPlugin')
options.plugins.push(new VueLoaderPlugin())
} else {
debug('found plugin VueLoaderPlugin already')
}
}
/**
* Basic Cypress Vue Webpack file loader for .vue files.
*/
const onFileDefaultPreprocessor = (config, webpackOptions = fw.getWebpackOptions()) => {
if (!webpackOptions) {
debug('Could not find webpack options, starting with default')
webpackOptions = {}
}
webpackOptions.mode = 'development'
inlineUrlLoadedAssets(webpackOptions)
preventChunking(webpackOptions)
compileTemplate(webpackOptions)
insertBabelLoader(config, webpackOptions)
// if I remove it, then get another message
// [VueLoaderPlugin Error] No matching use for vue-loader is found.
// removeForkTsCheckerWebpackPlugin(webpackOptions)
if (debug.enabled) {
console.error('final webpack')
console.error(util.inspect(webpackOptions, false, 2, true))
}
return webpackPreprocessor({
webpackOptions,
})
}
/**
* Custom Vue loader from the client projects that already have `webpack.config.js`
*
* @example
* const {
* onFilePreprocessor
* } = require('@cypress/vue/preprocessor/webpack')
* module.exports = on => {
* on('file:preprocessor', onFilePreprocessor('../path/to/webpack.config'))
* }
*/
const onFilePreprocessor = (webpackOptions) => {
if (typeof webpackOptions === 'string') {
// load webpack config from the given path
webpackOptions = require(webpackOptions)
}
return webpackPreprocessor({
webpackOptions,
})
}
module.exports = { onFilePreprocessor, onFileDefaultPreprocessor }
+14
View File
@@ -2,14 +2,24 @@
// The default for running tests in this project
// https://vue-loader.vuejs.org/guide/#manual-setup
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
mode: 'development',
output: {
path: path.join(__dirname, 'dist'),
filename: 'js/[name].js',
publicPath: '/',
chunkFilename: 'js/[name].js',
},
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
// point at the built file
'@cypress/vue': path.join(__dirname, 'dist'),
vue: 'vue/dist/vue.esm.js',
},
},
module: {
@@ -18,6 +28,10 @@ module.exports = {
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
+12
View File
@@ -0,0 +1,12 @@
# Webpack-ct
> **Note** this package is not meant to be used outside of cypress component testing.
## Responsibilities
- 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
+14
View File
@@ -0,0 +1,14 @@
version: 2.1
orbs:
node: circleci/node@1.1.6
jobs:
build:
executor:
name: node/default
tag: '12'
steps:
- checkout
- node/with-cache:
steps:
- run: yarn
- run: yarn test
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist')
+35
View File
@@ -0,0 +1,35 @@
{
"name": "@cypress/webpack-dev-server",
"version": "0.0.0-development",
"description": "Launches Webpack Dev Server for Component Testing",
"private": true,
"main": "index.js",
"scripts": {
"build": "tsc",
"build-prod": "tsc",
"test": "tsc && ts-mocha --config ./test/.mocharc.js --exit",
"watch": "tsc -w"
},
"dependencies": {
"debug": "4.3.2",
"semver": "^7.3.4",
"webpack-merge": "^5.4.0"
},
"devDependencies": {
"@types/webpack-dev-server": "^3.11.1",
"chai": "^4.2.0",
"mocha": "^8.1.3",
"ts-mocha": "8.0.0",
"typescript": "^3.9.7",
"webpack": "^4.44.2",
"webpack-dev-server": "^3.11.0"
},
"peerDependencies": {
"html-webpack-plugin": "> 3",
"webpack": "> 4"
},
"files": [
"dist"
],
"license": "MIT"
}
+36
View File
@@ -0,0 +1,36 @@
/*eslint-env browser,mocha*/
function appendTargetIfNotExists (id: string, tag = 'div', parent = document.body) {
let node = document.getElementById(id)
if (!node) {
node = document.createElement(tag)
node.setAttribute('id', id)
parent.appendChild(node)
}
node.innerHTML = ''
return node
}
export function init (importPromises, parent = (window.opener || window.parent)) {
const Cypress = (window as any).Cypress = parent.Cypress
if (!Cypress) {
throw new Error('Tests cannot run without a reference to Cypress!')
}
Cypress.onSpecWindow(window, importPromises)
Cypress.action('app:window:before:load', window)
beforeEach(() => {
const root = appendTargetIfNotExists('__cy_root')
root.appendChild(appendTargetIfNotExists('__cy_app'))
})
return {
restartRunner: Cypress.restartRunner,
}
}
+5
View File
@@ -0,0 +1,5 @@
function render () {
require('!!./loader.js!./browser.js')
}
render()
+42
View File
@@ -0,0 +1,42 @@
import { EventEmitter } from 'events'
import { debug as debugFn } from 'debug'
import { AddressInfo } from 'net'
import { Server } from 'http'
import { start as createDevServer } from './startServer'
const debug = debugFn('cypress:webpack-dev-server:webpack')
export interface DevServerOptions {
specs: Cypress.Cypress['spec'][]
config: {
supportFile: string
projectRoot: string
webpackDevServerPublicPathRoute: string
}
devServerEvents: EventEmitter
}
export interface StartDevServer {
/* this is the Cypress options object */
options: DevServerOptions
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
}
export interface ResolvedDevServerConfig {
port: number
server: Server
}
export async function startDevServer (startDevServerArgs: StartDevServer) {
const webpackDevServer = await createDevServer(startDevServerArgs)
return new Promise<ResolvedDevServerConfig>((resolve) => {
const httpSvr = webpackDevServer.listen(0, '127.0.0.1', () => {
// FIXME: handle address returning a string
const port = (httpSvr.address() as AddressInfo).port
debug('Component testing webpack server started on port', port)
resolve({ port, server: httpSvr })
})
})
}
+69
View File
@@ -0,0 +1,69 @@
/* global Cypress */
/// <reference types="cypress" />
import * as path from 'path'
import { CypressCTWebpackContext } from './plugin'
/**
* @param {ComponentSpec} file spec to create import string from.
* @param {string} filename name of the spec file - this is the same as file.name
* @param {string} chunkName webpack chunk name. eg: 'spec-0'
* @param {string} projectRoot absolute path to the project root. eg: /Users/<username>/my-app
*/
const makeImport = (file: Cypress.Cypress['spec'], filename: string, chunkName: string, projectRoot: string) => {
// If we want to rename the chunks, we can use this
const magicComments = chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''
return `"${filename}": {
shouldLoad: () => document.location.pathname.includes(${JSON.stringify(file.relative)}),
load: () => import(${JSON.stringify(path.resolve(projectRoot, file.relative))} ${magicComments}),
chunkName: "${chunkName}",
}`
}
/**
* Creates a object maping a spec file to an object mapping
* the spec name to the result of `makeImport`.
*
* @returns {Record<string, ReturnType<makeImport>}
* {
* "App.spec.js": {
* shouldLoad: () => document.location.pathname.includes("cypress/component/App.spec.js"),
* load: () => {
* return import("/Users/projects/my-app/cypress/component/App.spec.js" \/* webpackChunkName: "spec-0" *\/)
* },
* chunkName: "spec-0"
* }
* }
*/
function buildSpecs (projectRoot: string, files: Cypress.Cypress['spec'][] = []): string {
if (!Array.isArray(files)) return `{}`
return `{${files.map((f, i) => {
return makeImport(f, f.name, `spec-${i}`, projectRoot)
}).join(',')}}`
}
// Runs the tests inside the iframe
export default function loader (this: CypressCTWebpackContext) {
const { files, projectRoot, supportFile } = this._cypress
const supportFileAbsolutePath = supportFile ? JSON.stringify(path.resolve(projectRoot, supportFile)) : undefined
return `
var loadSupportFile = ${supportFile ? `() => import(${supportFileAbsolutePath})` : `() => Promise.resolve()`}
var allTheSpecs = ${buildSpecs(projectRoot, files)};
var { init } = require(${JSON.stringify(require.resolve('./aut-runner'))})
var scriptLoaders = Object.values(allTheSpecs).reduce(
(accSpecLoaders, specLoader) => {
if (specLoader.shouldLoad()) {
accSpecLoaders.push(specLoader.load)
}
return accSpecLoaders
}, [loadSupportFile])
init(scriptLoaders)
`
}
@@ -0,0 +1,55 @@
import { debug as debugFn } from 'debug'
import * as path from 'path'
import { Configuration } from 'webpack'
import { merge } from 'webpack-merge'
import CypressCTOptionsPlugin, { CypressCTOptionsPluginOptions } from './plugin'
const debug = debugFn('cypress:webpack-dev-server:makeWebpackConfig')
const mergePublicPath = (baseValue, userValue = '/') => {
return path.join(baseValue, userValue, '/')
}
interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions {
webpackDevServerPublicPathRoute: string
}
export async function makeWebpackConfig (userWebpackConfig: Configuration, options: MakeWebpackConfigOptions): Promise<Configuration> {
const { projectRoot, webpackDevServerPublicPathRoute, files, supportFile, devServerEvents } = options
debug(`User passed in webpack config with values %o`, userWebpackConfig)
const defaultWebpackConfig = require('./webpack.config')
debug(`Merging Evergreen's webpack config with users'`)
debug(`New webpack entries %o`, files)
debug(`Project root`, projectRoot)
debug(`Support file`, supportFile)
const entry = path.resolve(__dirname, './browser.js')
const publicPath = mergePublicPath(webpackDevServerPublicPathRoute, userWebpackConfig?.output?.publicPath)
const dynamicWebpackConfig = {
output: {
publicPath,
},
plugins: [
new CypressCTOptionsPlugin({
files,
projectRoot,
devServerEvents,
supportFile,
}),
],
}
const mergedConfig = merge<Configuration>(userWebpackConfig, defaultWebpackConfig, dynamicWebpackConfig)
mergedConfig.entry = entry
debug('Merged webpack config %o', mergedConfig)
return mergedConfig
}
+109
View File
@@ -0,0 +1,109 @@
import webpack, { Compiler, compilation, Plugin } from 'webpack'
import { EventEmitter } from 'events'
import _ from 'lodash'
import semver from 'semver'
import fs, { PathLike } from 'fs'
import path from 'path'
type UtimesSync = (path: PathLike, atime: string | number | Date, mtime: string | number | Date) => void
export interface CypressCTOptionsPluginOptions {
files: Cypress.Cypress['spec'][]
projectRoot: string
supportFile: string
devServerEvents?: EventEmitter
}
export interface CypressCTWebpackContext extends compilation.Compilation {
_cypress: CypressCTOptionsPluginOptions
}
export default class CypressCTOptionsPlugin implements Plugin {
private files: Cypress.Cypress['spec'][] = []
private supportFile: string
private errorEmitted = false
private readonly projectRoot: string
private readonly devServerEvents: EventEmitter
constructor (options: CypressCTOptionsPluginOptions) {
this.files = options.files
this.supportFile = options.supportFile
this.projectRoot = options.projectRoot
this.devServerEvents = options.devServerEvents
}
private pluginFunc = (context: CypressCTWebpackContext, module: compilation.Module) => {
context._cypress = {
files: this.files,
projectRoot: this.projectRoot,
supportFile: this.supportFile,
}
};
private setupCustomHMR = (compiler: webpack.Compiler) => {
compiler.hooks.afterCompile.tap(
'CypressCTOptionsPlugin',
(compilation: compilation.Compilation) => {
const stats = compilation.getStats()
if (stats.hasErrors()) {
this.errorEmitted = true
this.devServerEvents.emit('dev-server:compile:error', stats.toJson().errors[0])
} else if (this.errorEmitted) {
// compilation succeed but assets haven't emitted to the output yet
this.devServerEvents.emit('dev-server:compile:error', null)
}
},
)
compiler.hooks.afterEmit.tap(
'CypressCTOptionsPlugin',
(compilation: compilation.Compilation) => {
if (!compilation.getStats().hasErrors()) {
this.devServerEvents.emit('dev-server:compile:success')
}
},
)
}
/**
*
* @param compilation webpack 4 `compilation.Compilation`, webpack 5
* `Compilation`
*/
private plugin = (compilation: compilation.Compilation) => {
this.devServerEvents.on('dev-server:specs:changed', (specs) => {
if (_.isEqual(specs, this.files)) return
this.files = specs
const inputFileSystem = compilation.inputFileSystem
const utimesSync: UtimesSync = semver.gt('4.0.0', webpack.version) ? inputFileSystem.fileSystem.utimesSync : fs.utimesSync
utimesSync(path.resolve(__dirname, 'browser.js'), new Date(), new Date())
})
// Webpack 5
/* istanbul ignore next */
if ('NormalModule' in webpack) {
// @ts-ignore
webpack.NormalModule.getCompilationHooks(compilation).loader.tap(
'CypressCTOptionsPlugin',
this.pluginFunc,
)
return
}
// Webpack 4
compilation.hooks.normalModuleLoader.tap(
'CypressCTOptionsPlugin',
this.pluginFunc,
)
};
apply (compiler: Compiler): void {
this.setupCustomHMR(compiler)
compiler.hooks.compilation.tap('CypressCTOptionsPlugin', this.plugin)
}
}
+50
View File
@@ -0,0 +1,50 @@
import Debug from 'debug'
import webpack from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import { StartDevServer } from '.'
import { makeWebpackConfig } from './makeWebpackConfig'
const debug = Debug('cypress:webpack-dev-server:start')
export async function start ({ webpackConfig: userWebpackConfig, options }: StartDevServer): Promise<WebpackDevServer> {
if (!userWebpackConfig) {
debug('User did not pass in any webpack configuration')
}
const { projectRoot, webpackDevServerPublicPathRoute } = options.config
const webpackConfig = await makeWebpackConfig(userWebpackConfig || {}, {
files: options.specs,
projectRoot,
webpackDevServerPublicPathRoute,
devServerEvents: options.devServerEvents,
supportFile: options.config.supportFile,
})
debug('compiling webpack')
const compiler = webpack(webpackConfig)
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,
inline: false,
}
return new WebpackDevServer(compiler, webpackDevServerConfig)
}
@@ -0,0 +1,18 @@
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
optimization: {
splitChunks: {
chunks: 'all',
},
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [new HtmlWebpackPlugin({
template: path.join(__dirname, '../index-template.html'),
})],
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
spec: 'test/**/*.spec.ts',
}
+145
View File
@@ -0,0 +1,145 @@
import webpack from 'webpack'
import path from 'path'
import { expect } from 'chai'
import { EventEmitter } from 'events'
import http from 'http'
import fs from 'fs'
import { startDevServer } from '../'
const requestSpecFile = (port: number) => {
return new Promise((res) => {
const opts = {
host: 'localhost',
port,
path: '/test/fixtures/foo.spec.js',
}
const callback = (response: EventEmitter) => {
let str = ''
response.on('data', (chunk) => {
str += chunk
})
response.on('end', () => {
res(str)
})
}
http.request(opts, callback).end()
})
}
const root = path.join(__dirname, '..')
const webpackConfig: webpack.Configuration = {
output: {
path: root,
publicPath: root,
},
}
const specs: Cypress.Cypress['spec'][] = [
{
name: `${root}/test/fixtures/foo.spec.js`,
relative: `${root}/test/fixtures/foo.spec.js`,
absolute: `${root}/test/fixtures/foo.spec.js`,
},
]
const config = {
projectRoot: root,
webpackDevServerPublicPathRoute: root,
}
describe('#startDevServer', () => {
it('serves specs via a webpack dev server', async () => {
const { port, server } = await startDevServer({
webpackConfig,
options: {
config,
specs,
devServerEvents: new EventEmitter(),
},
})
const response = await requestSpecFile(port as number)
expect(response).to.eq('const foo = () => {}\n')
return new Promise((res) => {
server.close(() => res())
})
})
it('emits dev-server:compile:success event on successful compilation', async () => {
const devServerEvents = new EventEmitter()
const { server } = await startDevServer({
webpackConfig,
options: {
config,
specs,
devServerEvents,
},
})
return new Promise((res) => {
devServerEvents.on('dev-server:compile:success', () => {
server.close(() => res())
})
})
})
it('emits dev-server:compile:error event on error compilation', async () => {
const devServerEvents = new EventEmitter()
const { server } = await startDevServer({
webpackConfig,
options: {
config,
specs: [
{
name: `${root}/test/fixtures/compilation-fails.spec.js`,
relative: `${root}/test/fixtures/compilation-fails.spec.js`,
absolute: `${root}/test/fixtures/compilation-fails.spec.js`,
},
],
devServerEvents,
},
})
return new Promise((res) => {
devServerEvents.on('dev-server:compile:error', () => {
server.close(() => res())
})
})
})
it('touches browser.js when a spec file is added', async function () {
const devServerEvents = new EventEmitter()
const { server } = await startDevServer({
webpackConfig,
options: {
config,
specs,
devServerEvents,
},
})
const newSpec: Cypress.Cypress['spec'] = {
name: './some-newly-created-spec.js',
relative: './some-newly-created-spec.js',
absolute: '/some-newly-created-spec.js',
}
const oldmtime = fs.statSync('./dist/browser.js').mtimeMs
return new Promise((res) => {
devServerEvents.emit('dev-server:specs:changed', [newSpec])
const updatedmtime = fs.statSync('./dist/browser.js').mtimeMs
expect(oldmtime).to.not.equal(updatedmtime)
server.close(() => res())
})
})
})
@@ -0,0 +1 @@
this is an invalid spec file
+1
View File
@@ -0,0 +1 @@
const foo = () => {}
+53
View File
@@ -0,0 +1,53 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"skipLibCheck": true,
"lib": [
"es2015",
"dom"
] /* Specify library files to be included in the compilation: */,
"allowJs": true /* Allow javascript files to be compiled. */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist" /* Redirect output structure to the directory. */,
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": false /* Enable all strict type-checking options. */,
"noImplicitAny": false,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [] /* Type declaration files to be included in compilation. */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"esModuleInterop": true
},
"include": ["src"],
"exclude": ["*.js"],
"exclude": ["node_modules"]
}
@@ -6,7 +6,7 @@ exports['webpack preprocessor - e2e correctly preprocesses the file 1'] = `
exports['webpack preprocessor - e2e has less verbose syntax error 1'] = `
Webpack Compilation Error
.<path>/_test-output/syntax_error_spec.js
Module build failed (from /[root]/node_modules/babel-loader/lib/index.js):
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: <path>/_test-output/syntax_error_spec.js: Unexpected token (1:18)
> 1 | describe('fail', ->)
+1 -1
View File
@@ -23,7 +23,7 @@
},
"dependencies": {
"bluebird": "^3.7.1",
"debug": "^4.1.1",
"debug": "4.3.2",
"lodash": "^4.17.20"
},
"devDependencies": {
@@ -13,9 +13,7 @@ const { expect } = chai
const preprocessor = require('../../dist/index')
const normalizeErrMessage = (message) => {
return message
.replace(/\/\S+\/_test/g, '<path>/_test')
.split(path.resolve(process.cwd(), '../..')).join('/[root]')
return message.replace(/\/\S+\/_test/g, '<path>/_test')
}
const fixturesDir = path.join(__dirname, '..', 'fixtures')
+16 -9
View File
@@ -14,7 +14,6 @@
"build": "lerna run build --stream",
"build-prod": "lerna run build-prod --stream",
"bump": "node ./scripts/binary.js bump",
"check-next-dev-version": "node scripts/check-next-dev-version.js",
"check-node-version": "node scripts/check-node-version.js",
"check-terminal": "node scripts/check-terminal.js",
"clean": "lerna run clean --parallel",
@@ -34,6 +33,7 @@
"docker": "./scripts/run-docker-local.sh",
"effective:circle:config": "circleci config process circle.yml | sed /^#/d",
"ensure-deps": "./scripts/ensure-dependencies.sh",
"get-next-version": "node scripts/get-next-version.js",
"postinstall": "./scripts/run-if-not-ci.sh yarn build",
"jscodeshift": "jscodeshift -t ./node_modules/js-codemod/transforms/arrow-function-arguments.js",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json .",
@@ -41,7 +41,6 @@
"move-binaries": "node ./scripts/binary.js move-binaries",
"npm-release": "node scripts/npm-release.js",
"semantic-release": "node ./scripts/semantic-release.js",
"set-next-ci-version": "node ./scripts/binary.js setNextVersion",
"prestart": "yarn ensure-deps",
"start": "node $(yarn bin cypress) open --dev --global",
"stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude e2e.ts,cypress-tests.ts,unwritten.spec.ts",
@@ -58,7 +57,7 @@
"test-scripts": "mocha -r packages/ts/register --reporter spec 'scripts/unit/**/*spec.js'",
"test-scripts-watch": "yarn test-scripts --watch --watch-extensions 'ts,js'",
"pretest-unit": "yarn ensure-deps",
"test-unit": "lerna exec yarn test-unit --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"test-unit": "lerna exec yarn test-unit --ignore \"'{@packages/{desktop-gui,driver,root,static,web-config,net-stubbing,rewriter,ui-components},@cypress/{webpack-dev-server,eslint-plugin-dev}}'\"",
"pretest-watch": "yarn ensure-deps",
"test-watch": "lerna exec yarn test-watch --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"type-check": "node scripts/type_check",
@@ -87,7 +86,6 @@
"@types/chai-enzyme": "0.6.7",
"@types/classnames": "2.2.9",
"@types/debug": "4.1.5",
"@types/enzyme": "3.9.1",
"@types/enzyme-adapter-react-16": "1.0.5",
"@types/execa": "0.9.0",
"@types/fs-extra": "^8.0.1",
@@ -103,8 +101,8 @@
"@types/react-dom": "16.9.8",
"@types/request-promise": "4.1.45",
"@types/sinon-chai": "3.2.3",
"@typescript-eslint/eslint-plugin": "2.14.0",
"@typescript-eslint/parser": "2.14.0",
"@typescript-eslint/eslint-plugin": "3",
"@typescript-eslint/parser": "3",
"ansi-styles": "3.2.1",
"arg": "4.1.2",
"ascii-table": "0.0.9",
@@ -117,8 +115,10 @@
"chalk": "2.4.2",
"check-dependencies": "1.1.0",
"check-more-types": "2.24.0",
"commander": "6.2.1",
"common-tags": "1.8.0",
"debug": "4.3.1",
"conventional-recommended-bump": "6.1.0",
"debug": "4.3.2",
"del": "3.0.0",
"electron-builder": "22.9.1",
"electron-notarize": "1.0.0",
@@ -134,6 +134,7 @@
"find-package-json": "1.2.0",
"fs-extra": "8.1.0",
"gift": "0.10.2",
"glob": "7.1.6",
"globby": "10.0.1",
"got": "11.5.1",
"gulp": "4.0.2",
@@ -157,6 +158,7 @@
"listr": "0.14.3",
"lodash": "4.17.19",
"make-empty-github-commit": "1.2.0",
"minimist": "1.2.5",
"mocha": "3.5.3",
"mocha-banner": "1.1.2",
"mocha-junit-reporter": "2.0.0",
@@ -227,7 +229,9 @@
],
"nohoist": [
"**/@ffmpeg-installer",
"**/@ffmpeg-installer/**"
"**/@ffmpeg-installer/**",
"**/webpack-preprocessor/babel-loader",
"**/webpack-batteries-included-preprocessor/ts-loader"
]
},
"lint-staged": {
@@ -241,8 +245,11 @@
]
},
"resolutions": {
"**/@types/cheerio": "0.22.21",
"**/@types/enzyme": "3.10.5",
"**/@types/react": "16.9.50",
"**/jquery": "3.1.1",
"**/pretty-format": "26.4.0"
"**/pretty-format": "26.4.0",
"**/socket.io-parser": "4.0.2"
}
}
@@ -52,7 +52,7 @@ describe('src/cypress/script_utils', () => {
context('#runPromises', () => {
it('handles promises and doesnt try to fetch + eval manually', async () => {
const scriptsAsPromises = [Promise.resolve(), Promise.resolve()]
const scriptsAsPromises = [() => Promise.resolve(), () => Promise.resolve()]
const result = await $scriptUtils.runScripts({}, scriptsAsPromises)
expect(result).to.have.length(scriptsAsPromises.length)
+1 -1
View File
@@ -42,7 +42,7 @@
"cors": "2.8.5",
"cypress-multi-reporters": "1.4.0",
"dayjs": "^1.10.3",
"debug": "4.3.1",
"debug": "4.3.2",
"error-stack-parser": "2.0.6",
"errorhandler": "1.5.1",
"eventemitter2": "6.4.2",
@@ -352,6 +352,7 @@ module.exports = (Commands, Cypress, cy, state, config) => {
Cypress.on('test:before:run:async', () => {
// reset any state on the backend
// TODO: this is a bug in e2e it needs to be returned
return Cypress.backend('reset:server:state')
})
+15
View File
@@ -168,6 +168,21 @@ class $Cypress {
return this.runner.run(fn)
}
// Method to manually re-execute Runner (usually within $autIframe)
// used mainly by Component Testing
restartRunner () {
if (!window.top.Cypress) {
throw Error('Cannot re-run spec without Cypress')
}
// MobX state is only available on the Runner instance
// which is attached to the top level `window`
// We avoid infinite restart loop by checking if not in a loading state.
if (!window.top.Runner.state.isLoading) {
window.top.Runner.emit('restart')
}
}
// onSpecWindow is called as the spec window
// is being served but BEFORE any of the actual
// specs or support files have been downloaded
+6 -3
View File
@@ -38,9 +38,12 @@ const runScriptsFromUrls = (specWindow, scripts) => {
// Supports either scripts as objects or as async import functions
const runScripts = (specWindow, scripts) => {
// if scripts contains at least one promise
if (scripts.length && typeof scripts[0].then === 'function') {
// merge the awaiting of the promises
return Bluebird.all(scripts)
if (scripts.length && typeof scripts[0] === 'function') {
// chain the loading promises
// NOTE: since in evalScripts, scripts are evaluated in order,
// we chose to respect this constraint here too.
// indeed _.each goes through the array in order
return Bluebird.each(scripts, (script) => script())
}
return runScriptsFromUrls(specWindow, scripts)
+2 -2
View File
@@ -17,14 +17,14 @@
"dependencies": {
"@cypress/icons": "0.7.0",
"bluebird": "3.5.3",
"debug": "4.3.1",
"debug": "4.3.2",
"electron-packager": "14.1.1",
"fs-extra": "8.1.0",
"lodash": "4.17.19",
"minimist": "1.2.5"
},
"devDependencies": {
"electron": "11.2.1",
"electron": "11.2.2",
"execa": "4.1.0",
"mocha": "3.5.3"
},

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