0

I seem to have an issue when running a PowerShell script as a part of a step in our deployment Octopus pipeline. The tests run against a few websites, and each website has a range of tests. These are just locally setup websites on our environment.

The website with the most tests runs has about 100 tests, and the smallest website is about 5. The issue can occur with the website with 5 or 100. These are SpecFlow tests.

The results are then exported to an xml file. According to the xml file it does look like it has ran all the tests, as it shows the ones that succeeded and failed. It doesn't seem like this issue always occurs. Although it happens if I leave it on the nightly build. It won't always occur if I trigger a manual build on Octopus.

The version of NUnit is 3.1.1

Here is so far an example line of what I am passing in powershell for the NUnit console.

param(
    [string]$configurationDescription = $OctopusParameters['environment.Transform'] + " Env Test Run", 
    [string]$site,
    [string]$environmentName,
    [string]$environmentSiteTag,
    [string]$testDllName,
    [string]$testDllFolder
    )

$folderPath = $OctopusParameters['tests.rootFolder']

Set-Location $folderPath
$configurationDescription = $configurationDescription.Replace(" ", "-")
$testResultsFolderName = "testResults"
$testResultsPath = "$folderPath\$testResultsFolderName\$site"
$currentTimeFormatted = Get-Date -Format "dd-MM-yyyy_hh_mm_ss"

if(!(Test-Path -Path $testResultsPath))
{
    New-Item -Path $testResultsPath -ItemType Directory
}

$testDllPath = $folderPath + "\tests\$testDllFolder\$testDllName"
$environmentWipTag = $OctopusParameters["tests.environment.wipname"]

