Skip to content

Commit

Permalink
fix(gatsby): refresh browser when receiving update and runtime errored (
Browse files Browse the repository at this point in the history
#27467)

* fix(gatsby): refresh browser when receiving update and runtime errored

* add query validation error test

* add test cases for changing content causing and fixing runtime errors

* adjust test names
  • Loading branch information
pieh authored Oct 16, 2020
1 parent 26667be commit f227e85
Show file tree
Hide file tree
Showing 19 changed files with 449 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"selector": "page-query",
"hasError": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"selector": "static-query",
"hasError": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
before(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/compile-error.js --restore`
)
})

after(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/compile-error.js --restore`
)
})

const errorPlaceholder = `// compile-error`
const errorReplacement = `a b`

describe(`testing error overlay and ability to automatically recover from webpack compile errors`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(`/error-handling/compile-error/`).waitForRouteChange()
cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Working`)
})

it(`displays error with overlay on compilation errors`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/compile-error.js --replacements "${errorPlaceholder}:${errorReplacement}" --exact`
)

cy.getOverlayIframe().contains(`Failed to compile`)
cy.getOverlayIframe().contains(`Parsing error: Unexpected token`)
cy.screenshot()
})

it(`can recover without need to refresh manually`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/compile-error.js --replacements "Working:Updated" --replacements "${errorReplacement}:${errorPlaceholder}" --exact`
)

cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Updated`)
cy.assertNoOverlayIframe()
cy.screenshot()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Cypress.on("uncaught:exception", (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})

before(() => {
cy.exec(
`npm run update -- --file content/error-recovery/page-query.json --restore`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/page-query-result-runtime-error.js --restore`
)
})

after(() => {
cy.exec(
`npm run update -- --file content/error-recovery/page-query.json --restore`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/page-query-result-runtime-error.js --restore`
)
})

const errorPlaceholder = `false`
const errorReplacement = `true`

describe(`testing error overlay and ability to automatically recover runtime errors cause by content changes (page queries variant)`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(
`/error-handling/page-query-result-runtime-error/`
).waitForRouteChange()
cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Working`)
cy.getTestElement(`results`)
.invoke(`text`)
.should(`contain`, `"hasError": false`)
})

it(`displays error with overlay on runtime errors`, () => {
cy.exec(
`npm run update -- --file content/error-recovery/page-query.json --replacements "${errorPlaceholder}:${errorReplacement}" --exact`
)

// that's the exact error we throw and we expect to see that
cy.getOverlayIframe().contains(`Page query results caused runtime error`)
// contains details
cy.getOverlayIframe().contains(
`src/pages/error-handling/page-query-result-runtime-error.js`
)
cy.screenshot()
})

it(`can recover without need to refresh manually`, () => {
cy.exec(
`npm run update -- --file content/error-recovery/page-query.json --replacements "${errorReplacement}:${errorPlaceholder}" --exact`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/page-query-result-runtime-error.js --replacements "Working:Updated" --exact`
)

cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Updated`)
cy.getTestElement(`results`)
.invoke(`text`)
.should(`contain`, `"hasError": false`)

cy.assertNoOverlayIframe()
cy.screenshot()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
before(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/query-validation-error.js --restore`
)
})

after(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/query-validation-error.js --restore`
)
})

const errorPlaceholder = `# query-validation-error`
const errorReplacement = `fieldThatDoesNotExistOnSiteMapType`

describe(`testing error overlay and ability to automatically recover from query extraction validation errors`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(`/error-handling/query-validation-error/`).waitForRouteChange()
cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Working`)
})

it(`displays error with overlay on compilation errors`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/query-validation-error.js --replacements "${errorPlaceholder}:${errorReplacement}" --exact`
)

cy.getOverlayIframe().contains(`Failed to compile`)
cy.getOverlayIframe().contains(`There was an error in your GraphQL query`)
// make sure we mark location
cy.getOverlayIframe().contains(
`src/pages/error-handling/query-validation-error.js`
)
cy.screenshot()
})

it(`can recover without need to refresh manually`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/query-validation-error.js --replacements "Working:Updated" --replacements "${errorReplacement}:${errorPlaceholder}" --exact`
)

cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Updated`)
cy.assertNoOverlayIframe()
cy.screenshot()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Cypress.on("uncaught:exception", (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})

before(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/runtime-error.js --restore`
)
})

after(() => {
cy.exec(
`npm run update -- --file src/pages/error-handling/runtime-error.js --restore`
)
})

const errorPlaceholder = `// runtime-error`
const errorReplacement = `window.a.b.c.d.e.f.g()`

describe(`testing error overlay and ability to automatically recover from runtime errors`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(`/error-handling/runtime-error/`).waitForRouteChange()
cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Working`)
})

it(`displays error with overlay on runtime errors`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/runtime-error.js --replacements "${errorPlaceholder}:${errorReplacement}" --exact`
)

cy.getOverlayIframe().contains(`Cannot read property`)
// contains details
cy.getOverlayIframe().contains(`src/pages/error-handling/runtime-error.js`)
cy.screenshot()
})

