Skip to content
Browse files

Merge pull request #297 from jlipps/master

window maneuvering magic in safari
  • Loading branch information...
2 parents 39ad87e + 1cfa8c0 commit 5fb3dc5b6f758c6ad90bb73a35983c262337adaf @admc admc committed
Showing with 304 additions and 51 deletions.
  1. +11 −0 app/controller.js
  2. +27 −14 app/hybrid/ios/remote-debugger.js
  3. +83 −17 app/ios.js
  4. +1 −0 app/routing.js
  5. +131 −0 app/test/guinea-pig.html
  6. +22 −1 test/functional/safari/safari.js
  7. +29 −19 test/helpers/webview.js
View
11 app/controller.js
@@ -4,6 +4,8 @@
var status = require('./uiauto/lib/status')
, logger = require('../logger.js').get('appium')
, _s = require("underscore.string")
+ , fs = require('fs')
+ , path = require('path')
, _ = require('underscore');
function getResponseHandler(req, res) {
@@ -629,3 +631,12 @@ exports.produceError = function(req, res) {
exports.crash = function() {
throw new Error("We just tried to crash Appium!");
};
+
+exports.guineaPig = function(req, res) {
+ var file = path.resolve(__dirname, "test/guinea-pig.html");
+ fs.readFile(file, function(err, data) {
+ if (err) return res.send(500);
+ res.set('Content-Type', 'text/html');
+ res.send(data);
+ });
+};
View
41 app/hybrid/ios/remote-debugger.js
@@ -43,6 +43,7 @@ var RemoteDebugger = function(onDisconnect) {
this.curMsgId = 0;
this.dataCbs = [];
this.onAppDisconnect = onDisconnect || noop;
+ this.pageChangeCb = noop;
this.specialCbs = {
'_rpc_reportIdentifier:': noop
, '_rpc_forwardGetListing:': noop
@@ -58,8 +59,9 @@ var RemoteDebugger = function(onDisconnect) {
// API
// ====================================
-RemoteDebugger.prototype.connect = function(cb) {
+RemoteDebugger.prototype.connect = function(cb, pageChangeCb) {
var me = this;
+ this.pageChangeCb = pageChangeCb;
this.socket = new net.Socket({type: 'tcp6'});
this.socket.on('close', function() {
logger.info('Debugger socket disconnected');
@@ -99,17 +101,21 @@ RemoteDebugger.prototype.selectApp = function(appIdKey, cb) {
this.appIdKey = appIdKey;
var connectToApp = messages.connectToApp(this.connId, this.appIdKey);
logger.info("Selecting app");
- this.send(connectToApp, function(pageDict) {
- var newPageArray = [];
- _.each(pageDict, function(dict) {
- newPageArray.push({
- id: dict.WIRPageIdentifierKey
- , title: dict.WIRTitleKey
- , url: dict.WIRURLKey
- });
+ this.send(connectToApp, _.bind(function(pageDict) {
+ cb(this.pageArrayFromDict(pageDict));
+ }, this));
+};
+
+RemoteDebugger.prototype.pageArrayFromDict = function(pageDict) {
+ var newPageArray = [];
+ _.each(pageDict, function(dict) {
+ newPageArray.push({
+ id: dict.WIRPageIdentifierKey
+ , title: dict.WIRTitleKey
+ , url: dict.WIRURLKey
});
- cb(newPageArray);
});
+ return newPageArray;
};
RemoteDebugger.prototype.selectPage = function(pageIdKey, cb) {
@@ -127,12 +133,17 @@ RemoteDebugger.prototype.selectPage = function(pageIdKey, cb) {
if (err || res.result.value == 'loading') {
me.pageUnload();
}
+ me.specialCbs['_rpc_forwardGetListing:'] = _.bind(me.onPageChange, me);
cb();
}, 0);
});
});
};
+RemoteDebugger.prototype.onPageChange = function(pageDict) {
+ this.pageChangeCb(this.pageArrayFromDict(pageDict));
+};
+
RemoteDebugger.prototype.executeAtom = function(atom, args, cb) {
var atomSrc = atoms.get(atom);
args = _.map(args, JSON.stringify);
@@ -198,9 +209,9 @@ RemoteDebugger.prototype.pageLoad = function() {
};
RemoteDebugger.prototype.pageUnload = function() {
- logger.debug("Page loading");
- this.pageLoading = true;
- this.waitForDom(noop);
+ logger.debug("Page loading");
+ this.pageLoading = true;
+ this.waitForDom(noop);
};
RemoteDebugger.prototype.waitForDom = function(cb) {
@@ -229,7 +240,9 @@ RemoteDebugger.prototype.handleMessage = function(plist) {
RemoteDebugger.prototype.handleSpecialMessage = function(specialCb) {
var fn = this.specialCbs[specialCb];
if (fn) {
- this.specialCbs[specialCb] = null;
+ if (specialCb != "_rpc_forwardGetListing:") {
+ this.specialCbs[specialCb] = null;
+ }
fn.apply(this, _.rest(arguments));
}
};
View
100 app/ios.js
@@ -259,10 +259,6 @@ IOS.prototype.cleanupAppState = function(cb) {
IOS.prototype.listWebFrames = function(cb, exitCb) {
var me = this;
- if (this.remote) {
- logger.error("Can't enter a web frame when we're already in one!");
- throw new Error("Tried to enter a web frame when we were in one");
- }
if (!this.bundleId) {
logger.error("Can't enter web frame without a bundle ID");
throw new Error("Tried to enter web frame without a bundle ID");
@@ -281,11 +277,36 @@ IOS.prototype.listWebFrames = function(cb, exitCb) {
} else {
me.remote.selectApp(me.bundleId, cb);
}
- }, function() {
- logger.error("Remote debugger crashed before we shut it down!");
- me.stopRemote();
- exitCb();
+ }, _.bind(me.onPageChange, me));
+};
+
+IOS.prototype.onPageChange = function(pageArray) {
+ logger.info("Remote debugger notified us of a new page listing");
+ var newIds = []
+ , me = this;
+ _.each(pageArray, function(page) {
+ newIds.push(page.id.toString());
+ });
+ var newPages = [];
+ _.each(newIds, function(id) {
+ if (!_.contains(me.windowHandleCache, id)) {
+ newPages.push(id);
+ }
});
+ if (this.curWindowHandle === null) {
+ logger.info("We don't appear to have window set yet, ignoring");
+ } else if (newPages.length) {
+ logger.info("We have new pages, going to select page " + newPages[0]);
+ this.remote.selectPage(newPages[0], function() {
+ me.curWindowHandle = newPages[0];
+ });
+ } else if (!_.contains(me.windowHandleCache, me.curWindowHandle.toString())) {
+ logger.error("New page listing from remote debugger doesn't contain " +
+ "current window, not sure how to proceed");
+ } else {
+ logger.info("New page listing is same as old, doing nothing");
+ }
+ this.windowHandleCache = newIds;
};
IOS.prototype.getAtomsElement = function(wdId) {
@@ -507,8 +528,11 @@ IOS.prototype.findAndAct = function(strategy, selector, index, action, actionPar
// if you change these, also change in
// app/uiauto/appium/app.js:elemForAction
, supportedActions = ["tap", "isEnabled", "isValid", "isVisible",
- "value", "name", "label", "setValue"]
+ "value", "name", "label", "setValue", "click",
+ "selectPage"]
, many = index > 0;
+
+ if (action === "click") { action = "tap"; }
var doAction = function(findCb) {
var cmd = ["au.elemForAction(au.getElement", (many ? 's': ''), "By",
stratMap[strategy], "('", selector, "'), ", index].join('');
@@ -593,7 +617,6 @@ IOS.prototype.click = function(elementId, cb) {
};
IOS.prototype.clickCurrent = function(button, cb) {
- //console.log("clicking current loc");
var noMoveToErr = {
status: status.codes.UnknownError.code
, value: "Cannot call click() before calling moveTo() to set coords"
@@ -613,7 +636,6 @@ IOS.prototype.clickCurrent = function(button, cb) {
};
IOS.prototype.clickCoords = function(coords, cb) {
- //console.log("native-tapping coords");
var opts = coords;
opts.tapCount = 1;
opts.duration = 0.3;
@@ -1122,18 +1144,62 @@ IOS.prototype.setWindow = function(name, cb) {
var me = this;
if (_.contains(this.windowHandleCache, name)) {
var pageIdKey = parseInt(name, 10);
- me.remote.selectPage(pageIdKey, function() {
- me.curWindowHandle = pageIdKey;
- cb(null, {
- status: status.codes.Success.code
- , value: ''
+ var next = function() {
+ me.remote.selectPage(pageIdKey, function() {
+ me.curWindowHandle = pageIdKey;
+ cb(null, {
+ status: status.codes.Success.code
+ , value: ''
+ });
});
- });
+ };
+ //if (me.autoWebview) {
+ //me.setSafariWindow(pageIdKey - 1, function(err, res) {
+ //if (err) {
+ //cb(err);
+ //} else if (res.status !== status.codes.Success.code) {
+ //cb(res.status);
+ //} else {
+ //next();
+ //}
+ //});
+ //} else {
+ next();
+ //}
} else {
cb(status.codes.NoSuchWindow.code, null);
}
};
+IOS.prototype.setSafariWindow = function(windowId, cb) {
+ var me = this;
+ var success = function(err, res, cb) {
+ if (err || res.status !== status.codes.Success.code) {
+ cb(err, res);
+ return false;
+ }
+ return true;
+ };
+
+ me.findAndAct('name', 'Pages', 0, 'value', [], function(err, res) {
+ if (success(err, res, cb)) {
+ if (res.value === "") {
+ cb(err, res);
+ } else {
+ me.findAndAct('name', 'Pages', 0, 'tap', [], function(err, res) {
+ if (success(err, res, cb)) {
+ me.findAndAct('tag name', 'pageIndicator', 0, 'selectPage', [windowId], function(err, res) {
+ if (success(err, res, cb)) {
+ me.findAndAct('name', 'Done', 0, 'tap', [], cb);
+ }
+ });
+ }
+ });
+ }
+ }
+ });
+};
+
IOS.prototype.clearWebView = function(cb) {
if (this.curWindowHandle === null) {
cb(new NotImplementedError(), null);
View
1 app/routing.js
@@ -68,6 +68,7 @@ module.exports = function(appium) {
// these are for testing purposes only
rest.post('/wd/hub/produce_error', controller.produceError);
rest.post('/wd/hub/crash', controller.crash);
+ rest.get('/guinea-pig', controller.guineaPig);
// appium-specific extensions to JSONWP
// these aren't part of JSONWP but we want them or something like them to be
View
131 app/test/guinea-pig.html
@@ -0,0 +1,131 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+ <title>I am a page title - Sauce Labs</title>
+ <link href="/css/themes/cupertino/jquery-ui-1.8.5.custom.css" media="screen" rel="stylesheet" type="text/css" />
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
+</head>
+<body>
+
+<h1>This page is a Selenium sandbox</h1>
+
+I am some page content
+<div id="i_am_an_id" class="i_am_a_class">I am a div</div>
+<a href="/test-guinea-pig2.html" id="i am a link">i am a link</a><br/>
+<a href="http://google.com" id="googlelink" target="_blank">i am a google link</a>
+ <div>i appear 3 times</div>
+ <div>i appear 3 times</div>
+ <div>i appear 3 times</div>
+<div id="invisible div" style="display:none;">i am invisible</div>
+<form id="the_forms_id">
+<p>
+<input id="i_am_a_textbox" name="i_am_a_textbox" type="text" value="i has no focus" />
+</p>
+<input type="checkbox" id="unchecked_checkbox" name="unchecked_checkbox"/>
+<input type="checkbox" id="checked_checkbox" name="checked_checkbox" checked="checked"/>
+
+</form>
+<span id="your_comments">
+Your comments: None<br/>
+</span>
+ñ☃
+<script type="text/javascript">
+ $(document).ready(function(){
+ $("#i_am_a_textbox").focus(function(){$(this).val("");})
+ .blur(function(){$(this).val("i has no focus");});
+ });
+</script>
+
+ <div class="border">
+
+ <script type="text/javascript">
+ var resetFeedback = function() {
+ $(".jumpOkButton").css("display", "none")
+ $("form#jumpContact").css("display", "block");
+ $(".formMessage").css("display", "none");
+ $("#comments").attr("value", "");
+ $("#email").attr("value", "");
+ $("#email").trigger("focus");
+ };
+
+ var setupJumpFeedback = function() {
+ $("#jumpContact input[type=submit]").button();
+ var submitFunc = function() {
+ var form = $(this);
+ var data = form.serialize();
+ $("#comments").trigger("focus");
+
+ var successFunc = function(response) {
+ if (response == "Message sent") {
+ $(".formMessage")
+ .text("Thanks for your feedback, we'll get back to you soon if needed.")
+ .slideDown("normal");
+
+ form.slideUp("normal");
+ }
+ else {
+ $(".formMessage")
+ .text("Oops! Looks like something went wrong, please try again.")
+ .slideDown("normal");
+
+ $("input[type=submit]", form).slideDown();
+ }
+
+ $(".jumpOkButton").css("display", "block");
+ };
+
+ var errorFunc = function() {
+ $(".formMessage")
+ .text("Oops! Looks like something went wrong, please try again.")
+ .slideDown("normal");
+
+ $("input[type=submit]", form).slideDown();
+ };
+
+ $.ajax({
+ type: "POST",
+ url: "/jumpstart-send",
+ data: data,
+ success: successFunc,
+ error: errorFunc
+ });
+
+ return false;
+ };
+
+ $("form#jumpContact").submit(submitFunc);
+ };
+ </script>
+
+ <form id="jumpContact" method="post">
+ <p>
+ <label for="fbemail">Email:</label><br>
+ <input id="fbemail" type="text" size="50" name="fbemail" placeholder="We would really like to follow up!"></input>
+ </p>
+ <p>
+ <label for="comments">Comments:</label><br>
+ <textarea id="comments" style="width:400px;height:100px" name="comments" placeholder="Thanks in advance, this is really helpful."></textarea>
+ </p>
+ <div style="float:right">
+ <input class="jumpButton" type="submit" name="submit" id="submit" value="send" />
+ </div>
+ </form>
+ <div class="formMessage"></div><br><br>
+ <center>
+ <input style="display:none" class="jumpOkButton" type="button" value="OK" onclick="resetFeedback();"/>
+ </center>
+
+ </div>
+
+ <p>Server time: <span id="servertime">1363807183</span></p>
+ <p>Client time: <span id="clienttime"></span></p>
+ <script type="text/javascript">
+ $("#clienttime").text(parseInt(new Date().getTime() / 1000))
+ </script>
+
+ <p id="useragent">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22</p>
+
+</body>
+</html>
View
23 test/functional/safari/safari.js
@@ -2,7 +2,11 @@
"use strict";
var describeWd = require("../../helpers/driverblock.js").describeForSafari()
- , webviewTests = require("../../helpers/webview.js").buildTests
+ , wvHelpers = require("../../helpers/webview.js")
+ , webviewTests = wvHelpers.buildTests
+ , loadWebView = wvHelpers.loadWebView
+ , spinTitle = wvHelpers.spinTitle
+ , _ = require('underscore')
, should = require('should');
describeWd('safari init', function(h) {
@@ -39,4 +43,21 @@ describeWd('safari ipad', function(h) {
});
}, null, null, {device: 'iPad Simulator'});
+_.each(["iPhone", "iPad"], function(sim) {
+ describeWd('windows and frames', function(h) {
+ it("should automate a new window if one opens (" + sim + ")", function(done) {
+ loadWebView("safari", h.driver, function() {
+ h.driver.elementById('googlelink', function(err, link) {
+ link.click(function() {
+ spinTitle("Google", h.driver, function(err) {
+ should.not.exist(err);
+ done();
+ });
+ });
+ });
+ }, "http://localhost:4723/guinea-pig");
+ });
+ }, null, null, {device: sim + " Simulator"});
+});
+
webviewTests('safari');
View
48 test/helpers/webview.js
@@ -7,7 +7,7 @@ var driverBlock = require("./driverblock.js")
, should = require('should')
, spinWait = require('./spin.js').spinWait;
-var spinTitle = function (expTitle, driver, cb, timeout) {
+module.exports.spinTitle = function (expTitle, driver, cb, timeout) {
timeout = typeof timeout == 'undefined' ? 60 : timeout;
timeout.should.be.above(0);
driver.title(function(err, pageTitle) {
@@ -16,12 +16,35 @@ var spinTitle = function (expTitle, driver, cb, timeout) {
cb();
} else {
setTimeout(function () {
- spinTitle(expTitle, driver, cb, timeout - 1);
+ module.exports.spinTitle(expTitle, driver, cb, timeout - 1);
}, 500);
}
});
};
+module.exports.loadWebView = function(webviewType, driver, cb, guineaOverride) {
+ var title = 'I am a page title - Sauce Labs';
+ if (typeof guineaOverride === "undefined") {
+ guineaOverride = guinea;
+ }
+ if (webviewType === "safari") {
+ driver.get(guineaOverride, function(err) {
+ should.not.exist(err);
+ module.exports.spinTitle(title, driver, cb);
+ });
+ } else {
+ driver.windowHandles(function(err, handles) {
+ should.not.exist(err);
+ handles.length.should.be.above(0);
+ driver.window(handles[0], function(err) {
+ should.not.exist(err);
+ module.exports.spinTitle(title, driver, cb);
+ });
+ });
+ }
+};
+
+
module.exports.buildTests = function(webviewType) {
if (typeof webviewType === "undefined") {
webviewType = "WebViewApp";
@@ -33,25 +56,12 @@ module.exports.buildTests = function(webviewType) {
desc = driverBlock.describeForApp(webviewType);
}
- var loadWebView = function(driver, cb) {
- var title = 'I am a page title - Sauce Labs';
- if (webviewType === "safari") {
- driver.get(guinea, function(err) {
- should.not.exist(err);
- spinTitle(title, driver, cb);
- });
- } else {
- driver.windowHandles(function(err, handles) {
- should.not.exist(err);
- handles.length.should.be.above(0);
- driver.window(handles[0], function(err) {
- should.not.exist(err);
- spinTitle(title, driver, cb);
- });
- });
- }
+ var loadWebView = function(driver, cb, guineaOverride) {
+ return module.exports.loadWebView(webviewType, driver, cb, guineaOverride);
};
+ var spinTitle = module.exports.spinTitle;
+
desc('window title', function(h) {
it('should return a valid title on web view', function(done) {

0 comments on commit 5fb3dc5

Please sign in to comment.
Something went wrong with that request. Please try again.