How to perform scroll in android native app with javascript - android

I am using appium and webdriverIO to automate android native app. I need to perform scroll down and i have used
ele.scrollIntoView(true) //this returns not yet implemented error
is there any other way to scroll down?

I don't used java script to scroll down. but I have already given a detail answer with different approach (by some text, element and screen size). Please have a look on it.
How to reach the end of a scroll bar in appium?
Hope it will help.

I have find a way to perform swipe down by calling JsonWireProtocol api directly from my code.
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
here is my code
module.exports.scrollAndroid = function (ele) {
console.log('driver.sessionID = ', driver.sessionId);
while (!$(ele).isDisplayed()) { . //$(ele)=$(//xpath or any other attribute)
this.scrollAPICall();
driver.pause(3000);
}
};
module.exports.scrollAPICall = function () {
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
console.log("result status >> ", this.status);
console.log("result text >> ", this.responseText);
};
url1 = `http://127.0.0.1:4723/wd/hub/session/${driver.sessionId}/touch/flick`;
xhttp.open('POST', url1);
console.log("URL >> ", url1)
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.setRequestHeader("charset", "UTF-8");
xhttp.send(JSON.stringify({
xspeed: 10,
yspeed: -100,
}));
if you want to scroll up then give yspeed as + value. ex:100

You can achieve a scroll down by performing a Screen Swipe Up action. Implementation of swipe class can be found from Boilerplate project. The Gestures.js class has all required functions. Here is the link to class
Please keep in mind that the swipe is performed based on the percentage. Once you implement this Gestures class you can then use it like below:
while(!element.isDisplayed()) {
Gestures.swipeUp(0.5)
}

Related

Manually trigger cut/copy/paste in android webview

I'm building a little browser app using android webview and I've been using window.getSelection() in javascript to get the nature of any text selected by the user and show a custom context menu based on the type of the selection i.e. whether it's a range, a carat, whether it's in a contenteditable etc.
This works fine unless the selection is in an iframe, then the browser security measures kick in and prevent me sniffing what has been selected using window.getSelection(). How can I workaround this?
Ideally I need a way to get better information about what was selected from the webview or if that's not possible I need a way to sniff whether the selection occurred in an iframe so I can disable my custom context menu logic and fallback to the default android context menu.
UPDATE/FURTHER CLARIFICATION 07/05/2019:
Seems I wasn't clear enough in my initial description...
My goal is to have a visually and functionally custom menu when selecting content in the webview that can cut/copy/paste as the standard context menu does in any part of the page/iframes etc. e.g.
I realised my original approach using javascript to detect the type of selection and to perform the cut/copy/paste was wrong because it will be blocked by cross origin security in iframes.
What I need is a native android/webview based approach. I've discovered that I can sniff the type of selection in the webview by looking at the items in mode.getMenu() on onActionModeStarted. This will allow me to show the correct buttons in my custom menu UI but I have been unable to manually trigger the same logic that gets called when cut/copy/paste is clicked. I thought I found the solution with webView.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CUT, null); but this doesn't work for some reason so I guess my question really is how can I manually trigger cut/copy/paste on the selected text from webview without using javascript? or any other approach that will allow me to have a custom selection menu with lots of options based on what was selected without hitting the browser security limitations?
Okay I figured out how roughly how to do this.
Step 1) In your activity, override onActionModeStarted and check the menu items available in the default context menu. This gives you a clue as to what the type of selection is and which buttons you will need to show in your custom menu. Also it gives you a reference to the item ID which you can use to later to trigger the action e.g.
systemSelectionMenu = mode.getMenu(); // keep a reference to the menu
MenuItem copyItem = systemSelectionMenu.getItem(0); // fetch any menu items you want
copyActionId = copyItem.getItemId(); // store reference to each item you want to manually trigger
Step 2) Instead of clearing the menu, use setVisible() to hide each menu item you want a custom button for e.g.
copyItem.setVisible(false);
Step 3) In your custom button onclick event you can trigger the copy action using:
myActivity.systemSelectionMenu.performIdentifierAction(myActivity.copyActionId, 0)
You can retrieve iframe's selection only if it has the same origin. Otherwise, you have no chances to track any iframe's events(clicks, touches, key presses, etc.).
const getSelectedText = (win, doc) => {
const isWindowSelectionAvailable = win && typeof win.getSelection != "undefined";
if (isWindowSelectionAvailable) {
return win.getSelection().toString();
}
const hasDocumentSelection = doc && typeof doc.selection != "undefined" && doc.selection.type == "Text";
if (hasDocumentSelection) {
return doc.selection.createRange().text;
}
return '';
}
const doIfTextSelected = (win, doc, cb) => () => {
const selectedText = getSelectedText(win, doc);
if (selectedText) {
cb(selectedText);
}
}
const setupSelectionListener = (win, doc, cb) => {
doc.onmouseup = doIfTextSelected(win, doc, cb);
doc.onkeyup = doIfTextSelected(win, doc, cb);
}
const getIframeWinAndDoc = (iframe) => {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const win = iframe.contentWindow || iframe.contentDocument.defaultView;
return { win, doc };
} catch (e) {
console.error(`${e}`);
return {};
}
}
const callback = console.log;
setupSelectionListener(window, document, callback);
document.querySelectorAll('iframe').forEach(iframe => {
const { win, doc } = getIframeWinAndDoc(iframe, console.log);
// Only for same origin iframes due to https://en.wikipedia.org/wiki/Same-origin_policy
if (win && doc) {
setupSelectionListener(win, doc, callback);
}
})
<h3>Select me</h3>
<div class="container">
<iframe src="https://teimurjan.github.io"></iframe>
</div>
This issue varying from browser to other if it works with internet explorer so it may fall with chrome
Try this
App.util.getSelectedText = function(frameId) {
var frame = Ext.getDom(frameId);
var frameWindow = frame.contentWindow;
var frameDocument = frameWindow.document;
if (frameDocument.getSelection) {
return frameDocument.getSelection();
}
else if (frameDocument.selection) {
return frameDocument.selection.createRange().text;
}
};
Hope it runs fine
Main problem is the window.getSelection() will return selection only for the main context/window. As iframe is the other window and other context, you should call getSelection() from iframe which is "current".

