I'm using Pester with Selenium WebDriver. WebDriver is initialized in 'BeforeAll' block within corresponding 'Describe' block and the resulting instance is assigned to $driver variable. Then, in 'Describe' and 'It' block I call my custom functions that reside in external PowerShell module that is autoloaded with PowerShell. I expect that these functions have access to $driver variable defined in 'BeforeAll' block, but it does not happen and I get the following error message:
RuntimeException: You cannot call a method on a null-valued expression.
Here is the code from Search.Tests.ps1 Pester script:
Describe "Search for something" -Tag something {
BeforeAll {
$driver = New-WebDriver
$driver.Navigate().GoToUrl('http://example.com')
}
AfterAll {
$driver.Close()
$driver.Dispose()
$driver.Quit()
}
Find-WebElement -Selector ('some_selector')
It "Something is found in search results" {
GetTextFrom-WebElement -Selector ('some_selector') | Should Be 'something'
}
}
Find-WebElement and GetTextFrom-WebElement are helper functions that use $driver to get element by CSS and extract element's inner text.
I investigated the issue and found a workaround, but I don't think it's an elegant way to go. The workaround is to redefine $driver in each helper function in the external PowerShell module right after the param block like this:
$driver = $PSCmdlet.GetVariableValue('driver')
This way the functions can see $driver and everything works.
My question: is it possible to do something, so the functions always have access to $driver without a need to redefine driver in each of them?
"I expect that these functions [defined in a PowerShell module] have access to $driver variable defined in 'BeforeAll' block..."
They don't, and you probably shouldn't rely on that behaviour even if they did.
Variables defined in BeforeAll{}
,BeforeEach{}
,Context{}
, and It{}
blocks are not accessible from a module under test when the x.Tests.ps1
file is invoked by Invoke-Pester
(reference). If the x.Tests.ps1
file happens to be invoked directly (ie. by pressing F5 in ISE) then variables defined in BeforeAll{}
are accessible from a module under test. Relying on that behavior precludes that test from running in bigger batches, so should be avoided.
It seems like your custom module expects that $driver
is defined somewhere outside the module and is implicitly accessible from inside the module. That raises the following question: Where did the author of the custom module intend $driver
to be defined? As a script variable in the module? As a global variable? Both of those are pretty awkward public interfaces for a module because it is difficult to control whether the correct value for $driver
is indeed available to the module. If the module does rely on such behavior, I suggest changing the custom module to explicitly accept your $driver
object
(or at least the information required to create that object).
If you cannot change the custom module, you might be able to get by with changing your variable references from $driver
to $global:driver
. You should really try to avoid that though, because using global variables in that way will likely result in any of a variety of problems at some point.