686 lines
45 KiB
PowerShell
686 lines
45 KiB
PowerShell
function Start-WebTests {
|
|
<#
|
|
.SYNOPSIS
|
|
Runs web tests on web-tier servers only.
|
|
|
|
.DESCRIPTION
|
|
Site must be available in IIS. Keeps track of glogal pass/fail and returns a bool
|
|
|
|
.PARAMETER LoginsJson
|
|
Json file containing logins to test with. Pulled from Get-LoginsToTest function.
|
|
|
|
.PARAMETER CoreUrlsJson
|
|
Json file containing urls to test with. Pulled from Get-CoreUrlsToTest function.
|
|
|
|
.PARAMETER SitesToSkip
|
|
A list (array) of sites to skip web tests on. Http/https will be trimmed
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([System.Boolean])]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$LoginsJson,
|
|
[Parameter(Mandatory)]
|
|
[string]$CoreUrlsJson,
|
|
[Parameter(Mandatory = $false)]
|
|
[bool]$SkipAdmin = $false,
|
|
[Parameter(Mandatory = $false)]
|
|
[bool]$SkipAccountCount = $true,
|
|
[Parameter(Mandatory = $false)]
|
|
[bool]$SkipWidgetWarmUp = $true,
|
|
[Parameter(Mandatory = $false)]
|
|
[int]$MaximumSitesToTest = 20,
|
|
[Parameter(Mandatory = $false)]
|
|
[string[]]$SitesToSkip,
|
|
[Parameter(Mandatory = $false)]
|
|
[int]$SiteThreads = 4,
|
|
[Parameter(Mandatory = $false)]
|
|
[int]$WidgetThreads = 8
|
|
)
|
|
begin {
|
|
$previousGlobalVerbosity = $global:VerbosePreference
|
|
# Quiet the logs for now.
|
|
# $global:VerbosePreference = 'Continue'
|
|
}
|
|
process {
|
|
$logLead = (Get-LogLeadName)
|
|
$WebTestBaseName = "Start-WebTests"
|
|
(Reset-WebTestLogFolder -BaseLogFileName $WebTestBaseName)
|
|
$logFilePath = (Get-WebTestLogPath -LogName $WebTestBaseName)
|
|
|
|
# Only run on Web-tier servers.
|
|
if(!(Test-IsWebServer)) {
|
|
"$logLead : Current server is not a Web-tier server. Skipping." | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
return $true
|
|
}
|
|
|
|
Import-Module -Name PsRedis -Global
|
|
|
|
# Variables for setup
|
|
$logins = ($LoginsJson | ConvertFrom-Json)
|
|
$coreurls = ($CoreUrlsJson | ConvertFrom-Json)
|
|
|
|
# Get all of the URLs in the file that are on this web server.
|
|
$siteNames = ((Get-IISSiteList).Bindings.Host).Where({!(Test-IsCollectionNullOrEmpty $logins.$_)}) | Sort-Object -Unique
|
|
$siteNamesFormatted = ($siteNames | Format-Table -AutoSize | Out-String)
|
|
"$logLead : Sites available to test:`n$siteNamesFormatted" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"$logLead : SitesToSkip: $SitesToSkip" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
if(!(Test-IsCollectionNullOrEmpty $SitesToSkip))
|
|
{
|
|
# Trim any leading "http" nonsense, if necessary.
|
|
$SitesToSkip = $SitesToSkip | ForEach-Object {
|
|
try {
|
|
if ($_.StartsWith("http")) {
|
|
$site = $_
|
|
[System.Uri]::new($_).Host
|
|
} else { $_ }
|
|
}
|
|
catch {
|
|
"$logLead : $site could not be parsed as a site to skip. It is being ignored." | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
Resolve-Error
|
|
}
|
|
}
|
|
|
|
$siteNames = $siteNames | Where-Object{$_ -notin $SitesToSkip}
|
|
"$logLead : Site Names after exclusions: $siteNames" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
}
|
|
|
|
# If no sites found that match on this webserver, return.
|
|
if(Test-IsCollectionNullOrEmpty $siteNames) {
|
|
"$logLead : No sites to test on this web server!" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
return $true
|
|
}
|
|
|
|
# Limit the number of sites to test.
|
|
$siteNames = ($siteNames | Get-Random -Count $MaximumSitesToTest)
|
|
$siteNamesFormatted = ($siteNames | Format-Table -AutoSize | Out-String)
|
|
"$logLead : Limiting the number of sites to test to $MaximumSitesToTest. Actual number of sites under test is $($siteNames.Count)." | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"$logLead : Limited Site Names:`n$siteNamesFormatted" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
$allTestResults = @()
|
|
|
|
$StopWatch = [system.diagnostics.StopWatch]::StartNew()
|
|
"$logLead : Starting Web Tests" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
if($SkipAdmin) {
|
|
"$logLead : Skipping Admin Tests" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
}
|
|
if($SkipAccountCount) {
|
|
"$logLead : Skipping Account Count Tests" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
}
|
|
if($SkipWidgetWarmUp) {
|
|
"$logLead : Skipping Site Widget Warm Up" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
}
|
|
|
|
# Output some inputs
|
|
"$logLead : Maximum Sites To Test: $MaximumSitesToTest" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"$logLead : Site Threads: $SiteThreads" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"$logLead : Widget Threads: $WidgetThreads" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
# Iterate over the sites and get Synthetic accounts from $logins
|
|
$siteTestResult = Invoke-Parallel -Objects $siteNames -ReturnObjects -ThreadPerObject -NumThreads $SiteThreads -Arguments @($logins, $coreurls, $SkipAdmin, $SkipAccountCount, $SkipWidgetWarmUp, $WidgetThreads) -script {
|
|
Param(
|
|
$siteName,
|
|
$inputArguments
|
|
)
|
|
try {
|
|
|
|
$logLead = "[Invoke-Parallel `$siteNames ($siteName)]"
|
|
|
|
$logFilePath = (Get-WebTestLogPath -BankUrl $siteName)
|
|
|
|
$sb_logins = $inputArguments[0]
|
|
$sb_coreurls = $inputArguments[1]
|
|
$sb_skipAdmin = $inputArguments[2]
|
|
$sb_skipAccountCount = $inputArguments[3]
|
|
$sb_skipWidgetWarmUp = $inputArguments[4]
|
|
$sb_widgetThreads = $inputArguments[5]
|
|
|
|
$siteLogins = @($sb_logins.$siteName)
|
|
|
|
if (Test-IsCollectionNullOrEmpty $siteLogins) {
|
|
"$logLead : No logins to find, can't test the site!" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
$allTestResults += (Test-Should -Result $adminResponse -Predicate ${function:Test-Passthrough} -Widget "Site Logins" -Route "Logins not available" -Login @{UrlSignature = $siteName; Username = "" } $true "There was no corresponding site entry for running this test against. Can not verify if the site is up or down.")
|
|
|
|
continue
|
|
}
|
|
|
|
$siteTestResults = @()
|
|
|
|
if (!$sb_skipWidgetWarmUp) {
|
|
# Iterate over all of the widgets to warm them up.
|
|
$widgetStartResult = Invoke-Parallel -Objects $siteLogins.Widgets -ReturnObjects -ThreadPerObject -NumThreads $sb_widgetThreads -Arguments @($siteLogins.UrlSignature) -script {
|
|
Param(
|
|
$widgetName,
|
|
$inputArguments
|
|
)
|
|
try {
|
|
$logLead = "[Invoke-Parallel `$siteLogins.Widgets ($widgetName)]"
|
|
|
|
$urlSignature = $inputArguments[0]
|
|
$urlSignature = "https://$urlSignature/"
|
|
$status = "unknown"
|
|
|
|
$logFilePath = (Get-WebTestLogPath -BankUrl $urlSignature)
|
|
|
|
"$logLead : Widget Warmup $urlSignature$widgetName" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$result = (Invoke-Endpoint -Baseuri $urlSignature -Uri $widgetName)
|
|
$status = $result.StatusCode
|
|
|
|
if ([string]::IsNullOrWhiteSpace($status)) {
|
|
$status = $result.LastError
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($status)) {
|
|
$status = "Unknown"
|
|
}
|
|
|
|
$resultWidgetObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Site Warmup"
|
|
ReturnValue = $true
|
|
ReturnMessage = "Status code: $status"
|
|
Username = $null
|
|
Url = $urlSignature
|
|
Route = $widgetName
|
|
Widget = "Site Warmup"
|
|
Parameters = $null
|
|
Runtime = $result.TotalRuntime
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
return $resultWidgetObj
|
|
|
|
} catch {
|
|
Write-Warning "$logLead : Failed to properly warm up widgets."
|
|
Write-Warning "$logLead : $_"
|
|
|
|
$resultWidgetObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Site Warmup"
|
|
ReturnValue = $false
|
|
ReturnMessage = $_.Exception.Message
|
|
Username = $null
|
|
Url = $inputArguments[0]
|
|
Route = $widgetName
|
|
Widget = "Site Warmup"
|
|
Parameters = $null
|
|
Runtime = $null
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
|
|
return $resultWidgetObj
|
|
}
|
|
}
|
|
$siteTestResults += $widgetStartResult
|
|
|
|
# Give the machine a break.
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
|
|
# Each site base-url only has one admin site url to test
|
|
# Let's test that one first so we don't loop it when we loop users
|
|
# Cole says these will always be the same for a given FI in the above json
|
|
if (!$sb_skipAdmin) {
|
|
$adminSignature = ($siteLogins | Select-Object -First 1).AdminUrlSignature
|
|
|
|
# Test admin variant of URL.
|
|
$fakeAdminLogin = @{UrlSignature = $adminSignature; Username = "" } # This is for logging purposes only
|
|
|
|
$bankAdminUrl = "https://$($adminSignature)"
|
|
"$logLead : Testing Admin URL : $bankAdminUrl" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$adminResponse = Invoke-Endpoint -Uri $bankAdminUrl
|
|
if ($adminResponse.HasErrors -and !($adminResponse.success)) {
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = $adminResponse.LastError
|
|
Username = $null
|
|
Url = $authUrl
|
|
Route = $null
|
|
Widget = "Admin Site Warmup"
|
|
Parameters = $null
|
|
Runtime = $adminResponse.TotalRuntime
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$siteTestResults += $resultObj
|
|
"$logLead : Error caught for Admin URL: $authUrl. Skipping tests!" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
"$logLead : Exception: $($adminResponse.LastError)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
}
|
|
|
|
if ($adminResponse.Success) {
|
|
"$logLead : Running test Test-HaveStatusCode." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $adminResponse.Result -Predicate ${function:Test-HaveStatusCode} -Widget "Admin" -Route "Admin" -Login $fakeAdminLogin 200)
|
|
$siteTestResults += $testResult
|
|
|
|
"$logLead : Running test Test-HaveResponseHeader." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $adminResponse.Result -Predicate ${function:Test-HaveResponseHeader} -Widget "Admin" -Route "Admin" -Login $fakeAdminLogin 'Content-Type' 'text/html')
|
|
$siteTestResults += $testResult
|
|
|
|
# This is awful, but it's less awful than the rest of the options. TR
|
|
# Could I oneliner it? Yes. Would that be harder to maintain - or even read - in 6 months? Definitely. TR
|
|
$Content = $adminResponse.Result.Content
|
|
$newLine = [System.Environment]::NewLine
|
|
$lines = $Content.Split($newLine)
|
|
$iframe = $lines.Where( { $_ -match "<iframe" -and $_ -match "authFrame" })
|
|
|
|
if (![string]::IsNullOrWhiteSpace($iframe)) {
|
|
$attributes = $iframe.Trim() -split " "
|
|
$adminAuthFrameSrc = $attributes.Where( { $_ -match "src" }).Split("`"")[1]
|
|
Write-Host "$logLead : WARMUP Invoking Admin Auth Url (IPSTS) : $adminAuthFrameSrc"
|
|
$authUrl = $adminAuthFrameSrc
|
|
try {
|
|
$authResponse = Invoke-Endpoint -Uri $authUrl
|
|
} catch {
|
|
Write-Warning "$logLead : WARMUP Failed to GET Admin Auth Url (IPSTS)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($login in $siteLogins) {
|
|
$bankUrl = "https://$($login.UrlSignature)"
|
|
$userName = $login.Username
|
|
$bankId = $login.BankIdentifier
|
|
$rootBankUrl = $login.RootBankUrl # This is because sub-sites like Darden for USF cache things at the USF hostname level
|
|
$userAccountsCount = $login.UserAccountCount
|
|
$masqueradingIdentifier = $login.MasqueradeUser
|
|
$isProdLogin = $login.IsProd
|
|
# Used with the HTML generation below to omit the need for https:// parsing
|
|
# $signature = $login.UrlSignature
|
|
$WebSession = $null
|
|
$successfulAuthentication = $false
|
|
$authResponse = $null
|
|
|
|
"$logLead : bankUrl : $bankUrl" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
"$logLead : userName : $userName" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
"$logLead : bankId : $bankId" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
"$logLead : userAccountsCount : $userAccountsCount" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
|
|
$stoploop = $false
|
|
$retryCount = 0
|
|
$maxRetries = 4
|
|
|
|
$sbStopWatch = [System.Diagnostics.StopWatch]::StartNew()
|
|
do {
|
|
try {
|
|
if ($retryCount -le $maxRetries) {
|
|
# Poke nonce token into Redis for this user and get the token.
|
|
"$logLead : Poking nonce token into Redis for user $userName" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$nonce = (Set-RedisToken -BankUrl $rootBankUrl -UserName $userName)
|
|
|
|
# Send a request to the auth service to complete the auth process.
|
|
$authUrl = "$bankurl/Authentication/Authenticated"
|
|
$body = @{ username = $username; nonce = $nonce; source = 'Orion'; }
|
|
if ([string]::IsNullOrWhiteSpace($masqueradingIdentifier)) {
|
|
if ($isProdLogin) {
|
|
"##teamcity[message text='$bankUrl had a user $username that had a flag set for IsProd but the masquerading user was blank on this record. Check the source file.' status='ERROR']" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"$bankUrl had a user $username that had a flag set for IsProd but the masquerading user was blank on this record. Check the source file." | Tee-OutFile -Append -FilePath $logFilePath | Write-Error
|
|
|
|
$successfulAuthentication = $false # don't try to do anything useful
|
|
$authResponse = @{ Success = $false; } # tell it to not try to log anything on "if ($authResponse.Success)"
|
|
$stoploop = $true # leave the do-while
|
|
break
|
|
} else {
|
|
Write-Host "In a non-prod environment; Setting the Masquerading ID to the Bank Id."
|
|
$masqueradingIdentifier = $bankId
|
|
$body.masqueradingIdentifier = $masqueradingIdentifier
|
|
}
|
|
# it's ok if non-prod tests don't show masquerading users. If they do, cool. If they don't, meh.
|
|
# it matters in prod. SRE-16856
|
|
} else {
|
|
$body.masqueradingIdentifier = $masqueradingIdentifier
|
|
}
|
|
"$logLead : Sending request to authUrl : $authUrl" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$authResponse = (Invoke-Endpoint -Uri $authUrl -UseNewSession $true -Method 'Post' -Body $body) <# Set to use 'Post' per DEV-116571 #>
|
|
$WebSession = $authResponse.WebSession
|
|
|
|
if ($authResponse.Success) {
|
|
"$logLead : Testing if we authenticated successfully..." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
"$logLead : Running test Test-HaveStatusCode." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $authResponse.Result -Predicate ${function:Test-HaveStatusCode} -Widget "Authentication" -Route "Authenticated" -Login $login 200)
|
|
$successfulAuthentication = $testResult.ReturnValue
|
|
$siteTestResults += $testResult
|
|
|
|
"$logLead : Running test Test-HaveResponseHeader." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $authResponse.Result -Predicate ${function:Test-HaveResponseHeader} -Widget "Authentication" -Route "Authenticated" -Login $login 'Content-Type' 'text/html')
|
|
$siteTestResults += $testResult
|
|
|
|
# Test if we landed on /Authentication/ page after attempting to login.
|
|
if ($authResponse.Result.BaseResponse.ResponseUri.AbsolutePath -match "/Authentication") {
|
|
$successfulAuthentication = $false
|
|
|
|
"$logLead : Didn't authenticate for Auth URL: $authUrl. Retrying again in 30 seconds." | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
Start-Sleep -Seconds 3
|
|
$retryCount = $retryCount + 1
|
|
} else {
|
|
$stoploop = $true
|
|
}
|
|
} else {
|
|
"$logLead : Didn't authenticate for Auth URL: $authUrl. Retrying again in 30 seconds." | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
Start-Sleep -Seconds 10
|
|
$retryCount = $retryCount + 1
|
|
}
|
|
} else {
|
|
$sbStopWatch.Stop()
|
|
# Break out because we tried 8 times without success.
|
|
$stoploop = $true
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = "Authentication failed after $maxRetries retries."
|
|
Username = $userName
|
|
Url = $login.UrlSignature
|
|
Route = "Authenticated"
|
|
Widget = "Authentication"
|
|
Parameters = $null
|
|
Runtime = [System.TimeSpan]::FromMilliseconds($StopWatch.ElapsedMilliseconds).ToString()
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$siteTestResults += $resultObj
|
|
}
|
|
} catch {
|
|
"$logLead : Exception for Auth URL: $authUrl. Exception:`n$($_.Exception.Message)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
Start-Sleep -Seconds 10
|
|
$retryCount = $retryCount + 1
|
|
}
|
|
} while ($stoploop -eq $false)
|
|
# ensure it got stopped
|
|
$sbStopWatch.Stop()
|
|
|
|
# Test final auth attempt.
|
|
if (!$successfulAuthentication) {
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = "Authentication failed! We landed back on login."
|
|
Username = $userName
|
|
Url = $login.UrlSignature
|
|
Route = "Authenticated"
|
|
Widget = "Authentication"
|
|
Parameters = $null
|
|
Runtime = [System.TimeSpan]::FromMilliseconds($sbStopWatch.ElapsedMilliseconds).ToString()
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$siteTestResults += $resultObj
|
|
}
|
|
|
|
# Test if we authenticated.
|
|
if ($authResponse.Success) {
|
|
# Test if we landed on /AtLogin/ page
|
|
if ($authResponse.Result.BaseResponse.ResponseUri.AbsolutePath -match "/AtLogin") {
|
|
$successfulAuthentication = $false
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = "Authentication failed! We landed on AtLogin. Has user logged in before? Is this a faux-synthetic?"
|
|
Username = $userName
|
|
Url = $login.UrlSignature
|
|
Route = "Authenticated"
|
|
Widget = "AtLogin"
|
|
Parameters = $null
|
|
Runtime = [System.TimeSpan]::FromMilliseconds($sbStopWatch.ElapsedMilliseconds).ToString()
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$siteTestResults += $resultObj
|
|
}
|
|
|
|
if ($successfulAuthentication) {
|
|
"$logLead : Testing widgets." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
|
|
# Since webSession and Cookie collection can't be serialized going into the job, we must create a simple
|
|
# array with HashTable to hold the data and rebuild it inside the Invoke-Parallel.
|
|
$session = $WebSession
|
|
$cookies = @()
|
|
foreach ($cookie in $session.Cookies.GetCookies($authUrl)) {
|
|
$cookies += @{Name = $cookie.Name; Value = $cookie.Value; Domain = $cookie.Domain }
|
|
}
|
|
# Loop over the actual widget URL endpoints. Only the URLs where the widget name matches to the login widget.
|
|
$widgetUrlResult = Invoke-Parallel -Objects $login.Widgets -ReturnObjects -ThreadPerObject -NumThreads $sb_widgetThreads -Arguments @($sb_coreUrls, $login, $cookies, $bankUrl, $userAccountsCount, $userName, $sb_skipAccountCount) -script {
|
|
Param(
|
|
$widget,
|
|
$inputArguments
|
|
)
|
|
|
|
try {
|
|
$logLead = "[Invoke-Parallel `$login.Widgets ($widget)]"
|
|
|
|
$widgetResults = @()
|
|
|
|
$sb_coreUrls = $inputArguments[0]
|
|
$sb_widgetUrls = @()
|
|
if ($null -ne $sb_coreUrls.$widget) {
|
|
$sb_widgetUrls = @($sb_coreUrls.$widget)
|
|
}
|
|
$sb1_login = $inputArguments[1]
|
|
$sb1_WebSessionCookies = $inputArguments[2]
|
|
$sb1_bankUrl = $inputArguments[3]
|
|
$sb1_userAccountsCount = $inputArguments[4]
|
|
$sb1_userName = $inputArguments[5]
|
|
$sb1_skipAccountCount = $inputArguments[6]
|
|
|
|
# Create a new session with the cookies array passed in.
|
|
$sb_WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
|
foreach ($cookie in $sb1_WebSessionCookies) {
|
|
# Create a new cookie with the data passed in.
|
|
$newCookie = New-Object System.Net.Cookie
|
|
$newCookie.Name = $cookie.Name
|
|
$newCookie.Value = $cookie.Value
|
|
$newCookie.Domain = $cookie.Domain
|
|
$sb_WebSession.Cookies.Add($newCookie)
|
|
}
|
|
|
|
if (Test-IsCollectionNullOrEmpty $sb_widgetUrls) {
|
|
# We intentionally don't have anything to test here.
|
|
# This is okay.
|
|
# Also note the very special logging parameter because we don't have anything to do, so put this in the parent/site log
|
|
$logFilePath = (Get-WebTestLogPath -BankUrl $sb1_bankUrl)
|
|
"$logLead : Nothing to test for $widget. This is cool." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
continue
|
|
}
|
|
|
|
$logFilePath = (Get-WebTestLogPath -BankUrl $sb1_bankUrl -Widget $widget)
|
|
"$logLead : Testing widget [$widget] routes [$($sb_widgetUrls -join ',')]" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
|
|
foreach ($url in $sb_widgetUrls) {
|
|
$sbStopWatch2 = [System.Diagnostics.StopWatch]::StartNew()
|
|
if ([string]::IsNullOrWhiteSpace($url)) {
|
|
"$logLead : Found an empty url string for $widget. This indicates that either there was an empty string in the coreurls json array for this widget or something was misread. Continuing to the next route." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
continue
|
|
}
|
|
|
|
"$logLead : Testing URL : $sb1_bankUrl$url" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$result = (Invoke-Endpoint -Baseuri $sb1_bankUrl -Uri $url -WebSession $sb_WebSession)
|
|
if ($result.HasErrors -and !($result.success)) {
|
|
$sbStopWatch2.Stop()
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = $result.LastError
|
|
Username = $sb1_userName
|
|
Url = $sb1_bankUrl
|
|
Route = $url
|
|
Widget = $widget
|
|
Parameters = $null
|
|
Runtime = $result.TotalRuntime
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$widgetResults += $resultObj
|
|
"$logLead : Could not test $sb1_bankUrl$url for $widget - Is the widget installed?" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
"$logLead : Exception: $($result.LastError)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Warning
|
|
}
|
|
|
|
if ($result.Success) {
|
|
$sbStopWatch2.Stop()
|
|
$runtime = $sbStopWatch2.Elapsed.ToString("ss\.fffffff")
|
|
$status = $result.StatusCode
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $true
|
|
ReturnMessage = "Status code: $status"
|
|
Username = $sb1_userName
|
|
Url = $sb1_bankUrl
|
|
Route = $url
|
|
Widget = $widget
|
|
Parameters = $null
|
|
Runtime = $runtime
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
$widgetResults += $resultObj
|
|
# Only check the account count if the path is MyAccountsV2.
|
|
if (!$sb1_skipAccountCount -and $url -eq "/MyAccountsV2/") {
|
|
"$logLead : Url is /MyAccountsV2/, Running test Test-HaveAccountCount." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $result.Result -Predicate ${function:Test-HaveAccountCount} -Widget $widget -Route $url -Login $sb1_login $sb1_userAccountsCount)
|
|
$widgetResults += $testResult
|
|
}
|
|
|
|
"$logLead : Running test Test-HaveStatusCode." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $result.Result -Predicate ${function:Test-HaveStatusCode} -Widget $widget -Route $url -Login $sb1_login 200)
|
|
$widgetResults += $testResult
|
|
|
|
# Commented out for now. Sub-paths may return JSON, so this test fails.
|
|
#$testResult = (Test-Should -Result $result -Predicate ${function:Test-HaveResponseHeader} 'Content-Type' 'text/html;')
|
|
#$siteTestResults += $testResult
|
|
|
|
# If the URL ends with a '/', run the test.
|
|
if (($url -split '/')[-1].Length -eq 0) {
|
|
"$logLead : Running test Test-HaveContentThatMatches." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose
|
|
$testResult = (Test-Should -Result $result.Result -Predicate ${function:Test-HaveContentThatMatches} -Widget $widget -Route $url -Login $sb1_login $url)
|
|
$widgetResults += $testResult
|
|
}
|
|
}
|
|
}
|
|
|
|
return $widgetResults
|
|
} catch {
|
|
Write-Warning "$logLead : Exception occurred when testing widgets."
|
|
Write-Warning "$logLead : $_"
|
|
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = $_.Exception.Message
|
|
Username = $sb1_userName
|
|
Url = $sb1_bankUrl
|
|
Route = $null
|
|
Widget = $widget
|
|
Parameters = $null
|
|
Runtime = $null
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
|
|
$widgetResults += $resultObj
|
|
return $widgetResults
|
|
}
|
|
}
|
|
$siteTestResults += $widgetUrlResult
|
|
}
|
|
}
|
|
}
|
|
|
|
# foreach site result, write the aggregate times out by the widget
|
|
$hash = @{}
|
|
$maxKeyLength = 0
|
|
foreach ($result in $siteTestResults) {
|
|
# Can't calculate runtimes if you don't have them
|
|
if ([string]::IsNullOrWhiteSpace($result.Runtime)) {
|
|
continue
|
|
}
|
|
try {
|
|
$widgetName = $result.Widget
|
|
$runtime = $result.Runtime
|
|
$runtimeSplit = $runtime.Split(':')
|
|
switch ($runtimeSplit.Length) {
|
|
3 { $runtime = $runtime; break; }
|
|
2 { $runtime = "00:$runtime"; break; }
|
|
1 { $runtime = "00:00:$runtime"; break; }
|
|
}
|
|
$runtime = [System.TimeSpan]::Parse($runtime)
|
|
if ($null -eq $hash.$widgetName) {
|
|
$hash.$widgetName = [System.TimeSpan]::new(0)
|
|
}
|
|
$hash.$widgetName += $runtime
|
|
if ($widgetName.Length -gt $maxKeyLength) {
|
|
$maxKeyLength = $widgetName.Length
|
|
}
|
|
} catch {
|
|
Write-Warning "$logLead : Couldn't parse the runtime [$($result.Runtime)] for widget [$($result.Widget)]"
|
|
Resolve-Error
|
|
}
|
|
}
|
|
|
|
$maxKeyLength += 2
|
|
$siteUrl = $siteTestResults[0].Url
|
|
|
|
"$logLead : Test runtime results for $siteUrl" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
foreach ($key in $hash.Keys) {
|
|
"$siteUrl - $($key.PadLeft($maxKeyLength)) - $($hash.$key)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
}
|
|
|
|
return $siteTestResults
|
|
} catch {
|
|
Write-Warning "$logLead : Exception occurred while running tests."
|
|
Write-Warning "$logLead : $_"
|
|
|
|
$resultObj = New-Object PSObject -Property $([ordered]@{
|
|
FunctionName = "Invoke-Endpoint"
|
|
ReturnValue = $false
|
|
ReturnMessage = $_.Exception.Message
|
|
Username = $sb1_userName
|
|
Url = $sb1_bankUrl
|
|
Route = $null
|
|
Widget = $widget
|
|
Parameters = $null
|
|
Runtime = $null
|
|
MachineName = (Get-FullyQualifiedServerName)
|
|
})
|
|
|
|
$siteTestResults += $resultObj
|
|
return $siteTestResults
|
|
}
|
|
}
|
|
|
|
$allTestResults += @($siteTestResult)
|
|
|
|
# Log all test results at once for a pretty table
|
|
$resultMessage = (Format-Table -AutoSize -Property @(
|
|
@{Label = "Should Test"; Expression = {$_.FunctionName.Replace("Test-","")}; Alignment = "Left"},
|
|
@{Label = "Passed?"; Expression = {$_.ReturnValue}; Alignment = "Left"},
|
|
@{Label = "Url"; Expression = {$_.Url}; Alignment = "Left"},
|
|
@{Label = "Route"; Expression = {$_.Route}; Alignment = "Left"},
|
|
@{Label = "Result Message"; Expression = {$_.ReturnMessage}; Alignment = "Left";}
|
|
@{Label = "Runtime"; Expression = {$_.Runtime}; Alignment = "Left";}
|
|
) -InputObject $allTestResults) | Out-String -Width 300
|
|
"`n" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
$resultMessage.Trim() | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
"`n" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
<#
|
|
Commented out but keeping so we can bring this work into a next cycle.
|
|
Per discussion with Brent, Justin, and Cole
|
|
The two long javascript and css strings should be put into a ride-along file that we get-content and use inline below for maintainability.
|
|
This was demoable but not usable from the agent. Future work would be to generate this on the agent along with parsing all the results
|
|
On the agent so we can do BuildProblems on the failing lines as well.
|
|
|
|
$htmlParams = @{
|
|
Title = "Start-WebTests :: All Results"
|
|
PreContent = "<h2>All Results</h2><style>tr.error {background-color: #fdd;} table {border: 3px solid #000000;width: 100%;text-align: left;border-collapse: collapse;}table td, table th {border: 1px solid #000000;padding: 5px 4px;}table tbody td {font-size: 13px;}table thead {background: #CFCFCF;background: -moz-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);background: -webkit-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);border-bottom: 3px solid #000000;}table thead th {font-size: 15px;font-weight: bold;color: #000000;text-align: left;}table tfoot {font-size: 14px;font-weight: bold;color: #000000;border-top: 3px solid #000000;}table tfoot td {font-size: 14px;}th.sorted.ascending:after {content: ' \2191';}th.sorted.descending:after {content: ' \2193';}</style><script src='http://code.jquery.com/jquery-latest.min.js'></script><script>!function (t) { t.tablesort = function (e, s) { var i = this; this.`$table = e, this.`$thead = this.`$table.find('thead'), this.settings = t.extend({}, t.tablesort.defaults, s), this.`$sortCells = this.`$thead.length > 0 ? this.`$thead.find('th:not(.no-sort)') : this.`$table.find('th:not(.no-sort)'), this.`$sortCells.on('click.tablesort', function () { i.sort(t(this)) }), this.index = null, this.`$th = null, this.direction = null }, t.tablesort.prototype = { sort: function (e, s) { var i = new Date, n = this, o = this.`$table, l = o.find('tbody').length > 0 ? o.find('tbody') : o, a = l.find('tr').has('td, th'), r = a.find(':nth-child(' + (e.index() + 1) + ')').filter('td, th'), d = e.data().sortBy, h = [], c = r.map(function (s, i) { return d ? 'function' == typeof d ? d(t(e), t(i), n) : d : null != t(this).data().sortValue ? t(this).data().sortValue : t(this).text() }); 0 !== c.length && (this.index !== e.index() ? (this.direction = 'asc', this.index = e.index()) : 'asc' !== s && 'desc' !== s ? this.direction = 'asc' === this.direction ? 'desc' : 'asc' : this.direction = s, s = 'asc' == this.direction ? 1 : -1, n.`$table.trigger('tablesort:start', [n]), n.log('Sorting by ' + this.index + ' ' + this.direction), n.`$table.css('display'), setTimeout(function () { n.`$sortCells.removeClass(n.settings.asc + ' ' + n.settings.desc); for (var o = 0, d = c.length; o < d; o++)h.push({ index: o, cell: r[o], row: a[o], value: c[o] }); h.sort(function (t, e) { return n.settings.compare(t.value, e.value) * s }), t.each(h, function (t, e) { l.append(e.row) }), e.addClass(n.settings[n.direction]), n.log('Sort finished in ' + ((new Date).getTime() - i.getTime()) + 'ms'), n.`$table.trigger('tablesort:complete', [n]), n.`$table.css('display') }, c.length > 2e3 ? 200 : 10)) }, log: function (e) { (t.tablesort.DEBUG || this.settings.debug) && console && console.log && console.log('[tablesort] ' + e) }, destroy: function () { return this.`$sortCells.off('click.tablesort'), this.`$table.data('tablesort', null), null } }, t.tablesort.DEBUG = !1, t.tablesort.defaults = { debug: t.tablesort.DEBUG, asc: 'sorted ascending', desc: 'sorted descending', compare: function (t, e) { return t > e ? 1 : t < e ? -1 : 0 } }, t.fn.tablesort = function (e) { var s, i; return this.each(function () { s = t(this), i = s.data('tablesort'), i && i.destroy(), s.data('tablesort', new t.tablesort(s, e)) }) } }(window.Zepto || window.jQuery);</script>"
|
|
PostContent = "Generated $([System.DateTime]::Now.ToString())<script>let tableEl = `$('table'); if (!(`$('thead').length)) { tableEl.prepend('<thead>'); let firstTr = tableEl.children('tbody').children('tr').first().detach().appendTo('thead'); } `$('td:contains(`"False`")').parent('tr').addClass('error'); tableEl.tablesort();</script>"
|
|
}
|
|
$allTestResults | ConvertTo-Html @htmlParams | Out-File .\all.html
|
|
#>
|
|
|
|
$passedAllTests = $allTestResults.Where({ $_.returnValue -eq $false }).Count -eq 0
|
|
"$logLead : Passed all tests? $passedAllTests" | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
$StopWatch.Stop()
|
|
$totalHours = $StopWatch.Elapsed.Hours
|
|
$totalMinutes = $StopWatch.Elapsed.Minutes
|
|
$totalSecs = $StopWatch.Elapsed.Seconds
|
|
|
|
"$logLead : Web tests ran for a total of $totalHours hours, $totalMinutes minutes, $totalSecs seconds." | Tee-OutFile -Append -FilePath $logFilePath | Write-Host
|
|
|
|
return $passedAllTests
|
|
}
|
|
end {
|
|
$global:VerbosePreference = $previousGlobalVerbosity
|
|
}
|
|
} |