Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(unify): Updating reporter to consistently use app-provided "Preferred Editor" dialog #19933

Merged
merged 10 commits into from
Jan 31, 2022
90 changes: 90 additions & 0 deletions packages/app/cypress/e2e/runner/reporter.errors.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { verify } from './support/verify-helpers'
Copy link
Contributor Author

@tbiethman tbiethman Jan 28, 2022

Choose a reason for hiding this comment

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

These tests were carried forward from the runner package and modified to be more e2e. There's a lot more where this came from, I'll be adding more in future PRs.


describe('errors ui', {
viewportHeight: 768,
viewportWidth: 1024,
}, () => {
describe('assertion failures', () => {
beforeEach(() => {
cy.scaffoldProject('runner-e2e-specs')
cy.openProject('runner-e2e-specs')

// set preferred editor to bypass IDE selection dialog
cy.withCtx((ctx) => {
ctx.coreData.localSettings.availableEditors = [
...ctx.coreData.localSettings.availableEditors,
{
id: 'test-editor',
binary: '/usr/bin/test-editor',
name: 'Test editor',
},
]

ctx.coreData.localSettings.preferences.preferredEditorBinary = 'test-editor'
})

cy.startAppServer()
cy.visitApp()

cy.contains('[data-cy=spec-item]', 'assertions.cy.js').click()

cy.location().should((location) => {
expect(location.hash).to.contain('assertions.cy.js')
})

// Wait for specs to complete
cy.findByLabelText('Stats').get('.failed', { timeout: 10000 }).should('have.text', 'Failed:3')
})

verify.it('with expect().<foo>', {
file: 'assertions.cy.js',
hasPreferredIde: true,
column: 25,
message: `expected 'actual' to equal 'expected'`,
codeFrameText: 'with expect().<foo>',
})

verify.it('with assert()', {
file: 'assertions.cy.js',
hasPreferredIde: true,
column: '(5|12)', // (chrome|firefox)
message: `should be true`,
codeFrameText: 'with assert()',
})

verify.it('with assert.<foo>()', {
file: 'assertions.cy.js',
hasPreferredIde: true,
column: 12,
message: `expected 'actual' to equal 'expected'`,
codeFrameText: 'with assert.<foo>()',
})
})

describe('assertion failures - no preferred IDE', () => {
beforeEach(() => {
cy.scaffoldProject('runner-e2e-specs')
cy.openProject('runner-e2e-specs')

cy.startAppServer()
cy.visitApp()

cy.contains('[data-cy=spec-item]', 'assertions.cy.js').click()

cy.location().should((location) => {
expect(location.hash).to.contain('assertions.cy.js')
})

// Wait for specs to complete
cy.findByLabelText('Stats').get('.failed', { timeout: 10000 }).should('have.text', 'Failed:3')
})

verify.it('with expect().<foo>', {
file: 'assertions.cy.js',
hasPreferredIde: false,
column: 25,
message: `expected 'actual' to equal 'expected'`,
codeFrameText: 'with expect().<foo>',
})
})
})
205 changes: 205 additions & 0 deletions packages/app/cypress/e2e/runner/support/verify-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import _ from 'lodash'
Copy link
Contributor Author

@tbiethman tbiethman Jan 28, 2022

Choose a reason for hiding this comment

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

The majority of this file was pre-existing in the runner package here. It's been modified here to no longer use runIsolatedCypress, as the spec is responsible for scaffolding and initializing the e2e test. We also no longer spy on the socket for external IDE interactions and intercept mutations/assert DOM instead.

This might change a bit as more tests get ported over.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, definitely worth porting this - it might be a bit of work to figure out how it all works and what it does, but definitely time well spent.

import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'

// Assert that either the the dialog is presented or the mutation is emitted, depending on
// whether the test has a preferred IDE defined.
const verifyIdeOpen = ({ file, action, hasPreferredIde }) => {
if (hasPreferredIde) {
cy.intercept('mutation-OpenFileInIDE', { data: { 'openFileInIDE': true } }).as('OpenIDE')

action()

cy.wait('@OpenIDE').then(({ request }) => {
expect(request.body.variables.input.absolute).to.include(file)
})
} else {
action()

cy.contains(defaultMessages.globalPage.selectPreferredEditor).should('be.visible')
cy.findByRole('button', { name: defaultMessages.actions.close }).click()
}
}

