I am new to Karate UI automation, stuck with an issue and need advice on how to proceed.
I have a test scenario which is a 3 step process
User A logs in and fills up a form
User B logs in with a different browser and approves the application
User A(already logged in) in step 1 can view that his application is accepted
The e2e test snippet is as follows:
Scenario: E2E - User agreement acceptance
#1 USER A logs in and fills agreement form
When def userA_create_agreement = call read('./../Features/CreateAgreement.feature') { shared_agrname: '#(SharedAgreementName)' }
#2 USER B retrieve above agmt and approves it
When def userB_read_agreement = call read('./../Features/ReadAndApprove.feature') { shared_agrname: '#(SharedAgreementName)' }
#3 USER A confirms acceptance
When def userA_confirm_acceptance = call read('./../Features/ConfirmAcceptance.feature') { shared_agrname: '#(SharedAgreementName)' }
Each of the 'called' feature files - CreateAgreement.feature, ReadAndApprove.feature, ConfirmAcceptance.feature have the following background section
Background:
* configure driver = { type: '#(drivertype)', executable: '#(driverpath)'}
Scenario:
Given driver agreementmanager_url
.....
.....
For step 1 and 3 the drivertype and driverpath is chrome
and for step 2 its firefox. User A and B cannot be logged in at the same time with the same browser. This is by design.
With the above way currently, each of the steps invokes a new browser instance and runs the tests rightly so because I am invoking the driver every time in each of the called feature files. What I am looking for is to be able to switch between the browsers and continue running without needing to invoke a new instance in each feature file if its not needed.
So
Step 1 - User A logs in and registers using Chrome
Step 2 - User B logs in and approves using Firefox
Step 3 - Switches back to browser instance of step 1 with User A(who is already logged), views the update of acceptance.
I was wondering if this was possible using Karate? I read the documentation and we have switchPage() that switches between tabs of the same browser but is there a way we can switch browers without having the need to relaunch a new instance and login again?
Any advice or help in how to achieve this will be really helpful.
I would simplify your scenario to not have 2 browsers open at the same time. You can try this sequence that should switch from Chrome to Firefox:
* configure driver = { type: 'chrome' }
* driver 'https://github.com/login'
* driver.quit()
* configure driver = { type: 'geckodriver' }
* driver 'https://google.com'
So the rule is if you quit() you can start a new browser in a flow (0.9.6 onwards).
If you really insist on having Chrome open, maybe you can experiment with the Java API, which gives you more control: https://github.com/intuit/karate/tree/master/karate-core#chrome-java-api
But have you really reached a level of maturity where all the other things you want to test are running trouble free :)
Anyway, to simulate the "logged in" context from the first browser - all you need to do is pick up the cookie values and recreate the cookies later in the test. That is what I would recommend as your test strategy instead of juggling browsers. I would also get that flow to work first before even thinking of switching from Chrome to FireFox, for example I'm not sure if you can even get that to work in Docker or CI. Or are you using a Selenium grid ?
Related
I have a scenario which is a series of rest api calls but in the middle is a section that executes a few steps within a chrome browser. The browser steps are common to another scenario so I tried to extract the browser steps into a separate feature that could then be called from multiple scenarios.
When the main scenario executes it executes the browser feature but fails to auto-close the browser after execution. I read in the documentation "Karate will close the browser automatically after a Scenario unless the driver instance was created before entering the Scenario" . The configure driver code is in the callable scenario.
I also tried caling quit() but this resulted in the error: "The forked VM terminated without properly saying goodbye. VM crash or System.exit called?"
Does anyone know how I can ensure the browser closes in this circumstance?
UPDATE: As suggested by #PeterThomas I started to craft a full example to replicate this when I discovered that to replicate is actually quite simple.
If the UI feature is called like this then the browser is closed after execution:
* call read('classpath:/ui/callable/GoogleSearch.feature')
If called like this then the browser remains open:
* def result = call read('classpath:/ui/callable/GoogleSearch.feature')
My UI scenario scrapes a value from a web page which I then stored within a '* def ticket' within the called feature. I was hoping to access it via result.ticket. As I am unable to do this I am successfully using the following:
* def extractedTicket = { value: '' }
* call read('classpath:/ui/callable/GoogleSearch.feature')
* def ticket = extractedTicket.value
And within the called feature:
* set extractedTicket.value = karate.extract(val, '.ticket=(.*?)&', 1)
First, I think you should provide a way to replicate this, so that we can investigate and fix this for everyone. Please follow this process: https://github.com/karatelabs/karate/wiki/How-to-Submit-an-Issue
That said, maybe for just getting Chrome to do a few steps you should just use the Java API - and you can call it from wherever you want, even within a feature file using Java interop: https://github.com/karatelabs/karate#java-api
Also see if this answer gives you any pointers: https://stackoverflow.com/a/60387907/143475
For some unknown reasons ,my browser open test pages of my remote server very slowly. So I am thinking if I can reconnect to the browser after quitting the script but don't execute webdriver.quit() this will leave the browser opened. It is probably kind of HOOK or webdriver handle.
I have looked up the selenium API doc but didn't find any function.
I'm using Chrome 62,x64,windows 7,selenium 3.8.0.
I'll be very appreciated whether the question can be solved or not.
No, you can't reconnect to the previous Web Browsing Session after you quit the script. Even if you are able to extract the Session ID, Cookies and other session attributes from the previous Browsing Context still you won't be able to pass those attributes as a HOOK to the WebDriver.
A cleaner way would be to call webdriver.quit() and then span a new Browsing Context.
Deep Dive
There had been a lot of discussions and attempts around to reconnect WebDriver to an existing running Browsing Context. In the discussion Allow webdriver to attach to a running browser Simon Stewart [Creator WebDriver] clearly mentioned:
Reconnecting to an existing Browsing Context is a browser specific feature, hence can't be implemented in a generic way.
With internet-explorer, it's possible to iterate over the open windows in the OS and find the right IE process to attach to.
firefox and google-chrome needs to be started in a specific mode and configuration, which effectively means that just
attaching to a running instance isn't technically possible.
tl; dr
webdriver.firefox.useExisting not implemented
Yes, that's actually quite easy to do.
A selenium <-> webdriver session is represented by a connection url and session_id, you just reconnect to an existing one.
Disclaimer - the approach is using selenium internal properties ("private", in a way), which may change in new releases; you'd better not use it for production code; it's better not to be used against remote SE (yours hub, or provider like BrowserStack/Sauce Labs), because of a caveat/resource drainage explained at the end.
When a webdriver instance is initiated, you need to get the before-mentioned properties; sample:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.google.com/')
# now Google is opened, the browser is fully functional; print the two properties
# command_executor._url (it's "private", not for a direct usage), and session_id
print(f'driver.command_executor._url: {driver.command_executor._url}')
print(f'driver.session_id: {driver.session_id}')
With those two properties now known, another instance can connect; the "trick" is to initiate a Remote driver, and provide the _url above - thus it will connect to that running selenium process:
driver2 = webdriver.Remote(command_executor=the_known_url)
# when the started selenium is a local one, the url is in the form 'http://127.0.0.1:62526'
When that is ran, you'll see a new browser window being opened.
That's because upon initiating the driver, the selenium library automatically starts a new session for it - and now you have 1 webdriver process with 2 sessions (browsers instances).
If you navigate to an url, you'll see it is executed on that new browser instance, not the one that's left from the previous start - which is not the desired behavior.
At this point, two things need to be done - a) close the current SE session ("the new one"), and b) switch this instance to the previous session:
if driver2.session_id != the_known_session_id: # this is pretty much guaranteed to be the case
driver2.close() # this closes the session's window - it is currently the only one, thus the session itself will be auto-killed, yet:
driver2.quit() # for remote connections (like ours), this deletes the session, but does not stop the SE server
# take the session that's already running
driver2.session_id = the_known_session_id
# do something with the now hijacked session:
driver.get('https://www.bing.com/')
And, that is it - you're now connected to the previous/already existing session, with all its properties (cookies, LocalStorage, etc).
By the way, you do not have to provide desired_capabilities when initiating the new remote driver - those are stored and inherited from the existing session you took over.
Caveat - having a SE process running can lead to some resource drainage in the system.
Whenever one is started and then not closed - like in the first piece of the code - it will stay there until you manually kill it. By this I mean - in Windows for example - you'll see a "chromedriver.exe" process, that you have to terminate manually once you are done with it. It cannot be closed by a driver that has connected to it as to a remote selenium process.
The reason - whenever you initiate a local browser instance, and then call its quit() method, it has 2 parts in it - the first one is to delete the session from the Selenium instance (what's done in the second code piece up there), and the other is to stop the local service (the chrome/geckodriver) - which generally works ok.
The thing is, for Remote sessions the second piece is missing - your local machine cannot control a remote process, that's the work of that remote's hub. So that 2nd part is literally a pass python statement - a no-op.
If you start too many selenium services on a remote hub, and don't have a control over it - that'll lead to resource drainage from that server. Cloud providers like BrowserStack take measures against this - they are closing services with no activity for the last 60s, etc, yet - this is something you don't want to do.
And as for local SE services - just don't forget to occasionally clean up the OS from orphaned selenium drivers you forgot about :)
OK after mixing various solutions shared on here and tweaking I have this working now as below. Script will use previously left open chrome window if present - the remote connection is perfectly able to kill the browser if needed and code functions just fine.
I would love a way to automate the getting of session_id and url for previous active session without having to write them out to a file during hte previous session for pick up...
This is my first post on here so apologies for breaking any norms
#Set manually - read/write from a file for automation
session_id = "e0137cd71ab49b111f0151c756625d31"
executor_url = "http://localhost:50491"
def attach_to_session(executor_url, session_id):
original_execute = WebDriver.execute
def new_command_execute(self, command, params=None):
if command == "newSession":
# Mock the response
return {'success': 0, 'value': None, 'sessionId': session_id}
else:
return original_execute(self, command, params)
# Patch the function before creating the driver object
WebDriver.execute = new_command_execute
driver = webdriver.Remote(command_executor=executor_url, desired_capabilities={})
driver.session_id = session_id
# Replace the patched function with original function
WebDriver.execute = original_execute
return driver
remote_session = 0
#Try to connect to the last opened session - if failing open new window
try:
driver = attach_to_session(executor_url,session_id)
driver.current_url
print(" Driver has an active window we have connected to it and running here now : ")
print(" Chrome session ID ",session_id)
print(" executor_url",executor_url)
except:
print("No Driver window open - make a new one")
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=myoptions)
session_id = driver.session_id
executor_url = driver.command_executor._url
Without getting into why do you think that leaving an open browser windows will solve the problem of being slow, you don't really need a handle to do that. Just keep running the tests without closing the session or, in other words, without calling driver.quit() as you have mentioned yourself. The question here though framework that comes with its own runner? Like Cucumber?
In any case, you must have some "setup" and "cleanup" code. So what you need to do is to ensure during the "cleanup" phase that the browser is back to its initial state. That means:
Blank page is displayed
Cookies are erased for the session
I am new to Cucumber and have a difficulty which might be an easy one for those who know how. So can anybody help me out? Appreciate very much!
My Feature file is something like this:
Background: User logged in and goes to Consumer Page as an Internal Admin
Given user is logged in as Internal Admin
When user clicks on Consumer
Then the Consumer screen will display
#Consumer
Scenario Outline: New Application - Multiple Applicants: Verify consumer head line verification Message
When user select New Application from Enquiry Type
And user enters "<NoOfApplicant>" in Number of Applicants field
Then user should see correct Consumer "<verification>" messages for each consumer
Examples:
|NoOfApplicant|verification |
|1 |One Consumer |
|2 |Two Consumers|
What I want to achieve is to use one driver session to run background and first scenario, close the driver after first scenario finishes. Open a new driver, run background, run the second scenario and close the driver.
So does anyone know how to implement this?
My current situation is:
Define static driver and close it after hook. the result is the
driver been closed after background is executed, that will cause
first scenario has no driver;
If I change the driver to non-static and close it after hook, then the driver will be closed and a new driver will be created for the first scenario. but not run background again and run the first scenario directly. Because the background hasn't been executed so the driver is not on the page and not yet ready to run the first scenario.
But if I use a static driver and don't close the driver after hook, then the second scenario will use the same driver and run the
background again which will fail as well because the page is already
logged in...
I am a bit hung up here. Can anyone help? Thank you!
If you delete your cookies with selenium, in the majority of cases, you'll be logged out - refreshing the state without closing the browser.
Either that, or in your log in step, check who is logged in, if it's the right user, you have already finished that step, if it's the wrong user, log out and log back in again, and if it's not logged in, just log in.
On another note:
The contents of the Background are always executed before every scenario within the feature file, so that shouldn't be an issue at all - the issue would be where you are closing the browser, which should be in an after hook, and opening the browser, which should be in a before hook.
You should either
Run one scenario twice, using an environment variable to determine which driver to use
OR
Write a separate scenario for each browser and use tags to switch the driver
OR
Add the driver name to your example table and ensure that the driver is loaded from this before any steps that open the browser
The last solution is by far the worst
I would like to run tests with pybot, then run more tests with pybot using the same browser window that the first pybot opened.
So…
pybot test1.txt
#opens browser window and runs test1.txt and doesn't close the window
#pybot completes execution
pybot test2.txt
#uses the same browser window from test1
#pybot completes execution
pybot test3.txt
#uses the same browser window from test1
#pybot completes execution
can't figure out how to do that….
I've tried Open Browser www.mysite.com alias=1 in the first test and then Switch Browser 1 in the others, but they just error with No browser is open
A selenium driver instance has two properties characterizing its connection to a selenium webdriver - a connection url, and session id. By setting those to the values of an already running one, you effectively "hijack" it, and can use freely.
Disclaimer - the solution uses internal SE structures, so can break on newer versions. Also, as you are connecting to the running webdriver as to aRemote one, you cannot close it even if you want to - thus this can lead to resources leakage on the machine that it runs on; e.g. that webdriver has to be eventually terminated manually by a task manager.
So first things first - you have a running browser instance, and you need to get its properties for future connections. They are 2 - driver.command_executor._url, and driver.session_id where driver is the object name of the running instance. This python code will do just that:
from robot.libraries.BuiltIn import BuiltIn
def return_driver_props()
seLib = BuiltIn().get_library_instance('SeleniumLibrary')
# the driver is instantiated in the SeleniumLibrary, but not provided publicly, thus accessing it through this py code
remote_url = seLib.driver.command_executor._url # for local instance, this is a value in the form 'http://localhost:57856'
session_id = seLib.driver.session_id
return remote_url, session_id
Importing that file as a Library, by calling the function/method you'll have the 2 props:
${conn_url} ${session_id}= Return Driver Props
# and do whatever is needed to make them known - log, store in a file, DB, etc.
Now in the second run that needs to reattach, and with the 2 values in hand, you just use the keyword Open Browser and specify a remote connection:
Open Browser about:about remote_url=${that_known_url} browser=${the_used_browser_type} # the last args - chrome|firefox|edge - whatever you're connecting two
The tricky part - the moment you connect to a remote server, selenium automatically starts a new session - which is a second browser instance (this started somewhere around selenium3, though I'm not sure on the exact timing). E.g. if you start using it right now - that is not the browser you wanted, but a brand new. That's also the reason why I gave as target address "about:about" - so it loads a dummy page, very fast.
Two things must happen at this point - a) you have to get rid of the "dummy" SE session, and b) switch to the previous one:
def set_driver_session_id(sesion_id):
""" Sets the sessoin_id of the current driver insance to the provided one. """
seLib = BuiltIn().get_library_instance('SeleniumLibrary')
if seLib.driver.session_id != sesion_id: # this is pretty much guaranteed to be the case
seLib.driver.close() # this closes the session's window
seLib.driver.quit() # for remote connections (like ours), this deletes the session, but doesn't stop the SE
# set to the session that's already running
seLib.driver.session_id = sesion_id
This function/keyword is called with the known session id:
Set Driver Session ID ${session_id}
,and voilà, you are now in control of the previous browser, with its full state - the url it was at, cookies, localStorage, etc.
I'm leaving as an exercise to the reader how to automatically pass the url and the session id.
What I myself am doing is storing them in a file in a temp folder after running the first piece, and reading from there in the follow-up runs, with some error handling around it - missing or bad file, the connection cannot happen, and so on, with fallbacks to new instance creation.
Actually it is absolute possible to do however you have to organise your tests in testsuites
testsuites/
__init__.robot
test1.robot
test2.robot
test3.robot
in __init__.robot you have to open browser in suite setup and destroy it in suite teardown f.e.
*** Settings ***
Suite Setup Open Browser
Suite Teardown Close Browser
and run tests pybot ./testsuites
I don't know how effcient it is but I needed the same process and I used code like this to complete this...
open browser ${HOMEPAGE} ${BROWSER}
#opens the browser to the homepage using the browser of my choice
maximize browser window
#maximizes the window
(DOES STUFF)
#completes some processes
select window title=calendar
#selects the window with the name calendar
(DOES MORE STUFF)
This worked for me, try it out.
If you need any information on 'select window' look here: http://rtomac.github.io/robotframework-selenium2library/doc/Selenium2Library.html#Select%20Window
Or you can always try
switch browser 1
I'm new to robotframework, but I hope this helped.
There's an open issue for new functionality to re-use existing browser session. There are some workaround mentions and custom code modifications that seem to achieve this.
In my case, I was setting off warning emails from the target website, about logging in from a new device, every time I logged in during testing. Instead, I wanted to log in a single time and then resume the same (authenticated) browser session during subsequent runs.
To do this, the key is that you need to point the webdriver to the same user data directory so that subsequent runs can resume the browser session. For my needs, I created a local directory called user-data.
I then defined:
Open Browser Profiled
[Arguments] ${url}
${options}= Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys, selenium.webdriver
Call Method ${options} add_argument --user-data-dir\=${./user-data}
Create WebDriver Chrome chrome_options=${options}
Go To ${url}
And:
Log In
Open Browser Profiled ${LOGIN_URL}
Input Text email ${EMAIL}
Input Password password ${password}
Click Button Log in
Then, on the first execution, I run:
Do The Things
Log In
# Open Browser Profiled ${AFTER_LOGIN_URL}
...
And, on subsequent executions:
Do The Things
# Log In
Open Browser Profiled ${AFTER_LOGIN_URL}
...
For extra points, you can also make this more dynamic and detect whether a log in is needed so that you do not have to modify the script, e.g.:
Do The Things
Log In or Open
...
Log In or Open
Open Browser Profiled ${AFTER_LOGIN_URL}
Sleep 2 # Wait for page to settle
${logged_in}= Run Keyword And Return Status Title Should Be ${AFTER_LOGIN_TITLE}
Run Keyword Unless ${logged_in} Log In
Special thanks to this post, which outlined the method I used to resume the browser session.
I would like to use selenium to test how two or more applications (main, monitoring, management) work together. However all I was able to find is how to test a single application.
Sample scenarios could look like this:
App 1 - user x tries to log in, but has no account and the login fails
App 2 - a user for App 1 is created
App 1 - user is now able to log in
App 1 - user x performs a task
App 2 - displays the performed task
App 1 - user x finishes a task
App 2 - displays the finished task
The applications may be deployed on different servers. the communication is performed over a common database. The applications are not necessarily implemented using the same technology stack.
Selenium is meant to replicate real user behavior.
So if the session does terminate on navigating away from APP 1 when a real user would do it, then the exact same behavior is seen when running these steps via selenium webdriver.
If you still wish to do i, it can be done this way -
#driver1 = Selenium::WebDriver.for(:remote, :url => #sel_grid_url, :desired_capabilities => #browser) #create a browser session controlled by driver1
#driver2 = Selenium::WebDriver.for(:remote, :url => #sel_grid_url, :desired_capabilities => #browser) #create another browser session controlled by driver2
#driver1.get "http://#{app1}/"
## user x tries to log in, but has no account and the login fails
#driver2.get "http://#{app2}"
## a user for App 1 is created
.
.
.
The above code is in Ruby, and has been implemented with Selenium Grid 2 is the middle.
Selenium IDE doesn't allow you to change website during the same test. But you can easily do this with Selenium Webdriver. For example
driver.get("yourFirstApp.com");
//Test your stuff
driver.get("yourSecondApp.com");
//Test your stuff
etc
If you stay in the same testcase like the code bellow
you'll have no trouble with the conversation ID, session etc..
#Test
public void blablalb() {
driver.get(a1);
//code....
driver.get(a2);
//code...
}