Xamarin TestCloud execute test run

Octopus.Script exported 2016-01-19 by colin.dabritz@viewpoint.com belongs to ‘Xamarin’ category.

Executes a Xamarin TestCloud test run for an app contained in a nuget file.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Api Key

apiKey

The app specific API key for your test run. This may be found in the testcloud interface for your application:

  1. Visit the testcloud interface: https://testcloud.xamarin.com/
  2. Choose “New Test Run” and configure as desired.
  3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username

Devices Code

devicesCode

The code identifying the devices to test against.

This may be found in the testcloud interface for your application:

  1. Visit the testcloud interface: https://testcloud.xamarin.com/
  2. Choose “New Test Run” and configure as desired.
  3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username

TestCloud user name

testCloudUserName

The TestCloud user name (e.g. email) that should own the test run. This user must have appropriate permissions for test runs for the configured application.

(Note that this user name will automatically be converted to lower case at this time, to work around an issue with the TestCloud API)

Package Step Name

PackageStepName

The name of the previously-deployed package step that contains the app files that you want to test.

Series

series = master

You may specify a test series to run, or use the default of “master”.

Locale

locale = en_US

You may specify a locate to run the tests under, or use the default of “en_US”.

App path override (optional)

appPathOverride

This fully optional search path allows you to look for app files in a specific folder path in your nuget file, such as “bin\iPhone”. This may be needed in cases when the nuget file has multiple app files in different locations. This path override is a path relative to the root of the nuget file.

UITest DLL path override (optional)

dllPathOverride

This fully optional search path allows you to look for the UITest DLL library files in a specific folder path in your nuget file, such as “bin\UITest\Release”. This may be needed in cases when the nuget file has multiple UITest libraries in different locations. This path override is a path relative to the root of the nuget file.

Symbol Path Override (optional)

symbolPathOverride

!!IMPORTANT!! A symbols file is NOT REQUIRED for TestCloud. It only provides more information for crashes.

This optional search path allows you to look in a specific folder path in your nuget file, such as “bin\iPhone”, for a *.dSYM.zip symbols file. This path override is a path relative to the root of the nuget file.

If an override is specified and a .zip file is not found, the script will search for a *.dSYM folder to use instead. This means if you have a dSYM folder and not a zip file, you can specify ”.” as an override to search the entire nuget package for the folder, or specify a more specific search path.

This may be needed in cases when the nuget file has multiple symbol files in different locations, or when you only have a dSYM folder, not a .zip file available.

test-cloud.exe path override (optional)

testCloudExePathOverride

This fully optional search path allows you to look for the test-cloud.exe utility file in a specific folder path in your nuget file, such as “tools”. This may be needed in cases when the nuget file has multiple instances of the test-cloud.exe utility in different locations. This path override is a path relative to the root of the nuget file.

Script body

Steps based on this template will execute the following PowerShell script.

#####################################
# Xamarin TestCloud Start Test Run script
# 
# Kicks off a new test run for an app.
# This script uses the test-cloud.exe helper utility included with the Xamarin UITest nuget package. 
# https://www.nuget.org/packages/Xamarin.UITest
# 
# For use with Xamarin UITests
# https://developer.xamarin.com/guides/testcloud/uitest/
# being run in Xamarin TestCloud on physical devices
# https://developer.xamarin.com/guides/testcloud/introduction-to-test-cloud/
#
# v0.1 - kicks off configured test run, tested against iOS app only
#
# The nuget package must contain the *.iap or *.apk file, compiled with calabash included
# The nuget package must contain the DLLs from the UITest project
# The nuget package may optionally contain a symbols file, *.app.dSYM.zip
# The nuget package must contain the test-cloud.exe support utility
#
# The following *.nuspec example will package:
# * a release ipa
# * UITest DLLs
# * associated *.app.dSYM.zip file
# * The test-cloud.exe support utility
#
# all search paths default to the root of the nuget package,
# and may be further qualified relative to the root of the nuget package
# The upload script uses default or optionally qualified search paths for the following:
# * The .ipa or .apk location
# * The UITest project DLLs
# * The *.app.dSYM.zip symbols file
# * The test-cloud.exe utility
#
# It also requires the API Key from the application, and the code for the devices desired,
# and a valid user accout to run as.
#
# 1. Visit the testcloud interface: https://testcloud.xamarin.com/
# 2. Choose "New Test Run" and configure as desired.
# 3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username
#
# The nugetFile below is an example that retrieves the appropriate files from a typical iOS build
#
# https://docs.nuget.org/create/nuspec-reference#file-element-examples
#
# In some cases the ID, Version, and Description may need manually specified.
#