#Change the location of "bin\XXX" So it will pick up the respective dll file and use the respective App.XXX.config
$testResultsXmlPath = "$testResultsPath\TestResult_$currentTimeFormatted.xml"
$args = "$testDllPath --where `"cat != $environmentWipTag && (cat == $environmentName && cat == $environmentSiteTag)`" --result=$testResultsXmlPath --framework=net-4.0 --timeout=20000"


# SECTION: Update Chrome driver
# Update Chrome Driver for testing to match the machine.
#1. Get the version of Chrome driver installed.
$chromeDriverOutputPath = $folderPath + "\tests\$testDllFolder"

# Store original preference to revert back later
$OriginalProgressPreference = $ProgressPreference;

# Increases the performance of downloading the ChromeDriver.
$ProgressPreference = 'SilentlyContinue';

try
{
    $chromeVersion = (Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe').'(Default)').VersionInfo.FileVersion
    $chromeVersion = $chromeVersion.Substring(0, $chromeVersion.LastIndexOf("."));
} catch
{
    "Could not find Google Chrome in the registry."
}

#2. Get the latest version of chrome driver available.
$chromeDriverVersion = (Invoke-WebRequest "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$chromeVersion" -UseBasicParsing).Content;
Write-Host "Latest matching version of Chrome Driver is $chromeDriverVersion";

#3.Create a temp folder to store the chrome driver to be downloaded.
$tempFilePath = [System.IO.Path]::GetTempFileName();
$tempZipFilePath = $tempFilePath.Replace(".tmp", ".zip");
Rename-Item -Path $tempFilePath -NewName $tempZipFilePath;
$tempFileUnzipPath = $tempFilePath.Replace(".tmp", "");

# 4. Download correct chrome drive, unzip the archive and move the chromedriver to the correct location.
Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$chromeDriverVersion/chromedriver_win32.zip" -OutFile $tempZipFilePath -UseBasicParsing;
Expand-Archive $tempZipFilePath -DestinationPath $tempFileUnzipPath;
Move-Item "$tempFileUnzipPath/chromedriver.exe" -Destination $chromeDriverOutputPath -Force; 

# 5. Clean up files.
Remove-Item $tempZipFilePath;
Remove-Item $tempFileUnzipPath -Recurse;
# Set back to default ProgressPreference.
$ProgressPreference = $OriginalProgressPreference

#END SECTION: Update Chrome driver
Write-Host "Chrome Driver now matches the version installed on the machine. Beginning Test run."

#Begin Test Run.
$nunitRunnerResult = (Start-Process -FilePath "$folderPath\runner\nunit3-console.exe" -ArgumentList $args -PassThru -Wait).ExitCode

if(!(Test-Path -Path $testResultsXmlPath))
{
    Write-Host "Args:$args FilePath:$folderPath\runner\nunit3-console.exe"
    Write-Error "Unable to find $testResultsXmlPath"
    return;
}

$testsTestResultHtmlPagePath = "$testResultsPath\$configurationDescription-$currentTimeFormatted\ExtentReport"
$args = "-i $testResultsXmlPath -o $testsTestResultHtmlPagePath -r html"

Start-Process -FilePath "$folderPath\extentreport\extent.exe" -ArgumentList $args -Wait

$extentReportPath = $testsTestResultHtmlPagePath + "\index.html"
$extentSummaryPath =  $testsTestResultHtmlPagePath + "\dashboard.html"

$testsTestResultPath = "$testResultsFolderName\testsTestResult-$currentTimeFormatted.xml"
$args = [string]::Format("-f=Features/ -o=$testResultsFolderName/{0}-{1}/PicklesReport -trfmt=nunit3 -df=dhtml -lr={2}", $configurationDescription, $currentTimeFormatted, $testsTestResultPath)


Exit 0

The C# ran after each test.

using BoDi;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
using System.Configuration;
using TechTalk.SpecFlow;

namespace MyTestFramework.Helpers
{
    [Binding]
    public sealed class Hooks
    {
        private readonly IObjectContainer _container;
        private IWebDriver _webDriver = default;

        public Hooks(IObjectContainer objectContainer)
        {
            _container = objectContainer;
        }

        [BeforeScenario]
        public void BeforeTestRun()
        {
            InstantiateDriver();
        }

        [AfterScenario]
        public void AfterScenario()
        {
            if (!(_webDriver is null))
            {
                //Close any additional tabs open and then delete all cookies when one tab is open
                var tabs = _webDriver.WindowHandles;
                int numberOfTabs = _webDriver.WindowHandles.Count;
                while (numberOfTabs > 1)
                {
                    _webDriver.SwitchTo().Window(tabs[numberOfTabs - 1]);
                    _webDriver.Close();
                    numberOfTabs = _webDriver.WindowHandles.Count;
                    _webDriver.SwitchTo().Window(tabs[numberOfTabs - 1]);
                }
                _webDriver.Manage().Cookies.DeleteAllCookies();
                _webDriver.Close();
                _webDriver.Quit();
                _webDriver.Dispose();
            }
        }

        [AfterTestRun]
        public static void After()
        {

        }

        private void InstantiateDriver()
        {
            var selectedDriver = ConfigurationManager.AppSettings.Get("selectedWebDriver");

            if (string.IsNullOrEmpty(selectedDriver))
            {
                throw new ArgumentException(Constants.ExceptionMessages.SelectedWebdriverCouldNotBeFound, selectedDriver);
            }

            switch (selectedDriver)
            {
                case "Chrome":
                    _webDriver = new ChromeDriver();
                    break;

                case "Chrome Headless":
                    var chromeOption = new ChromeOptions();
                    chromeOption.AddArguments("headless");
                    chromeOption.AddArguments("--window-size=1680,1050");
                    _webDriver = new ChromeDriver(chromeOption);
                    break;

                default:
                    throw new ArgumentException("Webdriver could not be found", selectedDriver);
            }
            _webDriver.Manage().Window.Maximize();
            _container.RegisterInstanceAs<IWebDriver>(_webDriver);
        }
    }
}

If I close the chrome driver it must close everything as the Octopus step then seems to fail. Worth noting as well this does not happen if directly using visual studio.

minkz96
  • 21
  • 4
  • Please [edit] your question to include the code that initializes ChromeDriver, and any cleanup code that runs after a test finishes. We cannot answer your question without a [repro]. – Greg Burghardt Mar 30 '22 at 13:45
  • I have updated the question. This is the entire script that is being ran for each site. – minkz96 Mar 30 '22 at 18:21
  • That is good, but we need to see the C# code that initializes chrome driver as well. And the code run after each test. – Greg Burghardt Mar 30 '22 at 18:37
  • Ah right of course ,I have updated the comment. Essentially that hook is ran for Selenium for all the tests and initialises the driver. – minkz96 Mar 30 '22 at 19:02
  • 1
    Your cleanup code looks pretty comprehensive. You might need to kill the chromedriver process explicitly, which I think you can access from the default chrome driver service. Let me do some digging around. – Greg Burghardt Mar 30 '22 at 19:05
  • Maybe try `if (_webDriver != null)` instead of `!(_webDriver is null)`? – Greg Burghardt Mar 30 '22 at 19:07
  • The line `_webDriver.SwitchTo().Window(tabs[numberOfTabs - 1]);` occurs twice in the loop. Is that really needed? – Greg Burghardt Mar 30 '22 at 19:09
  • Do you get errors about ChromeDriver not being initialized, or it times out during initialization? – Greg Burghardt Mar 30 '22 at 19:10
  • Does this only seem to happen when tests fail, or does this also happen even when all tests pass? – Greg Burghardt Mar 30 '22 at 19:12
  • You might need to comment things out of your PowerShell script until you can find the line that hangs. – Greg Burghardt Mar 30 '22 at 19:13
  • Had a go today so, what I do notice is when it does happen and the tests, have completed(Passed or failed) and it's hanging the chrome driver process is still open. If I manually go onto the machine and kill the chrome driver, after a few seconds it seems the powershell script continues as expected(Executes everything after the nunit3-console.exe process), and Octopus proceeds and is not hanging. – minkz96 Mar 31 '22 at 19:19
  • 1
    I had this problem with running builds in Jenkins. I identified that sometimes ChromeDriver would not spawn correctly, and this caused my build to hang. Do you notice the ChromeDriver errors out or times out when it is first initialized? It can leave behind "zombie" processes that must be killed explicitly. – Greg Burghardt Mar 31 '22 at 19:22
  • Hmmm I haven't noticed any errors when initialising, maybe I need to check that but yes I do see the "zombie" chrome processes left behind. – minkz96 Mar 31 '22 at 19:35
  • You shouldn't need to close each window or tab individually. The call to `Quit()` should cause all open tabs and windows to close down. Maybe that's the issue? Try just calling, "Close() --> Quit() --> Dispose()"? – Greg Burghardt Mar 31 '22 at 19:47

1 Answers1

0

Changing -Wait to - WaitProcess worked and has fixed the issue.

$testResults = (Start-Process -FilePath "$folderPath\runner\nunit3-console.exe" -ArgumentList $args -PassThru)

Wait-Process -Id $testResults.Id $testExitCode = $testResults.ExitCode

@Greg Burghardt, you were right about the zombie processes, so I eventually for some reason had a look at the -Wait argument. I noticed there was another PowerShell argument called -WaitProcess. It seems there is a difference in this case.

Unlike Start-Process -Wait, Wait-Process only waits for the processes identified. Start-Process -Wait waits for the process tree (the process and all its descendants) to exit before returning control.

Source: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/wait-process?view=powershell-7.2

I suspect that as these chrome processes were not actually ending sometimes, even though the main process had ended it was waiting for all these other ones to exit. It would explain why I manually had shut down all the processes via Task Manager for it to continue beforehand as it was waiting for the child processes to end.

minkz96
  • 21
  • 4