Skip to content

Commit

Permalink
Update project error UI (#6432)
Browse files Browse the repository at this point in the history
* update default title to "an unexpected error occurred"

* show stack trace in error, if avail

* add "copy to clipboard"

* wrap browser errors

* add tests

* improve md formatting

* update desktopgui tests

* update
  • Loading branch information
flotwig committed Feb 14, 2020
1 parent 15c3e95 commit dd3b63a
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 13 deletions.
38 changes: 31 additions & 7 deletions packages/desktop-gui/cypress/integration/error_message_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,41 @@ describe('Error Message', function () {
this.start()

cy.get('.error').contains('ReferenceError: alsdkjf is not defined')
cy.get('details').should('not.have.attr', 'open')
cy.get('details').click().should('have.attr', 'open')
cy.get('summary').should('contain', 'ReferenceError')
cy.get('details.details-body').should('not.have.attr', 'open')
cy.get('details.details-body').click().should('have.attr', 'open')
cy.get('details.details-body > summary').should('contain', 'ReferenceError')
})

it('doesn\'t show error details if not provided', function () {
cy.stub(this.ipc, 'onProjectError').yields(null, this.err)
this.start()

cy.get('details.details-body > summary').should('not.exist')
})

it('shows error stack trace if provided', function () {
const err = new Error('foo')

err.stack = 'bar'

cy.stub(this.ipc, 'onProjectError').yields(null, err)
this.start()

cy.get('.error').contains('foo')
cy.get('details.stacktrace').should('not.have.attr', 'open')
cy.get('details.stacktrace').click().should('have.attr', 'open')
cy.get('details.stacktrace').should('contain', 'bar')
})

it('doesn\'t show error stack trace if not provided', function () {
const err = new Error()

delete err.stack
cy.stub(this.ipc, 'onProjectError').yields(null, err)
this.start()

cy.get('.error')
cy.get('summary').should('not.exist')
cy.get('details.stacktrace > summary').should('not.be.visible')
})

it('shows abbreviated error details if only one line', function () {
Expand All @@ -155,7 +179,7 @@ describe('Error Message', function () {
this.start()

cy.get('.error').contains('ReferenceError: alsdkjf is not defined')
cy.get('summary').should('not.exist')
cy.get('details.details-body > summary').should('not.exist')
})

it('opens links outside of electron', function () {
Expand Down Expand Up @@ -197,7 +221,7 @@ describe('Error Message', function () {
this.ipc.openProject.rejects(this.detailsErr)
this.start()

cy.get('details').click()
cy.get('details.details-body').click()
cy.get('nav').should('be.visible')
cy.get('footer').should('be.visible')
})
Expand All @@ -207,7 +231,7 @@ describe('Error Message', function () {
this.ipc.openProject.rejects(this.detailsErr)
this.start()

cy.get('details').click()
cy.get('details.details-body').click()
cy.contains('Try Again').should('be.visible')
cy.get('.full-alert pre').should('have.css', 'overflow', 'auto')
})
Expand Down
5 changes: 3 additions & 2 deletions packages/desktop-gui/cypress/integration/settings_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ describe('Settings', () => {
describe('errors', () => {
beforeEach(function () {
this.err = {
title: 'Foo Title',
message: 'Port \'2020\' is already in use.',
name: 'Error',
port: 2020,
Expand All @@ -514,11 +515,11 @@ describe('Settings', () => {
})

it('displays errors', () => {
cy.contains('Can\'t start server')
cy.contains('Foo Title')
})

it('displays config after error is fixed', function () {
cy.contains('Can\'t start server').then(() => {
cy.contains('Foo Title').then(() => {
this.ipc.openProject.onCall(1).resolves(this.config)

this.ipc.onConfigChanged.yield()
Expand Down
1 change: 1 addition & 0 deletions packages/desktop-gui/src/lib/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ register('updater:run', false)
register('window:open')
register('window:close')
register('onboarding:closed')
register('set:clipboard:text')

export default ipc
39 changes: 37 additions & 2 deletions packages/desktop-gui/src/project/error-message.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ import { configFileFormatted } from '../lib/config-file-formatted'

import Markdown from 'markdown-it'

const _copyErrorDetails = (err) => {
let details = [
`**Message:** ${err.message}`,
]

if (err.details) {
details.push(`**Details:** ${err.details}`)
}

if (err.title) {
details.unshift(`**Title:** ${err.title}`)
}

if (err.stack2) {
details.push(`**Stack trace:**\n\`\`\`\n${err.stack2}\n\`\`\``)
}

ipc.setClipboardText(details.join('\n\n'))
}

const md = new Markdown({
html: true,
linkify: true,
Expand All @@ -20,7 +40,7 @@ const ErrorDetails = observer(({ err }) => {
if (detailsBody) {
return (
<pre>
<details>
<details className='details-body'>
<summary>{detailsTitle}</summary>
{detailsBody}
</details>
Expand Down Expand Up @@ -61,7 +81,7 @@ class ErrorMessage extends Component {
<div className='full-alert alert alert-danger error'>
<p className='header'>
<i className='fas fa-exclamation-triangle'></i>{' '}
<strong>{err.title || 'Can\'t start server'}</strong>
<strong>{err.title || 'An unexpected error occurred'}</strong>
</p>
<span className='alert-content'>
<div ref={(node) => this.errorMessageNode = node} dangerouslySetInnerHTML={{
Expand All @@ -76,7 +96,22 @@ class ErrorMessage extends Component {
<p>To fix, stop the other running process or change the port in {configFileFormatted(this.props.project.configFile)}</p>
</div>
)}
{err.stack2 && (
<details className='stacktrace'>
<summary>Stack trace</summary>
<pre>{err.stack2}</pre>
</details>
)}
</span>
<button
className='btn btn-default btn-sm'
onClick={() => {
_copyErrorDetails(err)
}}
>
<i className='fas fa-copy'></i>{' '}
Copy to Clipboard
</button>
<button
className='btn btn-default btn-sm'
onClick={this.props.onTryAgain}
Expand Down
3 changes: 3 additions & 0 deletions packages/desktop-gui/src/project/project-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ export default class Project {
}

@action setError (err = {}) {
// for some reason, the original `stack` is unavailable on `err` once it is set on the model
// `stack2` remains usable though, for some reason
err.stack2 = err.stack
this.error = err
}

Expand Down
11 changes: 9 additions & 2 deletions packages/server/lib/gui/events.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
_ = require("lodash")
ipc = require("electron").ipcMain
shell = require("electron").shell
{ shell, clipboard } = require('electron')
debug = require('debug')('cypress:server:events')
pluralize = require("pluralize")
stripAnsi = require("strip-ansi")
Expand Down Expand Up @@ -109,7 +109,10 @@ handleEvent = (options, bus, event, id, type, arg) ->
onBrowserClose: ->
send({browserClosed: true})
})
.catch(sendErr)
.catch (err) =>
err.title ?= 'Error launching browser'

sendErr(err)

when "begin:auth"
onMessage = (msg) ->
Expand Down Expand Up @@ -299,6 +302,10 @@ handleEvent = (options, bus, event, id, type, arg) ->
err.apiUrl = apiUrl
sendErr(err)

when "set:clipboard:text"
clipboard.writeText(arg)
sendNull()

else
throw new Error("No ipc event registered for: '#{type}'")

Expand Down
22 changes: 22 additions & 0 deletions packages/server/test/unit/gui/events_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -699,3 +699,25 @@ describe "lib/gui/events", ->
expect(err.name).to.equal("ECONNREFUSED 127.0.0.1:1234")
expect(err.message).to.equal("ECONNREFUSED 127.0.0.1:1234")
expect(err.apiUrl).to.equal(konfig("api_url"))

describe "launch:browser", ->
it "launches browser via openProject", ->
sinon.stub(openProject, 'launch').callsFake (browser, spec, opts) ->
expect(browser).to.eq('foo')
expect(spec).to.eq('bar')

opts.onBrowserOpen()
opts.onBrowserClose()

Promise.resolve()

@handleEvent("launch:browser", { browser: 'foo', spec: 'bar' }).then =>
expect(@send.getCall(0).args[1].data).to.include({ browserOpened: true })
expect(@send.getCall(1).args[1].data).to.include({ browserClosed: true })

it "wraps error titles if not set", ->
err = new Error('foo')
sinon.stub(openProject, 'launch').rejects(err)

@handleEvent("launch:browser", {}).then =>
expect(@send.getCall(0).args[1].__error).to.include({ message: 'foo', title: 'Error launching browser' })

4 comments on commit dd3b63a

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dd3b63a Feb 14, 2020

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.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/linux-x64/circle-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-255888/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/circle-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-255873/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dd3b63a Feb 14, 2020

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.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/darwin-x64/circle-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-255953/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/circle-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-255896/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dd3b63a Feb 14, 2020

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.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

Instructions are included below, depending on the shell you are using.

In Command Prompt (cmd.exe):

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-x64/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

In PowerShell:

$env:CYPRESS_INSTALL_BINARY = https://cdn.cypress.io/beta/binary/4.0.2/win32-x64/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

In Git Bash:

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-x64/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

Using cross-env:

If the above commands do not work for you, you can also try using cross-env:

npm i -g cross-env
cross-env CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-x64/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dd3b63a Feb 14, 2020

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.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

Instructions are included below, depending on the shell you are using.

In Command Prompt (cmd.exe):

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-ia32/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

In PowerShell:

$env:CYPRESS_INSTALL_BINARY = https://cdn.cypress.io/beta/binary/4.0.2/win32-ia32/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

In Git Bash:

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-ia32/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip
npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

Using cross-env:

If the above commands do not work for you, you can also try using cross-env:

npm i -g cross-env
cross-env CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/4.0.2/win32-ia32/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.zip npm install https://cdn.cypress.io/beta/npm/4.0.2/appveyor-develop-dd3b63aa0be384b0207975430898f39eb6c4956d-30813649/cypress.tgz

Please sign in to comment.