<#

    <?xml version="1.0"?>
    <package>
      <metadata>
        <id>$id$</id>
        <title>$id$</title>
        <version>$version$</version>
        <description>Mobile project packaged for Octopus deploy. $description$</description>
      </metadata>
      <files>
        <!-- Matches mobile package files. Note this will only include the platform being built,
	         and should match only a single file. -->
        
        <!-- iOS -->
        <file src="**/Release/**/*.ipa" target="" />

        <!-- Include release dSYM symbols file -->
        <file src="**/Release/*.app.dSYM.zip" target="" />

        <!-- UITest DLLs -->
        <file src="..\*Test*\bin\Release\*.dll" target="bin/UITest/Release" />

        <!-- Utility EXE for TestCloud submission scripts -->
        <!-- Note: The first slash after the parent directory .. MUST be backslash or the package step fails -->
        <file src="..\packages/Xamarin.UITest.*/tools/test-cloud.exe" target="tools" />

      </files>
    </package>

#>

#############################
# Debug Parameter Overrides #
#############################

# These values are set explicitly durring debugging so that the script can
#   be run in the editor.
# For local debugging, uncomment these values and fill in appropriately.

<#

$OctopusParameters = @{
"appPathOverride" = "" # "bin\iPhone"
"dllPathOverride" = "" # "bin\UITest\Release"
"testCloudUserName" = "your.user@name.com"
"symbolPathOverride" = ""; # "bin\iPhone"
"apiKey" = "YOUR-KEY-HERE";
"devicesCode" = "ae978982"; # devices code here (example ae978982 is 2 devices)
"series" = "master"; # default is master
"locale" = "en_US"; # default locale is en_US
"testCloudExePathOverride" = "" # "tools"
}

# debug folder with app files
$stepPath = "C:\Temp\powershellscript\"

# #>

###################################
# Octopus Deploy common functions #
###################################

# A collection of functions that can be used by script steps to determine where packages installed
# by previous steps are located on the filesystem.
 
function Find-InstallLocations {
    $result = @()
    $OctopusParameters.Keys | foreach {
        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {
            $result += $OctopusParameters[$_]
        }
    }
    return $result
}
 
function Find-InstallLocation($stepName) {
    $result = $OctopusParameters.Keys | where {
        $_.Equals("Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath",  [System.StringComparison]::OrdinalIgnoreCase)
    } | select -first 1
 
    if ($result) {
        return $OctopusParameters[$result]
    }
 
    throw "No install location found for step: $stepName"
}

function Find-SingleInstallLocation {
    $all = @(Find-InstallLocations)
    if ($all.Length -eq 1) {
        return $all[0]
    }
    if ($all.Length -eq 0) {
        throw "No package steps found"
    }
    throw "Multiple package steps have run; please specify a single step"
}

#####################
# Utility functions #
#####################

function Get-ExactlyOneMobilePackageFileInfo($searchPath)
{
    $apkFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.apk #Android
    $ipaFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.ipa #iOS
    $appxFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.appx # windows

    $apkCount = $apkFiles.count

    $ipaCount = $ipaFiles.count

    $appxCount = $appxFiles.count

    $totalCount = $apkCount + $ipaCount + $appxCount

    if($totalCount -ne 1)
    {
        throw "Did not find exactly one (1) mobile application package. Found $apkCount APK file(s), $ipaCount IPA file(s), and $appxCount Appx file(s). Searched under path: $searchPath"
    }

    if($apkCount -eq 1)
    {
        return $apkFiles
    }

    if($ipaCount -eq 1)
    {
        return $ipaFiles
    }

    if($appxCount -eq 1)
    {
        return $appxFiles
    }

    throw "Unable to find mobile application packages (fallback error - not expected)"
}

function Get-OneDsymFileInfoOrNull($searchPath)
{
    $symbolFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.app.dSYM.zip
    
    $fileCount = $symbolFiles.count

    if($fileCount -eq 0)
    { 
        return $null
    }   

    if($fileCount -gt 1)
    {
        throw "Found more than one symbols file. Found $fileCount dSYM file(s). Searched under path: $searchPath"
    }

    return $symbolFiles
}

function Get-ExactlyOneUploadExeFileInfo($searchPath)
{
    $testcloudexefiles = Get-ChildItem -Path $searchPath -Recurse -Filter test-cloud.exe
    
    $fileCount = $testcloudexefiles.count

    if($fileCount -ne 1)
    {
        throw "Did not find exactly one (1) test-cloud.exe. Found $fileCount exe file(s). Searched under path: $searchPath"
    }

    return $testcloudexefiles
}

