From 5ed71fcb8f97e1aea92580f22ff09037665c1df3 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 27 Jun 2017 22:02:46 -0700 Subject: [PATCH] Test the 'networkidle' navigation logic This patch adds a test to verify that navigation properly waits for the network to become idle. References #10 --- lib/Page.js | 2 ++ test/StaticServer.js | 59 ++++++++++++++++++++++++++++++++++++ test/assets/networkidle.html | 5 +++ test/test.js | 40 ++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 test/assets/networkidle.html diff --git a/lib/Page.js b/lib/Page.js index 740e702c..b135bf34 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -70,6 +70,7 @@ class Page extends EventEmitter { client.on('Network.responseReceived', event => this.emit(Page.Events.ResponseReceived, event.response)); client.on('Network.loadingFailed', event => this.emit(Page.Events.ResourceLoadingFailed, event)); + client.on('Page.loadEventFired', event => this.emit(Page.Events.Load)); client.on('Network.requestIntercepted', event => this._onRequestIntercepted(event)); client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); @@ -632,6 +633,7 @@ Page.Events = { FrameAttached: 'frameattached', FrameDetached: 'framedetached', FrameNavigated: 'framenavigated', + Load: 'load', }; module.exports = Page; diff --git a/test/StaticServer.js b/test/StaticServer.js index c37e7bbd..b7fca60c 100644 --- a/test/StaticServer.js +++ b/test/StaticServer.js @@ -20,6 +20,9 @@ let fs = require('fs'); let path = require('path'); let mime = require('mime'); +const fulfillSymbol = Symbol('fullfill callback'); +const rejectSymbol = Symbol('reject callback'); + class StaticServer { /** * @param {string} dirPath @@ -29,13 +32,69 @@ class StaticServer { this._server = http.createServer(this._onRequest.bind(this)); this._server.listen(port); this._dirPath = dirPath; + + /** @type {!Map} */ + this._routes = new Map(); + /** @type {!Map} */ + this._requestSubscribers = new Map(); } stop() { this._server.close(); } + /** + * @param {string} path + * @param {function(!IncomingMessage, !ServerResponse)} handler + */ + setRoute(path, handler) { + this._routes.set(path, handler); + } + + /** + * @param {string} path + * @return {!Promise} + */ + waitForRequest(path) { + let promise = this._requestSubscribers.get(path); + if (promise) + return promise; + let fulfill, reject; + promise = new Promise((f, r) => { + fulfill = f; + reject = r; + }); + promise[fulfillSymbol] = fulfill; + promise[rejectSymbol] = reject; + this._requestSubscribers.set(path, promise); + return promise; + } + + reset() { + this._routes.clear(); + let error = new Error('Static Server has been reset'); + for (let subscriber of this._requestSubscribers.values()) + subscriber[rejectSymbol].call(null, error); + this._requestSubscribers.clear(); + } + _onRequest(request, response) { + let pathName = url.parse(request.url).path; + // Notify request subscriber. + if (this._requestSubscribers.has(pathName)) + this._requestSubscribers.get(pathName)[fulfillSymbol].call(null, request); + let handler = this._routes.get(pathName); + if (handler) + handler.call(null, request, response); + else + this.defaultHandler(request, response); + } + + /** + * @param {!IncomingMessage} request + * @param {!ServerResponse} response + */ + defaultHandler(request, response) { let pathName = url.parse(request.url).path; if (pathName === '/') pathName = '/index.html'; diff --git a/test/assets/networkidle.html b/test/assets/networkidle.html new file mode 100644 index 00000000..c95be2e7 --- /dev/null +++ b/test/assets/networkidle.html @@ -0,0 +1,5 @@ + diff --git a/test/test.js b/test/test.js index f9d3f121..6b4f76df 100644 --- a/test/test.js +++ b/test/test.js @@ -43,6 +43,7 @@ describe('Puppeteer', function() { beforeEach(SX(async function() { page = await browser.newPage(); + staticServer.reset(); GoldenUtils.addMatchers(jasmine); })); @@ -112,6 +113,45 @@ describe('Puppeteer', function() { let success = await page.navigate(EMPTY_PAGE); expect(success).toBe(true); })); + it('should wait for network idle to succeed navigation', SX(async function() { + let responses = []; + // Hold on a bunch of requests without answering. + staticServer.setRoute('/fetch-request-a.js', (req, res) => responses.push(res)); + staticServer.setRoute('/fetch-request-b.js', (req, res) => responses.push(res)); + staticServer.setRoute('/fetch-request-c.js', (req, res) => responses.push(res)); + let fetchResourcesRequested = Promise.all([ + staticServer.waitForRequest('/fetch-request-a.js'), + staticServer.waitForRequest('/fetch-request-b.js'), + staticServer.waitForRequest('/fetch-request-c.js'), + ]); + // Navigate to a page which loads immediately and then does a bunch of + // requests via javascript's fetch method. + let navigationPromise = page.navigate(STATIC_PREFIX + '/networkidle.html', { + minTime: 50 // Give page time to request more resources dynamically. + }); + // Track when the navigation gets completed. + let navigationFinished = false; + navigationPromise.then(() => navigationFinished = true); + + // Wait for the page's 'load' event. + await new Promise(fulfill => page.once('load', fulfill)); + expect(navigationFinished).toBe(false); + + // Wait for all three resources to be requested. + await fetchResourcesRequested; + + // Expect navigation still to be not finished. + expect(navigationFinished).toBe(false); + + // Respond to all requests. + for (let response of responses) { + response.statusCode = 404; + response.end(`File not found`); + } + let success = await navigationPromise; + // Expect navigation to succeed. + expect(success).toBe(true); + })); }); describe('Page.setInPageCallback', function() {