diff --git a/.travis.yml b/.travis.yml index 94e46a73..3698fc1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,43 +1,80 @@ language: php - sudo: false +dist: trusty -cache: - directories: - - $HOME/.composer/cache/files +services: + - docker + +addons: + chrome: stable -php: [5.4, 5.5, 5.6, 7.0, 7.1, 7.2] +php: + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - 7.3 env: global: - - WEBDRIVER=selenium + - DISPLAY=:99.0 + - BROWSER_NAME="chrome" + - DRIVER_OPTIONS='{"args":["headless", "no-sandbox","window-size=1024,768"]}' + - CHROMEDRIVER_VERSION="2.38" + - START_XVFB="0" + - SELENIUM_DRIVER="" + +cache: + directories: + - $HOME/.composer/cache/files + - jar matrix: - fast_finish: true include: - php: 7.0 - env: WEBDRIVER=selenium-remote - sudo: required - services: - - docker - - php: 5.3 - dist: precise - # Force using PHP 5.6 for the test server as PHP 5.3 does not have the builtin webserver - env: MINK_PHP_BIN=~/.phpenv/versions/5.6/bin/php + env: + - SELENIUM_DRIVER="2.53.1" + - BROWSER_NAME="firefox" + # XVFB with regular Chrome (not headless) + - php: 7.2 + env: + - DRIVER_OPTIONS='{"args":["no-sandbox","window-size=1024,768"]}' + - START_XVFB=1 + # build with lowest deps + - php: 7.2 + env: + - COMPOSER_FLAGS="--prefer-lowest" + # build with latest chromedriver + - php: 7.2 + env: + - CHROMEDRIVER_VERSION="latest" -before_script: - - sh bin/run-"$WEBDRIVER".sh - - - composer install +install: + - travis_retry composer update --no-interaction $COMPOSER_FLAGS - # Start a webserver for web fixtures. - - vendor/bin/mink-test-server > /dev/null 2>&1 & +before_script: + - mkdir ./logs + - if [[ "$BROWSER_NAME" = "chrome" && "$CHROMEDRIVER_VERSION" = "latest" ]]; then CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`; fi + - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi + - if [ "$START_XVFB" = "1" ]; then sh -e /etc/init.d/xvfb start; fi; + - | + if [ -z "$SELENIUM_DRIVER" ]; then + ./chromedriver/chromedriver --port=4444 --url-base=wd/hub --verbose &> ./logs/chromedriver.log & + else + docker run --rm --network=host -p 4444:4444 "selenium/standalone-firefox:$SELENIUM_DRIVER" &> ./logs/selenium.log & + fi; + - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for WebDriver on port 4444...; done; echo "ChromeDriver started" + - travis_retry ./vendor/bin/mink-test-server &> ./logs/mink-test-server.log & + - until $(echo | nc localhost 8002); do sleep 1; echo waiting for PHP server on port 8002...; done; echo "PHP server started" -script: phpunit -v --coverage-clover=coverage.clover +script: + - phpunit -v --coverage-clover=coverage.clover after_script: - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover coverage.clover after_failure: - - cat /tmp/webdriver_output.txt + - if [ -f ./logs/chromedriver.log ]; then cat ./logs/chromedriver.log; fi + - if [ -f ./logs/selenium.log ]; then cat ./logs/selenium.log; fi + - if [ -f ./logs/mink-test-server.log ]; then cat ./logs/mink-test-server.log; fi \ No newline at end of file diff --git a/README.md b/README.md index 01a9e3ce..50bfef0e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,29 @@ $> curl -sS http://getcomposer.org/installer | php $> php composer.phar install ``` +Testing +------------ + +1. Start WebDriver + 1. If you have Docker installed, run + ```bash + docker run -p 4444:4444 selenium/standalone-firefox:2.53.1 + ``` + 2. If you do not have Docker, but you have Java + ```bash + curl -L http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar > selenium-server-standalone-2.53.1.jar + java -jar selenium-server-standalone-2.53.1.jar + ``` +2. Start WebServer by running + ``` bash + ./vendor/bin/mink-test-server + ``` +3. Start PhpUnit + ```bash + composer require --dev phpunit/phpunit + ./vendor/bin/phpunit -v --coverage-clover=coverage.clover + ``` + Copyright --------- @@ -58,4 +81,4 @@ Maintainers ----------- * Christophe Coevoet [stof](https://github.com/stof) -* Pete Otaqui [pete-otaqui](http://github.com/pete-otaqui) +* Pete Otaqui [pete-otaqui](http://github.com/pete-otaqui) \ No newline at end of file diff --git a/bin/run-selenium-remote.sh b/bin/run-selenium-remote.sh deleted file mode 100644 index 9d2205d1..00000000 --- a/bin/run-selenium-remote.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -set -e - -echo ' Downloading selenium' -docker pull selenium/standalone-firefox:2.53.1 -echo ' Running selenium' -docker run -d -p 4444:4444 --network=host selenium/standalone-firefox:2.53.1 diff --git a/bin/run-selenium.sh b/bin/run-selenium.sh deleted file mode 100644 index e846d3ae..00000000 --- a/bin/run-selenium.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env sh -set -e - -echo ' Starting XVFB' -sh -e /etc/init.d/xvfb start -export DISPLAY=:99.0 - -echo ' Downloading selenium' -curl -L http://selenium-release.storage.googleapis.com/2.52/selenium-server-standalone-2.52.0.jar > selenium.jar -echo ' Running selenium' -java -jar selenium.jar -log /tmp/webdriver.log > /tmp/webdriver_output.txt 2>&1 & diff --git a/composer.json b/composer.json index bda59eae..1c44b4cf 100644 --- a/composer.json +++ b/composer.json @@ -18,32 +18,39 @@ "homepage": "http://everzet.com" } ], - + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/oleg-andreyev/driver-testsuite.git", + "no-api": true + }, + { + "type": "vcs", + "url": "https://github.com/oleg-andreyev/php-webdriver.git", + "no-api": true + } + ], "require": { - "php": ">=5.3.1", - "behat/mink": "~1.7@dev", - "instaclick/php-webdriver": "~1.1" + "php": ">=5.6", + "behat/mink": "~1.7@dev", + "facebook/webdriver": "dev-adjust-getRelatedElements" }, - "require-dev": { - "mink/driver-testsuite": "dev-master" + "mink/driver-testsuite": "dev-integration-branch" }, - "autoload": { "psr-4": { "Behat\\Mink\\Driver\\": "src/" } }, - "autoload-dev": { "psr-4": { "Behat\\Mink\\Tests\\Driver\\": "tests" } }, - "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 58a01533..a97185f2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,9 @@ - + tests @@ -11,8 +14,18 @@ - - + + + + + + + + + + + + @@ -20,6 +33,8 @@ + + diff --git a/src/Resources/syn.js b/src/Resources/syn.js deleted file mode 100644 index f48ee1f9..00000000 --- a/src/Resources/syn.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Syn - 0.0.2 - * - * @copyright 2014 Bitovi - * Mon, 30 Jun 2014 22:44:59 GMT - * @license MIT - */ -!function(window){var __m2=function(){var a,b,c,d,e=window.Syn?window.Syn:{},f=function(a,b){var c;for(c in b)a[c]=b[c];return a},g={msie:!(!window.attachEvent||window.opera),opera:!!window.opera,webkit:navigator.userAgent.indexOf("AppleWebKit/")>-1,safari:navigator.userAgent.indexOf("AppleWebKit/")>-1&&-1===navigator.userAgent.indexOf("Chrome/"),gecko:navigator.userAgent.indexOf("Gecko")>-1,mobilesafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/),rhino:navigator.userAgent.match(/Rhino/)&&!0},h=function(a,b,c){var d=c.ownerDocument.createEventObject();return f(d,b)},i={},j=1,k="_synthetic"+(new Date).getTime(),l=/keypress|keyup|keydown/,m=/load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,n=function(a,b,c,d){return new n.init(a,b,c,d)};n.config=e,n.__tryFocus=function(a){try{a.focus()}catch(b){}},a=function(a,b,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},b=function(a,b,c){return a.addEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)},c=n.config.schedule||function(a,b){setTimeout(a,b)},f(n,{init:function(a,b,c,d){var e=n.args(b,c,d),f=this;this.queue=[],this.element=e.element,"function"==typeof this[a]?this[a](e.options,e.element,function(){e.callback&&e.callback.apply(f,arguments),f.done.apply(f,arguments)}):(this.result=n.trigger(a,e.options,e.element),e.callback&&e.callback.call(this,e.element,this.result))},jquery:function(a){return window.FuncUnit&&window.FuncUnit.jQuery?window.FuncUnit.jQuery:a?n.helpers.getWindow(a).jQuery||window.jQuery:window.jQuery},args:function(){for(var a={},b=0;b0&&g.apply(this,[])},d.dispatchEvent(c),0>=h}try{window.event=c}catch(i){}return d.sourceIndex<=0||d.fireEvent&&d.fireEvent("on"+e,c)},create:{page:{event:function(a,b,c){var d,e=n.helpers.getWindow(c).document||document;if(e.createEvent)return d=e.createEvent("Events"),d.initEvent(a,!0,!0),d;try{d=h(a,b,c)}catch(f){}return d}},focus:{event:function(a,b,c){return n.onParents(c,function(a){if(n.isFocusable(a)){if("html"!==a.nodeName.toLowerCase())n.__tryFocus(a),d=a;else if(d){var b=n.helpers.getWindow(c).document;if(b!==window.document)return!1;b.activeElement?(b.activeElement.blur(),d=null):(d.blur(),d=null)}return!1}}),!0}}},support:{clickChanges:!1,clickSubmits:!1,keypressSubmits:!1,mouseupSubmits:!1,radioClickChanges:!1,focusChanges:!1,linkHrefJS:!1,keyCharacters:!1,backspaceWorks:!1,mouseDownUpClicks:!1,tabKeyTabs:!1,keypressOnAnchorClicks:!1,optionClickBubbles:!1,ready:0},trigger:function(a,b,c){b||(b={});var d,e,f,g=n.create,h=g[a]&&g[a].setup,i=l.test(a)?"key":m.test(a)?"page":"mouse",j=g[a]||{},k=g[i],o=c;return 2===n.support.ready&&h&&h(a,b,c),f=b._autoPrevent,delete b._autoPrevent,j.event?e=j.event(a,b,c):(b=k.options?k.options(a,b,c):b,!n.support.changeBubbles&&/option/i.test(c.nodeName)&&(o=c.parentNode),d=k.event(a,b,o),e=n.dispatch(d,o,a,f)),e&&2===n.support.ready&&n.defaults[a]&&n.defaults[a].call(c,b,f),e},eventSupported:function(a){var b=document.createElement("div");a="on"+a;var c=a in b;return c||(b.setAttribute(a,"return;"),c="function"==typeof b[a]),b=null,c}}),f(n.init.prototype,{then:function(a,b,c,d){n.autoDelay&&this.delay();var e=n.args(b,c,d),f=this;return this.queue.unshift(function(b){return"function"!=typeof this[a]?(this.result=n.trigger(a,e.options,e.element),e.callback&&e.callback.call(this,e.element,this.result),this):(this.element=e.element||b,void this[a](e.options,this.element,function(){e.callback&&e.callback.apply(f,arguments),f.done.apply(f,arguments)}))}),this},delay:function(a,b){"function"==typeof a&&(b=a,a=null),a=a||600;var d=this;return this.queue.unshift(function(){c(function(){b&&b.apply(d,[]),d.done.apply(d,arguments)},a)}),this},done:function(a,b){b&&(this.element=b),this.queue.length&&this.queue.pop().call(this,this.element,a)},_click:function(a,b,d,e){n.helpers.addOffset(a,b),n.trigger("mousedown",a,b),c(function(){n.trigger("mouseup",a,b),!n.support.mouseDownUpClicks||e?(n.trigger("click",a,b),d(!0)):(n.create.click.setup("click",a,b),n.defaults.click.call(b),c(function(){d(!0)},1))},1)},_rightClick:function(a,b,d){n.helpers.addOffset(a,b);var e=f(f({},n.mouse.browser.right.mouseup),a);n.trigger("mousedown",e,b),c(function(){n.trigger("mouseup",e,b),n.mouse.browser.right.contextmenu&&n.trigger("contextmenu",f(f({},n.mouse.browser.right.contextmenu),a),b),d(!0)},1)},_dblclick:function(a,b,d){n.helpers.addOffset(a,b);var e=this;this._click(a,b,function(){c(function(){e._click(a,b,function(){n.trigger("dblclick",a,b),d(!0)},!0)},2)})}});for(var o=["click","dblclick","move","drag","key","type","rightClick"],p=function(a){n[a]=function(b,c,d){return n("_"+a,b,c,d)},n.init.prototype[a]=function(b,c,d){return this.then("_"+a,b,c,d)}},q=0;qb;b++)if(b in this&&this[b]===a)return b;return-1};a.typeable=function(a){-1===c.call(b,a)&&b.push(a)},a.typeable.test=function(a){for(var c=0,d=b.length;d>c;c++)if(b[c](a))return!0;return!1};var d=a.typeable,e=/input|textarea/i;return d(function(a){return e.test(a.nodeName)}),d(function(a){return-1!==c.call(["","true"],a.getAttribute("contenteditable"))}),a}(__m2),__m5=function(a){var b=a.helpers,c=function(a){var c,d,f;if(void 0!==a.selectionStart)return document.activeElement&&document.activeElement!==a&&a.selectionStart===a.selectionEnd&&0===a.selectionStart?{start:a.value.length,end:a.value.length}:{start:a.selectionStart,end:a.selectionEnd};try{if("input"===a.nodeName.toLowerCase())return c=b.getWindow(a).document.selection.createRange(),d=a.createTextRange(),d.setEndPoint("EndToStart",c),f=d.text.length,{start:f,end:f+c.text.length};c=b.getWindow(a).document.selection.createRange(),d=c.duplicate();var g=c.duplicate(),h=c.duplicate();g.collapse(),h.collapse(!1),g.moveStart("character",-1),h.moveStart("character",-1),d.moveToElementText(a),d.setEndPoint("EndToEnd",c),f=d.text.length-c.text.length;var i=d.text.length;return 0!==f&&""===g.text&&(f+=2),0!==i&&""===h.text&&(i+=2),{start:f,end:i}}catch(j){var k=e.test(a.nodeName)?"value":"textContent";return{start:a[k].length,end:a[k].length}}},d=function(c){for(var d=b.getWindow(c).document,e=[],f=d.getElementsByTagName("*"),g=f.length,h=0;g>h;h++)a.isFocusable(f[h])&&f[h]!==d.documentElement&&e.push(f[h]);return e},e=/input|textarea/i,f=function(){var a=document.createElement("span");return null!=a.textContent?"textContent":"innerText"}(),g=function(a){return e.test(a.nodeName)?a.value:a[f]},h=function(a,b){e.test(a.nodeName)?a.value=b:a[f]=b};b.extend(a,{keycodes:{"\b":8," ":9,"\r":13,shift:16,ctrl:17,alt:18,"pause-break":19,caps:20,escape:27,"num-lock":144,"scroll-lock":145,print:44,"page-up":33,"page-down":34,end:35,home:36,left:37,up:38,right:39,down:40,insert:45,"delete":46," ":32,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,num0:96,num1:97,num2:98,num3:99,num4:100,num5:101,num6:102,num7:103,num8:104,num9:105,"*":106,"+":107,subtract:109,decimal:110,divide:111,";":186,"=":187,",":188,dash:189,"-":189,period:190,".":190,"forward-slash":191,"/":191,"`":192,"[":219,"\\":220,"]":221,"'":222,"left window key":91,"right window key":92,"select key":93,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123},selectText:function(b,c,d){if(b.setSelectionRange)d?(b.selectionStart=c,b.selectionEnd=d):(a.__tryFocus(b),b.setSelectionRange(c,c));else if(b.createTextRange){var e=b.createTextRange();e.moveStart("character",c),d=d||c,e.moveEnd("character",d-b.value.length),e.select()}},getText:function(b){if(a.typeable.test(b)){var d=c(b);return b.value.substring(d.start,d.end)}var e=a.helpers.getWindow(b);return e.getSelection?e.getSelection().toString():e.document.getSelection?e.document.getSelection().toString():e.document.selection.createRange().text},getSelection:c}),b.extend(a.key,{data:function(c){if(a.key.browser[c])return a.key.browser[c];for(var d in a.key.kinds)if(b.inArray(c,a.key.kinds[d])>-1)return a.key.browser[d];return a.key.browser.character},isSpecial:function(b){for(var c=a.key.kinds.special,d=0;d-1&&a.key.defaults[d])return a.key.defaults[d];return a.key.defaults.character},defaults:{character:function(b,c,d,e,f){if(/num\d+/.test(d)&&(d=d.match(/\d+/)[0]),e||!a.support.keyCharacters&&a.typeable.test(this)){var i=g(this),j=i.substr(0,f.start),k=i.substr(f.end),l=d;h(this,j+l+k);var m="\n"===l&&a.support.textareaCarriage?2:l.length;a.selectText(this,j.length+m)}},c:function(){a.key.ctrlKey?a.key.clipboard=a.getText(this):a.key.defaults.character.apply(this,arguments)},v:function(b,c,d,e,f){a.key.ctrlKey?a.key.defaults.character.call(this,b,c,a.key.clipboard,!0,f):a.key.defaults.character.apply(this,arguments)},a:function(){a.key.ctrlKey?a.selectText(this,0,g(this).length):a.key.defaults.character.apply(this,arguments)},home:function(){a.onParents(this,function(a){return a.scrollHeight!==a.clientHeight?(a.scrollTop=0,!1):void 0})},end:function(){a.onParents(this,function(a){return a.scrollHeight!==a.clientHeight?(a.scrollTop=a.scrollHeight,!1):void 0})},"page-down":function(){a.onParents(this,function(a){if(a.scrollHeight!==a.clientHeight){var b=a.clientHeight;return a.scrollTop+=b,!1}})},"page-up":function(){a.onParents(this,function(a){if(a.scrollHeight!==a.clientHeight){var b=a.clientHeight;return a.scrollTop-=b,!1}})},"\b":function(b,c,d,e,f){if(!a.support.backspaceWorks&&a.typeable.test(this)){var i=g(this),j=i.substr(0,f.start),k=i.substr(f.end);f.start===f.end&&f.start>0?(h(this,j.substring(0,j.length-1)+k),a.selectText(this,f.start-1)):(h(this,j+k),a.selectText(this,f.start))}},"delete":function(b,c,d,e,f){if(!a.support.backspaceWorks&&a.typeable.test(this)){var i=g(this),j=i.substr(0,f.start),k=i.substr(f.end);f.start===f.end&&f.start<=g(this).length-1?h(this,j+k.substring(1)):h(this,j+k),a.selectText(this,f.start)}},"\r":function(b,c,d,e,f){var g=this.nodeName.toLowerCase();if("input"===g&&a.trigger("change",{},this),!a.support.keypressSubmits&&"input"===g){var h=a.closest(this,"form");h&&a.trigger("submit",{},h)}a.support.keyCharacters||"textarea"!==g||a.key.defaults.character.call(this,b,c,"\n",void 0,f),a.support.keypressOnAnchorClicks||"a"!==g||a.trigger("click",{},this)}," ":function(){for(var b,c,e=d(this),f=null,g=0,h=[];gg(this).length?g(this).length:f.end+1):a.selectText(this,f.end+1>g(this).length?g(this).length:f.end+1))},up:function(){/select/i.test(this.nodeName)&&(this.selectedIndex=this.selectedIndex?this.selectedIndex-1:0)},down:function(){/select/i.test(this.nodeName)&&(a.changeOnBlur(this,"selectedIndex",this.selectedIndex),this.selectedIndex=this.selectedIndex+1)},shift:function(){return null},ctrl:function(){return null}}}),b.extend(a.create,{keydown:{setup:function(c,d,e){-1!==b.inArray(d,a.key.kinds.special)&&(a.key[d+"Key"]=e)}},keypress:{setup:function(b,c,d){a.support.keyCharacters&&!a.support.keysOnNotFocused&&a.__tryFocus(d)}},keyup:{setup:function(c,d){-1!==b.inArray(d,a.key.kinds.special)&&(a.key[d+"Key"]=null)}},key:{options:function(c,d){return d="object"!=typeof d?{character:d}:d,d=b.extend({},d),d.character&&(b.extend(d,a.key.options(d.character,c)),delete d.character),d=b.extend({ctrlKey:!!a.key.ctrlKey,altKey:!!a.key.altKey,shiftKey:!!a.key.shiftKey,metaKey:!!a.key.metaKey},d)},event:function(a,c,d){var e,f=b.getWindow(d).document||document;if(f.createEvent){try{e=f.createEvent("KeyEvents"),e.initKeyEvent(a,!0,!0,window,c.ctrlKey,c.altKey,c.shiftKey,c.metaKey,c.keyCode,c.charCode)}catch(g){e=b.createBasicStandardEvent(a,c,f)}return e.synthetic=!0,e}try{e=b.createEventObject.apply(this,arguments),b.extend(e,c)}catch(g){}return e}}});var i={enter:"\r",backspace:"\b",tab:" ",space:" "};return b.extend(a.init.prototype,{_key:function(d,e,f){if(/-up$/.test(d)&&-1!==b.inArray(d.replace("-up",""),a.key.kinds.special))return a.trigger("keyup",d.replace("-up",""),e),f(!0,e);var g,h=b.getWindow(e).document.activeElement,j=a.typeable.test(e)&&c(e),k=i[d]||d,l=a.trigger("keydown",k,e),m=a.key.getDefault,n=a.key.browser.prevent,o=a.key.options(k,"keypress");return l?o?(h!==b.getWindow(e).document.activeElement&&(e=b.getWindow(e).document.activeElement),l=a.trigger("keypress",o,e),l&&(g=m(k).call(e,o,b.getWindow(e),k,void 0,j))):g=m(k).call(e,o,b.getWindow(e),k,void 0,j):o&&-1===b.inArray("keypress",n.keydown)&&(h!==b.getWindow(e).document.activeElement&&(e=b.getWindow(e).document.activeElement),a.trigger("keypress",o,e)),g&&g.nodeName&&(e=g),null!==g?a.schedule(function(){a.support.oninput&&a.trigger("input",a.key.options(k,"input"),e),a.trigger("keyup",a.key.options(k,"keyup"),e),f(l,e)},1):f(l,e),e},_type:function(a,b,c){var d=(a+"").match(/(\[[^\]]+\])|([^\[])/g),e=this,f=function(a,g){var h=d.shift();return h?(g=g||b,h.length>1&&(h=h.substr(1,h.length-2)),void e._key(h,g,f)):void c(a,g)};f()}}),a}(__m2,__m6,__m4),__m7=function(a){!function j(){if(!document.body)return void a.schedule(j,1);var b=document.createElement("div");if(document.body.appendChild(b),a.helpers.extend(b.style,{width:"100px",height:"10000px",backgroundColor:"blue",position:"absolute",top:"10px",left:"0px",zIndex:19999}),document.body.scrollTop=11,document.elementFromPoint){var c=document.elementFromPoint(3,1);c===b?a.support.elementFromClient=!0:a.support.elementFromPage=!0,document.body.removeChild(b),document.body.scrollTop=0}}();var b=function(b,c){var d,e=b.clientX,f=b.clientY,g=a.helpers.getWindow(c);if(a.support.elementFromPage){var h=a.helpers.scrollOffset(g);e+=h.left,f+=h.top}return d=g.document.elementFromPoint?g.document.elementFromPoint(e,f):c,d===g.document.documentElement&&(b.clientY<0||b.clientX<0)?c:d},c=function(c,d,e){var f=b(d,e);return a.trigger(c,d,f||e),f},d=function(c,d,e){var f=b(c,d);if(e!==f&&f&&e){var g=a.helpers.extend({},c);g.relatedTarget=f,a.trigger("mouseout",g,e),g.relatedTarget=e,a.trigger("mouseover",g,f)}return a.trigger("mousemove",c,f||d),f},e=function(c,e,f,g,h){var i,j=new Date,k=e.clientX-c.clientX,l=e.clientY-c.clientY,m=a.helpers.getWindow(g),n=b(c,g),o=m.document.createElement("div"),p=0;i=function q(){var b=new Date,i=a.helpers.scrollOffset(m),r=(0===p?0:b-j)/f,s={clientX:k*r+c.clientX,clientY:l*r+c.clientY};p++,1>r?(a.helpers.extend(o.style,{left:s.clientX+i.left+2+"px",top:s.clientY+i.top+2+"px"}),n=d(s,g,n),a.schedule(q,15)):(n=d(e,g,n),m.document.body.removeChild(o),h())},a.helpers.extend(o.style,{height:"5px",width:"5px",backgroundColor:"red",position:"absolute",zIndex:19999,fontSize:"1px"}),m.document.body.appendChild(o),i()},f=function(a,b,d,f,g){c("mousedown",a,f),e(a,b,d,f,function(){c("mouseup",b,f),g()})},g=function(b){var c=a.jquery()(b),d=c.offset();return{pageX:d.left+c.outerWidth()/2,pageY:d.top+c.outerHeight()/2}},h=function(b,c,d){var e,f=/(\d+)[x ](\d+)/,h=/(\d+)X(\d+)/,i=/([+-]\d+)[xX ]([+-]\d+)/;if("string"==typeof b&&i.test(b)&&d){var j=g(d);e=b.match(i),b={pageX:j.pageX+parseInt(e[1]),pageY:j.pageY+parseInt(e[2])}}if("string"==typeof b&&f.test(b)&&(e=b.match(f),b={pageX:parseInt(e[1]),pageY:parseInt(e[2])}),"string"==typeof b&&h.test(b)&&(e=b.match(h),b={clientX:parseInt(e[1]),clientY:parseInt(e[2])}),"string"==typeof b&&(b=a.jquery()(b,c.document)[0]),b.nodeName&&(b=g(b)),b.pageX){var k=a.helpers.scrollOffset(c);b={clientX:b.pageX-k.left,clientY:b.pageY-k.top}}return b},i=function(b,c,d){if(b.clientY<0){var e=a.helpers.scrollOffset(d),f=e.top+b.clientY-100,g=f-e.top;f>0||(f=0,g=-e.top),b.clientY=b.clientY-g,c.clientY=c.clientY-g,a.helpers.scrollOffset(d,{top:f,left:e.left})}};return a.helpers.extend(a.init.prototype,{_move:function(b,c,d){var f=a.helpers.getWindow(c),g=h(b.from||c,f,c),j=h(b.to||b,f,c);b.adjust!==!1&&i(g,j,f),e(g,j,b.duration||500,c,d)},_drag:function(b,c,d){var e=a.helpers.getWindow(c),g=h(b.from||c,e,c),j=h(b.to||b,e,c);b.adjust!==!1&&i(g,j,e),f(g,j,b.duration||500,c,d)}}),a}(__m2),__m1=function(a){return window.Syn=a,a}(__m2,__m3,__m4,__m5,__m7)}(window); diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index 87d25065..f7f449da 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -11,14 +11,23 @@ namespace Behat\Mink\Driver; use Behat\Mink\Exception\DriverException; -use Behat\Mink\Selector\Xpath\Escaper; -use WebDriver\Element; -use WebDriver\Exception\NoSuchElement; -use WebDriver\Exception\UnknownCommand; -use WebDriver\Exception\UnknownError; -use WebDriver\Exception; -use WebDriver\Key; -use WebDriver\WebDriver; +use Behat\Mink\Exception\UnsupportedDriverActionException; +use Facebook\WebDriver\Cookie; +use Facebook\WebDriver\Exception\NoSuchElementException; +use Facebook\WebDriver\Exception\ScriptTimeoutException; +use Facebook\WebDriver\Exception\TimeOutException; +use Facebook\WebDriver\Remote\DesiredCapabilities; +use Facebook\WebDriver\Remote\LocalFileDetector; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\RemoteWebElement; +use Facebook\WebDriver\WebDriver; +use Facebook\WebDriver\WebDriverBy; +use Facebook\WebDriver\WebDriverDimension; +use Facebook\WebDriver\WebDriverElement; +use Facebook\WebDriver\WebDriverExpectedCondition; +use Facebook\WebDriver\WebDriverKeys; +use Facebook\WebDriver\WebDriverRadios; +use Facebook\WebDriver\WebDriverSelect; /** * Selenium2 driver. @@ -27,15 +36,10 @@ */ class Selenium2Driver extends CoreDriver { - /** - * Whether the browser has been started - * @var Boolean - */ - private $started = false; - /** * The WebDriver instance - * @var WebDriver + * + * @var RemoteWebDriver */ private $webDriver; @@ -45,26 +49,23 @@ class Selenium2Driver extends CoreDriver private $browserName; /** - * @var array + * @var DesiredCapabilities|null */ private $desiredCapabilities; - /** - * The WebDriverSession instance - * @var \WebDriver\Session - */ - private $wdSession; - /** * The timeout configuration + * * @var array */ private $timeouts = array(); /** - * @var Escaper + * Wd host + * + * @var string */ - private $xpathEscaper; + private $wdHost; /** * Instantiates the driver. @@ -75,10 +76,54 @@ class Selenium2Driver extends CoreDriver */ public function __construct($browserName = 'firefox', $desiredCapabilities = null, $wdHost = 'http://localhost:4444/wd/hub') { - $this->setBrowserName($browserName); - $this->setDesiredCapabilities($desiredCapabilities); - $this->setWebDriver(new WebDriver($wdHost)); - $this->xpathEscaper = new Escaper(); + $this->wdHost = $wdHost; + $this->browserName = $browserName; + + if ($browserName === 'firefox') { + $this->desiredCapabilities = DesiredCapabilities::firefox(); + } else if ($browserName === 'chrome') { + $this->desiredCapabilities = DesiredCapabilities::chrome(); + } else { + $this->desiredCapabilities = new DesiredCapabilities(); + } + + if ($desiredCapabilities) { + foreach ($desiredCapabilities as $key => $val) { + $this->desiredCapabilities->setCapability($key, $val); + } + } + } + + /** + * Sets the timeouts to apply to the webdriver session + * + * @param array $timeouts The session timeout settings: Array of {script, implicit, page} => time in milliseconds + */ + public function setTimeouts(array $timeouts) + { + $this->timeouts = $timeouts; + + if ($this->isStarted()) { + $this->applyTimeouts(); + } + } + + /** + * Applies timeouts to the current session + */ + private function applyTimeouts() + { + // @see https://w3c.github.io/webdriver/#set-timeouts + $timeouts = $this->webDriver->manage()->timeouts(); + if (isset($this->timeouts['implicit'])) { + $timeouts->implicitlyWait($this->timeouts['implicit']); + } else if (isset($this->timeouts['pageLoad'])) { + $timeouts->pageLoadTimeout($this->timeouts['pageLoad']); + } else if (isset($this->timeouts['script'])) { + $timeouts->setScriptTimeout($this->timeouts['script']); + } else { + throw new DriverException('Invalid timeout option'); + } } /** @@ -97,54 +142,20 @@ protected function setBrowserName($browserName = 'firefox') * * See http://code.google.com/p/selenium/wiki/DesiredCapabilities * - * @param array $desiredCapabilities an array of capabilities to pass on to the WebDriver server + * @param DesiredCapabilities|array|null $desiredCapabilities * * @throws DriverException */ public function setDesiredCapabilities($desiredCapabilities = null) { - if ($this->started) { - throw new DriverException("Unable to set desiredCapabilities, the session has already started"); + if ($this->isStarted()) { + throw new DriverException('Unable to set desiredCapabilities, the session has already started'); } - if (null === $desiredCapabilities) { - $desiredCapabilities = array(); - } - - // Join $desiredCapabilities with defaultCapabilities - $desiredCapabilities = array_replace(self::getDefaultCapabilities(), $desiredCapabilities); - - if (isset($desiredCapabilities['firefox'])) { - foreach ($desiredCapabilities['firefox'] as $capability => $value) { - switch ($capability) { - case 'profile': - $desiredCapabilities['firefox_'.$capability] = base64_encode(file_get_contents($value)); - break; - default: - $desiredCapabilities['firefox_'.$capability] = $value; - } - } - - unset($desiredCapabilities['firefox']); - } - - // See https://sites.google.com/a/chromium.org/chromedriver/capabilities - if (isset($desiredCapabilities['chrome'])) { - - $chromeOptions = array(); - - foreach ($desiredCapabilities['chrome'] as $capability => $value) { - if ($capability == 'switches') { - $chromeOptions['args'] = $value; - } else { - $chromeOptions[$capability] = $value; - } - $desiredCapabilities['chrome.'.$capability] = $value; - } - - $desiredCapabilities['chromeOptions'] = $chromeOptions; - - unset($desiredCapabilities['chrome']); + if (is_array($desiredCapabilities)) { + $desiredCapabilities = new DesiredCapabilities($desiredCapabilities); + } else if ($desiredCapabilities === null) { + $desiredCapabilities = new DesiredCapabilities(); } $this->desiredCapabilities = $desiredCapabilities; @@ -153,7 +164,7 @@ public function setDesiredCapabilities($desiredCapabilities = null) /** * Gets the desiredCapabilities * - * @return array $desiredCapabilities + * @return DesiredCapabilities */ public function getDesiredCapabilities() { @@ -161,23 +172,11 @@ public function getDesiredCapabilities() } /** - * Sets the WebDriver instance - * - * @param WebDriver $webDriver An instance of the WebDriver class - */ - public function setWebDriver(WebDriver $webDriver) - { - $this->webDriver = $webDriver; - } - - /** - * Gets the WebDriverSession instance - * - * @return \WebDriver\Session + * @return WebDriver */ - public function getWebDriverSession() + public function getWebDriver() { - return $this->wdSession; + return $this->webDriver; } /** @@ -188,62 +187,9 @@ public function getWebDriverSession() public static function getDefaultCapabilities() { return array( - 'browserName' => 'firefox', - 'name' => 'Behat Test', - ); - } - - /** - * Makes sure that the Syn event library has been injected into the current page, - * and return $this for a fluid interface, - * - * $this->withSyn()->executeJsOnXpath($xpath, $script); - * - * @return Selenium2Driver - */ - protected function withSyn() - { - $hasSyn = $this->wdSession->execute(array( - 'script' => 'return typeof window["Syn"]!=="undefined" && typeof window["Syn"].trigger!=="undefined"', - 'args' => array() - )); - - if (!$hasSyn) { - $synJs = file_get_contents(__DIR__.'/Resources/syn.js'); - $this->wdSession->execute(array( - 'script' => $synJs, - 'args' => array() - )); - } - - return $this; - } - - /** - * Creates some options for key events - * - * @param string $char the character or code - * @param string $modifier one of 'shift', 'alt', 'ctrl' or 'meta' - * - * @return string a json encoded options array for Syn - */ - protected static function charToOptions($char, $modifier = null) - { - $ord = ord($char); - if (is_numeric($char)) { - $ord = $char; - } - - $options = array( - 'keyCode' => $ord, - 'charCode' => $ord + 'browserName' => 'firefox', + 'name' => 'Behat Test', ); - - if ($modifier) { - $options[$modifier.'Key'] = 1; - } - - return json_encode($options); } /** @@ -258,9 +204,10 @@ protected static function charToOptions($char, $modifier = null) * * @return mixed */ - protected function executeJsOnXpath($xpath, $script, $sync = true) + private function executeJsOnXpath($xpath, $script, $sync = true) { - return $this->executeJsOnElement($this->findElement($xpath), $script, $sync); + $element = $this->findElement($xpath); + return $this->executeJsOnElement($element, $script, $sync); } /** @@ -269,26 +216,21 @@ protected function executeJsOnXpath($xpath, $script, $sync = true) * * @example $this->executeJsOnXpath($xpath, 'return {{ELEMENT}}.childNodes.length'); * - * @param Element $element the webdriver element - * @param string $script the script to execute - * @param Boolean $sync whether to run the script synchronously (default is TRUE) + * @param WebDriverElement $element the webdriver element + * @param string $script the script to execute + * @param Boolean $sync whether to run the script synchronously (default is TRUE) * * @return mixed */ - private function executeJsOnElement(Element $element, $script, $sync = true) + private function executeJsOnElement(WebDriverElement $element, $script, $sync = true) { - $script = str_replace('{{ELEMENT}}', 'arguments[0]', $script); - - $options = array( - 'script' => $script, - 'args' => array(array('ELEMENT' => $element->getID())), - ); + $script = str_replace('{{ELEMENT}}', 'arguments[0]', $script); if ($sync) { - return $this->wdSession->execute($options); + return $this->webDriver->executeScript($script, array(array('ELEMENT' => $element->getID()))); } - return $this->wdSession->execute_async($options); + return $this->webDriver->executeAsyncScript($script, array(array('ELEMENT' => $element->getID()))); } /** @@ -297,46 +239,17 @@ private function executeJsOnElement(Element $element, $script, $sync = true) public function start() { try { - $this->wdSession = $this->webDriver->session($this->browserName, $this->desiredCapabilities); - $this->applyTimeouts(); + $this->webDriver = RemoteWebDriver::create($this->wdHost, $this->desiredCapabilities); + if (\count($this->timeouts)) { + $this->applyTimeouts(); + } } catch (\Exception $e) { - throw new DriverException('Could not open connection: '.$e->getMessage(), 0, $e); + throw new DriverException('Could not open connection: ' . $e->getMessage(), 0, $e); } - if (!$this->wdSession) { + if (!$this->webDriver) { throw new DriverException('Could not connect to a Selenium 2 / WebDriver server'); } - $this->started = true; - } - - /** - * Sets the timeouts to apply to the webdriver session - * - * @param array $timeouts The session timeout settings: Array of {script, implicit, page} => time in milliseconds - * - * @throws DriverException - */ - public function setTimeouts($timeouts) - { - $this->timeouts = $timeouts; - - if ($this->isStarted()) { - $this->applyTimeouts(); - } - } - - /** - * Applies timeouts to the current session - */ - private function applyTimeouts() - { - try { - foreach ($this->timeouts as $type => $param) { - $this->wdSession->timeouts($type, $param); - } - } catch (UnknownError $e) { - throw new DriverException('Error setting timeout: ' . $e->getMessage(), 0, $e); - } } /** @@ -344,7 +257,7 @@ private function applyTimeouts() */ public function isStarted() { - return $this->started; + return $this->webDriver !== null; } /** @@ -352,13 +265,13 @@ public function isStarted() */ public function stop() { - if (!$this->wdSession) { + if (!$this->webDriver) { throw new DriverException('Could not connect to a Selenium 2 / WebDriver server'); } - $this->started = false; try { - $this->wdSession->close(); + $this->webDriver->quit(); + $this->webDriver = null; } catch (\Exception $e) { throw new DriverException('Could not close connection', 0, $e); } @@ -369,7 +282,7 @@ public function stop() */ public function reset() { - $this->wdSession->deleteAllCookies(); + $this->webDriver->manage()->deleteAllCookies(); } /** @@ -377,7 +290,18 @@ public function reset() */ public function visit($url) { - $this->wdSession->open($url); + try { + try { + $this->webDriver->navigate()->to($url); + } catch (ScriptTimeoutException $e) { + // selenium-firefox:2.53.1 has different exception code 'page load' and ScriptTimeoutException is thrown + if (strpos($e->getMessage(), 'Timed out waiting for page load') === 0) { + throw new TimeOutException($e->getMessage(), $e->getResults()); + } + } + } catch (TimeOutException $e) { + throw new DriverException($e->getMessage(), $e->getCode(), $e); + } } /** @@ -385,7 +309,7 @@ public function visit($url) */ public function getCurrentUrl() { - return $this->wdSession->url(); + return $this->webDriver->getCurrentURL(); } /** @@ -393,7 +317,18 @@ public function getCurrentUrl() */ public function reload() { - $this->wdSession->refresh(); + try { + try { + $this->webDriver->navigate()->refresh(); + } catch (ScriptTimeoutException $e) { + // selenium-firefox:2.53.1 has different exception code 'page load' and ScriptTimeoutException is thrown + if (strpos($e->getMessage(), 'Timed out waiting for page load') === 0) { + throw new TimeOutException($e->getMessage(), $e->getResults()); + } + } + } catch (TimeOutException $e) { + throw new DriverException($e->getMessage(), $e->getCode(), $e); + } } /** @@ -401,7 +336,7 @@ public function reload() */ public function forward() { - $this->wdSession->forward(); + $this->webDriver->navigate()->forward(); } /** @@ -409,7 +344,7 @@ public function forward() */ public function back() { - $this->wdSession->back(); + $this->webDriver->navigate()->back(); } /** @@ -417,7 +352,7 @@ public function back() */ public function switchToWindow($name = null) { - $this->wdSession->focusWindow($name ? $name : ''); + $this->webDriver->switchTo()->window($name); } /** @@ -425,7 +360,12 @@ public function switchToWindow($name = null) */ public function switchToIFrame($name = null) { - $this->wdSession->frame(array('id' => $name)); + if ($name) { + $element = $this->webDriver->findElement(WebDriverBy::name($name)); + $this->webDriver->switchTo()->frame($element); + } else { + $this->webDriver->switchTo()->defaultContent(); + } } /** @@ -434,18 +374,13 @@ public function switchToIFrame($name = null) public function setCookie($name, $value = null) { if (null === $value) { - $this->wdSession->deleteCookie($name); + $this->webDriver->manage()->deleteCookieNamed($name); return; } - $cookieArray = array( - 'name' => $name, - 'value' => urlencode($value), - 'secure' => false, // thanks, chibimagic! - ); - - $this->wdSession->setCookie($cookieArray); + $cookie = new Cookie($name, \urlencode($value)); + $this->webDriver->manage()->addCookie($cookie); } /** @@ -453,12 +388,12 @@ public function setCookie($name, $value = null) */ public function getCookie($name) { - $cookies = $this->wdSession->getAllCookies(); - foreach ($cookies as $cookie) { - if ($cookie['name'] === $name) { - return urldecode($cookie['value']); - } + $cookie = $this->webDriver->manage()->getCookieNamed($name); + if (!$cookie) { + return null; } + + return \urldecode($cookie->getValue()); } /** @@ -466,7 +401,8 @@ public function getCookie($name) */ public function getContent() { - return $this->wdSession->source(); + $source = $this->webDriver->getPageSource(); + return str_replace(array("\r", "\r\n", "\n"), \PHP_EOL, $source); } /** @@ -474,7 +410,7 @@ public function getContent() */ public function getScreenshot() { - return base64_decode($this->wdSession->screenshot()); + return $this->webDriver->takeScreenshot(); } /** @@ -482,7 +418,7 @@ public function getScreenshot() */ public function getWindowNames() { - return $this->wdSession->window_handles(); + return $this->webDriver->getWindowHandles(); } /** @@ -490,7 +426,7 @@ public function getWindowNames() */ public function getWindowName() { - return $this->wdSession->window_handle(); + return $this->webDriver->getWindowHandle(); } /** @@ -498,11 +434,11 @@ public function getWindowName() */ public function findElementXpaths($xpath) { - $nodes = $this->wdSession->elements('xpath', $xpath); + $nodes = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); $elements = array(); foreach ($nodes as $i => $node) { - $elements[] = sprintf('(%s)[%d]', $xpath, $i+1); + $elements[] = sprintf('(%s)[%d]', $xpath, $i + 1); } return $elements; @@ -513,7 +449,8 @@ public function findElementXpaths($xpath) */ public function getTagName($xpath) { - return $this->findElement($xpath)->name(); + $element = $this->findElement($xpath); + return $element->getTagName(); } /** @@ -521,8 +458,9 @@ public function getTagName($xpath) */ public function getText($xpath) { - $node = $this->findElement($xpath); - $text = $node->text(); + $element = $this->findElement($xpath); + $text = $element->getText(); + $text = (string) str_replace(array("\r", "\r\n", "\n"), ' ', $text); return $text; @@ -549,9 +487,33 @@ public function getOuterHtml($xpath) */ public function getAttribute($xpath, $name) { - $script = 'return {{ELEMENT}}.getAttribute(' . json_encode((string) $name) . ')'; + $element = $this->findElement($xpath); - return $this->executeJsOnXpath($xpath, $script); + /** + * If attribute is present but does not have value, it's considered as Boolean Attributes https://html.spec.whatwg.org/#boolean-attributes + * but here result may be unexpected in case of , my-attr should return TRUE, but it will return "empty string" + * + * @see https://w3c.github.io/webdriver/#get-element-attribute + */ + $hasAttribute = $this->hasAttribute($element, $name); + if ($hasAttribute) { + $value = $element->getAttribute($name); + } else { + $value = null; + } + + return $value; + } + + /** + * @param WebDriverElement $element + * @param string $name + * + * @return bool + */ + private function hasAttribute(WebDriverElement $element, $name) + { + return $this->executeJsOnElement($element, "return {{ELEMENT}}.hasAttribute('$name')"); } /** @@ -560,58 +522,33 @@ public function getAttribute($xpath, $name) public function getValue($xpath) { $element = $this->findElement($xpath); - $elementName = strtolower($element->name()); - $elementType = strtolower($element->attribute('type')); + $elementName = strtolower($element->getTagName()); + $elementType = strtolower($element->getAttribute('type')); // Getting the value of a checkbox returns its value if selected. if ('input' === $elementName && 'checkbox' === $elementType) { - return $element->selected() ? $element->attribute('value') : null; + return $element->isSelected() ? $element->getAttribute('value') : null; } if ('input' === $elementName && 'radio' === $elementType) { - $script = <<executeJsOnElement($element, $script); + $radios = new WebDriverRadios($element); + return $radios->getFirstSelectedOption()->getAttribute('value'); } // Using $element->attribute('value') on a select only returns the first selected option // even when it is a multiple select, so a custom retrieval is needed. - if ('select' === $elementName && $element->attribute('multiple')) { - $script = <<isMultiple()) { + return \array_map(function (WebDriverElement $element) { + return $element->getAttribute('value'); + }, $select->getAllSelectedOptions()); + } - return $this->executeJsOnElement($element, $script); + return $select->getFirstSelectedOption()->getAttribute('value'); } - return $element->attribute('value'); + return $element->getAttribute('value'); } /** @@ -620,33 +557,34 @@ public function getValue($xpath) public function setValue($xpath, $value) { $element = $this->findElement($xpath); - $elementName = strtolower($element->name()); + $elementName = strtolower($element->getTagName()); if ('select' === $elementName) { - if (is_array($value)) { - $this->deselectAllOptions($element); + $select = new WebDriverSelect($element); + if (is_array($value)) { + $select->deselectAll(); foreach ($value as $option) { - $this->selectOptionOnElement($element, $option, true); + $select->selectByValue($option); } return; } - $this->selectOptionOnElement($element, $value); + $select->selectByValue($value); return; } if ('input' === $elementName) { - $elementType = strtolower($element->attribute('type')); + $elementType = strtolower($element->getAttribute('type')); if (in_array($elementType, array('submit', 'image', 'button', 'reset'))) { throw new DriverException(sprintf('Impossible to set value an element with XPath "%s" as it is not a select, textarea or textbox', $xpath)); } if ('checkbox' === $elementType) { - if ($element->selected() xor (bool) $value) { + if ($element->isSelected() xor (bool) $value) { $this->clickOnElement($element); } @@ -654,28 +592,27 @@ public function setValue($xpath, $value) } if ('radio' === $elementType) { - $this->selectRadioValue($element, $value); - + $radios = new WebDriverRadios($element); + $radios->selectByValue($value); return; } if ('file' === $elementType) { - $element->postValue(array('value' => array(strval($value)))); - + $this->attachFile($xpath, $value); return; } } - $value = strval($value); + $value = (string) $value; if (in_array($elementName, array('input', 'textarea'))) { - $existingValueLength = strlen($element->attribute('value')); + $existingValueLength = strlen($element->getAttribute('value')); // Add the TAB key to ensure we unfocus the field as browsers are triggering the change event only // after leaving the field. - $value = str_repeat(Key::BACKSPACE . Key::DELETE, $existingValueLength) . $value; + $value = str_repeat(WebDriverKeys::BACKSPACE . WebDriverKeys::DELETE, $existingValueLength) . $value; } - $element->postValue(array('value' => array($value))); + $element->sendKeys($value); // Remove the focus from the element if the field still has focus in // order to trigger the change event. By doing this instead of simply // triggering the change event for the given xpath we ensure that the @@ -683,13 +620,7 @@ public function setValue($xpath, $value) // has lost focus in the meanwhile. If the element has lost focus // already then there is nothing to do as this will already have caused // the triggering of the change event for that element. - $script = <<executeJsOnElement($element, $script); + $element->sendKeys(WebDriverKeys::TAB); } /** @@ -700,7 +631,7 @@ public function check($xpath) $element = $this->findElement($xpath); $this->ensureInputType($element, $xpath, 'checkbox', 'check'); - if ($element->selected()) { + if ($element->isSelected()) { return; } @@ -715,7 +646,7 @@ public function uncheck($xpath) $element = $this->findElement($xpath); $this->ensureInputType($element, $xpath, 'checkbox', 'uncheck'); - if (!$element->selected()) { + if (!$element->isSelected()) { return; } @@ -727,7 +658,7 @@ public function uncheck($xpath) */ public function isChecked($xpath) { - return $this->findElement($xpath)->selected(); + return $this->isSelected($xpath); } /** @@ -736,16 +667,26 @@ public function isChecked($xpath) public function selectOption($xpath, $value, $multiple = false) { $element = $this->findElement($xpath); - $tagName = strtolower($element->name()); - - if ('input' === $tagName && 'radio' === strtolower($element->attribute('type'))) { - $this->selectRadioValue($element, $value); + $tagName = strtolower($element->getTagName()); + if ('input' === $tagName && 'radio' === strtolower($element->getAttribute('type'))) { + $element = new WebDriverRadios($element); + $element->selectByValue($value); return; } if ('select' === $tagName) { - $this->selectOptionOnElement($element, $value, $multiple); + $element = new WebDriverSelect($element); + if (!$multiple && $element->isMultiple()) { + $element->deselectAll(); + } + + try { + $element->selectByValue($value); + } catch (NoSuchElementException $e) { + // option may not have value attribute, so try to select by visible text + $element->selectByVisibleText($value); + } return; } @@ -758,7 +699,8 @@ public function selectOption($xpath, $value, $multiple = false) */ public function isSelected($xpath) { - return $this->findElement($xpath)->selected(); + $element = $this->findElement($xpath); + return $element->isSelected(); } /** @@ -766,19 +708,13 @@ public function isSelected($xpath) */ public function click($xpath) { - $this->clickOnElement($this->findElement($xpath)); + $element = $this->findElement($xpath); + $this->clickOnElement($element); } - private function clickOnElement(Element $element) + private function clickOnElement(WebDriverElement $element) { - try { - // Move the mouse to the element as Selenium does not allow clicking on an element which is outside the viewport - $this->wdSession->moveto(array('element' => $element->getID())); - } catch (UnknownCommand $e) { - // If the Webdriver implementation does not support moveto (which is not part of the W3C WebDriver spec), proceed to the click - } - - $element->click(); + $this->webDriver->action()->click($element)->perform(); } /** @@ -786,8 +722,8 @@ private function clickOnElement(Element $element) */ public function doubleClick($xpath) { - $this->mouseOver($xpath); - $this->wdSession->doubleclick(); + $element = $this->findElement($xpath); + $this->webDriver->action()->doubleClick($element)->perform(); } /** @@ -795,8 +731,8 @@ public function doubleClick($xpath) */ public function rightClick($xpath) { - $this->mouseOver($xpath); - $this->wdSession->click(array('button' => 2)); + $element = $this->findElement($xpath); + $this->webDriver->action()->contextClick($element)->perform(); } /** @@ -807,17 +743,8 @@ public function attachFile($xpath, $path) $element = $this->findElement($xpath); $this->ensureInputType($element, $xpath, 'file', 'attach a file on'); - // Upload the file to Selenium and use the remote path. This will - // ensure that Selenium always has access to the file, even if it runs - // as a remote instance. - try { - $remotePath = $this->uploadFile($path); - } catch (\Exception $e) { - // File could not be uploaded to remote instance. Use the local path. - $remotePath = $path; - } - - $element->postValue(array('value' => array($remotePath))); + $element->setFileDetector(new LocalFileDetector()); + return $element->sendKeys($path); } /** @@ -825,7 +752,8 @@ public function attachFile($xpath, $path) */ public function isVisible($xpath) { - return $this->findElement($xpath)->displayed(); + $element = $this->findElement($xpath); + return $element->isDisplayed(); } /** @@ -833,9 +761,8 @@ public function isVisible($xpath) */ public function mouseOver($xpath) { - $this->wdSession->moveto(array( - 'element' => $this->findElement($xpath)->getID() - )); + $element = $this->findElement($xpath); + $this->webDriver->action()->moveToElement($element)->perform(); } /** @@ -843,7 +770,13 @@ public function mouseOver($xpath) */ public function focus($xpath) { - $this->trigger($xpath, 'focus'); + $element = $this->findElement($xpath); + $action = $this->webDriver->action(); + + $action->moveToElement($element)->perform(); + + // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus + $this->executeJsOnElement($element, 'return {{ELEMENT}}.focus()'); } /** @@ -851,7 +784,10 @@ public function focus($xpath) */ public function blur($xpath) { - $this->trigger($xpath, 'blur'); + $element = $this->findElement($xpath); + + // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur + $this->executeJsOnElement($element, 'return {{ELEMENT}}.blur()'); } /** @@ -859,8 +795,7 @@ public function blur($xpath) */ public function keyPress($xpath, $char, $modifier = null) { - $options = self::charToOptions($char, $modifier); - $this->trigger($xpath, 'keypress', $options); + $this->sendKey($xpath, $char, $modifier); } /** @@ -868,8 +803,7 @@ public function keyPress($xpath, $char, $modifier = null) */ public function keyDown($xpath, $char, $modifier = null) { - $options = self::charToOptions($char, $modifier); - $this->trigger($xpath, 'keydown', $options); + $this->sendKey($xpath, $char, $modifier); } /** @@ -877,8 +811,7 @@ public function keyDown($xpath, $char, $modifier = null) */ public function keyUp($xpath, $char, $modifier = null) { - $options = self::charToOptions($char, $modifier); - $this->trigger($xpath, 'keyup', $options); + $this->sendKey($xpath, $char, $modifier); } /** @@ -886,42 +819,12 @@ public function keyUp($xpath, $char, $modifier = null) */ public function dragTo($sourceXpath, $destinationXpath) { - $source = $this->findElement($sourceXpath); + $source = $this->findElement($sourceXpath); $destination = $this->findElement($destinationXpath); + $action = $this->webDriver->action(); - $this->wdSession->moveto(array( - 'element' => $source->getID() - )); - - $script = <<withSyn()->executeJsOnElement($source, $script); - - $this->wdSession->buttondown(); - $this->wdSession->moveto(array( - 'element' => $destination->getID() - )); - $this->wdSession->buttonup(); - - $script = <<withSyn()->executeJsOnElement($destination, $script); + $action->dragAndDrop($source, $destination); + $action->perform(); } /** @@ -934,7 +837,21 @@ public function executeScript($script) $script = '(' . $script . ')'; } - $this->wdSession->execute(array('script' => $script, 'args' => array())); + $this->webDriver->executeScript($script); + } + + public function executeAsyncScript($script) + { + if (preg_match('/^function[\s\(]/', $script)) { + $script = preg_replace('/;$/', '', $script); + $script = '(' . $script . ')'; + } + + try { + $this->webDriver->executeAsyncScript($script); + } catch (ScriptTimeoutException $e) { + throw new DriverException($e->getMessage(), $e->getCode(), $e); + } } /** @@ -946,7 +863,7 @@ public function evaluateScript($script) $script = 'return ' . $script; } - return $this->wdSession->execute(array('script' => $script, 'args' => array())); + return $this->webDriver->executeScript($script); } /** @@ -954,16 +871,21 @@ public function evaluateScript($script) */ public function wait($timeout, $condition) { - $script = "return $condition;"; - $start = microtime(true); - $end = $start + $timeout / 1000.0; + $seconds = $timeout / 1000.0; + $wait = $this->webDriver->wait($seconds); - do { - $result = $this->wdSession->execute(array('script' => $script, 'args' => array())); - usleep(100000); - } while (microtime(true) < $end && !$result); + if (is_string($condition)) { + $script = "return $condition;"; + $condition = function (RemoteWebDriver $driver) use ($script) { + return $driver->executeScript($script); + }; + } - return (bool) $result; + try { + return (bool) $wait->until($condition); + } catch (TimeOutException $e) { + return false; + } } /** @@ -971,9 +893,12 @@ public function wait($timeout, $condition) */ public function resizeWindow($width, $height, $name = null) { - $this->wdSession->window($name ? $name : 'current')->postSize( - array('width' => $width, 'height' => $height) - ); + $dimension = new WebDriverDimension($width, $height); + if ($name) { + throw new UnsupportedDriverActionException('Named windows are not supported yet'); + } + + $this->webDriver->manage()->window()->setSize($dimension); } /** @@ -981,7 +906,8 @@ public function resizeWindow($width, $height, $name = null) */ public function submitForm($xpath) { - $this->findElement($xpath)->submit(); + $element = $this->findElement($xpath); + $element->submit(); } /** @@ -989,7 +915,11 @@ public function submitForm($xpath) */ public function maximizeWindow($name = null) { - $this->wdSession->window($name ? $name : 'current')->maximize(); + if ($name) { + throw new UnsupportedDriverActionException('Named window is not supported'); + } + + $this->webDriver->manage()->window()->maximize(); } /** @@ -999,216 +929,102 @@ public function maximizeWindow($name = null) */ public function getWebDriverSessionId() { - return $this->isStarted() ? basename($this->wdSession->getUrl()) : null; + if (!$this->isStarted()) { + return null; + } + + return $this->webDriver->getSessionID(); } /** * @param string $xpath * - * @return Element + * @return RemoteWebElement */ private function findElement($xpath) { - return $this->wdSession->element('xpath', $xpath); + return $this->webDriver->findElement(WebDriverBy::xpath($xpath)); } /** - * Selects a value in a radio button group + * Ensures the element is a checkbox * - * @param Element $element An element referencing one of the radio buttons of the group - * @param string $value The value to select + * @param WebDriverElement $element + * @param string $xpath + * @param string $type + * @param string $action * - * @throws DriverException when the value cannot be found - */ - private function selectRadioValue(Element $element, $value) - { - // short-circuit when we already have the right button of the group to avoid XPath queries - if ($element->attribute('value') === $value) { - $element->click(); - - return; - } - - $name = $element->attribute('name'); - - if (!$name) { - throw new DriverException(sprintf('The radio button does not have the value "%s"', $value)); - } - - $formId = $element->attribute('form'); - - try { - if (null !== $formId) { - $xpath = <<<'XPATH' -//form[@id=%1$s]//input[@type="radio" and not(@form) and @name=%2$s and @value = %3$s] -| -//input[@type="radio" and @form=%1$s and @name=%2$s and @value = %3$s] -XPATH; - - $xpath = sprintf( - $xpath, - $this->xpathEscaper->escapeLiteral($formId), - $this->xpathEscaper->escapeLiteral($name), - $this->xpathEscaper->escapeLiteral($value) - ); - $input = $this->wdSession->element('xpath', $xpath); - } else { - $xpath = sprintf( - './ancestor::form//input[@type="radio" and not(@form) and @name=%s and @value = %s]', - $this->xpathEscaper->escapeLiteral($name), - $this->xpathEscaper->escapeLiteral($value) - ); - $input = $element->element('xpath', $xpath); - } - } catch (NoSuchElement $e) { - $message = sprintf('The radio group "%s" does not have an option "%s"', $name, $value); - - throw new DriverException($message, 0, $e); - } - - $input->click(); - } - - /** - * @param Element $element - * @param string $value - * @param bool $multiple + * @throws DriverException */ - private function selectOptionOnElement(Element $element, $value, $multiple = false) + private function ensureInputType(WebDriverElement $element, $xpath, $type, $action) { - $escapedValue = $this->xpathEscaper->escapeLiteral($value); - // The value of an option is the normalized version of its text when it has no value attribute - $optionQuery = sprintf('.//option[@value = %s or (not(@value) and normalize-space(.) = %s)]', $escapedValue, $escapedValue); - $option = $element->element('xpath', $optionQuery); - - if ($multiple || !$element->attribute('multiple')) { - if (!$option->selected()) { - $option->click(); - } + if ('input' !== strtolower($element->getTagName()) || $type !== strtolower($element->getAttribute('type'))) { + $message = 'Impossible to %s the element with XPath "%s" as it is not a %s input'; - return; + throw new DriverException(sprintf($message, $action, $xpath, $type)); } - - // Deselect all options before selecting the new one - $this->deselectAllOptions($element); - $option->click(); } /** - * Deselects all options of a multiple select + * Converts alt/ctrl/shift/meta to corresponded WebDriverKeys::* constant * - * Note: this implementation does not trigger a change event after deselecting the elements. + * @param string $modifier * - * @param Element $element - */ - private function deselectAllOptions(Element $element) - { - $script = <<executeJsOnElement($element, $script); - } + return $modifier; +} /** - * Ensures the element is a checkbox + * Decodes char * - * @param Element $element - * @param string $xpath - * @param string $type - * @param string $action + * @param int|string $char if int is passed it will be converted to char using `chr` function * - * @throws DriverException + * @return string */ - private function ensureInputType(Element $element, $xpath, $type, $action) + private function decodeChar($char) { - if ('input' !== strtolower($element->name()) || $type !== strtolower($element->attribute('type'))) { - $message = 'Impossible to %s the element with XPath "%s" as it is not a %s input'; - - throw new DriverException(sprintf($message, $action, $xpath, $type)); + if (\is_numeric($char)) { + return \chr($char); } - } - /** - * @param $xpath - * @param $event - * @param string $options - */ - private function trigger($xpath, $event, $options = '{}') - { - $script = 'Syn.trigger("' . $event . '", ' . $options . ', {{ELEMENT}})'; - $this->withSyn()->executeJsOnXpath($xpath, $script); + return $char; } /** - * Uploads a file to the Selenium instance. - * - * Note that uploading files is not part of the official WebDriver - * specification, but it is supported by Selenium. - * - * @param string $path The path to the file to upload. - * - * @return string The remote path. - * - * @throws DriverException When PHP is compiled without zip support, or the file doesn't exist. - * @throws UnknownError When an unknown error occurred during file upload. - * @throws \Exception When a known error occurred during file upload. - * - * @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 + * @param $xpath + * @param $char + * @param $modifier */ - private function uploadFile($path) + private function sendKey($xpath, $char, $modifier) { - if (!is_file($path)) { - throw new DriverException('File does not exist locally and cannot be uploaded to the remote instance.'); - } - - if (!class_exists('ZipArchive')) { - throw new DriverException('Could not compress file, PHP is compiled without zip support.'); - } - - // Selenium only accepts uploads that are compressed as a Zip archive. - $tempFilename = tempnam('', 'WebDriverZip'); - - $archive = new \ZipArchive(); - $result = $archive->open($tempFilename, \ZipArchive::CREATE); - if (!$result) { - throw new DriverException('Zip archive could not be created. Error ' . $result); - } - $result = $archive->addFile($path, basename($path)); - if (!$result) { - throw new DriverException('File could not be added to zip archive.'); - } - $result = $archive->close(); - if (!$result) { - throw new DriverException('Zip archive could not be closed.'); - } + // @see https://w3c.github.io/uievents/#event-type-keydown + $element = $this->findElement($xpath); + $char = $this->decodeChar($char); + $action = $this->webDriver->action(); - try { - $remotePath = $this->wdSession->file(array('file' => base64_encode(file_get_contents($tempFilename)))); - - // If no path is returned the file upload failed silently. In this - // case it is possible Selenium was not used but another web driver - // such as PhantomJS. - // @todo Support other drivers when (if) they get remote file transfer - // capability. - if (empty($remotePath)) { - throw new UnknownError(); - } - } catch (\Exception $e) { - // Catch any error so we can still clean up the temporary archive. + if ($modifier) { + $action->keyDown($element, $this->keyModifier($modifier)); } - unlink($tempFilename); + $action->sendKeys($element, $char); - if (isset($e)) { - throw $e; + if ($modifier) { + $action->keyUp($element, $this->keyModifier($modifier)); } - return $remotePath; + $action->perform(); } - } diff --git a/tests/Custom/DesiredCapabilitiesTest.php b/tests/Custom/DesiredCapabilitiesTest.php index ae613fc9..f4609b00 100644 --- a/tests/Custom/DesiredCapabilitiesTest.php +++ b/tests/Custom/DesiredCapabilitiesTest.php @@ -4,6 +4,7 @@ use Behat\Mink\Driver\Selenium2Driver; use Behat\Mink\Tests\Driver\TestCase; +use Facebook\WebDriver\Remote\DesiredCapabilities; class DesiredCapabilitiesTest extends TestCase { @@ -23,8 +24,8 @@ public function testGetDesiredCapabilities() $driver = new Selenium2Driver('firefox', $caps); $this->assertNotEmpty($driver->getDesiredCapabilities(), 'desiredCapabilities empty'); - $this->assertInternalType('array', $driver->getDesiredCapabilities()); - $this->assertEquals($caps, $driver->getDesiredCapabilities()); + $this->assertInstanceOf(DesiredCapabilities::class, $driver->getDesiredCapabilities()); + $this->assertArraySubset($caps, $driver->getDesiredCapabilities()->toArray()); } /** @@ -46,6 +47,8 @@ public function testSetDesiredCapabilities() ); $session = $this->getSession(); $session->start(); + + /** @var Selenium2Driver $driver */ $driver = $session->getDriver(); $driver->setDesiredCapabilities($caps); } diff --git a/tests/Custom/TimeoutTest.php b/tests/Custom/TimeoutTest.php index 0c0dd451..2ab763c6 100644 --- a/tests/Custom/TimeoutTest.php +++ b/tests/Custom/TimeoutTest.php @@ -2,40 +2,92 @@ namespace Behat\Mink\Tests\Driver\Custom; +use Behat\Mink\Driver\Selenium2Driver; use Behat\Mink\Tests\Driver\TestCase; class TimeoutTest extends TestCase { + /** @var \Behat\Mink\Session */ + private $session; + + /** @var Selenium2Driver */ + private $driver; + + protected function setUp() + { + parent::setUp(); + $this->session = $this->getSession(); + $this->driver = $this->session->getDriver(); + } + /** * @expectedException \Behat\Mink\Exception\DriverException */ public function testInvalidTimeoutSettingThrowsException() { - $this->getSession()->start(); - - $this->getSession()->getDriver()->setTimeouts(array('invalid' => 0)); + $this->session->start(); + $this->driver->setTimeouts(array('invalid' => 0)); } public function testShortTimeoutDoesNotWaitForElementToAppear() { - $this->getSession()->getDriver()->setTimeouts(array('implicit' => 0)); + $this->driver->setTimeouts(array('implicit' => 0)); - $this->getSession()->visit($this->pathTo('/js_test.html')); + $this->session->visit($this->pathTo('/js_test.html')); $this->findById('waitable')->click(); - $element = $this->getSession()->getPage()->find('css', '#waitable > div'); + $element = $this->session->getPage()->find('css', '#waitable > div'); $this->assertNull($element); } public function testLongTimeoutWaitsForElementToAppear() { - $this->getSession()->getDriver()->setTimeouts(array('implicit' => 5000)); + $this->driver->setTimeouts(array('implicit' => 5000)); - $this->getSession()->visit($this->pathTo('/js_test.html')); + $this->session->visit($this->pathTo('/js_test.html')); $this->findById('waitable')->click(); - $element = $this->getSession()->getPage()->find('css', '#waitable > div'); + $element = $this->session->getPage()->find('css', '#waitable > div'); $this->assertNotNull($element); } + + /** + * @expectedException \Behat\Mink\Exception\DriverException + */ + public function testPageLoadTimeout() + { + $this->driver->setTimeouts(array('pageLoad' => 1)); + $this->session->visit($this->pathTo('/page_load.php?sleep=2')); + } + + /** + * @expectedException \Behat\Mink\Exception\DriverException + */ + public function testPageReloadTimeout() + { + $this->session->visit($this->pathTo('/page_load.php?sleep=2')); + $this->driver->setTimeouts(array('pageLoad' => 1)); + $this->session->reload(); + } + + /** + * @expectedException \Behat\Mink\Exception\DriverException + */ + public function testScriptTimeout() + { + $this->driver->setTimeouts(array('script' => 1)); + $this->session->visit($this->pathTo('/js_test.html')); + + // @see https://w3c.github.io/webdriver/#execute-async-script + $this->driver->executeAsyncScript( + 'var callback = arguments[arguments.length - 1]; + setTimeout( + function(){ + callback(); + }, + 2000 + );' + ); + } } diff --git a/tests/Selenium2Config.php b/tests/Selenium2Config.php index b5da1bff..4fcfade1 100644 --- a/tests/Selenium2Config.php +++ b/tests/Selenium2Config.php @@ -3,9 +3,18 @@ namespace Behat\Mink\Tests\Driver; use Behat\Mink\Driver\Selenium2Driver; +use Facebook\WebDriver\Chrome\ChromeOptions; +use Facebook\WebDriver\Firefox\FirefoxDriver; +use Facebook\WebDriver\Firefox\FirefoxProfile; +use Facebook\WebDriver\Remote\DesiredCapabilities; class Selenium2Config extends AbstractConfig { + /** + * @var Selenium2Driver + */ + private $driver; + public static function getInstance() { return new self(); @@ -16,10 +25,38 @@ public static function getInstance() */ public function createDriver() { - $browser = getenv('WEB_FIXTURES_BROWSER') ?: 'firefox'; + $browser = getenv('BROWSER_NAME') ?: 'firefox'; + $driverOptions = getenv('DRIVER_OPTIONS') ? \json_decode(getenv('DRIVER_OPTIONS'), true) : array(); $seleniumHost = $_SERVER['DRIVER_URL']; - return new Selenium2Driver($browser, null, $seleniumHost); + if ($browser === 'firefox') { + $desiredCapabilities = DesiredCapabilities::firefox(); + } else if ($browser === 'chrome') { + $desiredCapabilities = DesiredCapabilities::chrome(); + } else { + $desiredCapabilities = new DesiredCapabilities(); + } + + $capabilityMap = array( + 'firefox' => FirefoxDriver::PROFILE, + 'chrome' => ChromeOptions::CAPABILITY + ); + + if (isset($capabilityMap[$browser])) { + $capability = $desiredCapabilities->getCapability($capabilityMap[$browser]); + if ($browser === 'chrome') { + $capability = $this->buildChromeOptions($capability, $driverOptions); + } else if ($browser === 'firefox') { + $capability = $this->buildFirefoxProfile($capability, $driverOptions); + } + + $desiredCapabilities->setCapability($capabilityMap[$browser], $capability); + } + + $driver = new Selenium2Driver($browser, array(), $seleniumHost); + $driver->setDesiredCapabilities($desiredCapabilities); + + return $this->driver = $driver; } /** @@ -34,12 +71,30 @@ public function skipMessage($testCase, $test) return 'WebDriver does not support setting value in color inputs. See https://code.google.com/p/selenium/issues/detail?id=7650'; } + $desiredCapabilities = $this->driver->getDesiredCapabilities(); + $chromeOptions = $desiredCapabilities->getCapability(ChromeOptions::CAPABILITY); + + $headless = $desiredCapabilities->getBrowserName() === 'chrome' + && $chromeOptions instanceof ChromeOptions + && in_array('headless', $chromeOptions->toArray()['args'], true); + if ( 'Behat\Mink\Tests\Driver\Js\WindowTest' === $testCase && (0 === strpos($test, 'testWindowMaximize')) - && 'true' === getenv('TRAVIS') + && ('true' === getenv('TRAVIS') || $headless) + ) { + return 'Maximizing the window does not work when running the browser in Xvfb/Headless.'; + } + + if ( + PHP_OS === 'Darwin' + && 'Behat\Mink\Tests\Driver\Js\EventsTest' === $testCase + && 0 === strpos($test, 'testKeyboardEvents') ) { - return 'Maximizing the window does not work when running the browser in Xvfb.'; + // https://bugs.chromium.org/p/chromium/issues/detail?id=13891#c16 + // Control + will not trigger keypress + // Option + will output different results "special char" © + return 'MacOS does not behave same as Windows or Linux'; } return parent::skipMessage($testCase, $test); @@ -52,4 +107,47 @@ protected function supportsCss() { return true; } + + /** + * @param ChromeOptions|null $capability + * @param array $driverOptions + * + * @return ChromeOptions + */ + private function buildChromeOptions($capability, array $driverOptions) + { + if (!$capability) { + $capability = new ChromeOptions(); + } + $args = isset($driverOptions['args']) ? $driverOptions['args'] : []; + $capability->addArguments($args); + return $capability; + + //$capability->addEncodedExtension(); + //$capability->addExtension(); + //$capability->addEncodedExtensions(); + //$capability->addExtensions(); + } + + /** + * @param FirefoxProfile|null $capability + * @param array $driverOptions + * + * @return FirefoxProfile + */ + private function buildFirefoxProfile($capability, array $driverOptions) + { + if (!$capability) { + $capability = new FirefoxProfile(); + } + + $preferences = isset($driverOptions['preference']) ? $driverOptions['preference'] : []; + foreach ($preferences as $key => $preference) { + $capability->setPreference($key, $preference); + // $capability->setRdfFile($key, $preference); + // $capability->addExtensionDatas($key, $preference); + // $capability->addExtension($key, $preference); + } + return $capability; + } }