diff --git a/packages/driver/cypress/fixtures/resize-observer.html b/packages/driver/cypress/fixtures/resize-observer.html new file mode 100644 index 000000000000..1827ede5846a --- /dev/null +++ b/packages/driver/cypress/fixtures/resize-observer.html @@ -0,0 +1,31 @@ + + + + + + diff --git a/packages/driver/cypress/integration/cypress/error_utils_spec.ts b/packages/driver/cypress/integration/cypress/error_utils_spec.ts index fcd5a0b81db5..6c7dc30193e6 100644 --- a/packages/driver/cypress/integration/cypress/error_utils_spec.ts +++ b/packages/driver/cypress/integration/cypress/error_utils_spec.ts @@ -9,6 +9,19 @@ import $errUtils from '@packages/driver/src/cypress/error_utils' import $errorMessages from '@packages/driver/src/cypress/error_messages' describe('driver/src/cypress/error_utils', () => { + it('warns in console if ResizeObserver error is captured and suppressed', ({ browser: 'chrome' }), (done) => { + cy.spy(window.top.console, 'warn') + cy.visit('/fixtures/resize-observer.html') + + window.Cypress.once('resize-observer-triggered', () => { + expect(window.top.console.warn).to.be.calledWith( + 'Cypress is intentionally supressing and ignoring a unhandled ResizeObserver error. This can safely be ignored.', + ) + + done() + }) + }) + context('.modifyErrMsg', () => { let originalErr let newErrMsg diff --git a/packages/driver/src/cypress/cy.ts b/packages/driver/src/cypress/cy.ts index ee7037479d52..ba5477786206 100644 --- a/packages/driver/src/cypress/cy.ts +++ b/packages/driver/src/cypress/cy.ts @@ -6,7 +6,7 @@ import { registerFetch } from 'unfetch' import $dom from '../dom' import $utils from './utils' -import $errUtils, { ErrorFromProjectRejectionEvent } from './error_utils' +import $errUtils, { ErrorFromProjectRejectionEvent, shouldSuppressException } from './error_utils' import $stackUtils from './stack_utils' import { create as createChai, IChai } from '../cy/chai' @@ -797,6 +797,16 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert // AUT frame are the same if (frameType === 'app' || this.config('componentTesting')) { try { + const { suppress, warning } = shouldSuppressException(err) + + if (suppress) { + // eslint-disable-next-line no-console + console.warn(warning) + + // return undefined to skip logging the error and failing the test + return true + } + const results = this.Cypress.action('app:uncaught:exception', err, runnable, promise) // dont do anything if any of our uncaught:exception diff --git a/packages/driver/src/cypress/error_utils.ts b/packages/driver/src/cypress/error_utils.ts index 3d001726ecff..878d292fcaae 100644 --- a/packages/driver/src/cypress/error_utils.ts +++ b/packages/driver/src/cypress/error_utils.ts @@ -10,6 +10,38 @@ import $utils from './utils' const ERROR_PROPS = 'message type name stack sourceMappedStack parsedStack fileName lineNumber columnNumber host uncaught actual expected showDiff isPending docsUrl codeFrame'.split(' ') const ERR_PREPARED_FOR_SERIALIZATION = Symbol('ERR_PREPARED_FOR_SERIALIZATION') +export type ErrorHandlerType = 'error' | 'unhandledrejection' + +export interface KnownException { + message: string + warning: string +} + +// https://github.com/cypress-io/cypress/issues/8418 +// https://github.com/quasarframework/quasar/issues/2233#issuecomment-492975745 +export const knownExceptions: KnownException[] = [ + { + message: 'ResizeObserver loop limit exceeded', + warning: 'Cypress is intentionally supressing and ignoring a unhandled ResizeObserver error. This can safely be ignored.', + }, +] + +export function shouldSuppressException (error: Error) { + const knownException = knownExceptions.find((x) => error.message.includes(x.message)) + + if (!knownException) { + return { + suppress: false, + warning: null, + } + } + + return { + suppress: true, + warning: knownException.warning, + } +} + const crossOriginScriptRe = /^script error/i if (!Error.captureStackTrace) { @@ -538,7 +570,13 @@ const errorFromUncaughtEvent = (handlerType, event) => { errorFromProjectRejectionEvent(event) } -const logError = (Cypress, handlerType, err, handled = false) => { +const logError = (Cypress, handlerType: ErrorHandlerType, err: Error, handled = false) => { + const { suppress } = shouldSuppressException(err) + + if (suppress) { + return + } + Cypress.log({ message: `${err.name}: ${err.message}`, name: 'uncaught exception', diff --git a/packages/driver/src/cypress/runner.ts b/packages/driver/src/cypress/runner.ts index cad4c7780e91..d65d283ff546 100644 --- a/packages/driver/src/cypress/runner.ts +++ b/packages/driver/src/cypress/runner.ts @@ -8,7 +8,7 @@ import Promise from 'bluebird' import $Log from './log' import $utils from './utils' -import $errUtils from './error_utils' +import $errUtils, { ErrorHandlerType } from './error_utils' import $stackUtils from './stack_utils' import { getResolvedTestConfigOverride } from '../cy/testConfigOverrides' import debugFn from 'debug' @@ -1051,7 +1051,7 @@ export default { } // eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces - const onSpecError = (handlerType) => (event) => { + const onSpecError = (handlerType: ErrorHandlerType) => (event: Event) => { let { originalErr, err } = $errUtils.errorFromUncaughtEvent(handlerType, event) debugErrors('uncaught spec error: %o', originalErr)