├── junit ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ └── specs │ │ │ │ ├── shared │ │ │ │ ├── common.gspec │ │ │ │ ├── commonLayout.gspec │ │ │ │ ├── calloutComponent.gspec │ │ │ │ ├── galen-extras-rules.js │ │ │ │ └── galen-extras-rules.gspec │ │ │ │ ├── googlePageLayout.gspec │ │ │ │ ├── cssPageLayout.gspec │ │ │ │ ├── homePageLayout.gspec │ │ │ │ └── javascriptPageLayout.gspec │ │ └── java │ │ │ └── sample │ │ │ ├── layout │ │ │ ├── HomeLayoutTest.java │ │ │ ├── JavascriptLayoutTest.java │ │ │ ├── CssLayoutTest.java │ │ │ └── GoogleLayoutTest.java │ │ │ └── util │ │ │ ├── GalenBaseTest.java │ │ │ └── junit │ │ │ └── LabelledParameterized.java │ └── main │ │ └── resources │ │ └── log4j.properties ├── build │ └── classes │ │ └── log4j.properties ├── README.md ├── config └── pom.xml ├── testng ├── .gitignore ├── build │ └── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ └── specs │ │ │ │ ├── shared │ │ │ │ ├── common.gspec │ │ │ │ ├── commonLayout.gspec │ │ │ │ ├── calloutComponent.gspec │ │ │ │ ├── galen-extras-rules.js │ │ │ │ └── galen-extras-rules.gspec │ │ │ │ ├── googlePageLayout.gspec │ │ │ │ ├── cssPageLayout.gspec │ │ │ │ ├── homePageLayout.gspec │ │ │ │ └── javascriptPageLayout.gspec │ │ └── java │ │ │ ├── sample │ │ │ └── layout │ │ │ │ ├── HomeLayoutTest.java │ │ │ │ ├── JavascriptLayoutTest.java │ │ │ │ ├── CssLayoutTest.java │ │ │ │ └── GoogleLayoutTest.java │ │ │ └── util │ │ │ └── testng │ │ │ └── GalenBaseTest.java │ └── main │ │ └── resources │ │ └── log4j.properties ├── README.md ├── config └── pom.xml ├── javascript ├── testSuite │ └── bootstrap │ │ ├── README.MD │ │ ├── specs │ │ ├── shared │ │ │ ├── common.gspec │ │ │ ├── commonLayout.gspec │ │ │ ├── calloutComponent.gspec │ │ │ ├── galen-extras-rules.js │ │ │ └── galen-extras-rules.gspec │ │ ├── googlePageLayout.gspec │ │ ├── cssPageLayout.gspec │ │ ├── homePageLayout.gspec │ │ └── javascriptPageLayout.gspec │ │ ├── runSauceLabs.sh │ │ ├── runGalenTests.sh │ │ ├── galen.config │ │ ├── basic.test │ │ └── saucelabs.test ├── gulp │ ├── test │ │ ├── specs │ │ │ ├── google_failing.gspec │ │ │ ├── google_success1.gspec │ │ │ └── google_success2.gspec │ │ └── basics.js │ ├── Gulpfile.js │ └── package.json ├── node │ ├── test │ │ ├── specs │ │ │ ├── google_failing.gspec │ │ │ ├── google_success1.gspec │ │ │ └── google_success2.gspec │ │ └── basics.js │ ├── README.MD │ └── package.json ├── grunt │ ├── gl.config.js │ ├── test │ │ ├── example-page.gspec │ │ ├── example.test.js │ │ └── galen_pages.js │ ├── package.json │ ├── Gruntfile.js │ └── gl.js ├── testRunner │ └── bootstrap │ │ ├── specs │ │ ├── shared │ │ │ ├── common.gspec │ │ │ ├── commonLayout.gspec │ │ │ ├── calloutComponent.gspec │ │ │ ├── galen-extras-rules.js │ │ │ └── galen-extras-rules.gspec │ │ ├── googlePageLayout.gspec │ │ ├── cssPageLayout.gspec │ │ ├── homePageLayout.gspec │ │ └── javascriptPageLayout.gspec │ │ ├── runGalenTests.sh │ │ ├── pages │ │ └── homePage.js │ │ ├── bootstrap.test.js │ │ └── init.js ├── runSauceLabs.sh ├── runCI.sh └── README.MD ├── README.md ├── .gitignore └── LICENSE /junit/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /testng/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /testng/build/.gitignore: -------------------------------------------------------------------------------- 1 | /classes/ 2 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/README.MD: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Saucelabs 4 | 5 | For more about mobile platforms on Saucelabs, see https://docs.saucelabs.com/reference/platforms-configurator/ -------------------------------------------------------------------------------- /javascript/gulp/test/specs/google_failing.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | below submit -------------------------------------------------------------------------------- /javascript/gulp/test/specs/google_success1.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | above submit -------------------------------------------------------------------------------- /javascript/gulp/test/specs/google_success2.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | above submit -------------------------------------------------------------------------------- /javascript/node/test/specs/google_failing.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | below submit -------------------------------------------------------------------------------- /javascript/node/test/specs/google_success1.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | above submit -------------------------------------------------------------------------------- /javascript/node/test/specs/google_success2.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | input css input[name=q] 3 | submit css input[type=submit] 4 | 5 | = Main section = 6 | input: 7 | above submit -------------------------------------------------------------------------------- /junit/src/test/resources/specs/shared/common.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | navbar .navbar 4 | header .navbar-header 5 | item-* .navbar-collapse .nav li 6 | menubar-left .sidebar-left 7 | content #content -------------------------------------------------------------------------------- /testng/src/test/resources/specs/shared/common.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | navbar .navbar 4 | header .navbar-header 5 | item-* .navbar-collapse .nav li 6 | menubar-left .sidebar-left 7 | content #content -------------------------------------------------------------------------------- /javascript/grunt/gl.config.js: -------------------------------------------------------------------------------- 1 | config.set({"url":"http://example.com/","devices":{"desktop":{"deviceName":"desktop","browser":"firefox","size":"1280x800"},"tablet":{"deviceName":"tablet","browser":"firefox","size":"768x576"}}}); 2 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/shared/common.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | navbar .navbar 4 | header .navbar-header 5 | item-* .navbar-collapse .nav li 6 | menubar-left .sidebar-left 7 | content #content -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/shared/common.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | navbar .navbar 4 | header .navbar-header 5 | item-* .navbar-collapse .nav li 6 | menubar-left .sidebar-left 7 | content #content -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/runSauceLabs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | currentDir=$( pwd ) 4 | websiteUrl='http://getbootstrap.com' 5 | 6 | galen test saucelabs.test -DwebsiteUrl=${websiteUrl} --htmlreport ../../reports/bootstrap/saucelabs 7 | open ../../reports/bootstrap/saucelabs/report.html -------------------------------------------------------------------------------- /javascript/grunt/test/example-page.gspec: -------------------------------------------------------------------------------- 1 | @objects 2 | label css h1 3 | paragraph css p 4 | 5 | = Main section = 6 | label: 7 | visible 8 | above paragraph 9 | 10 | paragraph: 11 | visible 12 | below label 13 | -------------------------------------------------------------------------------- /javascript/node/README.MD: -------------------------------------------------------------------------------- 1 | # Galen samples for Node 2 | 3 | 4 | ## Setup 5 | 6 | ```bash 7 | npm i 8 | 9 | ``` 10 | 11 | ## Execution 12 | 13 | ``` 14 | npm run galen -- check test/specs/google_success1.gspec --url 'https://www.google.com' --size '1280x800' --htmlreport dist/galen-report 15 | ``` -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/runGalenTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | currentDir=$( pwd ) 4 | websiteUrl='http://getbootstrap.com' 5 | 6 | galen test . --htmlreport ../../reports/bootstrap/testrunner -Dwebdriver.chrome.driver=/opt/dev/chromedriver 7 | open ../../reports/bootstrap/testrunner/report.html 8 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/googlePageLayout.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | search-input #lst-ib 4 | search-results #ires 5 | 6 | # concrete layout tests 7 | 8 | = Search input visible = 9 | search-input: 10 | visible 11 | 12 | = Show search results = 13 | search-results: 14 | visible -------------------------------------------------------------------------------- /testng/README.md: -------------------------------------------------------------------------------- 1 | # Galen samples for Maven and TestNG 2 | 3 | A showcase of Maven + TestNG + Galen usage 4 | 5 | Jenkins Sample Build: [![Build Status](https://martinreinhardt-online.de/jenkins/buildStatus/icon?job=Galen/Galen_sample_TestNG)](https://martinreinhardt-online.de/jenkins/job/Galen/job/Galen_sample_TestNG/) 6 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/googlePageLayout.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | search-input #lst-ib 4 | search-results #ires 5 | 6 | # concrete layout tests 7 | 8 | = Search input visible = 9 | search-input: 10 | visible 11 | 12 | = Show search results = 13 | search-results: 14 | visible -------------------------------------------------------------------------------- /javascript/grunt/test/example.test.js: -------------------------------------------------------------------------------- 1 | load('../gl.js'); 2 | 3 | forAll(config.getDevices(), function (device) { 4 | test('Example page on ' + device.deviceName, function () { 5 | gl.openPage(device, config.getProjectPage()); 6 | 7 | gl.runSpecFile(device, './test/example-page.gspec'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/googlePageLayout.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | search-input #lst-ib 4 | search-results #ires 5 | 6 | # concrete layout tests 7 | 8 | = Search input visible = 9 | search-input: 10 | visible 11 | 12 | = Show search results = 13 | search-results: 14 | visible -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/runGalenTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | currentDir=$( pwd ) 4 | websiteUrl='http://getbootstrap.com' 5 | 6 | galen test basic.test -DwebsiteUrl=${websiteUrl} --htmlreport ../../reports/bootstrap/local -Dwebdriver.chrome.driver=/opt/dev/chromedriver 7 | open ../../reports/bootstrap/local/report.html -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/googlePageLayout.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | search-input #lst-ib 4 | search-results #ires 5 | 6 | # concrete layout tests 7 | 8 | = Search input visible = 9 | search-input: 10 | visible 11 | 12 | = Show search results = 13 | search-results: 14 | visible -------------------------------------------------------------------------------- /javascript/runSauceLabs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | currentDir=$( pwd ) 4 | websiteUrl='http://getbootstrap.com' 5 | 6 | cd testSuite 7 | 8 | cd bootstrap 9 | galen test saucelabs.test -DwebsiteUrl=${websiteUrl} --htmlreport ../../reports/bootstrap/saucelabsTestsuite --testngreport ../../reports/bootstrap/saucelabsTestsuite/testng.xml 10 | -------------------------------------------------------------------------------- /javascript/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "galen_samples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "galen": "galen" 7 | }, 8 | "author": { 9 | "name": "Martin Reinhardt" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "galenframework-cli": "2.3.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/cssPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | css-hint-* div.bs-callout 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = code snippets should render correct = 12 | css-hint-*: 13 | component shared/calloutComponent.gspec -------------------------------------------------------------------------------- /testng/src/test/resources/specs/cssPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | css-hint-* div.bs-callout 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = code snippets should render correct = 12 | css-hint-*: 13 | component shared/calloutComponent.gspec -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Galen Samples 2 | ===================================== 3 | 4 | This project is used in order to demonstrate the features of [Galen Framework](http://galenframework.com) and the power of layout tests 5 | 6 | Here you find two sample projects: 7 | * Using [TestNG](testng) 8 | * Using [JUnit](junit) 9 | * Using [JavaScript](javascript) 10 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/cssPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | css-hint-* div.bs-callout 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = code snippets should render correct = 12 | css-hint-*: 13 | component shared/calloutComponent.gspec -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/cssPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | css-hint-* div.bs-callout 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = code snippets should render correct = 12 | css-hint-*: 13 | component shared/calloutComponent.gspec -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/pages/homePage.js: -------------------------------------------------------------------------------- 1 | this.HomePage = function (driver) { 2 | GalenPages.extendPage(this, driver, { 3 | goToGitHubButton: "(//*[contains(@class,'bs-docs-featurette')]//a[contains(@class,'btn')])[1]", 4 | goToExpoButton: "(//*[contains(@class,'bs-docs-featurette')]//a[contains(@class,'btn')])[2]" 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /javascript/gulp/Gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | gulpGalen = require('gulp-galenframework'); 3 | 4 | gulp.task("test:galen", function (done) { 5 | gulp.src('test/specs/**/google*.gspec').pipe(gulpGalen.check({ 6 | url: 'https://www.google.com', 7 | cwd: 'test/galen/', 8 | size: '1280x800' 9 | }, done)); 10 | }); -------------------------------------------------------------------------------- /junit/src/test/resources/specs/homePageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | bootstrap-logo span.bs-docs-booticon 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = Bootstrap logo should be visible = 12 | bootstrap-logo: 13 | | should be squared 14 | visible -------------------------------------------------------------------------------- /testng/src/test/resources/specs/homePageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | bootstrap-logo span.bs-docs-booticon 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = Bootstrap logo should be visible = 12 | bootstrap-logo: 13 | | should be squared 14 | visible -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/homePageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | bootstrap-logo span.bs-docs-booticon 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = Bootstrap logo should be visible = 12 | bootstrap-logo: 13 | | should be squared 14 | visible -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/homePageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | bootstrap-logo span.bs-docs-booticon 5 | 6 | # common layout checks 7 | @import shared/commonLayout.gspec 8 | 9 | # concrete layout tests 10 | 11 | = Bootstrap logo should be visible = 12 | bootstrap-logo: 13 | | should be squared 14 | visible -------------------------------------------------------------------------------- /javascript/gulp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-galen-sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "keywords": [ 6 | "gulp", 7 | "galen" 8 | ], 9 | "author": { 10 | "name": "Martin Reinhardt" 11 | }, 12 | "license": "MIT", 13 | "devDependencies": { 14 | "gulp": "^3.9.1", 15 | "gulp-galenframework": "^2.2.4" 16 | }, 17 | "scripts": { 18 | "test": "npm install && gulp test:galen" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/galen.config: -------------------------------------------------------------------------------- 1 | # Range approximation 2 | galen.range.approximation=3 3 | 4 | # Fullscreen screenshots 5 | galen.browser.screenshots.fullPage = true 6 | 7 | # Exit with fail code in case of any failures 8 | galen.use.fail.exit.code = true 9 | 10 | # Default value for tolerance in image spec 11 | galen.spec.image.tolerance = 30 12 | 13 | # Default value for error rate in image spec 14 | galen.spec.image.error = 0px 15 | 16 | -------------------------------------------------------------------------------- /javascript/grunt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-galen-sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "keywords": [ 6 | "grunt", 7 | "galen" 8 | ], 9 | "author": { 10 | "name": "Martin Reinhardt" 11 | }, 12 | "license": "MIT", 13 | "devDependencies": { 14 | "grunt": "^0.4.5", 15 | "grunt-cli": "^0.1.13", 16 | "grunt-galenframework": "^2.2.4", 17 | "load-grunt-tasks": "^3.5.0" 18 | }, 19 | "scripts": { 20 | "test": "npm install && grunt" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/javascriptPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | js-code-snippet-* div.bs-callout 5 | ad-container #carbonads-container 6 | ad .carbonad 7 | 8 | # common layout checks 9 | @import shared/commonLayout.gspec 10 | 11 | # concrete layout tests 12 | 13 | = Code snippets should be vertical aligned = 14 | @forEach [js-code-snippet-*] as snippet, next as nextSnippet 15 | ${snippet}: 16 | aligned vertically left ${nextSnippet} 17 | 18 | = Ad should be centered = 19 | ad: 20 | centered horizontally on ad-container 21 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/javascriptPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | js-code-snippet-* div.bs-callout 5 | ad-container #carbonads-container 6 | ad .carbonad 7 | 8 | # common layout checks 9 | @import shared/commonLayout.gspec 10 | 11 | # concrete layout tests 12 | 13 | = Code snippets should be vertical aligned = 14 | @forEach [js-code-snippet-*] as snippet, next as nextSnippet 15 | ${snippet}: 16 | aligned vertically left ${nextSnippet} 17 | 18 | = Ad should be centered = 19 | ad: 20 | centered horizontally on ad-container 21 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/javascriptPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | js-code-snippet-* div.bs-callout 5 | ad-container #carbonads-container 6 | ad .carbonad 7 | 8 | # common layout checks 9 | @import shared/commonLayout.gspec 10 | 11 | # concrete layout tests 12 | 13 | = Code snippets should be vertical aligned = 14 | @forEach [js-code-snippet-*] as snippet, next as nextSnippet 15 | ${snippet}: 16 | aligned vertically left ${nextSnippet} 17 | 18 | = Ad should be centered = 19 | ad: 20 | centered horizontally on ad-container 21 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/javascriptPageLayout.gspec: -------------------------------------------------------------------------------- 1 | @import shared/common.gspec 2 | 3 | @objects 4 | js-code-snippet-* div.bs-callout 5 | ad-container #carbonads-container 6 | ad .carbonad 7 | 8 | # common layout checks 9 | @import shared/commonLayout.gspec 10 | 11 | # concrete layout tests 12 | 13 | = Code snippets should be vertical aligned = 14 | @forEach [js-code-snippet-*] as snippet, next as nextSnippet 15 | ${snippet}: 16 | aligned vertically left ${nextSnippet} 17 | 18 | = Ad should be centered = 19 | ad: 20 | centered horizontally on ad-container 21 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/shared/commonLayout.gspec: -------------------------------------------------------------------------------- 1 | @import galen-extras-rules.gspec 2 | 3 | = Overall layout = 4 | 5 | content: 6 | visible 7 | navbar.header: 8 | visible 9 | content: 10 | below navbar 11 | 12 | = navigation = 13 | 14 | @on mobile 15 | navbar.item-*: 16 | absent 17 | 18 | @on desktop 19 | navbar.item-*: 20 | visible 21 | 22 | = Content should fit to screen size = 23 | 24 | @on mobile 25 | content: 26 | width 100% of screen/width 27 | 28 | @on desktop 29 | content: 30 | width 80 to 90% of screen/width 31 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/shared/commonLayout.gspec: -------------------------------------------------------------------------------- 1 | @import galen-extras-rules.gspec 2 | 3 | = Overall layout = 4 | 5 | content: 6 | visible 7 | navbar.header: 8 | visible 9 | content: 10 | below navbar 11 | 12 | = navigation = 13 | 14 | @on mobile 15 | navbar.item-*: 16 | absent 17 | 18 | @on desktop 19 | navbar.item-*: 20 | visible 21 | 22 | = Content should fit to screen size = 23 | 24 | @on mobile 25 | content: 26 | width 100% of screen/width 27 | 28 | @on desktop 29 | content: 30 | width 80 to 90% of screen/width 31 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/shared/commonLayout.gspec: -------------------------------------------------------------------------------- 1 | @import galen-extras-rules.gspec 2 | 3 | = Overall layout = 4 | 5 | content: 6 | visible 7 | navbar.header: 8 | visible 9 | content: 10 | below navbar 11 | 12 | = navigation = 13 | 14 | @on mobile 15 | navbar.item-*: 16 | absent 17 | 18 | @on desktop 19 | navbar.item-*: 20 | visible 21 | 22 | = Content should fit to screen size = 23 | 24 | @on mobile 25 | content: 26 | width 100% of screen/width 27 | 28 | @on desktop 29 | content: 30 | width 80 to 90% of screen/width 31 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/shared/commonLayout.gspec: -------------------------------------------------------------------------------- 1 | @import galen-extras-rules.gspec 2 | 3 | = Overall layout = 4 | 5 | content: 6 | visible 7 | navbar.header: 8 | visible 9 | content: 10 | below navbar 11 | 12 | = navigation = 13 | 14 | @on mobile 15 | navbar.item-*: 16 | absent 17 | 18 | @on desktop 19 | navbar.item-*: 20 | visible 21 | 22 | = Content should fit to screen size = 23 | 24 | @on mobile 25 | content: 26 | width 100% of screen/width 27 | 28 | @on desktop 29 | content: 30 | width 80 to 90% of screen/width 31 | -------------------------------------------------------------------------------- /testng/src/test/java/sample/layout/HomeLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.testng.annotations.Test; 9 | 10 | import util.testng.GalenBaseTest; 11 | 12 | /** 13 | * @author mreinhardt 14 | * 15 | */ 16 | public class HomeLayoutTest extends GalenBaseTest { 17 | 18 | @Test(dataProvider = "devices") 19 | public void shouldShowCorrectBaseLayout(final TestDevice device) throws Exception { 20 | verifyPage("/", 21 | device, 22 | "/specs/homePageLayout.gspec", 23 | Arrays.asList("Homepage", "Bootstrap")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /javascript/runCI.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | currentDir=$( pwd ) 4 | websiteUrl='http://getbootstrap.com' 5 | 6 | cd testSuite 7 | 8 | cd bootstrap 9 | galen test basic.test -DwebsiteUrl=${websiteUrl} --htmlreport ../../reports/bootstrap/testsuite --testngreport ../../reports/bootstrap/testsuite/testng.xml -Dwebdriver.chrome.driver=/opt/dev/tools/chromedriver 10 | 11 | cd ../../testRunner 12 | 13 | cd bootstrap 14 | galen test . --htmlreport ../../reports/bootstrap/testrunner/ --testngreport ../../reports/bootstrap/testrunner/testng.xml -Dwebdriver.chrome.driver=/opt/dev/tools/chromedriver 15 | 16 | cd ../../gulp 17 | npm test 18 | 19 | cd ../../grunt 20 | npm test 21 | -------------------------------------------------------------------------------- /javascript/grunt/test/galen_pages.js: -------------------------------------------------------------------------------- 1 | this.SubmitPage = function (driver) { 2 | GalenPages.extendPage(this, driver, "Submit Page", { 3 | nameTextfield: "xpath: //*[@id='content']/div/input", // xpath locator 4 | submitButton: "xpath: //*[@id='content']/div/a" // xpath locator 5 | 6 | }); 7 | }; 8 | 9 | 10 | test("Home page test", function () { 11 | var driver = createDriver("http://samples.galenframework.com/tutorial-color-scheme/tutorial.html", "1400x1100", "firefox"); 12 | 13 | var submitPage = new SubmitPage(driver); 14 | 15 | submitPage.nameTextfield.typeText("Something"); 16 | submitPage.submitButton.click(); 17 | 18 | driver.close(); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /testng/src/test/java/sample/layout/JavascriptLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.testng.annotations.Test; 9 | 10 | import util.testng.GalenBaseTest; 11 | 12 | /** 13 | * @author mreinhardt 14 | * 15 | */ 16 | public class JavascriptLayoutTest extends GalenBaseTest { 17 | 18 | @Test(dataProvider = "devices") 19 | public void shouldShowCorrectBaseLayout(final TestDevice device) throws Exception { 20 | verifyPage("/javascript", 21 | device, 22 | "/specs/javascriptPageLayout.gspec", 23 | Arrays.asList("Javascript", "Bootstrap")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /junit/src/test/java/sample/layout/HomeLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import org.junit.Test; 7 | import sample.util.GalenBaseTest; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * @author mreinhardt 13 | */ 14 | public class HomeLayoutTest extends GalenBaseTest { 15 | 16 | /** 17 | * @param pTestDevice 18 | */ 19 | public HomeLayoutTest(TestDevice pTestDevice) { 20 | super(pTestDevice); 21 | } 22 | 23 | @Test 24 | public void shouldShowCorrectBaseLayout() throws Exception { 25 | verifyPage("/", "/specs/homePageLayout.gspec", Arrays.asList("Homepage", "Bootstrap")); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /junit/build/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO,console,file 2 | # disable hibernate debugging logging 3 | log4j.logger.org.hibernate=ERROR 4 | 5 | #console 6 | log4j.appender.console=org.apache.log4j.ConsoleAppender 7 | log4j.appender.console.Threshold=ERROR 8 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.MeinConsoleAppender.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 10 | 11 | #output in .log 12 | log4j.appender.file=org.apache.log4j.RollingFileAppender 13 | log4j.appender.file.file=logs/galen.log 14 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.file.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 16 | -------------------------------------------------------------------------------- /junit/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO,console,file 2 | # disable hibernate debugging logging 3 | log4j.logger.org.hibernate=ERROR 4 | 5 | #console 6 | log4j.appender.console=org.apache.log4j.ConsoleAppender 7 | log4j.appender.console.Threshold=ERROR 8 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.MeinConsoleAppender.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 10 | 11 | #output in .log 12 | log4j.appender.file=org.apache.log4j.RollingFileAppender 13 | log4j.appender.file.file=logs/galen.log 14 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.file.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 16 | -------------------------------------------------------------------------------- /testng/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO,console,file 2 | # disable hibernate debugging logging 3 | log4j.logger.org.hibernate=ERROR 4 | 5 | #console 6 | log4j.appender.console=org.apache.log4j.ConsoleAppender 7 | log4j.appender.console.Threshold=ERROR 8 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.MeinConsoleAppender.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 10 | 11 | #output in .log 12 | log4j.appender.file=org.apache.log4j.RollingFileAppender 13 | log4j.appender.file.file=logs/galen.log 14 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.file.layout.ConversionPattern=%d{ISO8601} [%-5p] %-12c: %m%n 16 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/bootstrap.test.js: -------------------------------------------------------------------------------- 1 | load("init.js"); 2 | load("pages/homePage.js"); 3 | 4 | testOnAllDevices("Bootstrap homepage", "/", function (driver, device) { 5 | var homePage = new HomePage(driver).waitForIt(); 6 | checkLayout(driver, "specs/homePageLayout.gspec", device.tags); 7 | homePage.goToExpoButton().click(); 8 | }); 9 | 10 | testOnDeviceAndBrowsers(devices.mobile, "Bootstrap CSS page", "/css/", function (driver, device, browser) { 11 | checkLayout(driver, "specs/cssPageLayout.gspec", device.tags); 12 | }); 13 | 14 | testOnAllDevicesAndBrowsers("Bootstrap JS page ", "/javascript/", function (driver, device, browser) { 15 | checkLayout(driver, "specs/javascriptPageLayout.gspec", device.tags); 16 | }); 17 | -------------------------------------------------------------------------------- /junit/src/test/java/sample/layout/JavascriptLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import org.junit.Test; 7 | import sample.util.GalenBaseTest; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * @author mreinhardt 13 | */ 14 | public class JavascriptLayoutTest extends GalenBaseTest { 15 | 16 | /** 17 | * @param pTestDevice 18 | */ 19 | public JavascriptLayoutTest(TestDevice pTestDevice) { 20 | super(pTestDevice); 21 | } 22 | 23 | @Test 24 | public void shouldShowCorrectBaseLayout() throws Exception { 25 | verifyPage("/javascript", 26 | "/specs/javascriptPageLayout.gspec", 27 | Arrays.asList("Javascript", "Bootstrap")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/shared/calloutComponent.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | heading h4 4 | text-* p 5 | 6 | = callout = on phone device 7 | @on phone 8 | heading: 9 | inside parent 10px top left 10 | width 90 to 100% of screen/width 11 | text-*: 12 | inside parent 10px top left 13 | width 90 to 100% of screen/width 14 | 15 | @on tablet 16 | heading: 17 | inside parent 10px top left 18 | width 80 to 90% of screen/width 19 | text-*: 20 | inside parent 10px top left 21 | width 80 to 90% of screen/width 22 | 23 | 24 | @on desktop 25 | heading: 26 | inside: parent 10px top left 27 | width: 75 to 85% of screen/width 28 | text-*: 29 | inside: parent 10px top left 30 | width: 75 to 85% of screen/width 31 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/shared/calloutComponent.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | heading h4 4 | text-* p 5 | 6 | = callout = on phone device 7 | @on phone 8 | heading: 9 | inside parent 10px top left 10 | width 90 to 100% of screen/width 11 | text-*: 12 | inside parent 10px top left 13 | width 90 to 100% of screen/width 14 | 15 | @on tablet 16 | heading: 17 | inside parent 10px top left 18 | width 80 to 90% of screen/width 19 | text-*: 20 | inside parent 10px top left 21 | width 80 to 90% of screen/width 22 | 23 | 24 | @on desktop 25 | heading: 26 | inside: parent 10px top left 27 | width: 75 to 85% of screen/width 28 | text-*: 29 | inside: parent 10px top left 30 | width: 75 to 85% of screen/width 31 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/shared/calloutComponent.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | heading h4 4 | text-* p 5 | 6 | = callout = on phone device 7 | @on phone 8 | heading: 9 | inside parent 10px top left 10 | width 90 to 100% of screen/width 11 | text-*: 12 | inside parent 10px top left 13 | width 90 to 100% of screen/width 14 | 15 | @on tablet 16 | heading: 17 | inside parent 10px top left 18 | width 80 to 90% of screen/width 19 | text-*: 20 | inside parent 10px top left 21 | width 80 to 90% of screen/width 22 | 23 | 24 | @on desktop 25 | heading: 26 | inside: parent 10px top left 27 | width: 75 to 85% of screen/width 28 | text-*: 29 | inside: parent 10px top left 30 | width: 75 to 85% of screen/width 31 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/shared/calloutComponent.gspec: -------------------------------------------------------------------------------- 1 | 2 | @objects 3 | heading h4 4 | text-* p 5 | 6 | = callout = on phone device 7 | @on phone 8 | heading: 9 | inside parent 10px top left 10 | width 90 to 100% of screen/width 11 | text-*: 12 | inside parent 10px top left 13 | width 90 to 100% of screen/width 14 | 15 | @on tablet 16 | heading: 17 | inside parent 10px top left 18 | width 80 to 90% of screen/width 19 | text-*: 20 | inside parent 10px top left 21 | width 80 to 90% of screen/width 22 | 23 | 24 | @on desktop 25 | heading: 26 | inside: parent 10px top left 27 | width: 75 to 85% of screen/width 28 | text-*: 29 | inside: parent 10px top left 30 | width: 75 to 85% of screen/width 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | testng/test-output/* 15 | testng/.project 16 | testng/.settings/* 17 | testng/.classpath 18 | testng/logs/* 19 | testng/build/classes/* 20 | 21 | junit/.project 22 | junit/.classpath 23 | junit/.settings/* 24 | junit/test-output/* 25 | junit/logs/* 26 | junit/build/classes/* 27 | 28 | reports/* 29 | 30 | javascript/testRunner/bootstrap/reports/* 31 | javascript/reports/* 32 | javascript/node/*.log 33 | javascript/node/dist/ 34 | javascript/node/node_modules/ 35 | javascript/grunt/node_modules/ 36 | javascript/gulp/node_modules/ 37 | 38 | ## IDEA 39 | .idea/* 40 | javascript/*.iml 41 | junit/*.iml 42 | testng/*.iml 43 | -------------------------------------------------------------------------------- /junit/README.md: -------------------------------------------------------------------------------- 1 | # Galen samples for Maven and JUnit 2 | 3 | A showcase of Maven + JUnit + Galen usage 4 | 5 | Jenkins Sample Build: [![Build Status](https://martinreinhardt-online.de/jenkins/buildStatus/icon?job=Galen/Galen_sample_JUnit)](https://martinreinhardt-online.de/jenkins/job/Galen/job/Galen_sample_JUnit/) 6 | 7 | ## Steps touse this sample for your own 8 | 9 | You need Maven 3+ installed. 10 | 11 | * Download the [repo](https://github.com/hypery2k/galen_samples/archive/master.zip) 12 | * Unzip the folder 13 | * Open command prompt in extracted folder and go to the junit folder and run the tests: 14 | ``` 15 | cd junit 16 | mvn verify 17 | ``` 18 | 19 | ## Extend the sample 20 | 21 | * To extend the project, run the following command: 22 | ``` 23 | cd junit 24 | mvn eclipse:eclipse idea:idea 25 | ``` 26 | * Import the Junit project (junit-folder) in IDEA and Eclipse 27 | -------------------------------------------------------------------------------- /javascript/README.MD: -------------------------------------------------------------------------------- 1 | # Galen samples for JavaScript 2 | 3 | 4 | ## Setup 5 | 6 | ```bash 7 | (sudo) npm install -g galenframework-cli 8 | 9 | ``` 10 | 11 | ## Execution 12 | 13 | A showcase of JavaScript + Galen usage 14 | 15 | Jenkins Sample Build: [![Build Status](https://martinreinhardt-online.de/jenkins/buildStatus/icon?job=Galen/Galen_sample_JavaScript)](https://martinreinhardt-online.de/jenkins/job/Galen/job/Galen_sample_JavaScript/) 16 | 17 | The sample script [runCI.sh](runCI.sh) just wrap up some calls for CI build within Jenkins: 18 | 19 | ```bash 20 | galen test . -DwebsiteUrl=${websiteUrl} --htmlreport ../../../reports/shopping-cart --testngreport ../../../reports/shopping-cart/testng.xml -Dwebdriver.chrome.driver=/opt/dev/chromedriver 21 | 22 | ``` 23 | The following arguments are used: 24 | 25 | * -DwebsiteUrl - a url of page for Galen to test on (passed to test file) 26 | * -Dwebdriver.chrome.driver - path the ChromeDriver 27 | * --htmlreport - path to folder in which Galen should generate html reports 28 | * --testngreport - path to xml file in which Galen should write testng report 29 | -------------------------------------------------------------------------------- /testng/src/test/java/sample/layout/CssLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.openqa.selenium.By; 9 | import org.testng.annotations.Test; 10 | 11 | import util.testng.GalenBaseTest; 12 | 13 | /** 14 | * @author mreinhardt 15 | * 16 | */ 17 | public class CssLayoutTest extends GalenBaseTest { 18 | 19 | public final static String NAV_FORM_BTN = "(//*[contains(@class,'bs-docs-sidenav')]/li/a)[6]"; 20 | 21 | public final static String INPUT_EMAIL = "//*[contains(@data-example-id,'basic-forms')]//input[contains(@type,'email')]"; 22 | 23 | 24 | @Test(dataProvider = "devices") 25 | public void shouldShowCorrectBaseLayout(final TestDevice device) throws Exception { 26 | // or use verifyPage("/css","/specs/cssPageLayout.gspec"); 27 | load("/css/#forms"); 28 | clickElement(By.xpath(NAV_FORM_BTN)); 29 | enterText(By.xpath(INPUT_EMAIL),"invalidEmail"); 30 | verifyPage(device, 31 | "/specs/cssPageLayout.gspec", 32 | Arrays.asList("Css", "Bootstrap")); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /testng/src/test/java/sample/layout/GoogleLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | 7 | import java.util.Arrays; 8 | 9 | import org.openqa.selenium.By; 10 | import org.testng.annotations.Test; 11 | 12 | import util.testng.GalenBaseTest; 13 | 14 | /** 15 | * @author mreinhardt 16 | * 17 | */ 18 | public class GoogleLayoutTest extends GalenBaseTest { 19 | 20 | public final static String NAV_FORM_BTN = "(//*[contains(@class,'bs-docs-sidenav')]/li/a)[6]"; 21 | 22 | public final static String INPUT_EMAIL = "//*[contains(@data-example-id,'basic-forms')]//input[contains(@type,'email')]"; 23 | 24 | protected String getDefaultURL(){ 25 | return "https://google.com"; 26 | } 27 | 28 | @Test(dataProvider = "devices") 29 | public void shouldShowCorrectBaseLayout(final TestDevice device) throws Exception { 30 | load("/"); 31 | enterText(By.id("lst-ib"),"Galen Testing"); 32 | clickElement(By.xpath("//*[contains(@class,'lsb')]//button")); 33 | verifyPage(device, 34 | "/specs/googlePageLayout.gspec", 35 | Arrays.asList("Google")); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /junit/src/test/java/sample/layout/CssLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import org.junit.Test; 7 | import org.openqa.selenium.By; 8 | import sample.util.GalenBaseTest; 9 | 10 | import java.util.Arrays; 11 | 12 | /** 13 | * @author mreinhardt 14 | */ 15 | public class CssLayoutTest extends GalenBaseTest { 16 | 17 | public final static String NAV_FORM_BTN = "(//*[contains(@class,'bs-docs-sidenav')]/li/a)[6]"; 18 | 19 | public final static String INPUT_EMAIL = "//*[contains(@data-example-id,'basic-forms')]//input[contains(@type,'email')]"; 20 | 21 | /** 22 | * @param pTestDevice 23 | */ 24 | public CssLayoutTest(TestDevice pTestDevice) { 25 | super(pTestDevice); 26 | } 27 | 28 | @Test 29 | public void shouldShowCorrectBaseLayout() throws Exception { 30 | // or use verifyPage("/css","/specs/cssPageLayout.gspec"); 31 | load("/css/#forms"); 32 | clickElement(By.xpath(NAV_FORM_BTN)); 33 | enterText(By.xpath(INPUT_EMAIL), "invalidEmail"); 34 | verifyPage("/specs/cssPageLayout.gspec", Arrays.asList("Css", "Bootstrap")); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Martin Reinhardt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /junit/src/test/java/sample/layout/GoogleLayoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.layout; 5 | 6 | import org.junit.Test; 7 | import org.openqa.selenium.By; 8 | import sample.util.GalenBaseTest; 9 | 10 | import java.util.Arrays; 11 | 12 | /** 13 | * @author mreinhardt 14 | */ 15 | public class GoogleLayoutTest extends GalenBaseTest { 16 | 17 | public final static String NAV_FORM_BTN = "(//*[contains(@class,'bs-docs-sidenav')]/li/a)[6]"; 18 | 19 | public final static String INPUT_EMAIL = "//*[contains(@data-example-id,'basic-forms')]//input[contains(@type,'email')]"; 20 | 21 | /** 22 | * @param pTestDevice 23 | */ 24 | public GoogleLayoutTest(TestDevice pTestDevice) { 25 | super(pTestDevice); 26 | } 27 | 28 | protected String getDefaultURL() { 29 | return "https://google.com"; 30 | } 31 | 32 | @Test 33 | public void shouldShowCorrectBaseLayout() throws Exception { 34 | load("/"); 35 | enterText(By.id("lst-ib"), "Galen Testing"); 36 | clickElement(By.xpath("//*[contains(@class,'lsb')]//button")); 37 | verifyPage("/specs/googlePageLayout.gspec", Arrays.asList("Google")); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/basic.test: -------------------------------------------------------------------------------- 1 | @@ table browsers 2 | | browerName | browser | 3 | | Chrome | chrome | 4 | | Firefox | firefox | 5 | @@ table devices 6 | | deviceName | tags | size | 7 | | Mobile | mobile | 320x600 | 8 | | Tablet | tablet | 640x480 | 9 | | Desktop | desktop | 1024x800 | 10 | 11 | @@ parameterized using browsers 12 | @@ parameterized using devices 13 | @@ groups home, page 14 | homepage on ${deviceName} in ${browerName} browser 15 | selenium ${browser} ${websiteUrl} ${size} 16 | check specs/homePageLayout.gspec --include "${device}" 17 | 18 | @@ parameterized using browsers 19 | @@ parameterized using devices 20 | @@ groups javascript, page 21 | javascript page on ${deviceName} in ${browerName} browser 22 | selenium ${browser} ${websiteUrl} ${size} 23 | check specs/homePageLayout.gspec --include "${device}" 24 | check specs/javascriptPageLayout.gspec --include "${device}" 25 | 26 | @@ parameterized using browsers 27 | @@ parameterized using devices 28 | @@ groups css, page 29 | css page on ${deviceName} in ${browerName} browser 30 | selenium ${browser} ${websiteUrl} ${size} 31 | check specs/cssPageLayout.gspec --include "${tags}" -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/saucelabs.test: -------------------------------------------------------------------------------- 1 | @@ set 2 | sauceKey aff16b42-9c23-4cb6-adf7-38da9e02193a 3 | sauceUser galen_mreinhardt 4 | gridLogin ${sauceUser}:${sauceKey} 5 | gridUrl http://${gridLogin}@ondemand.saucelabs.com:80/wd/hub 6 | @@ table browsers 7 | | browserName | gridArgs | 8 | | Safari on Mac | --browser "safari" --version 8 --dc.platform "OS X 10.10" | 9 | | Opera | --browser "opera" --version 12 --dc.platform "Linux" | 10 | | Firefox | --browser "firefox" --version 34 --dc.platform "Linux" | 11 | | Chrome | --browser "chrome" --version 39 --dc.platform "Linux" | 12 | | IE 11 | --browser "internet explorer" --version 11 --dc.platform "Windows 8.1" | 13 | | Mobile Safari iOS 10.0 | --browser "Safari" --dc.deviceName "iPhone Simulator" --dc.deviceOrientation "portrait" --dc.platformName "iOS" --dc.platformVersion "10.2" --dc.appiumVersion "1.7.1" | 14 | | Mobile Chrome Android 6.0 | --browser "Chrome" --dc.deviceName "Android Emulator" --dc.deviceOrientation "portrait" --dc.platformName "Android" --dc.platformVersion "6.0" --dc.appiumVersion "1.7.1" | 15 | @@ parameterized using browsers 16 | Home page on ${browserName} browser 17 | selenium grid ${gridUrl} --page ${websiteUrl} ${gridArgs} 18 | check specs/homePageLayout.gspec --include "${device}" 19 | -------------------------------------------------------------------------------- /junit/config: -------------------------------------------------------------------------------- 1 | 2 | # Range approximation 3 | # ~~~~~~~~~~~~~~~~~~~~~ 4 | # Defines the approximation value for ranges when using "~" in galen page specs 5 | # This value means how many pixels or percents should it take constructing a range 6 | # e.g. if we define approximation as 5 then the following spec: 7 | # height: ~ 50 px 8 | # will actually be replaced by Galen with this: 9 | # height: 45 to 55px 10 | galen.range.approximation=3 11 | 12 | 13 | # Custom Listeners 14 | # ~~~~~~~~~~~~~~~~~~~~~~~ 15 | # A comma separated list of class paths to reporting listeners 16 | # The defined listeners will be picked up by Galen and used for reporting 17 | # 18 | # galen.reporting.listeners= 19 | 20 | 21 | # Full screenshots 22 | # ~~~~~~~~~~~~~~~~~~~~ 23 | # In some browsers it is not possible to create a complete screenshot of whole page. 24 | # With this property enabled Galen will scroll page and make screenshots of parts of it. 25 | # Then it will assemble it in a one big screenshot 26 | # 27 | galen.browser.screenshots.fullPage = true 28 | 29 | 30 | 31 | # Default browser 32 | # ~~~~~~~~~~~~~~~~~~~~~~~~ 33 | # A browser that should be used by default in case it was not specified in galen test 34 | galen.default.browser=chrome 35 | 36 | 37 | # Color scheme spec precision 38 | # ~~~~~~~~~~~~~~~~~~~~~~~~ 39 | # A value between 8 and 256 for color spectrum accuracy. 40 | spec.colorscheme.precision = 256 41 | 42 | 43 | # Using page urls from last checked page in HTML report 44 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | # This property enables the use of last page url in a page test 46 | # Needed when for some pages on the website there is no way to open it by direct url 47 | # galen.reporting.html.useLastPageUrls = true 48 | 49 | 50 | # Color scheme spec test color range 51 | # ~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | # A value between 0 and 256 which defined the range of nearby colors 53 | # in spectrum which will be picked up for calculating the percentage of usage 54 | spec.colorscheme.testrange = 6 55 | 56 | 57 | # Running in Selenium Grid 58 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | # You can run your tests in Selenium Grid without modifying the tests. 60 | # Just enable the "galen.browserFactory.selenium.runInGrid" property 61 | # and Galen will always choose a Selenium Grid instead of running tests against local browsers 62 | # Also make sure you provide the proper url to grid 63 | # 64 | galen.browserFactory.selenium.runInGrid = true 65 | galen.browserFactory.selenium.grid.url = http://localhost:4444/wd/hub 66 | galen.browserFactory.selenium.grid.browser = Firefox 67 | galen.browserFactory.selenium.grid.browserVersion = 16 68 | galen.browserFactory.selenium.grid.platform = XP 69 | 70 | 71 | # Exit with fail code in case of any failures 72 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | galen.use.fail.exit.code = true -------------------------------------------------------------------------------- /testng/config: -------------------------------------------------------------------------------- 1 | 2 | # Range approximation 3 | # ~~~~~~~~~~~~~~~~~~~~~ 4 | # Defines the approximation value for ranges when using "~" in galen page specs 5 | # This value means how many pixels or percents should it take constructing a range 6 | # e.g. if we define approximation as 5 then the following spec: 7 | # height: ~ 50 px 8 | # will actually be replaced by Galen with this: 9 | # height: 45 to 55px 10 | galen.range.approximation=3 11 | 12 | 13 | # Custom Listeners 14 | # ~~~~~~~~~~~~~~~~~~~~~~~ 15 | # A comma separated list of class paths to reporting listeners 16 | # The defined listeners will be picked up by Galen and used for reporting 17 | # 18 | # galen.reporting.listeners= 19 | 20 | 21 | # Full screenshots 22 | # ~~~~~~~~~~~~~~~~~~~~ 23 | # In some browsers it is not possible to create a complete screenshot of whole page. 24 | # With this property enabled Galen will scroll page and make screenshots of parts of it. 25 | # Then it will assemble it in a one big screenshot 26 | # 27 | galen.browser.screenshots.fullPage = true 28 | 29 | 30 | 31 | # Default browser 32 | # ~~~~~~~~~~~~~~~~~~~~~~~~ 33 | # A browser that should be used by default in case it was not specified in galen test 34 | galen.default.browser=firefox 35 | 36 | 37 | # Color scheme spec precision 38 | # ~~~~~~~~~~~~~~~~~~~~~~~~ 39 | # A value between 8 and 256 for color spectrum accuracy. 40 | spec.colorscheme.precision = 256 41 | 42 | 43 | # Using page urls from last checked page in HTML report 44 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | # This property enables the use of last page url in a page test 46 | # Needed when for some pages on the website there is no way to open it by direct url 47 | # galen.reporting.html.useLastPageUrls = true 48 | 49 | 50 | # Color scheme spec test color range 51 | # ~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | # A value between 0 and 256 which defined the range of nearby colors 53 | # in spectrum which will be picked up for calculating the percentage of usage 54 | spec.colorscheme.testrange = 6 55 | 56 | 57 | # Running in Selenium Grid 58 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | # You can run your tests in Selenium Grid without modifying the tests. 60 | # Just enable the "galen.browserFactory.selenium.runInGrid" property 61 | # and Galen will always choose a Selenium Grid instead of running tests against local browsers 62 | # Also make sure you provide the proper url to grid 63 | # 64 | galen.browserFactory.selenium.runInGrid = true 65 | galen.browserFactory.selenium.grid.url = http://localhost:4444/wd/hub 66 | galen.browserFactory.selenium.grid.browser = Firefox 67 | galen.browserFactory.selenium.grid.browserVersion = 16 68 | galen.browserFactory.selenium.grid.platform = XP 69 | 70 | 71 | # Exit with fail code in case of any failures 72 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | galen.use.fail.exit.code = true -------------------------------------------------------------------------------- /javascript/gulp/test/basics.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, xit, before, after */ 2 | 3 | var assert = require("assert"); 4 | var gulp = require("gulp"); 5 | var path = require("path"); 6 | var es = require("event-stream"); 7 | var util = require("util"); 8 | var fs = require("fs"); 9 | var rimraf = require("rimraf"); 10 | 11 | var gulpGalen = require('../index.js'); 12 | 13 | describe("gulp-galen", function () { 14 | 15 | describe("basic functionality", function () { 16 | 17 | this.timeout(100000); 18 | 19 | var options = { 20 | url: "https://www.google.com", 21 | size: "800x600", 22 | galenPath: path.resolve('node_modules', 'galenframework', 'bin', 'galen' + (process.platform === 'win32' ? '.cmd' : '')) 23 | }; 24 | 25 | it("should iterate over some gspecs", function (done) { 26 | gulp.src("**/specs/google_success?.gspec").pipe(gulpGalen.check(options)) 27 | .pipe(es.writeArray(function (err, arr) { 28 | assert(!err, "There where errors present"); 29 | assert(arr, "Result missing"); 30 | for (var i = 0; i < 2; i++) { 31 | assert(arr[i].path.match(/specs\/google_success.\.gspec$/), 32 | "File's path didn't end with specs/google_success?.gspec: '" + arr[i].path + "'"); 33 | } 34 | done(); 35 | })); 36 | }); 37 | 38 | it("should handle failed specs", function (done) { 39 | try { 40 | expect(gulp.src("**/specs/google_failing.gspec").pipe(gulpGalen.check(options, function () { 41 | //ignore 42 | }))).toThrow(new Error("Unexpected error!")); 43 | done(); 44 | } catch (e) { 45 | done(); 46 | } 47 | }); 48 | 49 | }); 50 | 51 | describe("extended functionality", function () { 52 | 53 | this.timeout(30000); 54 | 55 | before(function (done) { 56 | rimraf("./tmp/test-reports", done); 57 | }); 58 | 59 | xit("should support some variables based upon the current file", function (done) { 60 | gulp.src("**/specs/google1.gspec").pipe(gulpGalen.check({ 61 | url: "https://www.google.com", 62 | size: "800x600", 63 | galenPath: "./node_modules/galenframework/bin/galen", 64 | testngreport: "./tmp/test-reports/testng-{basename}.xml" 65 | }, function (error) { 66 | es.writeArray(function (err, arr) { 67 | var fn = "./tmp/test-reports/testng-google_success1.gspec.xml"; 68 | fs.stat(fn, function (err, stats) { 69 | assert(!err, "File not found: " + fn); 70 | assert(stats.isFile(), "Is no file: " + fn); 71 | }); 72 | }); 73 | done(); 74 | })); 75 | }); 76 | 77 | }); 78 | 79 | }); -------------------------------------------------------------------------------- /javascript/node/test/basics.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, xit, before, after */ 2 | 3 | var assert = require("assert"); 4 | var gulp = require("gulp"); 5 | var path = require("path"); 6 | var es = require("event-stream"); 7 | var util = require("util"); 8 | var fs = require("fs"); 9 | var rimraf = require("rimraf"); 10 | 11 | var gulpGalen = require('../index.js'); 12 | 13 | describe("gulp-galen", function () { 14 | 15 | describe("basic functionality", function () { 16 | 17 | this.timeout(100000); 18 | 19 | var options = { 20 | url: "https://www.google.com", 21 | size: "800x600", 22 | galenPath: path.resolve('node_modules', 'galenframework', 'bin', 'galen' + (process.platform === 'win32' ? '.cmd' : '')) 23 | }; 24 | 25 | it("should iterate over some gspecs", function (done) { 26 | gulp.src("**/specs/google_success?.gspec").pipe(gulpGalen.check(options)) 27 | .pipe(es.writeArray(function (err, arr) { 28 | assert(!err, "There where errors present"); 29 | assert(arr, "Result missing"); 30 | for (var i = 0; i < 2; i++) { 31 | assert(arr[i].path.match(/specs\/google_success.\.gspec$/), 32 | "File's path didn't end with specs/google_success?.gspec: '" + arr[i].path + "'"); 33 | } 34 | done(); 35 | })); 36 | }); 37 | 38 | it("should handle failed specs", function (done) { 39 | try { 40 | expect(gulp.src("**/specs/google_failing.gspec").pipe(gulpGalen.check(options, function () { 41 | //ignore 42 | }))).toThrow(new Error("Unexpected error!")); 43 | done(); 44 | } catch (e) { 45 | done(); 46 | } 47 | }); 48 | 49 | }); 50 | 51 | describe("extended functionality", function () { 52 | 53 | this.timeout(30000); 54 | 55 | before(function (done) { 56 | rimraf("./tmp/test-reports", done); 57 | }); 58 | 59 | xit("should support some variables based upon the current file", function (done) { 60 | gulp.src("**/specs/google1.gspec").pipe(gulpGalen.check({ 61 | url: "https://www.google.com", 62 | size: "800x600", 63 | galenPath: "./node_modules/galenframework/bin/galen", 64 | testngreport: "./tmp/test-reports/testng-{basename}.xml" 65 | }, function (error) { 66 | es.writeArray(function (err, arr) { 67 | var fn = "./tmp/test-reports/testng-google_success1.gspec.xml"; 68 | fs.stat(fn, function (err, stats) { 69 | assert(!err, "File not found: " + fn); 70 | assert(stats.isFile(), "Is no file: " + fn); 71 | }); 72 | }); 73 | done(); 74 | })); 75 | }); 76 | 77 | }); 78 | 79 | }); -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/init.js: -------------------------------------------------------------------------------- 1 | 2 | var domain = "getbootstrap.com"; 3 | 4 | var devices = { 5 | mobile: { 6 | deviceName: "mobile", 7 | size: "450x800", 8 | tags: ["mobile"] 9 | }, 10 | tablet: { 11 | deviceName: "tablet", 12 | size: "600x800", 13 | tags: ["tablet"] 14 | }, 15 | desktop: { 16 | deviceName: "desktop", 17 | size: "1100x800", 18 | tags: ["desktop"] 19 | } 20 | }; 21 | 22 | var browsers = { 23 | chrome: { 24 | type: "chrome", 25 | browserName: "Chrome" 26 | }, 27 | firefox: { 28 | type: "firefox", 29 | browserName: "Firefox" 30 | } 31 | }; 32 | 33 | function openDriver(url, size, driver) { 34 | var driver = createDriver(null, size, driver); 35 | 36 | session.put("driver", driver); 37 | 38 | if (url != null) { 39 | if (url.indexOf("http://") != 0 && url.indexOf("https://") != 0) { 40 | url = "http://" + domain + url; 41 | } 42 | driver.get(url); 43 | } 44 | else { 45 | driver.get("http://" + domain); 46 | } 47 | return driver; 48 | } 49 | 50 | 51 | afterTest(function (test) { 52 | var driver = session.get("driver"); 53 | if (driver != null) { 54 | if (test.isFailed()) { 55 | session.report().info("Screenshot").withAttachment("Screenshot", takeScreenshot(driver)); 56 | } 57 | driver.quit(); 58 | } 59 | }); 60 | 61 | function _test(testNamePrefix, url, callback) { 62 | test(testNamePrefix + " on ${deviceName} device with firefox", function (device) { 63 | var driver = openDriver(url, device.size, "firefox"); 64 | callback.call(this, driver, device); 65 | }); 66 | } 67 | 68 | function _testWithBrowser(testNamePrefix, url, callback) { 69 | test(testNamePrefix + " on ${deviceName} device with ${browserName}", function (browser, device) { 70 | var driver = openDriver(url, device.size, browser.type); 71 | callback.call(this, driver, device, browser); 72 | }); 73 | } 74 | 75 | function testOnAllDevicesAndBrowsers(testNamePrefix, url, callback) { 76 | forAll(browsers, function () { 77 | forAll(devices, function () { 78 | _testWithBrowser(testNamePrefix, url, callback); 79 | }); 80 | }); 81 | } 82 | 83 | function testOnDeviceAndBrowsers(device, testNamePrefix, url, callback) { 84 | forAll(browsers, function () { 85 | forOnly(device, function() { 86 | _testWithBrowser(testNamePrefix, url, callback); 87 | }); 88 | }); 89 | } 90 | 91 | function testOnAllDevices(testNamePrefix, url, callback) { 92 | forAll(devices, function () { 93 | _test(testNamePrefix, url, callback); 94 | }); 95 | } 96 | 97 | function testOnDevice(device, testNamePrefix, url, callback) { 98 | forOnly(device, function() { 99 | _test(testNamePrefix, url, callback); 100 | }); 101 | } 102 | 103 | 104 | 105 | /* 106 | Exporting functions to all other tests that will use this script 107 | */ 108 | (function (export) { 109 | export.devices = devices; 110 | export.openDriver = openDriver; 111 | export.testOnAllDevices = testOnAllDevices; 112 | })(this); 113 | -------------------------------------------------------------------------------- /javascript/grunt/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | 4 | /* 5 | * Developer test suite. Uses the current (../tasks/galen.js) version 6 | * of the framework to launch the dynamic test example. 7 | * 8 | */ 9 | var sauceUser = 'galen_mreinhardt'; 10 | var sauceKey = 'aff16b42-9c23-4cb6-adf7-38da9e02193a'; 11 | var package = grunt.file.readJSON('./package.json'); 12 | var testPipeline = ['galen:local']; 13 | var BUILD_ID = package.version + '_' + String((new Date()).getTime()); 14 | grunt.initConfig({ 15 | galen: { 16 | options: { 17 | url: 'http://example.com/', 18 | output: true, 19 | concat: true 20 | }, 21 | local: { 22 | src: ['test/**/*.js'], 23 | options: { 24 | devices: { 25 | desktop: { 26 | deviceName: 'desktop', 27 | browser: 'firefox', 28 | size: '1280x800' 29 | }, 30 | tablet: { 31 | deviceName: 'tablet', 32 | browser: 'firefox', 33 | size: '768x576' 34 | } 35 | } 36 | } 37 | }, 38 | sl: { 39 | src: ['test/**/*.js'], 40 | options: { 41 | seleniumGrid: { 42 | login: sauceUser, 43 | username: sauceUser, 44 | accessKey: sauceKey 45 | }, 46 | devices: { 47 | desktop: { 48 | deviceName: 'desktop', 49 | browser: 'chrome', 50 | size: '1280x1024', 51 | desiredCapabilities: { 52 | name: 'example.com for desktop', 53 | platform: 'Windows 7', 54 | version: '43.0', 55 | passed: 'true', 56 | tags: [ 57 | 'grunt galen', 58 | 'example.com', 59 | 'remote testing', 60 | 'desktop browser' 61 | ].join(','), 62 | build: BUILD_ID 63 | } 64 | }, 65 | tablet: { 66 | deviceName: 'ipad', 67 | browser: 'ipad', 68 | desiredCapabilities: { 69 | name: 'example.com for tablet', 70 | 'device-orientation': 'portrait', 71 | platform: 'OS X 10.10', 72 | version: '8.0', 73 | passed: 'true', 74 | tags: [ 75 | 'grunt galen', 76 | 'example.com', 77 | 'remote testing', 78 | 'ipad browser' 79 | ].join(','), 80 | build: BUILD_ID 81 | } 82 | }, 83 | mobile: { 84 | deviceName: 'android', 85 | browser: 'android', 86 | desiredCapabilities: { 87 | name: 'example.com for mobile', 88 | 'device-orientation': 'portrait', 89 | platform: 'Linux', 90 | version: '4.4', 91 | passed: 'true', 92 | tags: [ 93 | 'grunt galen', 94 | 'example.com', 95 | 'remote testing', 96 | 'android browser' 97 | ].join(','), 98 | build: BUILD_ID 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | }); 106 | 107 | grunt.registerTask('default', testPipeline); 108 | }; 109 | -------------------------------------------------------------------------------- /junit/src/test/resources/specs/shared/galen-extras-rules.js: -------------------------------------------------------------------------------- 1 | 2 | function _ruleRenderedInTable(rule, itemPattern, columns, verticalMargin, horizontalMargin) { 3 | var allItems = findAll(itemPattern); 4 | 5 | var currentColumn = 0; 6 | 7 | for (var i = 0; i < allItems.length - 1; i += 1) { 8 | if (currentColumn < columns - 1) { 9 | rule.addObjectSpecs(allItems[i].name, [ 10 | "left-of " + allItems[i + 1].name + " " + horizontalMargin, 11 | "aligned horizontally all " + allItems[i + 1].name 12 | ]); 13 | } 14 | 15 | var j = i + columns; 16 | 17 | if (j < allItems.length) { 18 | rule.addObjectSpecs(allItems[i].name, [ 19 | "above " + allItems[j].name + " " + verticalMargin, 20 | "aligned vertically all " + allItems[j].name 21 | ]); 22 | } 23 | 24 | currentColumn += 1; 25 | if (currentColumn === columns) { 26 | currentColumn = 0; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * This is a high-level spec for checking that elements are displayed in table layout 33 | * with different margins for vertical and horizontal sides 34 | * e.g. 35 | * 36 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px vertical and 1px horizontal margins 37 | */ 38 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{verticalMargin} vertical and %{horizontalMargin} horizontal margin", function (objectName, parameters) { 39 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.verticalMargin, parameters.horizontalMargin); 40 | }); 41 | 42 | 43 | /** 44 | * This is a high-level spec for checking that elements are displayed in table layout 45 | * e.g. 46 | * 47 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px margin 48 | */ 49 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{margin} margin", function (objectName, parameters) { 50 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.margin, parameters.margin); 51 | }); 52 | 53 | 54 | function _applyRuleBodyForAllElements(rule, objectPattern, appliesConditionCallback) { 55 | var allElements = findAll(parameters.objectPattern); 56 | 57 | if (allElements.length > 0) { 58 | for (var i = 0; i < allElements.length - 1; i += 1) { 59 | if (!appliesConditionCallback(allElements[i])) { 60 | return; 61 | } 62 | } 63 | rule.doRuleBody(); 64 | } 65 | } 66 | 67 | function _applyRuleBodyForSingleElement(rule, objectPattern, appliesConditionCallback) { 68 | var allElements = findAll(parameters.objectPattern); 69 | 70 | if (allElements.length > 0) { 71 | for (var i = 0; i < allElements.length - 1; i += 1) { 72 | if (appliesConditionCallback(allElements[i])) { 73 | rule.doRuleBody(); 74 | return; 75 | } 76 | } 77 | } 78 | } 79 | 80 | rule("if all %{objectPattern} are visible", function (objectName, parameters) { 81 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 82 | return element.isVisible(); 83 | }); 84 | }); 85 | 86 | 87 | rule("if none of %{objectPattern} are visible", function (objectName, parameters) { 88 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 89 | return ! element.isVisible(); 90 | }); 91 | }); 92 | 93 | rule("if any of %{objectPattern} is visible", function (objectName, parameters) { 94 | _applyRuleBodyForSingleElement(this, parameters.objectPattern, function (element) { 95 | return element.isVisible(); 96 | }); 97 | }); 98 | 99 | 100 | rule("%{objectPattern} sides are inside %{containerObject} with %{margin} margin from %{sideAName} and %{sideBName}", function (objectName, parameters) { 101 | var items = findAll(parameters.objectPattern); 102 | 103 | 104 | if (items.length > 0) { 105 | this.addObjectSpecs(items[0].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideAName ]); 106 | 107 | for (var i = 1; i < items.length - 1; i++) { 108 | this.addObjectSpecs(items[i].name, [ "inside " + parameters.containerObject ]); 109 | } 110 | 111 | this.addObjectSpecs(items[items.length - 1].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideBName ]); 112 | } else { 113 | throw new Error("Couldn't find any items matching " + parameters.objectPattern); 114 | } 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/shared/galen-extras-rules.js: -------------------------------------------------------------------------------- 1 | 2 | function _ruleRenderedInTable(rule, itemPattern, columns, verticalMargin, horizontalMargin) { 3 | var allItems = findAll(itemPattern); 4 | 5 | var currentColumn = 0; 6 | 7 | for (var i = 0; i < allItems.length - 1; i += 1) { 8 | if (currentColumn < columns - 1) { 9 | rule.addObjectSpecs(allItems[i].name, [ 10 | "left-of " + allItems[i + 1].name + " " + horizontalMargin, 11 | "aligned horizontally all " + allItems[i + 1].name 12 | ]); 13 | } 14 | 15 | var j = i + columns; 16 | 17 | if (j < allItems.length) { 18 | rule.addObjectSpecs(allItems[i].name, [ 19 | "above " + allItems[j].name + " " + verticalMargin, 20 | "aligned vertically all " + allItems[j].name 21 | ]); 22 | } 23 | 24 | currentColumn += 1; 25 | if (currentColumn === columns) { 26 | currentColumn = 0; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * This is a high-level spec for checking that elements are displayed in table layout 33 | * with different margins for vertical and horizontal sides 34 | * e.g. 35 | * 36 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px vertical and 1px horizontal margins 37 | */ 38 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{verticalMargin} vertical and %{horizontalMargin} horizontal margin", function (objectName, parameters) { 39 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.verticalMargin, parameters.horizontalMargin); 40 | }); 41 | 42 | 43 | /** 44 | * This is a high-level spec for checking that elements are displayed in table layout 45 | * e.g. 46 | * 47 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px margin 48 | */ 49 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{margin} margin", function (objectName, parameters) { 50 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.margin, parameters.margin); 51 | }); 52 | 53 | 54 | function _applyRuleBodyForAllElements(rule, objectPattern, appliesConditionCallback) { 55 | var allElements = findAll(parameters.objectPattern); 56 | 57 | if (allElements.length > 0) { 58 | for (var i = 0; i < allElements.length - 1; i += 1) { 59 | if (!appliesConditionCallback(allElements[i])) { 60 | return; 61 | } 62 | } 63 | rule.doRuleBody(); 64 | } 65 | } 66 | 67 | function _applyRuleBodyForSingleElement(rule, objectPattern, appliesConditionCallback) { 68 | var allElements = findAll(parameters.objectPattern); 69 | 70 | if (allElements.length > 0) { 71 | for (var i = 0; i < allElements.length - 1; i += 1) { 72 | if (appliesConditionCallback(allElements[i])) { 73 | rule.doRuleBody(); 74 | return; 75 | } 76 | } 77 | } 78 | } 79 | 80 | rule("if all %{objectPattern} are visible", function (objectName, parameters) { 81 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 82 | return element.isVisible(); 83 | }); 84 | }); 85 | 86 | 87 | rule("if none of %{objectPattern} are visible", function (objectName, parameters) { 88 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 89 | return ! element.isVisible(); 90 | }); 91 | }); 92 | 93 | rule("if any of %{objectPattern} is visible", function (objectName, parameters) { 94 | _applyRuleBodyForSingleElement(this, parameters.objectPattern, function (element) { 95 | return element.isVisible(); 96 | }); 97 | }); 98 | 99 | 100 | rule("%{objectPattern} sides are inside %{containerObject} with %{margin} margin from %{sideAName} and %{sideBName}", function (objectName, parameters) { 101 | var items = findAll(parameters.objectPattern); 102 | 103 | 104 | if (items.length > 0) { 105 | this.addObjectSpecs(items[0].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideAName ]); 106 | 107 | for (var i = 1; i < items.length - 1; i++) { 108 | this.addObjectSpecs(items[i].name, [ "inside " + parameters.containerObject ]); 109 | } 110 | 111 | this.addObjectSpecs(items[items.length - 1].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideBName ]); 112 | } else { 113 | throw new Error("Couldn't find any items matching " + parameters.objectPattern); 114 | } 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/shared/galen-extras-rules.js: -------------------------------------------------------------------------------- 1 | 2 | function _ruleRenderedInTable(rule, itemPattern, columns, verticalMargin, horizontalMargin) { 3 | var allItems = findAll(itemPattern); 4 | 5 | var currentColumn = 0; 6 | 7 | for (var i = 0; i < allItems.length - 1; i += 1) { 8 | if (currentColumn < columns - 1) { 9 | rule.addObjectSpecs(allItems[i].name, [ 10 | "left-of " + allItems[i + 1].name + " " + horizontalMargin, 11 | "aligned horizontally all " + allItems[i + 1].name 12 | ]); 13 | } 14 | 15 | var j = i + columns; 16 | 17 | if (j < allItems.length) { 18 | rule.addObjectSpecs(allItems[i].name, [ 19 | "above " + allItems[j].name + " " + verticalMargin, 20 | "aligned vertically all " + allItems[j].name 21 | ]); 22 | } 23 | 24 | currentColumn += 1; 25 | if (currentColumn === columns) { 26 | currentColumn = 0; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * This is a high-level spec for checking that elements are displayed in table layout 33 | * with different margins for vertical and horizontal sides 34 | * e.g. 35 | * 36 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px vertical and 1px horizontal margins 37 | */ 38 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{verticalMargin} vertical and %{horizontalMargin} horizontal margin", function (objectName, parameters) { 39 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.verticalMargin, parameters.horizontalMargin); 40 | }); 41 | 42 | 43 | /** 44 | * This is a high-level spec for checking that elements are displayed in table layout 45 | * e.g. 46 | * 47 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px margin 48 | */ 49 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{margin} margin", function (objectName, parameters) { 50 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.margin, parameters.margin); 51 | }); 52 | 53 | 54 | function _applyRuleBodyForAllElements(rule, objectPattern, appliesConditionCallback) { 55 | var allElements = findAll(parameters.objectPattern); 56 | 57 | if (allElements.length > 0) { 58 | for (var i = 0; i < allElements.length - 1; i += 1) { 59 | if (!appliesConditionCallback(allElements[i])) { 60 | return; 61 | } 62 | } 63 | rule.doRuleBody(); 64 | } 65 | } 66 | 67 | function _applyRuleBodyForSingleElement(rule, objectPattern, appliesConditionCallback) { 68 | var allElements = findAll(parameters.objectPattern); 69 | 70 | if (allElements.length > 0) { 71 | for (var i = 0; i < allElements.length - 1; i += 1) { 72 | if (appliesConditionCallback(allElements[i])) { 73 | rule.doRuleBody(); 74 | return; 75 | } 76 | } 77 | } 78 | } 79 | 80 | rule("if all %{objectPattern} are visible", function (objectName, parameters) { 81 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 82 | return element.isVisible(); 83 | }); 84 | }); 85 | 86 | 87 | rule("if none of %{objectPattern} are visible", function (objectName, parameters) { 88 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 89 | return ! element.isVisible(); 90 | }); 91 | }); 92 | 93 | rule("if any of %{objectPattern} is visible", function (objectName, parameters) { 94 | _applyRuleBodyForSingleElement(this, parameters.objectPattern, function (element) { 95 | return element.isVisible(); 96 | }); 97 | }); 98 | 99 | 100 | rule("%{objectPattern} sides are inside %{containerObject} with %{margin} margin from %{sideAName} and %{sideBName}", function (objectName, parameters) { 101 | var items = findAll(parameters.objectPattern); 102 | 103 | 104 | if (items.length > 0) { 105 | this.addObjectSpecs(items[0].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideAName ]); 106 | 107 | for (var i = 1; i < items.length - 1; i++) { 108 | this.addObjectSpecs(items[i].name, [ "inside " + parameters.containerObject ]); 109 | } 110 | 111 | this.addObjectSpecs(items[items.length - 1].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideBName ]); 112 | } else { 113 | throw new Error("Couldn't find any items matching " + parameters.objectPattern); 114 | } 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/shared/galen-extras-rules.js: -------------------------------------------------------------------------------- 1 | 2 | function _ruleRenderedInTable(rule, itemPattern, columns, verticalMargin, horizontalMargin) { 3 | var allItems = findAll(itemPattern); 4 | 5 | var currentColumn = 0; 6 | 7 | for (var i = 0; i < allItems.length - 1; i += 1) { 8 | if (currentColumn < columns - 1) { 9 | rule.addObjectSpecs(allItems[i].name, [ 10 | "left-of " + allItems[i + 1].name + " " + horizontalMargin, 11 | "aligned horizontally all " + allItems[i + 1].name 12 | ]); 13 | } 14 | 15 | var j = i + columns; 16 | 17 | if (j < allItems.length) { 18 | rule.addObjectSpecs(allItems[i].name, [ 19 | "above " + allItems[j].name + " " + verticalMargin, 20 | "aligned vertically all " + allItems[j].name 21 | ]); 22 | } 23 | 24 | currentColumn += 1; 25 | if (currentColumn === columns) { 26 | currentColumn = 0; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * This is a high-level spec for checking that elements are displayed in table layout 33 | * with different margins for vertical and horizontal sides 34 | * e.g. 35 | * 36 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px vertical and 1px horizontal margins 37 | */ 38 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{verticalMargin} vertical and %{horizontalMargin} horizontal margin", function (objectName, parameters) { 39 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.verticalMargin, parameters.horizontalMargin); 40 | }); 41 | 42 | 43 | /** 44 | * This is a high-level spec for checking that elements are displayed in table layout 45 | * e.g. 46 | * 47 | * | menuItem-* are rendered in 2 column table layout, with 0 to 4px margin 48 | */ 49 | rule("%{itemPattern} are rendered in %{columns: [0-9]+} column table layout, with %{margin} margin", function (objectName, parameters) { 50 | _ruleRenderedInTable(this, parameters.itemPattern, parseInt(columns), parameters.margin, parameters.margin); 51 | }); 52 | 53 | 54 | function _applyRuleBodyForAllElements(rule, objectPattern, appliesConditionCallback) { 55 | var allElements = findAll(parameters.objectPattern); 56 | 57 | if (allElements.length > 0) { 58 | for (var i = 0; i < allElements.length - 1; i += 1) { 59 | if (!appliesConditionCallback(allElements[i])) { 60 | return; 61 | } 62 | } 63 | rule.doRuleBody(); 64 | } 65 | } 66 | 67 | function _applyRuleBodyForSingleElement(rule, objectPattern, appliesConditionCallback) { 68 | var allElements = findAll(parameters.objectPattern); 69 | 70 | if (allElements.length > 0) { 71 | for (var i = 0; i < allElements.length - 1; i += 1) { 72 | if (appliesConditionCallback(allElements[i])) { 73 | rule.doRuleBody(); 74 | return; 75 | } 76 | } 77 | } 78 | } 79 | 80 | rule("if all %{objectPattern} are visible", function (objectName, parameters) { 81 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 82 | return element.isVisible(); 83 | }); 84 | }); 85 | 86 | 87 | rule("if none of %{objectPattern} are visible", function (objectName, parameters) { 88 | _applyRuleBodyForAllElements(this, parameters.objectPattern, function (element) { 89 | return ! element.isVisible(); 90 | }); 91 | }); 92 | 93 | rule("if any of %{objectPattern} is visible", function (objectName, parameters) { 94 | _applyRuleBodyForSingleElement(this, parameters.objectPattern, function (element) { 95 | return element.isVisible(); 96 | }); 97 | }); 98 | 99 | 100 | rule("%{objectPattern} sides are inside %{containerObject} with %{margin} margin from %{sideAName} and %{sideBName}", function (objectName, parameters) { 101 | var items = findAll(parameters.objectPattern); 102 | 103 | 104 | if (items.length > 0) { 105 | this.addObjectSpecs(items[0].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideAName ]); 106 | 107 | for (var i = 1; i < items.length - 1; i++) { 108 | this.addObjectSpecs(items[i].name, [ "inside " + parameters.containerObject ]); 109 | } 110 | 111 | this.addObjectSpecs(items[items.length - 1].name, [ "inside " + parameters.containerObject + " " + parameters.margin + " " + parameters.sideBName ]); 112 | } else { 113 | throw new Error("Couldn't find any items matching " + parameters.objectPattern); 114 | } 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /junit/src/test/java/sample/util/GalenBaseTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import com.galenframework.junit.GalenJUnitTestBase; 4 | import com.galenframework.gspeclang2.pagespec.SectionFilter; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.junit.runners.Parameterized.Parameters; 7 | import org.openqa.selenium.*; 8 | import org.openqa.selenium.firefox.FirefoxDriver; 9 | import org.openqa.selenium.remote.DesiredCapabilities; 10 | import org.openqa.selenium.remote.RemoteWebDriver; 11 | 12 | import java.net.MalformedURLException; 13 | import java.net.URL; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Properties; 17 | 18 | import static java.util.Arrays.asList; 19 | 20 | /** 21 | * Base class for all Galen tests.
22 | *
23 | * To run with maven against Selenium grid use:
24 | * mvn verify -Dselenium.grid=http://grid-ip:4444/wd/hub 25 | */ 26 | public abstract class GalenBaseTest extends GalenJUnitTestBase { 27 | 28 | private static final String ENV_URL = "http://getbootstrap.com"; 29 | 30 | private TestDevice device; 31 | 32 | public GalenBaseTest(final TestDevice pTestDevice) { 33 | super(); 34 | this.device = pTestDevice; 35 | } 36 | 37 | protected String getDefaultURL() { 38 | return ENV_URL; 39 | } 40 | 41 | public WebElement scrollToElement(final By selector) throws MalformedURLException { 42 | WebElement element = getDriver().findElement(selector); 43 | String coordY = Integer.toString(element.getLocation().getY()); 44 | ((JavascriptExecutor) getDriver()).executeScript("window.scrollTo(0, " + coordY + ")"); 45 | return element; 46 | } 47 | 48 | public void clickElement(final By selector) throws MalformedURLException { 49 | WebElement element = scrollToElement(selector); 50 | element.click(); 51 | } 52 | 53 | public void enterText(final By selector, final String text) throws MalformedURLException { 54 | WebElement element = scrollToElement(selector); 55 | element.sendKeys(text); 56 | } 57 | 58 | public void verifyPage(final String uri, final String specPath, final List groups) 59 | throws Exception { 60 | load(uri, 61 | this.device.getScreenSize().getWidth(), 62 | this.device.getScreenSize().getHeight()); 63 | checkLayout(specPath, new SectionFilter(device.getTags(), null), 64 | new Properties(), null); 65 | } 66 | 67 | public void verifyPage(final String specPath, final List groups) 68 | throws Exception { 69 | resize(this.device.getScreenSize().getWidth(), this.device.getScreenSize().getHeight()); 70 | checkLayout(specPath, new SectionFilter(device.getTags(), null), 71 | new Properties(), null); 72 | } 73 | 74 | @Override 75 | public void load(final String uri) { 76 | // allow overwrite via parameters 77 | final String env = System.getProperty("selenium.start_uri"); 78 | final String completeUrl = (StringUtils.isEmpty(env) ? getDefaultURL() : env) + uri; 79 | getDriver().get(completeUrl); 80 | } 81 | 82 | @Override 83 | public WebDriver createDriver() { 84 | final String grid = System.getProperty("selenium.grid"); 85 | if (grid == null) { 86 | return new FirefoxDriver(); 87 | } else { 88 | // chrome runs much faster in a selenium grid 89 | try { 90 | return new RemoteWebDriver(new URL(grid), DesiredCapabilities.chrome()); 91 | } catch (MalformedURLException e) { 92 | throw new RuntimeException(e); 93 | } 94 | } 95 | } 96 | 97 | @Parameters 98 | public static Iterable devices() { 99 | return Arrays.asList(new Object[][]{// @formatter:off 100 | {new TestDevice("small-phone", new Dimension(280, 800), asList("small-phone", "phone", "mobile"))}, 101 | {new TestDevice("normal-phone", new Dimension(320, 800), asList("normal-phone", "phone", "mobile"))}, 102 | {new TestDevice("big-phone", new Dimension(380, 800), asList("big-phone", "phone", "mobile"))}, 103 | {new TestDevice("small-tablet", new Dimension(450, 800), asList("small-tablet", "tablet", "mobile"))}, 104 | {new TestDevice("normal-tablet", new Dimension(450, 800), asList("normal-tablet", "tablet", "mobile"))}, 105 | {new TestDevice("desktop", new Dimension(1024, 800), asList("normal", "desktop"))}, 106 | {new TestDevice("fullhd", new Dimension(1920, 1080), asList("fullhd", "desktop"))},// @formatter:on 107 | }); 108 | } 109 | 110 | public static class TestDevice { 111 | 112 | private final String name; 113 | private final Dimension screenSize; 114 | private final List tags; 115 | 116 | public TestDevice(String name, Dimension screenSize, List tags) { 117 | this.name = name; 118 | this.screenSize = screenSize; 119 | this.tags = tags; 120 | } 121 | 122 | public Dimension getScreenSize() { 123 | return screenSize; 124 | } 125 | 126 | public List getTags() { 127 | return tags; 128 | } 129 | 130 | /** 131 | * @see java.lang.Object#toString() 132 | */ 133 | @Override 134 | public String toString() { 135 | StringBuilder builder = new StringBuilder(); 136 | builder.append("TestDevice ["); 137 | if (name != null) { 138 | builder.append("name="); 139 | builder.append(name); 140 | } 141 | builder.append("]"); 142 | return builder.toString(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /junit/src/test/java/sample/util/junit/LabelledParameterized.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package sample.util.junit; 5 | 6 | import java.lang.reflect.Method; 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | import org.junit.runner.Description; 12 | import org.junit.runners.Parameterized; 13 | 14 | /** 15 | * Simple helper class to name JUnit parameterized tests 16 | * 17 | * @author mreinhardt 18 | * 19 | */ 20 | public class LabelledParameterized extends Parameterized { 21 | 22 | // list of all labels 23 | private List labels; 24 | 25 | // description for all labels 26 | private Description labelledDescription; 27 | 28 | public LabelledParameterized(Class cl) throws Throwable { 29 | super(cl); 30 | initialiseLabels(); 31 | generateLabelledDescription(); 32 | } 33 | 34 | // generate label from first parameter 35 | private void initialiseLabels() throws Exception { 36 | Collection parameterArrays = getParameterArrays(); 37 | labels = new ArrayList(); 38 | for (Object[] parameterArray : parameterArrays) { 39 | String label = parameterArray[0].toString(); 40 | labels.add(label); 41 | } 42 | 43 | } 44 | 45 | /** 46 | * retrieve the parameter array used in the test 47 | * 48 | * @return parameter array 49 | * @throws Exception 50 | */ 51 | private Collection getParameterArrays() throws Exception { 52 | Method testClassMethod = getDeclaredMethod(this.getClass(), "getTestClass"); 53 | Class returnType = testClassMethod.getReturnType(); 54 | if (returnType == Class.class) { 55 | return getParameterArrays43(); 56 | } else { 57 | return getParameterArrays44(); 58 | } 59 | } 60 | 61 | /** 62 | * 63 | * @return 64 | * @throws Exception 65 | */ 66 | private Collection getParameterArrays43() throws Exception { 67 | Object[][] methodCalls = new Object[][] { new Object[] { "getTestClass" } }; 68 | Class cl = invokeMethodChain(this, methodCalls); 69 | Method[] methods = cl.getMethods(); 70 | 71 | Method parametersMethod = null; 72 | for (Method method : methods) { 73 | boolean providesParameters = method.isAnnotationPresent(Parameters.class); 74 | if (!providesParameters) { 75 | continue; 76 | } 77 | if (parametersMethod != null) { 78 | throw new Exception("Only one method should be annotated with @Labels"); 79 | } 80 | parametersMethod = method; 81 | } 82 | 83 | if (parametersMethod == null) { 84 | throw new Exception("No @Parameters method found"); 85 | } 86 | 87 | @SuppressWarnings("unchecked") 88 | Collection parameterArrays = (Collection) parametersMethod.invoke(null); 89 | return parameterArrays; 90 | 91 | } 92 | 93 | private Collection getParameterArrays44() throws Exception { 94 | Object[][] methodCalls = new Object[][] { new Object[] { "getTestClass" }, 95 | new Object[] { "getAnnotatedMethods", Class.class, Parameters.class }, 96 | new Object[] { "get", int.class, 0 }, 97 | // use array type for varargs (equivalent (almost)) 98 | new Object[] { "invokeExplosively", Object.class, null, Object[].class, new Object[] {} } }; 99 | Collection parameterArrays = invokeMethodChain(this, methodCalls); 100 | return parameterArrays; 101 | } 102 | 103 | /** 104 | * execute the test methods 105 | * 106 | * @param object 107 | * @param methodCalls 108 | * @return 109 | * @throws Exception 110 | */ 111 | @SuppressWarnings("unchecked") 112 | protected T invokeMethodChain(Object object, Object[][] methodCalls) throws Exception { 113 | for (Object[] methodCall : methodCalls) { 114 | String methodName = (String) methodCall[0]; 115 | int parameterCount = (methodCall.length - 1) / 2; 116 | Class[] classes = new Class[parameterCount]; 117 | Object[] arguments = new Object[parameterCount]; 118 | for (int i = 1; i < methodCall.length; i += 2) { 119 | Class cl = (Class) methodCall[i]; 120 | Object argument = methodCall[i + 1]; 121 | int index = (i - 1) / 2; // messy! 122 | classes[index] = cl; 123 | arguments[index] = argument; 124 | } 125 | Method method = getDeclaredMethod(object.getClass(), methodName, classes); 126 | object = method.invoke(object, arguments); 127 | } 128 | return (T) object; 129 | } 130 | 131 | /** 132 | * iterates through super-classes until found. Throws NoSuchMethodException 133 | * 134 | * @param cl 135 | * @param methodName 136 | * @param parameterTypes 137 | * @return 138 | * @throws NoSuchMethodException 139 | * if no super class is found 140 | */ 141 | private Method getDeclaredMethod(Class cl, String methodName, Class... parameterTypes) 142 | throws NoSuchMethodException { 143 | do { 144 | try { 145 | Method method = cl.getDeclaredMethod(methodName, parameterTypes); 146 | return method; 147 | } catch (NoSuchMethodException e) { 148 | // do nothing - just fall through to the below 149 | } 150 | cl = cl.getSuperclass(); 151 | } while (cl != null); 152 | throw new NoSuchMethodException("Method " + methodName + "() not found in hierarchy"); 153 | } 154 | 155 | /** 156 | * Generate the label description in a pretty way 157 | * 158 | * @throws Exception 159 | */ 160 | private void generateLabelledDescription() throws Exception { 161 | Description originalDescription = super.getDescription(); 162 | labelledDescription = Description.createSuiteDescription(originalDescription.getDisplayName()); 163 | ArrayList childDescriptions = originalDescription.getChildren(); 164 | int childCount = childDescriptions.size(); 165 | if (childCount != labels.size()) { 166 | throw new Exception("Number of labels and number of parameters must match."); 167 | } 168 | for (int i = 0; i < childDescriptions.size(); i++) { 169 | Description childDescription = childDescriptions.get(i); 170 | String label = labels.get(i); 171 | Description newDescription = Description.createSuiteDescription(label); 172 | ArrayList grandChildren = childDescription.getChildren(); 173 | for (Description grandChild : grandChildren) { 174 | newDescription.addChild(grandChild); 175 | } 176 | labelledDescription.addChild(newDescription); 177 | } 178 | } 179 | 180 | public Description getDescription() { 181 | return labelledDescription; 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /junit/src/test/resources/specs/shared/galen-extras-rules.gspec: -------------------------------------------------------------------------------- 1 | 2 | @script galen-extras-rules.js 3 | 4 | 5 | # Check that all matching elements are squared 6 | # e.g. 7 | # 8 | # | header-icon, menu-button should be squared 9 | # 10 | @rule %{objectPattern} should be squared 11 | @forEach [${objectPattern}] as item 12 | ${item}: 13 | width 100% of ${objectName}/height 14 | 15 | 16 | 17 | # Check that all matching elements are almost squared 18 | # allowing 10% difference between width and height 19 | # e.g. 20 | # 21 | # | header-icon, menu-button should be almost squared 22 | # 23 | @rule %{objectPattern} should be almost squared 24 | @forEach [${objectPattern}] as item 25 | ${item}: 26 | width 90 to 110% of ${objectName}/height 27 | 28 | 29 | 30 | # Check that element is strictly squared 31 | # e.g. 32 | # 33 | # header.icon: 34 | # | should be squared 35 | # 36 | 37 | @rule should be squared 38 | width 100% of ${objectName}/height 39 | 40 | 41 | 42 | # Check that element is almost squared 43 | # allowing 10% difference between its width and height 44 | # e.g. 45 | # 46 | # header.icon: 47 | # | almost squared 48 | # 49 | @rule almost squared 50 | width 90 to 110% of ${objectName}/height 51 | 52 | 53 | 54 | # Checks width/height ratio in percentage of all specified objects 55 | # e.g. 56 | # 57 | # | login_button, cancel_button should have 130% width/height ratio 58 | # 59 | @rule %{itemPattern} should have %{ratio}% width/height ratio 60 | @forEach [${itemPattern}] as item 61 | ${item}: 62 | height ${ratio} % of ${item}/width 63 | 64 | 65 | 66 | # Checks width/height ratio in percentage 67 | # e.g. 68 | # 69 | # | login_button, cancel_button should have 130% width/height ratio 70 | # 71 | @rule %{ratio}% width/height ratio 72 | height ${ratio} % of ${objectName}/width 73 | 74 | 75 | 76 | # Checking amount of objects 77 | # e.g. 78 | # 79 | # | amount of any menu.item should be > 4 80 | # 81 | # or 82 | # 83 | # | amount of visible menu.item-* should be 5 to 6 84 | # 85 | @rule amount of %{visibilityType: any|visible|absent} %{objectPattern} should be %{amount} 86 | global: 87 | count ${visibilityType} "${objectPattern}" is ${amount} 88 | 89 | 90 | 91 | # Check elements horizontal alignment and equal distance between each other 92 | # e.g. 93 | # 94 | # | home_box_* are aligned horizontally next to each other with equal distance 95 | # 96 | @rule %{objectPattern} are aligned horizontally next to each other with equal distance 97 | @if ${count(objectPattern) > 1} 98 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 99 | @set _distance_a ${parseInt(_distance_) - 1} 100 | @set _distance_b ${parseInt(_distance_) + 1} 101 | @forEach [${objectPattern}] as item, next as nextItem 102 | ${item}: 103 | aligned horizontally all ${nextItem} 104 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 105 | 106 | # Check elements horizontal alignment top 107 | # e.g. 108 | # 109 | # | home_box_* are aligned horizontally next to each other 110 | # 111 | @rule %{objectPattern} are aligned horizontally top next to each other 112 | @if ${count(objectPattern) > 1} 113 | @forEach [${objectPattern}] as item, next as nextItem 114 | ${item}: 115 | aligned horizontally top ${nextItem} 116 | 117 | # Check elements horizontal alignment top and equal distance between each other 118 | # e.g. 119 | # 120 | # | home_box_* are aligned horizontally next to each other with equal distance 121 | # 122 | @rule %{objectPattern} are aligned horizontally top next to each other with equal distance 123 | @if ${count(objectPattern) > 1} 124 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 125 | @set _distance_a ${parseInt(_distance_) - 1} 126 | @set _distance_b ${parseInt(_distance_) + 1} 127 | @forEach [${objectPattern}] as item, next as nextItem 128 | ${item}: 129 | aligned horizontally top ${nextItem} 130 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 131 | 132 | 133 | # Check elements vertical alignment and equal distance between each other 134 | # e.g. 135 | # 136 | # | home_box_* are aligned vertically above each other with equal distance 137 | # 138 | @rule %{objectPattern} are aligned vertically above each other with equal distance 139 | @if ${count(objectPattern) > 1} 140 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].top() - all[0].bottom())} 141 | @forEach [${objectPattern}] as item, next as nextItem 142 | ${item}: 143 | aligned vertically all ${nextItem} 144 | above ${nextItem} ${_distance_} px 145 | 146 | 147 | 148 | # Check elements horizontal alignment and specific margin between each other 149 | # e.g. 150 | # 151 | # | home_box_* are aligned horizontally next to each other with 10 to 30px margin 152 | # 153 | @rule %{objectPattern} are aligned horizontally next to each other with %{margin} margin 154 | @forEach [${objectPattern}] as item, next as nextItem 155 | ${item}: 156 | aligned horizontally all ${nextItem} 157 | left-of ${nextItem} ${margin} 158 | 159 | 160 | 161 | # Check elements vertical alignment and specific margin between each other 162 | # e.g. 163 | # 164 | # | home_box_* are aligned vertically above each other with 10 to 20 px margin 165 | # 166 | @rule %{objectPattern} are aligned vertically above each other with %{margin} margin 167 | @forEach [${objectPattern}] as item, next as nextItem 168 | ${item}: 169 | aligned vertically all ${nextItem} 170 | above ${nextItem} ${margin} 171 | 172 | 173 | 174 | # Check that elements appear and hide on different tags 175 | # e.g. 176 | # 177 | # | login_button, menu.item-* should be visible on desktop, tablet but absent on mobile 178 | # 179 | @rule %{objectPatterns} should be visible on %{tagsVisible} but absent on %{tagsAbsent} 180 | ${objectPatterns}: 181 | @on ${tagsVisible} 182 | visible 183 | @on ${tagsAbsent} 184 | absent 185 | 186 | 187 | 188 | # Validate all matching objects using specified component spec 189 | # e.g. 190 | # 191 | # | test all box-* with components/box.gspec 192 | # 193 | @rule test all %{objectPattern} with %{componentPath} 194 | @forEach [${objectPattern}] as item 195 | ${item}: 196 | component ${componentPath} 197 | 198 | 199 | # Apply two specs to all elements in a single line 200 | # e.g. 201 | # 202 | # | every menu.item-* is inside menu 0px top bottom and has width 100px 203 | # 204 | # or 205 | # 206 | # | every menu.item-* has width and is inside menu 0px top bottom 207 | # 208 | @rule every %{objectPattern} %{verb: is|has} %{spec1} and %{verb2: is|has} %{spec2} 209 | @forEach [${objectPattern}] as item 210 | ${item}: 211 | ${spec1} 212 | ${spec2} 213 | 214 | 215 | # Apply spec to all elements in a single line 216 | # e.g. 217 | # 218 | # | every menu.item-* is inside menu 0px top bottom 219 | # 220 | # or 221 | # 222 | # | every menu.item-* has width 100px 223 | # 224 | @rule every %{objectPattern} %{verb: is|has} %{spec} 225 | @forEach [${objectPattern}] as item 226 | ${item}: 227 | ${spec} 228 | 229 | 230 | 231 | # Apply spec to only first element in a single line 232 | # e.g. 233 | # 234 | # | first menu.item-* is inside menu 0px top bottom 235 | # 236 | # or 237 | # 238 | # | first menu.item-* has width 100px 239 | # 240 | @rule first %{objectPattern} %{verb: is|has} %{spec} 241 | @if ${count(objectPattern) > 0} 242 | ${first(objectPattern).name}: 243 | ${spec} 244 | 245 | 246 | 247 | # Apply spec to only last element in a single line 248 | # e.g. 249 | # 250 | # | last menu.item-* is inside menu 0px top bottom 251 | # 252 | # or 253 | # 254 | # | last menu.item-* has width 100px 255 | # 256 | @rule last %{objectPattern} %{verb: is|has} %{spec} 257 | @if ${count(objectPattern) > 0} 258 | ${last(objectPattern).name}: 259 | ${spec} 260 | 261 | 262 | 263 | # Apply rule body to only first element 264 | # e.g. 265 | # 266 | # | first menu.item-* : 267 | # below header 10px 268 | # inside main_container 0px top left 269 | # 270 | @rule first %{objectPattern}: 271 | @if ${count(objectPattern) > 0} 272 | ${first(objectPattern).name}: 273 | @ruleBody 274 | 275 | 276 | # Apply rule body to only last element 277 | # e.g. 278 | # 279 | # | last menu.item-* : 280 | # above footer 10px 281 | # inside main_container 0px left right 282 | # 283 | @rule last %{objectPattern}: 284 | @if ${count(objectPattern) > 0} 285 | ${last(objectPattern).name}: 286 | @ruleBody 287 | 288 | -------------------------------------------------------------------------------- /testng/src/test/resources/specs/shared/galen-extras-rules.gspec: -------------------------------------------------------------------------------- 1 | 2 | @script galen-extras-rules.js 3 | 4 | 5 | # Check that all matching elements are squared 6 | # e.g. 7 | # 8 | # | header-icon, menu-button should be squared 9 | # 10 | @rule %{objectPattern} should be squared 11 | @forEach [${objectPattern}] as item 12 | ${item}: 13 | width 100% of ${objectName}/height 14 | 15 | 16 | 17 | # Check that all matching elements are almost squared 18 | # allowing 10% difference between width and height 19 | # e.g. 20 | # 21 | # | header-icon, menu-button should be almost squared 22 | # 23 | @rule %{objectPattern} should be almost squared 24 | @forEach [${objectPattern}] as item 25 | ${item}: 26 | width 90 to 110% of ${objectName}/height 27 | 28 | 29 | 30 | # Check that element is strictly squared 31 | # e.g. 32 | # 33 | # header.icon: 34 | # | should be squared 35 | # 36 | 37 | @rule should be squared 38 | width 100% of ${objectName}/height 39 | 40 | 41 | 42 | # Check that element is almost squared 43 | # allowing 10% difference between its width and height 44 | # e.g. 45 | # 46 | # header.icon: 47 | # | almost squared 48 | # 49 | @rule almost squared 50 | width 90 to 110% of ${objectName}/height 51 | 52 | 53 | 54 | # Checks width/height ratio in percentage of all specified objects 55 | # e.g. 56 | # 57 | # | login_button, cancel_button should have 130% width/height ratio 58 | # 59 | @rule %{itemPattern} should have %{ratio}% width/height ratio 60 | @forEach [${itemPattern}] as item 61 | ${item}: 62 | height ${ratio} % of ${item}/width 63 | 64 | 65 | 66 | # Checks width/height ratio in percentage 67 | # e.g. 68 | # 69 | # | login_button, cancel_button should have 130% width/height ratio 70 | # 71 | @rule %{ratio}% width/height ratio 72 | height ${ratio} % of ${objectName}/width 73 | 74 | 75 | 76 | # Checking amount of objects 77 | # e.g. 78 | # 79 | # | amount of any menu.item should be > 4 80 | # 81 | # or 82 | # 83 | # | amount of visible menu.item-* should be 5 to 6 84 | # 85 | @rule amount of %{visibilityType: any|visible|absent} %{objectPattern} should be %{amount} 86 | global: 87 | count ${visibilityType} "${objectPattern}" is ${amount} 88 | 89 | 90 | 91 | # Check elements horizontal alignment and equal distance between each other 92 | # e.g. 93 | # 94 | # | home_box_* are aligned horizontally next to each other with equal distance 95 | # 96 | @rule %{objectPattern} are aligned horizontally next to each other with equal distance 97 | @if ${count(objectPattern) > 1} 98 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 99 | @set _distance_a ${parseInt(_distance_) - 1} 100 | @set _distance_b ${parseInt(_distance_) + 1} 101 | @forEach [${objectPattern}] as item, next as nextItem 102 | ${item}: 103 | aligned horizontally all ${nextItem} 104 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 105 | 106 | # Check elements horizontal alignment top 107 | # e.g. 108 | # 109 | # | home_box_* are aligned horizontally next to each other 110 | # 111 | @rule %{objectPattern} are aligned horizontally top next to each other 112 | @if ${count(objectPattern) > 1} 113 | @forEach [${objectPattern}] as item, next as nextItem 114 | ${item}: 115 | aligned horizontally top ${nextItem} 116 | 117 | # Check elements horizontal alignment top and equal distance between each other 118 | # e.g. 119 | # 120 | # | home_box_* are aligned horizontally next to each other with equal distance 121 | # 122 | @rule %{objectPattern} are aligned horizontally top next to each other with equal distance 123 | @if ${count(objectPattern) > 1} 124 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 125 | @set _distance_a ${parseInt(_distance_) - 1} 126 | @set _distance_b ${parseInt(_distance_) + 1} 127 | @forEach [${objectPattern}] as item, next as nextItem 128 | ${item}: 129 | aligned horizontally top ${nextItem} 130 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 131 | 132 | 133 | # Check elements vertical alignment and equal distance between each other 134 | # e.g. 135 | # 136 | # | home_box_* are aligned vertically above each other with equal distance 137 | # 138 | @rule %{objectPattern} are aligned vertically above each other with equal distance 139 | @if ${count(objectPattern) > 1} 140 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].top() - all[0].bottom())} 141 | @forEach [${objectPattern}] as item, next as nextItem 142 | ${item}: 143 | aligned vertically all ${nextItem} 144 | above ${nextItem} ${_distance_} px 145 | 146 | 147 | 148 | # Check elements horizontal alignment and specific margin between each other 149 | # e.g. 150 | # 151 | # | home_box_* are aligned horizontally next to each other with 10 to 30px margin 152 | # 153 | @rule %{objectPattern} are aligned horizontally next to each other with %{margin} margin 154 | @forEach [${objectPattern}] as item, next as nextItem 155 | ${item}: 156 | aligned horizontally all ${nextItem} 157 | left-of ${nextItem} ${margin} 158 | 159 | 160 | 161 | # Check elements vertical alignment and specific margin between each other 162 | # e.g. 163 | # 164 | # | home_box_* are aligned vertically above each other with 10 to 20 px margin 165 | # 166 | @rule %{objectPattern} are aligned vertically above each other with %{margin} margin 167 | @forEach [${objectPattern}] as item, next as nextItem 168 | ${item}: 169 | aligned vertically all ${nextItem} 170 | above ${nextItem} ${margin} 171 | 172 | 173 | 174 | # Check that elements appear and hide on different tags 175 | # e.g. 176 | # 177 | # | login_button, menu.item-* should be visible on desktop, tablet but absent on mobile 178 | # 179 | @rule %{objectPatterns} should be visible on %{tagsVisible} but absent on %{tagsAbsent} 180 | ${objectPatterns}: 181 | @on ${tagsVisible} 182 | visible 183 | @on ${tagsAbsent} 184 | absent 185 | 186 | 187 | 188 | # Validate all matching objects using specified component spec 189 | # e.g. 190 | # 191 | # | test all box-* with components/box.gspec 192 | # 193 | @rule test all %{objectPattern} with %{componentPath} 194 | @forEach [${objectPattern}] as item 195 | ${item}: 196 | component ${componentPath} 197 | 198 | 199 | # Apply two specs to all elements in a single line 200 | # e.g. 201 | # 202 | # | every menu.item-* is inside menu 0px top bottom and has width 100px 203 | # 204 | # or 205 | # 206 | # | every menu.item-* has width and is inside menu 0px top bottom 207 | # 208 | @rule every %{objectPattern} %{verb: is|has} %{spec1} and %{verb2: is|has} %{spec2} 209 | @forEach [${objectPattern}] as item 210 | ${item}: 211 | ${spec1} 212 | ${spec2} 213 | 214 | 215 | # Apply spec to all elements in a single line 216 | # e.g. 217 | # 218 | # | every menu.item-* is inside menu 0px top bottom 219 | # 220 | # or 221 | # 222 | # | every menu.item-* has width 100px 223 | # 224 | @rule every %{objectPattern} %{verb: is|has} %{spec} 225 | @forEach [${objectPattern}] as item 226 | ${item}: 227 | ${spec} 228 | 229 | 230 | 231 | # Apply spec to only first element in a single line 232 | # e.g. 233 | # 234 | # | first menu.item-* is inside menu 0px top bottom 235 | # 236 | # or 237 | # 238 | # | first menu.item-* has width 100px 239 | # 240 | @rule first %{objectPattern} %{verb: is|has} %{spec} 241 | @if ${count(objectPattern) > 0} 242 | ${first(objectPattern).name}: 243 | ${spec} 244 | 245 | 246 | 247 | # Apply spec to only last element in a single line 248 | # e.g. 249 | # 250 | # | last menu.item-* is inside menu 0px top bottom 251 | # 252 | # or 253 | # 254 | # | last menu.item-* has width 100px 255 | # 256 | @rule last %{objectPattern} %{verb: is|has} %{spec} 257 | @if ${count(objectPattern) > 0} 258 | ${last(objectPattern).name}: 259 | ${spec} 260 | 261 | 262 | 263 | # Apply rule body to only first element 264 | # e.g. 265 | # 266 | # | first menu.item-* : 267 | # below header 10px 268 | # inside main_container 0px top left 269 | # 270 | @rule first %{objectPattern}: 271 | @if ${count(objectPattern) > 0} 272 | ${first(objectPattern).name}: 273 | @ruleBody 274 | 275 | 276 | # Apply rule body to only last element 277 | # e.g. 278 | # 279 | # | last menu.item-* : 280 | # above footer 10px 281 | # inside main_container 0px left right 282 | # 283 | @rule last %{objectPattern}: 284 | @if ${count(objectPattern) > 0} 285 | ${last(objectPattern).name}: 286 | @ruleBody 287 | 288 | -------------------------------------------------------------------------------- /javascript/testRunner/bootstrap/specs/shared/galen-extras-rules.gspec: -------------------------------------------------------------------------------- 1 | 2 | @script galen-extras-rules.js 3 | 4 | 5 | # Check that all matching elements are squared 6 | # e.g. 7 | # 8 | # | header-icon, menu-button should be squared 9 | # 10 | @rule %{objectPattern} should be squared 11 | @forEach [${objectPattern}] as item 12 | ${item}: 13 | width 100% of ${objectName}/height 14 | 15 | 16 | 17 | # Check that all matching elements are almost squared 18 | # allowing 10% difference between width and height 19 | # e.g. 20 | # 21 | # | header-icon, menu-button should be almost squared 22 | # 23 | @rule %{objectPattern} should be almost squared 24 | @forEach [${objectPattern}] as item 25 | ${item}: 26 | width 90 to 110% of ${objectName}/height 27 | 28 | 29 | 30 | # Check that element is strictly squared 31 | # e.g. 32 | # 33 | # header.icon: 34 | # | should be squared 35 | # 36 | 37 | @rule should be squared 38 | width 100% of ${objectName}/height 39 | 40 | 41 | 42 | # Check that element is almost squared 43 | # allowing 10% difference between its width and height 44 | # e.g. 45 | # 46 | # header.icon: 47 | # | almost squared 48 | # 49 | @rule almost squared 50 | width 90 to 110% of ${objectName}/height 51 | 52 | 53 | 54 | # Checks width/height ratio in percentage of all specified objects 55 | # e.g. 56 | # 57 | # | login_button, cancel_button should have 130% width/height ratio 58 | # 59 | @rule %{itemPattern} should have %{ratio}% width/height ratio 60 | @forEach [${itemPattern}] as item 61 | ${item}: 62 | height ${ratio} % of ${item}/width 63 | 64 | 65 | 66 | # Checks width/height ratio in percentage 67 | # e.g. 68 | # 69 | # | login_button, cancel_button should have 130% width/height ratio 70 | # 71 | @rule %{ratio}% width/height ratio 72 | height ${ratio} % of ${objectName}/width 73 | 74 | 75 | 76 | # Checking amount of objects 77 | # e.g. 78 | # 79 | # | amount of any menu.item should be > 4 80 | # 81 | # or 82 | # 83 | # | amount of visible menu.item-* should be 5 to 6 84 | # 85 | @rule amount of %{visibilityType: any|visible|absent} %{objectPattern} should be %{amount} 86 | global: 87 | count ${visibilityType} "${objectPattern}" is ${amount} 88 | 89 | 90 | 91 | # Check elements horizontal alignment and equal distance between each other 92 | # e.g. 93 | # 94 | # | home_box_* are aligned horizontally next to each other with equal distance 95 | # 96 | @rule %{objectPattern} are aligned horizontally next to each other with equal distance 97 | @if ${count(objectPattern) > 1} 98 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 99 | @set _distance_a ${parseInt(_distance_) - 1} 100 | @set _distance_b ${parseInt(_distance_) + 1} 101 | @forEach [${objectPattern}] as item, next as nextItem 102 | ${item}: 103 | aligned horizontally all ${nextItem} 104 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 105 | 106 | # Check elements horizontal alignment top 107 | # e.g. 108 | # 109 | # | home_box_* are aligned horizontally next to each other 110 | # 111 | @rule %{objectPattern} are aligned horizontally top next to each other 112 | @if ${count(objectPattern) > 1} 113 | @forEach [${objectPattern}] as item, next as nextItem 114 | ${item}: 115 | aligned horizontally top ${nextItem} 116 | 117 | # Check elements horizontal alignment top and equal distance between each other 118 | # e.g. 119 | # 120 | # | home_box_* are aligned horizontally next to each other with equal distance 121 | # 122 | @rule %{objectPattern} are aligned horizontally top next to each other with equal distance 123 | @if ${count(objectPattern) > 1} 124 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 125 | @set _distance_a ${parseInt(_distance_) - 1} 126 | @set _distance_b ${parseInt(_distance_) + 1} 127 | @forEach [${objectPattern}] as item, next as nextItem 128 | ${item}: 129 | aligned horizontally top ${nextItem} 130 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 131 | 132 | 133 | # Check elements vertical alignment and equal distance between each other 134 | # e.g. 135 | # 136 | # | home_box_* are aligned vertically above each other with equal distance 137 | # 138 | @rule %{objectPattern} are aligned vertically above each other with equal distance 139 | @if ${count(objectPattern) > 1} 140 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].top() - all[0].bottom())} 141 | @forEach [${objectPattern}] as item, next as nextItem 142 | ${item}: 143 | aligned vertically all ${nextItem} 144 | above ${nextItem} ${_distance_} px 145 | 146 | 147 | 148 | # Check elements horizontal alignment and specific margin between each other 149 | # e.g. 150 | # 151 | # | home_box_* are aligned horizontally next to each other with 10 to 30px margin 152 | # 153 | @rule %{objectPattern} are aligned horizontally next to each other with %{margin} margin 154 | @forEach [${objectPattern}] as item, next as nextItem 155 | ${item}: 156 | aligned horizontally all ${nextItem} 157 | left-of ${nextItem} ${margin} 158 | 159 | 160 | 161 | # Check elements vertical alignment and specific margin between each other 162 | # e.g. 163 | # 164 | # | home_box_* are aligned vertically above each other with 10 to 20 px margin 165 | # 166 | @rule %{objectPattern} are aligned vertically above each other with %{margin} margin 167 | @forEach [${objectPattern}] as item, next as nextItem 168 | ${item}: 169 | aligned vertically all ${nextItem} 170 | above ${nextItem} ${margin} 171 | 172 | 173 | 174 | # Check that elements appear and hide on different tags 175 | # e.g. 176 | # 177 | # | login_button, menu.item-* should be visible on desktop, tablet but absent on mobile 178 | # 179 | @rule %{objectPatterns} should be visible on %{tagsVisible} but absent on %{tagsAbsent} 180 | ${objectPatterns}: 181 | @on ${tagsVisible} 182 | visible 183 | @on ${tagsAbsent} 184 | absent 185 | 186 | 187 | 188 | # Validate all matching objects using specified component spec 189 | # e.g. 190 | # 191 | # | test all box-* with components/box.gspec 192 | # 193 | @rule test all %{objectPattern} with %{componentPath} 194 | @forEach [${objectPattern}] as item 195 | ${item}: 196 | component ${componentPath} 197 | 198 | 199 | # Apply two specs to all elements in a single line 200 | # e.g. 201 | # 202 | # | every menu.item-* is inside menu 0px top bottom and has width 100px 203 | # 204 | # or 205 | # 206 | # | every menu.item-* has width and is inside menu 0px top bottom 207 | # 208 | @rule every %{objectPattern} %{verb: is|has} %{spec1} and %{verb2: is|has} %{spec2} 209 | @forEach [${objectPattern}] as item 210 | ${item}: 211 | ${spec1} 212 | ${spec2} 213 | 214 | 215 | # Apply spec to all elements in a single line 216 | # e.g. 217 | # 218 | # | every menu.item-* is inside menu 0px top bottom 219 | # 220 | # or 221 | # 222 | # | every menu.item-* has width 100px 223 | # 224 | @rule every %{objectPattern} %{verb: is|has} %{spec} 225 | @forEach [${objectPattern}] as item 226 | ${item}: 227 | ${spec} 228 | 229 | 230 | 231 | # Apply spec to only first element in a single line 232 | # e.g. 233 | # 234 | # | first menu.item-* is inside menu 0px top bottom 235 | # 236 | # or 237 | # 238 | # | first menu.item-* has width 100px 239 | # 240 | @rule first %{objectPattern} %{verb: is|has} %{spec} 241 | @if ${count(objectPattern) > 0} 242 | ${first(objectPattern).name}: 243 | ${spec} 244 | 245 | 246 | 247 | # Apply spec to only last element in a single line 248 | # e.g. 249 | # 250 | # | last menu.item-* is inside menu 0px top bottom 251 | # 252 | # or 253 | # 254 | # | last menu.item-* has width 100px 255 | # 256 | @rule last %{objectPattern} %{verb: is|has} %{spec} 257 | @if ${count(objectPattern) > 0} 258 | ${last(objectPattern).name}: 259 | ${spec} 260 | 261 | 262 | 263 | # Apply rule body to only first element 264 | # e.g. 265 | # 266 | # | first menu.item-* : 267 | # below header 10px 268 | # inside main_container 0px top left 269 | # 270 | @rule first %{objectPattern}: 271 | @if ${count(objectPattern) > 0} 272 | ${first(objectPattern).name}: 273 | @ruleBody 274 | 275 | 276 | # Apply rule body to only last element 277 | # e.g. 278 | # 279 | # | last menu.item-* : 280 | # above footer 10px 281 | # inside main_container 0px left right 282 | # 283 | @rule last %{objectPattern}: 284 | @if ${count(objectPattern) > 0} 285 | ${last(objectPattern).name}: 286 | @ruleBody 287 | 288 | -------------------------------------------------------------------------------- /javascript/testSuite/bootstrap/specs/shared/galen-extras-rules.gspec: -------------------------------------------------------------------------------- 1 | 2 | @script galen-extras-rules.js 3 | 4 | 5 | # Check that all matching elements are squared 6 | # e.g. 7 | # 8 | # | header-icon, menu-button should be squared 9 | # 10 | @rule %{objectPattern} should be squared 11 | @forEach [${objectPattern}] as item 12 | ${item}: 13 | width 100% of ${objectName}/height 14 | 15 | 16 | 17 | # Check that all matching elements are almost squared 18 | # allowing 10% difference between width and height 19 | # e.g. 20 | # 21 | # | header-icon, menu-button should be almost squared 22 | # 23 | @rule %{objectPattern} should be almost squared 24 | @forEach [${objectPattern}] as item 25 | ${item}: 26 | width 90 to 110% of ${objectName}/height 27 | 28 | 29 | 30 | # Check that element is strictly squared 31 | # e.g. 32 | # 33 | # header.icon: 34 | # | should be squared 35 | # 36 | 37 | @rule should be squared 38 | width 100% of ${objectName}/height 39 | 40 | 41 | 42 | # Check that element is almost squared 43 | # allowing 10% difference between its width and height 44 | # e.g. 45 | # 46 | # header.icon: 47 | # | almost squared 48 | # 49 | @rule almost squared 50 | width 90 to 110% of ${objectName}/height 51 | 52 | 53 | 54 | # Checks width/height ratio in percentage of all specified objects 55 | # e.g. 56 | # 57 | # | login_button, cancel_button should have 130% width/height ratio 58 | # 59 | @rule %{itemPattern} should have %{ratio}% width/height ratio 60 | @forEach [${itemPattern}] as item 61 | ${item}: 62 | height ${ratio} % of ${item}/width 63 | 64 | 65 | 66 | # Checks width/height ratio in percentage 67 | # e.g. 68 | # 69 | # | login_button, cancel_button should have 130% width/height ratio 70 | # 71 | @rule %{ratio}% width/height ratio 72 | height ${ratio} % of ${objectName}/width 73 | 74 | 75 | 76 | # Checking amount of objects 77 | # e.g. 78 | # 79 | # | amount of any menu.item should be > 4 80 | # 81 | # or 82 | # 83 | # | amount of visible menu.item-* should be 5 to 6 84 | # 85 | @rule amount of %{visibilityType: any|visible|absent} %{objectPattern} should be %{amount} 86 | global: 87 | count ${visibilityType} "${objectPattern}" is ${amount} 88 | 89 | 90 | 91 | # Check elements horizontal alignment and equal distance between each other 92 | # e.g. 93 | # 94 | # | home_box_* are aligned horizontally next to each other with equal distance 95 | # 96 | @rule %{objectPattern} are aligned horizontally next to each other with equal distance 97 | @if ${count(objectPattern) > 1} 98 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 99 | @set _distance_a ${parseInt(_distance_) - 1} 100 | @set _distance_b ${parseInt(_distance_) + 1} 101 | @forEach [${objectPattern}] as item, next as nextItem 102 | ${item}: 103 | aligned horizontally all ${nextItem} 104 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 105 | 106 | # Check elements horizontal alignment top 107 | # e.g. 108 | # 109 | # | home_box_* are aligned horizontally next to each other 110 | # 111 | @rule %{objectPattern} are aligned horizontally top next to each other 112 | @if ${count(objectPattern) > 1} 113 | @forEach [${objectPattern}] as item, next as nextItem 114 | ${item}: 115 | aligned horizontally top ${nextItem} 116 | 117 | # Check elements horizontal alignment top and equal distance between each other 118 | # e.g. 119 | # 120 | # | home_box_* are aligned horizontally next to each other with equal distance 121 | # 122 | @rule %{objectPattern} are aligned horizontally top next to each other with equal distance 123 | @if ${count(objectPattern) > 1} 124 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].left() - all[0].right())} 125 | @set _distance_a ${parseInt(_distance_) - 1} 126 | @set _distance_b ${parseInt(_distance_) + 1} 127 | @forEach [${objectPattern}] as item, next as nextItem 128 | ${item}: 129 | aligned horizontally top ${nextItem} 130 | left-of ${nextItem} ${_distance_a} to ${_distance_b} px 131 | 132 | 133 | # Check elements vertical alignment and equal distance between each other 134 | # e.g. 135 | # 136 | # | home_box_* are aligned vertically above each other with equal distance 137 | # 138 | @rule %{objectPattern} are aligned vertically above each other with equal distance 139 | @if ${count(objectPattern) > 1} 140 | @set _distance_ ${var all = findAll(objectPattern); Math.abs(all[1].top() - all[0].bottom())} 141 | @forEach [${objectPattern}] as item, next as nextItem 142 | ${item}: 143 | aligned vertically all ${nextItem} 144 | above ${nextItem} ${_distance_} px 145 | 146 | 147 | 148 | # Check elements horizontal alignment and specific margin between each other 149 | # e.g. 150 | # 151 | # | home_box_* are aligned horizontally next to each other with 10 to 30px margin 152 | # 153 | @rule %{objectPattern} are aligned horizontally next to each other with %{margin} margin 154 | @forEach [${objectPattern}] as item, next as nextItem 155 | ${item}: 156 | aligned horizontally all ${nextItem} 157 | left-of ${nextItem} ${margin} 158 | 159 | 160 | 161 | # Check elements vertical alignment and specific margin between each other 162 | # e.g. 163 | # 164 | # | home_box_* are aligned vertically above each other with 10 to 20 px margin 165 | # 166 | @rule %{objectPattern} are aligned vertically above each other with %{margin} margin 167 | @forEach [${objectPattern}] as item, next as nextItem 168 | ${item}: 169 | aligned vertically all ${nextItem} 170 | above ${nextItem} ${margin} 171 | 172 | 173 | 174 | # Check that elements appear and hide on different tags 175 | # e.g. 176 | # 177 | # | login_button, menu.item-* should be visible on desktop, tablet but absent on mobile 178 | # 179 | @rule %{objectPatterns} should be visible on %{tagsVisible} but absent on %{tagsAbsent} 180 | ${objectPatterns}: 181 | @on ${tagsVisible} 182 | visible 183 | @on ${tagsAbsent} 184 | absent 185 | 186 | 187 | 188 | # Validate all matching objects using specified component spec 189 | # e.g. 190 | # 191 | # | test all box-* with components/box.gspec 192 | # 193 | @rule test all %{objectPattern} with %{componentPath} 194 | @forEach [${objectPattern}] as item 195 | ${item}: 196 | component ${componentPath} 197 | 198 | 199 | # Apply two specs to all elements in a single line 200 | # e.g. 201 | # 202 | # | every menu.item-* is inside menu 0px top bottom and has width 100px 203 | # 204 | # or 205 | # 206 | # | every menu.item-* has width and is inside menu 0px top bottom 207 | # 208 | @rule every %{objectPattern} %{verb: is|has} %{spec1} and %{verb2: is|has} %{spec2} 209 | @forEach [${objectPattern}] as item 210 | ${item}: 211 | ${spec1} 212 | ${spec2} 213 | 214 | 215 | # Apply spec to all elements in a single line 216 | # e.g. 217 | # 218 | # | every menu.item-* is inside menu 0px top bottom 219 | # 220 | # or 221 | # 222 | # | every menu.item-* has width 100px 223 | # 224 | @rule every %{objectPattern} %{verb: is|has} %{spec} 225 | @forEach [${objectPattern}] as item 226 | ${item}: 227 | ${spec} 228 | 229 | 230 | 231 | # Apply spec to only first element in a single line 232 | # e.g. 233 | # 234 | # | first menu.item-* is inside menu 0px top bottom 235 | # 236 | # or 237 | # 238 | # | first menu.item-* has width 100px 239 | # 240 | @rule first %{objectPattern} %{verb: is|has} %{spec} 241 | @if ${count(objectPattern) > 0} 242 | ${first(objectPattern).name}: 243 | ${spec} 244 | 245 | 246 | 247 | # Apply spec to only last element in a single line 248 | # e.g. 249 | # 250 | # | last menu.item-* is inside menu 0px top bottom 251 | # 252 | # or 253 | # 254 | # | last menu.item-* has width 100px 255 | # 256 | @rule last %{objectPattern} %{verb: is|has} %{spec} 257 | @if ${count(objectPattern) > 0} 258 | ${last(objectPattern).name}: 259 | ${spec} 260 | 261 | 262 | 263 | # Apply rule body to only first element 264 | # e.g. 265 | # 266 | # | first menu.item-* : 267 | # below header 10px 268 | # inside main_container 0px top left 269 | # 270 | @rule first %{objectPattern}: 271 | @if ${count(objectPattern) > 0} 272 | ${first(objectPattern).name}: 273 | @ruleBody 274 | 275 | 276 | # Apply rule body to only last element 277 | # e.g. 278 | # 279 | # | last menu.item-* : 280 | # above footer 10px 281 | # inside main_container 0px left right 282 | # 283 | @rule last %{objectPattern}: 284 | @if ${count(objectPattern) > 0} 285 | ${last(objectPattern).name}: 286 | @ruleBody 287 | 288 | -------------------------------------------------------------------------------- /testng/src/test/java/util/testng/GalenBaseTest.java: -------------------------------------------------------------------------------- 1 | package util.testng; 2 | 3 | import static java.util.Arrays.asList; 4 | 5 | import java.io.IOException; 6 | import java.net.MalformedURLException; 7 | import java.net.URISyntaxException; 8 | import java.net.URL; 9 | import java.util.List; 10 | import java.util.Properties; 11 | 12 | import com.galenframework.gspeclang2.pagespec.SectionFilter; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.openqa.selenium.By; 15 | import org.openqa.selenium.Dimension; 16 | import org.openqa.selenium.JavascriptExecutor; 17 | import org.openqa.selenium.WebDriver; 18 | import org.openqa.selenium.WebElement; 19 | import org.openqa.selenium.firefox.FirefoxDriver; 20 | import org.openqa.selenium.remote.DesiredCapabilities; 21 | import org.openqa.selenium.remote.RemoteWebDriver; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.testng.annotations.AfterClass; 25 | import org.testng.annotations.BeforeMethod; 26 | import org.testng.annotations.DataProvider; 27 | import org.testng.annotations.Listeners; 28 | 29 | import com.galenframework.api.Galen; 30 | import com.galenframework.reports.TestReport; 31 | import com.galenframework.reports.model.LayoutObject; 32 | import com.galenframework.reports.model.LayoutReport; 33 | import com.galenframework.reports.model.LayoutSection; 34 | import com.galenframework.reports.model.LayoutSpec; 35 | import com.galenframework.support.GalenReportsContainer; 36 | import com.galenframework.testng.GalenTestNgReportsListener; 37 | 38 | /** 39 | * Base class for all Galen tests.
40 | *
41 | * To run with maven against Selenium grid use:
42 | * mvn verify -Dselenium.grid=http://grid-ip:4444/wd/hub 43 | */ 44 | @Listeners(value = GalenTestNgReportsListener.class) 45 | public abstract class GalenBaseTest { 46 | 47 | private static final Logger LOG = LoggerFactory 48 | .getLogger("GalenBaseLayoutTests"); 49 | 50 | private WebDriver activeWebDriver; 51 | 52 | private static final String ENV_URL = "http://getbootstrap.com"; 53 | 54 | protected String getDefaultURL(){ 55 | return ENV_URL; 56 | } 57 | 58 | public WebElement scrollToElement(final By selector) throws MalformedURLException{ 59 | WebElement element = getDriver().findElement(selector); 60 | String coordY = Integer.toString(element.getLocation().getY()); 61 | ((JavascriptExecutor) getDriver()).executeScript("window.scrollTo(0, "+coordY+")"); 62 | return element; 63 | } 64 | 65 | public void clickElement(final By selector) throws MalformedURLException{ 66 | WebElement element = scrollToElement(selector); 67 | element.click(); 68 | } 69 | 70 | public void enterText(final By selector, final String text) throws MalformedURLException{ 71 | WebElement element = scrollToElement(selector); 72 | element.sendKeys(text); 73 | } 74 | 75 | 76 | public void verifyPage(final String uri, final TestDevice pDevice, final String specPath, final List groups) throws Exception { 77 | final String name = getCaller() + " on " + pDevice; 78 | load(uri); 79 | checkLayout(specPath, pDevice, name, groups); 80 | } 81 | 82 | public void verifyPage(final TestDevice pDevice, final String specPath, final List groups) throws Exception { 83 | final String name = getCaller() + " on " + pDevice; 84 | checkLayout(specPath, pDevice, name, groups); 85 | } 86 | 87 | public void load(final String uri) throws MalformedURLException { 88 | final String env = System.getProperty("selenium.start_uri"); 89 | final String completeUrl = (StringUtils.isEmpty(env) ? getDefaultURL() : env) 90 | + uri; 91 | getDriver().get(completeUrl); 92 | } 93 | 94 | public void checkLayout(final String pSpecPath, final TestDevice device, 95 | final String pName, final List groups) throws IOException, URISyntaxException { 96 | final String fullSpecPath; 97 | if (GalenBaseTest.class.getResource(pSpecPath) != null) { 98 | fullSpecPath = GalenBaseTest.class.getResource(pSpecPath).toURI() 99 | .getPath(); 100 | } else { 101 | fullSpecPath = pSpecPath; 102 | } 103 | TestReport test = GalenReportsContainer.get().registerTest(pName, groups); 104 | LayoutReport layoutReport = Galen.checkLayout(getDriver(), fullSpecPath, new SectionFilter(device.getTags(),null), 105 | new Properties(), null,null); 106 | layoutReport.setTitle(pName); 107 | test.layout(layoutReport, pName); 108 | if (layoutReport.errors() > 0) { 109 | final StringBuffer errorDetails = new StringBuffer(); 110 | for (LayoutSection layoutSection : layoutReport.getSections()) { 111 | final StringBuffer layoutDetails = new StringBuffer(); 112 | layoutDetails.append("\n").append("Layout Section: ") 113 | .append(layoutSection.getName()).append("\n"); 114 | for (LayoutObject layoutObject : layoutSection.getObjects()) { 115 | boolean hasErrors = false; 116 | final StringBuffer errorElementDetails = new StringBuffer(); 117 | errorElementDetails.append(" Element: ").append( 118 | layoutObject.getName()); 119 | for (LayoutSpec layoutSpec : layoutObject.getSpecs()) { 120 | if (layoutSpec.getErrors() != null && layoutSpec.getErrors().size() > 0) { 121 | errorElementDetails.append(layoutSpec 122 | .getErrors().toString()); 123 | hasErrors = true; 124 | } 125 | } 126 | if (hasErrors) { 127 | errorDetails.append("ViewPort Details: ") 128 | .append(device).append("\n"); 129 | errorDetails.append(layoutDetails); 130 | errorDetails.append(errorElementDetails).append("\n"); 131 | } 132 | } 133 | } 134 | throw new RuntimeException(errorDetails.toString()); 135 | } 136 | } 137 | 138 | @BeforeMethod(alwaysRun = true) 139 | public void setUpBrowser(final Object[] args) throws MalformedURLException { 140 | if (args != null && args.length > 0) { 141 | if (args[0] != null && args[0] instanceof TestDevice) { 142 | TestDevice device = (TestDevice) args[0]; 143 | if (device.getScreenSize() != null) { 144 | getDriver().manage().window() 145 | .setSize(device.getScreenSize()); 146 | } 147 | } 148 | } 149 | } 150 | 151 | @AfterClass(alwaysRun = true) 152 | public void quitDriver() throws MalformedURLException { 153 | if (activeWebDriver != null) { 154 | final String grid = System.getProperty("selenium.grid"); 155 | if (grid != null) { 156 | activeWebDriver.quit(); 157 | } else { 158 | ((RemoteWebDriver) activeWebDriver).close(); 159 | } 160 | } 161 | } 162 | 163 | public WebDriver getDriver() throws MalformedURLException { 164 | if (activeWebDriver == null) { 165 | final String grid = System.getProperty("selenium.grid"); 166 | if (grid == null) { 167 | activeWebDriver = new FirefoxDriver(); 168 | } else { 169 | // chrome runs much faster in a selenium grid 170 | activeWebDriver = new RemoteWebDriver(new URL(grid), 171 | DesiredCapabilities.chrome()); 172 | } 173 | } 174 | return activeWebDriver; 175 | 176 | } 177 | 178 | @DataProvider(name = "devices") 179 | public Object[][] devices() { 180 | LOG.info("devices"); 181 | return new Object[][] {// @formatter:off 182 | { new TestDevice("small-phone", new Dimension(280, 800), 183 | asList("small-phone", "phone", "mobile")) }, 184 | { new TestDevice("normal-phone", new Dimension(320, 800), 185 | asList("normal-phone", "phone", "mobile")) }, 186 | { new TestDevice("big-phone", new Dimension(380, 800), asList( 187 | "big-phone", "phone", "mobile")) }, 188 | { new TestDevice("small-tablet", new Dimension(450, 800), 189 | asList("small-tablet", "tablet", "mobile")) }, 190 | { new TestDevice("normal-tablet", new Dimension(450, 800), 191 | asList("normal-tablet", "tablet", "mobile")) }, 192 | { new TestDevice("desktop", new Dimension(1024, 800), asList( 193 | "desktop", "desktop")) }, 194 | { new TestDevice("fullhd", new Dimension(1920, 1080), asList( 195 | "fullhd", "desktop")) },// @formatter:on 196 | }; 197 | } 198 | 199 | private static String getCaller() throws ClassNotFoundException { 200 | Throwable t = new Throwable(); 201 | StackTraceElement[] elements = t.getStackTrace(); 202 | String callerMethodName = elements[2].getMethodName(); 203 | String callerClassName = elements[2].getClassName(); 204 | return callerClassName + "->" + callerMethodName; 205 | } 206 | 207 | public static class TestDevice { 208 | 209 | private final String name; 210 | private final Dimension screenSize; 211 | private final List tags; 212 | 213 | public TestDevice(String name, Dimension screenSize, List tags) { 214 | this.name = name; 215 | this.screenSize = screenSize; 216 | this.tags = tags; 217 | } 218 | 219 | public String getName() { 220 | return name; 221 | } 222 | 223 | public Dimension getScreenSize() { 224 | return screenSize; 225 | } 226 | 227 | public List getTags() { 228 | return tags; 229 | } 230 | 231 | /** 232 | * @see java.lang.Object#toString() 233 | */ 234 | @Override 235 | public String toString() { 236 | StringBuilder builder = new StringBuilder(); 237 | builder.append("TestDevice ["); 238 | if (name != null) { 239 | builder.append("name="); 240 | builder.append(name); 241 | } 242 | builder.append("]"); 243 | return builder.toString(); 244 | } 245 | } 246 | } -------------------------------------------------------------------------------- /testng/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | martinreinhardt-online.de.demos 8 | galen-samples-testng 9 | 0.1.0-SNAPSHOT 10 | jar 11 | 12 | 13 | Galen TestNG sample 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.codehaus.mojo 21 | build-helper-maven-plugin 22 | ${build-helper-maven-plugin.version} 23 | 24 | 25 | org.codehaus.mojo 26 | buildnumber-maven-plugin 27 | ${buildnumber-maven-plugin.version} 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | ${maven-compiler-plugin.version} 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-surefire-plugin 37 | ${maven-surefire-plugin.version} 38 | 39 | 40 | 41 | 42 | 43 | org.codehaus.mojo 44 | build-helper-maven-plugin 45 | 46 | 47 | parse-version 48 | 49 | parse-version 50 | 51 | 52 | 53 | 54 | 55 | org.codehaus.mojo 56 | buildnumber-maven-plugin 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-eclipse-plugin 61 | ${maven-eclipse-plugin.version} 62 | 63 | build/classes 64 | true 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-surefire-report-plugin 77 | ${maven-surefire-report-plugin.version} 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-project-info-reports-plugin 82 | ${maven-project-info-reports-plugin.version} 83 | 84 | false 85 | 86 | 87 | 88 | 89 | index 90 | dependency-convergence 91 | dependency-management 92 | issue-tracking 93 | license 94 | mailing-list 95 | plugin-management 96 | plugins 97 | project-team 98 | scm 99 | summary 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | log4j 110 | log4j 111 | ${log4j.version} 112 | provided 113 | 114 | 115 | org.slf4j 116 | slf4j-jdk14 117 | ${slf4j.version} 118 | provided 119 | 120 | 121 | org.slf4j 122 | slf4j-log4j12 123 | ${slf4j.version} 124 | provided 125 | 126 | 127 | org.slf4j 128 | slf4j-simple 129 | ${slf4j.version} 130 | provided 131 | 132 | 133 | 134 | 135 | org.testng 136 | testng 137 | ${testng.version} 138 | test 139 | 140 | 141 | com.galenframework 142 | galen-java-support 143 | ${galen.version} 144 | test 145 | 146 | 147 | com.galenframework 148 | galen-core 149 | ${galen.version} 150 | test 151 | 152 | 153 | org.seleniumhq.selenium 154 | selenium-java 155 | 156 | 157 | org.seleniumhq.selenium 158 | selenium-support 159 | 160 | 161 | 162 | 163 | org.seleniumhq.selenium 164 | selenium-java 165 | ${selenium.version} 166 | test 167 | 168 | 169 | org.seleniumhq.selenium 170 | selenium-support 171 | ${selenium.version} 172 | test 173 | 174 | 175 | 176 | 177 | 178 | 1.7 179 | 1.2.16 180 | 1.6.1 181 | 182 | 183 | 6.8.8 184 | 2.3.6 185 | 2.53.0 186 | 187 | 188 | 3.1 189 | 2.12 190 | 1.9 191 | 1.0 192 | 193 | 2.7 194 | 2.5 195 | 2.16 196 | 1.8 197 | 1.2.1 198 | 1.2 199 | 2.7 200 | 2.12 201 | 0.9 202 | 2.4 203 | 204 | 205 | 206 | scm:git://github.com/hypery2k/galen_samples.git 207 | scm:git:ssh://github.com/hypery2k/galen_samples.git 208 | https://github.com/hypery2k/galen_samples/tree/master 209 | 210 | 211 | 212 | GitHub 213 | https://github.com/hypery2k/galen_samples/issues 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /junit/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | martinreinhardt-online.de.demos 8 | galen-samples-junit 9 | 0.1.0-SNAPSHOT 10 | jar 11 | 12 | 13 | Galen JUnit sample 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.codehaus.mojo 21 | build-helper-maven-plugin 22 | ${build-helper-maven-plugin.version} 23 | 24 | 25 | org.codehaus.mojo 26 | buildnumber-maven-plugin 27 | ${buildnumber-maven-plugin.version} 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | ${maven-compiler-plugin.version} 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-surefire-plugin 37 | ${maven-surefire-plugin.version} 38 | 39 | 4 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.codehaus.mojo 48 | build-helper-maven-plugin 49 | 50 | 51 | parse-version 52 | 53 | parse-version 54 | 55 | 56 | 57 | 58 | 59 | org.codehaus.mojo 60 | buildnumber-maven-plugin 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-eclipse-plugin 65 | ${maven-eclipse-plugin.version} 66 | 67 | build/classes 68 | true 69 | true 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-surefire-report-plugin 81 | ${maven-surefire-report-plugin.version} 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-project-info-reports-plugin 86 | ${maven-project-info-reports-plugin.version} 87 | 88 | false 89 | 90 | 91 | 92 | 93 | index 94 | dependency-convergence 95 | dependency-management 96 | issue-tracking 97 | license 98 | mailing-list 99 | plugin-management 100 | plugins 101 | project-team 102 | scm 103 | summary 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | log4j 114 | log4j 115 | ${log4j.version} 116 | provided 117 | 118 | 119 | org.slf4j 120 | slf4j-jdk14 121 | ${slf4j.version} 122 | provided 123 | 124 | 125 | org.slf4j 126 | slf4j-log4j12 127 | ${slf4j.version} 128 | provided 129 | 130 | 131 | org.slf4j 132 | slf4j-simple 133 | ${slf4j.version} 134 | provided 135 | 136 | 137 | 138 | 139 | junit 140 | junit 141 | ${junit.version} 142 | test 143 | 144 | 145 | com.galenframework 146 | galen-java-support 147 | ${galen.version} 148 | test 149 | 150 | 151 | com.galenframework 152 | galen-core 153 | ${galen.version} 154 | test 155 | 156 | 157 | org.seleniumhq.selenium 158 | selenium-java 159 | 160 | 161 | org.seleniumhq.selenium 162 | selenium-support 163 | 164 | 165 | 166 | 167 | org.seleniumhq.selenium 168 | selenium-java 169 | ${selenium.version} 170 | test 171 | 172 | 173 | org.seleniumhq.selenium 174 | selenium-support 175 | ${selenium.version} 176 | test 177 | 178 | 179 | 180 | 181 | 182 | 183 | 1.7 184 | 1.2.16 185 | 1.6.1 186 | 1.2.16 187 | 1.6.1 188 | /opt/dev/geckodriver 189 | /opt/dev/chromedriver 190 | 191 | 4.11 192 | 2.3.6 193 | 2.53.0 194 | 195 | 196 | 3.1 197 | 2.12 198 | 1.9 199 | 1.0 200 | 201 | 2.7 202 | 2.5 203 | 2.16 204 | 1.8 205 | 1.2.1 206 | 1.2 207 | 2.7 208 | 2.12 209 | 0.9 210 | 2.4 211 | 212 | 213 | 214 | scm:git://github.com/hypery2k/galen_samples.git 215 | scm:git:ssh://github.com/hypery2k/galen_samples.git 216 | https://github.com/hypery2k/galen_samples/tree/master 217 | 218 | 219 | 220 | GitHub 221 | https://github.com/hypery2k/galen_samples/issues 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /javascript/grunt/gl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Galen JavaScript API 3 | */ 4 | 5 | /* 6 | * Global public API 7 | * 8 | * @global config 9 | * @global gl 10 | * @global elements 11 | */ 12 | var config; 13 | var gl; 14 | var elements; 15 | 16 | /** 17 | * config module describes the configuration of the testing 18 | * suite. It includes information about: 19 | * - project name and url address 20 | * - testing devices 21 | * - selenium grid settings 22 | * 23 | * Useful public interface methods: 24 | * - getProjectName: returns the project codename 25 | * - getProjectPage: returns project url address 26 | * - getProjectSubpage: returns project url address with appended subdir 27 | * 28 | * @returns {Array|Object} public interface methods 29 | */ 30 | config = function () { 31 | /* 32 | * @private 33 | * 34 | * Default project configuration 35 | */ 36 | var _project = { 37 | name: 'Project', 38 | protocol: 'http', 39 | host: '127.0.0.1', 40 | port: 80 41 | }; 42 | 43 | /* 44 | * @private 45 | * 46 | * Default Selenium grid configuration 47 | */ 48 | var _slGrid = { 49 | enabled: false, 50 | url: '' 51 | }; 52 | 53 | /* 54 | * @private 55 | */ 56 | var _devices = {}; 57 | var _singleDevice = false; 58 | 59 | /** 60 | * Set configuration. 61 | * @param {Object} configuration 62 | */ 63 | function set(data) { 64 | if (!data.project) { 65 | data.project = { 66 | name: null, 67 | url: null 68 | }; 69 | } 70 | 71 | config.setProjectName(data.project.name || 'Project'); 72 | config.setProjectUrl(data.project.url || data.url || 'http://127.0.0.1:80'); 73 | config.setDevices(data.devices || {}); 74 | 75 | if (data.seleniumGrid) { 76 | config.setSeleniumGrid(data.seleniumGrid.url); 77 | } 78 | }; 79 | 80 | /** 81 | * Enable Selenium grid. 82 | * @param {String} url Selenium Grid HUB url 83 | */ 84 | function setSeleniumGrid(url) { 85 | _slGrid.enabled = true; 86 | _slGrid.url = url; 87 | }; 88 | 89 | /** 90 | * Selenium Grid getter. 91 | * @returns {Object} Selenium Grid configuration 92 | */ 93 | function getSeleniumGrid() { 94 | return _slGrid; 95 | }; 96 | 97 | /** 98 | * Single device getter. 99 | * @returns {Boolean} true, if only one device is necessary 100 | */ 101 | function getSingleDeviceState() { 102 | return _singleDevice; 103 | }; 104 | 105 | /** 106 | * Set devices to be tested on, in form of an Object 107 | * container. Configurations depend on the target environment, 108 | * but two most useful docs for that are: 109 | * - http://galenframework.com/docs/reference-javascript-tests-guide/#CreatingdriverinSeleniumGrid 110 | * - https://docs.saucelabs.com/reference/test-configuration/ 111 | * 112 | * Also, call singleBrowserTests() to determine if only one device type is sufficient. 113 | * 114 | * @param {Object} devices devices container 115 | */ 116 | function setDevices(devices) { 117 | _devices = devices || {}; 118 | 119 | _singleDevice = singleBrowserTests(); 120 | }; 121 | 122 | /** 123 | * Devices getter. 124 | * @returns {Object} devices or empty object 125 | */ 126 | function getDevices() { 127 | return _devices; 128 | }; 129 | 130 | /** 131 | * Set project codename, available for tests via the public 132 | * methods interface. 133 | * @param {String} name project name 134 | */ 135 | function setProjectName(name) { 136 | _project.name = name; 137 | }; 138 | 139 | /** 140 | * Project name getter. 141 | * @returns {String} non-null project name 142 | */ 143 | function getProjectName() { 144 | return _project.name; 145 | }; 146 | 147 | /** 148 | * Set project url, available for tests via the public 149 | * methods interface. 150 | * Safest form would be: 151 | * {protocol}://{host}:{port}/ 152 | * Other forms' behaviour may vary. 153 | * 154 | * @param {String} url project url 155 | */ 156 | function setProjectUrl(url) { 157 | var _url = (url.split(/(:\/{2})|(:)|(\/{1}.*)/gm) || []).filter(Boolean); 158 | 159 | if (_url.length < 3) { 160 | throw new Error('Invalid project url'); 161 | } 162 | 163 | _project.protocol = _url[0]; 164 | _project.host = _url[2]; 165 | _project.port = _url.length >= 4 ? _url[4] : 80; 166 | }; 167 | 168 | /** 169 | * Getter for project base url address, available for tests via the public 170 | * methods interface. 171 | * @returns {String} url 172 | */ 173 | function getProjectPage() { 174 | return [ 175 | _project.protocol, 176 | '://', 177 | _project.host, 178 | ':', 179 | _project.port 180 | ].join(''); 181 | }; 182 | 183 | /** 184 | * Getter for project subpage url. Transforms the base url 185 | * by appending a proper suffix, ex.: 186 | * getProjectSubpage('#login') => http://myproj.com/#login 187 | * 188 | * Available via the public methods interface. 189 | * @param {String} url subpage 190 | * @returns {String} url 191 | */ 192 | function getProjectSubpage(url) { 193 | return [ 194 | getProjectPage(), 195 | url.indexOf('/') !== 0 ? url : url.substr(1) 196 | ].join('/'); 197 | }; 198 | 199 | /** 200 | * Determine whether tests are run on one or more browsers. 201 | * This allows a simple optimisation for local testing. 202 | * 203 | * @returns {Boolean} true, if only one browser is needed 204 | */ 205 | function singleBrowserTests() { 206 | var devices = getDevices(); 207 | var singleDevice = true; 208 | var priorType; 209 | 210 | forAll(devices, function (device) { 211 | if (!priorType) { 212 | priorType = device.browser; 213 | } else { 214 | if (device.browser !== priorType) { 215 | singleDevice = false; 216 | } 217 | } 218 | }); 219 | return singleDevice; 220 | }; 221 | 222 | /* 223 | * @public 224 | * 225 | * Public API 226 | */ 227 | return { 228 | set: set, 229 | setDevices: setDevices, 230 | setProjectName: setProjectName, 231 | setProjectUrl: setProjectUrl, 232 | setSeleniumGrid: setSeleniumGrid, 233 | 234 | getDevices: getDevices, 235 | getProjectName: getProjectName, 236 | getProjectPage: getProjectPage, 237 | getProjectSubpage: getProjectSubpage, 238 | getSeleniumGrid: getSeleniumGrid, 239 | getSingleDeviceState: getSingleDeviceState 240 | }; 241 | }(); 242 | 243 | /* 244 | * @public 245 | * 246 | * Cached elements on the page. 247 | */ 248 | elements = {}; 249 | 250 | /** 251 | * gl module serves a bunch of useful and fast implementations 252 | * of original Galen JavaScript API. 253 | * @returns {Object} public interface methods 254 | */ 255 | gl = function () { 256 | /* 257 | * @private 258 | * 259 | * Available webdrivers (one for every view type). 260 | */ 261 | var _drivers = {}; 262 | 263 | /** 264 | * @private 265 | * @class 266 | * 267 | * GalenPages forces us to create a page class. 268 | * It is created here as an uniformal scheme and 269 | * later used for every page initialized by tests. 270 | * @param {Object} driver target webdriver 271 | * @param {Object} name name of a page to be used in reports 272 | * @param {Object} primaryFields set of key/values pairs containing page elements and functions that will be in-built into the page 273 | * @param {Object} secondaryFields same as primaryFields but the elements defined inside this structure will not be used in waitForIt function 274 | */ 275 | function GalenPageCls(driver, name, primaryFields, secondaryFields) { 276 | GalenPages.extendPage(this, driver, name, primaryFields || {}, secondaryFields || {}); 277 | } 278 | 279 | /** 280 | * This method shortcuts both driver and page creation. 281 | * We open a page in a target driver and ask it to select 282 | * necessary elements before it proceeds to tests. 283 | * 284 | * Depending on the test files and options, this method behaves differently. 285 | * 286 | * If tests are run only on a single browser (which is ofter a case for local tests 287 | * on Firefox webdriver), only one instance is created and reinitialized for every new 288 | * test. This extremely speeds up the testing process, but is only available for local 289 | * testing. (whether only one device is needed determines #getSingleDeviceState) 290 | * 291 | * @see getSingleDeviceState 292 | * 293 | * If multiple browsers are present, all tests are run separately to preserve proper 294 | * execution. 295 | * 296 | * If tests are run remotely on Selenium Grid, all have to be run separately. This is due 297 | * to a timeout errors of Selenium Grids (ex. on SauceLabs if test exceeds a preset 298 | * execution time, test is aborted). For remote testing `concat` option in suggested 299 | * to minify the amount of instances created on Selenium Grid. 300 | * 301 | * @param {Object} device device specification 302 | * @param {String} url tested page url 303 | * @param {Object} name name of a page to be used in reports 304 | * @param {Object} primaryFields set of key/values pairs containing page elements and functions that will be in-built into the page 305 | * @param {Object} secondaryFields same as primaryFields but the elements defined inside this structure will not be used in waitForIt function 306 | */ 307 | function openPage(device, url, name, primaryFields, secondaryFields) { 308 | var page; 309 | var slGrid = config.getSeleniumGrid(); 310 | var singleDevice = config.getSingleDeviceState(); 311 | var targetDevice; 312 | 313 | primaryFields = primaryFields || {}; 314 | secondaryFields = secondaryFields || {}; 315 | name = name || url; 316 | 317 | if (slGrid.enabled === false) { 318 | if (singleDevice === true) { 319 | targetDevice = getSingleDeviceTarget(device); 320 | } else { 321 | targetDevice = getMultiDeviceTarget(device); 322 | } 323 | } else { 324 | targetDevice = getSlGridTarget(device, slGrid); 325 | } 326 | page = new GalenPageCls(targetDevice, name, primaryFields, secondaryFields); 327 | page.open(url); 328 | 329 | if (Object.keys(primaryFields).length > 0) { 330 | // Legen - 331 | page.waitForIt(); 332 | // - dary! 333 | } 334 | 335 | elements = page; 336 | } 337 | 338 | /** 339 | * Target device getter for single device tests. 340 | * 341 | * @param {Object} device device specification 342 | * @returns {Object} target instance 343 | */ 344 | function getSingleDeviceTarget(device) { 345 | if (!_drivers[0]) { 346 | _drivers[0] = createDriver('about:blank', device.size, device.browser); 347 | } else { 348 | resize(_drivers[0], device.size); 349 | } 350 | 351 | return _drivers[0]; 352 | } 353 | 354 | /** 355 | * Target device getter for multiple device tests. 356 | * 357 | * @param {Object} device device specification 358 | * @returns {Object} target instance 359 | */ 360 | function getMultiDeviceTarget(device) { 361 | if (!_drivers[device.deviceName]) { 362 | _drivers[device.deviceName] = createDriver('about:blank', device.size, device.browser); 363 | } 364 | 365 | return _drivers[device.deviceName]; 366 | } 367 | 368 | /** 369 | * Target device getter for selenium grid tests. 370 | * 371 | * This looks unnecessary, but added to keep consistency. 372 | * 373 | * @param {Object} device device specification 374 | * @param {Object} slGrid selenium grid specification 375 | * @returns {Object} target instance 376 | */ 377 | function getSlGridTarget(device, slGrid) { 378 | var driver = _drivers[device.deviceName]; 379 | if (!driver) { 380 | driver = _drivers[device.deviceName] = createGridDriver(slGrid.url, device); 381 | } 382 | 383 | return driver; 384 | } 385 | 386 | /** 387 | * Run a Galen spec file on the current page on a 388 | * target driver. 389 | * 390 | * Argument `specFile` is relative to gl.js, not the test 391 | * file. 392 | * 393 | * @param {Object} device device specification 394 | * @param {String} specFile Galen spec file path 395 | * @param {Array} tags (optional) Galen test tags 396 | */ 397 | function runSpecFile(device, specFile, tags) { 398 | var useSingleDevice = config.getSingleDeviceState() && !config.getSeleniumGrid().enabled; 399 | 400 | checkLayout( 401 | _drivers[useSingleDevice ? 0 : device.deviceName], 402 | specFile, 403 | tags 404 | ); 405 | } 406 | 407 | /** 408 | * Remove all elements stored in cache. 409 | * 410 | * Unlikely to be used in tests. 411 | */ 412 | function cleanCache() { 413 | elements = {}; 414 | } 415 | 416 | /** 417 | * Terminate all webdrivers, cleanCache and finish 418 | * the testing. 419 | * 420 | * Unlikely to be used in tests, unless you disable 421 | * gl#defaultAfterTest method. 422 | */ 423 | function quit() { 424 | if (_drivers) { 425 | forAll(_drivers, function (driver) { 426 | driver.quit(); 427 | driver = null; 428 | }); 429 | 430 | _drivers = {}; 431 | } 432 | cleanCache(); 433 | } 434 | 435 | /** 436 | * After a single test: clean elements cache. 437 | * After all tests: quit the testing process. 438 | */ 439 | function defaultAfterTest() { 440 | var slGrid = config.getSeleniumGrid(); 441 | 442 | if (!slGrid.enabled) { 443 | afterTest(function () { 444 | gl.cleanCache(); 445 | }); 446 | } else { 447 | afterTest(function () { 448 | gl.quit(); 449 | }); 450 | } 451 | 452 | afterTestSuite(function () { 453 | gl.quit(); 454 | }); 455 | } 456 | 457 | /* 458 | * @public 459 | * 460 | * Public API 461 | */ 462 | return { 463 | openPage: openPage, 464 | runSpecFile: runSpecFile, 465 | 466 | quit: quit, 467 | cleanCache: cleanCache, 468 | 469 | defaultAfterTest: defaultAfterTest 470 | }; 471 | }(); 472 | 473 | /* 474 | * Initialize default pre- and post- test callbacks. 475 | */ 476 | load('gl.config.js'); 477 | gl.defaultAfterTest(); 478 | --------------------------------------------------------------------------------