From 6c6362f0a3f15ef27bd478212e6095eea4cbb26c Mon Sep 17 00:00:00 2001 From: Parashuram Date: Wed, 3 Sep 2014 16:14:31 -0700 Subject: [PATCH] Enabled Performance Logs for iOS --- lib/devices/ios/ios-controller.js | 18 ++++++- lib/devices/ios/ios-perf-log.js | 48 +++++++++++++++++ lib/devices/ios/ios.js | 2 + lib/devices/ios/remote-debugger.js | 21 ++++++++ lib/devices/ios/remote-messages.js | 10 ++++ lib/devices/ios/webkit-remote-debugger.js | 3 ++ lib/server/capabilities.js | 2 + .../ios/safari/webview/special-caps-specs.js | 52 +++++++++++++------ 8 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 lib/devices/ios/ios-perf-log.js diff --git a/lib/devices/ios/ios-controller.js b/lib/devices/ios/ios-controller.js index 8aa4c2b63..82f902f72 100644 --- a/lib/devices/ios/ios-controller.js +++ b/lib/devices/ios/ios-controller.js @@ -20,6 +20,7 @@ var uuid = require('uuid-js') , xpath = require("xpath") , XMLDom = require("xmldom") , settings = require('./settings.js') + , IOSPerfLog = require('./ios-perf-log') , getSimRootsWithVersion = settings.getSimRootsWithVersion , errors = require('../../server/errors.js') , NotImplementedError = errors.NotImplementedError @@ -34,7 +35,8 @@ var WEBVIEW_BASE = WEBVIEW_WIN + "_"; var logTypesSupported = { 'syslog': 'Logs for iOS applications on real devices and simulators', - 'crashlog': 'Crash logs for iOS applications on real devices and simulators' + 'crashlog': 'Crash logs for iOS applications on real devices and simulators', + 'performance': 'Performance Logs - Debug Timelines on real devices and simulators' }; iOSController.getStatusExtensions = function () { @@ -1520,7 +1522,19 @@ iOSController.getContexts = function (cb) { }.bind(this)); }; -iOSController.setContext = function (name, cb, skipReadyCheck) { +iOSController.setContext = function (name, callback, skipReadyCheck) { + var cb = function (err, res) { + if (!err && res.status === status.codes.Success.code && this.perfLogEnabled) { + logger.debug('Starting performance log on ' + this.curContext); + this.logs.performance = new IOSPerfLog(this.remote); + this.logs.performance.startCapture(function () { + callback(err, res); + }); + } else { + callback(err, res); + } + }.bind(this); + logger.debug("Attempting to set context to '" + name + "'"); if (name === this.curContext) { cb(null, { diff --git a/lib/devices/ios/ios-perf-log.js b/lib/devices/ios/ios-perf-log.js new file mode 100644 index 000000000..e9a347170 --- /dev/null +++ b/lib/devices/ios/ios-perf-log.js @@ -0,0 +1,48 @@ +"use strict"; + +var logger = require('../../server/logger.js').get('appium'); + +// Date-Utils: Polyfills for the Date object +require('date-utils'); + +var MAX_EVENTS = 5000; + +var IosPerfLog = function (remote) { + this.remote = remote; + this.timelineEvents = []; + this.flushed = true; +}; + +IosPerfLog.prototype.startCapture = function (cb) { + logger.debug('Starting to capture timeline logs'); + this.timelineEvents = []; + return this.remote.startTimeline(this.onTimelineEvent.bind(this), cb); +}; + +IosPerfLog.prototype.stopCapture = function (cb) { + this.timelineEvents = null; + return this.remote.stopTimeline(cb); +}; + +IosPerfLog.prototype.onTimelineEvent = function (event) { + if (this.flushed) { + logger.debug('Flushing Timeline events'); + this.timelineEvents = []; + this.flushed = false; + } + this.timelineEvents.push(event); + if (this.timelineEvents.length > MAX_EVENTS) { + this.timelineEvents.shift(); + } +}; + +IosPerfLog.prototype.getLogs = function () { + this.flushed = true; + return this.timelineEvents; +}; + +IosPerfLog.prototype.getAllLogs = function () {}; + +module.exports = function (opts) { + return new IosPerfLog(opts); +}; diff --git a/lib/devices/ios/ios.js b/lib/devices/ios/ios.js index b2192c304..e641b8466 100644 --- a/lib/devices/ios/ios.js +++ b/lib/devices/ios/ios.js @@ -162,6 +162,8 @@ IOS.prototype.setIOSArgs = function () { null; this.curOrientation = this.args.initialOrientation; this.sock = path.resolve(this.args.tmpDir || '/tmp', 'instruments_sock'); + + this.perfLogEnabled = !!(typeof this.args.loggingPrefs === 'object' && this.args.loggingPrefs.performance); }; IOS.prototype.configureApp = function (cb) { diff --git a/lib/devices/ios/remote-debugger.js b/lib/devices/ios/remote-debugger.js index ccdad3c44..7c05b7d08 100644 --- a/lib/devices/ios/remote-debugger.js +++ b/lib/devices/ios/remote-debugger.js @@ -345,6 +345,25 @@ RemoteDebugger.prototype.pageLoad = function () { this.loadingTimeout = setTimeout(verify, intMs); }; +RemoteDebugger.prototype.startTimeline = function (fn, cb) { + this.logger.debug("Starting to record the timeline"); + this.timelineEventHandler = fn; + var sendJSCommand = messages.startTimeline(this.appIdKey, + this.connId, this.senderId, this.pageIdKey, this.debuggerType); + this.send(sendJSCommand, cb); +}; + +RemoteDebugger.prototype.stopTimeline = function (cb) { + this.logger.debug("Stopping to record the timeline"); + var sendJSCommand = messages.stopTimeline(this.appIdKey, + this.connId, this.senderId, this.pageIdKey, this.debuggerType); + this.send(sendJSCommand, cb); +}; + +RemoteDebugger.prototype.timelineEventRecorded = function (result) { + this.timelineEventHandler(result); +}; + RemoteDebugger.prototype.cancelPageLoad = function () { this.logger.debug("Unregistering from page readiness notifications"); this.pageLoadedCbs = []; @@ -450,6 +469,8 @@ RemoteDebugger.prototype.setHandlers = function () { } } else if (dataKey.method === "Page.loadEventFired") { this.pageLoad(); + } else if (dataKey.method === "Timeline.eventRecorded") { + this.timelineEventRecorded(dataKey.params.record); } else if (typeof this.dataCbs[msgId] === "function") { this.dataCbs[msgId](error, result); this.dataCbs[msgId] = null; diff --git a/lib/devices/ios/remote-messages.js b/lib/devices/ios/remote-messages.js index 38059ae6c..b821de320 100644 --- a/lib/devices/ios/remote-messages.js +++ b/lib/devices/ios/remote-messages.js @@ -70,6 +70,16 @@ exports.enablePage = function (appIdKey, connId, senderId, pageIdKey, debuggerTy pageIdKey, debuggerType); }; +exports.startTimeline = function (appIdKey, connId, senderId, pageIdKey, debuggerType) { + return exports.command("Timeline.start", {}, appIdKey, connId, senderId, + pageIdKey, debuggerType); +}; + +exports.stopTimeline = function (appIdKey, connId, senderId, pageIdKey, debuggerType) { + return exports.command("Timeline.stop", {}, appIdKey, connId, senderId, + pageIdKey, debuggerType); +}; + exports.command = function (method, params, appIdKey, connId, senderId, pageIdKey, debuggerType) { if (debuggerType !== null && debuggerType === 1) { return exports.commandWebKit(method, params); diff --git a/lib/devices/ios/webkit-remote-debugger.js b/lib/devices/ios/webkit-remote-debugger.js index afa15a050..133b27e56 100644 --- a/lib/devices/ios/webkit-remote-debugger.js +++ b/lib/devices/ios/webkit-remote-debugger.js @@ -138,6 +138,9 @@ WebKitRemoteDebugger.prototype.setHandlers = function () { 'Profiler.resetProfiles': function () { this.logger.debug("Device is telling us to reset profiles. Should probably " + "do some kind of callback here"); + }.bind(this), + 'Timeline.eventRecorded': function (data) { + this.timelineEventRecorded(data.result); }.bind(this) }; }; diff --git a/lib/server/capabilities.js b/lib/server/capabilities.js index d9eb9792f..0488e9784 100644 --- a/lib/server/capabilities.js +++ b/lib/server/capabilities.js @@ -12,6 +12,7 @@ var okObjects = [ , 'launchTimeout' , 'specialChromedriverSessionArgs' , 'chromeOptions' +, 'loggingPrefs' ]; var requiredCaps = [ @@ -83,6 +84,7 @@ var iosCaps = [ , 'localizableStringsDir' , 'interKeyDelay' , 'showIOSLog' +, 'loggingPrefs' ]; var Capabilities = function (capabilities) { diff --git a/test/functional/ios/safari/webview/special-caps-specs.js b/test/functional/ios/safari/webview/special-caps-specs.js index 3fcbd985c..4984d6bed 100644 --- a/test/functional/ios/safari/webview/special-caps-specs.js +++ b/test/functional/ios/safari/webview/special-caps-specs.js @@ -9,24 +9,46 @@ var env = require('../../../../helpers/env.js'), ChaiAsserter = require('../../../../helpers/asserter.js').ChaiAsserter; describe('safari - webview - special capabilities', function () { - var driver; - var specialCaps = _.clone(desired); - specialCaps.safariIgnoreFraudWarning = true; - setup(this, specialCaps, {'no-reset': true}).then(function (d) { driver = d; }); + describe('phishing warning', function () { + var driver; + var specialCaps = _.clone(desired); + specialCaps.safariIgnoreFraudWarning = true; + setup(this, specialCaps, {'no-reset': true}).then(function (d) { driver = d; }); - beforeEach(function (done) { - loadWebView(specialCaps, driver).nodeify(done); + beforeEach(function (done) { + loadWebView(specialCaps, driver).nodeify(done); + }); + + it('should not display a phishing warning with safariIgnoreFraudWarning @skip-chrome', function (done) { + var titleToBecomeRight = new ChaiAsserter(function (driver) { + return driver + .title() + .should.eventually.contain("I am another page title"); + }); + driver + .get(env.PHISHING_END_POINT + 'guinea-pig2.html') + .waitFor(titleToBecomeRight, 10000, 500) + .nodeify(done); + }); }); - it('should not display a phishing warning with safariIgnoreFraudWarning @skip-chrome', function (done) { - var titleToBecomeRight = new ChaiAsserter(function (driver) { - return driver - .title() - .should.eventually.contain("I am another page title"); + describe('performance logs', function () { + var driver; + var specialCaps = _.clone(desired); + specialCaps.loggingPrefs = {performance: 'ALL'}; + setup(this, specialCaps, {'no-reset': true}).then(function (d) { driver = d; }); + + beforeEach(function (done) { + loadWebView(specialCaps, driver).nodeify(done); + }); + + it('should fetch performance logs', function (done) { + driver + .logTypes() + .should.eventually.include('performance') + .log('performance') + .should.eventually.not.be.empty + .nodeify(done); }); - driver - .get(env.PHISHING_END_POINT + 'guinea-pig2.html') - .waitFor(titleToBecomeRight, 10000, 500) - .nodeify(done); }); });