feat: component testing (#14479)

Co-authored-by: Jessica Sachs <jess@jessicasachs.io>
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
Co-authored-by: Zach Bloomquist <github@chary.us>
Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
Co-authored-by: ElevateBart <ledouxb@gmail.com>
Co-authored-by: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
This commit is contained in:
Brian Mann
2021-02-04 15:45:16 -05:00
committed by GitHub
parent 0abb5efe90
commit af26fbebe6
394 changed files with 49255 additions and 9616 deletions

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": {

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
.vscode/tasks.json vendored Normal file
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": []
}
]
}

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,

View File

@@ -8,6 +8,9 @@ macBuildFilters: &macBuildFilters
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- include-electron-node-version
- sem-next-ver
defaults: &defaults
@@ -35,6 +38,8 @@ testBinaryFirefox: &testBinaryFirefox
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
@@ -642,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
@@ -893,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:
@@ -914,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:
@@ -1711,6 +1741,9 @@ linux-workflow: &linux-workflow
requires:
- build
- npm-webpack-dev-server:
requires:
- build
- npm-webpack-preprocessor:
requires:
- build
@@ -1808,6 +1841,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build
@@ -1826,6 +1861,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build
@@ -1838,6 +1875,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
@@ -1850,6 +1889,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-binary
@@ -1907,6 +1948,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- upload-npm-package
@@ -1917,6 +1960,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- upload-npm-package
@@ -1926,6 +1971,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
@@ -1936,6 +1983,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
@@ -1962,6 +2011,8 @@ linux-workflow: &linux-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- build-npm-package
@@ -2056,6 +2107,8 @@ mac-workflow: &mac-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package
@@ -2069,6 +2122,8 @@ mac-workflow: &mac-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package upload
@@ -2082,6 +2137,8 @@ mac-workflow: &mac-workflow
branches:
only:
- develop
- v6.0-release
- feat/component-testing
- sem-next-ver
requires:
- Mac NPM package upload

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:

View File

@@ -401,6 +401,7 @@ module.exports = {
.option('--global', text('global'))
.option('-p, --port <port>', text('port'))
.option('-P, --project <project-path>', text('project'))
.option('--run-project <project-path>', 'like `project`, but forces Cypress into runmode')
.option('--dev', text('dev'), coerceFalse)
.action((opts) => {
debug('opening Cypress')
@@ -422,6 +423,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')

View File

@@ -37,6 +37,10 @@ module.exports = {
if (isComponentTesting) {
args.push('--componentTesting')
if (options.runProject) {
args.push('--run-project', options.runProject)
}
}
debug('opening from options %j', options)

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

View File

@@ -220,6 +220,7 @@ const parseOpts = (opts) => {
'reporter',
'reporterOptions',
'record',
'runProject',
'spec',
'tag')

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",

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
cli/types/cy-http.d.ts vendored Normal file
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

View File

@@ -1,3 +1,4 @@
/// <reference path="./cy-http.d.ts" />
/// <reference path="./cypress-npm-api.d.ts" />
declare namespace Cypress {
@@ -5131,6 +5132,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
@@ -5139,6 +5157,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
}

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',
},
},
},

View File

@@ -5,6 +5,7 @@
"projectId": "z9dxah",
"testFiles": "**/*spec.{js,jsx,ts,tsx}",
"env": {
"reactDevtools": true,
"cypress-react-selector": {
"root": "#cypress-root"
}

View File

@@ -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()

View File

@@ -1,3 +0,0 @@
button {
height: 50px;
}

View File

@@ -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)')
})
})
})

View File

@@ -1,7 +0,0 @@
button.green {
background-color: #00ff00;
}
button.blue {
background-color: #0000ff;
}

View File

@@ -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)
})
})
})
})

View File

@@ -0,0 +1,5 @@
describe('Smoke Test', () => {
it('does not use the mount command', () => {
expect(true).to.eq(true)
})
})

View File

@@ -0,0 +1,23 @@
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)
})
})
})

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"
}
}

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
}

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

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/start.js --component-testing --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 open-ct --run-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",

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

View File

@@ -1,6 +1,3 @@
export * from './mount'
export * from './mountHook'
/** @deprecated */
export { default } from './mount'

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]

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')

View File

@@ -0,0 +1,6 @@
{
"plugins": [
"@babel/plugin-transform-modules-commonjs",
"babel-plugin-istanbul"
]
}

View File

@@ -2,6 +2,7 @@
"viewportWidth": 500,
"viewportHeight": 500,
"video": false,
"responseTimeout": 2500,
"projectId": "134ej7",
"testFiles": "**/*spec.js",
"experimentalComponentTesting": true,

View File

@@ -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

View File

@@ -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')

View File

@@ -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', () => {

View File

@@ -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)

View File

@@ -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)

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)
})
})
})

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
}

View File

@@ -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
npm/vue/examples/cli2/.gitignore vendored Normal file
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?

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/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
}

View File

@@ -0,0 +1 @@
// setup code goes here

View File

@@ -0,0 +1,7 @@
{
"experimentalComponentTesting": true,
"testFiles": "**/*.spec.*",
"pluginsFile": "./plugins.js",
"componentFolder": "./src",
"componentSupportFile": "./component-helpers.js"
}

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"
]
}

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

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>

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

View File

@@ -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!')
})
})

View File

@@ -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>

View File

@@ -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!')
})
})

View File

