SlideShare une entreprise Scribd logo
1  sur  74
Télécharger pour lire hors ligne
GEB FOR TESTING YOUR GRAILS
APPLICATION
Jacob Aae Mikkelsen
AGENDA
Functional testing
Geb - cudos and history
How Geb works
Geb and Grails 2 and 3
Browser support
Javascript
JACOB AAE MIKKELSEN
Senior Engineer at Lego
Microservice based architechture on JVM
Previously 4 years at Gennemtænkt IT
Consultant on Groovy and Grails
External Associate Professor - University of Southern
Denmark
@JacobAae
Blogs The Grails Diary
FUNCTIONAL TESTING
Ignores the specifics of the underlying software
component under test.
Merely asserts that providing certain input results in
certain output.
Web-application: Programmatically controlling a web
browser to simulate the actions of a user on a web page.
Traditionally been tedious, cumbersome and brittle
to do

Geb helps ease the pain
GEB
CREDITS
Luke Daley
Marcin Erdmann
GEB HISTORY
Started in November 2009
Created by Luke Daley
Current project lead Marcin Erdman
WHY GEB
jQuery like selector syntax
Power of WebDriver (Easier api)
Robustness of Page Object modeling
Expressiveness of the Groovy language
Integrates well with build systems (Gradle/Maven)
Excellent user manual/documentation
GEB IMPLEMENTATION
Build on top of the WebDriver browser automation library
successor to the Selenium Remote Control (RC) testing
framework.
Selenium RC → JavaScript to interact
WebDriver → native browser drivers
Use JUnit or Spock
USING GEB
GEB SELECTORS (1)
Jquery like syntax
// match all 'p' elements on page
$("p")
// match the first 'p' element on the page
$("p", 0)
// All 'p' elements with a title value 'section'
$("div", title: "section")
// match the first 'p' element title attribute 'section'
$("p", 0, title: "section")
// match the first 'p' element with the class 'main'
$("p.main", 0)
GEB SELECTORS (2)
Selecting returns Navigatorobjects
// The parent of the first div
$("div", 0).parent()
// All tables with a cellspacing
// attribute value of 0 that are nested in a paragraph
$("p").find("table", cellspacing: '0')
RETRIVING INFORMATION
$("p").text() == "a"
$("p").tag() == "p"
$("p").@title == "a"
$("p").classes() == ["a", "para"]
INTERACTION WITH CONTENT
click()
isDisplayed()
withConfirm{}
withAlert{}
$("a.btn").click()
$("div").isDisplayed()
withConfirm {
    $("button.delete").click()
}
SENDING INPUT
import org.openqa.selenium.Keys
// Shorthand for sendKeys() method of WebDriver.
$("div") << "abc"
$("input", name: "foo") << Keys.chord(Keys.CONTROL, "c")
MORE INTERACTION WITH FORMS
Consider the following HTML…
<form>
    <input type="text" name="geb" value="Functional" />
