In Nativescript Angular, I can swipe from left edge to right to go back on iOS. Is there any way i can detect or capture this swipe back?
The reason for this is because I have a button on screen that execute "this.routerExtensions.back()" when tapped. I would like to identify whether a "Back action" is from this button or iOS swipe event.
Thanks
NativeScript has this property on pages.
https://docs.nativescript.org/api-reference/classes/ui_page.page.html#enableswipebacknavigation
If you set that to false, you should then be able to create your own gesture for going back. Even on native iOS, I am not sure you can directly tap into the back gesture without instead replacing it with your own.
This is how I was able to solve it for Android and iOS:
adjust constructor to manipulate the pullToLeft function from Android, and disable the default swipe back from iOS:
constructor(...) {
if (isAndroid) {
Application.android.on(
AndroidApplication.activityBackPressedEvent,
(data: AndroidActivityBackPressedEventData) => {
data.cancel = true;
...yourCustomFunction()
}
);
} else {
page.enableSwipeBackNavigation = false;
}
}
for Android it is already working - for iOS you need then to write a function you can use on a template (this is also how you can recognize the swipe action):
public onSwipe = (args: SwipeGestureEventData) => {
if (!isAndroid) {
if (args.direction === SwipeDirection.right) {
...yourCustomFunction()
}
}
};
disable the default PopGestureSerializer and add the swipe function on top of your template/element and it should work on iOS as well:
<StackLayout [interactivePopGestureRecognizer]="false" (swipe)='onSwipe($event)'>
...
</StackLayout>
Related
I just started with UI testing in Xcode 7 and hit this problem:
I need to enter text into a textfield and then click a button. Unfortunately this button is hidden behind the keyboard which appeared while entering text into the textfield. Xcode is trying to scroll to make it visible but my view isn't scrollable so it fails.
My current solution is this:
let textField = app.textFields["placeholder"]
textField.tap()
textField.typeText("my text")
app.childrenMatchingType(.Window).elementBoundByIndex(0).tap() // hide keyboard
app.buttons["hidden button"].tap()
I can do this because my ViewController is intercepting touches:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
view.endEditing(false)
super.touchesBegan(touches, withEvent: event)
}
I am not really happy about my solution, is there any other way how to hide the keyboard during UI testing?
If you have set up your text fields to resign FirstResponder (either via textField.resignFirstResponder() or self.view.endEditing(true)) in the textFieldShouldReturn() delegate method, then
textField.typeText("\n")
will do it.
Swift 5 helper function
func dismissKeyboardIfPresent() {
if app.keyboards.element(boundBy: 0).exists {
if UIDevice.current.userInterfaceIdiom == .pad {
app.keyboards.buttons["Hide keyboard"].tap()
} else {
app.toolbars.buttons["Done"].tap()
}
}
}
Based on a question to Joe's blog, I have an issue in which after a few runs on simulator the keyboards fails to hide using this piece of code:
XCUIApplication().keyboard.buttons["Hide keyboard"]
So, I changed it to: (thanks Joe)
XCUIApplication().keyboard.buttons["Hide keyboard"]
let firstKey = XCUIApplication().keys.elementBoundByIndex(0)
if firstKey.exists {
app.typeText("\n")
}
What I try to do here is detecting if the keyboard stills open after tap the hide button, if it is up, I type a "\n", which in my case closes the keyboard too.
This also happens to be tricky, because sometimes the simulator lost the focus of the keyboard typing and this might make the test fail, but in my experience the failure rate is lower than the other approaches I've taken.
I hope this can help.
I always use this to programmatically hide the keyboard in Swift UITesting:
XCUIApplication().keyboards.buttons["Hide keyboard"].tap()
XCUIApplication().toolbars.buttons["Done"].tap()
With Swift 4.2, you can accomplish this now with the following snippet:
let app = XCUIApplication()
if app.keys.element(boundBy: 0).exists {
app.typeText("\n")
}
The answer to your question lies not in your test code but in your app code. If a user cannot enter text using the on-screen software keyboard and then tap on the button, you should either make the test dismiss the keyboard (as a user would have to, in order to tap on the button) or make the view scrollable.
Just make sure that the keyboard is turned off in the simulator before running the tests.
Hardware->Keyboard->Connect Hardware Keyboard.
Then enter your text using the paste board
textField.tap()
UIPasteboard.generalPasteboard().string = "Some text"
textField.doubleTap()
app.menuItems["paste"].tap()
I prefer to search for multiple elements that are possibly visible to tap, or continue, or whatever you want to call it. And choose the right one.
class ElementTapHelper {
///Possible elements to search for.
var elements:[XCUIElement] = []
///Possible keyboard element.
var keyboardElement:XCUIElement?
init(elements:[XCUIElement], keyboardElement:XCUIElement? = nil) {
self.elements = elements
self.keyboardElement = keyboardElement
}
func tap() {
let keyboard = XCUIApplication().keyboards.firstMatch
if let key = keyboardElement, keyboard.exists {
let frame = keyboard.frame
if frame != CGRect.zero {
key.forceTap()
return
}
}
for el in elements {
if el.exists && el.isHittable {
el.forceTap()
return
}
}
}
}
extension XCUIElement {
///If the element isn't hittable, try and use coordinate instead.
func forceTap() {
if self.isHittable {
self.tap()
return
}
//if element isn't reporting hittable, grab it's coordinate and tap it.
coordinate(withNormalizedOffset: CGVector(dx:0, dy:0)).tap()
}
}
It works well for me. This is how I would usually use it:
let next1 = XCUIApplication().buttons["Next"]
let keyboardNext = XCUIApplication().keyboards.firstMatch.buttons["Next"]
ElementTapHelper(elements: [next1], keyboardElement: keyboardNext).tap()
Nice thing about this is you can provide multiple elements that could be tapped, and it searches for keyboard element first.
Another benefit of this is if you are testing on real devices the keyboard opens by default. So why not just press the keyboard button?
I only use this helper when there are multiple buttons that do the same thing, and some may be hidden etc.
If you are using IQKeyboardManager you can easily do this:
app.toolbars.buttons["Done"].tap()
This way you capture the "Done" button in the keyboard toolbar and hide the keyboard. It also works for different localizations.
I couldn't find an implementation of a double tap for Appium that was straightforward and allowed you to pass in the element locator strategy, so here goes:
public static void doubleTapElementBy(By by) {
WebElement el = getDriver().findElement(by);
MultiTouchAction multiTouch = new MultiTouchAction(getDriver());
TouchAction action0 = new TouchAction(getDriver()).tap(el).waitAction(50).tap(el);
try {
multiTouch.add(action0).perform();
} catch (WebDriverException e) {
logger.info("Unable to do second tap on element, probably because element requieres single tap on this Android version");
}
}
You can also try below approach using tap method in TouchAction class.
TouchAction taction = new TouchAction(driver);
taction.tap(tapOptions().withElement(ElementOption.element(YOUR_WebElement))
.withTapsCount(2)).perform();
You will need to add below static import as well:
import static io.appium.java_client.touch.TapOptions.tapOptions;
This is a workaround in pseudocode and possibly there's a more "official" way to do it, but it should do the work if no other solution is available:
Interpretmessages(){
switch(msg)
{
OnClick:
{ if (lastClicked - thisTime() < 0.2) //if it was clicked very recently
{doubleTapped()} //handle it as a double tap
else{lastClicked = thisTime()} //otherwise keep the time of the tap
} //end of OnClick
} //End of Message Handler
}//End of switch
}//End of messageHandler
If you have access to ready timer functions, you can set a function to be executed 0.2s after the click has gone off:
OnClick: if (!functionWaiting) // has the timer not been set?
{
enableTimer(); // set a function to go off in x time
clicks = 0; //we'll tell it that there's been one click in a couple of lines
} //set it for the first click
clicks++; //if it's already clicked, it'll become 2 (double tap) otherwise it's just one
So, the idea is that when you get a tap, you check if there's been another one recently (a. by checking the relative times, b. by checking if the function is still pending) and you handle it dependingly, only note that you will have to implement a timer so your function fires a bit later so you have time to get a second tap
The style draws upon the Win32's message handling, I'm pretty sure it works there, it should work for you too.
Double tap and hold -- Use below code:
new TouchAction(driver).press(112,567).release().perform().press(112,567).perform();
Double tap -- Use below code:
new TouchAction(driver).press(112,567).release().perform().press(112,567).release().perform();
I'm working with the Blackberry Touch Event and I need to process the TouchEvent.MOVE, TouchEvent.UP and TouchEvent.DOWN to move a carousel of images and the TouchEvent.CLICK to generate some specific action.
My problem is that the touchEvent() method is being called several times. How can I prevent this? Because the behaviour is getting all messed up.
For example: when I just want to capture the TouchEvent.CLICK event, the UP-DOWN-MOVE-CLICK are being triggered one after the next.
My code does the following:
protected boolean touchEvent(TouchEvent message) {
if (message.getEvent() == TouchEvent.CLICK) {
//CHANGE THE CONTENT OF A FIELD
return true;
} else if ((message.getEvent() == TouchEvent.MOVE)
|| (message.getEvent() == TouchEvent.UP)
|| (message.getEvent() == TouchEvent.DOWN)) {
//DELETE THE FIELD
//MOVE A CAROUSEL OF IMAGES
} else {
return false;
}
}
public void moverTouch(int dx) {
//ADD THE FIELD PREVIOUSLY DELETED
}
As you can see, when the CLICK event is captured, I need to change the content of a field, but when the MOVE or UP or DOWN event is captured, I need to delete that Field from his manager, do some working with a carousel of images, and then re-add the field previously deleted.
The carousel moving part works fine, but when I try to capture JUST the CLICK event, and the others are being triggered as well, but the moverTouch() function doesn't get triggered because there is no actual movement on the carousel of images, I end up with a deleted field that I need to update its content.
At a glance:
I think you need to differentiate touch gestures (I believe you are "listening" for something like a swipe gesture to scroll the gallery image) from "normal" touch events (specifically you need to process TouchEvent.CLICK to apply "some specific action" to the clicked image).
To decide whether the TouchEvent represents a gesture there is a TouchEvent.GESTURE constant. Then it is possible to know what exactly TouchGesture it represents by calling TouchEvent.getGesture().
Some sample code for touch gestures can be viewed here.
I have some problem with my back button. In my game I have two screens. One with title (menu) and second with the game. When I use once back button i pause game and back to title screen. When i use it again I need to kill the app process. How can i do that? Down below i show u how I use back button. I try use 2 gestures, but when I declared them nothing go right.
That's how i declared gesture in Initialize():
TouchPanel.EnabledGestures = GestureType.FreeDrag
First I declared
bool IsPlayingGame = true;
int endGame= 0;
Then in function Update:
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
{
if (!IsPlayingGame) this.Exit();
}
if (isTitleScreenShown)
{
UpdateGameScreen();
}
else if (isGameSceenShown)
{
UpdateTitleScreen();
// TODO: Add your update logic here
/* MY FUNCTIONS */
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
{
if (endGame == 5) base.Exit();
}
}
base.Update(gameTime);
In Visual studio, it works. I thought great, but on Nokia phone it doesn't. Why? Any help?
In the GamePage.xaml.cs there should be a method protected override void OnNavigatedFrom(NavigationEventArgs e)
This method is triggered when you touch the 'back' button on your device.
I want to add a button to a markitup set by which i can toggle preview, that is if i click on preview button i can see the preview, if i want to hide the preview - i can hide it by using that button. I've googleed for this but still no result. Any idea how to do that?
Nevermind, i've figured it out. I've added a button to hide it, code as below -
{name:'Hide Preview', call: function (markitUp){ miu.hidepreview()}, className:'hidepreview'}
Where hidepreview() as my custom function. I defined this function as below
miu = {
hidepreview : function(){
$('.markItUpPreviewFrame').remove();
}
}
It works for me.
You can do it by holding the Alt key and clicking on the Preview button - this will close the opened preview window.
It's in the else if statement of the preview() function:
} else if (altKey === true) {
if (iFrame) {
iFrame.remove();
} else {
previewWindow.close();
}
previewWindow = iFrame = false;
}