function Get-ExactlyOneUITestDllPath($searchPath)
{
    $XamarinUITestdlls = Get-ChildItem -Path $searchPath -Recurse -Filter Xamarin.UITest.dll
    
    $fileCount = $XamarinUITestdlls.count

    if($fileCount -ne 1)
    {
        throw "Did not find exactly one (1) Test DLL location. Found $fileCount DLL location(s), based on finding 'Xamarin.UITest.dll' files. Searched under path: $searchPath"
    }
    
    $singleXamarinUITestDllFullPath = $XamarinUITestdlls.FullName
    $UITestDllPath = Split-Path -parent $singleXamarinUITestDllFullPath
    return $UITestDllPath
}

####################
# Basic Parameters #
####################

# required
$apiKey = $OctopusParameters['apiKey']
$devicesCode = $OctopusParameters['devicesCode']
$testCloudUserName = $OctopusParameters['testCloudUserName']

# optional
$series = $OctopusParameters['series'] # default "master"
$locale = $OctopusParameters['locale'] # default "en_US"

# optional additional path overrides
$appPathOverride = $OctopusParameters['appPathOverride']
$dllPathOverride = $OctopusParameters['dllPathOverride']
$symbolPathOverride = $OctopusParameters['symbolPathOverride']
$testCloudExePathOverride = $OctopusParameters['testCloudExePathOverride']

# test cloud user names must be lower case to work around API/Utility issue (until issue is fixed)
$testCloudUserName = $testCloudUserName.ToLower()

$stepName = $OctopusParameters['MobileAppPackageStepName']

# set step path, if not already set
If([string]::IsNullOrEmpty($stepPath))
{
    if (![string]::IsNullOrEmpty($stepName)) {
        Write-Host "Finding path to package step: $stepName"
        $stepPath = Find-InstallLocation $stepName
    } else {
        $stepPath = Find-SingleInstallLocation
    }
}

Write-Host "Nuget Package base path    : $stepPath"
# Write-Host "##octopus[stderr-progress]"

# find app

# complete search paths, overrides may be blank
$appSearchPath = Join-Path $stepPath $appPathOverride
$symbolSearchPath = Join-Path $stepPath $symbolPathOverride
$dllSearchPath = Join-Path $stepPath $dllPathOverride
$testCouldExeSearchPath = Join-Path $stepPath $testCloudExePathOverride

$appFileFullPath = (Get-ExactlyOneMobilePackageFileInfo $appSearchPath).FullName
$symbolFileFullPath = (Get-OneDsymFileInfoOrNull $symbolSearchPath).FullName
$dllDirectoryFullPath = Get-ExactlyOneUITestDllPath $dllSearchPath

$testCloudExeFullPath = (Get-ExactlyOneUploadExeFileInfo $testCouldExeSearchPath).FullName

# It turns out that the utility exe expects a dsym folder, convert to folder

# DIRTY HACKS - the API should accept a *.dSYM.zip like insights does, see
# https://testcloud.ideas.aha.io/ideas/XTA-I-50

Add-Type -AssemblyName System.IO.Compression.FileSystem

function Unzip
{
    param([string]$zipfile, [string]$outpath)

    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
}

$symbolFileDirectoryPath = ""
if($symbolFileFullPath) # has a full zip path
{
    $parentPath = Split-Path -parent $symbolFileFullPath
    Unzip $symbolFileFullPath $parentPath

    # get unzipped folder name ending in dSYM
    $symbolFileDirectoryPath = (Get-ChildItem -Path $parentPath -Recurse -Filter *.dSYM).FullName
}
elseif ($symbolPathOverride) # no zip, try to find folder
{
    # search for dSYM folder instead

    $symbolFileDirectorySearchResults = Get-ChildItem -Path $searchPath -Recurse -Filter *.dSYM
    
    # if exactly one result
    if($symbolFileDirectorySearchResults.Count -eq 1)
    {
        $symbolFileDirectoryPath = $symbolFileDirectorySearchResults.FullName
    }
}

######################
# Invoke the request #
######################
 
Write-Host "App path                   : " $appFileFullPath
Write-Host "Symbol File path (optional): " $symbolFileFullPath
Write-Host "Test DLL full path         : " $dllDirectoryFullPath
Write-Host "TestCloud exe path         : " $testCloudExeFullPath
Write-Host

