└── README.md /README.md: -------------------------------------------------------------------------------- 1 | #Recipes for Testing Grails 2 | 3 | ##The Command Line 4 | 5 | To enter the command line, type 'grails' in your terminal. 6 | 7 | ####Run all tests 8 | ``` 9 | test-app 10 | ``` 11 | 12 | ####Re-run failing tests 13 | ``` 14 | test-app -rerun 15 | ``` 16 | 17 | ####Run only a test based on their phase: 18 | ``` 19 | test-app unit: 20 | test-app integration: 21 | test-app functional: 22 | test-app other: 23 | ``` 24 | 25 | ####Run tests that match filenames: 26 | ``` 27 | test-app A* 28 | test-app A* B* C* 29 | ``` 30 | 31 | ####Run tests that match package names: 32 | ``` 33 | test-app com.mypackage.* 34 | test-app com.mypackage.**.* 35 | ``` 36 | 37 | ####Run tests based on type: 38 | ``` 39 | test-app :spock 40 | ``` 41 | 42 | ####Restarting test daemon 43 | ``` 44 | restart-daemon 45 | ``` 46 | 47 | ####View results of test 48 | ``` 49 | open test-report 50 | ``` 51 | 52 | ####Grails test Cheatsheet 53 | http://zanthrash.com/grailstesting/UnitTestingCheatSheet.pdf 54 | http://slid.es/colinharrington/testing-grails 55 | 56 | ####Configuring the test daemon 57 | The test deamon in the grails shell exists in 2.3.x branches to make running of the tests faster. This is similar to the gradle daemon. 58 | 59 | To disable this functionality, modify the BuildConfig.groovy file and change the last attribute of this map to false 60 | 61 | ``` 62 | test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true], 63 | ``` 64 | 65 | ####Printing out which tests are running 66 | 67 | Add the following script to scripts/_Events.groovy 68 | ```groovy 69 | eventTestCaseStart = { name -> 70 | println '-' * 60 71 | println "|$name : started" 72 | } 73 | 74 | eventTestCaseEnd = { name, err, out -> 75 | println "\n|$name : finished" 76 | } 77 | ``` 78 | 79 | ####Sharding tests for CI servers 80 | 81 | Add the following to scripts/TestParallel.groovy 82 | 83 | ```groovy 84 | import org.codehaus.groovy.grails.test.* 85 | import org.codehaus.groovy.grails.test.support.* 86 | import org.codehaus.groovy.grails.test.event.* 87 | 88 | includeTargets << grailsScript("_GrailsClean") 89 | includeTargets << grailsScript("_GrailsTest") 90 | 91 | target(main: "Runs functional tests in parallel in sets of bucketSize") { 92 | 93 | depends(checkVersion, configureProxy, parseArguments, cleanTestReports) 94 | 95 | def tests = new SpecFinder(binding).getTestClassNames() 96 | def id = (argsMap.set ?: 0) as int 97 | def sets = (argsMap.total ?: 4) as int 98 | 99 | List shard = [] 100 | 101 | tests.eachWithIndex { test, index -> 102 | if (index % sets == id) { 103 | shard << "${ tests.get(index) }" 104 | } 105 | } 106 | 107 | testNames = shard 108 | 109 | phasesToRun = ['functional'] 110 | 111 | allTests() 112 | 113 | } 114 | 115 | setDefaultTarget(main) 116 | 117 | class SpecFinder extends GrailsTestTypeSupport { 118 | 119 | SpecFinder(binding) { 120 | super('name', 'functional') 121 | buildBinding = binding 122 | } 123 | 124 | int doPrepare() { 125 | 0 126 | } 127 | 128 | GrailsTestTypeResult doRun(GrailsTestEventPublisher eventPublisher) { 129 | null 130 | } 131 | 132 | def getTestClassNames() { 133 | findSourceFiles(new GrailsTestTargetPattern('**.*Spec')).sort { -it.length() }.collect { sourceFileToClassName(it) } 134 | } 135 | } 136 | ``` 137 | 138 | To run, call grails test test-Parallel --set=2 --total=6 139 | 140 | where set is the shard number and total is the total number of shards. 141 | 142 | Be aware that you need to set the environment or it will default to Development. 143 | 144 | Update: There is also grails plugin for test partition - http://grails.org/plugin/partition-tests 145 | 146 | ####Running tests in parallel (Secret Escapes) 147 | 148 | Partitions tests and runs them in different processes 149 | 150 | ```groovy 151 | import groovy.sql.* 152 | import org.codehaus.groovy.grails.test.* 153 | import org.codehaus.groovy.grails.test.support.* 154 | import org.codehaus.groovy.grails.test.event.* 155 | 156 | includeTargets << grailsScript("TestApp") 157 | 158 | target(main: "Runs functional tests in parallel in sets of bucketSize") { 159 | def reportsDir = 'reports' 160 | def numberOfServers = 5 161 | 162 | def sql = Sql.newInstance('jdbc:mysql://localhost:3306/', 'root', '', 'com.mysql.jdbc.Driver') 163 | 164 | def tests = new SpecFinder(binding).getTestClassNames() 165 | new File(reportsDir).mkdirs() 166 | def commands = [] 167 | def threads = [] 168 | def results = '' 169 | 170 | numberOfServers.times { id -> 171 | 172 | def reportsFile = new File(reportsDir + '/' + 'test' + id).absolutePath 173 | 174 | sql.execute( "DROP DATABASE IF EXISTS parallelDB${id};" ) 175 | sql.execute( "CREATE DATABASE parallelDB${id};" ) 176 | 177 | def pattern = '' 178 | 179 | tests.eachWithIndex { test, index -> 180 | 181 | if (index % numberOfServers == id) 182 | { 183 | pattern += " ${ tests.get(index) }" 184 | } 185 | 186 | } 187 | 188 | def command = "grails -Dgrails.project.test.reports.dir=${reportsFile} -Dserver.port=909${id} -Ddb.name=parallelDB${id} test-app functional: ${pattern}" 189 | 190 | threads << Thread.start { 191 | 192 | println command 193 | ProcessBuilder builder = new ProcessBuilder(command.split(' ')); 194 | 195 | builder.redirectErrorStream(true); 196 | Process process = builder.start(); 197 | 198 | InputStream stdout = process.getInputStream(); 199 | BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)); 200 | 201 | while ((line = reader.readLine()) != null) 202 | { 203 | if( !line.contains( 'buildtestdata.DomainInstanceBuilder' ) ){ 204 | System.out.println("Server ${id}: " + line); 205 | } 206 | 207 | if( line.contains( 'Tests passed:' ) || line.contains( 'Tests failed:' ) ){ 208 | results += "Server ${id}: " + line + '\n' 209 | } 210 | } 211 | 212 | } 213 | 214 | } 215 | 216 | threads.each { 217 | it.join() 218 | } 219 | 220 | println '------------------------------------' 221 | println 'Tests FINISHED' 222 | println '------------------------------------' 223 | println results 224 | 225 | } 226 | 227 | setDefaultTarget(main) 228 | 229 | class SpecFinder extends GrailsTestTypeSupport { 230 | 231 | SpecFinder(binding) { 232 | super('name', 'functional') 233 | buildBinding = binding 234 | } 235 | 236 | int doPrepare() { 237 | 0 238 | } 239 | 240 | GrailsTestTypeResult doRun(GrailsTestEventPublisher eventPublisher) { 241 | null 242 | } 243 | 244 | def getTestClassNames() { 245 | findSourceFiles(new GrailsTestTargetPattern('**.*Spec')).sort{ -it.length() }.collect{ sourceFileToClassName(it) } 246 | } 247 | } 248 | ``` 249 | 250 | ##Taglibs 251 | 252 | ####Mocking a view in unit tests 253 | 254 | ```groovy 255 | void "constraints are passed to template"() { 256 | given: 257 | views["/_fields/default/_field.gsp"] = 'nullable=${constraints.nullable}, blank=${constraints.blank}' 258 | 259 | expect: 260 | applyTemplate('', [personInstance: personInstance]) == "nullable=false, blank=false" 261 | } 262 | ``` 263 | 264 | ####Setting pageScope variables 265 | 266 | ```groovy 267 | void testTagThatAccessesPageScope() { 268 | tagLib.pageScope.food = 'tacos' 269 | tagLib.tagThatAccessesPageScope([:]) 270 | assertEquals 'food is tacos', tagLib.out.toString() 271 | } 272 | } 273 | 274 | class PagePropertyTagLib { 275 | 276 | def tagThatAccessesPageScope = { 277 | out << "food is ${pageScope.food}" 278 | } 279 | 280 | } 281 | ``` 282 | 283 | ###Testing Grails 284 | 285 | Colin Harrington - Testing Grails : Experiences from the field - http://springone2gx.com/s/slides/2011/speaker/Colin_Harrington/testing_grails___experiencies_from_the_field/grails_testing.pdf 286 | Colin Harrington - Grails testing seminar : http://www.youtube.com/watch?v=RZlXFR013hg 287 | How to Make your Testing more Groovy - http://www.slideshare.net/smithcdau/how-to-make-your-testing-more-groovy 288 | 289 | ##Spock 290 | 291 | ( Mostly from: http://docs.spockframework.org/en/latest ) 292 | 293 | ####Configuration ( Next level spock ) 294 | 295 | Spock has a configuration API. 296 | 297 | Looks for (in order)… 298 | 299 | -Dspock.configuration=path/to/file 300 | SpockConfig.groovy file on class path 301 | ~/.spock/SpockConfig.groovy 302 | 303 | ``` 304 | runner { 305 | filterStackTrace true 306 | optimizeRunOrder false 307 | include Database 308 | exclude UI 309 | } 310 | ``` 311 | 312 | 313 | 314 | 315 | 316 | ####Compare with previous value using old() 317 | 318 | ```groovy 319 | when: 320 | value * 30 321 | then: 322 | value == old( value ) * 30 323 | ``` 324 | 325 | ####Changing label formatting in IntelliJ Idea 326 | 327 | ####Generate business friendly test run report 328 | 329 | Spock subproject - https://github.com/spockframework/spock/tree/groovy-1.8/spock-report 330 | 331 | ####Generate a HTML report based on labels 332 | 333 | Project on github - https://github.com/chanwit/spock-report-html 334 | 335 | ###Extensions 336 | 337 | ####Useful Extensions 338 | 339 | * @Unroll - splits out features into separate tests 340 | * @Ignore - do not run this test 341 | * @Ignore(reason = "I am too old for this shit") - can have a reason 342 | * @IgnoreRest - only run this test 343 | * @IgnoreIf({ os == 'windows' }) 344 | * @Require({ payscale == 'jedi' }) 345 | * @Stepwise - causes each feature method to be executed in declaration order and any after the first failure to be ignored (i.e. story mode) 346 | * @Shared - makes a field available to all tests / where clauses 347 | * @Timeout - causes a method to fail if it takes to long (and will interrupt it if it does take too long) 348 | * @AutoCleanup - allows a named cleaning up/closing method to be automatically called by Spock on a field 349 | * @FailsWith - makes a feature pass only if it raises a certain exception and it’s uncaught 350 | 351 | Annotations like @Unroll and @Timeout can be applied to the spec level. 352 | 353 | ####Make your own extensions 354 | 355 | Follow Luke Daley's guide at http://ldaley.com/post/971946675/annotation-driven-extensions-with-spock 356 | 357 | ####Hamcrest Matchers 358 | 359 | ```groovy 360 | import static spock.util.matcher.HamcrestSupport.that 361 | 362 | expect: 363 | that answer, closeTo(42, 0.001) 364 | ``` 365 | 366 | You can also use hamcrest matchers to constraint method arguments 367 | 368 | ```groovy 369 | import static spock.util.matcher.HamcrestMatchers.closeTo 370 | 371 | 1 * foo.bar(closeTo(42, 0.001)) 372 | ``` 373 | 374 | In then methods, you can use 'expect' for better readability 375 | 376 | ```groovy 377 | when: 378 | def x = computeValue() 379 | 380 | then: 381 | expect x, closeTo(42, 0.01) 382 | ``` 383 | 384 | Useful list of Hamcrest Matchers - https://code.google.com/p/hamcrest/wiki/Tutorial 385 | 386 | ####Parameter Typing 387 | You can add type information to the method (for IDE support). 388 | 389 | 390 | ```groovy 391 | def "some math"(Integer a, Integer b, Integer c) { 392 | expect: 393 | a + b == c 394 | where:... 395 | } 396 | 397 | ``` 398 | 399 | ###Working with Data - Data Tables 400 | 401 | ####One column where clauses 402 | ```groovy 403 | where: 404 | animal | _ 405 | 'rabbit' | _ 406 | 'cow' | _ 407 | ``` 408 | 409 | ####Double Pipe to separate input from output 410 | ```groovy 411 | where: 412 | english | plural || spanish 413 | 'rabbit' | true || 'conejos' 414 | 'cow' | false || 'vaca' 415 | ``` 416 | 417 | ####Assign variables 418 | ```groovy 419 | where: 420 | a = 'cars' 421 | ``` 422 | 423 | ####Data Pipes 424 | ```groovy 425 | where: 426 | a << ['one', 'two', 'three' ] 427 | ``` 428 | 429 | This will run each value of the pipe at each iteration - resulting in three tests. 430 | 431 | ####Multiple assignments 432 | ```groovy 433 | where: 434 | [a, b, c] << [ [ 1, 2, 3 ], 435 | [ 1, 3, 5 ] ] 436 | ``` 437 | 438 | ####Skip values 439 | ```groovy 440 | where: 441 | [a, _, c] << [ [ 1, 2, 3 ], [ 1, 3, 5 ] ] 442 | ``` 443 | 444 | ####Combinations 445 | 446 | Creates a data table with all 8 possible combinations: 447 | 448 | ```groovy 449 | where: 450 | [name, fingers, nombre] << [ [ 'one', 'two'], [1,2], ['uno', 'dos'] ].combinations() 451 | ``` 452 | 453 | This is equivalent to: 454 | ```groovy 455 | where: 456 | name | fingers | nombre 457 | 'one' | 1 | 'uno' 458 | 'two' | 1 | 'uno' 459 | 'one' | 2 | 'uno' 460 | 'two' | 2 | 'uno' 461 | 'one' | 1 | 'dos' 462 | 'two' | 1 | 'dos' 463 | 'one' | 2 | 'dos' 464 | 'two' | 2 | 'dos' 465 | ``` 466 | 467 | ####Can combine different types of data table format 468 | ```groovy 469 | where: 470 | name | nombre 471 | 'one' | 'uno' 472 | 473 | properName = name.capitalize() 474 | postCode << ['90210', '90222'] 475 | ``` 476 | 477 | IntelliJ won't recognize mixed format so you have to do the data table, format it and then the other values. 478 | 479 | ####Parameter Typing 480 | You can add type information to the method (for IDE support). 481 | 482 | ```groovy 483 | def "some math"(Integer a, Integer b, Integer c) { 484 | expect: 485 | a + b == c 486 | 487 | where: 488 | a | b || c 489 | 1 | 1 || 2 490 | 5 | 5 || 10 491 | } 492 | ``` 493 | 494 | ###Mocking 495 | 496 | ####Declarations can be chained 497 | 498 | ```groovy 499 | foo.bar() >> { throw new IOException() } >>> [1, 2, 3] >> { throw new RuntimeException() } 500 | ``` 501 | 502 | ####You can group Interactions together with the 'with' keyword 503 | 504 | ```groovy 505 | def service = Mock(Service) 506 | app.service = service 507 | 508 | when: 509 | app.run() 510 | 511 | then: 512 | with(service) { 513 | 1 * start() 514 | 1 * act() 515 | 1 * stop() 516 | } 517 | ``` 518 | 519 | ####Groovy mocks 520 | 521 | If you have dynamic methods, use the groovy version of mocks and stubs, 522 | 523 | ``` 524 | def mock = GroovyMock(Person) 525 | def stub = GroovyStub(Person) 526 | def spy = GroovySpy(Person) 527 | ``` 528 | 529 | They can also be made global 530 | 531 | ``` 532 | def spy = GroovySpy(Person, global:true) 533 | ``` 534 | 535 | ####Testing Domain Class Constraints with Spock 536 | 537 | http://www.christianoestreich.com/2012/11/domain-constraints-grails-spock-updated/ 538 | 539 | ####Presentations on Spock 540 | 541 | * Peter Niederwieser : Spock - the smarter testing and specification framework (Devoxx) 542 | http://parleys.com/play/514892260364bc17fc56bdb5/about 543 | * Rob Fletcher - Groovier Testing with Spock - http://skillsmatter.com/podcast/groovy-grails/groovier-testing-with-spock 544 | * Luke Daley - Smarter Testing with Spock - http://skillsmatter.com/podcast/java-jee/spock 545 | * Next Level Spock (Luke Daley) - http://springone2gx.com/s/slides/2012/speaker/Luke_Daley/next_level_spock/slides.pdf 546 | * Ken Kousen - Spock : Logical testing for the enterprise - SpringOne2GX 2013 - http://springone2gx.com/s/slides/2013/speaker/Kenneth_Kousen/spock_logical_testing_for_enterprise_applications/Spock_Friendly_Testing.pdf 547 | 548 | ##Betamax 549 | 550 | ####Presentations on Betamax 551 | * Rob Fletcher - Testing HTTP dependencies with Betamax http://skillsmatter.com/podcast/groovy-grails/testing-http-dependencies-with-betamax 552 | 553 | ##Geb 554 | 555 | ####Book of Geb 556 | http://www.gebish.org/manual/current/ 557 | 558 | ####Adding a button that pauses Geb via javascript (Luke Daley) 559 | 560 | ```groovy 561 | private void pause() { 562 | js.exec """ 563 | (function() { 564 | window.__gebPaused = true; 565 | var div = document.createElement("div"); 566 | div.setAttribute('style', "\\ 567 | position: absolute; top: 0px;\\ 568 | z-index: 3000;\\ 569 | padding: 10px;\\ 570 | background-color: red;\\ 571 | border: 2px solid black;\\ 572 | border-radius: 2px;\\ 573 | text-align: center;\\ 574 | "); 575 | 576 | var button = document.createElement("button"); 577 | button.innerHTML = "Unpause Geb"; 578 | button.onclick = function() { 579 | window.__gebPaused = false; 580 | } 581 | button.setAttribute("style", "\\ 582 | font-weight: bold;\\ 583 | "); 584 | 585 | div.appendChild(button); 586 | document.getElementsByTagName("body")[0].appendChild(div); 587 | })(); 588 | """ 589 | 590 | waitFor(300) { !js.__gebPaused } 591 | } 592 | ``` 593 | 594 | ####Extend your own elements by providing your own navigators (Sky) 595 | 596 | Provide your own class for empty and nonempty navigators 597 | ```groovy 598 | class NonEmptyNavigator extends geb.navigator.NonEmptyNavigator { 599 | NonEmptyNavigator(Browser browser, Collection contextElements) { 600 | super(browser, contextElements) 601 | } 602 | 603 | boolean isDirty() { 604 | hasClass 'dirty' 605 | } 606 | 607 | boolean isHidden() { 608 | hasClass 'hidden' 609 | } 610 | } 611 | ``` 612 | 613 | Add this to GebConfig 614 | 615 | ```groovy 616 | innerNavigatorFactory = { Browser browser, List elements -> 617 | elements ? new NonEmptyNavigator(browser, elements) : new EmptyNavigator(browser) 618 | } 619 | ``` 620 | 621 | ####Getting Raw HTML for an element 622 | 623 | in your custom Navigator 624 | 625 | ```groovy 626 | String rawHtml() { 627 | browser.js.exec(firstElement(), "return arguments[0].innerHTML;") 628 | } 629 | ``` 630 | 631 | ####Handling css transitions (Sky) 632 | 633 | Add this to the nonempty navigator / webkit only 634 | 635 | ```groovy 636 | void waitForCssTransition(Closure trigger) { 637 | def element = firstElement() 638 | 639 | browser.js.exec(element, ''' 640 | var o = jQuery(arguments[0]); 641 | window.setTransitionFinishedClass = function() { 642 | $(this).addClass('transitionFinished'); 643 | } 644 | o.bind('webkitTransitionEnd', window.setTransitionFinishedClass); 645 | ''') 646 | 647 | try { 648 | trigger.call() 649 | browser.waitFor { 650 | hasClass('transitionFinished') 651 | } 652 | } finally { 653 | browser.js.exec(element, ''' 654 | var o = jQuery(arguments[0]); 655 | o.removeClass('transitionFinished') 656 | o.unbind('webkitTransitionEnd', window.setTransitionFinishedClass); 657 | window.setTransitionFinishedClass = undefined; 658 | ''') 659 | } 660 | } 661 | ``` 662 | 663 | ####Automatically download chromedriver (Sky) 664 | 665 | Into your gebConfig 666 | ```groovy 667 | driver = { new FirefoxDriver() } 668 | 669 | private void downloadDriver(File file, String path) { 670 | if (!file.exists()) { 671 | def ant = new AntBuilder() 672 | ant.get(src: path, dest: 'driver.zip') 673 | ant.unzip(src: 'driver.zip', dest: file.parent) 674 | ant.delete(file: 'driver.zip') 675 | ant.chmod(file: file, perm: '700') 676 | } 677 | } 678 | 679 | environments { 680 | 681 | // run as “grails -Dgeb.env=chrome test-app” 682 | // See: http://code.google.com/p/selenium/wiki/ChromeDriver 683 | chrome { 684 | def chromeDriver = new File('test/drivers/chrome/chromedriver') 685 | downloadDriver(chromeDriver, "http://chromedriver.googlecode.com/files/chromedriver_mac_23.0.1240.0.zip") 686 | System.setProperty('webdriver.chrome.driver', chromeDriver.absolutePath) 687 | driver = { new ChromeDriver() } 688 | } 689 | 690 | ``` 691 | 692 | ####Page Builder Pattern (Craig Atkinson) 693 | 694 | Helps make tests more explicit by having helper methods always return the actual page. 695 | 696 | Instead of 697 | 698 | ```groovy 699 | to HomePage 700 | loginButton.click() 701 | ``` 702 | 703 | make it 704 | 705 | ```groovy 706 | Homepage homepage = to(HomePage) 707 | homepage.loginButton.click() 708 | ``` 709 | 710 | all helper methods should return the page as the last argument 711 | 712 | ```groovy 713 | SignedInPage login( String username, String password ){ 714 | // code here 715 | at SignedInPage 716 | browser.page 717 | } 718 | ``` 719 | 720 | In your tests, you call 721 | 722 | ```groovy 723 | when: 724 | Homepage homepage = to(HomePage) 725 | SignedInPage signedInPage = login( 'bob', 'password' ) 726 | then: 727 | signedInPage.displayedUsername == 'Bob Marley' 728 | ``` 729 | 730 | ####Inject a js library to the page 731 | 732 | Add helper method to Geb base page 733 | 734 | ```groovy 735 | def injectLibrary( library ){ 736 | js.exec("document.body.appendChild(document.createElement('script')).src='$library'"); 737 | } 738 | ``` 739 | 740 | Call in page that needs injection 741 | 742 | ```groovy 743 | injectLibrary( 'http://sinonjs.org/releases/sinon-1.4.2.js' ) 744 | 745 | ``` 746 | 747 | ####Setting up headless browsing on Jenkins with xvfb 748 | 749 | http://www.labelmedia.co.uk/blog/setting-up-selenium-server-on-a-headless-jenkins-ci-build-machine.html 750 | 751 | ####Use Sauce Labs 752 | 753 | add dependency to buildconfig 754 | 755 | ```groovy 756 | test "org.seleniumhq.selenium:selenium-remote-driver:2.31.0" 757 | ``` 758 | 759 | in GebConfig 760 | 761 | ```groovy 762 | import org.openqa.selenium.remote.DesiredCapabilities 763 | import org.openqa.selenium.remote.RemoteWebDriver 764 | driver = { 765 | DesiredCapabilities capabilities = DesiredCapabilities.firefox() 766 | capabilities.setCapability("version", "17") 767 | capabilities.setCapability("platform", "Windows 2012") 768 | new RemoteWebDriver( 769 | new URL("http://:@ondemand.saucelabs.com:80/wd/hub"), capabilities 770 | ) 771 | } 772 | ``` 773 | 774 | ####Use PhantomJS via Ghostdriver 775 | 776 | In BuildConfig 777 | 778 | ```groovy 779 | test( "com.github.detro.ghostdriver:phantomjsdriver:1.0.1" ) { 780 | transitive = false 781 | } 782 | ``` 783 | 784 | in GebConfig 785 | 786 | ```groovy 787 | import org.openqa.selenium.phantomjs.PhantomJSDriver 788 | import org.openqa.selenium.remote.DesiredCapabilities 789 | import org.openqa.selenium.Dimension 790 | driver = { 791 | new PhantomJSDriver(new DesiredCapabilities()) 792 | } 793 | ``` 794 | 795 | ####Switching to a different driver for one Spec 796 | 797 | ```groovy 798 | import geb.spock.GebSpec 799 | import spock.lang.Shared 800 | import org.openqa.selenium.firefox.FirefoxProfile 801 | import org.openqa.selenium.firefox.FirefoxDriver 802 | 803 | class MobileSpeck extends GebSpec { 804 | 805 | @Shared def cachedDriver 806 | 807 | def setupSpec(){ 808 | cachedDriver = new FirefoxDriver() 809 | } 810 | 811 | def setup(){ 812 | // assign this as the default driver on the browser for each test 813 | browser.driver = cachedDriver 814 | } 815 | 816 | def cleanupSpec(){ 817 | // after running the spec, kill the driver 818 | cachedDriver.quit() 819 | } 820 | 821 | } 822 | ``` 823 | 824 | ####Grails polydriver plugin (Object Partners) 825 | 826 | Set up multiple drivers and define preferred driver for one spec 827 | http://www.objectpartners.com/2013/05/05/poly-driver-a-phantom-band-aid-for-geb/ 828 | 829 | ####Run Javascript tests in Sauce Labs via Geb (Object Partners) 830 | 831 | http://www.objectpartners.com/2013/04/18/multi-browser-javascript-unit-testing-with-sauce/ 832 | 833 | ####Changing window sizes for tests (Marcin Erdmann) 834 | http://blog.proxerd.pl/article/resizing-browser-window-in-geb-tests 835 | 836 | ####Manipulating time with Sinon 837 | http://blog.proxerd.pl/article/mocking-browser-timers-in-geb-tests 838 | 839 | ####Modelling repeated structures with Geb 840 | http://adhockery.blogspot.co.uk/2010/11/modelling-repeating-structures-with-geb.html 841 | 842 | Update: Also use ModuleList - http://www.gebish.org/manual/0.9.0/modules.html#using_modules_for_repeating_content_on_a_page 843 | 844 | ####Useful WebDriver settings 845 | 846 | Fixed Screen Size 847 | 848 | ```groovy 849 | import org.openqa.selenium.phantomjs.PhantomJSDriver 850 | import org.openqa.selenium.remote.DesiredCapabilities 851 | import org.openqa.selenium.Dimension 852 | driver = { 853 | def d = new PhantomJSDriver(new DesiredCapabilities()) 854 | d.manage().window().setSize(new Dimension(1028, 768)) 855 | d 856 | } 857 | ``` 858 | 859 | Spoof User Agent in PhantomJS 860 | 861 | ```groovy 862 | def capabilities = new DesiredCapabilities() 863 | capabilities.setCapability("phantomjs.page.settings.userAgent", 864 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, 865 | like Gecko) Chrome/27.0.1453.93 Safari/537.36") 866 | new PhantomJSDriver(capabilities) 867 | ``` 868 | 869 | Spoof User Agent Firefox 870 | 871 | ```groovy 872 | FirefoxProfile profile = new FirefoxProfile(); 873 | profile.setPreference("general.useragent.override", 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.1'); 874 | cachedDriver = new FirefoxDriver(profile) 875 | ``` 876 | 877 | Accessing SSL Certificates 878 | 879 | http://onekilo79.wordpress.com/2013/08/17/groovy-with-geb-using-phantomjs-and-ssl-certs/ 880 | 881 | ####Hybrid model: add Sikuli to geb tests for things Geb can't deal with like File upload dialogs or system interactions. 882 | 883 | http://fbflex.wordpress.com/2012/10/27/geb-and-sikuli/ 884 | 885 | ####Presentations on Geb 886 | 887 | * Marcin Erdmann - Advanced Geb - http://skillsmatter.com/event-details/home/advanced-geb 888 | * Luke Daley - The evolution of Geb - http://skillsmatter.com/podcast/home/the-evolution-of-an-open-source-projects-build-automation 889 | * Luke Daley - Productive Grails functional testing - http://skillsmatter.com/podcast/home/productive-grails-functional-testing 890 | * Luke Daley - Very Groovy Browser Automation - http://www.youtube.com/watch?v=T2qXCBT_QBs 891 | * Tomas Lin - Testing Dynamic Pages with Geb - http://skillsmatter.com/podcast/groovy-grails/talk-by-tomas-lin 892 | * Richard Paul - Acceptance Testing via Geb - http://skillsmatter.com/podcast/agile-testing/acceptance-testing-with-geb 893 | * Craig Atkinson - Geb Groovy Grails Functional Testing - https://github.com/sjurgemeyer/GR8ConfUS2013/blob/master/CraigAtkinson-Geb/Geb_Groovy_Grails_Functional_Testing_Gr8Conf_US_2013_slides.pdf 894 | * Mike Ensor - Writing an Extensible Web Testing Framework ready for the Cloud - http://gr8conf.eu/Presentations/Writing-an-Extensible-Web-Test 895 | --------------------------------------------------------------------------------