Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module HAR #263

Merged
merged 16 commits into from
Mar 27, 2014
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ phantomas https://github.com/macbre/phantomas --verbose --no-externals --allow-d
* `--film-strip-dir=[dir path]` folder path to output film strip (default is ``./filmstrip`` directory) **experimental**
* `--assert-[metric-name]=value` assert that given metric should be less or equal the value
* `--screenshot=[file name]` render fully loaded page to a given file
* `--har=[file name]` save HAR to a given file
* `--wait-for-selector=[CSS selector]` wait for an element matching given CSS selector before generating a report, timeout setting still applies (e.g. ``--wait-for-selector "body.loaded"``)
* `--post-load-delay=[seconds]` wait X seconds before generating a report, timeout setting still applies
* `--ignore-ssl-errors` ignores SSL errors, such as expired or self-signed certificate errors
Expand Down
1 change: 1 addition & 0 deletions bin/phantomas.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ program
.describe('reporter', 'output format / reporter').default('reporter', 'plain').alias('reporter', 'R').alias('reporter', 'format')
.describe('runs', 'number of runs to perform')
.describe('screenshot', 'render fully loaded page to a given file')
.describe('har', 'save HAR to a given file')
.describe('silent', 'don\'t write anything to the console').boolean('silent')
.describe('skip-modules', 'skip selected modules [moduleOne],[moduleTwo],...')
.describe('tablet', 'force viewport and user agent of a tablet')
Expand Down
173 changes: 173 additions & 0 deletions modules/har/har.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* Log requests for build HAR output
*
* @see: https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html
*/

var fs = require('fs');

/**
* From netsniff.js with small workarounds
*
* @see: https://github.com/ariya/phantomjs/blob/master/examples/netsniff.js
*/

function createHAR(address, title, startTime, endTime, resources, creator)
{
var entries = [];

resources.forEach(function (resource) {
var request = resource.request,
startReply = resource.startReply,
endReply = resource.endReply;

if (!request || !startReply || !endReply) {
return;
}

// Exclude Data URI from HAR file because
// they aren't included in specification
if (request.url.match(/(^data:image\/.*)/i)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just check if the first five chars are data:

return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation?


entries.push({
startedDateTime: request.time.toISOString(),
time: endReply.time - request.time,
request: {
method: request.method,
url: request.url,
httpVersion: "HTTP/1.1",
cookies: [],
headers: request.headers,
queryString: [],
headersSize: -1,
bodySize: -1
},
response: {
status: endReply.status,
statusText: endReply.statusText,
httpVersion: "HTTP/1.1",
cookies: [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about cookies?

headers: endReply.headers,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about headersSize?

redirectURL: "",
headersSize: -1,
bodySize: startReply.bodySize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not accurate but that's phantomJS' fault. In a project of mine, I actually request each resource separately to get the accurate number of bytes transferred (and the content of each response). It's a pain.

content: {
size: startReply.bodySize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know there's an open phantomJS bug open, but it'd be nice if we could optionally add the content text

mimeType: endReply.contentType === null ? "" : endReply.contentType
}
},
cache: {},
timings: {
blocked: 0,
dns: -1,
connect: -1,
send: 0,
wait: startReply.time - request.time,
receive: endReply.time - startReply.time,
ssl: -1
},
pageref: address
});
});

return {
log: {
version: '1.2',
creator: creator,
pages: [{
startedDateTime: startTime.toISOString(),
id: address,
title: title,
pageTimings: {
onLoad: endTime.getTime() - startTime.getTime()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html#sec-object-types-pageTimings

HAR spec mentions the optional onContentLoad property that "represents DOMContentLoad event or document.readyState == interactive". Worth adding?

}
}],
entries: entries
}
};
}
/** End **/

exports.module = function(phantomas) {

var param = phantomas.getParam('har'),
path = '';

var creator = {
name: "Phantomas - har",
version: phantomas.getVersion()
};

if (typeof param === 'undefined') {
phantomas.log('No HAR path specified, use --har <path>');
return;
}

// --har
if (param === true) {
// defaults to "2013-12-07T20:15:01.521Z.har"
path = (new Date()).toJSON() + '.har';
}
// --har [file name]
else {
path = param;
}

phantomas.log('HAR path: %s', path);

var resources = [];
var startTime;
var endTime;
var page;

phantomas.on('pageBeforeOpen', function(p) {
page = p;
});

phantomas.on('pageOpen', function() {
startTime = new Date();
});

phantomas.on('loadFinished', function() {
endTime = new Date();
});

phantomas.on('onResourceRequested', function(res, req) {
resources[res.id] = {
request: res,
startReply: null,
endReply: null
};

});

phantomas.on('onResourceReceived', function(res) {
if (res.stage === 'start')
resources[res.id].startReply = res;

if (res.stage === 'end')
resources[res.id].endReply = res;
});

phantomas.on('report', function() {
var address = page.url;
var title = page.title;

// Warning page was not finished correctly
if (! endTime)
endTime = new Date();

phantomas.log('Create HAR');
var har = createHAR(address, title, startTime, endTime, resources, creator);

phantomas.log('Convert HAR to JSON');
var dump = JSON.stringify(har);

phantomas.log('Write HAR in \'%s\'', path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap with double quotes so you don't have to escape single quotes

fs.write(path, dump);

phantomas.log('HAR Done !');
});
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a newline at EOF