@@ -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!')
})
})

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

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/start.js --component-testing --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js open-ct --run-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",

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

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
}

View File

@@ -1,3 +0,0 @@
const onFilePreprocessor = require('./webpack').onFilePreprocessor
module.exports = onFilePreprocessor

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 }

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
{

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

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

View File

@@ -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>

View File

@@ -0,0 +1 @@
module.exports = require('./dist')

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"
}

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,
}
}

View File

@@ -0,0 +1,5 @@
function render () {
require('!!./loader.js!./browser.js')
}
render()

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 })
})
})
}

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)
`
}

View File

@@ -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
}

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)
}
}

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)
}

View File

@@ -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'),
})],
}

View File

@@ -0,0 +1,3 @@
module.exports = {
spec: 'test/**/*.spec.ts',
}

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())
})
})
})

View File

@@ -0,0 +1 @@
this is an invalid spec file

View File

@@ -0,0 +1 @@
const foo = () => {}

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"]
}

View File

@@ -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', ->)

View File

@@ -23,7 +23,7 @@
},
"dependencies": {
"bluebird": "^3.7.1",
"debug": "^4.1.1",
"debug": "4.3.2",
"lodash": "^4.17.20"
},
"devDependencies": {

View File

@@ -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')

View File

@@ -57,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",
@@ -86,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",
@@ -102,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",
@@ -116,9 +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",
"conventional-recommended-bump": "6.1.0",
"debug": "4.3.1",
"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"
}
}

View File

@@ -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)

View File

@@ -41,7 +41,7 @@
"compression": "1.7.4",
"cors": "2.8.5",
"cypress-multi-reporters": "1.4.0",
"debug": "4.3.1",
"debug": "4.3.2",
"error-stack-parser": "2.0.6",
"errorhandler": "1.5.1",
"eventemitter2": "6.4.2",

View File

@@ -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')
})

View File

@@ -169,6 +169,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

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)

View File

@@ -17,7 +17,7 @@
"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",

View File

@@ -1,19 +0,0 @@
const { client, circularParser } = require('@packages/socket/lib/browser')
const connect = (host, path, extraOpts = {}) => {
return client.connect(host, {
path,
transports: ['websocket'],
// @ts-ignore
parser: circularParser,
...extraOpts,
})
}
module.exports = {
connect,
socketIoClient: client,
socketIoParser: circularParser,
}

View File

@@ -0,0 +1,9 @@
import { client } from '@packages/socket/lib/browser'
export const connect = (host, path, extraOpts = {}) => {
return client.io(host, {
path,
transports: ['websocket'],
...extraOpts,
})
}

View File

@@ -1,15 +1,11 @@
const fs = require('fs-extra')
const pkg = require('./package.json')
const gulp = require('gulp')
const rimraf = require('rimraf')
const source = require('vinyl-source-stream')
const browserify = require('browserify')
const cypressIcons = require('@cypress/icons')
import fs from 'fs-extra'
import gulp from 'gulp'
import rimraf from 'rimraf'
import webpack from 'webpack'
import cypressIcons from '@cypress/icons'
import webpackConfig from './webpack.config.js'
const copySocketClient = () => {
return gulp.src(require('../socket').getPathToClientSource())
.pipe(gulp.dest('dist'))
}
const pkg = require('./package.json')
const clean = (done) => {
rimraf('dist', done)
@@ -29,13 +25,10 @@ const manifest = (done) => {
return null
}
const background = () => {
return browserify({
entries: 'app/init.js',
})
.bundle()
.pipe(source('background.js'))
.pipe(gulp.dest('dist'))
const background = (cb) => {
const compiler = webpack(webpackConfig as webpack.Configuration)
compiler.run(cb)
}
const html = () => {
@@ -69,7 +62,6 @@ const logos = () => {
const build = gulp.series(
clean,
gulp.parallel(
copySocketClient,
icons,
logos,
manifest,

View File

@@ -11,9 +11,9 @@
"postinstall": "echo '@packages/extension needs: yarn build'",
"test": "yarn test-unit",
"test-debug": "yarn test-unit --inspect-brk=5566",
"test-unit": "cross-env NODE_ENV=test mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json",
"test-unit": "cross-env NODE_ENV=test mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json",
"test-watch": "yarn test-unit --watch",
"watch": "gulp watch"
"watch": "webpack --watch --progress"
},
"dependencies": {
"bluebird": "3.5.3",
@@ -22,7 +22,6 @@
"devDependencies": {
"@cypress/icons": "0.7.0",
"@packages/socket": "0.0.0-development",
"browserify": "16.3.0",
"chai": "3.5.0",
"coffeescript": "1.12.7",
"cross-env": "6.0.3",
@@ -36,8 +35,9 @@
"rimraf": "3.0.2",
"sinon": "7.3.2",
"sinon-chai": "3.3.0",
"vinyl-source-stream": "2.0.0",
"webextension-polyfill": "0.4.0"
"ts-loader": "8.0.13",
"webextension-polyfill": "0.4.0",
"webpack": "4.44.2"
},
"files": [
"app",

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noImplicitAny": false,
"importHelpers": true,
"strict": false
}
}

View File

@@ -0,0 +1,22 @@
const path = require('path')
module.exports = {
mode: process.env.NODE_ENV || 'development',
entry: './app/init.js',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'background.js',
path: path.resolve(__dirname, 'dist'),
},
}

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