Skip to main content

Updating My Favorite Script

Since the last time I updated my blog, I've changed jobs and titles. While I still technically work in my new company's QA organization, my work is devoted more to app delivery than app testing. This doesn't mean I am not a tester anymore but that it consumes less of my time. It's no surprise therefore that I'm only NOW getting around to posting an update to my favorite script which I first shared 3 years ago here in this post.

3 years later and at least 3 years lazier, the ways I've modified the script are all designed to simplify it, make it more generic, and have it do more of the work for you. Instead of supplying a lot of configuration, it only concerns itself with the URL to the last successful build artifact of your build job. In this regard, I highly recommend NOT configuring your builds to name the files with dynamic data such as date, build number, git hash, etc. The filename is not something I'd consider a trustworthy source of build trivia even though it may be tempting to use it in that way. Besides, with a static link because the filename is consistent, you can do really awesome things more easily.

As it is basically an evolution of the script I've already written about, here's the list of changes:
  • Removed config file usage. Now the script only has a single dependency which you can edit in one line at the top of the file: APK_URL
  • Automatically handles multiple APKs in the directory (this is in case you want to use copies of the script for multiple APKs or just put the script in a generic working directory
  • Added pre-formatted JIRA template text file output for entering a new bug
  • Added pre-formatted JIRA template text file output for commenting on a bug you've worked on
  • Removed "clear" feature since many apps use the AccountManager API so an uninstall/reinstall is better most of the time
  • Automatically parses APK for launch activity to populate the launch command
  • Automatically handle case where no APK is available
  • Automatically handle case where no devices are available
The intended usage of the script is MOSTLY the same but in this case, you can better use it in situations where you may not have a TextExpander license. The pre-formatted JIRA comments are now baked into the script instead of into snippets in TextExpander, less flexible but faster to get new people started. Typical usage is when you're running a test pass or working on bugfix validations, you'd update the hardcoded APK_URL variable in the beginning of the file and run the script as new builds are available to test with. 

That's it. Sorry it's been over a year since my last posting. Life has been very busy. I've got a 7-month-old baby boy now, bringing the brood up to 3 delightful chaos machines. As I mentioned, I changed jobs. But I am definitely planning on updating more frequently. Expect the topics to shift slightly as my career has shifted slightly. Thanks for your time and see the GitHub gist below for the full details of the updated script.

#!/bin/sh
APK_URL="" ###### UPDATE THIS WITH THE URL TO THE ARCHIVED LAST SUCCESSFUL BUILD ARTIFACT ON JENKINS FOR EXAMPLE
FILEPATH=""
## MAKE SURE ADB IS RUNNING ##
adb version
## DEFAULTS ##
LAUNCH_ENABLED="false"
appVersionName=""
appPackageName=""
appVersionCode=""
appLaunchActivity=""
## USAGE ##
usage(){
echo
echo
echo
echo "#################### USAGE ####################"
echo
echo " -i: Install. This uninstalls the current app and carries out an install based on the supplied"
echo " parameters. Valid args include 'new', which will download the latest build, 'saved' which"
echo " will install the current locally saved build, and 'upgrade' which will install over the top of"
echo " the current build using install -r."
echo
echo " -l: Launch app. This launches the app with its first listed launchable-activity"
echo
echo " -h: Help. Displays script usage."
echo
echo "#################### /USAGE ####################"
echo
echo
echo
}
## DEVICEINFO ##
getConnectedDeviceInfo(){
rm -f actiontmpfile.txt
rm -f deviceInfoFile.txt
adb devices | grep device | grep -v attached | cut -f 1 > actiontmpfile.txt
if [ -s actiontmpfile.txt ]; then
while read deviceId;
do
DEVICE_OS_VERSION=`adb -s $deviceId shell getprop ro.build.version.release`
DEVICE_OS_SDK=`adb -s $deviceId shell getprop ro.build.version.sdk`
DEVICE_CARRIER=`adb -s $deviceId shell getprop ro.carrier`
DEVICE_BRAND=`adb -s $deviceId shell getprop ro.product.brand`
DEVICE_MODEL=`adb -s $deviceId shell getprop ro.product.model`
DEVICE_SIM_STATE=`adb -s $deviceId shell getprop gsm.sim.state`
DEVICE_SCREEN_DENSITY=`adb -s $deviceId shell getprop ro.sf.lcd_density`
DEVICE_SCREEN_RES=`adb -s $deviceId shell dumpsys window policy | grep mUnrestrictedScreen | cut -d' ' -f6`
echo "## device - $deviceId ###################" >> deviceInfoFile.txt
echo "Device OS:" >> deviceInfoFile.txt
echo " release version: $DEVICE_OS_VERSION" >> deviceInfoFile.txt
echo " SDK: $DEVICE_OS_SDK" >> deviceInfoFile.txt
echo "Device Build:" >> deviceInfoFile.txt
echo " brand: $DEVICE_BRAND" >> deviceInfoFile.txt
echo " model: $DEVICE_MODEL" >> deviceInfoFile.txt
echo " carrier: $DEVICE_CARRIER" >> deviceInfoFile.txt
echo " GSM SIM state: $DEVICE_SIM_STATE" >> deviceInfoFile.txt
echo "Device Screen:" >> deviceInfoFile.txt
echo " screen density: $DEVICE_SCREEN_DENSITY" >> deviceInfoFile.txt
echo " screen resolution: $DEVICE_SCREEN_RES" >> deviceInfoFile.txt
echo "" >> deviceInfoFile.txt
done < actiontmpfile.txt
else
echo ""
echo "NO attached devices found. Exiting."
echo ""
rm -f actiontmpfile.txt
exit 1
fi
rm actiontmpfile.txt
}
## FILEPATH ##
# looks for APKs in directory.
# if there are none, it offers y/n for downloading a new one.
# if there is one, it outputs the value of that relative filepath
# if there are more than one, it offers to clear them and download/install a new one, or install one from a list by numerical index
getFilePath(){
rm -f ./apklist.txt
ls *.apk > apklist.txt
apkArray=( $( cat ./apklist.txt ) )
arraySize=${#apkArray[@]}
if [ "$arraySize" -lt "1" ]; then
echo ""
echo "Sorry, it looks like you didn't download an APK yet, would you like me to? y/n"
read downloadRequest
case $downloadRequest in
y)
echo ""
echo "You entered 'y' for Yes. Okay, downloading a new one now"
download
FILEPATH=$( ls *.apk )
echo "$FILEPATH"
;;
n)
echo ""
echo "You entered 'n' for No. Well come back when you figure out what you want to do today."
exit 0
;;
*)
echo ""
echo "Seems like you misread. Let's see those instructions again."
usage
exit 0
;;
esac
elif [ "$arraySize" -gt "1" ]; then
echo ""
echo ""
echo "There are $arraySize APKs in this folder."
echo ""
echo "Type 'list' to choose from these or 'new' to delete them and download a new one."
read handleMultiples
case $handleMultiples in
new)
echo ""
echo "You entered 'new'. Let's clear the slate and grab a fresh APK."
rm -f ./*.apk
download
FILEPATH=$( ls *.apk )
;;
list)
echo ""
echo "You entered 'list'. Let's pick from among your existing files."
arrayIndex="0"
echo "### These are your choices. Enter the index for the desired APK. ###"
for i in "${apkArray[@]}"
do
versionName=$( getVersionName "$i" )
versionCode=$( getVersionCode "$i" )
echo " [$arrayIndex] - $i - versionName: $versionName - versionCode: $versionCode"
arrayIndex=$(( arrayIndex+=1 ))
done
read inputIndex
if [ "$inputIndex" -gt $(( arraySize-1 )) ]; then
echo ""
echo "Your selection of index '$inputIndex' exceeded the bounds of the array size $arraySize."
exit 0
elif [ "$inputIndex" -lt "$arraySize" ]; then
FILEPATH=${apkArray[$inputIndex]}
echo ""
echo "You selected $FILEPATH at index $inputIndex"
else
echo ""
echo "your input of '$inputIndex' was invalid."
usage
exit 0
fi
;;
*)
echo ""
echo "Seems like you misread. Let's see those instructions again."
usage
exit 0
;;
esac
elif [ "$arraySize" -eq "1" ]; then
FILEPATH=${apkArray[0]}
else
echo ""
echo "Something went horribly wrong with getFilePath() in this script."
exit 1
fi
}
## VERSIONNAME ##
getVersionName(){ # takes $FILEPATH as a parameter and gets versionName by dumping the apk badging
appVersionName=`aapt dump badging $1 | grep package | awk '{print $4}' | sed s/versionName=//g | sed s/\'//g`
}
## PACKAGENAME ##
getPackageName(){ # takes $FILEPATH as a parameter and gets packageName by dumping the apk badging
appPackageName=`aapt dump badging $1 | grep package | awk '{print $2}' | sed s/name=//g | sed s/\'//g`
}
## VERSIONCODE ##
getVersionCode(){ # takes $FILEPATH as a parameter and gets versionCode by dumping the apk badging
appVersionCode=`aapt dump badging $1 | grep package | awk '{print $3}' | sed s/versionCode=//g | sed s/\'//g`
}
## LAUNCHABLEACTIVITY ##
getLaunchActivity(){ # takes $FILEPATH as a parameter and gets launchable-activity by dumping the apk badging
appLaunchActivity=`aapt dump badging $1 | grep launchable-activity | awk '{print $2}' | sed s/name=//g | sed s/\'//g`
}
## INSTALL ##
installApp(){ # where the first parameter is the $FILEPATH and the second is the $deviceId. Uninstall always happens first.
echo "Uninstalling $appPackageName from $2"
adb -s $2 uninstall $appPackageName
echo "Installing $1 on $2"
adb -s $2 install $1
}
## UPGRADE ##
upgradeApp(){ # where the first parameter is the $FILEPATH and the second is the $deviceId.
echo "upgradeApp called with params $1 and $2..."
adb -s $2 install -r $i
}
## DOWNLOAD ##
download(){
#### Using --insecure option because the old Jenkins instance which is currently handling builds has a bad SSL cert ####
echo "## Downloading using $APK_URL ##"
curl -O --insecure $APK_URL
}
## LAUNCH ##
launch(){
LAUNCH_ENABLED=$1
rm -f launchConfig.txt
if [[ "$LAUNCH_ENABLED" = "true" ]] ; then
LAUNCH_CONFIG="shell am start -n $appPackageName/$appLaunchActivity -f 0x14000000"
echo $LAUNCH_CONFIG > launchConfig.txt
adb devices | grep device | grep -v attached | cut -f 1 > launchtmpfile
while read deviceId;
do
echo "My launch config is: $LAUNCH_CONFIG"
adb -s $deviceId $LAUNCH_CONFIG
done < launchtmpfile
rm launchtmpfile
fi
}
## ACTION ##
installAction(){
#I've separated these checks in case I want to do anything differently with upgrade paths in the future
if [[ "$1" = "install_new" ]] ; then
echo "Clearing all local APKs and downloading a new one"
rm -f ./*.apk
download
elif [[ "$1" = "upgrade" ]] ; then
echo "Clearing all local APKs and downloading a new one"
rm -f ./*.apk
download
else # call getVersionName directly since we're not sure whether the file has been updated manually
getFilePath
getVersionName $FILEPATH
fi
#identify app to install
getFilePath
#set app variables
getVersionName $FILEPATH
getVersionCode $FILEPATH
getPackageName $FILEPATH
getLaunchActivity $FILEPATH
adb devices | grep device | grep -v attached | cut -f 1 > actiontmpfile
while read deviceId;
do
case $1 in
install_new)
echo "installAction $1"
installApp $FILEPATH $deviceId
;;
reinstall) #identical to install_new for now because download logic is handled elsewhere
echo "installAction $1"
installApp $FILEPATH $deviceId
;;
upgrade) #for cases where you wish to upgrade the app without nuking app data
echo "installAction $1"
upgradeApp $FILEPATH $deviceId
;;
esac
done < actiontmpfile
rm actiontmpfile
}
########################################################################
########################## MAIN FUNCTIONALITY ##########################
# Start by gathering information about the connected devices
getConnectedDeviceInfo
# Default behavior - If no arguments are provided the script will uninstall and reinstall
# the app at the hardcoded $FILEPATH values without launching it afterwards
if (( $# < 1 )) ; then
installAction reinstall
fi
# Set option index to 1
OPTIND=1
while getopts "hli:" VALUE "$@" ; do
if [ "$VALUE" = "h" ] ; then
usage
exit 0
fi
if [ "$VALUE" = "l" ] ; then
LAUNCH_ENABLED="true"
fi
if [ "$VALUE" = "i" ] ; then
case $OPTARG in
new)
echo "user wants to install the newest build"
ACTION="install_new"
;;
saved)
echo "user wants to reinstall without a download"
ACTION="reinstall"
;;
upgrade)
echo "user wants to upgrade the current installation"
ACTION="upgrade"
;;
*)
echo "invalid -i parameter"
usage
exit 1
;;
esac
fi
if [ "$VALUE" = "?" ] ; then
usage
exit 1
fi
if [ "$VALUE" = ":" ] ; then
usage
exit 1
fi
done
## call installAction after gathering inputs
installAction $ACTION
## call launch after gathering inputs and installs complete
launch $LAUNCH_ENABLED
########################################################################
########################## JIRA FUNCTIONALITY ##########################
## JIRA new bug template output
rm -f jira_new_bug_template.txt
echo "Collecting build, device, and launch info for JIRA template."
echo ""
echo "h1. Issue" >> jira_new_bug_template.txt
echo "<< enter your description of the nature of the issue here >>" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "h1. Impact" >> jira_new_bug_template.txt
echo "<< enter your description of the impact of this issue on the users here >>" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "h1. Repro Steps" >> jira_new_bug_template.txt
echo "# enter your.." >> jira_new_bug_template.txt
echo "# steps to reproduce.." >> jira_new_bug_template.txt
echo "# the issue as observed here." >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "h1. App Details" >> jira_new_bug_template.txt
echo "* Source: $APK_URL" >> jira_new_bug_template.txt
echo "* File: $FILEPATH" >> jira_new_bug_template.txt
echo "* Release Version: $appVersionName" >> jira_new_bug_template.txt
echo "* Build Number/versionCode: $appVersionCode" >> jira_new_bug_template.txt
echo "* Launched Activity: $appLaunchActivity" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "h1. Device(s)" >> jira_new_bug_template.txt
echo "{noformat}" >> jira_new_bug_template.txt
cat deviceInfoFile.txt >> jira_new_bug_template.txt
echo "{noformat}" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "" >> jira_new_bug_template.txt
echo "h1. LogCat Snippet" >> jira_new_bug_template.txt
echo "{noformat}" >> jira_new_bug_template.txt
echo "replace this text with a copy of the relevant lines from the device(s) LogCat output" >> jira_new_bug_template.txt
echo "{noformat}" >> jira_new_bug_template.txt
## JIRA close bug template output
rm -f jira_close_bug_template.txt
echo ""
echo "h1. Scope of Work Tested" >> jira_close_bug_template.txt
echo "<< enter your description of the scope of work tested here >>" >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "h1. Validation Steps" >> jira_close_bug_template.txt
echo "# enter your.." >> jira_close_bug_template.txt
echo "# steps to validate.." >> jira_close_bug_template.txt
echo "# the fix as tested here." >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "h1. App Details" >> jira_close_bug_template.txt
echo "* Source: $APK_URL" >> jira_close_bug_template.txt
echo "* File: $FILEPATH" >> jira_close_bug_template.txt
echo "* Release Version: $appVersionName" >> jira_close_bug_template.txt
echo "* Build Number/versionCode: $appVersionCode" >> jira_close_bug_template.txt
echo "* Launched Activity: $appLaunchActivity" >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "" >> jira_close_bug_template.txt
echo "h1. Device(s)" >> jira_close_bug_template.txt
echo "{noformat}" >> jira_close_bug_template.txt
cat deviceInfoFile.txt >> jira_close_bug_template.txt
echo "{noformat}" >> jira_close_bug_template.txt

Comments

  1. The like is wrong that behind the "here in this post" .

    http://www.everybodytests.com/2014/10/my-favorite-script-being-lazy-for-fun.html%20Done

    ReplyDelete
  2. Usually I never comment on blogs but yours is so convincing that I never stop myself to say something about it. keep updating regularly.
    spoken english classes in arumbakkam | spoken english classes in koyambedu | spoken english classes in chennai ayanavaram

    ReplyDelete

Post a Comment

Popular posts from this blog

UiAutomator and Watchers: Adding Async Robustness to UI Automation

"I'm looking over your shoulder... only because I've got your back." ~ Stephen Colbert After my recent UiAutomator review a user brought up an important question about the use of UiWatcher. The watchers serve as async guardians of the test flow, making sure the odd dialog window doesn't completely frustrate your tests. Having a tool that automatically watches your back when you're focused on the functional flow of your tests is awesome. 100% pure awesomesauce. Since the API documentation on watchers is scant and the UI Testing tutorial on the Android dev guide doesn't cover their use in depth, I figured I should add a post here that goes over a simple scenario demonstrating how to use this fundamentally important UI automation tool. In my example code below, I'm using uiautomator to launch the API Demo app (meaning run this against an Emulator built in API level 17 - I used the Galaxy Nexus image included in the latest ADT and platform tools). ...

UiAutomator.jar: What happened when Android's JUnit and MonkeyRunner got drunk and hooked up

"Drunkenness does not create vice; it merely brings it into view" ~Seneca So Jelly Bean 4.2 landed with much fanfare and tucked in amongst the neat new OS and SDK features (hello, multi-user tablets!) was this little gem for testers: UiAutomator.jar. I have it on good authority that it snuck in amongst the updates in the preview tools and OS updates sometime around 4.1 with r3 of the platform. As a code-monkey of a tester, I was intrigued. One of the best ways Google can support developers struggling with platform fragmentation is to make their OS more testable so I hold high hopes with every release to see effort spent in that area. I have spent a couple days testing out the new UiAutomator API  and the best way I can think of describing it is that Android's JUnit and MonkeyRunner got drunk and had a code baby. Let me explain what I mean before that phrase sinks down into "mental image" territory. JUnit, for all its power and access to every interface, e...

Why Developers Shouldn't Perform Software Testing - A Rebuttal

Take a minute and read the following 2-pager entitled " Guest View: Why developers shouldn’t perform software testing ". Man, I tried to like this article because the author, Martin Mudge, clearly knows that businesses who undervalue quality by trying to eliminate testing through simply shuffling the traditional testing task to developers are making a huge mistake. Unfortunately he begins by making an assertion that testing overburdens a developer which at face value is complete nonsense. If you feel overburdened, it is a timeline issue and that is true no matter WHAT the nature of the tasking is.  So his assertion of “one the most important” contributing factors being overburdening the developers is massively flawed. It gets immediately worse from there because his second point is about time constraints.  Mr Mudge just gets so much wrong. What he really should be shooting down is the idea that testing, as a cost-center not as a task, can be eliminated by having your p...