Why ion-infinite-scroll keeps calling on scrolling in Android?

I am using 'ion-infinite-scroll' in html to load more items from server for this i am using below code
<ion-infinite-scroll immediate-check="false" on-infinite="getListOfAreas()" distance="1%">
</ion-infinite-scroll>
Here, getListOfAreas() function is called when I scroll screen to bottom and it fetches data from server.This is getListOfAreas() function defined on controller
$scope.getListOfAreas = function (shoudlShowLoader) {
AreaBusiness.getAreasListing(shoudlShowLoader, function(serviceResponse) {
$scope.$broadcast('scroll.infiniteScrollComplete');
if (serviceResponse != null) {
var isSuccess = serviceResponse.Success;
if (isSuccess) {
}
}
}
}
On browser, i have debugged some how
$scope.$broadcast('scroll.infiniteScrollComplete');
above line of code keeps calling and spinner keep rotating.I don't know the reason. Am I missing anything?
$scope.$broadcast('scroll.infiniteScrollComplete'); only lets ionic know that the current page of data has been fetched and that it's safe to now fetch the next page. This does not indicate that all data has finished loading.
Looks like the official recommendation is to add an ng-if to the scroll delegate and remove it from dom once there is not more data to load.
Example:
<ion-infinite-scroll
ng-if="moreDataCanBeLoaded()"
icon="ion-loading-c"
on-infinite="loadMoreData()">
</ion-infinite-scroll>
Source: https://ionicframework.com/docs/api/directive/ionInfiniteScroll/
I have resolved the issue, after spending sometime. The issue which I found, I was not adding those elements into the list which were to be shown on UI. So, list was not being updated on controller that is why it kept calling.So I updated my code and added those fetched items from service to list associated to ui as mentioned in my below code
$scope.$broadcast('scroll.infiniteScrollComplete');
if (serviceResponse != null) {
var isSuccess = serviceResponse.Success;
if (isSuccess) {
if ($scope.areas != undefined && $scope.areas.length > 0) {
pushDataToPOIList(serviceResponse.PointOfInterestData);
} else {
$scope.areas = serviceResponse.PointOfInterestData;
}
pushDataToVehiclesList() method I added that is pushing new data into list to update list and on UI as well. This helped me to sort out the problem.

Hide the default browser address bar in android [duplicate]

Does anyone know how I can remove the address bar from the Android browser to better view my web app and make it look more like a native app?
You can do that with the next code
if(navigator.userAgent.match(/Android/i)){
window.scrollTo(0,1);
}
I hope it helps you!
Here's the NON-jQuery solution that instantly removes the address bar without scrolling. Also, it works when you rotate the browser's orientation.
function hideAddressBar(){
if(document.documentElement.scrollHeight<window.outerHeight/window.devicePixelRatio)
document.documentElement.style.height=(window.outerHeight/window.devicePixelRatio)+'px';
setTimeout(window.scrollTo(1,1),0);
}
window.addEventListener("load",function(){hideAddressBar();});
window.addEventListener("orientationchange",function(){hideAddressBar();});
It should work with the iPhone also, but I couldn't test this.
If you've loaded jQuery, you can see if the height of the content is greater than the viewport height. If not, then you can make it that height (or a little less). I ran the following code in WVGA800 mode in the Android emulator, and then ran it on my Samsung Galaxy Tab, and in both cases it hid the addressbar.
$(document).ready(function() {
if (navigator.userAgent.match(/Android/i)) {
window.scrollTo(0,0); // reset in case prev not scrolled
var nPageH = $(document).height();
var nViewH = window.outerHeight;
if (nViewH > nPageH) {
nViewH -= 250;
$('BODY').css('height',nViewH + 'px');
}
window.scrollTo(0,1);
}
});
Referring to Volomike's answer, I would suggest replacing the line
nViewH -= 250;
with
nViewH = nViewH / window.devicePixelRatio;
It works exactly as I check on a HTC Magic (PixelRatio = 1) and a Samsung Galaxy Tab 7" (PixelRatio = 1.5).
The one below works for me every time..
This site also has a few other suggestions, but this no-nonsense, no-worry one is available in a github:gist and answers your question (pasted here for convenience):
function hideAddressBar()
{
if(!window.location.hash)
{
if(document.height < window.outerHeight)
{
document.body.style.height = (window.outerHeight + 50) + 'px';
}
setTimeout( function(){ window.scrollTo(0, 1); }, 50 );
}
}
window.addEventListener("load", function(){ if(!window.pageYOffset){ hideAddressBar(); } } );
window.addEventListener("orientationchange", hideAddressBar );
As far as I can tell, the combination of extra height added to the page (which caused problems for you) and the scrollTo() statement make the address bar disappear.
From the same site the 'simplest' solution to hiding the address bar is using the scrollTo() method:
window.addEventListener("load", function() { window.scrollTo(0, 1); });
This will hide the address bar until the user scrolls.
This site places the same method inside a timeout function (the justification is not explained, but it claims the code doesn't work well without it):
// When ready...
window.addEventListener("load",function() {
// Set a timeout...
setTimeout(function(){
// Hide the address bar!
window.scrollTo(0, 1);
}, 0);
});
The problem with most of these is that the user can still scroll up and see the addressbar.
To make a permanent solution, you need to add this as well.
//WHENEVER the user scrolls
$(window).scroll(function(){
//if you reach the top
if ($(window).scrollTop() == 0)
//scroll back down
{window.scrollTo(1,1)}
})
this works on android (at least on stock gingerbread browser):
<body onload="document.body.style.height=(2*window.innerHeight-window.outerHeight)+'px';"></body>
further if you want to disable scrolling you can use
setInterval(function(){window.scrollTo(1,0)},50);
Here's an example that makes sure that the body has minimum height of the device screen height and also hides the scroll bar. It uses DOMSubtreeModified event, but makes the check only every 400ms, to avoid performance loss.
var page_size_check = null, q_body;
(q_body = $('#body')).bind('DOMSubtreeModified', function() {
if (page_size_check === null) {
return;
}
page_size_check = setTimeout(function() {
q_body.css('height', '');
if (q_body.height() < window.innerHeight) {
q_body.css('height', window.innerHeight + 'px');
}
if (!(window.pageYOffset > 1)) {
window.scrollTo(0, 1);
}
page_size_check = null;
}, 400);
});
Tested on Android and iPhone.
I hope it also useful
window.addEventListener("load", function()
{
if(!window.pageYOffset)
{
hideAddressBar();
}
window.addEventListener("orientationchange", hideAddressBar);
});
Finally I Try with this. Its worked for me..
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ebook);
//webview use to call own site
webview =(WebView)findViewById(R.id.webView1);
webview.setWebViewClient(new WebViewClient());
webview .getSettings().setJavaScriptEnabled(true);
webview .getSettings().setDomStorageEnabled(true);
webview.loadUrl("http://www.google.com");
}
and your entire main.xml(res/layout) look should like this:
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
don't go to add layouts.
I found that if you add the command to unload, he keeps down the page, ie the page that move!
Hope it works with you too!
window.addEventListener("load", function() { window.scrollTo(0, 1); });
window.addEventListener("unload", function() { window.scrollTo(0, 1); });
Using a 7-inch tablet with android, www.kupsoft.com visit my website and check how it behaves page, I use this command in my portal.

How to establish communication between webview and html page in worklight?

I'm working on making a browser as a hybrid app using worklight framework for Android. I implemented my address bar as an input element which received the user input and pass the arguments to the webview to load the page.
However, I cannot figure out how to do the reverse: whenever the user click on a link in webview, I want the address bar to change to the new location.
Are you implementing a native page that is opened? If so, take a look at ChildBrowser, that basically does the same thing. It has a TextView being used as an address bar. You may decide to use it, or get the bits and pieces you want out of it. Regardless, I would image what you want to do something like this. By overriding the onLoadResource in the WebViewClient, you should be able to grab the url and change your TextBox.
In response to the comment below: inside your environment's main js file in the wlEnvInit() function:
function wlEnvInit(){
wlCommonInit();
// Environment initialization code goes here
document.onclick=manageLinks;
}
Then in this function get the url and set the text of your input element:
function manageLinks(event) {
var link = event.target;
//go up the family tree until we find the A tag
while (link && link.tagName != 'A') {
link = link.parentNode;
}
if (link) {
var url = link.href;
console.log("url = " + url);
//You can decide if you want to separate external or
//internal links, depending on your application
var linkIsExternal = ((url.indexOf('http://') == 0) || (url.indexOf('https://') == 0));
if (linkIsExternal) {
myInput.setText(url);
return false;
}
}
return true;
}
Inside of your WebView, inside the plugin, intercept the URL like this:
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
//use this area to set your input. Depending on how you
//implemented your plugin, you may need to return this value
//back to your main activity
Toast.makeText(cordova.getActivity(), "Loading: " + url, Toast.LENGTH_LONG).show();
}
});
Have you try to get the url from the href of and assign to the input variable and do the get/post? I know that it is possible in SDK i figure it dont will be harder in a framework. You can store the hiperlinks in a array with a parser or something similar.
example pseudocode:
When_hiperlink_clicked: //could be like a listener (search about it)
url = hiperlink.getURL("myHiperlink");
myinput.setText(url);
execute_input_bar_action();
Is difficult to figure out without code or something more, sorry.