</form>
The value can be read and written via property notation…
$("form").geb == "Functional"
$("form").geb = "Testing"
$("form").geb == "Testing"
These are literally shortcuts for…
$("form").find("input", name: "geb").value() == "Functional"
$("form").find("input", name: "geb").value("Testing")
$("form").find("input", name: "geb").value() == "Testing"
VARIABLES AVAILABLE
title
browser
currentUrl
currentWindow
MORE POSSIBILITIES
Uploading files
Interaction closures
Simulate drag-n-drop
Control-clicking
Interacting with javascript
js object
STRUCTURING GEB TESTS
Lets test a CRUD part of a grails application registrering
conference attendees
Lets test the following
1. Goto list of attendees page
2. Create new employee (incl. invalid data once)
3. Update the employee
4. Check data is updated
GEB SPEC BASICS
import geb.spock.GebSpec
@Stepwise // Ensures the tests are run sequentially
class AttendeeFunctionalSpec extends GebSpec {
    // Spock specs here
}
GEB SPEC (1)
The naive inmaintainable way!
void "Go to list page ­ check initial state"() {
    when:"The home page is visited"
    go '/attendee/index'
    then:
    title == "Attendee List"
}
GEB SPEC (2)
The naive inmaintainable way!
void "Click new attendee button"() {
    when:
    $("a.create").click()
    then:
    title == "Create Attendee"
}
GEB SPEC (3)
The naive inmaintainable way!
void "Submit form with errors"() {
    when:
    $("button.btn­primary").click()
    then:
    title == "Create Attendee"
}
GEB SPEC (4)
The naive inmaintainable way!
void "Submit form with no errors"() {
    when:
    $('form').name = 'Bobby'
    $('form').email = 'bobby@mail.dk'
    and:
    $("button.btn­primary").click()
    then:
    title == 'Show Attendee'
    $('span.property­value').find{ it.text() == 'Bobby'}
    $('span.property­value').find{ it.text() == 'bobby@mail.dk'}
}
GEB SPEC (5)
The naive inmaintainable way!
void "Click Edit Button"() {
    when:
    $("a.btn­primary").click()
    then:
    title == 'Edit Attendee'
}
GEB SPEC (6)
The naive inmaintainable way!
void "Update Attendee"() {
    when:
    $('form').name = 'Alison'
    $('form').email = 'alison@gr8.dk'
    and:
    $("button.btn­primary").click()
    then:
    title == 'Show Attendee'
    $('span.property­value').find{ it.text() == 'Alison'}
    $('span.property­value').find{ it.text() == 'alison@gr8.dk'}
}
GEB SPEC - THE BETTER WAY
If we make a few scenarios, there will be
Much duplication
Many places to correct if we change the layout / DOM
We can correct this using pages and modules
PAGE OBJECTS
Describes a web page
Url
How to check if we are at the correct place
Content we wish to interact with
PAGE OBJECTS
import eu.gr8conf.grailsdemo.modules.NavigationBarModule
import geb.Page
class AttendeeShowPage extends Page {
    static url = "/attendee/show"
    static at = { title ==~ /Show Attendee/ }
    static content = {
        attProp{ $('span.property­label') }
        name{ attProp.find{ it.text() == 'Name'}.next().text() }
        email{ attPro.find{ it.text() == 'Email'}.next().text() }
        editButton{ $("a.btn­primary") }
    }
}
MODULES
Describes repeated content
Across pages
Within the same page
MODULES
import geb.Module
class NavigationBarModule extends Module {
    static base = { $('nav.navbar') }
    static content = {
        home(required: false) { $('a.home') }
        listAttendee(required: false) { $('a.list') }
        newAttendee(required: false) { $('a.create') }
    }
}
MODULES
static content = {
// Like this, the module does not need a base
//  form{ module NavigationBarModule, $('nav.navbar') }
    form { module NavigationBarModule }
}
MODULE FOR REPEATED CONTENT
IN A PAGE
import geb.Module
class AttendeeListItemModule extends Module {
    static content = {
        data { $("td", it) }
        name { data(0).text() }
        email { data(1).text() }
        nationality { data(2).text() }
        dateCreated { data(3).text() }
        lastUpdated { data(4).text() }
    }
}
MODULE FOR REPEATED CONTENT
IN A PAGE
AttendeeListPage.groovy
static content = {
  menubar { module NavigationBarModule }
  attendees { moduleList AttendeeListItemModule,
                $("table tr").tail() }
}
MODULE FOR REPEATED CONTENT
IN A PAGE
when:
to AttendeeListPage
then:
attendees*.name.contains('Guillaume Laforge')
GEB SPEC - STRUCTURED (1)
Lets try to restructure the ugly spec from before
void "Go to list page ­ check initial state"() {
    when:
    to AttendeeIndexPage
    then:
    at AttendeeIndexPage
}
GEB SPEC - STRUCTURED (2)
void "Click new attendee button"() {
    when:
    menubar.newAttendee.click()
    then:
    at AttendeeCreatePage
}
GEB SPEC - STRUCTURED (3)
void "Submit form with errors"() {
    when:
    submitButton.click()
    then:
    at AttendeeCreatePage
}
GEB SPEC - STRUCTURED (4)
void "Submit form with no errors"() {
    when:
    form.name = 'Bob'
    form.email = 'bob@somemail.com'
    and:
    submitButton.click()
    then:
    at AttendeeShowPage
    name == 'Bob'
    email == 'bob@somemail.com'
}
GEB SPEC - STRUCTURED (5)
void "Click Edit Button"() {
    when:
    editButton.click()
    then:
    at AttendeeEditPage
}
GEB SPEC - STRUCTURED (6)
void "Update Attendee"() {
    when:
    form.name = 'Alice'
    form.email = 'alice@somemail.com'
    and:
    updateButton.click()
    then:
    at AttendeeShowPage
    title == 'Show Attendee'
    name == 'Alice'
    email == 'alice@somemail.com'
}
GEB WITH GRAILS
GEB AND GRAILS 2.X
Must install plugin in BuildConfig.groovy
dependencies {
  ...
  test("org.seleniumhq.selenium:selenium­support:2.45.0")
  test("org.seleniumhq.selenium:selenium­firefox­driver:2.45.0")
  test "org.gebish:geb­spock:0.10.0"
}
plugins {
  ...
  test "org.grails.plugins:geb:0.10.0"
}
GEB TESTS IN GRAILS 2.X
Tests placed in test/functionalfolder
Running the tests
grails test­app functional:
GEB AND GRAILS 3
Geb is default in build.gradle
dependencies {
  ...
  testCompile "org.grails.plugins:geb"
  // Note: It is recommended to update to a more robust driver
  // (Chrome, Firefox etc.)
  testRuntime 'org.seleniumhq.selenium:selenium­htmlunit­driver:2.44.0
}
GEB TESTS IN GRAILS 3
Creating Geb Spec
grails create­functional­test MyGebScenario
Placing the test in src/integration-test/groovy
Running the tests
grails test­app ­integration
GENERATED CLASS
@Integration
@Rollback
class ManyAttendeesSpec extends GebSpec {
    void "test something"() {
        when:"The home page is visited"
        go '/'
        then:"The title is correct"
        $('title').text() == "Welcome to Grails"
    }
}
INTERACTING WITH APPLICATION
When some functionality is needed that is not exposed
through the browser, it can be necessary to interact with the
application under test.
GRAILS 2.5
Tests not running in same JVM
Done with remote-control plugin
Send a closure for execution in application
compile ":remote­control:2.0"
REMOTE CONTROL
setup: 'Create some item not available through GUI'
def id = remote {
    Item item = new Item(name: "MyItem")
    item.save()
    item.id
}
GRAILS 3.0
Application is in same jvm
Interaction is possible directly
Tests run more like integration tests
INTERACTING WITH APPLICATION
void "Test Pagination is shown with 15 attendees"() {
    setup:
    Attendee.withNewTransaction {
       15.times {
          new Attendee(name: "N$it", email: "m$it@t.dk").save()
       }
    }
    when:
    to AttendeeIndexPage
    then:
    hasPagination()
}
INTERACTING WITH APPLICATION
static content = {
    menubar { module NavigationBarModule }
    pagination(required: false) { $('span.currentStep') }
}
boolean hasPagination() {
    pagination.text()
}
CONFIGURATION AND BROWSER
SUPPORT
GEBCONFIG
Configuration for Geb is placed in GebConfig.groovy
In Grails 3, place it in ´src/integration-test/groovy`
 http://www.gebish.org/manual/current/configuration.htm
DRIVER
It is possible to configure the browser used.
build.gradle
compile 'org.seleniumhq.selenium:selenium­chrome­driver:2.45.0'
compile 'org.seleniumhq.selenium:selenium­firefox­driver:2.45.0'
FIREFOX
GebConfig.groovy
import org.openqa.selenium.firefox.FirefoxProfile
import org.openqa.selenium.firefox.FirefoxDriver
driver = {
    FirefoxProfile profile = new FirefoxProfile()
    profile.setPreference("browser.download.folderList", 2)
    profile.setPreference("browser.download.dir", "/tmp")
    profile.setPreference(
         "browser.helperApps.neverAsk.saveToDisk", "text/csv")
    def driverInstance = new FirefoxDriver(profile)
    driverInstance.manage().window().maximize()
    driverInstance
}
CHROME
Needs ChromeDriver downloaded
Pretty fast and stable
CHROME (1)
GebConfig.groovy
private String driverLocationDependingOnOperatingSystem() {
    String os = System.getProperty("os.name").toLowerCase();
    def loc = "http://chromedriver.storage.googleapis.com/2.15"
    if( os.contains('mac')) {
        return "${loc}/chromedriver_mac32.zip"
    }
    if( os.contains('win')) {
        return "${loc}/chromedriver_win32.zip"
    }
    return "${loc}/chromedriver_linux64.zip"
}
CHROME (2)
GebConfig.groovy
private void downloadDriver(File file, String path) {
    if (!file.exists()) {
        def ant = new AntBuilder()
        ant.get(src: path, dest: 'driver.zip')
        ant.unzip(src: 'driver.zip', dest: file.parent)
        ant.delete(file: 'driver.zip')
        ant.chmod(file: file, perm: '700')
    }
}
CHROME (3)
GebConfig.groovy
def chromeDriver = new File('build/drivers/chrome/chromedriver')
downloadDriver(chromeDriver,
        driverLocationDependingOnOperatingSystem())
System.setProperty('webdriver.chrome.driver',
        chromeDriver.absolutePath)
driver = {
    def driverInstance = new ChromeDriver()
    def browserWindow = driverInstance.manage().window()
    // width, height
    browserWindow.size = new Dimension(1000, 2500)
    browserWindow.position = new Point(0, 0)
    driverInstance
}
WAITING
GebConfig.groovy
waiting {
    timeout = 10
    retryInterval = 0.5
}
baseNavigatorWaiting = true
atCheckWaiting = true
USING WAITING
<div class="fade­me­in" style="display: none">
  Hi ­ are yo waiting for me?
</div>
<script>
  $('div.fade­me­in').delay(3000).slideDown();
</script>
static content = {
    fadeInMessage{ $('div.fade­me­in') }
}
then:
waitFor {
    fadeInMessage.text() == 'Hi ­ are yo waiting for me?'
}
REPORTING
TEST REPORTS
Nicely formatted
Spock power-assert format
SCREENSHOTS
Screenshots and HTML from end of each test:
Extend from GebReportingSpec
class AttendeeFunctionalSpec extends GebReportingSpec
GebConfig.groovy
reportsDir = new File("build/geb­reports")
AD-HOC SCREENSHOTS
report "When­form­is­just­filled"
Saves a report in reportsDir
Numbered in increasing order
JAVASCRIPT
In case you need to interact using javascript
EXECUTING JAVASCRIPT
Clicking a button that is hidden will create a
ElementNotVisibleException
<fieldset class="well" style="display: none">
    <g:link class="btn" action="index">List</g:link>
</fieldset>
EXECUTING JAVASCRIPT
JavascriptExecutor executor = (JavascriptExecutor) driver
executor.executeScript('jQuery(".well").show();')
WRAPPING JAVASCRIPT
def js( String script ){
    (driver as JavascriptExecutor).executeScript( script )
}
js('jQuery(".well").show();')
OTHER USAGES
Screenscraping of a site
Solving complex problems like 2048
RESOURCES
http://gebish.org
https://github.com/geb
https://gist.github.com/melix/9619800
https://fbflex.wordpress.com/2010/08/25/geb-and-grails-
tips-tricks-and-gotchas/
https://github.com/JacobAae/eu-gr8conf-grailsdemo
QUESTIONS?

Contenu connexe

Tendances

Lunch and learn: Cucumber and Capybara
Lunch and learn: Cucumber and CapybaraLunch and learn: Cucumber and Capybara
Lunch and learn: Cucumber and Capybara
Marc Seeger
 
Usability in the GeoWeb
Usability in the GeoWebUsability in the GeoWeb
Usability in the GeoWeb
Dave Bouwman
 
Test automation expert days
Test automation   expert daysTest automation   expert days
Test automation expert days
Oren Rubin
 

Tendances (20)

DSL, Page Object and Selenium – a way to reliable functional tests
DSL, Page Object and Selenium – a way to reliable functional testsDSL, Page Object and Selenium – a way to reliable functional tests
DSL, Page Object and Selenium – a way to reliable functional tests
 
Lunch and learn: Cucumber and Capybara
Lunch and learn: Cucumber and CapybaraLunch and learn: Cucumber and Capybara
Lunch and learn: Cucumber and Capybara
 
Play framework 2 : Peter Hilton
Play framework 2 : Peter HiltonPlay framework 2 : Peter Hilton
Play framework 2 : Peter Hilton
 
Secrets of a Blazor Component Artisan
Secrets of a Blazor Component ArtisanSecrets of a Blazor Component Artisan
Secrets of a Blazor Component Artisan
 
Natural Language UI Testing using Behavior Driven Development with Pavlov and...
Natural Language UI Testing using Behavior Driven Development with Pavlov and...Natural Language UI Testing using Behavior Driven Development with Pavlov and...
Natural Language UI Testing using Behavior Driven Development with Pavlov and...
 
Testing Web Applications
Testing Web ApplicationsTesting Web Applications
Testing Web Applications
 
Automation Abstraction Layers: Page Objects and Beyond
Automation Abstraction Layers: Page Objects and BeyondAutomation Abstraction Layers: Page Objects and Beyond
Automation Abstraction Layers: Page Objects and Beyond
 
Blazor
BlazorBlazor
Blazor
 
Usability in the GeoWeb
Usability in the GeoWebUsability in the GeoWeb
Usability in the GeoWeb
 
Testing Any Site With Cucumber and Selenium
Testing Any Site With Cucumber and SeleniumTesting Any Site With Cucumber and Selenium
Testing Any Site With Cucumber and Selenium
 
Statistical Element Locator by Oren Rubin - SeleniumConf UK 2016
Statistical Element Locator by Oren Rubin - SeleniumConf UK 2016Statistical Element Locator by Oren Rubin - SeleniumConf UK 2016
Statistical Element Locator by Oren Rubin - SeleniumConf UK 2016
 
Testing with Codeception (Webelement #30)
Testing with Codeception (Webelement #30)Testing with Codeception (Webelement #30)
Testing with Codeception (Webelement #30)
 
Better Page Object Handling with Loadable Component Pattern
Better Page Object Handling with Loadable Component PatternBetter Page Object Handling with Loadable Component Pattern
Better Page Object Handling with Loadable Component Pattern
 
The Onion
The OnionThe Onion
The Onion
 
Test automation expert days
Test automation   expert daysTest automation   expert days
Test automation expert days
 
[Srijan Wednesday Webinars] Developing Large Scale Applications in AngularJS
[Srijan Wednesday Webinars] Developing Large Scale Applications in AngularJS[Srijan Wednesday Webinars] Developing Large Scale Applications in AngularJS
[Srijan Wednesday Webinars] Developing Large Scale Applications in AngularJS
 
Marcin Wasilczyk - Page objects with selenium
Marcin Wasilczyk - Page objects with seleniumMarcin Wasilczyk - Page objects with selenium
Marcin Wasilczyk - Page objects with selenium
 
Lets make a better react form
Lets make a better react formLets make a better react form
Lets make a better react form
 
20160905 - BrisJS - nightwatch testing
20160905 - BrisJS - nightwatch testing20160905 - BrisJS - nightwatch testing
20160905 - BrisJS - nightwatch testing
 
Selenium - The page object pattern
Selenium - The page object patternSelenium - The page object pattern
Selenium - The page object pattern
 

Similaire à Geb for testing your grails application

The Peanut Butter Cup of Web-dev: Plack and single page web apps
The Peanut Butter Cup of Web-dev: Plack and single page web appsThe Peanut Butter Cup of Web-dev: Plack and single page web apps
The Peanut Butter Cup of Web-dev: Plack and single page web apps
John Anderson
 
Automated UI Testing Done Right (QMSDNUG)
Automated UI Testing Done Right (QMSDNUG)Automated UI Testing Done Right (QMSDNUG)
Automated UI Testing Done Right (QMSDNUG)
Mehdi Khalili
 
Cut your hair and get an azure webjob
Cut your hair and get an azure webjobCut your hair and get an azure webjob
Cut your hair and get an azure webjob
Mark Greenway
 

Similaire à Geb for testing your grails application (20)

End-to-end testing with geb
End-to-end testing with gebEnd-to-end testing with geb
End-to-end testing with geb
 
Selenium
SeleniumSelenium
Selenium
 
The Peanut Butter Cup of Web-dev: Plack and single page web apps
The Peanut Butter Cup of Web-dev: Plack and single page web appsThe Peanut Butter Cup of Web-dev: Plack and single page web apps
The Peanut Butter Cup of Web-dev: Plack and single page web apps
 
APIs: A Better Alternative to Page Objects
APIs: A Better Alternative to Page ObjectsAPIs: A Better Alternative to Page Objects
APIs: A Better Alternative to Page Objects
 
Fewd week4 slides
Fewd week4 slidesFewd week4 slides
Fewd week4 slides
 
淺談 Geb 網站自動化測試(JCConf 2014)
淺談 Geb 網站自動化測試(JCConf 2014)淺談 Geb 網站自動化測試(JCConf 2014)
淺談 Geb 網站自動化測試(JCConf 2014)
 
Optimizely Developer Showcase
Optimizely Developer ShowcaseOptimizely Developer Showcase
Optimizely Developer Showcase
 
Introduction to Selenium and Ruby
Introduction to Selenium and RubyIntroduction to Selenium and Ruby
Introduction to Selenium and Ruby
 
Cordova: Making Native Mobile Apps With Your Web Skills
Cordova: Making Native Mobile Apps With Your Web SkillsCordova: Making Native Mobile Apps With Your Web Skills
Cordova: Making Native Mobile Apps With Your Web Skills
 
jQuery mobile UX
jQuery mobile UXjQuery mobile UX
jQuery mobile UX
 
Cut your hair and get an azure webjob
Cut your hair and get an azure webjobCut your hair and get an azure webjob
Cut your hair and get an azure webjob
 
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect ModelComprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
 
Custom Event Reporting from Flash to Google Analytics
Custom Event Reporting from Flash to Google AnalyticsCustom Event Reporting from Flash to Google Analytics
Custom Event Reporting from Flash to Google Analytics
 
Acceptance testing with Geb
Acceptance testing with GebAcceptance testing with Geb
Acceptance testing with Geb
 
Architecting test automation using selenium
Architecting test automation using seleniumArchitecting test automation using selenium
Architecting test automation using selenium
 
Automated UI Testing Done Right (QMSDNUG)
Automated UI Testing Done Right (QMSDNUG)Automated UI Testing Done Right (QMSDNUG)
Automated UI Testing Done Right (QMSDNUG)
 
Cut your hair and get an azure webjob
Cut your hair and get an azure webjobCut your hair and get an azure webjob
Cut your hair and get an azure webjob
 
Cut your hair and get an azure webjob
Cut your hair and get an azure webjobCut your hair and get an azure webjob
Cut your hair and get an azure webjob
 
Play framework
Play frameworkPlay framework
Play framework
 
BDD / cucumber /Capybara
BDD / cucumber /CapybaraBDD / cucumber /Capybara
BDD / cucumber /Capybara
 

Dernier

Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
chiefasafspells
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
masabamasaba
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 

Dernier (20)

Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 

Geb for testing your grails application