it(`can recover without need to refresh manually`, () => {
cy.exec(
`npm run update -- --file src/pages/error-handling/runtime-error.js --replacements "Working:Updated" --replacements "${errorReplacement}:${errorPlaceholder}" --exact`
)

cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Updated`)
cy.assertNoOverlayIframe()
cy.screenshot()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Cypress.on("uncaught:exception", (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})

before(() => {
cy.exec(
`npm run update -- --file content/error-recovery/static-query.json --restore`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/static-query-result-runtime-error.js --restore`
)
})

after(() => {
cy.exec(
`npm run update -- --file content/error-recovery/static-query.json --restore`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/static-query-result-runtime-error.js --restore`
)
})

const errorPlaceholder = `false`
const errorReplacement = `true`

describe(`testing error overlay and ability to automatically recover from runtime errors (static queries variant)`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(
`/error-handling/static-query-result-runtime-error/`
).waitForRouteChange()
cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Working`)
cy.getTestElement(`results`)
.invoke(`text`)
.should(`contain`, `"hasError": false`)
})

it(`displays error with overlay on runtime errors`, () => {
cy.exec(
`npm run update -- --file content/error-recovery/static-query.json --replacements "${errorPlaceholder}:${errorReplacement}" --exact`
)

// that's the exact error we throw and we expect to see that
cy.getOverlayIframe().contains(`Static query results caused runtime error`)
// contains details
cy.getOverlayIframe().contains(
`src/pages/error-handling/static-query-result-runtime-error.js`
)
cy.screenshot()
})

it(`can recover without need to refresh manually`, () => {
cy.exec(
`npm run update -- --file content/error-recovery/static-query.json --replacements "${errorReplacement}:${errorPlaceholder}" --exact`
)
cy.exec(
`npm run update -- --file src/pages/error-handling/static-query-result-runtime-error.js --replacements "Working:Updated" --exact`
)

cy.getTestElement(`hot`).invoke(`text`).should(`contain`, `Updated`)
cy.getTestElement(`results`)
.invoke(`text`)
.should(`contain`, `"hasError": false`)

cy.assertNoOverlayIframe()
cy.screenshot()
})
})
27 changes: 25 additions & 2 deletions e2e-tests/development-runtime/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Cypress.Commands.add(`lifecycleCallOrder`, expectedActionCallOrder =>
if (expectedActionCallOrderLength > actionsLength) {
return false
}

let prevActionIndex = -1
for (let i = 0; i < actionsLength; i += 1) {
const nextActionIndex = prevActionIndex + 1
Expand Down Expand Up @@ -81,6 +81,29 @@ Cypress.Commands.add(
}
)

Cypress.Commands.add(`assertRoute`, (route) => {
Cypress.Commands.add(`assertRoute`, route => {
cy.url().should(`equal`, `${window.location.origin}${route}`)
})

// react-error-overlay is iframe, so this is just convenience helper
// https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/#custom-command
Cypress.Commands.add(`getOverlayIframe`, () => {
// get the iframe > document > body
// and retry until the body element is not empty
return (
cy
.get(`iframe`, { log: true, timeout: 150000 })
.its(`0.contentDocument.body`)
.should(`not.be.empty`)
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then(cy.wrap, { log: true })
)
})

Cypress.Commands.add(`assertNoOverlayIframe`, () => {
// get the iframe > document > body
// and retry until the body element is not empty
return cy.get(`iframe`, { log: true, timeout: 15000 }).should(`not.exist`)
})
1 change: 1 addition & 0 deletions e2e-tests/development-runtime/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = {
},
`gatsby-source-fake-data`,
`gatsby-transformer-sharp`,
`gatsby-transformer-json`,
{
resolve: `gatsby-transformer-remark`,
options: {
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/development-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"gatsby-plugin-sharp": "^2.0.37",
"gatsby-seo": "^0.1.0",
"gatsby-source-filesystem": "^2.0.33",
"gatsby-transformer-json": "^2.4.14",
"gatsby-transformer-remark": "^2.3.12",
"gatsby-transformer-sharp": "^2.1.19",
"isomorphic-fetch": "^2.2.1",
Expand Down
18 changes: 17 additions & 1 deletion e2e-tests/development-runtime/scripts/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,29 @@ const args = yargs
`
).trim(),
type: `string`,
})
.option(`restore`, {
default: false,
type: `boolean`,
}).argv

async function update() {
const history = await getHistory()

const { file: fileArg, replacements } = args
const { file: fileArg, replacements, restore } = args
const filePath = path.resolve(fileArg)
if (restore) {
const original = history.get(filePath)
if (original) {
await fs.writeFile(filePath, original, `utf-8`)
} else if (original === false) {
await fs.remove(filePath)
} else {
console.log(`Didn't make changes to "${fileArg}". Nothing to restore.`)
}
history.delete(filePath)
return
}
let exists = true
if (!fs.existsSync(filePath)) {
exists = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react"

function CompileError() {
// compile-error
return <p data-testid="hot">Working</p>
}

export default CompileError
Loading

0 comments on commit f227e85

Please sign in to comment.