export const verifyFailure = (options) => {
const {
specTitle,
hasCodeFrame = true,
verifyOpenInIde = true,
hasPreferredIde,
column,
codeFrameText,
originalMessage,
message = [],
notInMessage = [],
command,
stack,
file,
uncaught = false,
uncaughtMessage,
} = options
let { regex, line } = options

regex = regex || new RegExp(`${file}:${line || '\\d+'}:${column}`)

cy.contains('.runnable-title', specTitle).closest('.runnable').as('Root')

cy.get('@Root').within(() => {
cy.contains('View stack trace').click()

const messageLines = [].concat(message)

if (messageLines.length) {
cy.log('message contains expected lines and stack does not include message')

_.each(messageLines, (msg) => {
cy.get('.runnable-err-message')
.should('include.text', msg)

cy.get('.runnable-err-stack-trace')
.should('not.include.text', msg)
})
}

if (originalMessage) {
cy.get('.runnable-err-message')
.should('include.text', originalMessage)
}

const notInMessageLines = [].concat(notInMessage)

if (notInMessageLines.length) {
cy.log('message does not contain the specified lines')

_.each(notInMessageLines, (msg) => {
cy.get('.runnable-err-message')
.should('not.include.text', msg)
})
}

cy.log('stack trace matches the specified pattern')
cy.get('.runnable-err-stack-trace')
.invoke('text')
.should('match', regex)

if (stack) {
const stackLines = [].concat(stack)

if (stackLines.length) {
cy.log('stack contains the expected lines')
}

_.each(stackLines, (stackLine) => {
cy.get('.runnable-err-stack-trace')
.should('include.text', stackLine)
})
}

cy.get('.runnable-err-stack-trace')
.invoke('text')
.should('not.include', '__stackReplacementMarker')
.should((stackTrace) => {
// if this stack trace has the 'From Your Spec Code' addendum,
// it should only appear once
const match = stackTrace.match(/From Your Spec Code/g)

if (match && match.length) {
expect(match.length, `'From Your Spec Code' should only be in the stack once, but found ${match.length} instances`).to.equal(1)
}
})
})

if (verifyOpenInIde) {
verifyIdeOpen({
file,
hasPreferredIde,
action: () => {
cy.get('@Root').contains('.runnable-err-stack-trace .runnable-err-file-path a', file)
.click('left')
},
})
}

cy.get('@Root').within(() => {
if (command) {
cy.log('the error is attributed to the correct command')
cy
.get('.command-state-failed')
.first()
.find('.command-method')
.invoke('text')
.should('equal', command)
}

if (uncaught) {
cy.log('uncaught error has an associated log for the original error')
cy.get('.command-name-uncaught-exception')
.should('have.length', 1)
.should('have.class', 'command-state-failed')
.find('.command-message-text')
.should('include.text', uncaughtMessage || originalMessage)
} else {
cy.log('"caught" error does not have an uncaught error log')
cy.get('.command-name-uncaught-exception').should('not.exist')
}

if (!hasCodeFrame) return

cy.log('code frame matches specified pattern')
cy
.get('.test-err-code-frame .runnable-err-file-path')
.invoke('text')
.should('match', regex)

cy.get('.test-err-code-frame pre span').should('include.text', codeFrameText)
})

if (verifyOpenInIde) {
verifyIdeOpen({
file,
hasPreferredIde,
action: () => {
cy.get('@Root').contains('.test-err-code-frame .runnable-err-file-path a', file)
.click()
},
})
}
}

const createVerifyTest = (modifier?: string) => {
return (title: string, opts: any, props?: any) => {
if (!props) {
props = opts
opts = null
}

props.specTitle ||= title

const verifyFn = props.verifyFn || verifyFailure.bind(null, props)

return (modifier ? it[modifier] : it)(title, verifyFn)
}
}

export const verify = {
it: createVerifyTest(),
}

verify.it['only'] = createVerifyTest('only')
verify.it['skip'] = createVerifyTest('skip')

export const verifyInternalFailure = (props) => {
const { method, stackMethod } = props

cy.get('.runnable-err-message')
.should('include.text', `thrown in ${method.replace(/\./g, '-')}`)

cy.get('.runnable-err-stack-expander > .collapsible-header').click()

cy.get('.runnable-err-stack-trace')
.should('include.text', stackMethod || method)

// this is an internal cypress error and we can only show code frames
// from specs, so it should not show the code frame
cy.get('.test-err-code-frame')
.should('not.exist')
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const e2eProjectDirs = [
'remote-debugging-disconnect',
'remote-debugging-port-removed',
'retries-2',
'runner-e2e-specs',
'same-fixtures-integration-folders',
'screen-size',
'selectFile',
Expand Down
Loading