Order of buttons in navigator.notification.confirm in PhoneGap framework

I am using the following piece of code in my PhoneGap app.
function registrationCallBack(button){
if(button == 2) {
window.location.href = "login.html";
}
}
navigator.notification.confirm("Are you sure ?", registrationCallBack, "Confirmation", "Cancel, Ok");
The order of the buttons is coming properly as "Cancel" and "Ok" in iPhone.
But for Android the order of buttons are reversed. Its coming as "Ok" and then "Cancel".
As a result the button indices are getting changed in the callback method.
All suggestions are welcomed :)
Thanks,
Try using the following solutions:
function showConfirm(message, callback, buttonLabels, title){
//Set default values if not specified by the user.
buttonLabels = buttonLabels || 'OK,Cancel';
title = title || "default title";
//Use Cordova version of the confirm box if possible.
if(navigator.notification && navigator.notification.confirm){
var _callback = function(index){
if(callback){
callback(index == 1);
}
};
navigator.notification.confirm(
message, // message
_callback, // callback
title, // title
buttonLabels // buttonName
);
//Default to the usual JS confirm method.
}else{
invoke(callback, confirm(message));
}
}
And here is how you would use it:
var message = "Would you like to proceed?";
var title = "Important Question";
//The first element of this list is the label for positive
//confirmation i.e. Yes, OK, Proceed.
var buttonLabels = "Yes,No";
var callback = function(yes){
if(yes){
alert('Proceed');
}else{
alert('Do Not Proceed');
}
};
showConfirm(message, callback, buttonLabels, title);
This is not an Issue. What ever you are getting is native behavior of respective platform. In iOS, "Cancel" button will appear on left side and in Android you will get it on Right side. If the problem is only button index, then you can handle it in your code. But you cannot change the sequence of the buttons on screen.
I haven't tried this in iphone. But have tried this in android. I didn't face any such issues with phonegap 1.4.1. Hence i suggest you to upgrade your phonegap version. Issue you faced might be fixed in their new release. :)
I'm a bit late but I had the same problem, however what I did was change them around so "Cancel" goes where "Ok" is and "Ok" goes where "Cancel" is. That worked for me.

Categories

Resources