Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 6 commits
  • 7 files changed
  • 0 comments
  • 3 contributors
11 app/controller.js
@@ -4,6 +4,8 @@
4 4 var status = require('./uiauto/lib/status')
5 5 , logger = require('../logger.js').get('appium')
6 6 , _s = require("underscore.string")
  7 + , fs = require('fs')
  8 + , path = require('path')
7 9 , _ = require('underscore');
8 10
9 11 function getResponseHandler(req, res) {
@@ -629,3 +631,12 @@ exports.produceError = function(req, res) {
629 631 exports.crash = function() {
630 632 throw new Error("We just tried to crash Appium!");
631 633 };
  634 +
  635 +exports.guineaPig = function(req, res) {
  636 + var file = path.resolve(__dirname, "test/guinea-pig.html");
  637 + fs.readFile(file, function(err, data) {
  638 + if (err) return res.send(500);
  639 + res.set('Content-Type', 'text/html');
  640 + res.send(data);
  641 + });
  642 +};
41 app/hybrid/ios/remote-debugger.js
@@ -43,6 +43,7 @@ var RemoteDebugger = function(onDisconnect) {
43 43 this.curMsgId = 0;
44 44 this.dataCbs = [];
45 45 this.onAppDisconnect = onDisconnect || noop;
  46 + this.pageChangeCb = noop;
46 47 this.specialCbs = {
47 48 '_rpc_reportIdentifier:': noop
48 49 , '_rpc_forwardGetListing:': noop
@@ -58,8 +59,9 @@ var RemoteDebugger = function(onDisconnect) {
58 59 // API
59 60 // ====================================
60 61
61   -RemoteDebugger.prototype.connect = function(cb) {
  62 +RemoteDebugger.prototype.connect = function(cb, pageChangeCb) {
62 63 var me = this;
  64 + this.pageChangeCb = pageChangeCb;
63 65 this.socket = new net.Socket({type: 'tcp6'});
64 66 this.socket.on('close', function() {
65 67 logger.info('Debugger socket disconnected');
@@ -99,17 +101,21 @@ RemoteDebugger.prototype.selectApp = function(appIdKey, cb) {
99 101 this.appIdKey = appIdKey;
100 102 var connectToApp = messages.connectToApp(this.connId, this.appIdKey);
101 103 logger.info("Selecting app");
102   - this.send(connectToApp, function(pageDict) {
103   - var newPageArray = [];
104   - _.each(pageDict, function(dict) {
105   - newPageArray.push({
106   - id: dict.WIRPageIdentifierKey
107   - , title: dict.WIRTitleKey
108   - , url: dict.WIRURLKey
109   - });
  104 + this.send(connectToApp, _.bind(function(pageDict) {
  105 + cb(this.pageArrayFromDict(pageDict));
  106 + }, this));
  107 +};
  108 +
  109 +RemoteDebugger.prototype.pageArrayFromDict = function(pageDict) {
  110 + var newPageArray = [];
  111 + _.each(pageDict, function(dict) {
  112 + newPageArray.push({
  113 + id: dict.WIRPageIdentifierKey
  114 + , title: dict.WIRTitleKey
  115 + , url: dict.WIRURLKey
110 116 });
111   - cb(newPageArray);
112 117 });
  118 + return newPageArray;
113 119 };
114 120
115 121 RemoteDebugger.prototype.selectPage = function(pageIdKey, cb) {
@@ -127,12 +133,17 @@ RemoteDebugger.prototype.selectPage = function(pageIdKey, cb) {
127 133 if (err || res.result.value == 'loading') {
128 134 me.pageUnload();
129 135 }
  136 + me.specialCbs['_rpc_forwardGetListing:'] = _.bind(me.onPageChange, me);
130 137 cb();
131 138 }, 0);
132 139 });
133 140 });
134 141 };
135 142
  143 +RemoteDebugger.prototype.onPageChange = function(pageDict) {
  144 + this.pageChangeCb(this.pageArrayFromDict(pageDict));
  145 +};
  146 +
136 147 RemoteDebugger.prototype.executeAtom = function(atom, args, cb) {
137 148 var atomSrc = atoms.get(atom);
138 149 args = _.map(args, JSON.stringify);
@@ -198,9 +209,9 @@ RemoteDebugger.prototype.pageLoad = function() {
198 209 };
199 210
200 211 RemoteDebugger.prototype.pageUnload = function() {
201   - logger.debug("Page loading");
202   - this.pageLoading = true;
203   - this.waitForDom(noop);
  212 + logger.debug("Page loading");
  213 + this.pageLoading = true;
  214 + this.waitForDom(noop);
204 215 };
205 216
206 217 RemoteDebugger.prototype.waitForDom = function(cb) {
@@ -229,7 +240,9 @@ RemoteDebugger.prototype.handleMessage = function(plist) {
229 240 RemoteDebugger.prototype.handleSpecialMessage = function(specialCb) {
230 241 var fn = this.specialCbs[specialCb];
231 242 if (fn) {
232   - this.specialCbs[specialCb] = null;
  243 + if (specialCb != "_rpc_forwardGetListing:") {
  244 + this.specialCbs[specialCb] = null;
  245 + }
233 246 fn.apply(this, _.rest(arguments));
234 247 }
235 248 };
114 app/ios.js
@@ -259,10 +259,6 @@ IOS.prototype.cleanupAppState = function(cb) {
259 259
260 260 IOS.prototype.listWebFrames = function(cb, exitCb) {
261 261 var me = this;
262   - if (this.remote) {
263   - logger.error("Can't enter a web frame when we're already in one!");
264   - throw new Error("Tried to enter a web frame when we were in one");
265   - }
266 262 if (!this.bundleId) {
267 263 logger.error("Can't enter web frame without a bundle ID");
268 264 throw new Error("Tried to enter web frame without a bundle ID");
@@ -281,11 +277,36 @@ IOS.prototype.listWebFrames = function(cb, exitCb) {
281 277 } else {
282 278 me.remote.selectApp(me.bundleId, cb);
283 279 }
284   - }, function() {
285   - logger.error("Remote debugger crashed before we shut it down!");
286   - me.stopRemote();
287   - exitCb();
  280 + }, _.bind(me.onPageChange, me));
  281 +};
  282 +
  283 +IOS.prototype.onPageChange = function(pageArray) {
  284 + logger.info("Remote debugger notified us of a new page listing");
  285 + var newIds = []
  286 + , me = this;
  287 + _.each(pageArray, function(page) {
  288 + newIds.push(page.id.toString());
  289 + });
  290 + var newPages = [];
  291 + _.each(newIds, function(id) {
  292 + if (!_.contains(me.windowHandleCache, id)) {
  293 + newPages.push(id);
  294 + }
288 295 });
  296 + if (this.curWindowHandle === null) {
  297 + logger.info("We don't appear to have window set yet, ignoring");
  298 + } else if (newPages.length) {
  299 + logger.info("We have new pages, going to select page " + newPages[0]);
  300 + this.remote.selectPage(newPages[0], function() {
  301 + me.curWindowHandle = newPages[0];
  302 + });
  303 + } else if (!_.contains(me.windowHandleCache, me.curWindowHandle.toString())) {
  304 + logger.error("New page listing from remote debugger doesn't contain " +
  305 + "current window, not sure how to proceed");
  306 + } else {
  307 + logger.info("New page listing is same as old, doing nothing");
  308 + }
  309 + this.windowHandleCache = newIds;
289 310 };
290 311
291 312 IOS.prototype.getAtomsElement = function(wdId) {
@@ -507,8 +528,11 @@ IOS.prototype.findAndAct = function(strategy, selector, index, action, actionPar
507 528 // if you change these, also change in
508 529 // app/uiauto/appium/app.js:elemForAction
509 530 , supportedActions = ["tap", "isEnabled", "isValid", "isVisible",
510   - "value", "name", "label", "setValue"]
  531 + "value", "name", "label", "setValue", "click",
  532 + "selectPage"]
511 533 , many = index > 0;
  534 +
  535 + if (action === "click") { action = "tap"; }
512 536 var doAction = function(findCb) {
513 537 var cmd = ["au.elemForAction(au.getElement", (many ? 's': ''), "By",
514 538 stratMap[strategy], "('", selector, "'), ", index].join('');
@@ -593,7 +617,6 @@ IOS.prototype.click = function(elementId, cb) {
593 617 };
594 618
595 619 IOS.prototype.clickCurrent = function(button, cb) {
596   - //console.log("clicking current loc");
597 620 var noMoveToErr = {
598 621 status: status.codes.UnknownError.code
599 622 , value: "Cannot call click() before calling moveTo() to set coords"
@@ -613,7 +636,6 @@ IOS.prototype.clickCurrent = function(button, cb) {
613 636 };
614 637
615 638 IOS.prototype.clickCoords = function(coords, cb) {
616   - //console.log("native-tapping coords");
617 639 var opts = coords;
618 640 opts.tapCount = 1;
619 641 opts.duration = 0.3;
@@ -798,7 +820,19 @@ IOS.prototype.getSize = function(elementId, cb) {
798 820
799 821 IOS.prototype.getWindowSize = function(windowHandle, cb) {
800 822 if (this.curWindowHandle) {
801   - cb(new NotImplementedError(), null);
  823 + if(windowHandle !== "current") {
  824 + cb(null, {
  825 + status: status.codes.NoSuchWindow.code
  826 + , value: "Currently only getting current window size is supported."
  827 + });
  828 + } else {
  829 + this.remote.executeAtom('get_window_size', [], function(err, res) {
  830 + cb(null, {
  831 + status: status.codes.Success.code
  832 + , value: res
  833 + });
  834 + });
  835 + }
802 836 } else {
803 837 if(windowHandle !== "current") {
804 838 cb(null, {
@@ -1110,18 +1144,62 @@ IOS.prototype.setWindow = function(name, cb) {
1110 1144 var me = this;
1111 1145 if (_.contains(this.windowHandleCache, name)) {
1112 1146 var pageIdKey = parseInt(name, 10);
1113   - me.remote.selectPage(pageIdKey, function() {
1114   - me.curWindowHandle = pageIdKey;
1115   - cb(null, {
1116   - status: status.codes.Success.code
1117   - , value: ''
  1147 + var next = function() {
  1148 + me.remote.selectPage(pageIdKey, function() {
  1149 + me.curWindowHandle = pageIdKey;
  1150 + cb(null, {
  1151 + status: status.codes.Success.code
  1152 + , value: ''
  1153 + });
1118 1154 });
1119   - });
  1155 + };
  1156 + //if (me.autoWebview) {
  1157 + //me.setSafariWindow(pageIdKey - 1, function(err, res) {
  1158 + //if (err) {
  1159 + //cb(err);
  1160 + //} else if (res.status !== status.codes.Success.code) {
  1161 + //cb(res.status);
  1162 + //} else {
  1163 + //next();
  1164 + //}
  1165 + //});
  1166 + //} else {
  1167 + next();
  1168 + //}
1120 1169 } else {
1121 1170 cb(status.codes.NoSuchWindow.code, null);
1122 1171 }
1123 1172 };
1124 1173
  1174 +IOS.prototype.setSafariWindow = function(windowId, cb) {
  1175 + var me = this;
  1176 + var success = function(err, res, cb) {
  1177 + if (err || res.status !== status.codes.Success.code) {
  1178 + cb(err, res);
  1179 + return false;
  1180 + }
  1181 + return true;
  1182 + };
  1183 +
  1184 + me.findAndAct('name', 'Pages', 0, 'value', [], function(err, res) {
  1185 + if (success(err, res, cb)) {
  1186 + if (res.value === "") {
  1187 + cb(err, res);
  1188 + } else {
  1189 + me.findAndAct('name', 'Pages', 0, 'tap', [], function(err, res) {
  1190 + if (success(err, res, cb)) {
  1191 + me.findAndAct('tag name', 'pageIndicator', 0, 'selectPage', [windowId], function(err, res) {
  1192 + if (success(err, res, cb)) {
  1193 + me.findAndAct('name', 'Done', 0, 'tap', [], cb);
  1194 + }
  1195 + });
  1196 + }
  1197 + });
  1198 + }
  1199 + }
  1200 + });
  1201 +};
  1202 +
1125 1203 IOS.prototype.clearWebView = function(cb) {
1126 1204 if (this.curWindowHandle === null) {
1127 1205 cb(new NotImplementedError(), null);
1  app/routing.js
@@ -68,6 +68,7 @@ module.exports = function(appium) {
68 68 // these are for testing purposes only
69 69 rest.post('/wd/hub/produce_error', controller.produceError);
70 70 rest.post('/wd/hub/crash', controller.crash);
  71 + rest.get('/guinea-pig', controller.guineaPig);
71 72
72 73 // appium-specific extensions to JSONWP
73 74 // these aren't part of JSONWP but we want them or something like them to be
131 app/test/guinea-pig.html
... ... @@ -0,0 +1,131 @@
  1 +
  2 +<!DOCTYPE html>
  3 +<html>
  4 +<head>
  5 + <title>I am a page title - Sauce Labs</title>
  6 + <link href="/css/themes/cupertino/jquery-ui-1.8.5.custom.css" media="screen" rel="stylesheet" type="text/css" />
  7 + <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
  8 + <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
  9 +</head>
  10 +<body>
  11 +
  12 +<h1>This page is a Selenium sandbox</h1>
  13 +
  14 +I am some page content
  15 +<div id="i_am_an_id" class="i_am_a_class">I am a div</div>
  16 +<a href="/test-guinea-pig2.html" id="i am a link">i am a link</a><br/>
  17 +<a href="http://google.com" id="googlelink" target="_blank">i am a google link</a>
  18 + <div>i appear 3 times</div>
  19 + <div>i appear 3 times</div>
  20 + <div>i appear 3 times</div>
  21 +<div id="invisible div" style="display:none;">i am invisible</div>
  22 +<form id="the_forms_id">
  23 +<p>
  24 +<input id="i_am_a_textbox" name="i_am_a_textbox" type="text" value="i has no focus" />
  25 +</p>
  26 +<input type="checkbox" id="unchecked_checkbox" name="unchecked_checkbox"/>
  27 +<input type="checkbox" id="checked_checkbox" name="checked_checkbox" checked="checked"/>
  28 +
  29 +</form>
  30 +<span id="your_comments">
  31 +Your comments: None<br/>
  32 +</span>
  33 +ñ☃
  34 +<script type="text/javascript">
  35 + $(document).ready(function(){
  36 + $("#i_am_a_textbox").focus(function(){$(this).val("");})
  37 + .blur(function(){$(this).val("i has no focus");});
  38 + });
  39 +</script>
  40 +
  41 + <div class="border">
  42 +
  43 + <script type="text/javascript">
  44 + var resetFeedback = function() {
  45 + $(".jumpOkButton").css("display", "none")
  46 + $("form#jumpContact").css("display", "block");
  47 + $(".formMessage").css("display", "none");
  48 + $("#comments").attr("value", "");
  49 + $("#email").attr("value", "");
  50 + $("#email").trigger("focus");
  51 + };
  52 +
  53 + var setupJumpFeedback = function() {
  54 + $("#jumpContact input[type=submit]").button();
  55 + var submitFunc = function() {
  56 + var form = $(this);
  57 + var data = form.serialize();
  58 + $("#comments").trigger("focus");
  59 +
  60 + var successFunc = function(response) {
  61 + if (response == "Message sent") {
  62 + $(".formMessage")
  63 + .text("Thanks for your feedback, we'll get back to you soon if needed.")
  64 + .slideDown("normal");
  65 +
  66 + form.slideUp("normal");
  67 + }
  68 + else {
  69 + $(".formMessage")
  70 + .text("Oops! Looks like something went wrong, please try again.")
  71 + .slideDown("normal");
  72 +
  73 + $("input[type=submit]", form).slideDown();
  74 + }
  75 +
  76 + $(".jumpOkButton").css("display", "block");
  77 + };
  78 +
  79 + var errorFunc = function() {
  80 + $(".formMessage")
  81 + .text("Oops! Looks like something went wrong, please try again.")
  82 + .slideDown("normal");
  83 +
  84 + $("input[type=submit]", form).slideDown();
  85 + };
  86 +
  87 + $.ajax({
  88 + type: "POST",
  89 + url: "/jumpstart-send",
  90 + data: data,
  91 + success: successFunc,
  92 + error: errorFunc
  93 + });
  94 +
  95 + return false;
  96 + };
  97 +
  98 + $("form#jumpContact").submit(submitFunc);
  99 + };
  100 + </script>
  101 +
  102 + <form id="jumpContact" method="post">
  103 + <p>
  104 + <label for="fbemail">Email:</label><br>
  105 + <input id="fbemail" type="text" size="50" name="fbemail" placeholder="We would really like to follow up!"></input>
  106 + </p>
  107 + <p>
  108 + <label for="comments">Comments:</label><br>
  109 + <textarea id="comments" style="width:400px;height:100px" name="comments" placeholder="Thanks in advance, this is really helpful."></textarea>
  110 + </p>
  111 + <div style="float:right">
  112 + <input class="jumpButton" type="submit" name="submit" id="submit" value="send" />
  113 + </div>
  114 + </form>
  115 + <div class="formMessage"></div><br><br>
  116 + <center>
  117 + <input style="display:none" class="jumpOkButton" type="button" value="OK" onclick="resetFeedback();"/>
  118 + </center>
  119 +
  120 + </div>
  121 +
  122 + <p>Server time: <span id="servertime">1363807183</span></p>
  123 + <p>Client time: <span id="clienttime"></span></p>
  124 + <script type="text/javascript">
  125 + $("#clienttime").text(parseInt(new Date().getTime() / 1000))
  126 + </script>
  127 +
  128 + <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>
  129 +
  130 +</body>
  131 +</html>
23 test/functional/safari/safari.js
@@ -2,7 +2,11 @@
2 2 "use strict";
3 3
4 4 var describeWd = require("../../helpers/driverblock.js").describeForSafari()
5   - , webviewTests = require("../../helpers/webview.js").buildTests
  5 + , wvHelpers = require("../../helpers/webview.js")
  6 + , webviewTests = wvHelpers.buildTests
  7 + , loadWebView = wvHelpers.loadWebView
  8 + , spinTitle = wvHelpers.spinTitle
  9 + , _ = require('underscore')
6 10 , should = require('should');
7 11
8 12 describeWd('safari init', function(h) {
@@ -39,4 +43,21 @@ describeWd('safari ipad', function(h) {
39 43 });
40 44 }, null, null, {device: 'iPad Simulator'});
41 45
  46 +_.each(["iPhone", "iPad"], function(sim) {
  47 + describeWd('windows and frames', function(h) {
  48 + it("should automate a new window if one opens (" + sim + ")", function(done) {
  49 + loadWebView("safari", h.driver, function() {
  50 + h.driver.elementById('googlelink', function(err, link) {
  51 + link.click(function() {
  52 + spinTitle("Google", h.driver, function(err) {
  53 + should.not.exist(err);
  54 + done();
  55 + });
  56 + });
  57 + });
  58 + }, "http://localhost:4723/guinea-pig");
  59 + });
  60 + }, null, null, {device: sim + " Simulator"});
  61 +});
  62 +
42 63 webviewTests('safari');
62 test/helpers/webview.js
@@ -7,7 +7,7 @@ var driverBlock = require("./driverblock.js")
7 7 , should = require('should')
8 8 , spinWait = require('./spin.js').spinWait;
9 9
10   -var spinTitle = function (expTitle, driver, cb, timeout) {
  10 +module.exports.spinTitle = function (expTitle, driver, cb, timeout) {
11 11 timeout = typeof timeout == 'undefined' ? 60 : timeout;
12 12 timeout.should.be.above(0);
13 13 driver.title(function(err, pageTitle) {
@@ -16,12 +16,35 @@ var spinTitle = function (expTitle, driver, cb, timeout) {
16 16 cb();
17 17 } else {
18 18 setTimeout(function () {
19   - spinTitle(expTitle, driver, cb, timeout - 1);
  19 + module.exports.spinTitle(expTitle, driver, cb, timeout - 1);
20 20 }, 500);
21 21 }
22 22 });
23 23 };
24 24
  25 +module.exports.loadWebView = function(webviewType, driver, cb, guineaOverride) {
  26 + var title = 'I am a page title - Sauce Labs';
  27 + if (typeof guineaOverride === "undefined") {
  28 + guineaOverride = guinea;
  29 + }
  30 + if (webviewType === "safari") {
  31 + driver.get(guineaOverride, function(err) {
  32 + should.not.exist(err);
  33 + module.exports.spinTitle(title, driver, cb);
  34 + });
  35 + } else {
  36 + driver.windowHandles(function(err, handles) {
  37 + should.not.exist(err);
  38 + handles.length.should.be.above(0);
  39 + driver.window(handles[0], function(err) {
  40 + should.not.exist(err);
  41 + module.exports.spinTitle(title, driver, cb);
  42 + });
  43 + });
  44 + }
  45 +};
  46 +
  47 +
25 48 module.exports.buildTests = function(webviewType) {
26 49 if (typeof webviewType === "undefined") {
27 50 webviewType = "WebViewApp";
@@ -33,25 +56,12 @@ module.exports.buildTests = function(webviewType) {
33 56 desc = driverBlock.describeForApp(webviewType);
34 57 }
35 58
36   - var loadWebView = function(driver, cb) {
37   - var title = 'I am a page title - Sauce Labs';
38   - if (webviewType === "safari") {
39   - driver.get(guinea, function(err) {
40   - should.not.exist(err);
41   - spinTitle(title, driver, cb);
42   - });
43   - } else {
44   - driver.windowHandles(function(err, handles) {
45   - should.not.exist(err);
46   - handles.length.should.be.above(0);
47   - driver.window(handles[0], function(err) {
48   - should.not.exist(err);
49   - spinTitle(title, driver, cb);
50   - });
51   - });
52   - }
  59 + var loadWebView = function(driver, cb, guineaOverride) {
  60 + return module.exports.loadWebView(webviewType, driver, cb, guineaOverride);
53 61 };
54 62
  63 + var spinTitle = module.exports.spinTitle;
  64 +
55 65
56 66 desc('window title', function(h) {
57 67 it('should return a valid title on web view', function(done) {
@@ -341,6 +351,20 @@ module.exports.buildTests = function(webviewType) {
341 351 });
342 352 });
343 353
  354 + desc('getWindowSize', function(h) {
  355 + it('should return the right size', function(done) {
  356 + loadWebView(h.driver, function() {
  357 + h.driver.getWindowSize(function(err, size) {
  358 + should.not.exist(err);
  359 + // iphone and ipad
  360 + [356, 928, 788].should.include(size.height);
  361 + [320, 768, 414].should.include(size.width);
  362 + done();
  363 + });
  364 + });
  365 + });
  366 + });
  367 +
344 368 desc('moveTo and click', function(h) {
345 369 it('should be able to click on arbitrary x-y elements', function(done) {
346 370 loadWebView(h.driver, function() {

No commit comments for this range

Something went wrong with that request. Please try again.