Friday, May 27, 2011

Praystation - An Android App

My first Android app is a wee little thing that draws an arrow pointing to Mecca from wherever you are. As such, it demonstrates some use of the Locator Api as well as some Canvas drawing and rotating

To begin with, the AndroidManifest.xml file contains simply a text area with a canvas beneath it. It also requires location permissions:

<manifest android:versioncode="1" android:versionname="1.0" 

package="com.praystation" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-sdk android:minsdkversion="8"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"> <application android:icon="@drawable/arrow" android:label="@string/app_name"> <activity android:label="@string/app_name" android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"> <category android:name="android.intent.category.LAUNCHER"> & </category> </action> </intent-filter> </activity> </application></uses-permission></uses-permission></uses-sdk></manifest>

I have an 'arrow.png' file in res/drawable for the app logo as well as for the arrow used in this app itself.

The MainActivity finds the best LocationManager and gets your location (assuming you have gps enabled o your device, of course). It also takes in the fixed coordinates of Mecca and calculates the distance and bearing from where you are to where it is:

package com.praystation;

import android.app.Activity;
import java.util.List;

import android.graphics.Canvas;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity  implements LocationListener {
    private static final String TAG = "LocationDemo";
    private static final String[] S = { "Out of Service",
            "Temporarily Unavailable", "Available" };
    
    private TextView output;
    private LocationManager locationManager;
    private Location mMarkedLocation;
    private String bestProvider;
    
    public static float sBearing = 0.0f;
    public static float sDistance = 0.0f;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Get the output UI
        output = (TextView) findViewById(R.id.output);

        // Get the location manager
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        Criteria criteria = new Criteria();
        bestProvider = locationManager.getBestProvider(criteria, false);

        Location location = locationManager.getLastKnownLocation(bestProvider);
        printLocation(location);
        
        mMarkedLocation = new Location("Mecca");
        mMarkedLocation.setLatitude(21.4266667);
        mMarkedLocation.setLongitude(39.8261111);
        
        sDistance = location.distanceTo(mMarkedLocation) / 1000;
        output.append(String.format("\n\ndistance to Mecca: %.2f kilometers\n", sDistance));
        
        sBearing = location.bearingTo(mMarkedLocation);
        output.append(String.format("\n\nbearing to Mecca: %.2f degrees\n", sBearing));
    }

    /** Register for the updates when Activity is in foreground */
    @Override
    protected void onResume() {
        super.onResume();
        locationManager.requestLocationUpdates(bestProvider, 20000L, 1.0f, this);
    }

    /** Stop the updates when Activity is paused */
    @Override
    protected void onPause() {
        super.onPause();
        locationManager.removeUpdates(this);
    }

    public void onLocationChanged(Location location) {
        printLocation(location);
    }

    public void onProviderDisabled(String provider) {
        // let okProvider be bestProvider
        // re-register for updates
//        output.append("\n\nProvider Disabled: " + provider);
    }

    public void onProviderEnabled(String provider) {
        // is provider better than bestProvider?
        // is yes, bestProvider = provider
//        output.append("\n\nProvider Enabled: " + provider);
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
//        output.append("\n\nProvider Status Changed: " + provider + ", Status="
//                + S[status] + ", Extras=" + extras);
    }

    private void printProvider(String provider) {
        LocationProvider info = locationManager.getProvider(provider);
//        output.append(info.toString() + "\n\n");
    }

    private void printLocation(Location location) {
//        if (location == null)
//            output.append("\nLocation[unknown]\n\n");
//        else
//            output.append("\n\n" + location.toString());
    }
    
}

The Panel class does the actual drawing of the canvas, taking the arrow image and rotating it in the direction of the static calculated bearing:

package com.praystation;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Panel extends SurfaceView implements SurfaceHolder.Callback{
    private CanvasThread canvasthread;
    
    public Panel(Context context, AttributeSet attrs) {
        super(context, attrs); 
        // TODO Auto-generated constructor stub
        getHolder().addCallback(this);
        canvasthread = new CanvasThread(getHolder(), this);
        setFocusable(true);
    }

     public Panel(Context context) {
           super(context);
            getHolder().addCallback(this);
            canvasthread = new CanvasThread(getHolder(), this);
            setFocusable(true);
        }

    @Override
    public void onDraw(Canvas canvas) {
        Log.d("ondraw", "lefutott");
        Paint paint = new Paint();
        int isRusty = 1;

        Bitmap arrow = BitmapFactory.decodeResource(getResources(),
                R.drawable.arrow2);
        canvas.drawColor(Color.BLACK);
        
        Matrix rotateMatrix = new Matrix();
        rotateMatrix.setRotate(MainActivity.sBearing, canvas.getWidth()/2, canvas.getHeight()/2);
        canvas.drawBitmap(arrow, rotateMatrix, null);
    }
    

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
        
    }
    
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        canvasthread.setRunning(true);
        canvasthread.start();    
    }
    
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        boolean retry = true;
        canvasthread.setRunning(false);
        while (retry) {
            try {
                canvasthread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }

    }
}

Finally, we have a CanvasThread class

package com.praystation;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class CanvasThread extends Thread {
    private SurfaceHolder _surfaceHolder;
    private Panel _panel;
    private boolean _run = false;

    public CanvasThread(SurfaceHolder surfaceHolder, Panel panel) {
        _surfaceHolder = surfaceHolder;
        _panel = panel;
    }

    public void setRunning(boolean run) {
        _run = run;
    }

    @Override
    public void run() {
        Canvas c;
        while (_run) {
            c = null;
            try {
                c = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    _panel.onDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    _surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }
}

Saturday, May 21, 2011

Using Grinder: some notes

Grinder is a free multi-purpose open source load testing tool. It can be used to drive Java/Jython tests as well as Web Browser tests, functional as well as load. Install Jython as well as Grinder.



There is also en Eclipse plugin to help run/debug Grinder scripts - called GrinderStone, you can find info about it here



To record and playback a Browser Test:



1) Configure your Browser to use a Proxy (e.g. localhost 8001 is the default - set this for http and https)

2) Set your classpath to include grinder/lib/grinder.jar and jython's jython.jar (not grinder's jython.jar !!!)

3) Start Grinder in Proxy Mode: java net.grinder.TCPProxy -console -http > grinder.py

launch with -localPort XXXX to change the Proxy port

4) Start your Browser and Do Your Actions

5) Press "Stop" on the TCPProxy console and the generated script will be written to grinder.py.



grinder.py is in python and can be edited to your heart's content (replace hard-coded users with variables read in from a file, for example)



To replay the file, create a grinder.properties text file with at least this setting:

grinder.script grinder.py

and run it

java net.grinder.Grinder grinder.properties



log files will be output in the current working directory, text and data (csv suitable for loading, parsing and charting in a spreadsheet)



other info here for proxy usage: http://grinder.sourceforge.net/g3/tcpproxy.html



for multi-threaaded, define more threads per process in your grinder.properties file:

grinder.processes 1

grinder.threads 100



if you customize your script to use python modules, you will need to set the python.home and python.path environment variables and pass them into your command-line arguments

java -Dpython.home=/Users/YOU/jython2.5.2 -Dpython.path=/Users/YOU/jython2.5.2/lib net.grinder.Grinder grinder.properties

and in grinder.properties:

grinder.jvm.arguments = -Dpython.home=/Users/YOU/jython2.5.2 -Dpython.path=/Users/YOU/jython2.5.2/lib





*Some lessons learned the hard way*:



disable cookies (that is to say, don't let ourselves get in the way of the record/replay process as far as cookies are concerned. leave your cookies alone)

connectionDefaults.setUseCookies(0)



avoid complex Wicket GETS

everything gets recorded but not everything needs to be replayed. for example, something like this:

self.token_wicketinterface = \

':2:ARightColumn:AudioAppsPanel:rows:1:cols:2:

cannot be relied on.

if you have a page method like that, simply "return ''" at the beginning of the method to avoid wicket/irresolution hell



definitely use the TCPProxy (formerly TCPSniffer) to watch what the hell is going on

java net.grinder.TCPProxy

and if you do so while replaying, make sure your replay test is using the proxy by uncommenting this line

connectionDefaults.setProxyServer("localhost", 8001)



*really gnarly grinder stuff*

say you have recorded a script and you want to add stuff to it from another recorded script.

yikes!!



several steps are involved because numbers will collide and it's all numbers in grinder (requestXXXX, pageXXX, TestXXXX)

1. in the new script, find all the 'request' methods of the areas you want to add, and give them real names



e.g. change

request1700 = HTTPRequest(url=url1, headers=headers4)

request1700 = Test(1700, 'GET redButton_White_150.png').wrap(1700)

request1701 = HTTPRequest(url=url1, headers=headers4)

request1701 = Test(1701, 'GET collapseButton.png').wrap(request1701)



to:

requestAolExtra1700 = HTTPRequest(url=url1, headers=headers4)

requestAolExtra1700 = Test(1700, 'GET redButton_White_150.png').wrap(requestAolExtra1700)

requestAolExtra1701 = HTTPRequest(url=url1, headers=headers4)

requestAolExtra1701 = Test(1701, 'GET collapseButton.png').wrap(requestAolExtra1701)



change the pageXXXX method to match (request170x will be page17, for example)

change:

def page17(self):

to:

def pageAolExtra17(self):



how change the instrumented 'Test' as well

change:

instrumentMethod(Test(1700, 'Page 17'), 'page17')

to:

instrumentMethod(Test(1700, 'Do Aol Extra'), 'pageAolExtra17')



now, copy those changed lines and methods (request lines, former 'page' methods, and InstrumentMethod lines) into the original file: BUT WAIT!! The Test number may well collide with some existing one, so change it too, for example by adding '1' or '2' (etc) in front of the number, e.g:

instrumentMethod(Test(11700, 'Go To Extras Tab'), 'pageAolExtra17')



In the __call__ method, pageXXX methods are called. Here is where you can call your newly renamed page methods as well. The pageXXXX methods call the requestXXXX methods, which use the Instrumented Test methods to make the actual call.



Also watch out for headers referenced in the new request methods - make sure you merge the right ones properly into the original file.

Ad-Hoc Selenium Development using Ruby and IRB: some notes

There are a number of ways to go about developing Selenium tests. You can use the Selenium IDE Firefox plugin and record browser actions, but often the xpaths the IDE selects are not necessarily the ones you want to use - for example, they might capture wicket id's that are dynamically created and may not even work on immediate playback. With any luck, your web page developers will use custom, unique id's for their html widgets, and you can find these using Google Chrome inspection (right-click on a widget, and select 'Inspect Element') or the Firebug plugin to Firefox which does the same thing. Even so, xpaths can be tricky to get exactly right, which is why I often do ad-hoc Selenium development using Ruby and IRB (the interactive ruby environment). With this approach, you can do trial and error and get immediate feedback while developing your Selenium test scripts.



There are of course a few things you need to get going: Ruby, for one, Java, and Selenium. You can get the selenium-server.jar here, and for Ruby, you will need the selenium client gem:

sudo gem install selenium-client



You will also want to bookmark the Ruby SeleniumRC docs



In a terminal window, you can start the default selenium server like this:

java -jar selenium-server.jar

(you can now use selenium-server-standalone-2.1.0.jar for Firefox 5 support)

but when using Firefox it can be helpful to use a Firefox Profile that includes Firebug - this allows you to explore xpaths while doing your ad-hoc selenium explorations.



Create a separate Firefox profile

1 - (on Mac) /Applications/Firefox.app/Contents/MacOS/firefox-bin -ProfileManager

2 - Create a new profile, name it an save it to a new folder (e.g. /Users/YOU/firefox)

3 - Start Firefox using the new profile, and install the firebug Plugin

4 - Edit the file prefs.js within the new profile's folder

comment out this line: user_pref("browser.startup.page", 0);

make sure this value is 'true': user_pref("extensions.firebug.console.enableSites", true);

5 - Launch the selenium server with option to use the new profile

java -jar selenium-server.jar -firefoxProfileTemplate "/Users/YOU/firefox"



Now, in a different terminal window, start IRB and launch a Selenium client (this example uses the a bogus page and assumes a signed-up account, which can easily be done manually from the same page)



> irb



---------



# create and launch the selenium client (naming it '@browser')

require 'selenium/client'

@browser = Selenium::Client::Driver.new \

:host => "localhost",

:port => 4444,

:browser => "*chrome",

:url => "http:/www.bogus.com",

:timeout_in_second => 60



@browser.start_new_browser_session

# goto nowhere

url = "http://www.bogus.com/nowhere"

@browser.open url

# login

@browser.type("//form[@id='signInForm']//input[@name='email']", "you@email.com")

@browser.type("//form[@id='signInForm']//input[@name='password']", "password")

@browser.click("//form[@id='signInForm']//a")



# once signed in, see how many tutorial links there are

@browser.get_xpath_count("//div[@id='videoCategories']//td")



--------



change the url string to any website you like, and once there, select any widget, right-click on it and select 'Inspect Element' to bring up the Firebug console and start experimenting with the xpaths.



Some of the more common Selenium Client commands:

@browser.click(xpath_to_clickable)

@browser.type(xpath_to_input_form)

@browser.is_text_present(some_string)

@browser.go_back



The Selenium client commands come in different languages, and are essentially the same in all of them. The main difference between methods in Ruby and Java is that the Java methods use camelCase (isTextPresent instead of is_text_present) - this makes it pretty easy to translate your ad-hoc Ruby experiments into Java test cases

Wednesday, May 11, 2011

Using PushToTest: some notes

This tool can be used to record browser activity using Selenium IDE, drive the recorded script with a csv file (users,passwords, etc ...) and run the same test as functional and load.

download from here

Steps:

Run PushToTest
Select Tools > Designer menu item
Select Script Type: Selenium from TestObject Window
Click on Record - enter a URL, select Firefox browser, and click OK
Perform your Browser actions
Click on 'End Record' when you're done

To drive from a csv file, select the Data Icon in the middle of the TestObject Window (looks like a book, to the right of the light bulb icon)
Select the 'Get Next Row' radio button
Select a .csv file, where the first line contains variable names:

EMAIL,PASSWORD
you@email.com,passwd9

The CSV file, when loaded, is presented in the Data tab in the bottom of the TestObjectWindow
Click and Drag a field from the CSV (in the Data tab) to a Value field in the Action window above - the value already there will be replaced by "$EMAIL" (for example)

Click the Play Icon to replay the recording and make sure it works

Select File > Save to save your recording (will be saved as a .ds file)
To Convert this functional test into a Load Test:

From PushToTest Main Window, select File > New Load Test
this brings up another Window - the PushToTest TestMaker Editor

Select the Use Cases tab
for 'Resource', Browse to select the .ds file you just saved
for 'Test Type', select 'Designer Script'
for 'Test Name', name your test
for 'Command Language', 'sahi'
for 'Browser', 'SahiHtmlUnit'
for 'Instance', you can leave this blank

Click on the 'Add DPL' link
for 'Data Source', browse for your .csv file
for 'DPL Type', select 'Hash DPL' (the default)
for 'DPL Name', give it a name or leave as untitled
for 'Action' select 'Get Next Row of Data'

Click on the 'General' Tab
Add Virtual Users Levels, e.g.
begin with one level for 1 user for 1 minute
add another level with 2 users for 1 minute
add another level with 4 users for 1 minute
etc ...

Click on the Save icon in the Left pane - (will be saved as a .scenario file)

Click on the Play icon in the left pane (to the right of the Save icon) to run the test

When the test is done, another Window pops up with all sorts of graphs and charts
Re-Open and explore other scenario options

Open the Scenario from the Main PushToTest Window through the Tools > Editor menu item