I recently came across a test case where I needed to open the same URL in two different sessions to simulate two users interacting with the same DOM. I use Seleniumbase to automate my tests, but I couldn’t find an example or documentation to illustrate how I could automate this test case. So I crafted a solution using some built in Python methods and bash scripting.
At its highest level, I’m using subprocess.run()
to open a new test case file in an incognito window. With this little workaround, I was able to have my first window open, run through some steps, open an incognito window, run through some steps on the incognito window, close the incognito window, then go back to the default window and wrap up the test.
Before you dive in, make sure you have Python 3.8+ to use this functionality.
The Hypothetical Test Case
Say that you’d like to use two users to verify if a comment has been added to a forum without reloading a page. The first user opens a URL. The second user opens that same URL, adds a comment, and then closes the window. The first user then waits until the comment has been added. If the comment doesn’t get added, the test will fail. If the commend does get added, the test will pass.
This requires 2 windows operating on the same URL/DOM. To do that, I’ll walk you through some steps that I’ve taken to resolve an issue like this.
1. The Breakdown
I use the Page Object Model, or POM, when developing my automated tests. This means that whenever I can, I break up my functions and CSS selectors into their own pages. You’ll see that segmentation in the code examples below. I’ve included the file name in the code blocks and also the file structure below.
So, in our hypothetical test case we’ll need two windows, one for each user. In order to do that, I’ve found that using Python’s subprocess
module with the .run()
function the best way to do this. The subprocess
module in Python is useful for this situation because it will allowed me to initiate a process using a shell script that, when complete, will allow the original test to wrap up.
Let me break that down a bit more with an example.
The POM file structure I’m using looks something like this:
pages
|- page.py
objects
|- css_selectors.py
all_tests
|- test.py
|- second_user_test.py
With that structure, below is what I’m calling the [page.py](http://page.py)
file would look like for our given hypothetical test case. This is where I define all the functions for the Forum
class that could then get used in all the test cases related to the Forum
page. Keep in mind, this is using some built in Seleniumbase functions from the BaseCase
class which is why I’m importing BaseCase
from Seleniumbase at the top of the file. By inheriting from the BaseCase
, I’m able to use Seleniumbase functions like self.open(URL)
. So, with our hypothetical test case in mind, the below code is a quick example of the function definition I’d use for our test case:
# page.py
import subprocess
from seleniumbase import BaseCase
class Forum(BaseCase):
def two_windows(self):
self.open('https://thisisatest.com')
subprocess.run({cmd}, shell=True)
self.click(verify_comment)
The two_windows
method is the method that my finished test case will eventually call. One of the reasons I use POM when developing my tests is so my functions are separated out from my actual test cases in case I need to use those functions in other tests. It makes my code reusable so I minimize copying and pasting code across the code base.
So the actual test case would look something like:
#test.py
from pages import Forum
def test_verify_comment(self):
Forum.two_windows(self)
The test case is the def test_verify_comment
which is invoking the two_windows
function. Seleniumbase, running on pytest essentially, will pick up that this is a test case because the function begins with test_
and the file name also begins with test
.
Once the two_windows
function is kicked off by a test run it will open the URL, then kick off a subprocess with Python’s subprocess.run()
function, and then finally verify the comment the second user has posted.
2. The {cmd}
in the page.py file
In this code:
# page.py
import subprocess
from seleniumbase import BaseCase
class Forum(BaseCase):
def two_windows(self):
self.open('https://thisisatest.com')
subprocess.run({cmd}, shell=True)
self.click(verify_comment)
the {cmd}
is meant to represent the command that you’d like to use. What I did was use this as a shell command, hence shell=True
. This means that subprocess.run()
will kick off a subprocess and execute the shell command you give it. What I did was create a string that took a path to a new test file and also passed in the --incognito
flag to pytest so that it would open up a new session that represents a second user in an incognito window.
So, we could expand our code to look like this:
# page.py
import subprocess
from seleniumbase import BaseCase
class Forum(BaseCase):
def two_windows(self):
self.open('https://thisisatest.com')
cmd = 'pytest --incognito all_tests/second_user_test.py'
subprocess.run(cmd, shell=True)
self.click(verify_comment)
subprocess.run()
will now take that cmd
and execute it as it would a shell script. This means it will open another test using pytest in an incognito window. It’s the same as you running pytest --incognito all_tests/second_user_test.py
in your terminal or shell. Now, where it says all_tests/second_user_test.py
, you’d place the path to the test where your second user is adding a comment to the forum and closing the second, incognito window. After that executes, the first user in the original window will then verify the comment has populated and the full test run will be complete.
3. Passing in data to subprocess.run()
Let’s say that the requirements for the test case have been updated so that there is a specific string that you have to pass into the command you’re giving subprocess.run()
. This can easily be done using the --data=
flag from Seleniumbase. So our code would now look like:
# page.py
import subprocess
from seleniumbase import BaseCase
class Forum(BaseCase):
def two_windows(self):
self.open('https://thisisatest.com')
my_string = 'Hello World!'
cmd = f"pytest --data='{my_string}' --incognito all_tests/second_user_test.py"
subprocess.run(cmd, shell=True)
self.click(verify_comment)
This is allowing my_string
to be passed into the command that subprocess
is taking in. This is useful if in your incognito window test user test needs to access that data object, which can be done with [self.data](http://self.data)
in your secondary test.
Conclusion
By using Python’s subprocess.run()
to kick off a, well, subprocess - we’re able to open an incognito window to run a secondary test operating on the same DOM/URL as the primary window. Once the secondary test and window have been finished and closed, the primary window and test can be complete as well.
Hopefully this was helpful and if you have any questions, add them below!
More Resources
https://realpython.com/python-subprocess/ https://seleniumbase.io/ https://seleniumbase.io/help_docs/method_summary/ https://www.geeksforgeeks.org/page-object-model-pom/