diff --git a/packages/next/client/index.js b/packages/next/client/index.js
index 4650c74e1af3f24..bbbb9f60732ae85 100644
--- a/packages/next/client/index.js
+++ b/packages/next/client/index.js
@@ -114,6 +114,14 @@ class Container extends React.Component {
}
)
}
+
+ if (process.env.__NEXT_TEST_MODE) {
+ window.__NEXT_HYDRATED = true
+
+ if (window.__NEXT_HYDRATED_CB) {
+ window.__NEXT_HYDRATED_CB()
+ }
+ }
}
componentDidUpdate() {
diff --git a/test/integration/auto-export/test/index.test.js b/test/integration/auto-export/test/index.test.js
index 0b1d30fa66a12ad..a7216042c7398e7 100644
--- a/test/integration/auto-export/test/index.test.js
+++ b/test/integration/auto-export/test/index.test.js
@@ -8,7 +8,6 @@ import {
findPort,
killApp,
launchApp,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
@@ -42,14 +41,12 @@ const runTests = () => {
it('should update asPath after mount', async () => {
const browser = await webdriver(appPort, '/zeit/cmnt-2')
- await waitFor(500)
const html = await browser.eval(`document.documentElement.innerHTML`)
expect(html).toMatch(/\/zeit\/cmnt-2/)
})
it('should not replace URL with page name while asPath is delayed', async () => {
const browser = await webdriver(appPort, '/zeit/cmnt-1')
- await waitFor(500)
const val = await browser.eval(`!!window.pathnames.find(function(p) {
return p !== '/zeit/cmnt-1'
})`)
@@ -84,7 +81,6 @@ describe('Auto Export', () => {
it('should not show hydration warning from mismatching asPath', async () => {
const browser = await webdriver(appPort, '/zeit/cmnt-1')
- await waitFor(500)
const numCaught = await browser.eval(`window.caughtWarns.length`)
expect(numCaught).toBe(0)
diff --git a/test/integration/chunking/test/index.test.js b/test/integration/chunking/test/index.test.js
index 01a32228f705e14..f5563a6b554ef72 100644
--- a/test/integration/chunking/test/index.test.js
+++ b/test/integration/chunking/test/index.test.js
@@ -3,13 +3,7 @@
import { join } from 'path'
import express from 'express'
import http from 'http'
-import {
- nextBuild,
- waitFor,
- nextServer,
- promiseCall,
- stopApp,
-} from 'next-test-utils'
+import { nextBuild, nextServer, promiseCall, stopApp } from 'next-test-utils'
import { readdir, readFile, unlink, access } from 'fs-extra'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
@@ -134,7 +128,6 @@ describe('Chunking', () => {
it('should hydrate with granularChunks config', async () => {
const browser = await webdriver(appPort, '/page2')
- await waitFor(1000)
const text = await browser.elementByCss('#padded-str').text()
expect(text).toBe('__rad__')
@@ -144,7 +137,6 @@ describe('Chunking', () => {
it('should load chunks when navigating', async () => {
const browser = await webdriver(appPort, '/page3')
- await waitFor(1000)
const text = await browser
.elementByCss('#page2-link')
.click()
diff --git a/test/integration/config/test/client.js b/test/integration/config/test/client.js
index 9d09666a7a0f0ec..5181256e2381e73 100644
--- a/test/integration/config/test/client.js
+++ b/test/integration/config/test/client.js
@@ -8,8 +8,6 @@ export default (context, render) => {
describe('Configuration', () => {
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(10000)
const serverText = await browser.elementByCss('#server-only').text()
const serverClientText = await browser
diff --git a/test/integration/conflicting-public-file-page/test/index.test.js b/test/integration/conflicting-public-file-page/test/index.test.js
index cb5a7ee3c0d426b..2ac948bb92f214f 100644
--- a/test/integration/conflicting-public-file-page/test/index.test.js
+++ b/test/integration/conflicting-public-file-page/test/index.test.js
@@ -7,7 +7,6 @@ import {
findPort,
killApp,
renderViaHTTP,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
@@ -25,7 +24,6 @@ describe('Errors on conflict between public file and page file', () => {
/A conflicting public file and page file was found for path/
)
}
- await waitFor(1000)
await killApp(app)
})
diff --git a/test/integration/css-client-nav/test/index.test.js b/test/integration/css-client-nav/test/index.test.js
index 3fdbcf58db2c157..d5dfc8cba184a0a 100644
--- a/test/integration/css-client-nav/test/index.test.js
+++ b/test/integration/css-client-nav/test/index.test.js
@@ -5,7 +5,6 @@ import { remove } from 'fs-extra'
import {
nextBuild,
nextStart,
- waitFor,
findPort,
killApp,
renderViaHTTP,
@@ -80,9 +79,6 @@ describe('CSS Module client-side navigation in Production', () => {
let browser
try {
browser = await webdriver(appPort, '/blue')
-
- await waitFor(2000) // Ensure hydration
-
await browser.eval(`window.__did_not_ssr = 'make sure this is set'`)
const redColor = await browser.eval(
diff --git a/test/integration/css-modules/test/index.test.js b/test/integration/css-modules/test/index.test.js
index cb2eb58aeff4e56..3c0d865fd44dc09 100644
--- a/test/integration/css-modules/test/index.test.js
+++ b/test/integration/css-modules/test/index.test.js
@@ -178,7 +178,6 @@ xdescribe('Can hot reload CSS Module without losing state', () => {
// FIXME: this is broken
it('should update CSS color without remounting ', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000) // ensure application hydrates
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
diff --git a/test/integration/css/test/index.test.js b/test/integration/css/test/index.test.js
index 8d2b401e62a54e0..b4a971c74c934d9 100644
--- a/test/integration/css/test/index.test.js
+++ b/test/integration/css/test/index.test.js
@@ -304,7 +304,6 @@ describe('CSS Support', () => {
let browser
try {
browser = await webdriver(appPort, '/page1')
- await waitFor(2000) // ensure application hydrates
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
@@ -703,7 +702,6 @@ describe('CSS Support', () => {
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000) // ensure application hydrates
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
@@ -729,7 +727,6 @@ describe('CSS Support', () => {
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000) // ensure application hydrates
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
diff --git a/test/integration/duplicate-pages/test/index.test.js b/test/integration/duplicate-pages/test/index.test.js
index 2b2b50f5e411996..cbcd06fffcd6d0f 100644
--- a/test/integration/duplicate-pages/test/index.test.js
+++ b/test/integration/duplicate-pages/test/index.test.js
@@ -8,7 +8,6 @@ import {
launchApp,
renderViaHTTP,
killApp,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
@@ -34,7 +33,6 @@ describe('Handles Duplicate Pages', () => {
onStderr: handleOutput,
})
await renderViaHTTP(appPort, '/hello')
- await waitFor(3000)
await killApp(app)
expect(output).toMatch(/Duplicate page detected/)
})
diff --git a/test/integration/dynamic-routing/test/index.test.js b/test/integration/dynamic-routing/test/index.test.js
index 643c6407b82a322..4edbcae89075d77 100644
--- a/test/integration/dynamic-routing/test/index.test.js
+++ b/test/integration/dynamic-routing/test/index.test.js
@@ -304,7 +304,6 @@ function runTests(dev) {
expect(html).toMatch(/onmpost:.*pending/)
const browser = await webdriver(appPort, '/on-mount/post-1')
- await waitFor(1000)
const text = await browser.eval(`document.body.innerHTML`)
expect(text).toMatch(/onmpost:.*post-1/)
})
@@ -316,14 +315,12 @@ function runTests(dev) {
it('should update with a hash in the URL', async () => {
const browser = await webdriver(appPort, '/on-mount/post-1#abc')
- await waitFor(1000)
const text = await browser.eval(`document.body.innerHTML`)
expect(text).toMatch(/onmpost:.*post-1/)
})
it('should scroll to a hash on mount', async () => {
const browser = await webdriver(appPort, '/on-mount/post-1#item-400')
- await waitFor(1000)
const text = await browser.eval(`document.body.innerHTML`)
expect(text).toMatch(/onmpost:.*post-1/)
@@ -334,7 +331,6 @@ function runTests(dev) {
it('should scroll to a hash on client-side navigation', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(1000)
await browser.elementByCss('#view-dynamic-with-hash').click()
await browser.waitForElementByCss('p')
diff --git a/test/integration/empty-object-getInitialProps/test/index.test.js b/test/integration/empty-object-getInitialProps/test/index.test.js
index 951969217a548c8..e3773336850864d 100644
--- a/test/integration/empty-object-getInitialProps/test/index.test.js
+++ b/test/integration/empty-object-getInitialProps/test/index.test.js
@@ -49,7 +49,6 @@ describe('Empty Project', () => {
it('should show empty object warning during client transition', async () => {
const browser = await webdriver(appPort, '/static')
- await waitFor(1000)
await browser.eval(`(function() {
window.gotWarn = false
const origWarn = console.warn
diff --git a/test/integration/export-dynamic-pages-serverless/test/index.test.js b/test/integration/export-dynamic-pages-serverless/test/index.test.js
index dd2c576ee0ae301..c7fc27ac4462ae2 100644
--- a/test/integration/export-dynamic-pages-serverless/test/index.test.js
+++ b/test/integration/export-dynamic-pages-serverless/test/index.test.js
@@ -9,7 +9,6 @@ import {
startCleanStaticServer,
stopApp,
renderViaHTTP,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60
@@ -41,7 +40,6 @@ describe('Export Dyanmic Pages', () => {
expect.assertions(1)
const browser = await webdriver(port, '/regression/jeff-is-cool')
try {
- await waitFor(3000)
expect(await browser.eval(`window.__AS_PATHS`)).toEqual([
'/regression/jeff-is-cool',
])
diff --git a/test/integration/export-dynamic-pages/test/index.test.js b/test/integration/export-dynamic-pages/test/index.test.js
index dd2c576ee0ae301..c7fc27ac4462ae2 100644
--- a/test/integration/export-dynamic-pages/test/index.test.js
+++ b/test/integration/export-dynamic-pages/test/index.test.js
@@ -9,7 +9,6 @@ import {
startCleanStaticServer,
stopApp,
renderViaHTTP,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60
@@ -41,7 +40,6 @@ describe('Export Dyanmic Pages', () => {
expect.assertions(1)
const browser = await webdriver(port, '/regression/jeff-is-cool')
try {
- await waitFor(3000)
expect(await browser.eval(`window.__AS_PATHS`)).toEqual([
'/regression/jeff-is-cool',
])
diff --git a/test/integration/export-serverless/test/browser.js b/test/integration/export-serverless/test/browser.js
index c62984d088c438e..8a73de458198d61 100644
--- a/test/integration/export-serverless/test/browser.js
+++ b/test/integration/export-serverless/test/browser.js
@@ -1,6 +1,6 @@
/* eslint-env jest */
import webdriver from 'next-webdriver'
-import { check, waitFor, getBrowserBodyText } from 'next-test-utils'
+import { check, getBrowserBodyText } from 'next-test-utils'
export default function(context) {
describe('Render via browser', () => {
@@ -184,7 +184,6 @@ export default function(context) {
it('should update query after mount', async () => {
const browser = await webdriver(context.port, '/query-update?hello=world')
- await waitFor(2000)
const query = await browser.elementByCss('#query').text()
expect(JSON.parse(query)).toEqual({ hello: 'world', a: 'blue' })
await browser.close()
diff --git a/test/integration/export/test/browser.js b/test/integration/export/test/browser.js
index 201634536fe6475..92ea5604a50c248 100644
--- a/test/integration/export/test/browser.js
+++ b/test/integration/export/test/browser.js
@@ -1,6 +1,6 @@
/* eslint-env jest */
import webdriver from 'next-webdriver'
-import { check, waitFor, getBrowserBodyText } from 'next-test-utils'
+import { check, getBrowserBodyText } from 'next-test-utils'
export default function(context) {
describe('Render via browser', () => {
@@ -191,7 +191,6 @@ export default function(context) {
it('should update query after mount', async () => {
const browser = await webdriver(context.port, '/query-update?hello=world')
- await waitFor(2000)
const query = await browser.elementByCss('#query').text()
expect(JSON.parse(query)).toEqual({ hello: 'world', a: 'blue' })
await browser.close()
diff --git a/test/integration/future/test/index.test.js b/test/integration/future/test/index.test.js
index 3878b8d8e77d233..8988c97205a5411 100644
--- a/test/integration/future/test/index.test.js
+++ b/test/integration/future/test/index.test.js
@@ -8,7 +8,6 @@ import {
startApp,
stopApp,
renderViaHTTP,
- waitFor,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
@@ -34,7 +33,6 @@ describe('future.excludeDefaultMomentLocales', () => {
it('should load momentjs', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(5000)
expect(await browser.elementByCss('h1').text()).toMatch(/current time/i)
const locales = await browser.eval('moment.locales()')
expect(locales).toEqual(['en'])
diff --git a/test/integration/hydration/test/index.test.js b/test/integration/hydration/test/index.test.js
index 5a18abec58403b1..bca7263bf2feff6 100644
--- a/test/integration/hydration/test/index.test.js
+++ b/test/integration/hydration/test/index.test.js
@@ -3,13 +3,7 @@
import path from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'
-import {
- nextBuild,
- nextStart,
- findPort,
- waitFor,
- killApp,
-} from 'next-test-utils'
+import { nextBuild, nextStart, findPort, killApp } from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
const appDir = path.join(__dirname, '..')
@@ -27,7 +21,6 @@ describe('Hydration', () => {
it('Hydrates correctly', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000)
expect(await browser.eval('window.didHydrate')).toBe(true)
})
})
diff --git a/test/integration/initial-ref/test/index.test.js b/test/integration/initial-ref/test/index.test.js
index a418167f7db847b..6ec48eddd025ef9 100644
--- a/test/integration/initial-ref/test/index.test.js
+++ b/test/integration/initial-ref/test/index.test.js
@@ -7,7 +7,6 @@ import {
nextStart,
launchApp,
findPort,
- waitFor,
killApp,
} from 'next-test-utils'
@@ -19,12 +18,11 @@ let appPort
const runTest = () => {
it('Has correct initial ref values', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000)
expect(await browser.elementByCss('#ref-val').text()).toContain('76px')
})
}
-describe('Hydration', () => {
+describe('Initial Refs', () => {
describe('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
diff --git a/test/integration/invalid-href/test/index.test.js b/test/integration/invalid-href/test/index.test.js
index 0bd3d46fb917c91..8382ed9b43b95fc 100644
--- a/test/integration/invalid-href/test/index.test.js
+++ b/test/integration/invalid-href/test/index.test.js
@@ -64,7 +64,6 @@ const showsError = async (
const noError = async (pathname, click = false) => {
const browser = await webdriver(appPort, '/')
- await waitFor(2000)
await browser.eval(`(function() {
window.caughtErrors = []
window.addEventListener('error', function (error) {
diff --git a/test/integration/link-ref/test/index.test.js b/test/integration/link-ref/test/index.test.js
index d51489091a8cc84..cab64da4cafc8bc 100644
--- a/test/integration/link-ref/test/index.test.js
+++ b/test/integration/link-ref/test/index.test.js
@@ -35,7 +35,6 @@ const noError = async pathname => {
const didPrefetch = async pathname => {
const browser = await webdriver(appPort, pathname)
- await waitFor(500)
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
diff --git a/test/integration/next-dynamic/test/index.test.js b/test/integration/next-dynamic/test/index.test.js
index 6e16fcee311ab13..01db1ae88fd498d 100644
--- a/test/integration/next-dynamic/test/index.test.js
+++ b/test/integration/next-dynamic/test/index.test.js
@@ -7,7 +7,6 @@ import {
findPort,
launchApp,
killApp,
- waitFor,
runNextCommand,
nextServer,
startApp,
@@ -29,7 +28,6 @@ function runTests() {
it('should render dynamic server rendered values on client mount', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(5000)
const text = await browser.elementByCss('#first-render').text()
// Failure case is 'Index3'
diff --git a/test/integration/next-plugins/test/index.test.js b/test/integration/next-plugins/test/index.test.js
index dcedcfd0c998ed6..2024a179ad58116 100644
--- a/test/integration/next-plugins/test/index.test.js
+++ b/test/integration/next-plugins/test/index.test.js
@@ -8,7 +8,6 @@ import {
findPort,
launchApp,
killApp,
- waitFor,
nextBuild,
nextStart,
renderViaHTTP,
@@ -57,7 +56,6 @@ function runTests() {
it('should call clientInit from plugin correctly', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(250)
expect(await browser.eval('window.didClientInit')).toBe(true)
})
@@ -125,7 +123,6 @@ describe('Next.js plugins', () => {
it('should expose a plugins config', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(500)
expect(await browser.eval('window.initClientConfig')).toBe('world')
})
})
diff --git a/test/integration/polyfills/test/index.test.js b/test/integration/polyfills/test/index.test.js
index 27e404a6770baf2..d14af807461f80b 100644
--- a/test/integration/polyfills/test/index.test.js
+++ b/test/integration/polyfills/test/index.test.js
@@ -1,13 +1,7 @@
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
-import {
- nextBuild,
- findPort,
- waitFor,
- nextStart,
- killApp,
-} from 'next-test-utils'
+import { nextBuild, findPort, nextStart, killApp } from 'next-test-utils'
import webdriver from 'next-webdriver'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
@@ -34,7 +28,6 @@ describe('Polyfills', () => {
it('should alias fetch', async () => {
const browser = await webdriver(appPort, '/fetch')
- await waitFor(1000)
const text = await browser.elementByCss('#test-status').text()
expect(text).toBe('pass')
diff --git a/test/integration/preload-viewport/test/index.test.js b/test/integration/preload-viewport/test/index.test.js
index 8b49bcf85bb1f1c..96e246fe0f871f4 100644
--- a/test/integration/preload-viewport/test/index.test.js
+++ b/test/integration/preload-viewport/test/index.test.js
@@ -36,7 +36,6 @@ describe('Prefetching Links in viewport', () => {
let browser
try {
browser = await webdriver(appPort, '/')
- await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
@@ -124,7 +123,6 @@ describe('Prefetching Links in viewport', () => {
it('should not prefetch when prefetch is explicitly set to false', async () => {
const browser = await webdriver(appPort, '/opt-out')
- await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
let found = false
@@ -141,7 +139,6 @@ describe('Prefetching Links in viewport', () => {
it('should not duplicate prefetches', async () => {
const browser = await webdriver(appPort, '/multi-prefetch')
- await waitFor(2 * 1000)
const links = await browser.elementsByCss('link[rel=prefetch]')
@@ -163,7 +160,6 @@ describe('Prefetching Links in viewport', () => {
// info: both `/` and `/de-duped` ref the `/first` page, which we don't
// want to be re-fetched/re-observed.
const browser = await webdriver(appPort, '/')
- await waitFor(2 * 1000)
await browser.eval(`(function() {
window.calledPrefetch = false
window.next.router.prefetch = function() {
diff --git a/test/integration/prerender/test/index.test.js b/test/integration/prerender/test/index.test.js
index 928add00f53a687..036070238794e92 100644
--- a/test/integration/prerender/test/index.test.js
+++ b/test/integration/prerender/test/index.test.js
@@ -150,9 +150,6 @@ const navigateTest = (dev = false) => {
let text = await browser.elementByCss('p').text()
expect(text).toMatch(/hello.*?world/)
- // hydration
- await waitFor(2500)
-
// go to /another
async function goFromHomeToAnother() {
await browser.eval('window.beforeAnother = true')
@@ -322,7 +319,6 @@ const runTests = (dev = false) => {
it('should parse query values on mount correctly', async () => {
const browser = await webdriver(appPort, '/blog/post-1?another=value')
- await waitFor(2000)
const text = await browser.elementByCss('#query').text()
expect(text).toMatch(/another.*?value/)
expect(text).toMatch(/post.*?post-1/)
@@ -330,7 +326,6 @@ const runTests = (dev = false) => {
it('should reload page on failed data request', async () => {
const browser = await webdriver(appPort, '/')
- await waitFor(500)
await browser.eval('window.beforeClick = true')
await browser.elementByCss('#broken-post').click()
await waitFor(1000)
diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js
index 56f48a3676f7d2c..1533089d0a4f829 100644
--- a/test/integration/production/test/index.test.js
+++ b/test/integration/production/test/index.test.js
@@ -608,7 +608,6 @@ describe('Production Usage', () => {
it('should handle AMP correctly in IE', async () => {
const browser = await webdriver(appPort, '/some-amp')
- await waitFor(1000)
const text = await browser.elementByCss('p').text()
expect(text).toBe('Not AMP')
})
diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js
index a76efbc36aea923..93eac4bda209519 100644
--- a/test/integration/production/test/security.js
+++ b/test/integration/production/test/security.js
@@ -6,10 +6,10 @@ import { renderViaHTTP, getBrowserBodyText, waitFor } from 'next-test-utils'
import { recursiveReadDir } from 'next/dist/lib/recursive-readdir'
import { homedir } from 'os'
-// Does the same evaluation checking for INJECTED for 15 seconds, triggering every 500ms
+// Does the same evaluation checking for INJECTED for 5 seconds after hydration, triggering every 500ms
async function checkInjected(browser) {
const start = Date.now()
- while (Date.now() - start < 15000) {
+ while (Date.now() - start < 5000) {
const bodyText = await getBrowserBodyText(browser)
if (/INJECTED/.test(bodyText)) {
throw new Error('Vulnerable to XSS attacks')
diff --git a/test/integration/size-limit/test/index.test.js b/test/integration/size-limit/test/index.test.js
index 10ae42c581ad4ff..70fd0d332d00dd2 100644
--- a/test/integration/size-limit/test/index.test.js
+++ b/test/integration/size-limit/test/index.test.js
@@ -81,7 +81,7 @@ describe('Production response size', () => {
)
// These numbers are without gzip compression!
- const delta = responseSizeKilobytes - 234
+ const delta = responseSizeKilobytes - 235
expect(delta).toBeLessThanOrEqual(0) // don't increase size
expect(delta).toBeGreaterThanOrEqual(-1) // don't decrease size without updating target
})
diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js
index a4bfcb75f6d71a0..0a66a45232c9aef 100644
--- a/test/lib/next-test-utils.js
+++ b/test/lib/next-test-utils.js
@@ -82,7 +82,12 @@ export function runNextCommand(argv, options = {}) {
const nextBin = path.join(nextDir, 'dist/bin/next')
const cwd = options.cwd || nextDir
// Let Next.js decide the environment
- const env = { ...process.env, ...options.env, NODE_ENV: '' }
+ const env = {
+ ...process.env,
+ ...options.env,
+ NODE_ENV: '',
+ __NEXT_TEST_MODE: 'true',
+ }
return new Promise((resolve, reject) => {
console.log(`Running command "next ${argv.join(' ')}"`)
diff --git a/test/lib/next-webdriver.d.ts b/test/lib/next-webdriver.d.ts
new file mode 100644
index 000000000000000..9c243dcc6f69237
--- /dev/null
+++ b/test/lib/next-webdriver.d.ts
@@ -0,0 +1,28 @@
+interface Chain {
+ elementByCss: () => Chain
+ elementById: () => Chain
+ getValue: () => Chain
+ text: () => Chain
+ type: () => Chain
+ moveTo: () => Chain
+ getComputedCss: () => Chain
+ getAttribute: () => Chain
+ hasElementByCssSelector: () => Chain
+ click: () => Chain
+ elementsByCss: () => Chain
+ waitForElementByCss: () => Chain
+ eval: () => Chain
+ log: () => Chain
+ url: () => Chain
+ back: () => Chain
+ forward: () => Chain
+ refresh: () => Chain
+ close: () => Chain
+ quit: () => Chain
+}
+
+export default function(
+ appPort: number,
+ path: string,
+ waitHydration?: boolean
+): Promise
diff --git a/test/lib/next-webdriver.js b/test/lib/next-webdriver.js
index faf241c16992e2b..bd6d975a50ef032 100644
--- a/test/lib/next-webdriver.js
+++ b/test/lib/next-webdriver.js
@@ -1,7 +1,9 @@
+///
import os from 'os'
import path from 'path'
import fetch from 'node-fetch'
-import { until, Builder, By } from 'selenium-webdriver'
+import Chain from './wd-chain'
+import { Builder, By } from 'selenium-webdriver'
import { Options as ChromeOptions } from 'selenium-webdriver/chrome'
import { Options as SafariOptions } from 'selenium-webdriver/safari'
import { Options as FireFoxOptions } from 'selenium-webdriver/firefox'
@@ -153,7 +155,7 @@ const freshWindow = async () => {
await browser.switchTo().window(newWindow)
}
-export default async (appPort, path) => {
+export default async (appPort, path, waitHydration = true) => {
if (!initialWindow) {
initialWindow = await browser.getWindowHandle()
}
@@ -173,137 +175,33 @@ export default async (appPort, path) => {
await browser.get(url)
console.log(`\n> Loaded browser with ${url}\n`)
- class Chain {
- updateChain(nextCall) {
- if (!this.promise) {
- this.promise = Promise.resolve()
- }
- this.promise = this.promise.then(nextCall)
- this.then = cb => this.promise.then(cb)
- this.catch = cb => this.promise.catch(cb)
- this.finally = cb => this.promise.finally(cb)
- return this
- }
-
- elementByCss(sel) {
- return this.updateChain(() =>
- browser.findElement(By.css(sel)).then(el => {
- el.sel = sel
- el.text = () => el.getText()
- el.getComputedCss = prop => el.getCssValue(prop)
- el.type = text => el.sendKeys(text)
- el.getValue = () =>
- browser.executeScript(
- `return document.querySelector('${sel}').value`
- )
- return el
- })
- )
- }
-
- elementById(sel) {
- return this.elementByCss(`#${sel}`)
- }
+ // Wait for application to hydrate
+ if (waitHydration) {
+ console.log(`\n> Waiting hydration for ${url}\n`)
+ await browser.executeAsyncScript(function() {
+ var callback = arguments[arguments.length - 1]
- getValue() {
- return this.updateChain(el =>
- browser.executeScript(
- `return document.querySelector('${el.sel}').value`
- )
- )
- }
-
- text() {
- return this.updateChain(el => el.getText())
- }
-
- type(text) {
- return this.updateChain(el => el.sendKeys(text))
- }
-
- moveTo() {
- return this.updateChain(el => {
- return browser
- .actions()
- .move({ origin: el })
- .perform()
- .then(() => el)
- })
- }
-
- getComputedCss(prop) {
- return this.updateChain(el => {
- return el.getCssValue(prop)
- })
- }
-
- getAttribute(attr) {
- return this.updateChain(el => el.getAttribute(attr))
- }
-
- hasElementByCssSelector(sel) {
- return this.eval(`document.querySelector('${sel}')`)
- }
-
- click() {
- return this.updateChain(el => {
- return el.click().then(() => el)
- })
- }
-
- elementsByCss(sel) {
- return this.updateChain(() => browser.findElements(By.css(sel)))
- }
-
- waitForElementByCss(sel, timeout) {
- return this.updateChain(() =>
- browser.wait(until.elementLocated(By.css(sel), timeout))
- )
- }
-
- eval(snippet) {
- if (typeof snippet === 'string' && !snippet.startsWith('return')) {
- snippet = `return ${snippet}`
+ // if it's not a Next.js app return
+ if (document.documentElement.innerHTML.indexOf('__NEXT_DATA__') === -1) {
+ callback()
}
- return this.updateChain(() => browser.executeScript(snippet))
- }
-
- log(type) {
- return this.updateChain(() =>
- browser
- .manage()
- .logs()
- .get(type)
- )
- }
-
- url() {
- return this.updateChain(() => browser.getCurrentUrl())
- }
- back() {
- return this.updateChain(() => browser.navigate().back())
- }
-
- forward() {
- return this.updateChain(() => browser.navigate().forward())
- }
-
- refresh() {
- return this.updateChain(() => browser.navigate().refresh())
- }
-
- close() {
- return this.updateChain(() => Promise.resolve())
- }
- quit() {
- return this.close()
- }
+ if (window.__NEXT_HYDRATED) {
+ callback()
+ } else {
+ var timeout = setTimeout(callback, 10 * 1000)
+ window.__NEXT_HYDRATED_CB = function() {
+ clearTimeout(timeout)
+ callback()
+ }
+ }
+ })
+ console.log(`\n> Hydration complete for ${url}\n`)
}
const promiseProp = new Set(['then', 'catch', 'finally'])
- return new Proxy(new Chain(), {
+ return new Proxy(new Chain(browser), {
get(obj, prop) {
if (obj[prop] || promiseProp.has(prop)) {
return obj[prop]
diff --git a/test/lib/wd-chain.js b/test/lib/wd-chain.js
new file mode 100644
index 000000000000000..0d402eee575f1a8
--- /dev/null
+++ b/test/lib/wd-chain.js
@@ -0,0 +1,133 @@
+import { until, By } from 'selenium-webdriver'
+
+export default class Chain {
+ constructor(browser) {
+ this.browser = browser
+ }
+
+ updateChain(nextCall) {
+ if (!this.promise) {
+ this.promise = Promise.resolve()
+ }
+ this.promise = this.promise.then(nextCall)
+ this.then = cb => this.promise.then(cb)
+ this.catch = cb => this.promise.catch(cb)
+ this.finally = cb => this.promise.finally(cb)
+ return this
+ }
+
+ elementByCss(sel) {
+ return this.updateChain(() =>
+ this.browser.findElement(By.css(sel)).then(el => {
+ el.sel = sel
+ el.text = () => el.getText()
+ el.getComputedCss = prop => el.getCssValue(prop)
+ el.type = text => el.sendKeys(text)
+ el.getValue = () =>
+ this.browser.executeScript(
+ `return document.querySelector('${sel}').value`
+ )
+ return el
+ })
+ )
+ }
+
+ elementById(sel) {
+ return this.elementByCss(`#${sel}`)
+ }
+
+ getValue() {
+ return this.updateChain(el =>
+ this.browser.executeScript(
+ `return document.querySelector('${el.sel}').value`
+ )
+ )
+ }
+
+ text() {
+ return this.updateChain(el => el.getText())
+ }
+
+ type(text) {
+ return this.updateChain(el => el.sendKeys(text))
+ }
+
+ moveTo() {
+ return this.updateChain(el => {
+ return this.browser
+ .actions()
+ .move({ origin: el })
+ .perform()
+ .then(() => el)
+ })
+ }
+
+ getComputedCss(prop) {
+ return this.updateChain(el => {
+ return el.getCssValue(prop)
+ })
+ }
+
+ getAttribute(attr) {
+ return this.updateChain(el => el.getAttribute(attr))
+ }
+
+ hasElementByCssSelector(sel) {
+ return this.eval(`document.querySelector('${sel}')`)
+ }
+
+ click() {
+ return this.updateChain(el => {
+ return el.click().then(() => el)
+ })
+ }
+
+ elementsByCss(sel) {
+ return this.updateChain(() => this.browser.findElements(By.css(sel)))
+ }
+
+ waitForElementByCss(sel, timeout) {
+ return this.updateChain(() =>
+ this.browser.wait(until.elementLocated(By.css(sel), timeout))
+ )
+ }
+
+ eval(snippet) {
+ if (typeof snippet === 'string' && !snippet.startsWith('return')) {
+ snippet = `return ${snippet}`
+ }
+ return this.updateChain(() => this.browser.executeScript(snippet))
+ }
+
+ log(type) {
+ return this.updateChain(() =>
+ this.browser
+ .manage()
+ .logs()
+ .get(type)
+ )
+ }
+
+ url() {
+ return this.updateChain(() => this.browser.getCurrentUrl())
+ }
+
+ back() {
+ return this.updateChain(() => this.browser.navigate().back())
+ }
+
+ forward() {
+ return this.updateChain(() => this.browser.navigate().forward())
+ }
+
+ refresh() {
+ return this.updateChain(() => this.browser.navigate().refresh())
+ }
+
+ close() {
+ return this.updateChain(() => Promise.resolve())
+ }
+ quit() {
+ return this.close()
+ }
+}
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 000000000000000..010d7d03ce9685e
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "es6",
+ "allowJs": true,
+ "baseUrl": "./lib"
+ },
+ "include": ["./**/*"]
+}