Merge remote-tracking branch 'origin/develop' into 7.0-release

This commit is contained in:
Zach Bloomquist
2021-02-17 13:44:20 -05:00
47 changed files with 676 additions and 232 deletions

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1,4 @@
{}
{
"testFiles": "**/*spec.{ts,tsx}",
"video": false
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View 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;
}
}
}
}
}

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

View File

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

View File

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

View File

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

View 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()
})
}, [])
}

View File

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

View File

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

View File

@@ -2,6 +2,11 @@
margin: 8px;
}
.iframes-ct-container-screenshotting {
margin: 0;
}
.size-container {
width: 100%;
overflow: auto;

View File

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

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

View File

@@ -0,0 +1,11 @@
describe('suite', () => {
beforeEach(() => {
cy.visit('the://url')
})
it('test', () => {
cy.get('body').then(() => {
throw new Error('Failing Test')
})
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,6 +65,7 @@ const DEFAULT_ARGS = [
'--allow-insecure-localhost',
'--reduce-security-for-testing',
'--enable-automation',
'--disable-print-preview',
'--disable-device-discovery-notifications',

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ module.exports = {
appendArgument () {},
},
disableHardwareAcceleration () {},
async whenReady () {},
},
systemPreferences: {
isDarkMode () {},

View File

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

View File

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

View File

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

View File

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