mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-25 08:29:06 -06:00
Merge remote-tracking branch 'origin/develop' into 7.0-release
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,6 +39,9 @@ packages/server/test/support/fixtures/server/libs
|
||||
/npm/react/bin/*
|
||||
/npm/react/cypress/videos
|
||||
|
||||
# from runner-ct
|
||||
/packages/runner-ct/cypress/screenshots
|
||||
|
||||
# Building app binary
|
||||
scripts/support
|
||||
package-lock.json
|
||||
|
||||
@@ -198,6 +198,15 @@ commands:
|
||||
PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \
|
||||
PERCY_PARALLEL_TOTAL=-1 \
|
||||
$cmd yarn workspace @packages/runner-ct run cypress:run --browser <<parameters.browser>>
|
||||
- run:
|
||||
command: |
|
||||
if [[ <<parameters.percy>> == 'true' ]]; then
|
||||
PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \
|
||||
PERCY_PARALLEL_TOTAL=-1 \
|
||||
yarn percy upload packages/runner-ct/cypress/screenshots/screenshot.spec.tsx/percy
|
||||
else
|
||||
echo "skipping percy screenshots uploading"
|
||||
fi
|
||||
- store_test_results:
|
||||
path: /tmp/cypress
|
||||
- store_artifacts:
|
||||
|
||||
@@ -415,14 +415,25 @@ module.exports = {
|
||||
.command('run-ct', { hidden: true })
|
||||
.usage('[options]')
|
||||
.description('Runs all Cypress Component Testing suites')
|
||||
.option('-b, --browser <browser-path>', text('browserOpenMode'))
|
||||
.option('-b, --browser <browser-name-or-path>', text('browserRunMode'))
|
||||
.option('--ci-build-id <id>', text('ciBuildId'))
|
||||
.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('--group <name>', text('group'))
|
||||
.option('-k, --key <record-key>', text('key'))
|
||||
.option('--headed', text('headed'))
|
||||
.option('--headless', text('headless'))
|
||||
.option('--no-exit', text('exit'))
|
||||
.option('--parallel', text('parallel'))
|
||||
.option('-p, --port <port>', text('port'))
|
||||
.option('-P, --project <project-path>', text('project'))
|
||||
.option('-q, --quiet', text('quiet'))
|
||||
.option('--record [bool]', text('record'), coerceFalse)
|
||||
.option('-r, --reporter <reporter>', text('reporter'))
|
||||
.option('-o, --reporter-options <reporter-options>', text('reporterOptions'))
|
||||
.option('-s, --spec <spec>', text('spec'))
|
||||
.option('-t, --tag <tag>', text('tag'))
|
||||
.option('--dev', text('dev'), coerceFalse)
|
||||
.action((opts) => {
|
||||
debug('running Cypress run-ct')
|
||||
|
||||
2
cli/types/cypress.d.ts
vendored
2
cli/types/cypress.d.ts
vendored
@@ -5412,6 +5412,8 @@ declare namespace Cypress {
|
||||
/** Override *name* for display purposes only */
|
||||
displayName: string
|
||||
message: any
|
||||
/** Set to false if you want to control the finishing of the command in the log yourself */
|
||||
autoEnd: boolean
|
||||
/** Return an object that will be printed in the dev tools console */
|
||||
consoleProps(): ObjectLike
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"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.2",
|
||||
@@ -37,6 +36,7 @@
|
||||
"@babel/preset-typescript": "7.10.4",
|
||||
"@bahmutov/cy-api": "1.4.2",
|
||||
"@bahmutov/cy-rollup": "2.0.0",
|
||||
"@cypress/webpack-dev-server": "0.0.0-development",
|
||||
"@date-io/date-fns": "1",
|
||||
"@emotion/babel-preset-css-prop": "10.0.27",
|
||||
"@emotion/core": "10.0.22",
|
||||
|
||||
@@ -12,26 +12,26 @@
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "7.10.4",
|
||||
"@cypress/code-coverage": "3.8.1",
|
||||
"@cypress/webpack-preprocessor": "0.0.0-development",
|
||||
"@intlify/vue-i18n-loader": "1.0.0",
|
||||
"@cypress/webpack-dev-server": "0.0.0-development",
|
||||
"@vue/test-utils": "1.0.3",
|
||||
"babel-plugin-istanbul": "6.0.0",
|
||||
"debug": "4.3.2",
|
||||
"find-webpack": "2.1.0",
|
||||
"unfetch": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.9.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.10.4",
|
||||
"@babel/preset-env": "7.9.5",
|
||||
"@intlify/vue-i18n-loader": "1.0.0",
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-service": "~4.4.0",
|
||||
"axios": "0.19.2",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-plugin-istanbul": "6.0.0",
|
||||
"css-loader": "3.4.2",
|
||||
"cypress": "0.0.0-development",
|
||||
"debug": "4.3.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"find-webpack": "2.1.0",
|
||||
"mocha": "7.1.1",
|
||||
"tailwindcss": "1.1.4",
|
||||
"typescript": "3.9.6",
|
||||
|
||||
@@ -13,7 +13,7 @@ export function measureWebpackPerformance (webpackConfig: webpack.Configuration)
|
||||
const compareWithPrevious = process.env.WEBPACK_PERF_MEASURE_COMPARE
|
||||
|
||||
function percentageDiff (a: number, b: number) {
|
||||
return 100 * (a - b) / ((a + b) / 2)
|
||||
return ((a - b) / a) * 100
|
||||
}
|
||||
|
||||
const compareOutput = (output: string) => {
|
||||
@@ -31,7 +31,10 @@ export function measureWebpackPerformance (webpackConfig: webpack.Configuration)
|
||||
const delimiter = new Array(process.stdout.columns).fill('═').join('')
|
||||
|
||||
console.log(delimiter)
|
||||
console.log(`${chalk.bold('WEBPACK_PERF_MEASURE:')} ${result}`)
|
||||
console.log(`${chalk.bold('WEBPACK_PERF_MEASURE')}`)
|
||||
console.log(`Before: ${chalk.bold(oldStats.misc.compileTime / 1000)}s`)
|
||||
console.log(`After: ${chalk.bold(newStats.misc.compileTime / 1000)}s`)
|
||||
console.log(result)
|
||||
console.log(delimiter)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cypress",
|
||||
"version": "6.4.0",
|
||||
"version": "6.5.0",
|
||||
"description": "Cypress.io end to end testing tool",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -163,6 +163,7 @@
|
||||
"mocha-junit-reporter": "2.0.0",
|
||||
"mocha-multi-reporters": "1.1.7",
|
||||
"mock-fs": "4.9.0",
|
||||
"odiff-bin": "2.1.0",
|
||||
"parse-github-repo-url": "1.4.1",
|
||||
"patch-package": "6.2.2",
|
||||
"percy": "0.26.9",
|
||||
|
||||
@@ -29,7 +29,6 @@ const createCommand = (props: Partial<CommandProps> = {}) => {
|
||||
testId: 'r3',
|
||||
timeout: 4000,
|
||||
wallClockStartedAt: new Date().toString(),
|
||||
|
||||
} as CommandProps
|
||||
|
||||
return _.defaults(props, defaults)
|
||||
@@ -262,6 +261,19 @@ describe('Test model', () => {
|
||||
test.updateLog(createCommand({ timeout: 6000 }))
|
||||
expect(test.lastAttempt.commands[0].timeout).to.equal(6000)
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/14978
|
||||
it('does not change test state based on log state', () => {
|
||||
const test = createTest()
|
||||
|
||||
test.addLog(createCommand({ state: 'active' }))
|
||||
expect(test.lastAttempt.commands[0].state).to.equal('active')
|
||||
expect(test.state).to.equal('processing')
|
||||
|
||||
test.updateLog(createCommand({ state: 'failed' }))
|
||||
expect(test.lastAttempt.commands[0].state).to.equal('failed')
|
||||
expect(test.state).to.equal('processing')
|
||||
})
|
||||
})
|
||||
|
||||
context('#removeLog', () => {
|
||||
|
||||
@@ -109,15 +109,11 @@ export default class Attempt {
|
||||
}
|
||||
}
|
||||
|
||||
@action updateLog (props: LogProps) {
|
||||
updateLog (props: LogProps) {
|
||||
const log = this._logs[props.id]
|
||||
|
||||
if (log) {
|
||||
log.update(props)
|
||||
|
||||
if (log.state === 'failed') {
|
||||
this._state = 'failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import Runnables from './runnables/runnables'
|
||||
|
||||
interface BaseReporterProps {
|
||||
appState: AppState
|
||||
className?: string
|
||||
runnablesStore: RunnablesStore
|
||||
runner: Runner
|
||||
scroller: Scroller
|
||||
@@ -78,6 +79,7 @@ class Reporter extends Component<SingleReporterProps | MultiReporterProps> {
|
||||
render () {
|
||||
const {
|
||||
appState,
|
||||
className,
|
||||
runMode,
|
||||
runnablesStore,
|
||||
scroller,
|
||||
@@ -89,7 +91,7 @@ class Reporter extends Component<SingleReporterProps | MultiReporterProps> {
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={cs('reporter', {
|
||||
<div className={cs(className, 'reporter', {
|
||||
multiSpecs: runMode === 'multi',
|
||||
'experimental-studio-enabled': experimentalStudioEnabled,
|
||||
'studio-active': appState.studioActive,
|
||||
|
||||
@@ -168,7 +168,7 @@ export default class Test extends Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
if (props.err) {
|
||||
if (props.err || props.state) {
|
||||
this._withAttempt(this.currentRetry, (attempt: Attempt) => {
|
||||
attempt.update(props)
|
||||
})
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
{}
|
||||
{
|
||||
"testFiles": "**/*spec.{ts,tsx}",
|
||||
"video": false
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('RunnerCt', () => {
|
||||
|
||||
cy.get('[data-cy=resizer]').trigger('mouseup', 'center')
|
||||
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '429px')
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '435px')
|
||||
})
|
||||
|
||||
it('restore specs list width after closing and reopen', () => {
|
||||
@@ -117,14 +117,14 @@ describe('RunnerCt', () => {
|
||||
})
|
||||
|
||||
cy.get('[data-cy=resizer]').trigger('mouseup', 'center')
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '479px')
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '485px')
|
||||
|
||||
cy.get('[aria-label="Open the menu"').click()
|
||||
assertSpecsListIs('closed')
|
||||
|
||||
cy.get('[aria-label="Open the menu"').click()
|
||||
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '479px')
|
||||
cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '485px')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -188,5 +188,16 @@ describe('SpecList', () => {
|
||||
.parent()
|
||||
.should('be.focused')
|
||||
})
|
||||
|
||||
it('Allows to navigate between files when spec list is searched', () => {
|
||||
cy.get('input').type('bar')
|
||||
cy.realPress('ArrowDown')
|
||||
|
||||
cy
|
||||
.get('[role=radio]')
|
||||
.contains('bar.js')
|
||||
.parent()
|
||||
.should('be.focused')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
67
packages/runner-ct/cypress/component/screenshot.spec.tsx
Normal file
67
packages/runner-ct/cypress/component/screenshot.spec.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
|
||||
const styles = `
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
#wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
#body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#header, #footer {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: silver;
|
||||
}
|
||||
`
|
||||
|
||||
const Layout: React.FC = () => {
|
||||
return (
|
||||
<div id='wrapper'>
|
||||
<div id='header'>Header</div>
|
||||
<div id='body'>Body</div>
|
||||
<div id='footer'>Footer</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
describe('screenshot', () => {
|
||||
it('takes a standard screenshot', () => {
|
||||
cy.viewport(500, 500)
|
||||
mount(<Layout />, {
|
||||
styles,
|
||||
})
|
||||
|
||||
cy.screenshot('percy/component_testing_takes_a_screenshot')
|
||||
})
|
||||
|
||||
it('takes a screenshot with a custom viewport', () => {
|
||||
cy.viewport(750, 750)
|
||||
mount(<Layout />, {
|
||||
styles,
|
||||
})
|
||||
|
||||
cy.screenshot('percy/component_testing_screenshot_custom_viewport_screenshot')
|
||||
})
|
||||
|
||||
// TODO: This will technically pass, but the screenshot is not correct.
|
||||
// AUT transform appears to be buggy for extreme viewports.
|
||||
xit('screenshot with a really long viewport', () => {
|
||||
cy.viewport(200, 2000)
|
||||
mount(<Layout />, {
|
||||
styles,
|
||||
})
|
||||
|
||||
cy.screenshot('percy/component_testing_screenshot_long_viewport')
|
||||
})
|
||||
})
|
||||
@@ -22,6 +22,7 @@ function injectStylesInlineForPercyInPlace (webpackConfig) {
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
on('task', percyHealthCheck)
|
||||
|
||||
on('dev-server:start', (options) => {
|
||||
/** @type {import('webpack').Configuration} */
|
||||
const { default: webpackConfig } = require(path.resolve(__dirname, '..', '..', 'webpack.config.ts'))
|
||||
|
||||
@@ -24,8 +24,8 @@ export const SpecList: React.FC<SpecsListProps> = observer((props) => {
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const selectSpecByIndex = (index: number) => {
|
||||
const spec = typeof index !== 'number' || index < 0
|
||||
? props.specs[0]
|
||||
: props.specs[index]
|
||||
? filteredSpecs[0]
|
||||
: filteredSpecs[index]
|
||||
|
||||
const specElement = document.querySelector(`[data-spec="${spec.relative}"]`) as HTMLDivElement
|
||||
|
||||
@@ -34,7 +34,7 @@ export const SpecList: React.FC<SpecsListProps> = observer((props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectedSpecIndex = props.specs.findIndex((spec) =>
|
||||
const selectedSpecIndex = filteredSpecs.findIndex((spec) =>
|
||||
spec.relative === (document.activeElement as HTMLElement)?.dataset?.spec)
|
||||
|
||||
if (e.key === 'ArrowUp') {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import './SearchSpec.scss'
|
||||
|
||||
interface SearchSpecProps extends React.RefAttributes<HTMLInputElement> {
|
||||
@@ -8,12 +7,6 @@ interface SearchSpecProps extends React.RefAttributes<HTMLInputElement> {
|
||||
}
|
||||
|
||||
export const SearchSpec: React.FC<SearchSpecProps> = React.forwardRef((props, ref) => {
|
||||
// const ignoreSlashInput
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => hotkeys.unbind('/')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="specs-list-search-input-container">
|
||||
<input
|
||||
|
||||
65
packages/runner-ct/src/app/NoSpecSelected.scss
Normal file
65
packages/runner-ct/src/app/NoSpecSelected.scss
Normal file
@@ -0,0 +1,65 @@
|
||||
.no-spec {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
color: #555;
|
||||
font-family: "Muli", "Helvetica Neue", "Arial", sans-serif;
|
||||
font-size: 13px;
|
||||
|
||||
.no-spec-content-container {
|
||||
display: flex;
|
||||
flex-basis: 45%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
color: #3386D4;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.no-spec-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-spec-custom-children {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.keyboard-helper {
|
||||
width: 224px;
|
||||
.keyboard-shortcut {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
height: 23px;
|
||||
justify-content: space-between;
|
||||
|
||||
.shortcut {
|
||||
display: flex;
|
||||
|
||||
.key {
|
||||
display: flex;
|
||||
font-family: sans-serif; // display keys symbols correctly
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
height: 23px;
|
||||
min-width: 23px;
|
||||
margin-right: 4px;
|
||||
padding: 0px 4px;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
packages/runner-ct/src/app/NoSpecSelected.tsx
Normal file
53
packages/runner-ct/src/app/NoSpecSelected.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react'
|
||||
import './NoSpecSelected.scss'
|
||||
|
||||
const KeyboardShortcut: React.FC<{ shortcut: string[], description: string }> = ({
|
||||
shortcut,
|
||||
description,
|
||||
}) => {
|
||||
const metaSymbol = window.navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'
|
||||
|
||||
return (
|
||||
<li className="keyboard-shortcut">
|
||||
<p> {description} </p>
|
||||
<div className="shortcut">
|
||||
{shortcut.map((key) => (
|
||||
<div className="key"> {key === 'Meta' ? metaSymbol : key} </div>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export const KeyboardHelper = () => {
|
||||
return (
|
||||
<ul className="keyboard-helper">
|
||||
<KeyboardShortcut shortcut={['/']} description="Search spec" />
|
||||
<KeyboardShortcut shortcut={['Meta', 'B']} description="Toggle specs list" />
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
interface NoSpecSelectedProps {
|
||||
onSelectSpecRequest: () => void;
|
||||
}
|
||||
|
||||
export const NoSpecSelected: React.FC<NoSpecSelectedProps> = ({ onSelectSpecRequest, children }) => {
|
||||
return (
|
||||
<div className="no-spec">
|
||||
<div className="no-spec-content-container">
|
||||
<svg width="40" height="50" viewBox="0 0 40 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.3333 49H6.66659C5.2521 49 3.89554 48.4381 2.89535 47.4379C1.89515 46.4377 1.33325 45.0812 1.33325 43.6667V6.33333C1.33325 4.91885 1.89515 3.56229 2.89535 2.5621C3.89554 1.5619 5.2521 1 6.66659 1H21.5626C22.2698 1.00015 22.9479 1.2812 23.4479 1.78133L37.8853 16.2187C38.3854 16.7186 38.6664 17.3968 38.6666 18.104V43.6667C38.6666 45.0812 38.1047 46.4377 37.1045 47.4379C36.1043 48.4381 34.7477 49 33.3333 49Z" stroke="#B4B5BC" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<h2 className="no-spec-title"> No spec selected. </h2>
|
||||
<a onClick={onSelectSpecRequest}> Select Spec </a>
|
||||
|
||||
{children && (
|
||||
<div className="no-spec-custom-children">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,15 @@ import { observer } from 'mobx-react'
|
||||
import { ReporterHeaderProps } from '@packages/reporter/src/header/header'
|
||||
import Stats from '@packages/reporter/src/header/stats'
|
||||
import Controls from '@packages/reporter/src/header/controls'
|
||||
import { StatsStore } from '@packages/reporter/src/header/stats-store'
|
||||
|
||||
export const EmptyReporterHeader: React.FC = () => {
|
||||
return (
|
||||
<header>
|
||||
<Stats stats={{ numPassed: 0, numFailed: 0, numPending: 0, duration: 0 } as StatsStore} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export const ReporterHeader: React.FC<ReporterHeaderProps> = observer(
|
||||
function ReporterHeader ({ statsStore, appState }) {
|
||||
|
||||
@@ -12,6 +12,10 @@ main.app-ct {
|
||||
font-size: $font-size;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
z-index: 2;
|
||||
:hover {
|
||||
@@ -32,11 +36,13 @@ main.app-ct {
|
||||
box-shadow: $box-shadow-closest;
|
||||
transition: ease-in-out 0.2s;
|
||||
padding-left: $specs-list-padding;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.specs-list-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 20px);
|
||||
height: calc(100vh);
|
||||
padding-top: $specs-list-padding;
|
||||
box-sizing: border-box;
|
||||
|
||||
.specs-list-focus-container {
|
||||
width: 100%;
|
||||
@@ -68,6 +74,14 @@ main.app-ct {
|
||||
margin-inline: $specs-list-offset;
|
||||
}
|
||||
|
||||
.app-wrapper-screenshotting {
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
.screenshotting {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.runner-ct {
|
||||
left: 0;
|
||||
|
||||
@@ -136,14 +150,13 @@ main.app-ct {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// styles for react-split-pane and custom ResizableBox
|
||||
.Resizer {
|
||||
$resize-thickness: 12px;
|
||||
$resize-thickness: 11px;
|
||||
border: 5px solid transparent;
|
||||
margin: -5px;
|
||||
|
||||
background: transparent;
|
||||
background: #ddd;
|
||||
transition: background-color .3s ease-in-out;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cs from 'classnames'
|
||||
import { observer } from 'mobx-react'
|
||||
import * as React from 'react'
|
||||
|
||||
import { Reporter } from '@packages/reporter/src/main'
|
||||
|
||||
import errorMessages from '../errors/error-messages'
|
||||
@@ -10,7 +11,7 @@ import SplitPane from 'react-split-pane'
|
||||
import Header from '../header/header'
|
||||
import Iframes from '../iframe/iframes'
|
||||
import Message from '../message/message'
|
||||
import { ReporterHeader } from './ReporterHeader'
|
||||
import { EmptyReporterHeader, ReporterHeader } from './ReporterHeader'
|
||||
import EventManager from '../lib/event-manager'
|
||||
import { Hidden } from '../lib/Hidden'
|
||||
import { SpecList } from '../SpecList'
|
||||
@@ -20,6 +21,8 @@ import { useWindowSize } from '../lib/useWindowSize'
|
||||
import { useGlobalHotKey } from '../lib/useHotKey'
|
||||
|
||||
import './RunnerCt.scss'
|
||||
import { KeyboardHelper, NoSpecSelected } from './NoSpecSelected'
|
||||
import { useScreenshotHandler } from './useScreenshotHandler'
|
||||
|
||||
// Cypress.ConfigOptions only appears to have internal options.
|
||||
// TODO: figure out where the "source of truth" should be for
|
||||
@@ -41,6 +44,7 @@ const VIEWPORT_SIDE_MARGIN = 40 + 17
|
||||
const App: React.FC<AppProps> = observer(
|
||||
function App (props: AppProps) {
|
||||
const searchRef = React.useRef<HTMLInputElement>(null)
|
||||
const splitPaneRef = React.useRef<{ splitPane: HTMLDivElement }>(null)
|
||||
const pluginRootContainer = React.useRef<null | HTMLDivElement>(null)
|
||||
|
||||
const { state, eventManager, config } = props
|
||||
@@ -85,11 +89,13 @@ const App: React.FC<AppProps> = observer(
|
||||
monitorWindowResize()
|
||||
}, [])
|
||||
|
||||
useGlobalHotKey('ctrl+b,command+b', () => {
|
||||
setIsSpecsListOpen((isOpenNow) => !isOpenNow)
|
||||
useScreenshotHandler({
|
||||
state,
|
||||
eventManager,
|
||||
splitPaneRef,
|
||||
})
|
||||
|
||||
useGlobalHotKey('/', () => {
|
||||
function focusSpecsList () {
|
||||
setIsSpecsListOpen(true)
|
||||
|
||||
// a little trick to focus field on the next tick of event loop
|
||||
@@ -97,8 +103,14 @@ const App: React.FC<AppProps> = observer(
|
||||
setTimeout(() => {
|
||||
searchRef.current?.focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
useGlobalHotKey('ctrl+b,command+b', () => {
|
||||
setIsSpecsListOpen((isOpenNow) => !isOpenNow)
|
||||
})
|
||||
|
||||
useGlobalHotKey('/', focusSpecsList)
|
||||
|
||||
function onSplitPaneChange (newWidth: number) {
|
||||
setLeftSideOfSplitPaneWidth(newWidth)
|
||||
state.updateWindowDimensions({
|
||||
@@ -113,7 +125,12 @@ const App: React.FC<AppProps> = observer(
|
||||
<>
|
||||
<main className="app-ct">
|
||||
<div
|
||||
className="specs-list-drawer"
|
||||
className={cs(
|
||||
'specs-list-drawer',
|
||||
{
|
||||
'display-none': state.screenshotting,
|
||||
},
|
||||
)}
|
||||
style={{
|
||||
transform: isSpecsListOpen ? `translateX(0)` : `translateX(-${drawerWidth - 20}px)`,
|
||||
}}
|
||||
@@ -147,34 +164,43 @@ const App: React.FC<AppProps> = observer(
|
||||
/>
|
||||
</ResizableBox>
|
||||
</div>
|
||||
<div className="app-wrapper">
|
||||
<div className={cs('app-wrapper', { 'app-wrapper-screenshotting': state.screenshotting })}>
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
primary="first"
|
||||
minSize={100}
|
||||
ref={splitPaneRef}
|
||||
minSize={state.screenshotting ? 0 : 100}
|
||||
// calculate maxSize of IFRAMES preview to not cover specs list and command log
|
||||
maxSize={400}
|
||||
defaultSize={355}
|
||||
maxSize={state.screenshotting ? 0 : 400}
|
||||
defaultSize={state.screenshotting ? 0 : 355}
|
||||
onDragStarted={() => setIsResizing(true)}
|
||||
onDragFinished={() => setIsResizing(false)}
|
||||
onChange={onSplitPaneChange}
|
||||
// For some reason on each dom snapshot restoring the viewport is jumping up to 4 pixels
|
||||
// It causes a weird white line in the bottom, so here is a fix which is not ideal
|
||||
paneStyle={{ height: 'calc(100vh + 4px)' }}
|
||||
className={cs('reporter-pane', { 'is-reporter-resizing': isResizing })}
|
||||
>
|
||||
<div>
|
||||
{state.spec && (
|
||||
<div style={{ height: '100%' }}>
|
||||
{state.spec ? (
|
||||
<Reporter
|
||||
runMode={state.runMode}
|
||||
runner={eventManager.reporterBus}
|
||||
className={cs({ 'display-none': state.screenshotting })}
|
||||
spec={state.spec}
|
||||
specRunId={state.specRunId}
|
||||
allSpecs={state.multiSpecs}
|
||||
// @ts-ignore
|
||||
error={errorMessages.reporterError(state.scriptError, state.spec.relative)}
|
||||
firefoxGcInterval={config.firefoxGcInterval}
|
||||
resetStatsOnSpecChange={state.runMode === 'single'}
|
||||
renderReporterHeader={(props) => <ReporterHeader {...props} />}
|
||||
experimentalStudioEnabled={false}
|
||||
/>
|
||||
) : (
|
||||
<div className="reporter">
|
||||
<EmptyReporterHeader />
|
||||
<NoSpecSelected onSelectSpecRequest={focusSpecsList} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SplitPane
|
||||
@@ -191,9 +217,15 @@ const App: React.FC<AppProps> = observer(
|
||||
: state.isAnyPluginToShow ? 30 : 0
|
||||
}
|
||||
>
|
||||
<div className="runner runner-ct container">
|
||||
<div className={cs('runner runner-ct container', { screenshotting: state.screenshotting })}>
|
||||
<Header {...props} ref={headerRef}/>
|
||||
<Iframes {...props} />
|
||||
{!state.spec ? (
|
||||
<NoSpecSelected onSelectSpecRequest={focusSpecsList}>
|
||||
<KeyboardHelper />
|
||||
</NoSpecSelected>
|
||||
) : (
|
||||
<Iframes {...props} />
|
||||
)}
|
||||
<Message state={state}/>
|
||||
</div>
|
||||
|
||||
|
||||
60
packages/runner-ct/src/app/useScreenshotHandler.tsx
Normal file
60
packages/runner-ct/src/app/useScreenshotHandler.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react'
|
||||
import { runInAction } from 'mobx'
|
||||
import EventManager from '../lib/event-manager'
|
||||
import State from '../lib/state'
|
||||
|
||||
/**
|
||||
* SplitPane hierarchy looks like this:
|
||||
* ```jsx
|
||||
* <div class="SplitPane">
|
||||
* <div class="Pane vertical Pane1 ">..</div>
|
||||
* <span role="presentation" class="Resizer vertical ">...</span>
|
||||
* </div>
|
||||
*```
|
||||
* we need to set these to display: none during cy.screenshot.
|
||||
*/
|
||||
export function useScreenshotHandler ({ eventManager, state, splitPaneRef } : {
|
||||
eventManager: typeof EventManager, state: State, splitPaneRef: React.MutableRefObject<{ splitPane: HTMLDivElement }>
|
||||
}) {
|
||||
const showPane = () => {
|
||||
if (!splitPaneRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
splitPaneRef.current.splitPane.firstElementChild.classList.remove('d-none')
|
||||
splitPaneRef.current.splitPane.querySelector('[role="presentation"]').classList.remove('d-none')
|
||||
}
|
||||
|
||||
const hidePane = () => {
|
||||
if (!splitPaneRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
splitPaneRef.current.splitPane.firstElementChild.classList.add('d-none')
|
||||
splitPaneRef.current.splitPane.querySelector('[role="presentation"]').classList.add('d-none')
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
eventManager.on('before:screenshot', (config) => {
|
||||
runInAction(() => {
|
||||
state.setScreenshotting(true)
|
||||
hidePane()
|
||||
})
|
||||
})
|
||||
|
||||
const revertFromScreenshotting = () => {
|
||||
runInAction(() => {
|
||||
state.setScreenshotting(false)
|
||||
showPane()
|
||||
})
|
||||
}
|
||||
|
||||
eventManager.on('after:screenshot', (config) => {
|
||||
revertFromScreenshotting()
|
||||
})
|
||||
|
||||
eventManager.on('run:start', () => {
|
||||
revertFromScreenshotting()
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
@@ -4,17 +4,23 @@ import { action, observable } from 'mobx'
|
||||
import { observer } from 'mobx-react'
|
||||
import React, { Component } from 'react'
|
||||
import Tooltip from '@cypress/react-tooltip'
|
||||
import { $ } from '@packages/driver'
|
||||
import State from '../lib/state'
|
||||
|
||||
import { configFileFormatted } from '../lib/config-file-formatted'
|
||||
import SelectorPlayground from '../selector-playground/selector-playground'
|
||||
import selectorPlaygroundModel from '../selector-playground/selector-playground-model'
|
||||
import { ExtendedConfigOptions } from '../app/RunnerCt'
|
||||
|
||||
interface HeaderProps {
|
||||
state: State
|
||||
config: ExtendedConfigOptions
|
||||
}
|
||||
|
||||
@observer
|
||||
export default class Header extends Component {
|
||||
export default class Header extends Component<HeaderProps> {
|
||||
headerRef = React.createRef()
|
||||
|
||||
@observable showingViewportMenu = false;
|
||||
@observable showingViewportMenu = false
|
||||
|
||||
render () {
|
||||
const { state, config } = this.props
|
||||
@@ -24,6 +30,7 @@ export default class Header extends Component {
|
||||
ref={this.headerRef}
|
||||
className={cs({
|
||||
'showing-selector-playground': selectorPlaygroundModel.isOpen,
|
||||
'display-none': state.screenshotting,
|
||||
})}
|
||||
>
|
||||
<div className='sel-url-wrap'>
|
||||
@@ -22,10 +22,13 @@ export default class Iframes extends Component {
|
||||
containerRef = null
|
||||
|
||||
render () {
|
||||
const { height, width, scriptError, scale } = this.props.state
|
||||
const { height, width, scriptError, scale, screenshotting } = this.props.state
|
||||
|
||||
return (
|
||||
<div className={cs('iframes-ct-container', { 'has-error': !!scriptError })}>
|
||||
<div className={cs('iframes-ct-container', {
|
||||
'has-error': !!scriptError,
|
||||
'iframes-ct-container-screenshotting': screenshotting,
|
||||
})}>
|
||||
<div
|
||||
ref={(container) => this.containerRef = container}
|
||||
className='size-container'
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.iframes-ct-container-screenshotting {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.size-container {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
@@ -73,6 +73,8 @@ export default class State {
|
||||
@observable width = _defaults.width
|
||||
@observable height = _defaults.height
|
||||
|
||||
@observable screenshotting = false
|
||||
|
||||
// if null, the default CSS handles it
|
||||
// if non-null, the user has set it by resizing
|
||||
@observable reporterWidth = _defaults.reporterWidth
|
||||
@@ -151,6 +153,10 @@ export default class State {
|
||||
}
|
||||
}
|
||||
|
||||
@action setScreenshotting (screenshotting: boolean) {
|
||||
this.screenshotting = screenshotting
|
||||
}
|
||||
|
||||
@action setIsLoading (isLoading) {
|
||||
this.isLoading = isLoading
|
||||
}
|
||||
|
||||
13
packages/runner/cypress/fixtures/studio/error_hooks_spec.js
Normal file
13
packages/runner/cypress/fixtures/studio/error_hooks_spec.js
Normal file
@@ -0,0 +1,13 @@
|
||||
describe('suite', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('the://url')
|
||||
|
||||
cy.get('body').then(() => {
|
||||
throw new Error('Failing Test')
|
||||
})
|
||||
})
|
||||
|
||||
it('test', () => {
|
||||
cy.get('body')
|
||||
})
|
||||
})
|
||||
11
packages/runner/cypress/fixtures/studio/error_test_spec.js
Normal file
11
packages/runner/cypress/fixtures/studio/error_test_spec.js
Normal file
@@ -0,0 +1,11 @@
|
||||
describe('suite', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('the://url')
|
||||
})
|
||||
|
||||
it('test', () => {
|
||||
cy.get('body').then(() => {
|
||||
throw new Error('Failing Test')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -42,6 +42,7 @@ describe('studio ui', () => {
|
||||
cy.get('.runner').find('.url').should('have.value', '')
|
||||
|
||||
cy.get('.runner').find('.url-menu').should('be.visible')
|
||||
cy.get('.runner').find('.url-menu').find('.btn-submit').should('be.disabled')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
@@ -63,6 +64,25 @@ describe('studio ui', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// doesn't actually test visiting, just ui state
|
||||
it('allows user to visit inputted url and prompts for interaction after visit', () => {
|
||||
runIsolatedCypress('cypress/fixtures/studio/basic_spec.js', {
|
||||
config: {
|
||||
baseUrl: null,
|
||||
},
|
||||
state: {
|
||||
studioTestId: 'r5',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('.runner').find('.url').type('the://url')
|
||||
cy.get('.runner').find('.url-menu').find('.btn-submit').click()
|
||||
|
||||
cy.get('.reporter').contains('the://url').closest('.command-wrapper-text').contains('visit')
|
||||
cy.get('.reporter').contains('Interact with your site to add test commands.')
|
||||
})
|
||||
})
|
||||
|
||||
it('displays modal when available commands is clicked', () => {
|
||||
runIsolatedCypress('cypress/fixtures/studio/basic_spec.js', {
|
||||
state: {
|
||||
@@ -76,4 +96,59 @@ describe('studio ui', () => {
|
||||
cy.get('reach-portal').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('error state', () => {
|
||||
it('displays error state when extending a failed test', () => {
|
||||
runIsolatedCypress('cypress/fixtures/studio/error_test_spec.js', {
|
||||
state: {
|
||||
studioTestId: 'r3',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('.reporter').contains('test').closest('.runnable').should('have.class', 'runnable-failed')
|
||||
cy.get('.reporter').contains('Studio cannot add commands to a failing test.').should('exist')
|
||||
|
||||
cy.get('.runner').find('.iframes-container').should('have.class', 'studio-is-failed')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
it('displays error state when a before hook fails', () => {
|
||||
runIsolatedCypress('cypress/fixtures/studio/error_hooks_spec.js', {
|
||||
state: {
|
||||
studioTestId: 'r3',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('.reporter').contains('test').closest('.runnable').should('have.class', 'runnable-failed')
|
||||
cy.get('.reporter').contains('Studio cannot add commands to a failing test.').should('exist')
|
||||
|
||||
cy.get('.runner').find('.iframes-container').should('have.class', 'studio-is-failed')
|
||||
})
|
||||
})
|
||||
|
||||
it('displays error state when cy.visit() fails on user inputted url', () => {
|
||||
runIsolatedCypress('cypress/fixtures/studio/basic_spec.js', {
|
||||
config: {
|
||||
baseUrl: null,
|
||||
},
|
||||
state: {
|
||||
studioTestId: 'r5',
|
||||
},
|
||||
visitUrl: 'http://localhost:3500/foo',
|
||||
visitSuccess: false,
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('.runner').find('.url').type('the://url')
|
||||
cy.get('.runner').find('.url-menu').find('.btn-submit').click()
|
||||
|
||||
cy.get('.reporter').contains('test 3').closest('.runnable').should('have.class', 'runnable-failed')
|
||||
cy.get('.reporter').contains('the://url').closest('.command-wrapper-text').contains('visit')
|
||||
cy.get('.reporter').contains('Studio cannot add commands to a failing test.').should('exist')
|
||||
|
||||
cy.get('.runner').find('.iframes-container').should('have.class', 'studio-is-failed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -106,6 +106,7 @@ function createCypress (defaultOptions = {}) {
|
||||
config: { video: false },
|
||||
onBeforeRun () {},
|
||||
visitUrl: 'http://localhost:3500/fixtures/dom.html',
|
||||
visitSuccess: true,
|
||||
})
|
||||
|
||||
return cy.visit('/fixtures/isolated-runner.html#/tests/cypress/fixtures/empty_spec.js')
|
||||
@@ -254,7 +255,7 @@ function createCypress (defaultOptions = {}) {
|
||||
|
||||
.withArgs('backend:request', 'resolve:url')
|
||||
.yieldsAsync({ response: {
|
||||
isOkStatusCode: true,
|
||||
isOkStatusCode: opts.visitSuccess,
|
||||
isHtml: true,
|
||||
url: opts.visitUrl,
|
||||
} })
|
||||
|
||||
@@ -388,9 +388,7 @@ const eventManager = {
|
||||
Cypress.on('log:added', (log) => {
|
||||
const displayProps = Cypress.runner.getDisplayPropsForLog(log)
|
||||
|
||||
if (studioRecorder.isActive) {
|
||||
displayProps.hookId = studioRecorder.hookId
|
||||
}
|
||||
this._interceptStudio(displayProps)
|
||||
|
||||
reporterBus.emit('reporter:log:add', displayProps)
|
||||
})
|
||||
@@ -398,9 +396,7 @@ const eventManager = {
|
||||
Cypress.on('log:changed', (log) => {
|
||||
const displayProps = Cypress.runner.getDisplayPropsForLog(log)
|
||||
|
||||
if (studioRecorder.isActive) {
|
||||
displayProps.hookId = studioRecorder.hookId
|
||||
}
|
||||
this._interceptStudio(displayProps)
|
||||
|
||||
reporterBus.emit('reporter:log:state:changed', displayProps)
|
||||
})
|
||||
@@ -556,6 +552,19 @@ const eventManager = {
|
||||
}
|
||||
},
|
||||
|
||||
_interceptStudio (displayProps) {
|
||||
if (studioRecorder.isActive) {
|
||||
displayProps.hookId = studioRecorder.hookId
|
||||
|
||||
if (displayProps.name === 'visit' && displayProps.state === 'failed') {
|
||||
studioRecorder.testFailed()
|
||||
reporterBus.emit('test:set:state', studioRecorder.testError, _.noop)
|
||||
}
|
||||
}
|
||||
|
||||
return displayProps
|
||||
},
|
||||
|
||||
emit (event, ...args) {
|
||||
localBus.emit(event, ...args)
|
||||
},
|
||||
|
||||
@@ -68,6 +68,13 @@ export class StudioRecorder {
|
||||
return this.isActive && !this.url && !this.isFailed
|
||||
}
|
||||
|
||||
@computed get testError () {
|
||||
return {
|
||||
id: this.testId,
|
||||
state: 'failed',
|
||||
}
|
||||
}
|
||||
|
||||
@computed get saveError () {
|
||||
return {
|
||||
id: this.testId,
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
"test-unit": "mocha -r @packages/ts/register test/**/*.spec.ts --config ./test/.mocharc.js --exit"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.7.2",
|
||||
"chokidar": "^3.4.3",
|
||||
"debug": "^4.2.0",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy": "^1.18.1",
|
||||
"lodash": "^4.17.20",
|
||||
"send": "^0.17.1"
|
||||
"bluebird": "3.5.3",
|
||||
"chalk": "2.4.2",
|
||||
"chokidar": "3.2.2",
|
||||
"debug": "4.3.2",
|
||||
"express": "4.17.1",
|
||||
"http-proxy": "1.18.1",
|
||||
"lodash": "4.17.20",
|
||||
"send": "0.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
|
||||
@@ -97,39 +97,6 @@ describe('top level suite', () => {
|
||||
|
||||
`
|
||||
|
||||
exports['lib/util/spec_writer #appendCommandsToTest can add commands to an existing test defined with it.only 1'] = `
|
||||
describe('top level suite', () => {
|
||||
describe('inner suite with describe', () => {
|
||||
it('test with it', () => {
|
||||
cy.get('.btn').click()
|
||||
})
|
||||
|
||||
specify('test with specify', () => {
|
||||
cy.get('.btn').click()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||
it.only('test with it.only', () => {
|
||||
cy.get('.btn').click()
|
||||
/* ==== Generated with Cypress Studio ==== */
|
||||
cy.get('.input').type('typed text');
|
||||
cy.get('.btn').click();
|
||||
/* ==== End Cypress Studio ==== */
|
||||
})
|
||||
})
|
||||
|
||||
context('inner suite with context', () => {
|
||||
|
||||
})
|
||||
|
||||
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||
describe.only('inner suite with describe.only', () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
`
|
||||
|
||||
exports['lib/util/spec_writer #createNewTestInSuite can create a new test in a suite defined with describe 1'] = `
|
||||
describe('top level suite', () => {
|
||||
describe('inner suite with describe', () => {
|
||||
@@ -202,41 +169,6 @@ describe('top level suite', () => {
|
||||
|
||||
`
|
||||
|
||||
exports['lib/util/spec_writer #createNewTestInSuite can create a new test in a suite defined with describe.only 1'] = `
|
||||
describe('top level suite', () => {
|
||||
describe('inner suite with describe', () => {
|
||||
it('test with it', () => {
|
||||
cy.get('.btn').click()
|
||||
})
|
||||
|
||||
specify('test with specify', () => {
|
||||
cy.get('.btn').click()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||
it.only('test with it.only', () => {
|
||||
cy.get('.btn').click()
|
||||
})
|
||||
})
|
||||
|
||||
context('inner suite with context', () => {
|
||||
|
||||
})
|
||||
|
||||
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||
describe.only('inner suite with describe.only', () => {
|
||||
/* === Test Created with Cypress Studio === */
|
||||
it('test added to describe.only', function() {
|
||||
/* ==== Generated with Cypress Studio ==== */
|
||||
cy.get('.input').type('typed text');
|
||||
cy.get('.btn').click();
|
||||
/* ==== End Cypress Studio ==== */
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
`
|
||||
|
||||
exports['lib/util/spec_writer #appendCommandsToTest can add commands to an existing test defined with it only 1'] = `
|
||||
describe('top level suite', () => {
|
||||
describe('inner suite with describe', () => {
|
||||
|
||||
@@ -65,6 +65,7 @@ const DEFAULT_ARGS = [
|
||||
'--allow-insecure-localhost',
|
||||
'--reduce-security-for-testing',
|
||||
'--enable-automation',
|
||||
'--disable-print-preview',
|
||||
|
||||
'--disable-device-discovery-notifications',
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ const os = require('os')
|
||||
const EE = require('events')
|
||||
const { app } = require('electron')
|
||||
const image = require('electron').nativeImage
|
||||
const Promise = require('bluebird')
|
||||
const cyIcons = require('@cypress/icons')
|
||||
const electronApp = require('../util/electron-app')
|
||||
const savedState = require('../saved_state')
|
||||
const menu = require('../gui/menu')
|
||||
const Events = require('../gui/events')
|
||||
@@ -112,21 +110,9 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
run (options) {
|
||||
const waitForReady = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return app.on('ready', resolve)
|
||||
})
|
||||
}
|
||||
async run (options) {
|
||||
await app.whenReady()
|
||||
|
||||
electronApp.allowRendererProcessReuse()
|
||||
|
||||
return Promise.any([
|
||||
waitForReady(),
|
||||
Promise.delay(500),
|
||||
])
|
||||
.then(() => {
|
||||
return this.ready(options)
|
||||
})
|
||||
return this.ready(options)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-console, @cypress/dev/arrow-body-multiline-braces */
|
||||
const _ = require('lodash')
|
||||
const { app } = require('electron')
|
||||
const la = require('lazy-ass')
|
||||
const pkg = require('@packages/root')
|
||||
const path = require('path')
|
||||
@@ -27,7 +28,6 @@ const newlines = require('../util/newlines')
|
||||
const terminal = require('../util/terminal')
|
||||
const specsUtil = require('../util/specs')
|
||||
const humanTime = require('../util/human_time')
|
||||
const electronApp = require('../util/electron-app')
|
||||
const settings = require('../util/settings')
|
||||
const chromePolicyCheck = require('../util/chrome_policy_check')
|
||||
const experiments = require('../experiments')
|
||||
@@ -1557,11 +1557,16 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
run (options) {
|
||||
return electronApp
|
||||
.waitForReady()
|
||||
.then(() => {
|
||||
return this.ready(options)
|
||||
async run (options) {
|
||||
// electron >= 5.0.0 will exit the app if all browserwindows are closed,
|
||||
// this is obviously undesirable in run mode
|
||||
// https://github.com/cypress-io/cypress/pull/4720#issuecomment-514316695
|
||||
app.on('window-all-closed', () => {
|
||||
debug('all BrowserWindows closed, not exiting')
|
||||
})
|
||||
|
||||
await app.whenReady()
|
||||
|
||||
return this.ready(options)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -165,6 +165,10 @@ const _providerCiParams = () => {
|
||||
'BITBUCKET_BUILD_NUMBER',
|
||||
'BITBUCKET_PARALLEL_STEP',
|
||||
'BITBUCKET_STEP_RUN_NUMBER',
|
||||
// the PR variables are only set on pull request builds
|
||||
'BITBUCKET_PR_ID',
|
||||
'BITBUCKET_PR_DESTINATION_BRANCH',
|
||||
'BITBUCKET_PR_DESTINATION_COMMIT',
|
||||
]),
|
||||
buildkite: extract([
|
||||
'BUILDKITE_REPO',
|
||||
|
||||
@@ -8,53 +8,13 @@ const scale = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const allowRendererProcessReuse = () => {
|
||||
const { app } = require('electron')
|
||||
|
||||
// @see https://github.com/electron/electron/issues/18397
|
||||
// NOTE: in Electron 9, this can be removed, since it will be the new default
|
||||
app.allowRendererProcessReuse = true
|
||||
}
|
||||
|
||||
// NOTE: this function is only called in run mode
|
||||
const waitForReady = () => {
|
||||
const debug = require('debug')('cypress:server:electron-app')
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const { app } = require('electron')
|
||||
|
||||
allowRendererProcessReuse()
|
||||
|
||||
// electron >= 5.0.0 will exit the app if all browserwindows are closed,
|
||||
// this is obviously undesirable in run mode
|
||||
// https://github.com/cypress-io/cypress/pull/4720#issuecomment-514316695
|
||||
app.on('window-all-closed', () => {
|
||||
debug('all BrowserWindows closed, not exiting')
|
||||
})
|
||||
|
||||
const onReadyEvent = () => {
|
||||
return new Promise((resolve) => {
|
||||
app.on('ready', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.any([
|
||||
onReadyEvent(),
|
||||
Promise.delay(500),
|
||||
])
|
||||
}
|
||||
|
||||
const isRunning = () => {
|
||||
// are we in the electron or the node process?
|
||||
return Boolean(process.versions && process.versions.electron)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
allowRendererProcessReuse,
|
||||
|
||||
scale,
|
||||
|
||||
waitForReady,
|
||||
|
||||
isRunning,
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ module.exports = {
|
||||
appendArgument () {},
|
||||
},
|
||||
disableHardwareAcceleration () {},
|
||||
async whenReady () {},
|
||||
},
|
||||
systemPreferences: {
|
||||
isDarkMode () {},
|
||||
|
||||
@@ -213,6 +213,61 @@ describe('lib/util/ci_provider', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('bitbucket pull request', () => {
|
||||
resetEnv = mockedEnv({
|
||||
CI: '1',
|
||||
|
||||
// build information
|
||||
BITBUCKET_BUILD_NUMBER: 'bitbucketBuildNumber',
|
||||
BITBUCKET_REPO_OWNER: 'bitbucketRepoOwner',
|
||||
BITBUCKET_REPO_SLUG: 'bitbucketRepoSlug',
|
||||
BITBUCKET_PARALLEL_STEP: 'bitbucketParallelStep',
|
||||
BITBUCKET_STEP_RUN_NUMBER: 'bitbucketStepRunNumber',
|
||||
|
||||
// git information
|
||||
BITBUCKET_COMMIT: 'bitbucketCommit',
|
||||
BITBUCKET_BRANCH: 'bitbucketBranch',
|
||||
|
||||
// pull request info
|
||||
BITBUCKET_PR_ID: 'bitbucketPrId',
|
||||
BITBUCKET_PR_DESTINATION_BRANCH: 'bitbucketPrDestinationBranch',
|
||||
BITBUCKET_PR_DESTINATION_COMMIT: 'bitbucketPrDestinationCommit',
|
||||
}, { clear: true })
|
||||
|
||||
expectsName('bitbucket')
|
||||
expectsCiParams({
|
||||
bitbucketBuildNumber: 'bitbucketBuildNumber',
|
||||
bitbucketRepoOwner: 'bitbucketRepoOwner',
|
||||
bitbucketRepoSlug: 'bitbucketRepoSlug',
|
||||
bitbucketParallelStep: 'bitbucketParallelStep',
|
||||
bitbucketStepRunNumber: 'bitbucketStepRunNumber',
|
||||
bitbucketPrId: 'bitbucketPrId',
|
||||
bitbucketPrDestinationBranch: 'bitbucketPrDestinationBranch',
|
||||
bitbucketPrDestinationCommit: 'bitbucketPrDestinationCommit',
|
||||
})
|
||||
|
||||
expectsCommitParams({
|
||||
sha: 'bitbucketCommit',
|
||||
branch: 'bitbucketBranch',
|
||||
})
|
||||
|
||||
expectsCommitDefaults({
|
||||
sha: null,
|
||||
branch: 'gitFoundBranch',
|
||||
}, {
|
||||
sha: 'bitbucketCommit',
|
||||
branch: 'gitFoundBranch',
|
||||
})
|
||||
|
||||
return expectsCommitDefaults({
|
||||
sha: undefined,
|
||||
branch: '',
|
||||
}, {
|
||||
sha: 'bitbucketCommit',
|
||||
branch: 'bitbucketBranch',
|
||||
})
|
||||
})
|
||||
|
||||
it('buildkite', () => {
|
||||
resetEnv = mockedEnv({
|
||||
BUILDKITE: 'true',
|
||||
|
||||
@@ -167,7 +167,7 @@ describe('gui/interactive', () => {
|
||||
|
||||
context('.run', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
|
||||
sinon.stub(electron.app, 'whenReady').resolves()
|
||||
})
|
||||
|
||||
it('calls ready with options', () => {
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const semver = require('semver')
|
||||
|
||||
const fail = (...reason) => {
|
||||
console.error(...reason)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const bump = require('conventional-recommended-bump')
|
||||
const Bluebird = require('bluebird')
|
||||
const bumpCb = require('conventional-recommended-bump')
|
||||
const currentVersion = require('../package.json').version
|
||||
|
||||
bump({ preset: 'angular' }, (err, { releaseType }) => {
|
||||
if (err) {
|
||||
return fail('Error getting next version', err)
|
||||
}
|
||||
const bump = Bluebird.promisify(bumpCb)
|
||||
const paths = ['packages', 'cli']
|
||||
|
||||
const nextVersion = semver.inc(currentVersion, releaseType || 'patch')
|
||||
let nextVersion
|
||||
|
||||
const getNextVersionForPath = async (path) => {
|
||||
const { releaseType } = await bump({ preset: 'angular', path })
|
||||
|
||||
return semver.inc(currentVersion, releaseType || 'patch')
|
||||
}
|
||||
|
||||
Bluebird.mapSeries(paths, async (path) => {
|
||||
const pathNextVersion = await getNextVersionForPath(path)
|
||||
|
||||
if (!nextVersion || semver.gt(pathNextVersion, nextVersion)) {
|
||||
nextVersion = pathNextVersion
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (!nextVersion) {
|
||||
throw new Error('Unable to determine next version.')
|
||||
}
|
||||
|
||||
if (process.argv.includes('--npm')) {
|
||||
const cmd = `npm --no-git-tag-version version ${nextVersion}`
|
||||
|
||||
35
yarn.lock
35
yarn.lock
@@ -10710,10 +10710,10 @@ chokidar@3.4.3:
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
"chokidar@>=2.0.0 <4.0.0", chokidar@^3.3.0, chokidar@^3.4.1, chokidar@^3.4.3:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
|
||||
"chokidar@>=2.0.0 <4.0.0", chokidar@^3.3.0, chokidar@^3.4.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.0.tgz#458a4816a415e9d3b3caa4faec2b96a6935a9e65"
|
||||
integrity sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
@@ -12808,7 +12808,7 @@ de-indent@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||
|
||||
debug@*, debug@4, debug@4.3.1, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.0, debug@~4.3.1:
|
||||
debug@*, debug@4, debug@4.3.1, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@~4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||
@@ -18494,7 +18494,7 @@ http-proxy-middleware@^0.19.1:
|
||||
lodash "^4.17.11"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
http-proxy@1.18.1, http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
version "1.18.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
|
||||
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
||||
@@ -22260,16 +22260,16 @@ lodash@4.17.19:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.14.0, lodash@^4.16.2, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.2:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
lodash@4.17.4:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.14.0, lodash@^4.16.2, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.2:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
log-symbols@2.2.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
|
||||
@@ -25041,6 +25041,11 @@ octokit-pagination-methods@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
|
||||
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
|
||||
|
||||
odiff-bin@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/odiff-bin/-/odiff-bin-2.1.0.tgz#6ef727b44a1843d9215408b99774c05567176776"
|
||||
integrity sha512-jN74kFP216ltssc72ig/48NtFO81AOvO5pJevDykVCA4hmyhzf5M/wyNFt0RxJZT8MWi2g/I4svp6VDjFhtuCQ==
|
||||
|
||||
omggif@^1.0.10, omggif@^1.0.9:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19"
|
||||
@@ -29547,9 +29552,9 @@ rollup@^2.35.1:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
rollup@^2.38.5:
|
||||
version "2.38.5"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.38.5.tgz#be41ad4fe0c103a8794377afceb5f22b8f603d6a"
|
||||
integrity sha512-VoWt8DysFGDVRGWuHTqZzT02J0ASgjVq/hPs9QcBOGMd7B+jfTr/iqMVEyOi901rE3xq+Deq66GzIT1yt7sGwQ==
|
||||
version "2.39.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.39.0.tgz#be4f98c9e421793a8fec82c854fb567c35e22ab6"
|
||||
integrity sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.1"
|
||||
|
||||
@@ -30030,7 +30035,7 @@ semver@~5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
|
||||
integrity sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=
|
||||
|
||||
send@0.17.1, send@^0.17.1:
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||
|
||||
Reference in New Issue
Block a user