# run command with optional argument

if($symbolFileDirectoryPath) # symbols file present
{
    Write-Host "Running command: " 
    Write-Host """$testCloudExeFullPath"" submit ""$appFileFullPath"" $apiKey --user $testCloudUserName --devices $devicesCode --series ""$series"" --locale ""$locale"" --assembly-dir ""$dllDirectoryFullPath"" --dsym ""$symbolFileDirectoryPath"""
    Write-Host 
    cmd /c "$testCloudExeFullPath" submit "$appFileFullPath" $apiKey --user $testCloudUserName --devices $devicesCode --series "$series" --locale "$locale" --assembly-dir "$dllDirectoryFullPath" --dsym "$symbolFileDirectoryPath"
}
else # no symbols file
{
    Write-Host "Running command: " 
    Write-Host """$testCloudExeFullPath"" submit ""$appFileFullPath"" $apiKey --user $testCloudUserName --devices $devicesCode --series ""$series"" --locale ""$locale"" --assembly-dir ""$dllDirectoryFullPath"""
    Write-Host
    cmd /c "$testCloudExeFullPath" submit "$appFileFullPath" $apiKey --user $testCloudUserName --devices $devicesCode --series "$series" --locale "$locale" --assembly-dir "$dllDirectoryFullPath"
}

