1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Even more reliable error-recovery tests (#5284)

This commit is contained in:
Tim Neutkens 2018-09-26 01:04:15 +02:00 committed by GitHub
parent 139bc40fb5
commit db216e0086
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 290 additions and 158 deletions

View file

@ -128,7 +128,7 @@
"babel-jest": "23.4.2",
"benchmark": "2.1.4",
"cheerio": "0.22.0",
"chromedriver": "2.32.3",
"chromedriver": "2.42.0",
"clone": "2.1.1",
"coveralls": "2.13.1",
"cross-env": "5.2.0",

View file

@ -1,10 +1,42 @@
/* global describe, it, expect */
import webdriver from 'next-webdriver'
import { join } from 'path'
import { check, File, getReactErrorOverlayContent } from 'next-test-utils'
import { check, File, waitFor, getReactErrorOverlayContent, getBrowserBodyText } from 'next-test-utils'
export default (context, render) => {
describe('Error Recovery', () => {
it('should recover from 404 after a page has been added', async () => {
let browser
const newPage = new File(join(__dirname, '../', 'pages', 'hmr', 'new-page.js'))
try {
browser = await webdriver(context.appPort, '/hmr/new-page')
expect(await browser.elementByCss('body').text()).toMatch(/This page could not be found/)
// Add the page
newPage.write('export default () => (<div id="new-page">the-new-page</div>)')
await check(
() => getBrowserBodyText(browser),
/the-new-page/
)
newPage.delete()
await check(
() => getBrowserBodyText(browser),
/This page could not be found/
)
} catch (err) {
newPage.delete()
throw err
} finally {
if (browser) {
browser.close()
}
}
})
it('should have installed the react-overlay-editor editor handler', async () => {
let browser
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
@ -19,12 +51,20 @@ export default (context, render) => {
aboutPage.restore()
await check(
() => browser.elementByCss('body').text(),
() => getBrowserBodyText(browser),
/This is the about page/
)
} finally {
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
browser.close()
}
@ -36,8 +76,7 @@ export default (context, render) => {
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
try {
browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser
.elementByCss('p').text()
const text = await browser.elementByCss('p').text()
expect(text).toBe('This is the about page.')
aboutPage.replace('</div>', 'div')
@ -47,12 +86,20 @@ export default (context, render) => {
aboutPage.restore()
await check(
() => browser.elementByCss('body').text(),
() => getBrowserBodyText(browser),
/This is the about page/
)
} finally {
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
browser.close()
}
@ -72,9 +119,19 @@ export default (context, render) => {
aboutPage.restore()
await check(
() => browser.elementByCss('body').text(),
() => getBrowserBodyText(browser),
/This is the contact page/
)
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the contact page/
)
}
throw err
} finally {
aboutPage.restore()
if (browser) {
@ -99,7 +156,7 @@ export default (context, render) => {
aboutPage.restore()
await check(
() => browser.elementByCss('body').text(),
() => getBrowserBodyText(browser),
/This is the about page/
)
} finally {
@ -111,137 +168,195 @@ export default (context, render) => {
})
it('should recover from errors in the render function', async () => {
const browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser
.elementByCss('p').text()
expect(text).toBe('This is the about page.')
let browser
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
aboutPage.replace('return', 'throw new Error("an-expected-error");\nreturn')
try {
browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser.elementByCss('p').text()
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error/)
expect(text).toBe('This is the about page.')
aboutPage.restore()
aboutPage.replace('return', 'throw new Error("an-expected-error");\nreturn')
await check(
() => browser.elementByCss('body').text(),
/This is the about page/
)
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error/)
browser.close()
aboutPage.restore()
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
browser.close()
}
}
})
it('should recover after exporting an invalid page', async () => {
const browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser
.elementByCss('p').text()
expect(text).toBe('This is the about page.')
let browser
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
aboutPage.replace('export default', 'export default "not-a-page"\nexport const fn = ')
try {
browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser.elementByCss('p').text()
expect(text).toBe('This is the about page.')
await check(
() => browser.elementByCss('body').text(),
/The default export is not a React Component/
)
aboutPage.replace('export default', 'export default "not-a-page"\nexport const fn = ')
aboutPage.restore()
await check(
() => getBrowserBodyText(browser),
/The default export is not a React Component/
)
await check(
() => browser.elementByCss('body').text(),
/This is the about page/
)
aboutPage.restore()
browser.close()
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
browser.close()
}
}
})
it('should recover after a bad return from the render function', async () => {
const browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser
.elementByCss('p').text()
expect(text).toBe('This is the about page.')
let browser
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
aboutPage.replace('export default', 'export default () => /search/ \nexport const fn = ')
try {
browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser.elementByCss('p').text()
expect(text).toBe('This is the about page.')
await check(
() => browser.elementByCss('body').text(),
/Objects are not valid as a React child/
)
aboutPage.replace('export default', 'export default () => /search/ \nexport const fn = ')
aboutPage.restore()
await check(
() => getBrowserBodyText(browser),
/Objects are not valid as a React child/
)
await check(
() => browser.elementByCss('body').text(),
/This is the about page/
)
aboutPage.restore()
browser.close()
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
} catch (err) {
aboutPage.restore()
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
browser.close()
}
}
})
it('should recover from errors in getInitialProps in client', async () => {
const browser = await webdriver(context.appPort, '/hmr')
await browser.elementByCss('#error-in-gip-link').click()
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)
let browser
const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
erroredPage.replace('throw error', 'return {}')
try {
browser = await webdriver(context.appPort, '/hmr')
await browser.elementByCss('#error-in-gip-link').click()
await check(
() => browser.elementByCss('body').text(),
/Hello/
)
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)
erroredPage.restore()
browser.close()
erroredPage.replace('throw error', 'return {}')
await check(
() => getBrowserBodyText(browser),
/Hello/
)
erroredPage.restore()
await check(
async () => {
await browser.refresh()
const text = await browser.elementByCss('body').text()
if (text.includes('Hello')) {
await waitFor(2000)
throw new Error('waiting')
}
return getReactErrorOverlayContent(browser)
},
/an-expected-error-in-gip/
)
} catch (err) {
erroredPage.restore()
throw err
} finally {
if (browser) {
browser.close()
}
}
})
it('should recover after an error reported via SSR', async () => {
const browser = await webdriver(context.appPort, '/hmr/error-in-gip')
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)
const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
erroredPage.replace('throw error', 'return {}')
await check(
() => browser.elementByCss('body').text(),
/Hello/
)
erroredPage.restore()
browser.close()
})
it('should recover from 404 after a page has been added', async () => {
let browser
let newPage
const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
try {
browser = await webdriver(context.appPort, '/hmr/new-page')
browser = await webdriver(context.appPort, '/hmr/error-in-gip')
expect(await browser.elementByCss('body').text()).toMatch(/This page could not be found/)
expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)
// Add the page
newPage = new File(join(__dirname, '../', 'pages', 'hmr', 'new-page.js'))
newPage.write('export default () => (<div id="new-page">the-new-page</div>)')
const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
erroredPage.replace('throw error', 'return {}')
await check(
() => {
if (!browser.hasElementById('new-page')) {
throw new Error('waiting')
}
return browser.elementByCss('body').text()
},
/the-new-page/
() => getBrowserBodyText(browser),
/Hello/
)
// expect(await browser.elementByCss('body').text()).toMatch(/the-new-page/)
erroredPage.restore()
await check(
async () => {
await browser.refresh()
const text = await getBrowserBodyText(browser)
if (text.includes('Hello')) {
await waitFor(2000)
throw new Error('waiting')
}
return getReactErrorOverlayContent(browser)
},
/an-expected-error-in-gip/
)
} catch (err) {
erroredPage.restore()
throw err
} finally {
if (newPage) {
newPage.delete()
}
if (browser) {
browser.close()
}

View file

@ -1,7 +1,7 @@
/* global describe, it, expect */
import webdriver from 'next-webdriver'
import { waitFor } from 'next-test-utils'
import { waitFor, check, File } from 'next-test-utils'
import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
@ -10,7 +10,7 @@ export default (context, render) => {
it('should have config available on the client', async () => {
const browser = await webdriver(context.appPort, '/next-config')
// Wait for client side to load
await waitFor(5000)
await waitFor(10000)
const serverText = await browser.elementByCss('#server-only').text()
const serverClientText = await browser.elementByCss('#server-and-client').text()
@ -37,8 +37,7 @@ export default (context, render) => {
// Change the page
writeFileSync(pagePath, editedContent, 'utf8')
// wait for 5 seconds
await waitFor(5000)
await waitFor(10000)
try {
// Check whether the this page has reloaded or not.
@ -59,36 +58,47 @@ export default (context, render) => {
})
it('should update sass styles using hmr', async () => {
const file = new File(join(__dirname, '../', 'components', 'hello-webpack-sass.scss'))
let browser
try {
browser = await webdriver(context.appPort, '/webpack-css')
const pTag = await browser.elementByCss('.hello-world')
const initialFontSize = await pTag.getComputedCss('color')
expect(initialFontSize).toBe('rgba(255, 255, 0, 1)')
expect(
await browser.elementByCss('.hello-world').getComputedCss('color')
).toBe('rgba(255, 255, 0, 1)')
const pagePath = join(__dirname, '../', 'components', 'hello-webpack-sass.scss')
file.replace('yellow', 'red')
const originalContent = readFileSync(pagePath, 'utf8')
const editedContent = originalContent.replace('yellow', 'red')
await waitFor(10000)
// Change the page
writeFileSync(pagePath, editedContent, 'utf8')
await check(
async () => {
const tag = await browser.elementByCss('.hello-world')
const prop = await tag.getComputedCss('color')
// wait for 5 seconds
await waitFor(5000)
expect(prop).toBe('rgba(255, 0, 0, 1)')
return 'works'
},
/works/
)
try {
// Check whether the this page has reloaded or not.
const editedPTag = await browser.elementByCss('.hello-world')
const editedFontSize = await editedPTag.getComputedCss('color')
file.restore()
expect(editedFontSize).toBe('rgba(255, 0, 0, 1)')
} finally {
// Finally is used so that we revert the content back to the original regardless of the test outcome
// restore the about page content.
writeFileSync(pagePath, originalContent, 'utf8')
}
await waitFor(10000)
await check(
async () => {
const tag = await browser.elementByCss('.hello-world')
const prop = await tag.getComputedCss('color')
expect(prop).toBe('rgba(255, 255, 0, 1)')
return 'works'
},
/works/
)
} catch (err) {
file.restore()
throw err
} finally {
if (browser) {
browser.close()

View file

@ -1,6 +1,6 @@
/* global describe, it, expect */
import webdriver from 'next-webdriver'
import { waitFor } from 'next-test-utils'
import { check } from 'next-test-utils'
export default function (context) {
describe('Render via browser', () => {
@ -100,39 +100,36 @@ export default function (context) {
.elementByCss('#dynamic-imports-page').click()
.waitForElementByCss('#dynamic-imports-page')
// Wait until browser loads the dynamic import chunk
// TODO: We may need to find a better way to do this
await waitFor(5000)
await check(
() => browser.elementByCss('#dynamic-imports-page p').text(),
/Welcome to dynamic imports/
)
const text = await browser
.elementByCss('#dynamic-imports-page p').text()
expect(text).toBe('Welcome to dynamic imports.')
browser.close()
})
it('should render pages with url hash correctly', async () => {
const browser = await webdriver(context.port, '/')
let browser
try {
browser = await webdriver(context.port, '/')
// Check for the query string content
const text = await browser
.elementByCss('#with-hash').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
// Check for the query string content
const text = await browser
.elementByCss('#with-hash').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('zeit is awesome')
expect(text).toBe('zeit is awesome')
// Check for the hash
while (true) {
const hashText = await browser
.elementByCss('#hash').text()
if (/cool/.test(hashText)) {
break
await check(
() => browser.elementByCss('#hash').text(),
/cool/
)
} finally {
if (browser) {
browser.close()
}
}
browser.close()
})
it('should navigate even if used a button inside <Link />', async () => {

View file

@ -1,5 +1,7 @@
import Link from 'next/link'
export default () => <div>
<Link href='/nav/dynamic'><a id='to-dynamic'>To dynamic import</a></Link>
<Link href='/nav/dynamic'>
<a id='to-dynamic'>To dynamic import</a>
</Link>
</div>

View file

@ -7,7 +7,9 @@ import {
findPort,
launchApp,
killApp,
waitFor
waitFor,
check,
getBrowserBodyText
} from 'next-test-utils'
const context = {}
@ -63,12 +65,13 @@ describe('On Demand Entries', () => {
let browser
try {
browser = await webdriver(context.appPort, '/nav')
const text = await browser
.elementByCss('#to-dynamic').click()
.waitForElementByCss('.dynamic-page')
.elementByCss('p').text()
expect(text).toBe('Hello')
await browser.eval('document.getElementById("to-dynamic").click()')
await check(async () => {
const text = await getBrowserBodyText(browser)
return text
}, /Hello/)
} finally {
if (browser) {
browser.close()

View file

@ -152,7 +152,7 @@ export async function startStaticServer (dir) {
export async function check (contentFn, regex) {
let found = false
setTimeout(async () => {
const timeout = setTimeout(async () => {
if (found) {
return
}
@ -170,6 +170,7 @@ export async function check (contentFn, regex) {
const newContent = await contentFn()
if (regex.test(newContent)) {
found = true
clearTimeout(timeout)
break
}
await waitFor(1000)
@ -231,3 +232,7 @@ export async function getReactErrorOverlayContent (browser) {
}
return browser.eval(`document.querySelector('iframe').contentWindow.document.body.innerHTML`)
}
export function getBrowserBodyText (browser) {
return browser.eval('document.getElementsByTagName("body")[0].innerText')
}