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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
The like is wrong that behind the "here in this post" .
ReplyDeletehttp://www.everybodytests.com/2014/10/my-favorite-script-being-lazy-for-fun.html%20Done
Thanks for the catch! Updated.
DeleteIts a great post.
ReplyDeleteSpoken english Training in Bangalore
Usually I never comment on blogs but yours is so convincing that I never stop myself to say something about it. keep updating regularly.
ReplyDeletespoken english classes in arumbakkam | spoken english classes in koyambedu | spoken english classes in chennai ayanavaram