I'm trying to test the presence of an UIAlertView with UIAutomation but my handler never gets called.
At the beginning of my javascript i write :
UIATarget.onAlert = function onAlert(alert) {
UIALogger.logMessage("alertShown");
return false;
}
As i understand it, as soon as i specify my onAlert function, it should get called when an alertView appears during my tests.
So i run a test that shows an alertView, here is the code that shows the alert :
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:message message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
alertView.accessibilityLabel = #"alerte d'avertissement";
[alertView show];
I run my test in instruments, the alert shows up but my handler is never called. Has anybody been able to use event handlers with UIAutomation ?
Thanks,
Vincent.
The documentation seems to be wrong. It turns out that alerts are handled on the same thread your script tries to run. So if you want the alert handler to be called, you need to sleep, e.g.,
UIATarget.onAlert = { ... }
window.buttons().triggerAlertButton.tap();
UIATarget.localTarget().delay(4);
Also, it appears that the alert's name and value are always set to null. I was, however, able to access the first static text which contained the alert's title.
Make sure the UI Automation script is still running when the UIAlertView shows.
For example, adding the following line to the end of your script will keep it running until an alert becomes accessible or the grace period for object resolution expires.
// Wait for UIAlert to appear so that UIATarget.onAlert gets called.
target.frontMostApp().alert();
I figured this out by thoroughly reading & understanding Instruments User Guide: Automating UI Testing, which I highly recommend doing as an introduction to UI Automation.
It may also be helpful to review the UIATarget Class Reference, specifically the methods popTimeout, pushTimeout, setTimeout, timeout, and delay.
The below code works for me. The function is handling the alert and "alert Shown" is printed on the logs.
var target = UIATarget.localTarget();
var application = target.frontMostApp();
var window = application.mainWindow();
UIATarget.onAlert = function onAlert(alert){
UIALogger.logMessage("alert Shown");
}
target.frontMostApp().mainWindow().tableViews()[0]
.cells()["Fhgui"].buttons()["Images"].tap();
// Alert detected. Expressions for handling alerts
// should be moved into the UIATarget.onAlert function definition.
target.frontMostApp().alert().defaultButton().tap();
#vdaubry the solution is simple.
According to Apple documentation, if you want to handle alerts manually then you should return true instead of false in onAlert(alert)
UIATarget.onAlert = function onAlert(alert) {
UIALogger.logMessage("alertShown");
return true;
}
#Drew Crawford the delays will not work because by default can button is clicked by UI Automation. The documentation is not wrong but it is not clearly explained.
I was having "never called alert handler" problem too.
Simply restarting apple's Instruments solved it for me :-).
e.g. - onAlert is not called
var target = UIATarget.localTarget();
target.buttons()["ShowAlert"].tap()
UIAtarget.onAlert = function onAlert(alert)
{...}
-
e.g. - onAlert is called
var target = UIATarget.localTarget();
UIAtarget.onAlert = function onAlert(alert)
{......}
target.buttons()["ShowAlert"].tap()
or
#import "onAlert.js"
var target = UIATarget.localTarget();
target.buttons()["ShowAlert"].tap()
Try it out.
Following snippet works for me on XCode 6.3.1 & Instruments(6.3.1 (6D1002)) :
var target = UIATarget.localTarget();
// Following line shows an internal alert with 'Cancel' & 'Continue' buttons
target.frontMostApp().mainWindow().buttons()["ShowAlert"].tap();
// Handle an internal alert
UIATarget.onAlert = function onAlert(alert) {
return true;
}
// Perform Tap on alert.
target.frontMostApp().alert().buttons()["Continue"].tap();
Related
It seems that if Find.Execute finds a result inside a ContentControl, it will cause the ContentControlOnEnter and ContentControlOnExit events to fire. It's particularly annoying because the exit event fires even if the selection is still in the content control, so any code which sets the states of buttons dependent upon a content control being active will appear to be in the incorrect state.
Given a document containing a single content control with the word "test", and the following code:
// In setup
Application.ActiveDocument.ContentControlOnEnter += ActiveDocument_ContentControlOnEnter;
private void ActiveDocument_ContentControlOnEnter(Word.ContentControl ContentControl)
{
var selRange = _Application.Selection.Range;
_logger.Debug(m => m("Selection: {0}-{1}", selRange.Start, selRange.End));
}
//Later in another method
var finder = _Application.ActiveDocument.Range().Find;
_logger.Debug("Find.Execute start");
finder.Execute("test);
_logger.Debug("Find.Execute end");
The following gets logged:
38137 [VSTA_Main] DEBUG - Find.Execute start
38141 [VSTA_Main] DEBUG - Selection: 1-5
38149 [VSTA_Main] DEBUG - Find.Execute end
We have a lot of code that handles ContentControlOnEnter and ContentControlOnExit events, and having the find operation cause them to be called is really causing problems!
Is there any way to use Find.Execute without having it trigger these events? Failing that, is there a good way to distinguish between the Find-triggered ones and the genuine user ones? I have tried using the time between the enter and exit events, but this is not reliable.
I had similar problems in Word, though it was about the Selection event. I tried many solutions, but only one helped. In your case, make a new field bool _skipEnterAndExitEvents and set it true before calling
finder.Execute("test) and false after calling. And in the enter and exit event handlers check this field, if the field is true then just skip. This solutions is not beautiful, looks like a hack, but other solutions are even uglier and don't really work.
I think I found a decent solution:
private bool _doIgnoreNextExit = false;
private void ActiveDocument_ContentControlOnEnter(Word.ContentControl ContentControl)
{
if (Application.Selection.Find.Found)
{
_logger.Debug("Ignoring CC enter event caused by Find operation");
_doIgnoreNextExit = true;
return;
}
// Do things
}
private void ActiveDocument_ContentControlOnExit(Word.ContentControl ContentControl)
{
if(_doIgnoreNextExit)
{
_logger.Debug("Ignoring fake exit");
_doIgnoreNextExit = false;
return;
}
// Do things
}
I have a QT app, and I'm using native menus on OSX. I have custom-drawn menu items, which I created by attaching my own NSView-derived class to the NSMenuItems that I want to draw specially. This all works fine; the menu items draw right and activate the menu function correctly. However, after activating the menu function, the menu doesn't go away -- it's still tracking the mouse movement. (The cursor still highlights items) I've spent days googling for answers, and I haven't seen a similar problem elsewhere. My NSView class is simple; I've overridden the "rect" class for drawing, and my mouseUp event is here:
-(void)mouseUp:(NSEvent *)theEvent
{
NSMenuItem* item = [self enclosingMenuItem];
if ( item != nil ){
NSMenu *menu = [item menu];
if ( menu != nil ){
[menu cancelTracking];
[NSApp sendAction:[item action] to:[item target] from:item];
}
}
}
I've also tried using "cancelTrackingWithoutAnimation", and I've tried calling cancelTracking on the parent menuBar. Can anyone tell me under what circumstances "cancelTracking" might fail? I'm not sure what to try next. Thanks.
Its too late but I also faced the same issue and fixed it by using carbon API CancelMenuTracking(),
CancelMenuTracking(
MenuRef inRootMenu,
Boolean inImmediate,
UInt32 inDismissalReason)
Used _NSGetCarbonMenu to get the menuref of NSMenu.
menuRef = _NSGetCarbonMenu(myMenu);
CancelMenuTracking(menuRef,YES,kHIMenuDismissedByCancelMenuTracking); for 10.5 and CancelMenuTracking(menuRef,YES,0); for 10.6 and above
Assume button A in an HTML5 webapp built with jQuery Mobile.
If someone taps button A, we call foo(). Foo() should get called once even if the user double taps button A.
We tried using event.preventDefault(), but that didn't stop the second tap from invoking foo(). event.stopImmediatePropagation() might work, but it also stops other methods further up the stack and may not lead to clean code maintenance.
Other suggestions? Maintaining a tracking variable seems like an awfully ugly solution and is undesirable.
You can set a flag and check if it's OK to run the foo() function or unbind the event for the time you don't want the user to be able to use it and then re-bind the event handler after a delay (just a couple options).
Here's what I would do. I would use a timeout to exclude the subsequent events:
$(document).delegate('#my-page-id', 'pageinit', function () {
//setup a flag to determine if it's OK to run the event handler
var okFlag = true;
//bind event handler to the element in question for the `click` event
$('#my-button-id').bind('click', function () {
//check to see if the flag is set to `true`, do nothing if it's not
if (okFlag) {
//set the flag to `false` so the event handler will be disabled until the timeout resolves
okFlag = false;
//set a timeout to set the flag back to `true` which enables the event handler once again
//you can change the delay for the timeout to whatever you may need, note that units are in milliseconds
setTimeout(function () {
okFlag = true;
}, 300);
//and now, finally, run your original event handler
foo();
}
});
});
I've created a sample here http://jsfiddle.net/kiliman/kH924/
If you're using <a data-role="button"> type buttons, there is no 'disabled' status, but you can add the appropriate class to give it the disabled look.
In your event handler, check to see if the button has the ui-disabled class, and if so, you can return right away. If it doesn't, add the ui-disabled class, then call foo()
If you want to re-enable the button, simply remove the class.
$(function() {
$('#page').bind('pageinit', function(e, data) {
// initialize page
$('#dofoo').click(function() {
var $btn = $(this),
isDisabled = $btn.hasClass('ui-disabled');
if (isDisabled) {
e.preventDefault();
return;
}
$btn.addClass('ui-disabled');
foo();
});
});
function foo() {
alert('I did foo');
}
});
I am trying to test the presence of UIAlertview in app.
From the documentation i have added the following handler in the starting of the script.
UIATarget.onAlert = function onAlert(alert) {
var title = alert.name();
// add a warning to the log for each alert encountered
UIALogger.logWarning("Alert with title '" + title + "' encountered!");
UIATarget.localTarget().captureScreenWithName("alert_" + (new Date()).UTC());
// test if your script should handle the alert, and if so, return true
// otherwise, return false to use the default handler
return false;
}
But this handler is not being called when the alert pop ups. Can any one help me with this?
It might happen if the alert pops up very soon. You can still access its title with
UIATarget.localTarget().frontMostApp().alert().scrollViews()[0].staticTexts()[0].value();
Note: leave out scrollViews()[0] prior to iOS7.
system.logElementTree();
var target = UIATarget.localTarget();
target.onAlert = function onAlert(alert) {
UIALogger.logDebug("There was an alert!");
target.onAlert.buttons()["No"].tap({x:164,y:278});
return false;
even though no option is clicked systen not performing any action
Can anyone please help me ...
Instead of BamboOS suggestion which loops through various positions, you can try this inside your onAlert function:
alert.tapWithOptions({tapOffset:{x:0.5, y:0.6}});
This tap targets the middle of the UIAAlert (x:0.5) and 60% from top to bottom (y:0.6). This works when there is only one button. You have multiple buttons, then you have to changed the value of x. This works for me.
I just published a blog post regarding UI Automation and dealing with alerts:
http://www.conduce.net/Blog.aspx?f=Automated-Test-of-iPad-Apps
Basically following alert handler worked for me:
UIATarget.onAlert = function onAlert(alert){
var name = alert.name();
UIALogger.logMessage("alert "+name+" encountered");
if(name == "errorAlert"){
var positionX = 500;
for(var positionY=300; positionY<600;positionY+=10){
target.tap({x:positionX,y:positionY});
}
return true;
}
return false;
}
I would either use the "cancelButton" or "defaultButton" methods when handling alerts.