Skip to content

Commit c0fc23a

Browse files
feat(npm/webpack-dev-server,runner-ct): Normalize webpack errors + general React/TS improvements (#16613)
* feat: normalize webpack errors * move some files to TS * add tests * simplify test * improve types Co-authored-by: Jessica Sachs <[email protected]>
1 parent 8099b64 commit c0fc23a

File tree

11 files changed

+98
-51
lines changed

11 files changed

+98
-51
lines changed

npm/webpack-dev-server/src/plugin.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export type Webpack45Compilation = Compilation & {
3333
}
3434
}
3535

36+
export const normalizeError = (error: Error | string) => {
37+
return typeof error === 'string' ? error : error.message
38+
}
39+
3640
export default class CypressCTOptionsPlugin {
3741
private files: Cypress.Cypress['spec'][] = []
3842
private supportFile: string
@@ -64,7 +68,16 @@ export default class CypressCTOptionsPlugin {
6468

6569
if (stats.hasErrors()) {
6670
this.errorEmitted = true
67-
this.devServerEvents.emit('dev-server:compile:error', stats.toJson().errors?.[0])
71+
72+
// webpack 4: string[]
73+
// webpack 5: Error[]
74+
const errors = stats.toJson().errors as Array<Error | string> | undefined
75+
76+
if (!errors || !errors.length) {
77+
return
78+
}
79+
80+
this.devServerEvents.emit('dev-server:compile:error', normalizeError(errors[0]))
6881
} else if (this.errorEmitted) {
6982
// compilation succeed but assets haven't emitted to the output yet
7083
this.devServerEvents.emit('dev-server:compile:error', null)

npm/webpack-dev-server/test/e2e.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ describe('#startDevServer', () => {
117117
exitSpy()
118118

119119
return new Promise((res) => {
120-
devServerEvents.on('dev-server:compile:error', () => {
120+
devServerEvents.on('dev-server:compile:error', (err: string) => {
121+
expect(err).to.contain('./test/fixtures/compilation-fails.spec.js 1:5')
122+
expect(err).to.contain('Module parse failed: Unexpected token (1:5)')
123+
expect(err).to.contain('You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders')
124+
expect(err).to.contain('> this is an invalid spec file')
121125
expect(exitSpy.calledOnce).to.be.true
122126
close(() => res())
123127
})

npm/webpack-dev-server/test/unit/makeWebpackConfig.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect } from 'chai'
2+
import EventEmitter from 'events'
23
import snapshot from 'snap-shot-it'
34
import { makeWebpackConfig } from '../../src/makeWebpackConfig'
45

@@ -9,6 +10,7 @@ describe('makeWebpackConfig', () => {
910
publicPath: '/this-will-be-ignored',
1011
},
1112
}, {
13+
devServerEvents: new EventEmitter(),
1214
devServerPublicPathRoute: '/test-public-path',
1315
isOpenMode: true,
1416
supportFile: '/support.js',
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect } from 'chai'
2+
import { normalizeError } from '../../src/plugin'
3+
4+
describe('normalizeError', () => {
5+
it('normalizes Error to string', () => {
6+
const e = new Error('Bad')
7+
8+
expect(normalizeError(e)).to.eq('Bad')
9+
})
10+
11+
it('returns string as is', () => {
12+
expect(normalizeError('Bad')).to.eq('Bad')
13+
})
14+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference types="@percy/cypress" />
2+
3+
import React from 'react'
4+
import { mount } from '@cypress/react'
5+
import ScriptError from '../../src/errors/script-error'
6+
7+
describe('ScriptError', () => {
8+
it('renders an error', () => {
9+
const error = 'There is an error in your code.'
10+
11+
mount(<ScriptError error={error} />).get('pre').contains(error)
12+
cy.percySnapshot()
13+
})
14+
})

packages/runner-ct/src/errors/script-error.jsx

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react'
2+
import { namedObserver } from '../lib/mobx'
3+
const ansiToHtml = require('ansi-to-html')
4+
5+
const convert = new ansiToHtml({
6+
fg: '#000',
7+
bg: '#fff',
8+
newline: false,
9+
escapeXML: true,
10+
stream: false,
11+
})
12+
13+
export const ScriptError: React.FC<{ error: string }> = namedObserver('ScriptError', ({ error }) => {
14+
if (!error) {
15+
return null
16+
}
17+
18+
const errorHTML = convert.toHtml(error)
19+
20+
return (
21+
<pre
22+
className='script-error'
23+
dangerouslySetInnerHTML={{ __html: errorHTML }}
24+
/>
25+
)
26+
})
27+
28+
export default ScriptError

packages/runner-ct/src/iframe/iframe-model.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import eventManager from '../lib/event-manager'
55
import selectorPlaygroundModel from '../selector-playground/selector-playground-model'
66

77
export default class IframeModel {
8-
constructor ({ state, detachDom, removeHeadStyles, restoreDom, highlightEl, snapshotControls }) {
8+
constructor ({ state, detachDom, restoreDom, highlightEl, snapshotControls }) {
99
this.state = state
1010
this.detachDom = detachDom
11-
this.removeHeadStyles = removeHeadStyles
1211
this.restoreDom = restoreDom
1312
this.highlightEl = highlightEl
1413
this.snapshotControls = snapshotControls

packages/runner-ct/src/iframe/iframes.jsx renamed to packages/runner-ct/src/iframe/iframes.tsx

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,35 @@ import cs from 'classnames'
22
import { action, when, autorun } from 'mobx'
33
import { observer } from 'mobx-react'
44
import React, { Component } from 'react'
5-
import { $ } from '@packages/driver'
5+
import { default as $Cypress } from '@packages/driver'
6+
import State from '../../src/lib/state'
67

78
import AutIframe from './aut-iframe'
8-
import ScriptError from '../errors/script-error'
9+
import { ScriptError } from '../errors/script-error'
910
import SnapshotControls from './snapshot-controls'
1011

1112
import IframeModel from './iframe-model'
1213
import selectorPlaygroundModel from '../selector-playground/selector-playground-model'
1314
import styles from '../app/RunnerCt.module.scss'
1415
import './iframes.scss'
16+
import eventManager from '../lib/event-manager'
1517

1618
export function getSpecUrl ({ namespace, spec }, prefix = '') {
1719
return spec ? `${prefix}/${namespace}/iframes/${spec.absolute}` : ''
1820
}
1921

22+
interface IFramesProps {
23+
state: State
24+
eventManager: typeof eventManager
25+
config: any
26+
}
27+
2028
@observer
21-
export default class Iframes extends Component {
29+
export default class Iframes extends Component<IFramesProps> {
2230
_disposers = []
2331
containerRef = null
32+
autIframe: AutIframe
33+
iframeModel: IframeModel
2434

2535
render () {
2636
const { viewportHeight, viewportWidth, scriptError, scale, screenshotting } = this.props.state
@@ -84,7 +94,6 @@ export default class Iframes extends Component {
8494

8595
this.iframeModel = new IframeModel({
8696
state: this.props.state,
87-
removeHeadStyles: this.autIframe.removeHeadStyles,
8897
restoreDom: this.autIframe.restoreDom,
8998
highlightEl: this.autIframe.highlightEl,
9099
detachDom: this.autIframe.detachDom,
@@ -110,7 +119,7 @@ export default class Iframes extends Component {
110119
}))
111120
}
112121

113-
@action _setScriptError = (err) => {
122+
@action _setScriptError = (err: string | undefined) => {
114123
this.props.state.scriptError = err
115124
}
116125

@@ -119,7 +128,7 @@ export default class Iframes extends Component {
119128

120129
// this.props.eventManager.notifyRunningSpec(specPath)
121130
// logger.clearLog()
122-
this._setScriptError(null)
131+
this._setScriptError(undefined)
123132

124133
this.props.eventManager.setup(config)
125134

@@ -137,7 +146,7 @@ export default class Iframes extends Component {
137146
// wiped out and reset on re-runs and the snapshots are from dom we don't control
138147
_loadIframes (spec) {
139148
const specSrc = getSpecUrl({ namespace: this.props.config.namespace, spec })
140-
const $container = $(this.containerRef).empty()
149+
const $container = $Cypress.$(this.containerRef).empty()
141150
const $autIframe = this.autIframe.create().appendTo($container)
142151

143152
this.autIframe.showBlankContents()
@@ -147,15 +156,6 @@ export default class Iframes extends Component {
147156
$autIframe.prop('src', specSrc)
148157

149158
return $autIframe
150-
151-
// const $specIframe = $('<iframe />', {
152-
// id: `Your Spec: '${specSrc}'`,
153-
// class: 'spec-iframe',
154-
// }).appendTo($container)
155-
156-
// $specIframe.prop('src', specSrc)
157-
158-
// return $autIframe
159159
}
160160

161161
_toggleSnapshotHighlights = (snapshotProps) => {
@@ -184,11 +184,7 @@ export default class Iframes extends Component {
184184
}
185185

186186
componentDidUpdate () {
187-
const cb = this.props.state.callbackAfterUpdate
188-
189-
if (cb) {
190-
cb()
191-
}
187+
this.props.state.callbackAfterUpdate?.()
192188
}
193189

194190
_printSelectorElementsToConsole = () => {

packages/runner-ct/src/lib/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default class State {
116116

117117
@observable automation = automation.CONNECTING
118118

119-
@observable.ref scriptError = null
119+
@observable.ref scriptError: string | undefined
120120

121121
@observable spec = _defaults.spec
122122
@observable specs = _defaults.specs

packages/server-ct/src/socket-ct.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class SocketCt extends SocketBase {
1010
constructor (config: Record<string, any>) {
1111
super(config)
1212

13-
devServer.emitter.on('dev-server:compile:error', (error) => {
14-
this.toRunner('dev-server:hmr:error', error === null ? null : { error })
13+
devServer.emitter.on('dev-server:compile:error', (error: string | undefined) => {
14+
this.toRunner('dev-server:hmr:error', error)
1515
})
1616

1717
// should we use this option at all for component testing 😕?

0 commit comments

Comments
 (0)