Skip to content

Commit

Permalink
fix(@cypress/react): Devtools unpredictable resets (#15612)
Browse files Browse the repository at this point in the history
* fix: Devtools unpredictable resets

* Remove cleaning up from webpack-dev-server

* Fix lint errors

* Get back observer

* fix: bring back cleanup (#15634)

* fix: wait for fw teardown to do html teardown

* fix: port responsibility of teardown to frameworks

* chore: add comments

* fix: typings

* fix: react unmount cannot be called in the right hook

* run dtslint

Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
Co-authored-by: ElevateBart <ledouxb@gmail.com>
  • Loading branch information
3 people committed Mar 25, 2021
1 parent d9c3ae2 commit b1f831a
Show file tree
Hide file tree
Showing 24 changed files with 182 additions and 173 deletions.
12 changes: 12 additions & 0 deletions cli/types/cypress.d.ts
Expand Up @@ -527,6 +527,18 @@ declare namespace Cypress {
* @see https://on.cypress.io/catalog-of-events#App-Events
*/
off: Actions

/**
* Trigger action
* @private
*/
action: (action: string, ...args: any[]) => void

/**
* Load files
* @private
*/
onSpecWindow: (window: Window, specList: string[] | Array<() => Promise<void>>) => void
}

type CanReturnChainable = void | Chainable | Promise<unknown>
Expand Down
Expand Up @@ -9,3 +9,11 @@ it('renders a button', () => {
</Button>,
)
})

it('renders a button with an icon', () => {
mount(
<Button variant="contained" color="primary" startIcon="⛹️">
Hello World
</Button>,
)
})
Expand Up @@ -43,6 +43,7 @@ export default class MouseMovement extends React.Component {
clearTimeout(this.state.timer)
this.props.onMoved(false)
}

render () {
return null
}
Expand Down
Expand Up @@ -31,7 +31,13 @@ describe('Renderless component', () => {

cy.get('@log')
.invoke('getCalls')
.then((calls) => calls.map((call) => call.args[0]))
.then((calls) => {
return calls.map((call) => {
console.log('one', call.args[0])

return call.args[0]
})
})
.should('deep.equal', [
'MouseMovement constructor',
'MouseMovement componentWillMount',
Expand Down
19 changes: 17 additions & 2 deletions npm/react/src/mount.ts
Expand Up @@ -136,6 +136,12 @@ export const mount = (jsx: React.ReactNode, options: MountOptions = {}) => {
})
}

let initialInnerHtml = ''

Cypress.on('run:start', () => {
initialInnerHtml = document.head.innerHTML
})

/**
* Removes the mounted component. Notice this command automatically
* queues up the `unmount` into Cypress chain, thus you don't need `.then`
Expand Down Expand Up @@ -166,8 +172,17 @@ export const unmount = (options = { log: true }) => {
})
}

beforeEach(() => {
unmount()
// Cleanup before each run
// NOTE: we cannot use unmount here because
// we are not in the context of a test
Cypress.on('test:before:run', () => {
const el = document.getElementById(ROOT_ID)

if (el) {
const wasUnmounted = ReactDOM.unmountComponentAtNode(el)

document.head.innerHTML = initialInnerHtml
}
})

/**
Expand Down
23 changes: 23 additions & 0 deletions npm/vue/cypress/component/basic/code-coverage/Calculator-spec.js
Expand Up @@ -25,4 +25,27 @@ describe('Calculator', () => {
expect(includesVue, 'Calculator.vue is instrumented').to.be.true
})
})

it('adds two decimal numbers', () => {
cy.viewport(400, 50)
mount(Calculator)
cy.get('[data-cy=a]').clear().type('22.6')
cy.get('[data-cy=b]').clear().type('19.4')
cy.contains('= 42')

cy.log('**check coverage**')
cy.wrap(window)
.its('__coverage__')
// and it includes information even for this file
.then(Object.keys)
.should('include', Cypress.spec.absolute)
.and((filenames) => {
// coverage should include Calculator.vue file
const includesVue = filenames.some((filename) => {
return filename.endsWith('Calculator.vue')
})

expect(includesVue, 'Calculator.vue is instrumented').to.be.true
})
})
})
34 changes: 0 additions & 34 deletions npm/vue/cypress/component/support.spec.js

This file was deleted.

15 changes: 12 additions & 3 deletions npm/vue/src/index.ts
Expand Up @@ -307,11 +307,20 @@ function failTestOnVueError (err, vm, info) {
window.top.onerror(err)
}

function cyBeforeEach (cb: () => void) {
Cypress.on('test:before:run', cb)
let initialInnerHtml = ''

Cypress.on('run:start', () => {
initialInnerHtml = document.head.innerHTML
})

function registerAutoDestroy ($destroy: () => void) {
Cypress.on('test:before:run', () => {
$destroy()
document.head.innerHTML = initialInnerHtml
})
}

enableAutoDestroy(cyBeforeEach)
enableAutoDestroy(registerAutoDestroy)

/**
* Mounts a Vue component inside Cypress browser.
Expand Down
1 change: 1 addition & 0 deletions npm/webpack-dev-server/index-template.html
Expand Up @@ -7,5 +7,6 @@
<title>Components App</title>
</head>
<body>
<div id="__cy_root"></div>
</body>
</html>
38 changes: 2 additions & 36 deletions npm/webpack-dev-server/src/aut-runner.ts
@@ -1,46 +1,12 @@
/* eslint-disable no-console */
/*eslint-env browser */

function appendTargetIfNotExists (id: string, tag = 'div', parent = document.body) {
let node = document.getElementById(id)

node = document.createElement(tag)
node.setAttribute('id', id)
parent.appendChild(node)

return node
}

export function init (importPromises, parent = (window.opener || window.parent)) {
const Cypress = (window as any).Cypress = parent.Cypress
export function init (importPromises, parent: Window = (window.opener || window.parent)) {
const Cypress = window.Cypress = parent.Cypress

if (!Cypress) {
throw new Error('Tests cannot run without a reference to Cypress!')
}

Cypress.onSpecWindow(window, importPromises)
Cypress.action('app:window:before:load', window)

// In this variable, we save head
// innerHTML to account for loader installed styles
let headInnerHTML = ''

// before the run starts save
Cypress.on('run:start', () => {
headInnerHTML = document.head.innerHTML
})

// Before all tests we are mounting the root element, **not beforeEach**
// Cleaning up platform between tests is the responsibility of the specific adapter
// because unmounting react/vue component should be done using specific framework API
// (for devtools and to get rid of global event listeners from previous tests.)
Cypress.on('test:before:run', () => {
document.body.innerHTML = ''
document.head.innerHTML = headInnerHTML
appendTargetIfNotExists('__cy_root')
})

return {
restartRunner: Cypress.restartRunner,
}
}
8 changes: 8 additions & 0 deletions npm/webpack-dev-server/src/window.d.ts
@@ -0,0 +1,8 @@
/**
* We need this file to call window.Cypress
*/
export declare global {
interface Window {
Cypress: Cypress.Cypress;
}
}
7 changes: 3 additions & 4 deletions npm/webpack-dev-server/tsconfig.json
Expand Up @@ -17,14 +17,14 @@
"outDir": "dist" /* Redirect output structure to the directory. */,
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": false /* Enable all strict type-checking options. */,
"noImplicitAny": false,
// "noImplicitAny": true,

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
Expand All @@ -48,6 +48,5 @@
"esModuleInterop": true
},
"include": ["src"],
"exclude": ["*.js"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "*.js"]
}
7 changes: 5 additions & 2 deletions packages/runner-ct/index.d.ts
Expand Up @@ -2,13 +2,16 @@
/// <reference path="../../cli/types/cypress.d.ts" />

declare module 'react-devtools-inline/frontend' {
import * as React from 'react'

export type DevtoolsProps = { browserTheme: 'dark' | 'light'}
export const initialize: (window: Window) => React.ComponentType<DevtoolsProps>;
}

declare module 'react-devtools-inline/backend' {
export const initialize: (window: Window) => void
export const activate: (window: Window) => void
}

declare module "*.scss" {
const value: Record<string, string>
export default value
}
78 changes: 39 additions & 39 deletions packages/runner-ct/src/app/Plugins.tsx
Expand Up @@ -12,46 +12,46 @@ import styles from './RunnerCt.module.scss'
interface PluginsProps {
state: State
pluginsHeight: number
pluginRootContainerRef: React.MutableRefObject<HTMLDivElement>
}

export const Plugins = namedObserver('Plugins',
(props: PluginsProps) => (
<Hidden
type="layout"
hidden={!props.state.isAnyPluginToShow}
className={styles.ctPlugins}
>
<div className={styles.ctPluginsHeader}>
{props.state.plugins.map((plugin) => (
<button
key={plugin.name}
className={cs(styles.ctPluginToggleButton)}
onClick={() => props.state.openDevtoolsPlugin(plugin)}
>
<span className={styles.ctPluginsName}>
{plugin.name}
</span>
<div
className={cs(styles.ctTogglePluginsSectionButton, {
[styles.ctTogglePluginsSectionButtonOpen]: props.state.isAnyDevtoolsPluginOpen,
})}
>
<FontAwesomeIcon
icon='chevron-up'
className={styles.ctPluginsName}
/>
</div>
</button>
))}
</div>
(props: PluginsProps) => {
const ref = React.useRef<HTMLDivElement>(null)

return (
<Hidden
ref={props.pluginRootContainerRef}
type="layout"
className={styles.ctPluginsContainer}
// deal with jumps when inspecting element
hidden={!props.state.isAnyDevtoolsPluginOpen}
style={{ height: props.pluginsHeight - PLUGIN_BAR_HEIGHT }}
/>
</Hidden>
))
type="visual"
hidden={!props.state.isAnyPluginToShow}
className={styles.ctPlugins}
>
<div className={styles.ctPluginsHeader}>
{props.state.plugins.map((plugin) => (
<button
key={plugin.name}
className={cs(styles.ctPluginToggleButton)}
onClick={() => props.state.toggleDevtoolsPlugin(plugin, ref.current)}
>
<span className={styles.ctPluginsName}>
{plugin.name}
</span>
<div
className={cs(styles.ctTogglePluginsSectionButton, {
[styles.ctTogglePluginsSectionButtonOpen]: props.state.isAnyDevtoolsPluginOpen,
})}
>
<FontAwesomeIcon
icon='chevron-up'
className={styles.ctPluginsName}
/>
</div>
</button>
))}
</div>
<div
ref={ref}
className={styles.ctPluginsContainer}
style={{ height: props.pluginsHeight - PLUGIN_BAR_HEIGHT }}
/>
</Hidden>
)
})

4 comments on commit b1f831a

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on b1f831a Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.9.0/circle-develop-b1f831a86a8bcc6646067bc8a9e67871026ff575/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on b1f831a Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.7.2/appveyor-develop-b1f831a86a8bcc6646067bc8a9e67871026ff575/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on b1f831a Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.7.2/appveyor-develop-b1f831a86a8bcc6646067bc8a9e67871026ff575/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on b1f831a Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.9.0/circle-develop-b1f831a86a8bcc6646067bc8a9e67871026ff575/cypress.tgz

Please sign in to comment.