I would like to be able to restart my browser session mid test using Geb and Spock Framework. I no howto close the browser and update after test compltion etc, but when i close during the test and try and re use the browser object i get a session error thrown by selenium. Below is the base outline i am trying to execute. NB never allows me to navigate to the new StoreHome and if i try and use just Browser i get error thrown.
#Category(High.class)
def "TC1: Verify Browser Restart"() {
when: "On my StoreFront HP wait until title displayed"
to StoreHomePage
waitFor { homepagetitle.displayed }
then: "Update your site picker"
mySitePicker.click()
waitFor { myNewHomePageTitle.displayed }
when: "Close the browser and insure on restart new page is loaded"
browser.close()
browser.quit()
def nb = new Browser()
nb.to(NewStoreHomePage)
then: "Validate on New HP"
asset myNewHomePageTitle.displayed
}
It's as simple as doing the following in your spec:
resetBrowser()
CachingDriverFactory.clearCacheAndQuitDriver()
After that any code that tries to access browser will trigger automatic creation of new WebDriver and Browser instances.
This is how you force a new driver:
CachingDriverFactory.clearCache()
I tested it, it works beautifully. This hint can also be found in the Geb manual.
Update 2017-02-07 15:10 CET: Thanks for the follow-up question. Well, my brief answer was made under the assumption that the command is issued at the end of one feature method and the next feature method starts with a new browser session. In order to do this mid-test you would have to create a new WebDriver instance manually and somehow trick Geb into updating its browser session.
Because this is tricky at least and I do not know how to do it, I recommend using two separate feature methods for testing what should be tested before and after quitting the browser. You can share state between them via #Shared members, if necessary. This also had the advantage that if you let Geb create the new WebDriver and browser session for you, everything configured in GebConfig.groovy, such as browser type and capabilities, will automatically be considered. If you would create a driver manually, you would have to parse the Geb config by yourself - ugly!
But the main problem with this approach is: How to assure that the feature methods are executed in the (lexical) order of declaration? Normally tests should be runnable in any order, so you cannot and should not rely on a specific execution order. Spock offers the Stepwise annotation to adress the rare case in which you want to enforce execution order, but this would lead to the same problem as in the mid-test situation because Geb implicitly assumes that it should continue to test in the same session. I.e. we need a trick to enforce lexical execution order without using #Stepwise.
Another problem is that if your spec extends GebReportingSpec because you want to take screenshots, Geb fails to take the last screenshot at the end of the feature method with the browser gone. Now you can configure Geb not to take screenshots if the test succeeds via reportOnTestFailureOnly, but that still leaves us with failed tests. So I added an override for the report method with some additional exception handling.
The full solution looks like this, derived from one of my real-life tests:
package de.scrum_master.tdd
import geb.driver.CachingDriverFactory
import geb.spock.GebReportingSpec
import org.openqa.selenium.Keys
import org.spockframework.runtime.model.FeatureInfo
import spock.lang.Shared
class SampleGebIT extends GebReportingSpec {
#Override
void report(String label = "") {
// GebReportingSpec tries to write a report (screenshot) at the end of each feature
// method. But because we use 'CachingDriverFactory.clearCacheAndQuitDriver()',
// there is no valid driver instance anymore from which to get a screenshot. Geb is
// unprepared for this kind of error, so we handle it gracefully so as to keep the
// test from failing just because the last screenshot cannot be taken anymore.
try {
super.report(label)
}
catch (Exception e) {
System.err.println("Cannot create screenshot: ${e.message}")
}
}
// We cannot use 'specificationContext' directly from 'setupSpec()' because of this
// compilation error: "Only #Shared and static fields may be accessed from here"
// Okay then, so use we a #Shared field as a workaround. ;-)
#Shared
def currentSpec = specificationContext.currentSpec
def setupSpec() {
// Make sure that feature methods are run in declaration order. Normally we could
// use #Stepwise for this, but because #Stepwise implies staying in the same
// browser session, it would not work in connection with
// 'CachingDriverFactory.clearCacheAndQuitDriver()'. This is the workaround for it.
for (FeatureInfo feature : currentSpec.features)
feature.executionOrder = feature.declarationOrder
}
def "Search web site Scrum-Master.de"() {
setup:
def deactivateAutoComplete =
"document.getElementById('mod_search_searchword')" +
".setAttribute('autocomplete', 'off')"
def regexNumberOfMatches = /Insgesamt wurden ([0-9]+) Ergebnisse gefunden/
when:
go "https://scrum-master.de"
report "welcome page"
then:
$("h2").text().startsWith("Herzlich Willkommen bei Scrum-Master.de")
when:
js.exec(deactivateAutoComplete)
$("form").searchword = "Product Owner" + Keys.ENTER
then:
waitFor { $("form#searchForm") }
when:
report "search results"
def searchResultSummary = $("form#searchForm").$("table.searchintro").text()
def numberOfMatches = (searchResultSummary =~ regexNumberOfMatches)[0][1] as int
then:
numberOfMatches > 0
cleanup:
println "Closing browser and WebDriver"
CachingDriverFactory.clearCacheAndQuitDriver()
}
def "Visit Scrum-Master.de download page"() {
when:
go "https://scrum-master.de/Downloads"
report "download page"
then:
$("h2").text().startsWith("Scrum on a Page")
}
}
BTW, I tested this successfully with several browsers on my Windows 10 machine:
HtmlUnit (with activated JavaScript)
PhantomJS
Chrome
Internet Explorer
Edge
Firefox
Related
I imported an old project from that has a class named "TestGebSpec". The test cases of it were running on the same browser so I added a "CachingDriverFactory.clearCacheAndQuitDriver()" to gebConfig.groovy file. Still, test cases were running on the same browser so I create a new groovy test case file "Login TC"
Now the problem is the test case of Login TC are running on a separate browser i.e for each test case a new driver is initiated but for file "TestGebSpec" somehow TC runs on the same browser
Any suggestions???
Code of "TestGebSpec" file
#Stepwise
#SuppressWarnings(["GrUnresolvedAccess", "GroovyAssignabilityCheck", "UnnecessaryQualifiedReference"])
class KohlerSanityTestGebSpec extends GebReportingSpec {
public static final String USER_EMAIL = "test_user." + UUID.randomUUID() + "#kohler.com"
public static final String USER_PASSWORD = "pass123Word"
#Shared
productAddedToFolder
def setupSpec() {
driver.manage().window().maximize()
}
def setup() {}
def cleanup() {}
def cleanupSpec() {}
//--------------------------------------------------------------
// Utility methods start here.
//--------------------------------------------------------------
/** Using javascript runner to scroll an element into view so selenium can work with it. */
protected void scrollIntoViewJS(NonEmptyNavigator element) {
((JavascriptExecutor) driver).executeScript(
"arguments[0].scrollIntoView(true);",
element.firstElement());
sleep(1000)
}
protected void scrollUp(int distance) {
((JavascriptExecutor) driver).executeScript("scroll( 0, ${-distance});");
sleep(100)
}
protected void scrollDown(int distance) {
((JavascriptExecutor) driver).executeScript("scroll( 0, ${distance});");
sleep(100)
}
protected void hoverTest2(NonEmptyNavigator element) {
((JavascriptExecutor) driver).executeScript(
"arguments[0].trigger(\"hover\");",
element.firstElement());
}
protected void hoverOver(String path) {
org.openqa.selenium.WebElement element =
driver.findElement(org.openqa.selenium.By.cssSelector(path))
hoverOver(element)
}
protected void hoverOver(NonEmptyNavigator element) {
org.openqa.selenium.interactions.Actions builder = new
org.openqa.selenium.interactions.Actions(driver)
builder.moveToElement(element.getElement(0)).build().perform()
}
/** click, when the click() method does not work. */
#SuppressWarnings("GroovyUnusedDeclaration")
protected void clickElement(NonEmptyNavigator element) {
org.openqa.selenium.interactions.Actions builder = new org.openqa.selenium.interactions.Actions(driver)
builder.moveToElement(element.getElement(0)).click().build().perform()
}
protected void moveElement(NonEmptyNavigator handleElement, NonEmptyNavigator trackElement, int xPercent) {
org.openqa.selenium.WebElement handle = handleElement.getElement(0)
org.openqa.selenium.WebElement track = trackElement.getElement(0)
org.openqa.selenium.interactions.Actions builder = new org.openqa.selenium.interactions.Actions(driver)
int width = track.getSize().getWidth()
int moveLength = width * xPercent / 100
builder.dragAndDropBy(handle as org.openqa.selenium.WebElement, moveLength.intValue(), (int) 0).build().perform()
}
protected void closeSurveyPopup() {
if ($("#IPEbgCover")) {
$("area", alt: "close").click()
}
}
//----------------------------------------------------------------
// End of utility methods. Start of feature methods.
//----------------------------------------------------------------
def "Create project in bCC TESTCASE1"() {
browser.baseUrl = "URL"
when: "I log in to the BCC"
to BccLoginPage
loginForm.login = "data"
loginForm.$("#loginPassword").value("data")
$("input", type: "submit").click()
then:
at BccHomePage
when:
newCaProjectButton.click()
then:
at BccNewCaProject01Page
when: "I name and describe the new project."
def newProjectName = "gebtest-" + randomString(16, ('A'..'Z') + ('0'..'9'))
println "Creating BCC project \"$newProjectName\"."
projectNameInput.value(newProjectName)
projectDescriptionInput.value(randomString(50))
createProcessButton.click()
then:
at BccCaProjectDetailsPage
}
}
code of "Login TC" file
class LoginTC extends GebReportingSpec{
def setupSpec() {
driver.manage().window().maximize()
//driver.manage().window().size = new org.openqa.selenium.Dimension( 1200, 1200 )
} // run before the first feature method
def setup() {} // run before every feature method
def cleanup() {} // run after every feature method
def cleanupSpec() {} // run after the last feature method
void "login tc2"(){
setup:
to HomePage
final String searchString = "string data"
searchInput = searchString
when:
btnSearch.click()
then:
at waitFor{ ProductDetailPage }
and:
sku.text().toLowerCase().contains( searchString.toLowerCase() )
}
void "login tc3"(){
setup:
to HomePage
final String searchString = "string data"
searchInput = searchString
when:
btnSearch.click()
then:
at waitFor{ ProductDetailPage }
}
}
In short problem is why features on "TestGebSpec" file runs on same browser
Flow is as below
1.Open browser
2.Feature 1 run
3.Feature 2 run
.
.
.
final point. Browser close
What I except
1.open browser
2.Feature 1 run
3.Close browser
4.Open browser
5.Feature 2 run
6.Close browser
Actually an error on your web page should not be a problem if the next feature opens the initial URL it needs afresh. Also cookies should not be a problem because you can delete them manually or automatically. Over-using #Stepwise is a source of problems for many users, too. You should avoid it whenever possible. I even see you use it in a specification with only one feature (or maybe you only showed me one and actually there are more). Using many browsers is a huge resource waste and just makes your tests slow. The Book of Geb (manual) is an excellent source of information.
Take a look at implicit lifecycle with regard to clearCookies() andclearWebStorage(). Auto-clearing cookies and/or web storage might also be helpful. The same chapter also explains that if you just use CachingDriverFactory.clearCacheAndQuitDriver() at the end of a feature method (or in cleanup() if you need it in every feature), the next method will get a new browser instance automatically.
Of course you can quit the browser via quit() (quits the browser) and close() (closes current browser window) if you want to start the new browser manually. But implicit driver management is easier to use IMO, so I am just mentioning it for completeness' sake.
Now for a very special case: You can even use something like resetBrowser(); CachingDriverFactory.clearCacheAndQuitDriver() in the middle of a single feature method like this:
package de.scrum_master.testing
import geb.driver.CachingDriverFactory
import geb.spock.GebReportingSpec
class RestartBrowserIT extends GebReportingSpec {
def "Search web site Scrum-Master.de"() {
when:_ "download page is opened"
go "https://scrum-master.de"
report "welcome page"
then:_ "expected text is found on page"
$("h2").text().startsWith("Herzlich Willkommen bei Scrum-Master.de")
when:_ "browser is reset"
resetBrowser()
CachingDriverFactory.clearCacheAndQuitDriver()
and:_ "download page is opened again in new browser"
go "https://scrum-master.de/Downloads"
report "download page"
then:_ "expected text is found on page"
$("h2").text().startsWith("Scrum on a Page")
}
}
Then on the console you see something like this:
download page is opened
07:49:49.105 [main] INFO i.g.bonigarcia.wdm.WebDriverManager - Using chromedriver 83.0.4103.39 (since Google Chrome 83 is installed in your machine)
07:49:49.146 [main] INFO i.g.bonigarcia.wdm.WebDriverManager - Exporting webdriver.chrome.driver as C:\Users\alexa\.m2\repository\webdriver\chromedriver\win32\83.0.4103.39\chromedriver.exe
Starting ChromeDriver 83.0.4103.39 (ccbf011cb2d2b19b506d844400483861342c20cd-refs/branch-heads/4103#{#416}) on port 3302
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 03, 2020 7:49:53 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFORMATION: Detected dialect: W3C
expected text is found on page
browser is reset
download page is opened again in new browser
07:49:57.387 [main] INFO i.g.bonigarcia.wdm.WebDriverManager - Using chromedriver 83.0.4103.39 (since Google Chrome 83 is installed in your machine)
07:49:57.413 [main] INFO i.g.bonigarcia.wdm.WebDriverManager - Exporting webdriver.chrome.driver as C:\Users\alexa\.m2\repository\webdriver\chromedriver\win32\83.0.4103.39\chromedriver.exe
Starting ChromeDriver 83.0.4103.39 (ccbf011cb2d2b19b506d844400483861342c20cd-refs/branch-heads/4103#{#416}) on port 27426
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
Jul 03, 2020 7:50:00 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFORMATION: Detected dialect: W3C
expected text is found on page
P.S.: Just in case you are wondering why I write when:_ "label" instead of just when: "label", I use this SpockConfig.groovy file in order to help me print labels in my test, as you can also see in the console log above:
import spock.lang.Specification
/**
* Use like this in order to print Spock/Geb labels:
* given:_ "foo"
* when:_ "bar"
* then:_ "zot"
*/
class LabelPrinter {
def _(def message) {
println message
true
}
}
Specification.mixin LabelPrinter
The Spock configuration file should be in src/main/resources/SpockConfig.groovy if you use a standard Maven directory layout.
I'm using the selenium2 driver to test my Drupal site using Behat/Mink in a docker container.
Using the Selenium Standalone-Chrome container, I can watch my behat tests fail, but the problem is that as soon as they fail, the browser is closed, which makes it harder for me to see what the problem is.
I'm running my tests like this:
behat --tags '#mystuff' --config=behat-myconfig.yml --strict --stop-on-failure
Is there a way to leave the remote-controlled browser open even when a test fails?
By default it is not possible.
Maybe you could find some hack to do it but it is not recommended, since each scenario should be isolated and this is not a good solution at least when running some suite with multiple tests.
For one time only see if you can use the logic for printscreen and use a breakpoint instead.
Anyway, you should use a verbose (-vvv for Behat 3) output + ide debugger to debug your code.
Finally I found a good solution for this: behat-fail-aid.
Add the fail aid to your FeatureContext and then run behat with the --wait-on-failure option:
the --wait-on-failure={seconds} option can be used to
investigate/inspect failures in the browser.
You can take a screenshot whenever an error occurs using Behat hook "AfterStep".
Consider having a look at the Panther Driver or DChrome Driver.
Here you are a shortened example considering also non javascript tests (which are faster):
use Behat\Mink\Driver\Selenium2Driver;
/** Context Class Definition ... */
/**
* #AfterStep
*/
public function takeScreenShotAfterFailedStep(AfterStepScope $scope)
{
if (99 !== $scope->getTestResult()->getResultCode()) {
return;
}
$this->takeAScreenShot('error');
}
private function takeAScreenShot($prefix = 'screenshot')
{
$baseName= sprintf('PATH_FOR_YOUR_SCREENSHOTS/%s-%s', $prefix, (new \DateTime())->format('Y_m_d_H_i_s'));
if ($this->supportsJavascript()) {
$extension = '.png';
$content = $this->session->getScreenshot();
} else {
$extension = '.html';
$content = $this->getSession()->getPage()->getOuterHtml();
}
file_put_contents(sprintf('%s%s', $baseName, $extension), $content);
}
private function supportsJavascript()
{
return $this->getSession()->getDriver() instanceof Selenium2Driver;
}
I have a Spock class, that when run as a test suite, throws Unable to resolve iconRow as content for geb.Page, or as a property on its Navigator context. Is iconRow a class you forgot to import? unless I annotate my class with #Stepwise. However, I really don't want the test execution to stop on the first failure, which #Stepwise does.
I've tried writing (copy and pasting) my own extension using this post, but I still get these errors. It is using my extension, as I added some logging statements that were printed out to the console.
Here is one of my modules:
class IconRow extends Module {
static content = {
iconRow (required: false) {$("div.report-toolbar")}
}
}
And a page that uses it:
class Report extends SomeOtherPage {
static at = {$("div.grid-container").displayed}
static content = {
iconRow { module IconRow }
}
}
And a snippet of the test that is failing:
class MyFailingTest extends GebReportingSpec {
def setupSpec() {
via Dashboard
SomeClass.login("SourMonk", "myPassword")
assert page instanceof Dashboard
nav.goToReport("Some report name")
assert page instanceof Report
}
#Unroll
def "I work"() {
given:
at Report
expect:
this == that
where:
this << ["some list", "of values"]
that << anotherModule.someContent*.#id
}
#Unroll
def "I don't work"() {
given:
at Report
expect:
this == that
where:
this << ["some other", "list", "of values"]
that << iconRow.columnHeaders*.attr("innerText")*.toUpperCase()
}
}
When executed as a suite I work passes and I don't work fails because it cannot identify "iconRow" as content for the page. If I switch the order of the test cases, I don't work will pass and I work will fail. Alternatively, if I execute each test separately, they both pass.
What I have tried:
Adding/removing the required: true property from content in the modules
Prefixing the module name with the class, such as IconRow.iconRow
Defining my modules as static #Shared properties
Initialize the modules both in and outside of my setupSpec()
Making simple getter methods in each module's class that return the module, and referencing content such as IconRow.getIconRow().columnHeaders*.attr("innerText")*.toUpperCase()
Moving the contents of my setupSpec() into setup()
Adding autoClearCookies = false into my GebConfig.groovy
Making a #Shared Report report variable and prefix all modules with that such as report.iconRow
Very peculiar note about that last bullet point -- it magically resolves the modules that don't have the prefix -- so it won't resolve report.IconRow but will resolve just iconRow -- absolutely bizarre, because if I remove that variable the module that was just previously working suddenly can't be resolved again. I even tried declaring this variable and then not prefixing anything, and that did not work either.
Another problem that I keep banging my head against the wall with is that I'm also not sure of where the problem is. The error it throws leads me to believe that it's a project setup issue, but running each feature individually works fine, so it appears to be resolving the classes just fine.
On the other hand, perhaps it's an issue with the session and/or cookies? Although I have yet to see any official documentation on this, it seems to be the general consensus (from other posts and articles I've read) that only using #Stepwise will maintain your session between feature methods. If this is the case, why is my extension not working? It's pretty much a copy and paste of #Stepwise without the skipFeaturesAfterFirstFailingFeature method (I can post if needed), unless there is some other stuff going on behind the scenes with #Stepwise.
Apologies for the wall of text, but I've been trying to figure this out for about 6 hours now, so my brain is pretty fried.
Geb has special support for #Stepwise, if a spec is annotated with it it does not call resetBrowser() after each test, instead it is called after the spec is completed. See the code on github
So basically you need to change your setupSpec to setup so that it will be executed before each test.
Regarding your observation, if you just run a focused test the setupSpec is executed for that test and thus it passes. The problem arises, that the cleanup is invoked afterwards and resets the browser, breaking subsequent tests.
EDIT
I overlooked your usage of where blocks, everything in the where block needs to be statically (#Shared) available, so using instance level constructs won't work. Resetting the browser will also kill every reference so just getting it before wont work either. Basically, don't use Geb objects in where blocks!
Looking at your code however I don't see any reason to use data driven tests here.
This can be easily done with one assertion in a normal test
It is good practice for unit tests to just test one thing. Geb however, is not an unit test but an acceptance/frontend test. The problem here is that they are way slower than unit tests and it makes sense to combine sensible assertions into one test.
class MyFailingTest extends GebReportingSpec {
def setup() {
via Dashboard
SomeClass.login("SourMonk", "myPassword")
assert page instanceof Dashboard
nav.goToReport("Some report name")
assert page instanceof Report
}
def "I work"() {
given:
at Report
expect:
["some list", "of values"] == anotherModule.someContent*.#id
}
def "I don't work"() {
given:
at Report
expect:
["some other", "list", "of values"] == iconRow.columnHeaders*.attr("innerText")*.toUpperCase()
}
}
I want to add a tag #skiponchrome to a scenario, this should skip the scenario when running a Selenium test with the Chrome browser. The reason to-do this is because some scenario's work in some environments and not in others, this might not even be browser testing specific and could be applied in other situation for example OS platforms.
Example hook:
#Before("#skiponchrome") // this works
public void beforeScenario() {
if(currentBrowser == 'chrome') { // this works
// Skip scenario code here
}
}
I know it is possible to define ~#skiponchrome in the cucumber tags to skip the tag, but I would like to skip a tag at run-time. This way I don't have to think about which steps to skip in advance when I starting a test run on a certain environment.
I would like to create a hook that catches the tag and skips the scenario without reporting a fail/error. Is this possible?
I realized that this is a late update to an already answered question, but I want to add one more option directly supported by cucumber-jvm:
#Before //(cucumber one)
public void setup(){
Assume.assumeTrue(weAreInPreProductionEnvironment);
}
"and the scenario will be marked as ignored (but the test will pass) if weAreInPreProductionEnvironment is false."
You will need to add
import org.junit.Assume;
The major difference with the accepted answer is that JUnit assume failures behave just like pending
Important Because of a bug fix you will need cucumber-jvm release 1.2.5 which as of this writing is the latest. For example, the above will generate a failure instead of a pending in cucumber-java8-1.2.3.jar
I really prefer to be explicit about which tests are being run, by having separate run configurations defined for each environment. I also like to keep the number of tags I use to a minimum, to keep the number of configurations manageable.
I don't think it's possible to achieve what you want with tags alone. You would need to write a custom jUnit test runner to use in place of #RunWith(Cucumber.class). Take a look at the Cucumber implementation to see how things work. You would need to alter the RuntimeOptions created by the RuntimeOptionsFactory to include/exclude tags depending on the browser, or other runtime condition.
Alternatively, you could consider writing a small script which invokes your test suite, building up a list of tags to include/exclude dynamically, depending on the environment you're running in. I would consider this to be a more maintainable, cleaner solution.
It's actually really easy. If you dig though the Cucumber-JVM and JUnit 4 source code, you'll find that JUnit makes skipping during runtime very easy (just undocumented).
Take a look at the following source code for JUnit 4's ParentRunner, which Cucumber-JVM's FeatureRunner (which is used in Cucumber, the default Cucumber runner):
#Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
try {
Statement statement = classBlock(notifier);
statement.evaluate();
} catch (AssumptionViolatedException e) {
testNotifier.fireTestIgnored();
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
}
}
This is how JUnit decides what result to show. If it's successful it will show a pass, but it's possible to #Ignore in JUnit, so what happens in that case? Well, an AssumptionViolatedException is thrown by the RunNotifier (or Cucumber FeatureRunner in this case).
So your example becomes:
#Before("#skiponchrome") // this works
public void beforeScenario() {
if(currentBrowser == 'chrome') { // this works
throw new AssumptionViolatedException("Not supported on Chrome")
}
}
If you've used vanilla JUnit 4 before, you'd remember that #Ignore takes an optional message that is displayed when a test is ignored by the runner. AssumptionViolatedException carries the message, so you should see it in your test output after a test is skipped this way without having to write your own custom runner.
I too had the same challenge, where in I need to skip a scenario from running based on a flag which I obtain from the application dynamically in run-time, which tells whether the feature to be tested is enabled on the application or not..
so this is how I wrote my logic in the scenarios file, where we have the glue code for each step.
I have used a unique tag '#Feature-01AXX' to mark my scenarios that need to be run only when that feature(code) is available on the application.
so for every scenario, the tag '#Feature-01XX' is checked first, if its present then the check for the availability of the feature is made, only then the scenario will be picked for running. Else it will be merely skipped, and Junit will not mark this as failure, instead it will me marked as Pass. So the final result if these tests did not run due to the un-availability of the feature will be pass, that's cool...
#Before
public void before(final Scenario scenario) throws Exception {
/*
my other pre-setup tasks for each scenario.
*/
// get all the scenario tags from the scenario head.
final ArrayList<String> scenarioTags = new ArrayList<>();
scenarioTags.addAll(scenario.getSourceTagNames());
// check if the feature is enabled on the appliance, so that the tests can be run.
if (checkForSkipScenario(scenarioTags)) {
throw new AssumptionViolatedException("The feature 'Feature-01AXX' is not enabled on this appliance, so skipping");
}
}
private boolean checkForSkipScenario(final ArrayList<String> scenarioTags) {
// I use a tag "#Feature-01AXX" on the scenarios which needs to be run when the feature is enabled on the appliance/application
if (scenarioTags.contains("#Feature-01AXX") && !isTheFeatureEnabled()) { // if feature is not enabled, then we need to skip the scenario.
return true;
}
return false;
}
private boolean isTheFeatureEnabled(){
/*
my logic to check if the feature is available/enabled on the application.
in my case its an REST api call, I parse the JSON and check if the feature is enabled.
if it is enabled return 'true', else return 'false'
*/
}
I've implemented a customized junit runner as below. The idea is to add tags during runtime.
So say for a scenario we need new users, we tag the scenarios as "#requires_new_user". Then if we run our test in an environment (say production environment which dose not allow you to register new user easily), then we will figure out that we are not able to get new user. Then the ""not #requires_new_user" will be added to cucumber options to skip the scenario.
This is the most clean solution I can imagine now.
public class WebuiCucumberRunner extends ParentRunner<FeatureRunner> {
private final JUnitReporter jUnitReporter;
private final List<FeatureRunner> children = new ArrayList<FeatureRunner>();
private final Runtime runtime;
private final Formatter formatter;
/**
* Constructor called by JUnit.
*
* #param clazz the class with the #RunWith annotation.
* #throws java.io.IOException if there is a problem
* #throws org.junit.runners.model.InitializationError if there is another problem
*/
public WebuiCucumberRunner(Class clazz) throws InitializationError, IOException {
super(clazz);
ClassLoader classLoader = clazz.getClassLoader();
Assertions.assertNoCucumberAnnotatedMethods(clazz);
RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(clazz);
RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();
addTagFiltersAsPerTestRuntimeEnvironment(runtimeOptions);
ResourceLoader resourceLoader = new MultiLoader(classLoader);
runtime = createRuntime(resourceLoader, classLoader, runtimeOptions);
formatter = runtimeOptions.formatter(classLoader);
final JUnitOptions junitOptions = new JUnitOptions(runtimeOptions.getJunitOptions());
final List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader, runtime.getEventBus());
jUnitReporter = new JUnitReporter(runtime.getEventBus(), runtimeOptions.isStrict(), junitOptions);
addChildren(cucumberFeatures);
}
private void addTagFiltersAsPerTestRuntimeEnvironment(RuntimeOptions runtimeOptions)
{
String channel = Configuration.TENANT_NAME.getValue().toLowerCase();
runtimeOptions.getTagFilters().add("#" + channel);
if (!TestEnvironment.getEnvironment().isNewUserAvailable()) {
runtimeOptions.getTagFilters().add("not #requires_new_user");
}
}
...
}
Or you can extends the official Cucumber Junit test runner cucumber.api.junit.Cucumber and override method
/**
* Create the Runtime. Can be overridden to customize the runtime or backend.
*
* #param resourceLoader used to load resources
* #param classLoader used to load classes
* #param runtimeOptions configuration
* #return a new runtime
* #throws InitializationError if a JUnit error occurred
* #throws IOException if a class or resource could not be loaded
* #deprecated Neither the runtime nor the backend or any of the classes involved in their construction are part of
* the public API. As such they should not be exposed. The recommended way to observe the cucumber process is to
* listen to events by using a plugin. For example the JSONFormatter.
*/
#Deprecated
protected Runtime createRuntime(ResourceLoader resourceLoader, ClassLoader classLoader,
RuntimeOptions runtimeOptions) throws InitializationError, IOException {
ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
return new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
}
You can manipulate runtimeOptions here as you wish. But the method is marked as deprecated, so use it with caution.
If you're using Maven, you could read use a browser profile and then set the appropriate ~ exclude tags there?
Unless you're asking how to run this from command line, in which case you tag the scenario with #skipchrome and then when you run cucumber set the cucumber options to tags = {"~#skipchrome"}
If you wish simply to temporarily skip a scenario (for example, while writing the scenarios), you can comment it out (ctrl+/ in Eclipse or Intellij).
My question is about phpunit+selenium usage.
The standard usage of this union is
class BlaBlaTest extends PHPUnit_Extensions_SeleniumTestCase
{... }
OR
class BlaBlaTest extends PHPUnit_Extensions_Selenium2TestCase
{...}
The first one (PHPUnit_Extensions_SeleniumTestCase) is not very convinient to use
(e.g. there is no such thing as $this->elements('xpath')).
Second(PHPUnit_Extensions_Selenium2TestCase) also has limited functionality
(e.g. there is no such functions as waitForPageToLoad() or clickAndWait(),
and using something like $this->timeouts()->implicitWait(10000) looks for me like
complete nonsense).
Is it possible to use the functional
PHPUnit_Extensions_SeleniumTestCase + PHPUnit_Extensions_Selenium2TestCase
in one test class?
Maybe smb knows good alternatives to phpunit+selenium?
Inspired by Dan I've written this for use in PHPUnit_Extensions_Selenium2TestCase and it seems to work ok:
/**
* #param string $id - DOM id
* #param int $wait - maximum (in seconds)
* #retrn element|false - false on time-out
*/
protected function waitForId($id, $wait=30) {
for ($i=0; $i <= $wait; $i++) {
try{
$x = $this->byId($id);
return $x;
}
catch (Exception $e) {
sleep(1);
}
}
return false;
}
Sorry for resurrecting this but I'd like to hopefully clear up some confusion for anyone stumbling across this.
You're saying that you wanted functionality from RC and WebDriver combined where there are workarounds to it, but I wouldn't recommend it. Firstly you'll need to understand the difference between both frameworks.
My brief definitions...
Selenium RC (PHPUnit_Extensions_SeleniumTestCase) is script oriented. By that I mean it will run your tests exactly how you expect the page to respond. This often will require more explicit commands such as the waitForPageToLoad() that you have mentioned when waiting for elements to appear and/or pages to loads.
Selenium WebDriver (PHPUnit_Extensions_Selenium2TestCase) uses a more native approach. It cuts off 'the middle-man' and runs your tests through your chosen browsers driver. Using the waitForPageToLoad() example, you wont need to explicitly put that wherever you open a page in your code because the WebDriver already knows when the page is loading and will resume the test when the page load request is complete.
If you need to define an implicit timeout in WebDriver, you will only need to place that in the setUp() method within a base Selenium class that will be extended in your test classes;
class BaseSelenium extends PHPUnit_Extensions_Selenium2TestCase {
protected function setUp() {
// Whatever else needs to be in here like setting
// the host url and port etc.
$this->setSeleniumServerRequestsTimeout( 100 ); // <- seconds
}
}
That will happily span across all of your tests and will timeout whenever a page takes longer than that to load.
Although I personally prefer WebDriver over RC (mainly because it's a lot faster!) there is a big difference between the methods available. Whenever I got stuck when recently converting a lot a RC tests to WebDriver I always turned to this first. It's a great reference to nearly every situation.
I hope that helps?
For functions such as waitForPageToLoad() and clickAndWait(), which are unavailable in Selenium2, you can reproduce those functions by using try catch blocks, in conjunction with implicit waits and explicit sleeps.
So, for a function like clickAndWait(), you can define what element you are waiting for, and then check for that element's existence for a set amount of seconds. If the element doesn't exist, the try catch block will stop the error from propagating. If the element does exist, you can continue. If the element doesn't exist after the set amount of time, then bubble up the error.
I would recommend using Selenium2 and then reproducing any functionality that you feel is missing from within your framework.
EXAMPLE:
def wait_for_present(element, retry = 10, seconds = 2)
for i in 0...retry
return true if element.present?
sleep(seconds)
end
return false
end
you can try use traits to extend two different classes http://php.net/manual/en/language.oop5.traits.php
class PHPUnit_Extensions_SeleniumTestCase{
...
}
change PHPUnit_Extensions_Selenium2TestCase class to trait:
trait PHPUnit_Extensions_Selenium2TestCase {
...
}
class blabla extends PHPUnit_Extensions_SeleniumTestCase {
use PHPUnit_Extensions_Selenium2TestCase;
your tests here..
}