Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ブラウザテストをサクサク自動化するためのGeb実践入門 #jjug_ccc

ブラウザテストをサクサク自動化するためのGeb実践入門 #jjug_ccc

JJUG CCC Spring 2016 での発表資料です。

B765faf519f19520e1495bd870f4d7f0?s=128

PoohSunny

May 21, 2016
Tweet

Transcript

  1. ϒϥ΢βςετΛ αΫαΫࣗಈԽ͢ΔͨΊͷ Geb࣮ફೖ໳ @PoohSunny JJUG CCC Spring 2016 #ccc_e2

  2. ͸͡Ίʹ

  3. Έͳ͞Μ͖ͯ͘Εͯ ͋Γ͕ͱ͏͍͟͝ ·͢ʂʂ

  4. @PoohSunny { work: "σΟϕϩούʔ" geb: "very minor contributor" community: [

    "TDDBC", "Agile Samurai Base Camp", "೔ຊSeleniumϢʔβʔίϛϡχςΟ" ] }
  5. None
  6. ΑΖ͘͠ ͓Ͷ͕͍͠·͢

  7. ࠓ೔ͷ͓͸ͳ͠

  8. None
  9. ຖճฉ͍ͯ·͕͢ ஌Βͳ͍ ʮήϒʯͩͱ͓΋ͬͯͨ ࢖ͬͨ͜ͱ͋Δ ࢖ͬͯΔ

  10. Groovy੡ͷ ϒϥ΢βΦʔτϝʔγϣϯπʔϧ ʮ͐͡Ϳʯ License͸Apache License, Version 2.0 ΋͏͙͢1.0.0͕ग़ͦ͏ ݱࡏͷ࠷৽όʔδϣϯ͸0.13.1

  11. ϒϥ΢βΦʔτϝʔγϣϯʁ

  12. None
  13. Gebͷ΢Ϧ

  14. ؆ܿͳهड़1 import geb.Browser Browser.drive { go "http://myapp.com/login" assert $("h1").text() ==

    "Please Login" $("form.login").with { username = "admin" password = "password" login().click() } assert $("h1").text() == "Admin Section" } 1 http://www.gebish.org/
  15. ؆ܿʹͳΔ2 2 http://www.slideshare.net/youtaroutakahashi/what-makes-geb-groovy

  16. ͜͏͍͏ίʔυ͕

  17. ͜͏ͳΓ·͢

  18. ࢖ͬͯΈ͍ͨ

  19. ϋϚΓͲ͜Ζ΋͋Δ

  20. ࣮ࡍʹGebΛ ࢓ࣄͰ࢖͑ΔΑ͏ʹ

  21. Gebͦͷલʹ

  22. Why?

  23. ςετͷ޻਺࡟ݮʁ όάͷআڈʁ ઃܭ΁ͷϑΟʔυόοΫʁ

  24. ͦΕ͸ຊ౰ʹϒϥ΢βΦʔτ ϝʔγϣϯͰ΍Δ΂͖͜ͱʁ

  25. ͨ·ʹ͋Δޡղ GebͰύϑΥʔϚϯεςετ3 ઐ༻ͷπʔϧΛ࢖͓͏ 3 https://groups.google.com/d/msg/geb-user/VhbZq9IamqQ/goXzkJkFAwAJ

  26. ఆظతʹݟ௚͢

  27. Whyͷ࣍

  28. Where

  29. ͓͢͢Ί γεςϜςετࣗಈԽ ඪ४ΨΠυ4 4 http://www.shoeisha.co.jp/book/detail/9784798139210

  30. Whereͷ࣍

  31. How

  32. None
  33. ϙΠϯτ ࠓ࢖͍ͬͯΔٕज़ͱͷ਌࿨ੑ ಡΉ vs ॻ͘ υΩϡϝϯτ

  34. ͨͱ͑͹ GrailsΛ࢖͍ͬͯΔ ಡΉෛՙΛԼ͍͛ͨ ؆ܿͳهड़ΛѪ͍ͯ͠Δ ࣗ෼͕υΩϡϝϯτʹͳΔͷ͸ݏͩ ੵۃతʹ֦ு͍ͨ͠

  35. ͜͜·Ͱ͖ͨΒ

  36. Α͏ͦ͜

  37. ελʔτΛ੾Δ

  38. པΔͳΒެࣜ

  39. νϡʔτϦΞϧ͋Δͷʁ I don't know about any tutorials but the documentation

    is quite comprehensive. It also lists a couple of example projects hosted on github which you might want to have a look at. -- ஌Βͳ͍͚ͲυΩϡϝϯτΈͨΒ͍͍Αɻ͋ͱ githubʹ͍͔ͭ͘αϯϓϧ͕͋ΔΑ5 5 https://groups.google.com/d/msg/geb-user/tgnM4gVMRFs/j3Bdo3D6NgAJ
  40. ެࣜυΩϡϝϯτ http://www.gebish.org/manual/current/ αϯϓϧ https://github.com/geb/geb-example- gradle Maven, Grails, Cucumber൛΋͋Δ

  41. @RunWith(JUnit4) class GebishOrgTest extends GebReportingTest { @Test void canGetToTheCurrentBookOfGeb() {

    to GebishOrgHomePage //hover over to expand the menu interact { moveToElement(manualsMenu.toggle) } //first link is for the current manual assert manualsMenu.links[0].text().endsWith("- CURRENT") manualsMenu.links[0].click() at TheBookOfGebPage } }
  42. ૸ΒͤͯΈΑ͏ ./gradlew chromeTest # chromeͰ࣮ߦ ./gradlew firefoxTest # firefoxͰ࣮ߦ ./gradlew

    phantomJsTest # phantomjsͰ࣮ߦ ./gradlew test # ্ه3ͭͷϒϥ΢βͰͦΕͧΕ࣮ߦ
  43. σΟϨΫτϦߏ੒ root |-- src | `-- test | |-- groovy

    | | `- xxxSpecs.groovy | `-- resources | `-- GebConfig.groovy `-- build.gradle
  44. build.gradle ґଘؔ܎ͷఆٛ6 dependencies { // ུ // ඞཁͳυϥΠόΛͦΕͧΕґଘؔ܎ʹ௥Ճ testCompile "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"

    testCompile "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion" testCompile("com.codeborne:phantomjsdriver:1.2.1") { // phantomjs driver pulls in a different selenium version transitive = false } } 6 ͳͥɺWebDriverΛͦΕͧΕґଘؔ܎ʹ௥Ճ͢Δͷ͔ʹ͍ͭͯ͸ɺhttps://groups.google.com/d/ msg/geb-user/Og40o5mXK-4/tga5H2zuRRYJ
  45. Gradleʹ͍ͭͯৄ͘͠͸ to M-6_1

  46. JUnitͰར༻͢Δ৔߹ extends GebReportingTest SpockΛར༻͢Δ৔߹ extends GebReportingSpec Reportingͱ͍͍ͭͯΔΫϥε → ೚ҙͷλΠϛϯάɾऴྃ࣌ʹΩϟ ϓνϟ͕औΕ·͢ɻ

  47. SpockΛར༻ දݱྗ ศརͳػೳ

  48. BDDදه given - when - then thenϒϩοΫͷத͸ ҉໧తʹΞαʔτ͕૸ Δ

  49. ֘౰ͷϖʔδΦϒδΣ ΫτʹભҠ to GebishOrgHomePage ݱࡏͷϖʔδ͕ਖ਼͍͠ ͔ΛνΣοΫ at TheBookOfGebPage

  50. ϖʔδΦϒδΣΫτύλʔϯ ར༻͢ΔίϯςϯπͷϝϯςφϯεੑΛ্͛ɺ࠶ ར༻͠΍͘͢͢ΔͨΊͷσβΠϯύλʔϯ7 7 http://www.seleniumhq.org/docs/06testdesign_considerations.jsp#page-object-design-pattern

  51. GebͷϖʔδΦϒδΣΫτ PageΫϥεΛܧঝ class GebishOrgHomePage extends Page { static at =

    { title == "Sample page" } static content = { header { $("#header") } manualsMenu { module MenuModule, $("#header", 0) } } }
  52. ࠶ར༻Մೳͳύʔπ͸Moduleʹ import geb.Module class MenuModule extends Module { static content

    = { toggle { children("span") } links { $('.link-list li a') } } }
  53. Page url at content

  54. url8 to()ϝιουͰݺΜͩ࣌ʹભҠ͢ΔઌΛઃఆ class PageWithUrl extends Page { static url =

    "example" } ૬ରύεͰهड़͢Δͱ͖͸ɺbaseUrlͷઃఆ͕ඞཁ9 9 http://www.gebish.org/manual/current/#base-url 8 http://www.gebish.org/manual/current/#page-urls
  55. at10 at()ϝιουར༻࣌ʹ͜ͷϖʔδͷΞαʔτ͕ߦΘΕΔ to()ϝιουͰϖʔδભҠͨ͠ͱ͖΋atνΣοΫ͕૸Δ class PageWithAtChecker extends Page { static at

    = { $("h1").text() == "Example" } } 10 http://www.gebish.org/manual/current/#at-checker
  56. content ϖʔδ಺ͷཁૉΛهड़ class PageWithDiv extends Page { static content =

    { theDiv { $('div', id: 'a') } } }
  57. jQueryͬΆ͍API11 $("div") // ࠷ॳʹݟ͔ͭͬͨ div ཁૉ $("div", name: "main") //

    <div name="main"></div> $("div", 1, name: "main") // ೋ൪໨ͷ<div name="main"></div> ͜Μͳॻ͖ํ΋ $("#user-name") // user-name ͱ͍͏id͕ࢦఆ͞Ε͍ͯΔཁૉ $(".btn") // btn ͱ͍͏class͕ࢦఆ͞Ε͍ͯΔཁૉ 11 http://www.gebish.org/manual/current/#the-jquery-ish-navigator-api
  58. Page, Module͸ઐ༻ͷσΟϨΫτϦ ʹ഑ஔ͠·͠ΐ͏ root |-- src | `-- test |

    |-- groovy | | |-- pages | | |-- modules | | `- xxxSpecs.groovy | `-- resources | `-- GebConfig.groovy `-- build.gradle
  59. interact()12 ෳࡶͳૢ࡞Λ͢Δͱ͖ʹ υϥοά & υϩοϓ interact { clickAndHold($('#draggable')) moveByOffset(150, 200)

    release() } 12 http://www.gebish.org/manual/current/#interact-closures
  60. ςΩετϚον13 // CURRENT Ͱऴྃ͢Δจࣈྻ͔ΛνΣοΫ manualsMenu.links[0].text().endsWith(CURRENT) /** CURRENTɹʢେจࣈখจࣈ۠ผͤͣʣ Ͱ࢝·Δจࣈྻ͔ΛνΣοΫ */ manualsMenu.links[0].text().iStartsWith(CURRENT)

    13 http://www.gebish.org/manual/current/#attribute-and-text-matching
  61. click manualsMenu.links[0].click() ϖʔδΦϒδΣΫτͷఆٛͷ࢓ํ͍Ζ͍Ζ content { // to: ͜ͷΤϨϝϯτ͕ΫϦοΫ͞Εͨͱ͖ʹConfirmPageΛ౉͢ // toWait:

    ࣍ͷϖʔδͷatνΣοΫ͕trueʹͳΔ·Ͱ଴ͭ button(to: ConfirmPage, toWait: true) { $('button#save') } }
  62. GebConfig Α͘࢖͍ͦ͏ͳઃఆ14 baseUrl = "http://gebish.org" environments { // systemProperty "geb.env",

    "chrome" chrome { driver = { new ChromeDriver() } } } 14 http://www.gebish.org/manual/current/#configuration
  63. GebConfig ϨϙʔτपΓ // Ϩϙʔτͷग़ྗઌ reportsDir = "target/geb-reports" // ࣦഊͨ͠ͱ͖͚ͩग़ྗ reportOnTestFailureOnly

    = true
  64. GebConfig wait·ΘΓ15 waiting { includeCauseInMessage = true timeout = 10

    retryInterval = 0.5 } atCheckWaiting = true // at ͷ࣌ʹwait͕૸Δ // ݺͼํ waitFor {} waitFor(20) // 20ඵͰtimeout 15 http://www.gebish.org/manual/current/#waiting-configuration
  65. GebConfig ͜Μͳࢦఆ΋ΞϦ waiting { presets { slow { timeout =

    20 retryInterval = 1 } quick { timeout = 1 } } } // ݺͼํ waitFor("quick")
  66. ิ଍

  67. None
  68. JVM্Ͱಈ࡞͢Δಈతݴޠ ίϯύΠϧ࣌ɺ࣮ߦ࣌ͷϝλ ϓϩάϥϛϯά͕Մೳ

  69. ͳʹ͕ى͖͍ͯΔͷʁ to GebishOrgHomePage

  70. ϝιουʹҾ਺͕͋Δͱ͖͸()লུՄ to GebishOrgHomePage to(GebishOrgHomePage) ͭ·Γ͜Ε͸GebishOrgSpec#toͷ ݺͼग़͠

  71. GebishOrgHomePage#to ະఆٛ(্ҐΫϥεͰ΋) methodMissingΛݺͼग़͠

  72. methodMissing browserΦϒδΣΫτʹॲཧΛҠৡ16 class GebSpec extends Specification { Browser _browser Browser

    getBrowser() { if (_browser == null) { _browser = createBrowser() } _browser } def methodMissing(String name, args) { getBrowser()."$name"(*args) } } 16 https://github.com/geb/geb/blob/master/module/geb-spock/src/main/groovy/geb/spock/ GebSpec.groovy ͔ΒҰ෦ൈਮ
  73. Լه͸ಉ͡ҙຯ to GebishOrgHomePage browser.to(GebishOrgHomePage)

  74. লུͤͣʹॻ͘͜ͱ΋Մೳ17 HomePage homePage = browser.to HomePage homePage.loginPageLink.click() LoginPage loginPage =

    browser.at LoginPage SecurePage securePage = loginPage.login("user1", "password1") ͜͏ॻ͘ͱIDEͷิ׬͕ޮ͖·͢ ิ׬ΛऔΔ͔ɺ؆ܿ͞ΛऔΔ͔͸Α͘ߟ͑ͯʂ 17 http://www.gebish.org/manual/current/#ide-support
  75. جຊΛཧղ

  76. ࣮ફฤ

  77. ࢲ͕ϋϚࣦͬͨഊ

  78. ࢲ͕ϋϚࣦͬͨഊ • ͳΜͰಈ͔ͳ͍ͷʁʁ • ಡΈʹ͍͘... • ௨ͬͨΓམͪͨΓ͢Δ • ςετ஗͍

  79. ͳΜͰಈ͔ͳ͍ͷʁʁ

  80. πʔϧબఆࣦഊʹΑΔ शಘίετ૿େ

  81. Ͳͷπʔϧͱ૊Έ߹ΘͤΔ͔

  82. νʔϜͷঢ়گʹґଘ ໨త ݱঢ়࢖͍ͬͯΔπʔϧ εΩϧ ޷Έ

  83. ಡΈʹ͍͘ • ͜ͷॻ͖ํͷهड़ํ๏͸৑௕Ͱ௕ʑ͍͠ɻ৑௕ ͳ΋ͷ͸ՄಡੑΛ͛͞Δ͚ͩͰͳ͘ϝϯςφϯ είετΛ... • ͜ͷϘλϯͲ͜ͷʁ • ͳΜͰ͜͏΍ͬͯॻ͍ͨͷʁ

  84. هड़͸γϯϓϧΛ৺͕͚Δ ਂ͗͢ΔΤϨϝϯτऔಘ $("#id").find(".class-a").find("td").find(".class-b")

  85. هड़͸γϯϓϧΛ৺͕͚Δ ఆٛ͸୹͘ // ్தʹࢦఆͰ͖ͦ͏ͳattributeΛ୳͢ $("#user-td").find(".class-b") // ΤϨϝϯτΛ෼ׂ͢Δ userTd { $("#id").find(".class-a").find("td")

    } column { userTd.find(".class-b") }
  86. Θ͔Γ΍͍͢هड़Λ ͜ͷϘλϯ͸Ͳ͜ͷϘλϯʁ to LoginPage login("user", "password") clickListButton()

  87. Θ͔Γ΍͍͢هड़Λ SpockͷػೳΛ׆༻ given: "user is at Top page" to LoginPage

    login("user", "password") at TopPage when: "user clicks list button" listButton.click() then: "user moves to List page" at ListPage
  88. ͳΜͰ͜͏ॻ͍ͨͷʁ

  89. ॻ͖ํόϥόϥ໰୊ // A͞Μ go "http://myapp.com/login" $("form.login").with { username = "admin"

    password = "password" login().click() } // B͞Μ to LoginPage username = "admin" password = "password" loginButton.click() // C͞Μ to LoginPage login("admin", password)
  90. ॻ͖ํͷ౷Ұ ·ͣ͸ϖʔδΦϒδΣΫτԽ ·ͱ·ͬͨॲཧ͸ϝιουͱͯ͠ఆٛ͢Δ

  91. ϝιουͷཻ౓όϥόϥ໰୊ // A͞Μ to HomePage menuLinks[0].click() // B͞Μ to HomePage

    manualLink.click() // C͞Μ to HomePage showManual()
  92. ͳͥɺͳʹΛ ςετ͍ͨ͠ͷ͔ʹґଘ18 ໨తΛࢥ͍ग़͢ νʔϜͰٞ࿦͢Δ 18 http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html

  93. ಈ࡞͕ෆ҆ఆʁͳ࣌

  94. ద੾ʹwait͕ೖ͍ͬͯΔʁ // ΤϨϝϯτ͕ʮදࣔʯ͞ΕΔ·Ͱ଴ͭ waitFor { $("#id").isDisplayed() }

  95. εϩʔςετ໰୊

  96. ରࡦ εΫϦϓτͷվળ ςΩετϚονϯά͸࠷ऴखஈ19 $('div', text: 'text matcher') ՄೳͳݶΓCSS selectorsΛར༻͢Δ(id, class,

    attributes) 19 http://markmail.org/message/3fl2bqjmnzsanjyu#query:+page:1+mid: 2oh4r2ycbdluq4l7+state:results
  97. ରࡦ ฒྻ࣮ߦ20 // GebConfig cacheDriverPerThread = true quitCachedDriverOnShutdown = false

    // build.gradle test { maxParallelForks = 4 } 20 http://www.gebish.org/manual/current/#driver-caching-configuration
  98. ࠷ޙʹ

  99. ϒϥ΢βࣗಈԽʹ͸ ࢁ΄Ͳπʔϧ(ํ๏)͕͋Δ

  100. πʔϧͰෆ޾ʹ ͳΒͳ͍Α͏ʹ

  101. ؆ܿ͞ΛѪ͢ΔͳΒGeb

  102. GebͰ؆ܿ͞Λ Ѫͦ͏

  103. Special Thanks @oota_ken @ito_nozomi

  104. ͓͠·͍