I just started using EarlGrey, and now I try to realize construction which will make screenshot if the test fails on Assert.
It is rather simple, e.g. when I check if the keyboard is visible, because GREYKeyboard.waitForKeyboardToAppear() returns Bool:
let image = GREYScreenshotUtil.takeScreenshot()
GREYScreenshotUtil.saveImage(asPNG: image, toFile: "\(testMethodName).png", inDirectory: path/to/necessary/directory)
GreyAssert(GREYKeyboard.waitForKeyboardToAppear(), "Keyboard is not active") //works correct
But if I want to check the same way if some element (for example, button or text field) I can't just use grey_sufficientlyVisible(), and most of other matchers return non-Bool.
I know that .assert(with: ) exists, but it's not so versatile.
So, is there any way to make this working?
Related
I'm new to the Xcode User Interface testing framework. I can successfully manipulate the screen elements, but cannot work out how to produce a meaningful assertion about what text is visible in a scrolling view.
The test I would like to write would go as follows: launch the app, type lots of text into a text view (enough that the first line scrolls out of view), assert that the first line of text is not visible, scroll the view back up to the top, then assert that the first line is now visible. Note that the purpose of this test is to ensure my app has wired things up correctly, not to test Apple's code.
XCUIApplication allows me to type into my NSTextView instance, and also allows me to scroll the associated NSScrollView. But how do I assert whether the first line of text is currently visible? The value attribute on XCUIElement provides the entire text content of the view, whether or not it is currently displayed.
The accessibilityRange(forLine:) and accessibilityString(for:) methods on NSTextView would be ideal, but I can't see how to access them as the UI test only has access to an XCUIElement, not the underlying NSTextView.
Have I missed something, or is there a better way to approach this?
If you set the accessibility identifier in the storyboard or in code for the text view you can get the text view via (assuming you gave it the id "textview1" and the window it's in has the default accessibility identifier of "Window"):
let textview1TextView = app.windows["Window"].textViews["textview1"]
but that won't actually get you what you need.
Instead, set the accessibility identifier of the scrollview and get that:
let scrollview = app.windows["Window"].scrollViews["scrollview1"]
Then use that to get the scrollbars (you should only have one in this case; you can use scrollbars.count to check.
let scrollbars = scrollview.scrollBars
print("scrollbars count: \(scrollbars.count)")
Then you can use the value attribute of the scrollbar to get it's value:
(you're converting a XCUIElemenTypeQueryProvider into an XCUIElement so you can get it's value):
let val = scrollbars.element.value
it will be 0 at the top and a floating point value when scrolled (one line of text in my test code showed a value of {{0.02409638554216868}}.
Documentation that will help you explore further:
XCUIElementTypeQueryProvider
XCUIElementAttributes
Note that you can put a breakpoint in the middle of your test, run it and then use the debugger console to examine things:
(lldb) po scrollbars.element.value
t = 749.66s Find the ScrollBar ▿ Optional<Any>
- some : 0
(lldb) po scrollbars.element.value
t = 758.17s Find the ScrollBar ▿ Optional<Any>
- some : 0.05421686746987952
and while in the debugger you can even interact with your app's window to scroll it manually (which is what I did between typing in those two po calls), or perhaps add text and so on.
OK OP now noted that they're interested in the specific text showing or not rather than the first line in view or not (which is what I previously answered above).
Here's a bit of a hack, but I think it'll work:
Use XCUICoordinate's click(forDuration:, thenDragTo:) method to select the first line of text (use the view frame to calculate coordinates) and then use the typeKey( modifierFlags:) to invoke the edit menu "copy" command. Then use NSPasteboard methods to get the pasteboard contents and check the text.
Here's a quick test I did to validate the approach (selecting the first line of text using XCUICoordinate as noted above is left as an exercise for the reader):
NSPasteboard.general.clearContents()
// stopped at break point on next line and I manually selected the text of the first line of text in my test app and then hit continue in the debugger
textview1TextView.typeKey("c", modifierFlags:.command)
print( NSPasteboard.general.pasteboardItems?.first?.string(forType: NSPasteboard.PasteboardType.string) ?? "none" );
-> "the text of the first line" was printed to the console.
Note that you can scroll the selection off screen so you have to not scroll after doing the select or you won't be getting the answer you want.
I'm trying to make a Shop enclosure when you touch a brick, it'll open the Shop Gui,
Now the main problem is that I do not know how to make the GUI open since using scripts whilst filtering enabled just won't cut it.
Does anyone have a solid explanation?
First of all, in order to execute any action when a brick is touched, you will need to use the .Touched attribute of your brick. Your brick has this attribute because it is a data type called a Part.
Second of all, I am not sure how you want the GUI to open, but the most basic way is to enable it using the .Active attribute of your GUI element. This will simply make it appear on screen. You GUI element has this attribute because it is a GuiObject, whether it be a Frame, TextButton, or whatever else.
The code will look something like this:
brick = path.to.part.here
gui = path.to.gui.here
function activateGui() --shorthand for "activateGui = function()"
gui.Enabled = true
end
brick.Touched:connect(activateGui)
Notice that .Enabled is a boolean (true or false). Also, notice that .Touched is a special object with a :connect(func) function. This is because .Touched is actually an Event. All Events have a special :connect(func) function that take an argument of another function func which is to be executed when the event occurs. In this case, we asked the brick's .Touched event to execute activateGui when it occurs.
Also, .Enabled is set to true by default, so in order for this method to work, make sure you set it to false in ROBLOX Studio by unchecking .Enabled in the Properties tab for the GUI element. Note that you do not have to do this for every single element of the GUI; if you set .Enabled to false on a certain element, all of its children will also automatically be hidden, so you only have to do it on the parent element.
Finally, you must do this in a Local Script. Because the GUI is unique for every single player, it is actually handled by each player's computer, not the ROBLOX server itself. Local Scripts are scripts that are specifically handled by a player's computer and not the server, so it is crucial that you do not try to do this with a regular Script, which is handled by the server.
For your information, the above code can be condensed to this if you would like:
brick = path.to.part.here
gui = path.to.gui.here
brick.Touched:connect(function()
gui.Enabled = true
end)
This is because you do not have to create a function, name it, and then give that name to .Touched; instead, you can just create it right on the spot.
I am new to UI test writing.
I wanted to know if it is possible to know what elements are/exists in view. I want to know how many and how they are called.
I tried something like this to loop through all elements, but it does not work.
for element in app.accessibilityElements! {
print(element)
}
You're looking for the debugDescription method of XCUIElement, in your case to get the entire hierarchy of the current visible window:
app.debugDescription
Quoting header comments:
Provides debugging information about the element. The data in the string will vary based on the
time at which it is captured, but it may include any of the following as well as additional data:
• Values for the elements attributes.
• The entire tree of descendants rooted at the element.
• The element's query.
This data should be used for debugging only - depending on any of the data as part of a test is unsupported.
You could always have a break point in the testcase and then make some print calls to the console.
po app.accessibilityElements
po app.accessibilityElements.elementBoundByIndex(0)
po app.buttons["Icon Menu Light"]
etc.
And see what comes back in the output view hierarchy to reference, or just a simple call to po app will print the hierarchy.
Once you know a particular view exists.. app.buttons["Icon Menu Light"].exists.. you can then try using something like this to confirm the button/element is visible within the current view by passing it to a helper function like:
func isElementInView(element: XCUIElement) -> Bool {
let window = XCUIApplication().windows.elementBoundByIndex(0)
return CGRectContainsRect(window.frame, element.frame) }
This will tell you whether the element is visible on the screen, because the element.exist call will return true, even if the element is off screen and hasnt been shown to the user (i.e. if something hides off screen and then transitions into the frame)
I am trying to pass in contentDescription into a custom step definition, with little success and I am not sure I can do it, there is very little help out there, so I am a bit lost.
so I have started calabash-android console
then start_test_server_in_background
then query("TextView") which returns a list of elements in the textView, in this list are contentDescription, each has a string value, e.g "thisIsValue"
now I have written a step in my feature file as:
Then I touch contentDescription "thisIsValue" text
the syntax of my custom step method is:
Then /^I touch contentDescription text (\d+)$/ do |text, contentDescription|
tap_when_element_exists("contentDescription contentDescription:#{arg1}")
I'm starting to think passing in contentDescription just isn't possible for multiple values of the same text on a form, using ID is not possible due to the way xamarin forms are generated in our instance, another option would be on index, however that is not really good moving forward.
thanks all.
Graeme
There are few possibly wrong details about your step definition.
The (\d+) regular expression indicates, that you are looking only for elements with digits in contentDescription.
You are passing into block one value (which is mentioned above digit-only value) and then expecting two values to be passed (text and contentDescription).
You should tap element of type TextView, ImageView, * etc., but you want to tap contentDescription element.
You want to tap element with contentDescription with value of arg1, but there is none arg1 inside your block.
Do not forget about apostrophes around contentDescription's value in your query.
So, your step definition possibly should look something like that:
Then /^I touch contentDescription text: (.*?)$/ do |arg1|
tap_when_element_exists("TextView contentDescription:'#{arg1}'")
end
#kjuri - your solution has now worked, it appears that in my windows environment set up it was looking at the incorrect step def, I cleared out the folder and started again - basically turned it on and off again!! thank you very very much for your patience on this, and your help.. greatly appreciated indeed. To summize this worked:
Then /^I touch the contentDescription "(.*?)" text$/ do |text|
tap_when_element_exists("RadioButton contentDescription:'#{text}'")
end
I am building a GUI in Netbeans - it is for a simple little application - a converter program. Basically, user types whatever it is they want to convert into a text field, selects the conversion from a number of radio buttons (say lbs to kg) and then clicks "Convert".
The thing is, I want the "Convert" button and the radio buttons to behave like this:
Radio buttons and "Convert" button are disabled when program loads.
Radio buttons and "Convert" button will become enabled if user types a number (and only a number) into the text field.
If used deletes what they have typed, everything will be disabled again until they type in another number.
I have managed to set the Radio buttons and "Convert" button up so they are disabled, by unchecking the "enabled" box in the properties for each component. I have also been able to use a simple if statement and the keyTyped event to enable/disable as follows:
private void txtUsrInputKeyTyped(java.awt.event.KeyEvent evt)
{
if (!txtUsrInput.getText().equals(""))
{
btnCalculate.setEnabled(true);
}
else
{
btnCalculate.setEnabled(false);
}
}
I want to extend my code so that if the user accidentally types a letter or symbol into the text field (don't ask me why they'd do that, when they know they must only type a number) then the program will either ignore what they typed, or display an error. The exception to this is, of course, typing a period (.) because they might want to indicate a decimal number.
Any thoughts on how I might do this? Hope what I wrote makes sense!
Your GUI
If you do not already know swing, then learn it. Knowing how all of the components work is always a huge advantage for any developer, and if anything goes wrong, you know exactly how to fix it (or at least have a much better chance). Take a look at this stuff, for some help on getting started.
Checking for a decimal
This looks like a job for regular expressions. What you can do is specify a number [0-9], and a period, \.. Then ensure that either a number or a decimal is typed a certain amount of times. The important thing to note here though, is that the user can only type one decimal, which winds up being:
([0-9]+(\.[0-9]+))
This will allow values like 9, 0.9, 1.9302. It will not allow values like .901. If you wish to allow that, simple swap the first + for a *.:
([0-9]*(\.[0-9]+))
Using it
Because a text box contains a string, you can compare it with your regex. Simply:
if(textBox1.getText().matches("([0-9]+(\.[0-9]+))")) {
// Run some code in here.
}
Well, if GUI is simple, DO NOT use GUI Builder, but code from scratch. Following harder path, you will learn about layouts, actionlisteners and so on ...