Change Page.navigate to return main resource response

This patch changes Page.navigate API:
- Page.navigate now resolves to the main page response
- Page.navigate throws errors if there's no main page response,
  e.g. in case of SSL errors, max navigation timeout,
  or invalid url.

This patch also adds httpsServer with a self-signed certificates
for the testing purposes.

Fixes #10.
This commit is contained in:
Andrey Lushnikov 2017-07-10 15:09:52 -07:00
parent a7e91dc126
commit 50d9c186b5
8 changed files with 251 additions and 64 deletions

View File

@ -21,7 +21,7 @@
* [page.httpHeaders()](#pagehttpheaders) * [page.httpHeaders()](#pagehttpheaders)
* [page.injectFile(filePath)](#pageinjectfilefilepath) * [page.injectFile(filePath)](#pageinjectfilefilepath)
* [page.mainFrame()](#pagemainframe) * [page.mainFrame()](#pagemainframe)
* [page.navigate(url)](#pagenavigateurl) * [page.navigate(url, options)](#pagenavigateurl-options)
* [page.plainText()](#pageplaintext) * [page.plainText()](#pageplaintext)
* [page.printToPDF(filePath[, options])](#pageprinttopdffilepath-options) * [page.printToPDF(filePath[, options])](#pageprinttopdffilepath-options)
* [page.screenshot([options])](#pagescreenshotoptions) * [page.screenshot([options])](#pagescreenshotoptions)
@ -162,12 +162,22 @@ Pages could be closed by `page.close()` method.
#### page.mainFrame() #### page.mainFrame()
#### page.navigate(url) #### page.navigate(url, options)
- `url` <[string]> URL to navigate page to - `url` <[string]> URL to navigate page to
- returns: <[Promise]<[boolean]>> Promise which resolves when the page is navigated. The promise resolves to: - `options` <[Object]> Navigation parameters which might have the following properties:
- `true` if the navigation succeeds and page's `load` event is fired. - `maxTime` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds.
- `false` if the navigation fails due to bad URL or SSL errors. - `waitFor` <[string]> When to consider navigation succeeded, defaults to `load`. Could be either:
- `load` - consider navigation to be finished when the `load` event is fired.
- `networkidle` - consider navigation to be finished when the network activity stays "idle" for at least `networkIdleTimeout`ms.
- `networkIdleInflight` <[number]> Maximum amount of inflight requests which are considered "idle". Takes effect only with `waitFor: 'networkidle'` parameter.
- `networkIdleTimeout` <[number]> A timeout to wait before completing navigation. Takes effect only with `waitFor: 'networkidle'` parameter.
- returns: <[Promise]<[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
The `page.navigate` will throw an error if:
- there's an SSL error (e.g. in case of self-signed certificates).
- target URL is invalid.
- the `maxTime` is exceeded during navigation.
#### page.plainText() #### page.plainText()

View File

@ -34,26 +34,28 @@ class Navigator {
} }
/** /**
* @return {!Promise<boolean>} * @return {!Promise}
*/ */
async navigate() { async navigate() {
this._init(); this._init();
let certificateError = new Promise(fulfill => this._client.once('Security.certificateError', fulfill)).then(() => false); let certificateError = new Promise(fulfill => this._client.once('Security.certificateError', fulfill))
let networkIdle = new Promise(fulfill => this._networkIdleCallback = fulfill).then(() => true); .then(error => new Error('SSL Certiciate error: ' + error.errorType));
let loadEventFired = new Promise(fulfill => this._client.once('Page.loadEventFired', fulfill)).then(() => true); let networkIdle = new Promise(fulfill => this._networkIdleCallback = fulfill).then(() => null);
let watchdog = new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._maxTime)).then(() => false); let loadEventFired = new Promise(fulfill => this._client.once('Page.loadEventFired', fulfill)).then(() => null);
let watchdog = new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._maxTime)).then(() => new Error('Navigation Timeout Exceeded: ' + this._maxTime + 'ms exceeded'));
// Await for the command to throw exception in case of illegal arguments. // Await for the command to throw exception in case of illegal arguments.
try { try {
await this._client.send('Page.navigate', {url: this._url, referrer: this._referrer}); await this._client.send('Page.navigate', {url: this._url, referrer: this._referrer});
} catch (e) { } catch (e) {
this._cleanup(); this._cleanup();
return false; throw e;
} }
let result = await Promise.race([certificateError, watchdog, this._waitFor === 'load' ? loadEventFired : networkIdle]); const error = await Promise.race([certificateError, watchdog, this._waitFor === 'load' ? loadEventFired : networkIdle]);
this._cleanup(); this._cleanup();
return result; if (error)
throw error;
} }
/** /**

View File

@ -260,10 +260,26 @@ class Page extends EventEmitter {
/** /**
* @param {string} html * @param {string} html
* @param {!Object=} options * @param {!Object=} options
* @return {!Promise<boolean>} * @return {!Promise<!Response>}
*/ */
navigate(url, options) { async navigate(url, options) {
return new Navigator(this._client, url, this._networkManager.httpHeaders().referer, options).navigate(); /** @type {!Map<string, !Response>} */
const responses = new Map();
const onResponse = response => responses.set(response.url, response);
const navigator = new Navigator(this._client, url, this._networkManager.httpHeaders().referer, options);
try {
this._networkManager.on(NetworkManager.Events.Response, onResponse);
await navigator.navigate();
} catch (e) {
this._networkManager.removeListener(NetworkManager.Events.Response, onResponse);
throw e;
}
this._networkManager.removeListener(NetworkManager.Events.Response, onResponse);
const response = responses.get(this.mainFrame().url());
console.assert(response);
return response;
} }
/** /**

View File

@ -355,22 +355,25 @@ class WebPage {
this._deferEvaluate = false; this._deferEvaluate = false;
this.loading = true; this.loading = true;
this.loadingProgress = 50; this.loadingProgress = 50;
this._page.navigate(url).then(result => {
const handleNavigation = (error, response) => {
this.loadingProgress = 100; this.loadingProgress = 100;
this.loading = false; this.loading = false;
let status = result ? 'success' : 'fail'; if (error) {
if (!result) {
this.onResourceError.call(null, { this.onResourceError.call(null, {
url, url,
errorString: 'SSL handshake failed' errorString: 'SSL handshake failed'
}); });
} }
let status = error ? 'fail' : 'success';
if (this.onLoadFinished) if (this.onLoadFinished)
this.onLoadFinished.call(null, status); this.onLoadFinished.call(null, status);
if (callback) if (callback)
callback.call(null, status); callback.call(null, status);
this.loadingProgress = 0; this.loadingProgress = 0;
}); };
this._page.navigate(url).then(response => handleNavigation(null, response))
.catch(e => handleNavigation(e, null));
} }
/** /**

View File

@ -15,6 +15,7 @@
*/ */
let http = require('http'); let http = require('http');
let https = require('https');
let url = require('url'); let url = require('url');
let fs = require('fs'); let fs = require('fs');
let path = require('path'); let path = require('path');
@ -39,8 +40,27 @@ class SimpleServer {
/** /**
* @param {string} dirPath * @param {string} dirPath
* @param {number} port * @param {number} port
* @return {!SimpleServer}
*/ */
constructor(dirPath, port) { static async createHTTPS(dirPath, port) {
let server = new SimpleServer(dirPath, port, {
key: fs.readFileSync(path.join(__dirname, 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'cert.pem')),
passphrase: 'aaaa',
});
await new Promise(x => server._server.once('listening', x));
return server;
}
/**
* @param {string} dirPath
* @param {number} port
* @param {!Object=} sslOptions
*/
constructor(dirPath, port, sslOptions) {
if (sslOptions)
this._server = https.createServer(sslOptions, this._onRequest.bind(this));
else
this._server = http.createServer(this._onRequest.bind(this)); this._server = http.createServer(this._onRequest.bind(this));
this._server.on('connection', socket => this._onSocket(socket)); this._server.on('connection', socket => this._onSocket(socket));
this._wsServer = new WebSocketServer({server: this._server}); this._wsServer = new WebSocketServer({server: this._server});

31
test/server/cert.pem Normal file
View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFXzCCA0egAwIBAgIJAM+8uXXn61zZMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwIBcNMTcwNzEwMjI1MDA2WhgPMzAxNjExMTAyMjUwMDZa
MEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQCfsIbw1Q91wooUdSu5tDiyrSYB6ZQmY49Y141KQ0ll0ZXzf1sPTpPg
OuBjJE8Fre2Wn3jJ0SfLFyQBMvE49hqWyY/U5Xc367ujqKFQVoItnoV5MM2TPu5J
/zhtf26Vq0Pcrujt5LfRe1JSYKdJ21Tquqa0MAGI0HghaCdSdtyo2xuotnukirKb
QrvP/YNa+ONZT6KW8MFAwfoCOJMo1idrkBA1Wve5xcCd7J9Oy5mWCBxTSR67W2vQ
izoOTSkzD0xoXpFF5V/34zJGWU9t6Z5qytV/w5ROY3Tk9EaKs0NcQmXlCxSmkXil
KSTlZ/VDeDliI92jGn4hT+apisglm3aaTnVVAP0EbZ/CF9Fwb601M7IcAP9ejaeB
EEs+smXpuzhAfxPa5SpZCWeaXLcFq6Ewi2LXrMaChWvbu9AUi3QjuT3u9PW3B0w5
C54FLfvcy9X9dQQ/jCgydF3eyhiO3SuLZqrhofHUn53z4UCEYgbC7uQSv08ep2UD
kT2ARN6aetXVgiQBYS8hcGaFrdsUTSAmT0flt0g8ZoHn+NmuAWxbAx8UnPd0p/EP
B4cZByDOUDGgDMSOEluheiCFlZBQEJnvOhim6rwSje87EzQazGkwRpOrBtzGIGcM
xmotG9IrMbzKb4Z+yg5pOEW2WKEy3f2h8P2bisbbHwYa/tgTHVWbGwIDAQABo1Aw
TjAdBgNVHQ4EFgQUZvIOJVkyQTAd0fpUkpqgcXVID+4wHwYDVR0jBBgwFoAUZvIO
JVkyQTAd0fpUkpqgcXVID+4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AgEASmqzfr4TOVFbNo3r85QlCQB8W4xaArRpY6k7ugxJm2/9iyyovMKMhCcRvmPV
HZXDTV8WEbLDg3uNF56WU39YmtfjghnsYcGQ/UhnH2PoHQxGIKzOoalLew7kP0Uc
HCmXi7OPjqeFQgjWVhLkL/RIzZ3vOVFQfqodTVIdYDe0O1cNRD0U0rtGa0MHzZlD
y/ZaksiB0E3UL/GsgyJoCxCAF1E80PU9aw1K9COyTOChpUKnhSHC964KlhnJKyOe
HKCYtb5RVrF7ryBcGneLTcQm/oNUp8aRyiwyQIDH7djFSp11MakXBF+EeGR523pp
u+sleR7ZFBGyb3Z5K9NdRdUk0SWu7Hu4jQtJHn+PmIZ1qjfbPv5qDfVd1vmFwqsu
7NfsLoNm0dQNu5WOMLISQHmQiT86AH2wWQ3l5Sm+g8/BdNLQLklhtZcRhp2efyiL
ciUmGugKqoX+nPIZ36kuoRTZy++AnTiid011vZFe1qrfug/ykWiqWmBSvD/cfRU4
ydoK87cfjIixqmpRZ7j2q+/cDK2SbYN0t/Xrufw3L6TjDgUEL7ZCImcwqqWJz9I8
ASnnL5PhX8bbsUrtE21Ugqk2zYnVnqRO5FjINtlTb07y9pGC/QpBkb1AasF5okhe
7lw/sMiryKKzS4A10nRV/+gErDBsIBj+cpGPM8brLfZuy2o=
-----END CERTIFICATE-----

54
test/server/key.pem Normal file
View File

@ -0,0 +1,54 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
4Ig=
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -17,12 +17,14 @@
let fs = require('fs'); let fs = require('fs');
let path = require('path'); let path = require('path');
let Browser = require('../lib/Browser'); let Browser = require('../lib/Browser');
let SimpleServer = require('./SimpleServer'); let SimpleServer = require('./server/SimpleServer');
let GoldenUtils = require('./golden-utils'); let GoldenUtils = require('./golden-utils');
let PORT = 8907; let PORT = 8907;
let STATIC_PREFIX = 'http://localhost:' + PORT; let PREFIX = 'http://localhost:' + PORT;
let EMPTY_PAGE = STATIC_PREFIX + '/empty.html'; let EMPTY_PAGE = PREFIX + '/empty.html';
let HTTPS_PORT = 8908;
let HTTPS_PREFIX = 'https://localhost:' + HTTPS_PORT;
if (process.env.DEBUG_TEST) if (process.env.DEBUG_TEST)
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 1000 * 1000; jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 1000 * 1000;
@ -32,22 +34,29 @@ else
describe('Puppeteer', function() { describe('Puppeteer', function() {
let browser; let browser;
let server; let server;
let httpsServer;
let page; let page;
beforeAll(SX(async function() { beforeAll(SX(async function() {
browser = new Browser({args: ['--no-sandbox']}); browser = new Browser({args: ['--no-sandbox']});
server = await SimpleServer.create(path.join(__dirname, 'assets'), PORT); const assetsPath = path.join(__dirname, 'assets');
server = await SimpleServer.create(assetsPath, PORT);
httpsServer = await SimpleServer.createHTTPS(assetsPath, HTTPS_PORT);
GoldenUtils.removeOutputDir(); GoldenUtils.removeOutputDir();
})); }));
afterAll(SX(async function() { afterAll(SX(async function() {
await server.stop(); await Promise.all([
server.stop(),
httpsServer.stop(),
]);
browser.close(); browser.close();
})); }));
beforeEach(SX(async function() { beforeEach(SX(async function() {
page = await browser.newPage(); page = await browser.newPage();
server.reset(); server.reset();
httpsServer.reset();
GoldenUtils.addMatchers(jasmine); GoldenUtils.addMatchers(jasmine);
})); }));
@ -186,12 +195,54 @@ describe('Puppeteer', function() {
describe('Page.navigate', function() { describe('Page.navigate', function() {
it('should fail when navigating to bad url', SX(async function() { it('should fail when navigating to bad url', SX(async function() {
let success = await page.navigate('asdfasdf'); let error = null;
expect(success).toBe(false); try {
await page.navigate('asdfasdf');
} catch (e) {
error = e;
}
expect(error.message).toContain('Cannot navigate to invalid URL');
})); }));
it('should succeed when navigating to good url', SX(async function() { it('should fail when navigating to bad SSL', SX(async function() {
let success = await page.navigate(EMPTY_PAGE); let error = null;
expect(success).toBe(true); try {
await page.navigate(HTTPS_PREFIX + '/empty.html');
} catch (e) {
error = e;
}
expect(error.message).toContain('SSL Certiciate error');
}));
it('should fail when exceeding maximum navigation timeout', SX(async function() {
let error = null;
// Hang for request to the empty.html
server.setRoute('/empty.html', (req, res) => { });
try {
await page.navigate(PREFIX + '/empty.html', {maxTime: 59});
} catch (e) {
error = e;
}
expect(error.message).toContain('Navigation Timeout Exceeded: 59ms');
}));
it('should work when navigating to valid url', SX(async function() {
const response = await page.navigate(EMPTY_PAGE);
expect(response.ok).toBe(true);
}));
it('should work when navigating to data url', SX(async function() {
const response = await page.navigate('data:text/html,hello');
expect(response.ok).toBe(true);
}));
it('should work when navigating to 404', SX(async function() {
const response = await page.navigate(PREFIX + '/not-found');
expect(response.ok).toBe(false);
expect(response.status).toBe(404);
}));
it('should return last response in redirect chain', SX(async function() {
server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/redirect/3.html');
server.setRedirect('/redirect/3.html', EMPTY_PAGE);
const response = await page.navigate(PREFIX + '/redirect/1.html');
expect(response.ok).toBe(true);
expect(response.url).toBe(EMPTY_PAGE);
})); }));
it('should wait for network idle to succeed navigation', SX(async function() { it('should wait for network idle to succeed navigation', SX(async function() {
let responses = []; let responses = [];
@ -209,7 +260,7 @@ describe('Puppeteer', function() {
// Navigate to a page which loads immediately and then does a bunch of // Navigate to a page which loads immediately and then does a bunch of
// requests via javascript's fetch method. // requests via javascript's fetch method.
let navigationPromise = page.navigate(STATIC_PREFIX + '/networkidle.html', { let navigationPromise = page.navigate(PREFIX + '/networkidle.html', {
waitFor: 'networkidle', waitFor: 'networkidle',
networkIdleTimeout: 100, networkIdleTimeout: 100,
networkIdleInflight: 0, // Only be idle when there are 0 inflight requests networkIdleInflight: 0, // Only be idle when there are 0 inflight requests
@ -248,9 +299,9 @@ describe('Puppeteer', function() {
response.end(`File not found`); response.end(`File not found`);
} }
let success = await navigationPromise; const response = await navigationPromise;
// Expect navigation to succeed. // Expect navigation to succeed.
expect(success).toBe(true); expect(response.ok).toBe(true);
})); }));
it('should wait for websockets to succeed navigation', SX(async function() { it('should wait for websockets to succeed navigation', SX(async function() {
let responses = []; let responses = [];
@ -259,7 +310,7 @@ describe('Puppeteer', function() {
let fetchResourceRequested = server.waitForRequest('/fetch-request.js'); let fetchResourceRequested = server.waitForRequest('/fetch-request.js');
// Navigate to a page which loads immediately and then opens a bunch of // Navigate to a page which loads immediately and then opens a bunch of
// websocket connections and then a fetch request. // websocket connections and then a fetch request.
let navigationPromise = page.navigate(STATIC_PREFIX + '/websocket.html', { let navigationPromise = page.navigate(PREFIX + '/websocket.html', {
waitFor: 'networkidle', waitFor: 'networkidle',
networkIdleTimeout: 100, networkIdleTimeout: 100,
networkIdleInflight: 0, // Only be idle when there are 0 inflight requests/connections networkIdleInflight: 0, // Only be idle when there are 0 inflight requests/connections
@ -283,9 +334,9 @@ describe('Puppeteer', function() {
response.statusCode = 404; response.statusCode = 404;
response.end(`File not found`); response.end(`File not found`);
} }
let success = await navigationPromise; const response = await navigationPromise;
// Expect navigation to succeed. // Expect navigation to succeed.
expect(success).toBe(true); expect(response.ok).toBe(true);
})); }));
}); });
@ -331,8 +382,8 @@ describe('Puppeteer', function() {
expect(request.postData).toBe(undefined); expect(request.postData).toBe(undefined);
request.continue(); request.continue();
}); });
let success = await page.navigate(EMPTY_PAGE); const response = await page.navigate(EMPTY_PAGE);
expect(success).toBe(true); expect(response.ok).toBe(true);
})); }));
it('should show custom HTTP headers', SX(async function() { it('should show custom HTTP headers', SX(async function() {
await page.setHTTPHeaders({ await page.setHTTPHeaders({
@ -342,8 +393,8 @@ describe('Puppeteer', function() {
expect(request.headers.get('foo')).toBe('bar'); expect(request.headers.get('foo')).toBe('bar');
request.continue(); request.continue();
}); });
let success = await page.navigate(EMPTY_PAGE); const response = await page.navigate(EMPTY_PAGE);
expect(success).toBe(true); expect(response.ok).toBe(true);
})); }));
it('should be abortable', SX(async function() { it('should be abortable', SX(async function() {
page.setRequestInterceptor(request => { page.setRequestInterceptor(request => {
@ -354,8 +405,8 @@ describe('Puppeteer', function() {
}); });
let failedRequests = 0; let failedRequests = 0;
page.on('requestfailed', event => ++failedRequests); page.on('requestfailed', event => ++failedRequests);
let success = await page.navigate(STATIC_PREFIX + '/one-style.html'); const response = await page.navigate(PREFIX + '/one-style.html');
expect(success).toBe(true); expect(response.ok).toBe(true);
expect(failedRequests).toBe(1); expect(failedRequests).toBe(1);
})); }));
it('should amend HTTP headers', SX(async function() { it('should amend HTTP headers', SX(async function() {
@ -400,7 +451,7 @@ describe('Puppeteer', function() {
expect(error.message).toContain('Fancy'); expect(error.message).toContain('Fancy');
done(); done();
}); });
page.navigate(STATIC_PREFIX + '/error.html'); page.navigate(PREFIX + '/error.html');
}); });
}); });
@ -417,13 +468,13 @@ describe('Puppeteer', function() {
describe('Page.screenshot', function() { describe('Page.screenshot', function() {
it('should work', SX(async function() { it('should work', SX(async function() {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
let screenshot = await page.screenshot(); let screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-sanity.png'); expect(screenshot).toBeGolden('screenshot-sanity.png');
})); }));
it('should clip rect', SX(async function() { it('should clip rect', SX(async function() {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
let screenshot = await page.screenshot({ let screenshot = await page.screenshot({
clip: { clip: {
x: 50, x: 50,
@ -436,7 +487,7 @@ describe('Puppeteer', function() {
})); }));
it('should work for offscreen clip', SX(async function() { it('should work for offscreen clip', SX(async function() {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
let screenshot = await page.screenshot({ let screenshot = await page.screenshot({
clip: { clip: {
x: 50, x: 50,
@ -449,7 +500,7 @@ describe('Puppeteer', function() {
})); }));
it('should run in parallel', SX(async function() { it('should run in parallel', SX(async function() {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
let promises = []; let promises = [];
for (let i = 0; i < 3; ++i) { for (let i = 0; i < 3; ++i) {
promises.push(page.screenshot({ promises.push(page.screenshot({
@ -466,7 +517,7 @@ describe('Puppeteer', function() {
})); }));
it('should take fullPage screenshots', SX(async function() { it('should take fullPage screenshots', SX(async function() {
await page.setViewportSize({width: 500, height: 500}); await page.setViewportSize({width: 500, height: 500});
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
let screenshot = await page.screenshot({ let screenshot = await page.screenshot({
fullPage: true fullPage: true
}); });
@ -477,7 +528,7 @@ describe('Puppeteer', function() {
describe('Frame Management', function() { describe('Frame Management', function() {
let FrameUtils = require('./frame-utils'); let FrameUtils = require('./frame-utils');
it('should handle nested frames', SX(async function() { it('should handle nested frames', SX(async function() {
await page.navigate(STATIC_PREFIX + '/frames/nested-frames.html'); await page.navigate(PREFIX + '/frames/nested-frames.html');
expect(FrameUtils.dumpFrames(page.mainFrame())).toBeGolden('nested-frames.txt'); expect(FrameUtils.dumpFrames(page.mainFrame())).toBeGolden('nested-frames.txt');
})); }));
it('should send events when frames are manipulated dynamically', SX(async function() { it('should send events when frames are manipulated dynamically', SX(async function() {
@ -523,7 +574,7 @@ describe('Puppeteer', function() {
page.on('frameattached', frame => attachedFrames.push(frame)); page.on('frameattached', frame => attachedFrames.push(frame));
page.on('framedetached', frame => detachedFrames.push(frame)); page.on('framedetached', frame => detachedFrames.push(frame));
page.on('framenavigated', frame => navigatedFrames.push(frame)); page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.navigate(STATIC_PREFIX + '/frames/nested-frames.html'); await page.navigate(PREFIX + '/frames/nested-frames.html');
expect(attachedFrames.length).toBe(4); expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0); expect(detachedFrames.length).toBe(0);
expect(navigatedFrames.length).toBe(5); expect(navigatedFrames.length).toBe(5);
@ -540,12 +591,12 @@ describe('Puppeteer', function() {
describe('input', function() { describe('input', function() {
it('should click the button', SX(async function() { it('should click the button', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/button.html'); await page.navigate(PREFIX + '/input/button.html');
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked'); expect(await page.evaluate(() => result)).toBe('Clicked');
})); }));
it('should fail to click a missing button', SX(async function() { it('should fail to click a missing button', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/button.html'); await page.navigate(PREFIX + '/input/button.html');
try { try {
await page.click('button.does-not-exist'); await page.click('button.does-not-exist');
fail('Clicking the button did not throw.'); fail('Clicking the button did not throw.');
@ -554,20 +605,20 @@ describe('Puppeteer', function() {
} }
})); }));
it('should type into the textarea', SX(async function() { it('should type into the textarea', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/textarea.html'); await page.navigate(PREFIX + '/input/textarea.html');
await page.focus('textarea'); await page.focus('textarea');
await page.type('Type in this text!'); await page.type('Type in this text!');
expect(await page.evaluate(() => result)).toBe('Type in this text!'); expect(await page.evaluate(() => result)).toBe('Type in this text!');
})); }));
it('should click the button after navigation ', SX(async function() { it('should click the button after navigation ', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/button.html'); await page.navigate(PREFIX + '/input/button.html');
await page.click('button'); await page.click('button');
await page.navigate(STATIC_PREFIX + '/input/button.html'); await page.navigate(PREFIX + '/input/button.html');
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked'); expect(await page.evaluate(() => result)).toBe('Clicked');
})); }));
it('should upload the file', SX(async function(){ it('should upload the file', SX(async function(){
await page.navigate(STATIC_PREFIX + '/input/fileupload.html'); await page.navigate(PREFIX + '/input/fileupload.html');
await page.uploadFile('input', __dirname + '/assets/file-to-upload.txt'); await page.uploadFile('input', __dirname + '/assets/file-to-upload.txt');
expect(await page.evaluate(() => { expect(await page.evaluate(() => {
let input = document.querySelector('input'); let input = document.querySelector('input');
@ -626,7 +677,7 @@ describe('Puppeteer', function() {
it('should work', SX(async function() { it('should work', SX(async function() {
let response = null; let response = null;
page.on('response', r => response = r); page.on('response', r => response = r);
await page.navigate(STATIC_PREFIX + '/simple.json'); await page.navigate(PREFIX + '/simple.json');
expect(response).toBeTruthy(); expect(response).toBeTruthy();
expect(response.bodyUsed).toBe(false); expect(response.bodyUsed).toBe(false);
expect(await response.text()).toBe('{"foo": "bar"}\n'); expect(await response.text()).toBe('{"foo": "bar"}\n');
@ -663,7 +714,7 @@ describe('Puppeteer', function() {
}); });
let failedRequests = []; let failedRequests = [];
page.on('requestfailed', request => failedRequests.push(request)); page.on('requestfailed', request => failedRequests.push(request));
await page.navigate(STATIC_PREFIX + '/one-style.html'); await page.navigate(PREFIX + '/one-style.html');
expect(failedRequests.length).toBe(1); expect(failedRequests.length).toBe(1);
expect(failedRequests[0].url).toContain('one-style.css'); expect(failedRequests[0].url).toContain('one-style.css');
expect(failedRequests[0].response()).toBe(null); expect(failedRequests[0].response()).toBe(null);
@ -691,7 +742,7 @@ describe('Puppeteer', function() {
page.on('requestfinished', request => events.push(`DONE ${request.url}`)); page.on('requestfinished', request => events.push(`DONE ${request.url}`));
page.on('requestfailed', request => events.push(`FAIL ${request.url}`)); page.on('requestfailed', request => events.push(`FAIL ${request.url}`));
server.setRedirect('/foo.html', '/empty.html'); server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = STATIC_PREFIX + '/foo.html'; const FOO_URL = PREFIX + '/foo.html';
await page.navigate(FOO_URL); await page.navigate(FOO_URL);
expect(events).toEqual([ expect(events).toEqual([
`GET ${FOO_URL}`, `GET ${FOO_URL}`,
@ -733,7 +784,7 @@ describe('Puppeteer', function() {
await page.evaluateOnInitialized(function(){ await page.evaluateOnInitialized(function(){
window.injected = 123; window.injected = 123;
}); });
await page.navigate(STATIC_PREFIX + '/tamperable.html'); await page.navigate(PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123); expect(await page.evaluate(() => window.result)).toBe(123);
})); }));
}); });
@ -745,7 +796,7 @@ describe('Puppeteer', function() {
}); });
it('should print to pdf', SX(async function() { it('should print to pdf', SX(async function() {
await page.navigate(STATIC_PREFIX + '/grid.html'); await page.navigate(PREFIX + '/grid.html');
await page.printToPDF(outputFile); await page.printToPDF(outputFile);
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
})); }));
@ -760,7 +811,7 @@ describe('Puppeteer', function() {
describe('Page.title', function() { describe('Page.title', function() {
it('should return the page title', SX(async function(){ it('should return the page title', SX(async function(){
await page.navigate(STATIC_PREFIX + '/input/button.html'); await page.navigate(PREFIX + '/input/button.html');
expect(await page.title()).toBe('Button test'); expect(await page.title()).toBe('Button test');
})); }));
}); });