1

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?

YMM
  • 632
  • 1
  • 10
  • 21
  • Cannot figure out how InModuleScope can help here. Helper functions are exported, they are not private. – YMM Sep 03 '16 at 11:57
  • You should be able to define `$driver` outside the `Describe` block to change its scope. – Eris Sep 03 '16 at 16:52
  • But I need to initialize driver as part of the test setup procedure, that's why it's in 'BeforeAll' block. Maybe there is a way to change its scope while leaving initialization in 'BeforeAll'? – YMM Sep 03 '16 at 16:54
  • Yes, `$driver = $null` outside, then initialize it. I also recommend `Set-Strictmode -Version latest` to help avoid undefined variables. – Eris Sep 03 '16 at 16:57

1 Answers1

2

"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 Pester Scriptblocks Aren't Accessible from Modules

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.

Reliance of Implicit Accessibility of External Variables 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.

alx9r
  • 3,675
  • 4
  • 26
  • 55
  • Thanks for such a detailed explanation! Changing $driver to $global:driver did not work, the functions in the module still cannot access variable value. But I will be passing this $driver variable in a function call — this works. – YMM Sep 05 '16 at 23:55