diff --git a/package.json b/package.json index 94c20b43..8941c970 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/integration/basic/test/error-recovery.js b/test/integration/basic/test/error-recovery.js index 19f4c9c4..fd76db4b 100644 --- a/test/integration/basic/test/error-recovery.js +++ b/test/integration/basic/test/error-recovery.js @@ -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 () => (
the-new-page
)') + + 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') @@ -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 () => (
the-new-page
)') + 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() } diff --git a/test/integration/config/test/client.js b/test/integration/config/test/client.js index 6432857b..b116c7ad 100644 --- a/test/integration/config/test/client.js +++ b/test/integration/config/test/client.js @@ -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() diff --git a/test/integration/export/test/browser.js b/test/integration/export/test/browser.js index d1bc169a..529c4eca 100644 --- a/test/integration/export/test/browser.js +++ b/test/integration/export/test/browser.js @@ -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 ', async () => { diff --git a/test/integration/ondemand/pages/nav/index.js b/test/integration/ondemand/pages/nav/index.js index 0b873033..0f48260e 100644 --- a/test/integration/ondemand/pages/nav/index.js +++ b/test/integration/ondemand/pages/nav/index.js @@ -1,5 +1,7 @@ import Link from 'next/link' export default () =>
- To dynamic import + + To dynamic import +
diff --git a/test/integration/ondemand/test/index.test.js b/test/integration/ondemand/test/index.test.js index a163409a..f8b8dda2 100644 --- a/test/integration/ondemand/test/index.test.js +++ b/test/integration/ondemand/test/index.test.js @@ -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() diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index d6cfe316..6673f373 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -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') +}