Update to Drupal 8.0.0-rc3. For more information, see https://www.drupal.org/node/2608078

This commit is contained in:
Pantheon Automation 2015-11-04 11:11:27 -08:00 committed by Greg Anderson
parent 6419a031d7
commit 4afb23bbd3
762 changed files with 20080 additions and 6368 deletions

View file

@ -0,0 +1,40 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
fast_finish: true
include:
- php: 5.4
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' SYMFONY_DEPRECATIONS_HELPER=weak
- php: 5.6
env: DEPENDENCIES=dev
allow_failures:
- php: 7.0
- php: hhvm
cache:
directories:
- $HOME/.composer/cache/files
before_install:
- composer self-update
- if [ "$DEPENDENCIES" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
install:
- composer update $COMPOSER_FLAGS
before_script:
- mkdir -p /tmp/jcalderonzumba/phantomjs
script:
- bin/run-tests.sh
after_script:
- ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
- ps axo pid,command | grep php | grep -v grep | awk '{print $1}' | xargs -I {} kill {}

View file

@ -0,0 +1,7 @@
CHANGELOG for 0.2.x
===================
This changelog references the relevant changes (bug and security fixes) done in 0.2 minor versions.
* 0.2.3
* bug #1 set_url_blacklist was not working properly (thanks to reporter)

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Juan Francisco Calderón Zumba
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,61 @@
Mink PhantomJS Driver
===========================
[![Build Status](https://travis-ci.org/jcalderonzumba/MinkPhantomJSDriver.svg?branch=master)](https://travis-ci.org/jcalderonzumba/MinkPhantomJSDriver)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jcalderonzumba/MinkPhantomJSDriver/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jcalderonzumba/MinkPhantomJSDriver/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/jcalderonzumba/mink-phantomjs-driver/v/stable)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
[![Total Downloads](https://poser.pugx.org/jcalderonzumba/mink-phantomjs-driver/downloads)](https://packagist.org/packages/jcalderonzumba/mink-phantomjs-driver)
Installation & Compatibility
----------------------------
You need a working installation of [PhantomJS](http://phantomjs.org/download.html)
This driver is tested using PhantomJS 1.9.8 but it should work with 1.9.X or latest 2.0.X versions
This driver supports **PHP 5.4 or greater**, there is NO support for PHP 5.3
Use [Composer](https://getcomposer.org/) to install all required PHP dependencies:
```bash
$ composer require --dev behat/mink jcalderonzumba/mink-phantomjs-driver
```
How to use
-------------
Extension configuration (for the moment NONE).
```yml
default:
extensions:
Zumba\PhantomJSExtension:
```
Driver specific configuration:
```yml
Behat\MinkExtension:
phantomjs:
phantom_server: "http://localhost:8510/api"
template_cache: "/tmp/pjsdrivercache/phantomjs"
```
PhantomJS browser start:
```bash
phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /tmp/gastonjs.log &
```
FAQ
---------
1. Is this a selenium based driver?:
**NO**, it has nothing to do with Selenium it's inspired on [Poltergeist](https://github.com/teampoltergeist/poltergeist)
2. What features does this driver implements?
**ALL** of the features defined in Mink DriverInterface. maximizeWindow is the only one not implemented since is a headless browser it does not make sense to implement it.
3. Do i need to modify my selenium based tests?
If you only use the standard behat driver defined methods then NO, you just have to change your default javascript driver.
Copyright
---------
Copyright (c) 2015 Juan Francisco Calderon Zumba <juanfcz@gmail.com>

View file

@ -0,0 +1,40 @@
#!/bin/sh
set -e
start_browser_api(){
CURRENT_DIR=$(pwd)
LOCAL_PHANTOMJS="${CURRENT_DIR}/bin/phantomjs"
if [ -f ${LOCAL_PHANTOMJS} ]; then
${LOCAL_PHANTOMJS} --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 &
else
phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /dev/null &
fi
sleep 2
}
stop_services(){
ps axo pid,command | grep phantomjs | grep -v grep | awk '{print $1}' | xargs -I {} kill {}
ps axo pid,command | grep php | grep -v grep | grep -v phpstorm | awk '{print $1}' | xargs -I {} kill {}
sleep 2
}
star_local_browser(){
CURRENT_DIR=$(pwd)
cd ${CURRENT_DIR}/vendor/behat/mink/driver-testsuite/web-fixtures
if [ "$TRAVIS" = true ]; then
echo "Starting webserver fox fixtures...."
~/.phpenv/versions/5.6/bin/php -S 127.0.0.1:6789 > /dev/null 2>&1 &
else
php -S 127.0.0.1:6789 2>&1 >> /dev/null &
fi
sleep 2
}
mkdir -p /tmp/jcalderonzumba/phantomjs
stop_services
start_browser_api
star_local_browser
cd ${CURRENT_DIR}
${CURRENT_DIR}/bin/phpunit --configuration integration_tests.xml
stop_services
start_browser_api

View file

@ -0,0 +1,53 @@
{
"name": "jcalderonzumba/mink-phantomjs-driver",
"description": "PhantomJS driver for Mink framework",
"keywords": [
"phantomjs",
"headless",
"javascript",
"ajax",
"testing",
"browser"
],
"homepage": "http://mink.behat.org/",
"type": "mink-driver",
"license": "MIT",
"authors": [
{
"name": "Juan Francisco Calderón Zumba",
"email": "juanfcz@gmail.com",
"homepage": "http://github.com/jcalderonzumba"
}
],
"require": {
"php": ">=5.4",
"behat/mink": "~1.6",
"twig/twig": "~1.8",
"jcalderonzumba/gastonjs": "~1.0"
},
"require-dev": {
"symfony/process": "~2.3",
"symfony/phpunit-bridge": "~2.7",
"symfony/css-selector": "~2.1",
"phpunit/phpunit": "~4.6",
"silex/silex": "~1.2"
},
"config": {
"bin-dir": "bin"
},
"autoload": {
"psr-4": {
"Zumba\\Mink\\Driver\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Behat\\Mink\\Tests\\Driver\\": "tests/integration"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.4.x-dev"
}
}
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="./tests/integration/bootstrap.php" stopOnFailure="true">
<testsuites>
<testsuite name="PhantomJS Driver test suite">
<directory>tests/integration</directory>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/BasicAuthTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/ContentTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/CookieTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/ErrorHandlingTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/IFrameTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/ScreenshotTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/TraversingTest.php</file>
<file>vendor/behat/mink/driver-testsuite/tests/Basic/VisibilityTest.php</file>
<directory>vendor/behat/mink/driver-testsuite/tests/Form</directory>
<directory>vendor/behat/mink/driver-testsuite/tests/Js</directory>
<!-- The following have been disabled and their respective equals added to Custom driver tests -->
<!--<directory>vendor/behat/mink/driver-testsuite/tests/Css</directory>-->
<!--<file>vendor/behat/mink/driver-testsuite/tests/Basic/StatusCodeTest.php</file>-->
<!--<file>vendor/behat/mink/driver-testsuite/tests/Basic/HeaderTest.php</file>-->
<!--<file>vendor/behat/mink/driver-testsuite/tests/Basic/NavigationTest.php</file>-->
</testsuite>
</testsuites>
<php>
<var name="driver_config_factory" value="Behat\Mink\Tests\Driver\PhantomJSConfig::getInstance"/>
<server name="WEB_FIXTURES_HOST" value="http://127.0.0.1:6789"/>
<!-- where driver will connect to -->
<server name="DRIVER_URL" value="http://127.0.0.1:8510/"/>
<server name="TEMPLATE_CACHE_DIR" value="/tmp/jcalderonzumba/phantomjs"/>
</php>
<filter>
<whitelist>
<directory>./src/Behat/Mink/Driver</directory>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,109 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Driver\CoreDriver;
use Behat\Mink\Exception\DriverException;
use Behat\Mink\Session;
use Zumba\GastonJS\Browser\Browser;
/**
* Class BasePhantomJSDriver
* @package Zumba\Mink\Driver
*/
class BasePhantomJSDriver extends CoreDriver {
/** @var Session */
protected $session;
/** @var Browser */
protected $browser;
/** @var string */
protected $phantomHost;
/** @var \Twig_Loader_Filesystem */
protected $templateLoader;
/** @var \Twig_Environment */
protected $templateEnv;
/**
* Instantiates the driver
* @param string $phantomHost browser "api" oriented host
* @param string $templateCache where we are going to store the templates cache
*/
public function __construct($phantomHost, $templateCache = null) {
$this->phantomHost = $phantomHost;
$this->browser = new Browser($phantomHost);
$this->templateLoader = new \Twig_Loader_Filesystem(realpath(__DIR__ . '/Resources/Script'));
$this->templateEnv = new \Twig_Environment($this->templateLoader, array('cache' => $this->templateCacheSetup($templateCache), 'strict_variables' => true));
}
/**
* Sets up the cache template location for the scripts we are going to create with the driver
* @param $templateCache
* @return string
* @throws DriverException
*/
protected function templateCacheSetup($templateCache) {
$cacheDir = $templateCache;
if ($templateCache === null) {
$cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "jcalderonzumba" . DIRECTORY_SEPARATOR . "phantomjs";
if (!file_exists($cacheDir)) {
mkdir($cacheDir, 0777, true);
}
}
if (!file_exists($cacheDir)) {
throw new DriverException("Template cache $cacheDir directory does not exist");
}
return $cacheDir;
}
/**
* Helper to find a node element given an xpath
* @param string $xpath
* @param int $max
* @returns int
* @throws DriverException
*/
protected function findElement($xpath, $max = 1) {
$elements = $this->browser->find("xpath", $xpath);
if (!isset($elements["page_id"]) || !isset($elements["ids"]) || count($elements["ids"]) !== $max) {
throw new DriverException("Failed to get elements with given $xpath");
}
return $elements;
}
/**
* {@inheritdoc}
* @param Session $session
*/
public function setSession(Session $session) {
$this->session = $session;
}
/**
* @return Browser
*/
public function getBrowser() {
return $this->browser;
}
/**
* @return \Twig_Environment
*/
public function getTemplateEnv() {
return $this->templateEnv;
}
/**
* Returns a javascript script via twig template engine
* @param $templateName
* @param $viewData
* @return string
*/
public function javascriptTemplateRender($templateName, $viewData) {
/** @var $templateEngine \Twig_Environment */
$templateEngine = $this->getTemplateEnv();
return $templateEngine->render($templateName, $viewData);
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Zumba\Mink\Driver;
use Zumba\GastonJS\Cookie;
/**
* Trait CookieTrait
* @package Zumba\Mink\Driver
*/
trait CookieTrait {
/**
* Sets a cookie on the browser, if null value then delete it
* @param string $name
* @param string $value
*/
public function setCookie($name, $value = null) {
if ($value === null) {
$this->browser->removeCookie($name);
}
//TODO: set the cookie with domain, not with url, meaning www.aaa.com or .aaa.com
if ($value !== null) {
$urlData = parse_url($this->getCurrentUrl());
$cookie = array("name" => $name, "value" => $value, "domain" => $urlData["host"]);
$this->browser->setCookie($cookie);
}
}
/**
* Gets a cookie by its name if exists, else it will return null
* @param string $name
* @return string
*/
public function getCookie($name) {
$cookies = $this->browser->cookies();
foreach ($cookies as $cookie) {
if ($cookie instanceof Cookie && strcmp($cookie->getName(), $name) === 0) {
return $cookie->getValue();
}
}
return null;
}
}

View file

@ -0,0 +1,168 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Trait FormManipulationTrait
* @package Zumba\Mink\Driver
*/
trait FormManipulationTrait {
/**
* Returns the value of a given xpath element
* @param string $xpath
* @return string
* @throws DriverException
*/
public function getValue($xpath) {
$this->findElement($xpath, 1);
$javascript = $this->javascriptTemplateRender("get_value.js.twig", array("xpath" => $xpath));
return $this->browser->evaluate($javascript);
}
/**
* @param string $xpath
* @param string $value
* @throws DriverException
*/
public function setValue($xpath, $value) {
$this->findElement($xpath, 1);
//This stuff is BECAUSE the way the driver works for setting values when being checkboxes, radios, etc.
if (is_bool($value)) {
$value = $this->boolToString($value);
}
$javascript = $this->javascriptTemplateRender("set_value.js.twig", array("xpath" => $xpath, "value" => json_encode($value)));
$this->browser->evaluate($javascript);
}
/**
* Submits a form given an xpath selector
* @param string $xpath
* @throws DriverException
*/
public function submitForm($xpath) {
$element = $this->findElement($xpath, 1);
$tagName = $this->browser->tagName($element["page_id"], $element["ids"][0]);
if (strcmp(strtolower($tagName), "form") !== 0) {
throw new DriverException("Can not submit something that is not a form");
}
$this->browser->trigger($element["page_id"], $element["ids"][0], "submit");
}
/**
* Helper method needed for twig and javascript stuff
* @param $boolValue
* @return string
*/
protected function boolToString($boolValue) {
if ($boolValue === true) {
return "1";
}
return "0";
}
/**
* Selects an option
* @param string $xpath
* @param string $value
* @param bool $multiple
* @return bool
* @throws DriverException
*/
public function selectOption($xpath, $value, $multiple = false) {
$element = $this->findElement($xpath, 1);
$tagName = strtolower($this->browser->tagName($element["page_id"], $element["ids"][0]));
$attributes = $this->browser->attributes($element["page_id"], $element["ids"][0]);
if (!in_array($tagName, array("input", "select"))) {
throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath));
}
if ($tagName === "input" && $attributes["type"] != "radio") {
throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath));
}
return $this->browser->selectOption($element["page_id"], $element["ids"][0], $value, $multiple);
}
/**
* Check control over an input element of radio or checkbox type
* @param $xpath
* @return bool
* @throws DriverException
*/
protected function inputCheckableControl($xpath) {
$element = $this->findElement($xpath, 1);
$tagName = strtolower($this->browser->tagName($element["page_id"], $element["ids"][0]));
$attributes = $this->browser->attributes($element["page_id"], $element["ids"][0]);
if ($tagName != "input") {
throw new DriverException("Can not check when the element is not of the input type");
}
if (!in_array($attributes["type"], array("checkbox", "radio"))) {
throw new DriverException("Can not check when the element is not checkbox or radio");
}
return true;
}
/**
* We click on the checkbox or radio when possible and needed
* @param string $xpath
* @throws DriverException
*/
public function check($xpath) {
$this->inputCheckableControl($xpath);
$javascript = $this->javascriptTemplateRender("check_element.js.twig", array("xpath" => $xpath, "check" => "true"));
$this->browser->evaluate($javascript);
}
/**
* We click on the checkbox or radio when possible and needed
* @param string $xpath
* @throws DriverException
*/
public function uncheck($xpath) {
$this->inputCheckableControl($xpath);
$javascript = $this->javascriptTemplateRender("check_element.js.twig", array("xpath" => $xpath, "check" => "false"));
$this->browser->evaluate($javascript);
}
/**
* Checks if the radio or checkbox is checked
* @param string $xpath
* @return bool
* @throws DriverException
*/
public function isChecked($xpath) {
$this->findElement($xpath, 1);
$javascript = $this->javascriptTemplateRender("is_checked.js.twig", array("xpath" => $xpath));
$checked = $this->browser->evaluate($javascript);
if ($checked === null) {
throw new DriverException("Can not check when the element is not checkbox or radio");
}
return $checked;
}
/**
* Checks if the option is selected or not
* @param string $xpath
* @return bool
* @throws DriverException
*/
public function isSelected($xpath) {
$elements = $this->findElement($xpath, 1);
$javascript = $this->javascriptTemplateRender("is_selected.js.twig", array("xpath" => $xpath));
$tagName = $this->browser->tagName($elements["page_id"], $elements["ids"][0]);
if (strcmp(strtolower($tagName), "option") !== 0) {
throw new DriverException("Can not assert on element that is not an option");
}
return $this->browser->evaluate($javascript);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Zumba\Mink\Driver;
/**
* Class HeadersTrait
* @package Zumba\Mink\Driver
*/
trait HeadersTrait {
/**
* Gets the current request response headers
* Should be called only after a request, other calls are undefined behaviour
* @return array
*/
public function getResponseHeaders() {
return $this->browser->responseHeaders();
}
/**
* Current request status code response
* @return int
*/
public function getStatusCode() {
return $this->browser->getStatusCode();
}
/**
* The name say its all
* @param string $name
* @param string $value
*/
public function setRequestHeader($name, $value) {
$header = array();
$header[$name] = $value;
//TODO: as a limitation of the driver it self, we will send permanent for the moment
$this->browser->addHeader($header, true);
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Class JavascriptTrait
* @package Zumba\Mink\Driver
*/
trait JavascriptTrait {
/**
* Executes a script on the browser
* @param string $script
*/
public function executeScript($script) {
$this->browser->execute($script);
}
/**
* Evaluates a script and returns the result
* @param string $script
* @return mixed
*/
public function evaluateScript($script) {
return $this->browser->evaluate($script);
}
/**
* Waits some time or until JS condition turns true.
*
* @param integer $timeout timeout in milliseconds
* @param string $condition JS condition
* @return boolean
* @throws DriverException When the operation cannot be done
*/
public function wait($timeout, $condition) {
$start = microtime(true);
$end = $start + $timeout / 1000.0;
do {
$result = $this->browser->evaluate($condition);
usleep(100000);
} while (microtime(true) < $end && !$result);
return (bool)$result;
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Class KeyboardTrait
* @package Zumba\Mink\Driver
*/
trait KeyboardTrait {
/**
* Does some normalization for the char we want to do keyboard events with.
* @param $char
* @throws DriverException
* @return string
*/
protected function normalizeCharForKeyEvent($char) {
if (!is_int($char) && !is_string($char)) {
throw new DriverException("Unsupported key type, can only be integer or string");
}
if (is_string($char) && strlen($char) !== 1) {
throw new DriverException("Key can only have ONE character");
}
$key = $char;
if (is_int($char)) {
$key = chr($char);
}
return $key;
}
/**
* Does some control and normalization for the key event modifier
* @param $modifier
* @return string
* @throws DriverException
*/
protected function keyEventModifierControl($modifier) {
if ($modifier === null) {
$modifier = "none";
}
if (!in_array($modifier, array("none", "alt", "ctrl", "shift", "meta"))) {
throw new DriverException("Unsupported key modifier $modifier");
}
return $modifier;
}
/**
* Send a key-down event to the browser element
* @param $xpath
* @param $char
* @param string $modifier
* @throws DriverException
*/
public function keyDown($xpath, $char, $modifier = null) {
$element = $this->findElement($xpath, 1);
$key = $this->normalizeCharForKeyEvent($char);
$modifier = $this->keyEventModifierControl($modifier);
return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keydown", $key, $modifier);
}
/**
* @param string $xpath
* @param string $char
* @param string $modifier
* @throws DriverException
*/
public function keyPress($xpath, $char, $modifier = null) {
$element = $this->findElement($xpath, 1);
$key = $this->normalizeCharForKeyEvent($char);
$modifier = $this->keyEventModifierControl($modifier);
return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keypress", $key, $modifier);
}
/**
* Pressed up specific keyboard key.
*
* @param string $xpath
* @param string|integer $char could be either char ('b') or char-code (98)
* @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
*
* @throws DriverException When the operation cannot be done
*/
public function keyUp($xpath, $char, $modifier = null) {
$this->findElement($xpath, 1);
$element = $this->findElement($xpath, 1);
$key = $this->normalizeCharForKeyEvent($char);
$modifier = $this->keyEventModifierControl($modifier);
return $this->browser->keyEvent($element["page_id"], $element["ids"][0], "keyup", $key, $modifier);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Class MouseTrait
* @package Zumba\Mink\Driver
*/
trait MouseTrait {
/**
* Generates a mouseover event on the given element by xpath
* @param string $xpath
* @throws DriverException
*/
public function mouseOver($xpath) {
$element = $this->findElement($xpath, 1);
$this->browser->hover($element["page_id"], $element["ids"][0]);
}
/**
* Clicks if possible on an element given by xpath
* @param string $xpath
* @return mixed
* @throws DriverException
*/
public function click($xpath) {
$elements = $this->findElement($xpath, 1);
$this->browser->click($elements["page_id"], $elements["ids"][0]);
}
/**
* {@inheritdoc}
*/
/**
* Double click on element found via xpath
* @param string $xpath
* @throws DriverException
*/
public function doubleClick($xpath) {
$elements = $this->findElement($xpath, 1);
$this->browser->doubleClick($elements["page_id"], $elements["ids"][0]);
}
/**
* Right click on element found via xpath
* @param string $xpath
* @throws DriverException
*/
public function rightClick($xpath) {
$elements = $this->findElement($xpath, 1);
$this->browser->rightClick($elements["page_id"], $elements["ids"][0]);
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Zumba\Mink\Driver;
/**
* Trait NavigationTrait
* @package Zumba\Mink\Driver
*/
trait NavigationTrait {
/**
* Visits a given url
* @param string $url
*/
public function visit($url) {
$this->browser->visit($url);
}
/**
* Gets the current url if any
* @return string
*/
public function getCurrentUrl() {
return $this->browser->currentUrl();
}
/**
* Reloads the page if possible
*/
public function reload() {
$this->browser->reload();
}
/**
* Goes forward if possible
*/
public function forward() {
$this->browser->goForward();
}
/**
* Goes back if possible
*/
public function back() {
$this->browser->goBack();
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Class PageContentTrait
* @package Zumba\Mink\Driver
*/
trait PageContentTrait {
/**
* @return string
*/
public function getContent() {
return $this->browser->getBody();
}
/**
* Given xpath, will try to get ALL the text, visible and not visible from such xpath
* @param string $xpath
* @return string
* @throws DriverException
*/
public function getText($xpath) {
$elements = $this->findElement($xpath, 1);
//allText works only with ONE element so it will be the first one and also returns new lines that we will remove
$text = $this->browser->allText($elements["page_id"], $elements["ids"][0]);
$text = trim(str_replace(array("\r", "\r\n", "\n"), ' ', $text));
$text = preg_replace('/ {2,}/', ' ', $text);
return $text;
}
/**
* Returns the inner html of a given xpath
* @param string $xpath
* @return string
* @throws DriverException
*/
public function getHtml($xpath) {
$elements = $this->findElement($xpath, 1);
//allText works only with ONE element so it will be the first one
return $this->browser->allHtml($elements["page_id"], $elements["ids"][0], "inner");
}
/**
* Gets the outer html of a given xpath
* @param string $xpath
* @return string
* @throws DriverException
*/
public function getOuterHtml($xpath) {
$elements = $this->findElement($xpath, 1);
//allText works only with ONE element so it will be the first one
return $this->browser->allHtml($elements["page_id"], $elements["ids"][0], "outer");
}
/**
* Returns the binary representation of the current page we are in
* @throws DriverException
* @return string
*/
public function getScreenshot() {
$options = array("full" => true, "selector" => null);
$b64ScreenShot = $this->browser->renderBase64("JPEG", $options);
if (($binaryScreenShot = base64_decode($b64ScreenShot, true)) === false) {
throw new DriverException("There was a problem while doing the screenshot of the current page");
}
return $binaryScreenShot;
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\DriverException;
/**
* Class PhantomJSDriver
* @package Behat\Mink\Driver
*/
class PhantomJSDriver extends BasePhantomJSDriver {
use SessionTrait;
use NavigationTrait;
use CookieTrait;
use HeadersTrait;
use JavascriptTrait;
use MouseTrait;
use PageContentTrait;
use KeyboardTrait;
use FormManipulationTrait;
use WindowTrait;
/**
* Sets the basic auth user and password
* @param string $user
* @param string $password
*/
public function setBasicAuth($user, $password) {
$this->browser->setHttpAuth($user, $password);
}
/**
* Gets the tag name of a given xpath
* @param string $xpath
* @return string
* @throws DriverException
*/
public function getTagName($xpath) {
$elements = $this->findElement($xpath, 1);
return $this->browser->tagName($elements["page_id"], $elements["ids"][0]);
}
/**
* Gets the attribute value of a given element and name
* @param string $xpath
* @param string $name
* @return string
* @throws DriverException
*/
public function getAttribute($xpath, $name) {
$elements = $this->findElement($xpath, 1);
return $this->browser->attribute($elements["page_id"], $elements["ids"][0], $name);
}
/**
* Check if element given by xpath is visible or not
* @param string $xpath
* @return bool
* @throws DriverException
*/
public function isVisible($xpath) {
$elements = $this->findElement($xpath, 1);
return $this->browser->isVisible($elements["page_id"], $elements["ids"][0]);
}
/**
* Drags one element to another
* @param string $sourceXpath
* @param string $destinationXpath
* @throws DriverException
*/
public function dragTo($sourceXpath, $destinationXpath) {
$sourceElement = $this->findElement($sourceXpath, 1);
$destinationElement = $this->findElement($destinationXpath, 1);
$this->browser->drag($sourceElement["page_id"], $sourceElement["ids"][0], $destinationElement["ids"][0]);
}
/**
* Upload a file to the browser
* @param string $xpath
* @param string $path
* @throws DriverException
*/
public function attachFile($xpath, $path) {
if (!file_exists($path)) {
throw new DriverException("Wow there the file does not exist, you can not upload it");
}
if (($realPath = realpath($path)) === false) {
throw new DriverException("Wow there the file does not exist, you can not upload it");
}
$element = $this->findElement($xpath, 1);
$tagName = $this->getTagName($xpath);
if ($tagName != "input") {
throw new DriverException("The element is not an input element, you can not attach a file to it");
}
$attributes = $this->getBrowser()->attributes($element["page_id"], $element["ids"][0]);
if (!isset($attributes["type"]) || $attributes["type"] != "file") {
throw new DriverException("The element is not an input file type element, you can not attach a file to it");
}
$this->browser->selectFile($element["page_id"], $element["ids"][0], $realPath);
}
/**
* Puts the browser control inside the IFRAME
* You own the control, make sure to go back to the parent calling this method with null
* @param string $name
*/
public function switchToIFrame($name = null) {
//TODO: check response of the calls
if ($name === null) {
$this->browser->popFrame();
return;
} else {
$this->browser->pushFrame($name);
}
}
/**
* Focus on an element
* @param string $xpath
* @throws DriverException
*/
public function focus($xpath) {
$element = $this->findElement($xpath, 1);
$this->browser->trigger($element["page_id"], $element["ids"][0], "focus");
}
/**
* Blur on element
* @param string $xpath
* @throws DriverException
*/
public function blur($xpath) {
$element = $this->findElement($xpath, 1);
$this->browser->trigger($element["page_id"], $element["ids"][0], "blur");
}
/**
* Finds elements with specified XPath query.
* @param string $xpath
* @return NodeElement[]
* @throws DriverException When the operation cannot be done
*/
public function find($xpath) {
$elements = $this->browser->find("xpath", $xpath);
$nodeElements = array();
if (!isset($elements["ids"])) {
return null;
}
foreach ($elements["ids"] as $i => $elementId) {
$nodeElements[] = new NodeElement(sprintf('(%s)[%d]', $xpath, $i + 1), $this->session);
}
return $nodeElements;
}
}

View file

@ -0,0 +1,35 @@
{% autoescape 'js' %}
(function (xpath, check) {
function getPolterNode(xpath) {
var polterAgent = window.__poltergeist;
var ids = polterAgent.find("xpath", xpath, document);
return polterAgent.get(ids[0]);
}
var pNode = getPolterNode(xpath);
if (check && pNode.element.checked) {
//requested to check the element and is already check, do nothing.
return true;
}
if (!check && pNode.element.checked == false) {
//move along nothing to be done
return true;
}
if (check && pNode.element.checked == false) {
//we have to check the element, we will do so by triggering a click event so all change listeners are aware.
pNode.trigger("click");
pNode.element.checked = true;
}
if (!check && pNode.element.checked) {
//move along nothing to be done
pNode.trigger("click");
pNode.element.checked = false;
return true;
}
return false;
}('{{xpath}}', {{check}}));
{% endautoescape %}

View file

@ -0,0 +1,3 @@
{% autoescape false %}
{{ script }};
{% endautoescape %}

View file

@ -0,0 +1,63 @@
{% autoescape 'js' %}
(function (xpath) {
function getElement(xpath) {
var polterAgent = window.__poltergeist;
var ids = polterAgent.find("xpath", xpath, document);
var polterNode = polterAgent.get(ids[0]);
return polterNode.element;
}
function inputRadioGetValue(element){
var value = null;
var name = element.getAttribute('name');
if (!name){
return null;
}
var fields = window.document.getElementsByName(name);
var i;
var l = fields.length;
for (i = 0; i < l; i++) {
var field = fields.item(i);
if (field.form === element.form && field.checked) {
return field.value;
}
}
return null;
}
var node = getElement(xpath);
var tagName = node.tagName.toLowerCase();
var value = null;
if (tagName == "input") {
var type = node.type.toLowerCase();
if (type == "checkbox") {
value = node.checked ? node.value : null;
} else if (type == "radio") {
value = inputRadioGetValue(node);
} else {
value = node.value;
}
} else if (tagName == "textarea") {
value = node.value;
} else if (tagName == "select") {
if (node.multiple) {
value = [];
for (var i = 0; i < node.options.length; i++) {
if (node.options[i].selected) {
value.push(node.options[i].value);
}
}
} else {
var idx = node.selectedIndex;
if (idx >= 0) {
value = node.options.item(idx).value;
} else {
value = null;
}
}
} else {
value = node.value;
}
return value;
}('{{ xpath }}'));
{% endautoescape %}

View file

@ -0,0 +1,31 @@
{% autoescape 'js' %}
(function (xpath) {
function getElement(xpath, within) {
var result;
if (within === null || within === undefined) {
within = document;
}
result = document.evaluate(xpath, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (result.snapshotLength !== 1) {
return null;
}
return result.snapshotItem(0);
}
var node = getElement(xpath);
if (node === null) {
return null;
}
if(node.tagName.toLowerCase() != "input"){
return null;
}
if(node.type.toLowerCase() != "checkbox" && node.type.toLowerCase() != "radio"){
return null;
}
return node.checked;
}('{{ xpath }}'));
{% endautoescape %}

View file

@ -0,0 +1,16 @@
{% autoescape 'js' %}
(function (xpath) {
function getElement(xpath) {
var polterAgent = window.__poltergeist;
var ids = polterAgent.find("xpath", xpath, document);
var polterNode = polterAgent.get(ids[0]);
return polterNode.element;
}
var node = getElement(xpath);
if(typeof node.selected == "undefined"){
return null;
}
return node.selected;
}('{{xpath}}'));
{% endautoescape %}

View file

@ -0,0 +1,213 @@
{% autoescape 'js' %}
(function (xpath, value) {
function getElement(xpath, within) {
var result;
if (within === null || within === undefined) {
within = document;
}
result = document.evaluate(xpath, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (result.snapshotLength !== 1) {
return null;
}
return result.snapshotItem(0);
}
function isInput(element) {
if (element === null || element === undefined) {
return false;
}
return (element.tagName.toLowerCase() == "input");
}
function isTextArea(element) {
if (element === null || element === undefined) {
return false;
}
return (element.tagName.toLowerCase() == "textarea");
}
function isSelect(element) {
if (element === null || element === undefined) {
return false;
}
return (element.tagName.toLowerCase() == "select");
}
function deselectAllOptions(element) {
var i, l = element.options.length;
for (i = 0; i < l; i++) {
element.options[i].selected = false;
}
}
function xpathStringLiteral(s) {
if (s.indexOf('"') === -1)
return '"' + s + '"';
if (s.indexOf("'") === -1)
return "'" + s + "'";
return 'concat("' + s.replace(/"/g, '",\'"\',"') + '")';
}
function clickOnElement(element) {
// create a mouse click event
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
// send click to element
element.dispatchEvent(event);
//After dispatching the event let's wait for 2 seconds at least...
return setTimeout(function () {
}, 2);
}
function dispatchChange(element) {
var tagName =element.tagName.toLowerCase();
var elementType = element.getAttribute("type");
if (tagName != "option" || (tagName == "input" && elementType == "radio")){
return true;
}
//Force the change when element is option
var event;
event = document.createEvent('HTMLEvents');
event.initEvent('change', true, false);
element.dispatchEvent(event);
return true;
}
function selectOptionOnElement(element, option, multiple) {
var polterAgent = window.__poltergeist;
var escapedOption = xpathStringLiteral(option);
// The value of an option is the normalized version of its text when it has no value attribute
var optionQuery = ".//option[@value = " + escapedOption + " or (not(@value) and normalize-space(.) = " + escapedOption + ")]";
var ids = polterAgent.find("xpath", optionQuery, element);
var polterNode = polterAgent.get(ids[0]);
var optionElement = polterNode.element;
if (multiple || !element.multiple) {
if (!optionElement.selected) {
clickOnElement(optionElement);
optionElement.selected = true;
}
return dispatchChange(optionElement);
}
deselectAllOptions(element);
clickOnElement(optionElement);
optionElement.selected = true;
return dispatchChange(optionElement);
}
function selectSetValue(element, value) {
var option;
if ((Array.isArray && Array.isArray(value)) || (value instanceof Array)) {
deselectAllOptions(element);
for (option in value) {
if (value.hasOwnProperty(option)) {
selectOptionOnElement(element, value[option], true);
}
}
return true;
}
selectOptionOnElement(element, value, false);
return true;
}
function selectRadioValue(element, value) {
if (element.value === value) {
clickOnElement(element);
element.checked=true;
dispatchChange(element);
return true;
}
var formElements = element.form.elements;
var name = element.getAttribute("name");
var radioElement, i;
if (!name) {
return null;
}
for (i = 0; i < formElements.length; i++) {
radioElement = formElements[i];
if (radioElement.tagName.toLowerCase() == 'input' && radioElement.type.toLowerCase() == 'radio' && radioElement.name === name) {
if (value === radioElement.value) {
clickOnElement(radioElement);
radioElement.checked=true;
dispatchChange(radioElement);
return true;
}
}
}
return null;
}
function inputSetValue(element, value, elementXpath) {
var allowedTypes = ['submit', 'image', 'button', 'reset'];
var elementType = element.type.toLowerCase();
var textLikeInputType = ['file', 'text', 'password', 'url', 'email', 'search', 'number', 'tel', 'range', 'date', 'month', 'week', 'time', 'datetime', 'color', 'datetime-local'];
if (allowedTypes.indexOf(elementType) !== -1) {
return null;
}
if (elementType == "checkbox") {
var booleanValue = false;
if (value == "1" || value == 1) {
booleanValue = true;
} else if (value == "0" || value == 0) {
booleanValue = false;
}
if ((element.checked && !booleanValue) || (!element.checked && booleanValue)) {
clickOnElement(element);
dispatchChange(element);
}
return true;
}
if (elementType == "radio") {
return selectRadioValue(element, value);
}
if (textLikeInputType.indexOf(elementType) !== -1) {
return textAreaSetValue(elementXpath, value);
}
//No support for the moment for file stuff or other input types
return null;
}
function textAreaSetValue(elementXpath, value) {
var polterAgent = window.__poltergeist;
var ids = polterAgent.find("xpath", elementXpath, document);
var polterNode = polterAgent.get(ids[0]);
polterNode.set(value);
return true;
}
var node = getElement(xpath);
if (node === null) {
return null;
}
if (isSelect(node)) {
return selectSetValue(node, value);
}
if (isInput(node)) {
return inputSetValue(node, value, xpath);
}
if (isTextArea(node)) {
return textAreaSetValue(xpath, value);
}
//for the moment everything else also to textArea stuff
return textAreaSetValue(xpath, value);
}('{{xpath}}', JSON.parse('{{ value }}')));
{% endautoescape %}

View file

@ -0,0 +1,50 @@
<?php
namespace Zumba\Mink\Driver;
/**
* Trait SessionTrait
* @package Zumba\Mink\Driver
*/
trait SessionTrait {
/** @var bool */
protected $started;
/**
* Starts a session to be used by the driver client
*/
public function start() {
$this->started = true;
}
/**
* Tells if the session is started or not
* @return bool
*/
public function isStarted() {
return $this->started;
}
/**
* Stops the session completely, clean slate for the browser
* @return bool
*/
public function stop() {
//Since we are using a remote browser "API", stopping is just like resetting, say good bye to cookies
//TODO: In the future we may want to control a start / stop of the remove browser
return $this->reset();
}
/**
* Clears the cookies in the browser, all of them
* @return bool
*/
public function reset() {
$this->getBrowser()->clearCookies();
$this->getBrowser()->reset();
$this->started = false;
return true;
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Zumba\Mink\Driver;
use Behat\Mink\Exception\DriverException;
/**
* Class WindowTrait
* @package Zumba\Mink\Driver
*/
trait WindowTrait {
/**
* Returns the current page window name
* @return string
*/
public function getWindowName() {
return $this->browser->windowName();
}
/**
* Return all the window handles currently present in phantomjs
* @return array
*/
public function getWindowNames() {
return $this->browser->windowHandles();
}
/**
* Switches to window by name if possible
* @param $name
* @throws DriverException
*/
public function switchToWindow($name = null) {
$handles = $this->browser->windowHandles();
if ($name === null) {
//null means back to the main window
return $this->browser->switchToWindow($handles[0]);
}
$windowHandle = $this->browser->windowHandle($name);
if (!empty($windowHandle)) {
$this->browser->switchToWindow($windowHandle);
} else {
throw new DriverException("Could not find window handle by a given window name: $name");
}
}
/**
* Resizing a window with specified size
* @param int $width
* @param int $height
* @param string $name
* @throws DriverException
*/
public function resizeWindow($width, $height, $name = null) {
if ($name !== null) {
//TODO: add this on the phantomjs stuff
throw new DriverException("Resizing other window than the main one is not supported yet");
}
$this->browser->resize($width, $height);
}
}