Write-Host
Write-Host "TestCloud upload command complete."

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "fd79c2f6-8983-4f91-a36d-aa622f44f16f",
  "Name": "Xamarin TestCloud execute test run",
  "Description": "Executes a Xamarin TestCloud test run for an app contained in a nuget file.",
  "Version": 2,
  "ExportedAt": "2016-01-19T23:14:49.354+00:00",
  "ActionType": "Octopus.Script",
  "Author": "colin.dabritz@viewpoint.com",
  "Parameters": [
    {
      "Name": "apiKey",
      "Label": "Api Key",
      "HelpText": "The app specific API key for your test run. This may be found in the testcloud interface for your application:\n\n1. Visit the testcloud interface: https://testcloud.xamarin.com/\n2. Choose \"New Test Run\" and configure as desired.\n3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Name": "devicesCode",
      "Label": "Devices Code",
      "HelpText": "The code identifying the devices to test against.\n\nThis may be found in the testcloud interface for your application:\n\n1. Visit the testcloud interface: https://testcloud.xamarin.com/\n2. Choose \"New Test Run\" and configure as desired.\n3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "testCloudUserName",
      "Label": "TestCloud user name",
      "HelpText": "The TestCloud user name (e.g. email) that should own the test run. This user must have appropriate permissions for test runs for the configured application.\n\n(Note that this user name will automatically be converted to lower case at this time, to work around an issue with the TestCloud API)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "PackageStepName",
      "Label": "Package Step Name",
      "HelpText": "The name of the previously-deployed package step that contains the app files that you want to test.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "StepName"
      }
    },
    {
      "Name": "series",
      "Label": "Series",
      "HelpText": "You may specify a test series to run, or use the default of \"master\".",
      "DefaultValue": "master",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "locale",
      "Label": "Locale",
      "HelpText": "You may specify a locate to run the tests under, or use the default of \"en_US\".",
      "DefaultValue": "en_US",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "appPathOverride",
      "Label": "App path override (optional)",
      "HelpText": "This fully optional search path allows you to look for app files in a specific folder path in your nuget file, such as \"bin\\iPhone\". This may be needed in cases when the nuget file has multiple app files in different locations. This path override is a path relative to the root of the nuget file.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "dllPathOverride",
      "Label": "UITest DLL path override (optional)",
      "HelpText": "This fully optional search path allows you to look for the UITest DLL library files in a specific folder path in your nuget file, such as \"bin\\UITest\\Release\". This may be needed in cases when the nuget file has multiple UITest libraries in different locations. This path override is a path relative to the root of the nuget file.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "symbolPathOverride",
      "Label": "Symbol Path Override (optional)",
      "HelpText": "!!IMPORTANT!!\nA symbols file is NOT REQUIRED for TestCloud. It only provides more information for crashes.\n\nThis optional search path allows you to look in a specific folder path in your nuget file, such as \"bin\\iPhone\", for a *.dSYM.zip symbols file. This path override is a path relative to the root of the nuget file.\n\nIf an override is specified and a .zip file is not found, the script will search for a *.dSYM folder to use instead. This means if you have a dSYM folder and not a zip file, you can specify \".\" as an override to search the entire nuget package for the folder, or specify a more specific search path.\n\nThis may be needed in cases when the nuget file has multiple symbol files in different locations, or when you only have a dSYM folder, not a .zip file available.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "testCloudExePathOverride",
      "Label": "test-cloud.exe path override (optional)",
      "HelpText": "This fully optional search path allows you to look for the test-cloud.exe utility file in a specific folder path in your nuget file, such as \"tools\". This may be needed in cases when the nuget file has multiple instances of the test-cloud.exe utility in different locations. This path override is a path relative to the root of the nuget file.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptBody": "#####################################\n# Xamarin TestCloud Start Test Run script\n# \n# Kicks off a new test run for an app.\n# This script uses the test-cloud.exe helper utility included with the Xamarin UITest nuget package. \n# https://www.nuget.org/packages/Xamarin.UITest\n# \n# For use with Xamarin UITests\n# https://developer.xamarin.com/guides/testcloud/uitest/\n# being run in Xamarin TestCloud on physical devices\n# https://developer.xamarin.com/guides/testcloud/introduction-to-test-cloud/\n#\n# v0.1 - kicks off configured test run, tested against iOS app only\n#\n# The nuget package must contain the *.iap or *.apk file, compiled with calabash included\n# The nuget package must contain the DLLs from the UITest project\n# The nuget package may optionally contain a symbols file, *.app.dSYM.zip\n# The nuget package must contain the test-cloud.exe support utility\n#\n# The following *.nuspec example will package:\n# * a release ipa\n# * UITest DLLs\n# * associated *.app.dSYM.zip file\n# * The test-cloud.exe support utility\n#\n# all search paths default to the root of the nuget package,\n# and may be further qualified relative to the root of the nuget package\n# The upload script uses default or optionally qualified search paths for the following:\n# * The .ipa or .apk location\n# * The UITest project DLLs\n# * The *.app.dSYM.zip symbols file\n# * The test-cloud.exe utility\n#\n# It also requires the API Key from the application, and the code for the devices desired,\n# and a valid user accout to run as.\n#\n# 1. Visit the testcloud interface: https://testcloud.xamarin.com/\n# 2. Choose \"New Test Run\" and configure as desired.\n# 3. In the last step, copy the large hash (app specific API Key), devices parameter code, and username\n#\n# The nugetFile below is an example that retrieves the appropriate files from a typical iOS build\n#\n# https://docs.nuget.org/create/nuspec-reference#file-element-examples\n#\n# In some cases the ID, Version, and Description may need manually specified.\n#\n\n<#\n\n    <?xml version=\"1.0\"?>\n    <package>\n      <metadata>\n        <id>$id$</id>\n        <title>$id$</title>\n        <version>$version$</version>\n        <description>Mobile project packaged for Octopus deploy. $description$</description>\n      </metadata>\n      <files>\n        <!-- Matches mobile package files. Note this will only include the platform being built,\n\t         and should match only a single file. -->\n        \n        <!-- iOS -->\n        <file src=\"**/Release/**/*.ipa\" target=\"\" />\n\n        <!-- Include release dSYM symbols file -->\n        <file src=\"**/Release/*.app.dSYM.zip\" target=\"\" />\n\n        <!-- UITest DLLs -->\n        <file src=\"..\\*Test*\\bin\\Release\\*.dll\" target=\"bin/UITest/Release\" />\n\n        <!-- Utility EXE for TestCloud submission scripts -->\n        <!-- Note: The first slash after the parent directory .. MUST be backslash or the package step fails -->\n        <file src=\"..\\packages/Xamarin.UITest.*/tools/test-cloud.exe\" target=\"tools\" />\n\n      </files>\n    </package>\n\n#>\n\n#############################\n# Debug Parameter Overrides #\n#############################\n\n# These values are set explicitly durring debugging so that the script can\n#   be run in the editor.\n# For local debugging, uncomment these values and fill in appropriately.\n\n<#\n\n$OctopusParameters = @{\n\"appPathOverride\" = \"\" # \"bin\\iPhone\"\n\"dllPathOverride\" = \"\" # \"bin\\UITest\\Release\"\n\"testCloudUserName\" = \"your.user@name.com\"\n\"symbolPathOverride\" = \"\"; # \"bin\\iPhone\"\n\"apiKey\" = \"YOUR-KEY-HERE\";\n\"devicesCode\" = \"ae978982\"; # devices code here (example ae978982 is 2 devices)\n\"series\" = \"master\"; # default is master\n\"locale\" = \"en_US\"; # default locale is en_US\n\"testCloudExePathOverride\" = \"\" # \"tools\"\n}\n\n# debug folder with app files\n$stepPath = \"C:\\Temp\\powershellscript\\\"\n\n# #>\n\n###################################\n# Octopus Deploy common functions #\n###################################\n\n# A collection of functions that can be used by script steps to determine where packages installed\n# by previous steps are located on the filesystem.\n \nfunction Find-InstallLocations {\n    $result = @()\n    $OctopusParameters.Keys | foreach {\n        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {\n            $result += $OctopusParameters[$_]\n        }\n    }\n    return $result\n}\n \nfunction Find-InstallLocation($stepName) {\n    $result = $OctopusParameters.Keys | where {\n        $_.Equals(\"Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath\",  [System.StringComparison]::OrdinalIgnoreCase)\n    } | select -first 1\n \n    if ($result) {\n        return $OctopusParameters[$result]\n    }\n \n    throw \"No install location found for step: $stepName\"\n}\n\nfunction Find-SingleInstallLocation {\n    $all = @(Find-InstallLocations)\n    if ($all.Length -eq 1) {\n        return $all[0]\n    }\n    if ($all.Length -eq 0) {\n        throw \"No package steps found\"\n    }\n    throw \"Multiple package steps have run; please specify a single step\"\n}\n\n#####################\n# Utility functions #\n#####################\n\nfunction Get-ExactlyOneMobilePackageFileInfo($searchPath)\n{\n    $apkFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.apk #Android\n    $ipaFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.ipa #iOS\n    $appxFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.appx # windows\n\n    $apkCount = $apkFiles.count\n\n    $ipaCount = $ipaFiles.count\n\n    $appxCount = $appxFiles.count\n\n    $totalCount = $apkCount + $ipaCount + $appxCount\n\n    if($totalCount -ne 1)\n    {\n        throw \"Did not find exactly one (1) mobile application package. Found $apkCount APK file(s), $ipaCount IPA file(s), and $appxCount Appx file(s). Searched under path: $searchPath\"\n    }\n\n    if($apkCount -eq 1)\n    {\n        return $apkFiles\n    }\n\n    if($ipaCount -eq 1)\n    {\n        return $ipaFiles\n    }\n\n    if($appxCount -eq 1)\n    {\n        return $appxFiles\n    }\n\n    throw \"Unable to find mobile application packages (fallback error - not expected)\"\n}\n\nfunction Get-OneDsymFileInfoOrNull($searchPath)\n{\n    $symbolFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.app.dSYM.zip\n    \n    $fileCount = $symbolFiles.count\n\n    if($fileCount -eq 0)\n    { \n        return $null\n    }   \n\n    if($fileCount -gt 1)\n    {\n        throw \"Found more than one symbols file. Found $fileCount dSYM file(s). Searched under path: $searchPath\"\n    }\n\n    return $symbolFiles\n}\n\nfunction Get-ExactlyOneUploadExeFileInfo($searchPath)\n{\n    $testcloudexefiles = Get-ChildItem -Path $searchPath -Recurse -Filter test-cloud.exe\n    \n    $fileCount = $testcloudexefiles.count\n\n    if($fileCount -ne 1)\n    {\n        throw \"Did not find exactly one (1) test-cloud.exe. Found $fileCount exe file(s). Searched under path: $searchPath\"\n    }\n\n    return $testcloudexefiles\n}\n\nfunction Get-ExactlyOneUITestDllPath($searchPath)\n{\n    $XamarinUITestdlls = Get-ChildItem -Path $searchPath -Recurse -Filter Xamarin.UITest.dll\n    \n    $fileCount = $XamarinUITestdlls.count\n\n    if($fileCount -ne 1)\n    {\n        throw \"Did not find exactly one (1) Test DLL location. Found $fileCount DLL location(s), based on finding 'Xamarin.UITest.dll' files. Searched under path: $searchPath\"\n    }\n    \n    $singleXamarinUITestDllFullPath = $XamarinUITestdlls.FullName\n    $UITestDllPath = Split-Path -parent $singleXamarinUITestDllFullPath\n    return $UITestDllPath\n}\n\n####################\n# Basic Parameters #\n####################\n\n# required\n$apiKey = $OctopusParameters['apiKey']\n$devicesCode = $OctopusParameters['devicesCode']\n$testCloudUserName = $OctopusParameters['testCloudUserName']\n\n# optional\n$series = $OctopusParameters['series'] # default \"master\"\n$locale = $OctopusParameters['locale'] # default \"en_US\"\n\n# optional additional path overrides\n$appPathOverride = $OctopusParameters['appPathOverride']\n$dllPathOverride = $OctopusParameters['dllPathOverride']\n$symbolPathOverride = $OctopusParameters['symbolPathOverride']\n$testCloudExePathOverride = $OctopusParameters['testCloudExePathOverride']\n\n# test cloud user names must be lower case to work around API/Utility issue (until issue is fixed)\n$testCloudUserName = $testCloudUserName.ToLower()\n\n$stepName = $OctopusParameters['MobileAppPackageStepName']\n\n# set step path, if not already set\nIf([string]::IsNullOrEmpty($stepPath))\n{\n    if (![string]::IsNullOrEmpty($stepName)) {\n        Write-Host \"Finding path to package step: $stepName\"\n        $stepPath = Find-InstallLocation $stepName\n    } else {\n        $stepPath = Find-SingleInstallLocation\n    }\n}\n\nWrite-Host \"Nuget Package base path    : $stepPath\"\n# Write-Host \"##octopus[stderr-progress]\"\n\n# find app\n\n# complete search paths, overrides may be blank\n$appSearchPath = Join-Path $stepPath $appPathOverride\n$symbolSearchPath = Join-Path $stepPath $symbolPathOverride\n$dllSearchPath = Join-Path $stepPath $dllPathOverride\n$testCouldExeSearchPath = Join-Path $stepPath $testCloudExePathOverride\n\n$appFileFullPath = (Get-ExactlyOneMobilePackageFileInfo $appSearchPath).FullName\n$symbolFileFullPath = (Get-OneDsymFileInfoOrNull $symbolSearchPath).FullName\n$dllDirectoryFullPath = Get-ExactlyOneUITestDllPath $dllSearchPath\n\n$testCloudExeFullPath = (Get-ExactlyOneUploadExeFileInfo $testCouldExeSearchPath).FullName\n\n# It turns out that the utility exe expects a dsym folder, convert to folder\n\n# DIRTY HACKS - the API should accept a *.dSYM.zip like insights does, see\n# https://testcloud.ideas.aha.io/ideas/XTA-I-50\n\nAdd-Type -AssemblyName System.IO.Compression.FileSystem\n\nfunction Unzip\n{\n    param([string]$zipfile, [string]$outpath)\n\n    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)\n}\n\n$symbolFileDirectoryPath = \"\"\nif($symbolFileFullPath) # has a full zip path\n{\n    $parentPath = Split-Path -parent $symbolFileFullPath\n    Unzip $symbolFileFullPath $parentPath\n\n    # get unzipped folder name ending in dSYM\n    $symbolFileDirectoryPath = (Get-ChildItem -Path $parentPath -Recurse -Filter *.dSYM).FullName\n}\nelseif ($symbolPathOverride) # no zip, try to find folder\n{\n    # search for dSYM folder instead\n\n    $symbolFileDirectorySearchResults = Get-ChildItem -Path $searchPath -Recurse -Filter *.dSYM\n    \n    # if exactly one result\n    if($symbolFileDirectorySearchResults.Count -eq 1)\n    {\n        $symbolFileDirectoryPath = $symbolFileDirectorySearchResults.FullName\n    }\n}\n\n######################\n# Invoke the request #\n######################\n \nWrite-Host \"App path                   : \" $appFileFullPath\nWrite-Host \"Symbol File path (optional): \" $symbolFileFullPath\nWrite-Host \"Test DLL full path         : \" $dllDirectoryFullPath\nWrite-Host \"TestCloud exe path         : \" $testCloudExeFullPath\nWrite-Host\n\n# run command with optional argument\n\nif($symbolFileDirectoryPath) # symbols file present\n{\n    Write-Host \"Running command: \" \n    Write-Host \"\"\"$testCloudExeFullPath\"\" submit \"\"$appFileFullPath\"\" $apiKey --user $testCloudUserName --devices $devicesCode --series \"\"$series\"\" --locale \"\"$locale\"\" --assembly-dir \"\"$dllDirectoryFullPath\"\" --dsym \"\"$symbolFileDirectoryPath\"\"\"\n    Write-Host \n    cmd /c \"$testCloudExeFullPath\" submit \"$appFileFullPath\" $apiKey --user $testCloudUserName --devices $devicesCode --series \"$series\" --locale \"$locale\" --assembly-dir \"$dllDirectoryFullPath\" --dsym \"$symbolFileDirectoryPath\"\n}\nelse # no symbols file\n{\n    Write-Host \"Running command: \" \n    Write-Host \"\"\"$testCloudExeFullPath\"\" submit \"\"$appFileFullPath\"\" $apiKey --user $testCloudUserName --devices $devicesCode --series \"\"$series\"\" --locale \"\"$locale\"\" --assembly-dir \"\"$dllDirectoryFullPath\"\"\"\n    Write-Host\n    cmd /c \"$testCloudExeFullPath\" submit \"$appFileFullPath\" $apiKey --user $testCloudUserName --devices $devicesCode --series \"$series\" --locale \"$locale\" --assembly-dir \"$dllDirectoryFullPath\"\n}\n\nWrite-Host\nWrite-Host \"TestCloud upload command complete.\"",
    "Octopus.Action.Script.Syntax": "PowerShell"
  },
  "Category": "Xamarin",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/xamarin-testcloud-execute-testrun.json",
  "Website": "/step-templates/fd79c2f6-8983-4f91-a36d-aa622f44f16f",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////NJjbzOX2QZ7d8vn9TaXgZ7Lkmcvt5vL6gL/ps9jx2ez4dLjmptLvjcXrv9/0WqviJo8DRgAAA/pJREFUeNrsnd2S5CAIhcekjfk1ef+n3anau96kA4jYYc+5nJqaGtR8ICj+/EAQBEEQBEEQBEEQBEEQBEEQBBnp1Sno1dqKMcegopjHlpORg6KmZtOyxKCquDSyow/K6ptY8lK349eSFqtrDRW02tvRhSrqzA2JdQyJ1nZsoZK253/pTb73HKopm0YmoaIsY5WhpiHD49Frj+BY1xAzBKdQWenp6DVG8BSqazKJ3oOBlqej1xDBezDR/nT0miE4BSNVRvDYWxnSj09HrwmCu2Co7unoNUDwHEw1Vwuyoq0h8fV09FZGsB16KyN4DeZaH4/emgg+aDm1WUCk6+TSYYrene9rBmpErY7gT/vbgb8ER6rt6rveRF/JhHhsopuujOCRsXm4x/TbMEfDxOPKCSYSz9HNhgjuWEN8ly+KrN9WRfChOsbM+VPc9W7cYCLS0UtI7G8W6BVwiM04NQRnfjAxUD9ektfJFuhlL5iRHwcoIZi4v91pKyZL0n2DBXrP4XL1XbF8oS6CyfvbjULVTVbkjhbo5Qx2ZONQC8Gcqk6+94q7tMhdjGBWavEWSIMAhzqJR15V59ZFdAVJgMUCvcR/dBLhUAPB3KrOzdIZSwapqPbDTi1+DGxZIbIqgvmpxU94FfpChV2vpKCerp3QXDhIcgRLqjrXwx6LB0mKYFlq8ZJMXfEgSUMuYVXnAk0FvrAQwdKqznDuUReV0pcg8Siv6pyuoUlh1cpqP/KqznGSrntjwCH+46m+L/zI2aSyaiVesaQa8paAi2c/MauYFNUL/xn/udbfrlxne0vXHYdiLXK1cSKnXnHfFXyh1JW4MURzacWWS8vNx66I3/6f+oklft04RO0QZWwWorgJGpXC+K55GO9nYyWc/8v6hzjtW15d8JJ8UEgHJXb9pE5G3kuCrjhlmr8lZeonia1cVlhLqFhW2V0KqHJb6OFRsfCST0Hp7fim0hsLwYSPWbwxKT+PIi1Pv76sPK19YCA1OzAgPMIxft8RDiqCiUQSbUx0rpJIjjkt7DCfgcO6B8/IG6eB76i0mg64OQqofDiTna5TbMzBPC67sdNFtdFLhQvvSHLf7gCzmyPlNwg+mLhmpeuU71kxrl2sqozTvvmmfBGG7HX02z/M1AkRXE3qlFOLUgQnQUA709Zthctifq7vublQ6eeKq5tLx36ugbu5mO+nVYKf5hVu2on4afDipuWOnyZIftpSuWkU5qd1m5tmen7aG7ppOOmnBaifpqxu2uT6aVzsppW0n+bebtqt+2mA7+dJAjePRPh5tsPNQyp+nrb5XV2qW5Pc8g0oJ88//Z0WFw9yQRAEQRAEQRAEQRAEQRAEQRD0/+iPAAMARZVI2Ng2lE8AAAAASUVORK5CYII=",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Tuesday, January 19, 2016