I am very new to the iOS UIAutomation, here is the problem I am facing
I have a view hierarchy as below and want to access CustomView2 elements in the automation sctipt
UIWindow > UIScrollView > CustomView1 (Multiple) > CustomView2 (Multiple)
The scrollview has subviews of type CustomView1 and CustomView1 in turn has subviews of type CustomView2.
I have assigned the accessibility information to all of the views in hierarchy, but I am not able to access the CustomView2 elements in my automation script.
When I do a logElementTree() on UIScrollView, all I get is the instances of CustomView2, CustomView2 is not even in the tree structure of UIWindow.
Please suggest if is there anything missing or anything going wrong.
Here is the code I am using
var mainWindow = application.mainWindow();
var scrollView = mainWindow.scrollViews()[0];
var custom1 = scrollView.elements().withName("CustomView1");
for(var index=0; index<custom1.length; index++){
currentIndustry.tap();
custom1[index].logElementTree();
var custom2 = custom1[index].elements().withName("CustomView2");
UIALogger.logPass("Custom2 Length : " + custom2.length);
}
The tree printed by
custom1[index].logElementTree();
does not contain instances of CustomView2
P.S. I need to access both CustomView1 and CustomView2 elements
This may help you if you haven't found your answer already:
UIAutomation Nested Accessibilty Elements Disappear from Hierarchy
In your CustomView1 class implement the following:
- (BOOL)isAccessibilityElement
{
return NO;
}
This will make your CustomView2 elements visible when you logElementTree().
If CustomView2 contains accessible elements and is basically a container view as well, then implement the above in that class as well, and it's child views will become accessible.
Related
Recently I wrote an app with one single scene and ViewController. I had to set a custom background picture for the View, which the ViewController manages (i.e. my top view contained the UIImageView). Later on I had to implement some logic in ViewController, so that it properly rotates/changes the picture when the screen is rotated. Also I had to overwrite some properties like preferredStatusBarStyle for the ViewController.
Now I have to implement a couple more scenes / screens in my app and it turns out that they all must have the same design as this currently present screen, so I think it makes sense if I create a CommonViewController which contains this common rotation-related logic for background picture, so that I can inherit all my other ViewControllers from this CommonViewController. The only problem I have is that CommonViewController "requires" that the view it manages has a backgroundPicture: UIView property, which I don't know how to ensure.
If I create a new file CommonViewController together with XIB-file, I can add the backgroundPicture image view in XIB and connect it with code (via regular "control-drag" approach), but apparently this won't work, as there is no guarantee that the views which inherit CommonViewController will have this property. What is the correct way to solve this issue without hacks on iOS in Swift?
Unfortunately I could not find a solution, maybe I've been searching for something wrong. It seems that I somehow need to inherit a CommonViewController for each scene (for each CustomViewController), but also I have to somehow set the top view of each of these controller's to be equal to some CommonView, so that CommonViewController does not crash when I try to access #IBOutlet weak var backgroundPicutre: UIImageView!.
The obvious way would be to define some method or property in the CommonViewController, so that the controllers which inherit it, can implement / override it, but it seems a bit hacky as it still requires copy-pasting in each ViewController which inherits CommonViewController.
How I imagined the solution: I create CustomViewController: CommonViewController, then I create a view controller in my Storyboard and change the "Class" property to "CustomViewController" (in property editor), then I select the view which corresponds to this newly added controller and change the "Class" property to "BackgroundImageView. But I'm not sure if it's the correct way to do (also I doubt thatCustomViewControllerwill properly "connect" itsIBOutletfieldbakcgroundImageViewwith the correspondingUIViewfromBackgroundImageView`, that's why I wanted to ask experts what they think about it.
I think you should define your base controller (CommonViewController) entirely in code, i.e. don't use no xibs / storyboards for the base controller. It doesn't mean you should rid off storyboards / xibs completely. Interface for alll other view controllers except CommonViewController may still be implemented with xibs / storyboards.
In this case CommonViewController implementation may look like this:
import UIKit
class CommonViewController: UIViewController {
// use this property every time you need to
// manipulate backgroundPicture
var backgroundPicture: UIImageView = {
// Replace with your image name
let image = UIImage(named: "BackgroundPicture")!
let imageView = UIImageView()
imageView.image = image
return imageView
}()
override func viewDidLoad() {
// If subclass overrides viewDidLoad()
// it should contain super.viewDidLoad()
super.viewDidLoad()
view.addSubview(backgroundPicture)
// Align backgroundPicture to bounds of superview
// You can remove this code and implement
// your own alignment with frames or Autolayout
backgroundPicture.frame = view.bounds
// Send backgroundPicture to back of the view
// Otherwise backgroundPicture may overlap views added in subclasses
view.sendSubviewToBack(backgroundPicture)
}
override func viewDidLayoutSubviews() {
// If subclass overrides viewDidLayoutSubviews()
// It should contain super.viewDidLayoutSubviews()
super.viewDidLayoutSubvews()
// Align backgroundPicture to bounds of superview
// You can remove this code and implement
// your own alignment with frames or Autolayout
backgroundPicture.frame = view.bounds
}
}
I would like to create multiple UIViews that can be reproduced by using a single function. I have a UIView that is placed on a storyboard and connected to my class with an IBOutlet:
#IBOutlet weak var contentView: UIView!
I have a function that loads a xib into my UIView:
func createView(layoutConstant: CGFloat) {
if let customView = NSBundle.mainBundle().loadNibNamed("TestView", owner: self, options: nil).first as? TestView {
contentViewTopLayoutConstraint.constant = layoutConstant
contentView.addSubview(customView)
}
}
I am now trying to add two of them to my view, but only one shows up:
createView(0)
createView(70)
Any ideas?
I think both views are added, although they happen to be in the same spot, so it looks like there is only one! A quick and dirty way to verify that would be updating your createView method with this line:
contentView.frame.origin.y = layoutConstant
Basically your contentViewTopLayoutConstraint is not connected to the views you are creating, so setting its constant value will not have any impact.
Because frames for all those views will of same size. Origin(x,y) will be same for all the views, so they are overlapping one on another and you can only see the top one view.
In your code example it looks like you're only setting a layout constraint on the contentView you are placing your two new views inside of. What you need to do is set layout constraints on the two views your are placing inside in relation to their superview i.e. the contentView.
Basically, add the layout constraints to the customView views.
its quite simple.. iterate a loop by creating uiview along with adding those into the array and customize your particular view by getting them using array index.
Happy code ..
Here is a draft of what I would like to do :
I would like to have a main container, which will be used to trigger Menu from everywhere.
I try to do this thanks to view.addSubview and addChildViewController.
But the second view disables the first one. It simply goes over.
How could I do to keep both functionalities ?
UPDATE:
I found a way that works, but it seems dirty :
I just move down the secondView frame according to the menu height, here is the code :
var test = mainStoryboard.instantiateViewControllerWithIdentifier("TestViewController") as TestViewController
var test2 = mainStoryboard.instantiateViewControllerWithIdentifier("Test2ViewController") as Test2ViewController
//proposeOrChooseViewController.delegate = self
view.addSubview(test.view)
addChildViewController(test)
test2.view.frame = CGRectMake(0, 60, test2.view.frame.size.width, test2.view.frame.size.height)
view.addSubview(test2.view)
addChildViewController(test2)
Is the frame of second view controller's view set correctly? Visually you can test it by setting a background color of view.
Also you might want to explore the Container View in storyboard. Container View lets you add a View Controller as child of another view controller.
I'm just starting out with UI Automation for my iOS app and am already having trouble. I'm unable to attach screen shots so I'll do my best to describe my scenario.
I'm building for iOS 6.0 and am using a storyboard. The app launches to a screen with a navigation controller. The root view controller contains a main view that has 1 UIView subview that takes up the bottom 60% of the screen and a segmented control that sits above that subview. I was able to configure accessibility for the main view (label "mainview"). I am then able to locate this element in my test no problem. However, I am now having trouble finding the segmented controller. So I decided to log out the length of "elements()" and "segementedControls()" from my "mainview" element and the length of each array is 0. So somehow when the test is running my app it's saying there are no sub-elements on my main view.
Another thing to note is that I could not find any accessibility section in the identity inspector of the storyboard editor for the segmented control. However I temporarily added a button to my main view and configured that with an accessibility label, just to test if the elements() or buttons() calls would subsequently show an element for the main view when running my test, but these arrays were still returning as empty, even with the button.
Here's my script:
var target = UIATarget.localTarget();
var app = target.frontMostApp();
function selectListView() {
var testName = "selectListView";
UIALogger.logStart(testName);
var view = app.mainWindow().elements()["mainview"];
if (!view.isValid()) {
UIALogger.logFail("Could not locate main view");
}
UIALogger.logMessage("Number of elements for sub element: " + view.elements().length);
var segmentedControl = view.segmentedControls()[0];
if (!segmentedControl.isValid()) {
UIALogger.logFail("Could not locate segmented control on physician collection parent view");
}
var listButton = segmentedControl.buttons()[1];
if (!listButton.isValid()) {
UIALogger.logFail("Could not locate list button on segemented controller on physician collection parent view");
}
UIALogger.logMessage("Tapping List button on physician collection view's segmented control");
listButton.tap();
UIALogger.logPass(testName);
}
selectListView();
Any help greatly appreciated.
EDIT: I added this to my script to search the entire view hierarchy from the main window, set an accessibility label value for my segmented control in initWithCoder (since I don't seem able to set one in the storyboard editor for a segmented control, as I stated earlier) and still could not find the element - it's as though it's just not in the view hierarchy, though it's on the screen and functions just fine:
function traverse(root, name) {
if (root.name() == name) {
return root;
}
var elements = root.elements();
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var result = traverse(e, name);
if(result != null) {
return result;
}
}
return null;
}
function selectListView() {
var testName = "selectListView";
var segmentedControl = traverse(UIATarget.localTarget().frontMostApp().mainWindow(), "mysegementedcontrol");
if (segmentedControl == null) {
UIALogger.logMessage("Still could not find it");
}
....
}
EDIT: Added call to app.logElementTree() and still no segmented control in sight ("PhysicianCollectionParentView" is my "mainview" - you can see, no sub-elements there):
EDIT: Here are some screen shots. The first shows my "master" view controller. The next shows that in addition to the segmented control there is also a UIView subview. The 3rd shows the basic entry point for the app in my storyboard.
Here is the class extension for my "master" view controller here, showing the outlets for the segmented control and the other UIView subview:
#interface PhysicianCollectionMasterViewController ()
#property (strong, nonatomic) IBOutlet UISegmentedControl *viewSelectionControl;
#property (strong, nonatomic) IBOutlet UIView *physicianCollectionView;
#end
EDIT: Here's something very interesting - I decided to go with a brand new script created within instruments and take advantage of the record feature. When I clicked on my segmented control, here's the JavaScript it created to show me how it had accessed one of the buttons on my segmented control:
var target = UIATarget.localTarget();
target.frontMostApp().mainWindow().elements()["PhysicianCollectionParentView"].tapWithOptions({tapOffset:{x:0.45, y:0.04}});
So, I guess worst-case I could go with something like this, but it just makes no sense to me that UI Automation just does not think that the control exists. So strange. There must be something I'm missing but my setup is so basic I can't imagine what it could be.
When you set accessibilityLabel for an element and flag it isAccessibilityElement = YES; the subviews of that element are hidden. For automation, you should use accessibilityIdentifier instead of accessibilityLabel and set isAccessibilityElement = NO;
In your objective C code after physicianCollectionView is rendered, remove the label and accessibility flag and do this instead:
physicianCollectionView.accessibilityIdentifier = #"PhysicianCollectionParentView";
physicianCollectionView.isAccessibilityElement = NO;
For last elements in element tree, which do not have sub views, set isAccessibilityElement = YES;
If you haven't tried it already, you might try adding a delay at the beginning of your script:
UIATarget.localTarget().delay(3);
I think that it is possible that your app isn't done rendering/animating before the logElementTree() that you have posted above. We add delays at the beginning and between each application transition in our automated testing scripts to ensure that the screen has finished rendering.
EDIT: After messing around with your setup in a test app, I believe that your issue is that you are enabling Accessibility on the UIView that contains your segmented control. With accessibility disabled on the UIView, I am able to get the UISegmentedControl to show in the element tree, but once I enable it, the UIView then begins displaying as a UIAElement with no children. My suggestion is to disable accessibility for the containing UIView, and only use accessibility for base controls (like buttons, table view cells, or your segmented button control).
currently i am loading View that has some subviews
with the
NSBundle.MainBundle.LoadNib("NibName", this, null) function
but than i manually add the subviews of the loaded view with the
this.AddSubviews(this.subview1).
how can i load the view from the Nib so i wont need to manually add the subviews to the current view?
EDIT:
now i tried to get the result of the LoadNib function :
var res = NSBundle.MainBundle.LoadNib("ViewX",this,null);
var betterRes = Runtime.GetNSObject(res.ValueAt(0)) as Viewx;
now "this" has the properties of the sub elements initialized but his view is empty.
and "betterRes" has all the subviews in the view but all the properties of the sub elements are null.
just to clarify ... each sub element is button or label and has is own view.
If I understand correctly what do you want: load the view with outlet or action from nib file to main view, here is a try:
//Load the view
var res = NSBundle.MainBundle.LoadNib("ViewX",this,null);
// init your view with IntPtr
var betterRes = new Viewx(res.ValueAt(0));
//then use this view as main view
this.View = betterRes; //Or add subview: this.View.AddSubView(betterRes);
Now some properties in your view should work. Hope this help!